/* load with libMagick7
 *
 * 8/7/16
 * 	- from magickload
 * 25/11/16
 * 	- add @n, deprecate @all_frames (just sets n = -1)
 * 24/7/18
 * 	- sniff extra filetypes
 * 4/2/19
 * 	- add profile (xmp, ipct, etc.) read
 * 12/11/21
 * 	- set "orientation"
 * 22/3/23 MathemanFlo
 * 	- add bits per sample metadata
 */

/*

	This file is part of VIPS.

	VIPS is free software; you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <vips/vips.h>
#include <vips/buf.h>
#include <vips/internal.h>

#ifdef ENABLE_MAGICKLOAD

#ifdef HAVE_MAGICK7

#include <MagickCore/MagickCore.h>

#include "magick.h"

typedef struct _VipsForeignLoadMagick7 {
	VipsForeignLoad parent_object;

	/* Deprecated. Just sets n = -1.
	 */
	gboolean all_frames;

	char *density; /* Load at this resolution */
	int page;	   /* Load this page (frame) */
	int n;		   /* Load this many pages */

	Image *image;
	ImageInfo *image_info;
	ExceptionInfo *exception;

	/* Number of pages in image.
	 */
	int n_pages;

	int n_frames;			/* Number of frames we will read */
	Image **frames;			/* An Image* for each frame */
	CacheView **cache_view; /* A CacheView for each frame */
	int frame_height;

	/* Mutex to serialise calls to libMagick during threaded read.
	 */
	GMutex lock;

} VipsForeignLoadMagick7;

typedef VipsForeignLoadClass VipsForeignLoadMagick7Class;

G_DEFINE_ABSTRACT_TYPE(VipsForeignLoadMagick7, vips_foreign_load_magick7,
	VIPS_TYPE_FOREIGN_LOAD);

#ifdef DEBUG
static void
vips_magick7_print_traits(Image *image)
{
	static const int trait_bits[] = {
		CopyPixelTrait,
		UpdatePixelTrait,
		BlendPixelTrait
	};
	static const char *trait_names[] = {
		"CopyPixelTrait",
		"UpdatePixelTrait",
		"BlendPixelTrait"
	};

	int b;
	int i;

	printf("vips_magick7_print_traits: channel traits:\n");
	for (b = 0; b < GetPixelChannels(image); b++) {
		PixelChannel channel =
			GetPixelChannelChannel(image, b);
		PixelTrait traits =
			GetPixelChannelTraits(image, channel);

		printf("\t%d) ", b);
		for (i = 0; i < VIPS_NUMBER(trait_bits); i++)
			if (traits & trait_bits[i])
				printf("%s ", trait_names[i]);
		if (traits == 0)
			printf("undefined");
		printf("\n");
	}
}

static void
vips_magick7_print_channel_names(Image *image)
{
	static const int pixel_channels[] = {
		UndefinedPixelChannel,
		RedPixelChannel,
		CyanPixelChannel,
		GrayPixelChannel,
		LPixelChannel,
		LabelPixelChannel,
		YPixelChannel,
		aPixelChannel,
		GreenPixelChannel,
		MagentaPixelChannel,
		CbPixelChannel,
		bPixelChannel,
		BluePixelChannel,
		YellowPixelChannel,
		CrPixelChannel,
		BlackPixelChannel,
		AlphaPixelChannel,
		IndexPixelChannel,
		ReadMaskPixelChannel,
		WriteMaskPixelChannel,
		MetaPixelChannel,
		IntensityPixelChannel,
		CompositePixelChannel,
		SyncPixelChannel
	};
	static const char *pixel_channel_names[] = {
		"UndefinedPixelChannel",
		"RedPixelChannel",
		"CyanPixelChannel",
		"GrayPixelChannel",
		"LPixelChannel",
		"LabelPixelChannel",
		"YPixelChannel",
		"aPixelChannel",
		"GreenPixelChannel",
		"MagentaPixelChannel",
		"CbPixelChannel",
		"bPixelChannel",
		"BluePixelChannel",
		"YellowPixelChannel",
		"CrPixelChannel",
		"BlackPixelChannel",
		"AlphaPixelChannel",
		"IndexPixelChannel",
		"ReadMaskPixelChannel",
		"WriteMaskPixelChannel",
		"MetaPixelChannel",
		"IntensityPixelChannel",
		"CompositePixelChannel",
		"SyncPixelChannel",
	};

	int b;
	int i;

	printf("vips_magick7_print_channel_names: channel names:\n");
	for (b = 0; b < GetPixelChannels(image); b++) {
		PixelChannel channel =
			GetPixelChannelChannel(image, b);

		printf("\t%d) ", b);
		for (i = 0; i < VIPS_NUMBER(pixel_channels); i++)
			/* Don't break on found, many channel names repeat.
			 */
			if (channel == pixel_channels[i])
				printf("%s ", pixel_channel_names[i]);
		printf("\n");
	}
}

static void
vips_magick7_print_image_type(Image *image)
{
	static const int image_types[] = {
		UndefinedType,
		BilevelType,
		GrayscaleType,
		GrayscaleAlphaType,
		PaletteType,
		PaletteAlphaType,
		TrueColorType,
		TrueColorAlphaType,
		ColorSeparationType,
		ColorSeparationAlphaType,
		OptimizeType,
		PaletteBilevelAlphaType
	};

	static const char *image_type_names[] = {
		"UndefinedType",
		"BilevelType",
		"GrayscaleType",
		"GrayscaleAlphaType",
		"PaletteType",
		"PaletteAlphaType",
		"TrueColorType",
		"TrueColorAlphaType",
		"ColorSeparationType",
		"ColorSeparationAlphaType",
		"OptimizeType",
		"PaletteBilevelAlphaType"
	};

	int i;

	for (i = 0; i < VIPS_NUMBER(image_types); i++)
		if (GetImageType(image) == image_types[i]) {
			printf("\t%s\n", image_type_names[i]);
			break;
		}
	if (i == VIPS_NUMBER(image_types))
		printf("\tunknown GetImageType()\n");
}
#endif /*DEBUG*/

static VipsForeignFlags
vips_foreign_load_magick7_get_flags_filename(const char *filename)
{
	return VIPS_FOREIGN_PARTIAL;
}

static VipsForeignFlags
vips_foreign_load_magick7_get_flags(VipsForeignLoad *load)
{
	return VIPS_FOREIGN_PARTIAL;
}

static void
vips_foreign_load_magick7_finalize(GObject *gobject)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) gobject;

	int i;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_finalize: %p\n", gobject);
#endif /*DEBUG*/

	for (i = 0; i < magick7->n_frames; i++) {
		VIPS_FREEF(DestroyCacheView, magick7->cache_view[i]);
	}
	VIPS_FREEF(DestroyImageList, magick7->image);
	VIPS_FREEF(DestroyImageInfo, magick7->image_info);
	VIPS_FREE(magick7->frames);
	VIPS_FREE(magick7->cache_view);
	VIPS_FREEF(magick_destroy_exception, magick7->exception);
	g_mutex_clear(&magick7->lock);

	G_OBJECT_CLASS(vips_foreign_load_magick7_parent_class)->finalize(gobject);
}

static int
vips_foreign_load_magick7_build(VipsObject *object)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) object;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_build: %p\n", object);
#endif /*DEBUG*/

	magick_genesis();

	magick7->image_info = CloneImageInfo(NULL);
	magick7->exception = magick_acquire_exception();

	if (!magick7->image_info)
		return -1;

	if (magick7->all_frames)
		magick7->n = -1;

	/* Canvas resolution for rendering vector formats like SVG.
	 */
	VIPS_SETSTR(magick7->image_info->density, magick7->density);

	/* When reading DICOM images, we want to ignore any
	 * window_center/_width setting, since it may put pixels outside the
	 * 0-65535 range and lose data.
	 *
	 * These window settings are attached as vips metadata, so our caller
	 * can interpret them if it wants.
	 */
	magick_set_image_option(magick7->image_info,
		"dcm:display-range", "reset");

	if (magick7->page > 0)
		magick_set_number_scenes(magick7->image_info,
			magick7->page, magick7->n);

	return VIPS_OBJECT_CLASS(vips_foreign_load_magick7_parent_class)
		->build(object);
}

static void
vips_foreign_load_magick7_class_init(VipsForeignLoadMagick7Class *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsOperationClass *operation_class = VIPS_OPERATION_CLASS(class);
	VipsForeignClass *foreign_class = (VipsForeignClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->finalize = vips_foreign_load_magick7_finalize;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "magickload_base";
	object_class->description = _("load with ImageMagick7");
	object_class->build = vips_foreign_load_magick7_build;

	/* Don't cache magickload: it can gobble up memory and disc.
	 */
	operation_class->flags |= VIPS_OPERATION_NOCACHE;

	/* *magick is fuzzed, but it's such a huge thing it's safer to
	 * disable it.
	 */
	operation_class->flags |= VIPS_OPERATION_UNTRUSTED;

	/* We need to be well to the back of the queue since vips's
	 * dedicated loaders are usually preferable.
	 */
	foreign_class->priority = -100;

	load_class->get_flags_filename =
		vips_foreign_load_magick7_get_flags_filename;
	load_class->get_flags = vips_foreign_load_magick7_get_flags;

	VIPS_ARG_STRING(class, "density", 20,
		_("Density"),
		_("Canvas resolution for rendering vector formats like SVG"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7, density),
		NULL);

	VIPS_ARG_INT(class, "page", 21,
		_("Page"),
		_("First page to load"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7, page),
		0, 100000, 0);

	VIPS_ARG_INT(class, "n", 22,
		_("n"),
		_("Number of pages to load, -1 for all"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7, n),
		-1, 100000, 1);

	VIPS_ARG_BOOL(class, "all_frames", 23,
		_("All frames"),
		_("Read all frames from an image"),
		VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7, all_frames),
		FALSE);
}

static void
vips_foreign_load_magick7_init(VipsForeignLoadMagick7 *magick7)
{
	magick7->n = 1;
	g_mutex_init(&magick7->lock);
}

static void
vips_foreign_load_magick7_error(VipsForeignLoadMagick7 *magick7)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(magick7);

	vips_error(class->nickname, _("Magick: %s %s"),
		magick7->exception->reason,
		magick7->exception->description);
}

static int
magick7_get_bands(Image *image)
{
	int bands;
	int i;

	/* We skip all index channels. Lots of images can have these, it's not
	 * just the palette ones.
	 */
	bands = 0;
	for (i = 0; i < GetPixelChannels(image); i++) {
		PixelChannel channel = GetPixelChannelChannel(image, i);

		if (channel != IndexPixelChannel)
			bands += 1;
	}

	return bands;
}

static int
vips_foreign_load_magick7_parse(VipsForeignLoadMagick7 *magick7,
	Image *image, VipsImage *out)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(magick7);

	const char *key;
	Image *p;

#ifdef DEBUG
	printf("image->depth = %zd\n", image->depth);
	printf("GetImageType() = %d\n", GetImageType(image));
	vips_magick7_print_image_type(image);
	printf("GetPixelChannels() = %zd\n", GetPixelChannels(image));
	printf("image->columns = %zd\n", image->columns);
	printf("image->rows = %zd\n", image->rows);
#endif /*DEBUG*/

	/* Ysize updated again below once we have worked out how many frames to
	 * load.
	 */
	out->Xsize = image->columns;
	out->Ysize = image->rows;
	out->Bands = magick7_get_bands(image);
	if (out->Xsize <= 0 ||
		out->Ysize <= 0 ||
		out->Bands <= 0 ||
		out->Xsize >= VIPS_MAX_COORD ||
		out->Ysize >= VIPS_MAX_COORD ||
		out->Bands >= VIPS_MAX_COORD) {
		vips_error(class->nickname,
			_("bad image dimensions %d x %d pixels, %d bands"),
			out->Xsize, out->Ysize, out->Bands);
		return -1;
	}

	out->Coding = VIPS_CODING_NONE;
	magick7->frame_height = image->rows;

	/* Depth can be 'fractional'. You'd think we should use
	 * GetImageDepth() but that seems to compute something very complex.
	 */
	out->BandFmt = -1;
	if (image->depth >= 1 && image->depth <= 8)
		out->BandFmt = VIPS_FORMAT_UCHAR;
	if (image->depth >= 9 && image->depth <= 16)
		out->BandFmt = VIPS_FORMAT_USHORT;
	if (image->depth == 32)
		out->BandFmt = VIPS_FORMAT_FLOAT;
	if (image->depth == 64)
		out->BandFmt = VIPS_FORMAT_DOUBLE;

	if (out->BandFmt == -1) {
		vips_error(class->nickname,
			_("unsupported bit depth %zd"), image->depth);
		return -1;
	}

	switch (image->units) {
	case PixelsPerInchResolution:
		out->Xres = image->resolution.x / 25.4;
		out->Yres = image->resolution.y / 25.4;
		vips_image_set_string(out, VIPS_META_RESOLUTION_UNIT, "in");
		break;

	case PixelsPerCentimeterResolution:
		out->Xres = image->resolution.x / 10.0;
		out->Yres = image->resolution.y / 10.0;
		vips_image_set_string(out, VIPS_META_RESOLUTION_UNIT, "cm");
		break;

	default:
		/* Things like GIF have no resolution info.
		 */
		out->Xres = 1.0;
		out->Yres = 1.0;
		break;
	}

	switch (image->colorspace) {
	case GRAYColorspace:
		if (out->BandFmt == VIPS_FORMAT_USHORT)
			out->Type = VIPS_INTERPRETATION_GREY16;
		else
			out->Type = VIPS_INTERPRETATION_B_W;
		break;

	case sRGBColorspace:
	case RGBColorspace:
		if (out->BandFmt == VIPS_FORMAT_USHORT)
			out->Type = VIPS_INTERPRETATION_RGB16;
		else
			out->Type = VIPS_INTERPRETATION_sRGB;
		break;

	case CMYKColorspace:
		out->Type = VIPS_INTERPRETATION_CMYK;
		break;

	default:
		out->Type = VIPS_INTERPRETATION_ERROR;
		break;
	}

	// revise the interpretation if it seems crazy
	out->Type = vips_image_guess_interpretation(out);

	magick7->n_pages = GetImageListLength(GetFirstImageInList(image));
#ifdef DEBUG
	printf("image has %d pages\n", magick7->n_pages);
#endif /*DEBUG*/

	/* Do we have a set of equal-sized frames? Append them.

		FIXME ... there must be an attribute somewhere from dicom read
		which says this is a volumetric image

	 */
	magick7->n_frames = 0;
	for (p = image; p; (p = GetNextImageInList(p))) {
		if (p->columns != (unsigned int) out->Xsize ||
			p->rows != (unsigned int) out->Ysize ||
			magick7_get_bands(p) != out->Bands ||
			p->depth != image->depth) {
#ifdef DEBUG
			printf("frame %d differs\n", magick7->n_frames);
			printf("%zdx%zd, %d bands\n",
				p->columns, p->rows, magick7_get_bands(p));
			printf("first frame is %dx%d, %d bands\n",
				out->Xsize, out->Ysize, out->Bands);
#endif /*DEBUG*/

			break;
		}

		magick7->n_frames += 1;
	}
	if (p)
		/* Nope ... just do the first image in the list.
		 */
		magick7->n_frames = 1;

#ifdef DEBUG
	printf("will read %d frames\n", magick7->n_frames);
#endif /*DEBUG*/

	if (magick7->n != -1)
		magick7->n_frames = VIPS_MIN(magick7->n_frames, magick7->n);

	/* So we can finally set the height.
	 */
	if (magick7->n_frames > 1) {
		vips_image_set_int(out, VIPS_META_PAGE_HEIGHT, magick7->frame_height);

		guint64 total_y;
		if (!g_uint64_checked_mul(&total_y,
			(guint64)out->Ysize, magick7->n_frames) ||
			total_y > VIPS_MAX_COORD) {
			vips_error(class->nickname, "%s", _("image dimensions overflow"));
			return -1;
		}

		out->Ysize = total_y;
	}

	vips_image_set_int(out, VIPS_META_N_PAGES, magick7->n_pages);

	/* Get all the string metadata.
	 */
	ResetImagePropertyIterator(image);
	while ((key = GetNextImageProperty(image))) {
		char name_text[256];
		VipsBuf name = VIPS_BUF_STATIC(name_text);
		const char *value;

		value = GetImageProperty(image, key, magick7->exception);
		if (!value) {
			vips_foreign_load_magick7_error(magick7);
			return -1;
		}
		vips_buf_appendf(&name, "magick-%s", key);
		vips_image_set_string(out, vips_buf_all(&name), value);
	}

	/* Set vips metadata from ImageMagick profiles.
	 */
	if (magick_set_vips_profile(out, image))
		return -1;

	/* Something like "BMP".
	 */
	if (strlen(magick7->image->magick) > 0)
		vips_image_set_string(out, "magick-format", magick7->image->magick);

	vips_image_set_int(out, VIPS_META_ORIENTATION,
		VIPS_CLIP(1, image->orientation, 8));

	vips_image_set_int(out, VIPS_META_BITS_PER_SAMPLE, image->depth);

	return 0;
}

/* We don't bother with GetPixelReadMask(), assume it's everywhere. Don't
 * bother with traits, assume that's always updated.
 *
 * We do skip index channels. Palette images add extra index channels
 * containing the index value from the file before colourmap lookup.
 */
#define UNPACK(TYPE) \
	{ \
		TYPE *restrict tq = (TYPE *) q; \
		int x; \
		int b; \
\
		for (x = 0; x < r->width; x++) { \
			for (b = 0; b < GetPixelChannels(image); b++) { \
				PixelChannel channel = \
					GetPixelChannelChannel(image, b); \
\
				if (channel != IndexPixelChannel) \
					*tq++ = p[b]; \
			} \
\
			p += GetPixelChannels(image); \
		} \
	}

static int
vips_foreign_load_magick7_fill_region(VipsRegion *out_region,
	void *seq, void *a, void *b, gboolean *stop)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) a;
	VipsRect *r = &out_region->valid;
	VipsImage *im = out_region->im;

	int y;

	for (y = 0; y < r->height; y++) {
		int top = r->top + y;
		int frame = top / magick7->frame_height;
		int line = top % magick7->frame_height;
		Image *image = magick7->frames[frame];

		Quantum *restrict p;
		VipsPel *restrict q;

		vips__worker_lock(&magick7->lock);

		p = GetCacheViewAuthenticPixels(magick7->cache_view[frame],
			r->left, line, r->width, 1,
			magick7->exception);

		g_mutex_unlock(&magick7->lock);

		if (!p)
			/* This can happen if, for example, some frames of a
			 * gif are shorter than others. It's not always
			 * an error.
			 */
			continue;

		q = VIPS_REGION_ADDR(out_region, r->left, top);

		switch (im->BandFmt) {
		case VIPS_FORMAT_UCHAR:
			UNPACK(unsigned char);
			break;

		case VIPS_FORMAT_USHORT:
			UNPACK(unsigned short);
			break;

		case VIPS_FORMAT_FLOAT:
			UNPACK(float);
			break;

		case VIPS_FORMAT_DOUBLE:
			UNPACK(double);
			break;

		default:
			g_assert_not_reached();
		}
	}

	return 0;
}

static int
vips_foreign_load_magick7_load(VipsForeignLoadMagick7 *magick7)
{
	VipsForeignLoad *load = (VipsForeignLoad *) magick7;

	Image *p;
	int i;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_load: %p\n", magick7);
#endif /*DEBUG*/

	if (vips_foreign_load_magick7_parse(magick7, magick7->image, load->out) ||
		vips_image_pipelinev(load->out, VIPS_DEMAND_STYLE_SMALLTILE, NULL))
		return -1;

	/* Record frame pointers.
	 */
	g_assert(!magick7->frames);
	if (!(magick7->frames = VIPS_ARRAY(NULL, magick7->n_frames, Image *)))
		return -1;
	p = magick7->image;
	for (i = 0; i < magick7->n_frames; i++) {
		magick7->frames[i] = p;
		p = GetNextImageInList(p);
	}

	/* And a cache_view for each frame.
	 */
	g_assert(!magick7->cache_view);
	if (!(magick7->cache_view = VIPS_ARRAY(NULL,
			  magick7->n_frames, CacheView *)))
		return -1;
	for (i = 0; i < magick7->n_frames; i++) {
		magick7->cache_view[i] = AcquireAuthenticCacheView(
			magick7->frames[i], magick7->exception);
	}

#ifdef DEBUG
	/* Only display the traits from frame0, they should all be the same.
	 */
	vips_magick7_print_traits(magick7->frames[0]);
	vips_magick7_print_channel_names(magick7->frames[0]);
#endif /*DEBUG*/

	if (vips_image_generate(load->out,
			NULL, vips_foreign_load_magick7_fill_region, NULL,
			magick7, NULL))
		return -1;

	return 0;
}

typedef struct _VipsForeignLoadMagick7File {
	VipsForeignLoadMagick7 parent_object;

	char *filename;

} VipsForeignLoadMagick7File;

typedef VipsForeignLoadMagick7Class VipsForeignLoadMagick7FileClass;

G_DEFINE_TYPE(VipsForeignLoadMagick7File, vips_foreign_load_magick7_file,
	vips_foreign_load_magick7_get_type());

static gboolean
ismagick7(const char *filename)
{
	/* Fetch up to the first 100 bytes. Hopefully that'll be enough.
	 */
	unsigned char buf[100];
	int len;

	return (len = vips__get_bytes(filename, buf, 100)) > 10 &&
		magick_ismagick(buf, len);
}

static int
vips_foreign_load_magick7_file_header(VipsForeignLoad *load)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) load;
	VipsForeignLoadMagick7File *file = (VipsForeignLoadMagick7File *) load;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_file_header: %p\n", load);
#endif /*DEBUG*/

	g_strlcpy(magick7->image_info->filename, file->filename,
		MagickPathExtent);

	magick_sniff_file(magick7->image_info, file->filename);

	/* It would be great if we could PingImage and just read the header,
	 * but sadly many IM coders do not support ping. The critical one for
	 * us is DICOM.
	 *
	 * We have to read the whole image in _header.
	 */
	magick7->image = ReadImage(magick7->image_info, magick7->exception);
	if (!magick7->image) {
		vips_foreign_load_magick7_error(magick7);
		return -1;
	}

	if (vips_foreign_load_magick7_load(magick7))
		return -1;

	VIPS_SETSTR(load->out->filename, file->filename);

	return 0;
}

static void
vips_foreign_load_magick7_file_class_init(
	VipsForeignLoadMagick7FileClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "magickload";
	object_class->description = _("load file with ImageMagick7");

	load_class->is_a = ismagick7;
	load_class->header = vips_foreign_load_magick7_file_header;

	VIPS_ARG_STRING(class, "filename", 1,
		_("Filename"),
		_("Filename to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7File, filename),
		NULL);
}

static void
vips_foreign_load_magick7_file_init(VipsForeignLoadMagick7File *magick7_file)
{
}

typedef struct _VipsForeignLoadMagick7Buffer {
	VipsForeignLoadMagick7 parent_object;

	VipsArea *buf;

} VipsForeignLoadMagick7Buffer;

typedef VipsForeignLoadMagick7Class VipsForeignLoadMagick7BufferClass;

G_DEFINE_TYPE(VipsForeignLoadMagick7Buffer, vips_foreign_load_magick7_buffer,
	vips_foreign_load_magick7_get_type());

static gboolean
vips_foreign_load_magick7_buffer_is_a_buffer(const void *buf, size_t len)
{
	return len > 10 && magick_ismagick((const unsigned char *) buf, len);
}

static int
vips_foreign_load_magick7_buffer_header(VipsForeignLoad *load)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) load;
	VipsForeignLoadMagick7Buffer *magick7_buffer =
		(VipsForeignLoadMagick7Buffer *) load;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_buffer_header: %p\n", load);
#endif /*DEBUG*/

	/* It would be great if we could PingBlob and just read the header,
	 * but sadly many IM coders do not support ping. The critical one for
	 * us is DICOM.
	 *
	 * We have to read the whole image in _header.
	 */
	magick_sniff_bytes(magick7->image_info,
		magick7_buffer->buf->data, magick7_buffer->buf->length);
	magick7->image = BlobToImage(magick7->image_info,
		magick7_buffer->buf->data, magick7_buffer->buf->length,
		magick7->exception);
	if (!magick7->image) {
		vips_foreign_load_magick7_error(magick7);
		return -1;
	}

	if (vips_foreign_load_magick7_load(magick7))
		return -1;

	return 0;
}

static void
vips_foreign_load_magick7_buffer_class_init(
	VipsForeignLoadMagick7BufferClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "magickload_buffer";
	object_class->description = _("load buffer with ImageMagick7");

	load_class->is_a_buffer = vips_foreign_load_magick7_buffer_is_a_buffer;
	load_class->header = vips_foreign_load_magick7_buffer_header;

	VIPS_ARG_BOXED(class, "buffer", 1,
		_("Buffer"),
		_("Buffer to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7Buffer, buf),
		VIPS_TYPE_BLOB);
}

static void
vips_foreign_load_magick7_buffer_init(VipsForeignLoadMagick7Buffer *buffer)
{
}

typedef struct _VipsForeignLoadMagick7Source {
	VipsForeignLoadMagick7 parent_object;

	VipsSource *source;

} VipsForeignLoadMagick7Source;

typedef VipsForeignLoadMagick7Class VipsForeignLoadMagick7SourceClass;

G_DEFINE_TYPE(VipsForeignLoadMagick7Source, vips_foreign_load_magick7_source,
	vips_foreign_load_magick7_get_type());

static gboolean
vips_foreign_load_magick7_source_is_a_source(VipsSource *source)
{
	const unsigned char *p;

	// just use the first 100 bytes, we don't want to force too much into
	// memory
	return (p = vips_source_sniff(source, 100)) &&
		magick_ismagick(p, 100);
}

/* Unfortunately, libMagick7 does not support header-only reads very well. See
 *
 * http://www.imagemagick7.org/discourse-server/viewtopic.php?f=1&t=20017
 *
 * Test especially with BMP, GIF, TGA. So we are forced to read the entire
 * image in the @header() method.
 */
static int
vips_foreign_load_magick7_source_header(VipsForeignLoad *load)
{
	VipsForeignLoadMagick7 *magick7 = (VipsForeignLoadMagick7 *) load;
	VipsForeignLoadMagick7Source *magick7_source =
		(VipsForeignLoadMagick7Source *) load;

#ifdef DEBUG
	printf("vips_foreign_load_magick7_source_header: %p\n", load);
#endif /*DEBUG*/

	if (vips_source_is_file(magick7_source->source)) {
		const char *filename =
			vips_connection_filename(VIPS_CONNECTION(magick7_source->source));

		g_strlcpy(magick7->image_info->filename, filename, MaxTextExtent);
		magick_sniff_file(magick7->image_info, filename);
		magick7->image = ReadImage(magick7->image_info, magick7->exception);
	}
	else {
		size_t length;
		const void *data;

		if (!(data = vips_source_map(magick7_source->source, &length)))
			return -1;

		magick_sniff_bytes(magick7->image_info, data, length);
		magick7->image = BlobToImage(magick7->image_info, data, length,
			magick7->exception);
	}

	/* It would be great if we could PingImage and just read the header,
	 * but sadly many IM coders do not support ping. The critical one for
	 * us is DICOM. TGA also has issues.
	 */
	if (!magick7->image) {
		vips_foreign_load_magick7_error(magick7);
		return -1;
	}

	if (vips_foreign_load_magick7_load(magick7))
		return -1;

	return 0;
}

static void
vips_foreign_load_magick7_source_class_init(
	VipsForeignLoadMagick7SourceClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "magickload_source";
	object_class->description = _("load source with ImageMagick7");

	load_class->is_a_source = vips_foreign_load_magick7_source_is_a_source;
	load_class->header = vips_foreign_load_magick7_source_header;

	VIPS_ARG_OBJECT(class, "source", 1,
		_("Source"),
		_("Source to load from"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsForeignLoadMagick7Source, source),
		VIPS_TYPE_SOURCE);
}

static void
vips_foreign_load_magick7_source_init(VipsForeignLoadMagick7Source *source)
{
}

#endif /*HAVE_MAGICK7*/

#endif /*ENABLE_MAGICKLOAD*/
