/* Common functions for interfacing with ImageMagick.
 *
 * 22/12/17 dlemstra
 *
 * 24/7/18
 * 	- add the sniffer
 * 16/10/20 [bfriesen]
 * 	- set matte and depth appropriately for GM in magick_import_pixels()
 */

/*

	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

 */

#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/internal.h>

#include "pforeign.h"
#include "magick.h"

#if defined(HAVE_MAGICK6) || defined(HAVE_MAGICK7)

/* Imagemagick has weak support for some formats -- for example, AVI is
 * delegated to ffmpeg, and just getting the header can take many seconds and
 * many GB of memory.
 *
 * This function detects blocked formats which we don't pass to
 * imagemagick.
 */
static gboolean
magick_block(const unsigned char *bytes, size_t length)
{
	// AVI is a RIFF with AVI as the first chunk
	if (length > 12 &&
		bytes[0] == 'R' &&
		bytes[1] == 'I' &&
		bytes[2] == 'F' &&
		bytes[3] == 'F' &&
		bytes[8] == 'A' &&
		bytes[9] == 'V' &&
		bytes[10] == 'I' &&
		bytes[11] == ' ')
		return TRUE;

	// XML is very slow for some reason, and we handle SVG, so ...
	if (length > 5 &&
		bytes[0] == '<' &&
		bytes[1] == '?' &&
		bytes[2] == 'x' &&
		bytes[3] == 'm' &&
		bytes[4] == 'l' &&
		bytes[5] == ' ')
		return TRUE;
	if (length > 5 &&
		bytes[0] == '<' &&
		bytes[1] == '?' &&
		bytes[2] == 'X' &&
		bytes[3] == 'M' &&
		bytes[4] == 'L' &&
		bytes[5] == ' ')
		return TRUE;

	return FALSE;
}

/* ImageMagick can't detect some formats (eg. ICO and TGA) by examining the
 * contents -- ico.c and tga.c simply do not have recognisers.
 *
 * For these formats, do the detection ourselves.
 * Return an IM format specifier, or NULL to let IM do the detection.
 *
 * For sniffing TGAs, we check that there is at least enough room for the
 * header and that the preamble contains valid values:
 *
 * -----------------------------------------------------------
 * |0x00 | 0-255 idlength, skip                              |
 * |0x01 | 0-1, color map present                            |
 * |0x02 | Any of (0, 1, 2, 3, 9, 10, 11), Image type        |
 * -----------------------------------------------------------
 *
 * However, this would catch ISOBMFF formats, so we must also check for the
 * 'ftyp' signature at offset 4.
 *
 * References:
 * * https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf
 * * http://www.paulbourke.net/dataformats/tga/
 * * https://en.wikipedia.org/wiki/Truevision_TGA#Technical_details
 * * https://en.wikipedia.org/wiki/ISO_base_media_file_format#Technical_details
 */
static const char *
magick_sniff(const unsigned char *bytes, size_t length)
{
	if (length >= 5 &&
		bytes[0] == 0 &&
		bytes[1] == 1 &&
		bytes[2] == 0 &&
		bytes[3] == 0 &&
		bytes[4] == 0)
		return "TTF";

	if (length >= 6 &&
		bytes[0] == 0 &&
		bytes[1] == 0 &&
		(bytes[2] == 1 || bytes[2] == 2) &&
		bytes[3] == 0 &&
		(bytes[4] != 0 || bytes[5] != 0))
		return "ICO";

	if (length >= 8 &&
		bytes[1] == 0 &&
		bytes[2] == 0 &&
		bytes[5] == 0 &&
		bytes[6] == 0 &&
		(bytes[4] == 7 ||
		 bytes[7] == 7))
		return "XWD";

	if (length >= 18 &&
		(bytes[1] == 0 ||
			bytes[1] == 1) &&
		(bytes[2] == 0 ||
			bytes[2] == 1 ||
			bytes[2] == 2 ||
			bytes[2] == 3 ||
			bytes[2] == 9 ||
			bytes[2] == 10 ||
			bytes[2] == 11) &&
		memcmp(bytes + 4, "ftyp", 4) != 0)
		return "TGA";

#if defined(HAVE_GETMAGICINFO) || defined(HAVE_MAGICK7)
	/* Try to search the internal magic list for a match.
	 */
	ExceptionInfo *exception = magick_acquire_exception();
	const MagicInfo *magic_info = GetMagicInfo(bytes, length, exception);
	magick_destroy_exception(exception);

	if (magic_info) {
		const char *magic_name = GetMagicName(magic_info);

		/* Avoid using TIFF as a format hint since RAW/DNG images often
		 * share the same magic signature as TIFF.
		 */
		if (magic_name &&
			g_ascii_strcasecmp(magic_name, "TIFF") != 0) {
			return magic_name;
		}
	}
#endif

	return NULL;
}

void
magick_sniff_bytes(ImageInfo *image_info,
	const unsigned char *bytes, size_t length)
{
	const char *format;

	if ((format = magick_sniff(bytes, length)))
		g_strlcpy(image_info->magick, format, MaxTextExtent);
}

void
magick_sniff_file(ImageInfo *image_info, const char *filename)
{
	unsigned char bytes[256];
	size_t length;

	if ((length = vips__get_bytes(filename, bytes, 256)) >= 4)
		magick_sniff_bytes(image_info, bytes, length);
}

#endif /*defined(HAVE_MAGICK6) || defined(HAVE_MAGICK7)*/

#ifdef HAVE_MAGICK7

Image *
magick_acquire_image(const ImageInfo *image_info, ExceptionInfo *exception)
{
	return AcquireImage(image_info, exception);
}

void
magick_acquire_next_image(const ImageInfo *image_info, Image *image,
	ExceptionInfo *exception)
{
	AcquireNextImage(image_info, image, exception);
}

int
magick_set_image_size(Image *image, const size_t width, const size_t height,
	ExceptionInfo *exception)
{
	return SetImageExtent(image, width, height, exception);
}

int
magick_import_pixels(Image *image, const gssize x, const gssize y,
	const size_t width, const size_t height, const char *map,
	const StorageType type, const void *pixels, ExceptionInfo *exception)
{
	return ImportImagePixels(image, x, y, width, height, map,
		type, pixels, exception);
}

void *
magick_images_to_blob(const ImageInfo *image_info, Image *images,
	size_t *length, ExceptionInfo *exception)
{
	return ImagesToBlob(image_info, images, length, exception);
}

void
magick_set_property(Image *image, const char *property, const char *value,
	ExceptionInfo *exception)
{
	(void) SetImageProperty(image, property, value, exception);
}

int
magick_set_profile(Image *image,
	const char *name, const void *data, size_t length,
	ExceptionInfo *exception)
{
	StringInfo *string;
	MagickBooleanType result;

	string = BlobToStringInfo(data, length);
	result = SetImageProfile(image, name, string, exception);
	DestroyStringInfo(string);

	return result;
}

void *
magick_profile_map(Image *image, MagickMapProfileFn fn, void *a)
{
	char *name;

	ResetImageProfileIterator(image);
	while ((name = GetNextImageProfile(image))) {
		const StringInfo *profile;
		void *data;
		size_t length;
		void *result;

		profile = GetImageProfile(image, name);
		data = GetStringInfoDatum(profile);
		length = GetStringInfoLength(profile);
		if ((result = fn(image, name, data, length, a)))
			return result;
	}

	return NULL;
}

ExceptionInfo *
magick_acquire_exception(void)
{
	return AcquireExceptionInfo();
}

void
magick_destroy_exception(ExceptionInfo *exception)
{
	VIPS_FREEF(DestroyExceptionInfo, exception);
}

void
magick_inherit_exception(ExceptionInfo *exception, Image *image)
{
	(void) exception;
	(void) image;
}

void
magick_set_number_scenes(ImageInfo *image_info, int scene, int number_scenes)
{
	/* I can't find docs for these fields, but this seems to work.
	 */
	char page[256];

	image_info->scene = scene;
	image_info->number_scenes = number_scenes;

	/* Some IMs must have the string version set as well.
	 */
	g_snprintf(page, 256, "%d-%d", scene, scene + number_scenes);
	image_info->scenes = g_strdup(page);
}

int
magick_optimize_image_layers(Image **images, ExceptionInfo *exception)
{
	Image *tmp;

	tmp = OptimizePlusImageLayers(*images, exception);

	if (exception->severity != UndefinedException) {
		VIPS_FREEF(DestroyImageList, tmp);
		return MagickFalse;
	}

	VIPS_FREEF(DestroyImageList, *images);
	*images = tmp;

	return MagickTrue;
}

int
magick_optimize_image_transparency(const Image *images,
	ExceptionInfo *exception)
{
	OptimizeImageTransparency(images, exception);

	return exception->severity == UndefinedException;
}

/* Does a few bytes look like a file IM can handle?
 */
gboolean
magick_ismagick(const unsigned char *bytes, size_t length)
{
	char format[MagickPathExtent];

	magick_genesis();

	/* Try with our custom sniffers first.
	 */
	return !magick_block(bytes, length) &&
		(magick_sniff(bytes, length) ||
		GetImageMagick(bytes, length, format));
}

int
magick_quantize_images(Image *images,
	const size_t depth, ExceptionInfo *exception)
{
	QuantizeInfo info;

	GetQuantizeInfo(&info);
	info.number_colors = 1 << depth;
	QuantizeImages(&info, images, exception);

	return exception->severity == UndefinedException;
}

#endif /*HAVE_MAGICK7*/

#ifdef HAVE_MAGICK6

Image *
magick_acquire_image(const ImageInfo *image_info, ExceptionInfo *exception)
{
	(void) exception;

#ifdef HAVE_ACQUIREIMAGE
	return AcquireImage(image_info);
#else /*!HAVE_ACQUIREIMAGE*/
	/* IM5-ish and GraphicsMagick use AllocateImage().
	 */
	return AllocateImage(image_info);
#endif
}

void
magick_acquire_next_image(const ImageInfo *image_info, Image *image,
	ExceptionInfo *exception)
{
	(void) exception;
#ifdef HAVE_ACQUIREIMAGE
	AcquireNextImage(image_info, image);
#else /*!HAVE_ACQUIREIMAGE*/
	/* IM5-ish and GraphicsMagick use AllocateNextImage().
	 */
	AllocateNextImage(image_info, image);
#endif
}

int
magick_set_image_size(Image *image, const size_t width, const size_t height,
	ExceptionInfo *exception)
{
#ifdef HAVE_SETIMAGEEXTENT
	int result = SetImageExtent(image, width, height);

	/* IM6 sets the exception on the image.
	 */
	if (!result)
		magick_inherit_exception(exception, image);

	return result;
#else  /*!HAVE_SETIMAGEEXTENT*/
	(void) exception;
	image->columns = width;
	image->rows = height;

	/* imagemagick does a SyncImagePixelCache() at the end of
	 * SetImageExtent(), but GM does not really have an equivalent. Just
	 * always return True.
	 */
	return MagickTrue;
#endif /*HAVE_SETIMAGEEXTENT*/
}

int
magick_import_pixels(Image *image, const gssize x, const gssize y,
	const size_t width, const size_t height, const char *map,
	const StorageType type, const void *pixels, ExceptionInfo *exception)
{
#ifdef HAVE_IMPORTIMAGEPIXELS
	return ImportImagePixels(image, x, y, width, height, map,
		type, pixels);
#else  /*!HAVE_IMPORTIMAGEPIXELS*/
	Image *constitute_image;
	unsigned int storage_type_depth;

	g_assert(image);
	g_assert(image->signature == MagickSignature);

	constitute_image = ConstituteImage(width, height, map, type,
		pixels, &image->exception);
	if (!constitute_image)
		return MagickFalse;

	/* image needs to inherit these fields from constitute_image.
	 */
	switch (type) {
	case CharPixel:
		storage_type_depth = sizeof(unsigned char) * 8;
		break;

	case ShortPixel:
		storage_type_depth = sizeof(unsigned short) * 8;
		break;

	case IntegerPixel:
		storage_type_depth = sizeof(unsigned short) * 8;
		break;

	case LongPixel:
		storage_type_depth = sizeof(unsigned long) * 8;
		break;

	case FloatPixel:
		storage_type_depth = sizeof(float) * 8;
		break;

	case DoublePixel:
		storage_type_depth = sizeof(double) * 8;
		break;

	default:
		storage_type_depth = QuantumDepth;
		break;
	}
	image->depth = VIPS_MIN(storage_type_depth, QuantumDepth);
	image->matte = constitute_image->matte;

	(void) CompositeImage(image, CopyCompositeOp, constitute_image, x, y);

	DestroyImage(constitute_image);

	return image->exception.severity == UndefinedException;
#endif /*HAVE_IMPORTIMAGEPIXELS*/
}

void *
magick_images_to_blob(const ImageInfo *image_info, Image *images,
	size_t *length, ExceptionInfo *exception)
{
#ifdef HAVE_IMAGESTOBLOB
	return ImagesToBlob(image_info, images, length, exception);
#else
	return ImageToBlob(image_info, images, length, exception);
#endif /*HAVE_IMAGESTOBLOB*/
}

void
magick_set_property(Image *image, const char *property, const char *value,
	ExceptionInfo *exception)
{
	(void) exception;
#ifdef HAVE_SETIMAGEPROPERTY
	(void) SetImageProperty(image, property, value);
#else  /*!HAVE_SETIMAGEPROPERTY*/
	(void) SetImageAttribute(image, property, value);
#endif /*HAVE_SETIMAGEPROPERTY*/
}

int
magick_set_profile(Image *image,
	const char *name, const void *data, size_t length,
	ExceptionInfo *exception)
{
	int result;

#ifdef HAVE_BLOBTOSTRINGINFO
	StringInfo *string;

	string = BlobToStringInfo(data, length);
	result = SetImageProfile(image, name, string);
	DestroyStringInfo(string);
#else  /*!HAVE_BLOBTOSTRINGINFO*/
	result = SetImageProfile(image, name, data, length);
#endif /*HAVE_BLOBTOSTRINGINFO*/

	return result;
}

void *
magick_profile_map(Image *image, MagickMapProfileFn fn, void *a)
{
	const char *name;
	const void *data;
	size_t length;
	void *result;

#ifdef HAVE_RESETIMAGEPROFILEITERATOR
	ResetImageProfileIterator(image);
	while ((name = GetNextImageProfile(image))) {
		const StringInfo *profile;

		profile = GetImageProfile(image, name);
		data = GetStringInfoDatum(profile);
		length = GetStringInfoLength(profile);
		if ((result = fn(image, name, data, length, a)))
			return result;
	}
#else  /*!HAVE_RESETIMAGEPROFILEITERATOR*/
	{
		ImageProfileIterator *iter;

		iter = AllocateImageProfileIterator(image);
		while (NextImageProfile(iter,
			&name, (const unsigned char **) &data, &length)) {
			if ((result = fn(image, name, data, length, a))) {
				DeallocateImageProfileIterator(iter);
				return result;
			}
		}
		DeallocateImageProfileIterator(iter);
	}
#endif /*HAVE_RESETIMAGEPROFILEITERATOR*/

	return NULL;
}

ExceptionInfo *
magick_acquire_exception(void)
{
	ExceptionInfo *exception;

#ifdef HAVE_ACQUIREEXCEPTIONINFO
	/* IM6+
	 */
	exception = AcquireExceptionInfo();
#else  /*!HAVE_ACQUIREEXCEPTIONINFO*/
	/* gm
	 */
	exception = g_new(ExceptionInfo, 1);
	GetExceptionInfo(exception);
#endif /*HAVE_ACQUIREEXCEPTIONINFO*/

	return exception;
}

void
magick_destroy_exception(ExceptionInfo *exception)
{
#ifdef HAVE_ACQUIREEXCEPTIONINFO
	/* IM6+ will free the exception in destroy.
	 */
	VIPS_FREEF(DestroyExceptionInfo, exception);
#else  /*!HAVE_ACQUIREEXCEPTIONINFO*/
	/* gm and very old IM need to free the memory too.
	 */
	if (exception) {
		DestroyExceptionInfo(exception);
		g_free(exception);
	}
#endif /*HAVE_ACQUIREEXCEPTIONINFO*/
}

void
magick_inherit_exception(ExceptionInfo *exception, Image *image)
{
#ifdef HAVE_INHERITEXCEPTION
	InheritException(exception, &image->exception);
#endif /*HAVE_INHERITEXCEPTION*/
}

void
magick_set_number_scenes(ImageInfo *image_info, int scene, int number_scenes)
{
#ifdef HAVE_NUMBER_SCENES
	/* I can't find docs for these fields, but this seems to work.
	 */
	char page[256];

	image_info->scene = scene;
	image_info->number_scenes = number_scenes;

	/* Some IMs must have the string version set as well.
	 */
	g_snprintf(page, 256, "%d-%d", scene, scene + number_scenes);
	image_info->scenes = g_strdup(page);
#else /*!HAVE_NUMBER_SCENES*/
	/* This works with GM 1.2.31 and probably others.
	 */
	image_info->subimage = scene;
	image_info->subrange = number_scenes;
#endif
}

int
magick_optimize_image_layers(Image **images, ExceptionInfo *exception)
{
#ifdef HAVE_OPTIMIZEPLUSIMAGELAYERS
	Image *tmp;

	tmp = OptimizePlusImageLayers(*images, exception);

	if (exception->severity != UndefinedException)
		return MagickFalse;

	VIPS_FREEF(DestroyImageList, *images);

	*images = tmp;

	return MagickTrue;
#else  /*!HAVE_OPTIMIZEPLUSIMAGELAYERS*/
	g_warning("layer optimization is not supported by "
			  "your version of libMagick");

	return MagickTrue;
#endif /*HAVE_OPTIMIZEPLUSIMAGELAYERS*/
}

int
magick_optimize_image_transparency(const Image *images,
	ExceptionInfo *exception)
{
#ifdef HAVE_OPTIMIZEIMAGETRANSPARENCY
	OptimizeImageTransparency(images, exception);

	return exception->severity == UndefinedException;
#else  /*!HAVE_OPTIMIZEIMAGETRANSPARENCY*/
	g_warning("transparency optimization is not supported by "
			  "your version of libMagick");

	return MagickTrue;
#endif /*HAVE_OPTIMIZEIMAGETRANSPARENCY*/
}

/* Does a few bytes look like a file IM can handle?
 */
gboolean
magick_ismagick(const unsigned char *bytes, size_t length)
{
	magick_genesis();

	/* Try with our custom sniffers first.
	 */
#ifdef HAVE_GETIMAGEMAGICK3
	{
		char format[MaxTextExtent];

		return !magick_block(bytes, length) &&
			(magick_sniff(bytes, length) ||
			GetImageMagick(bytes, length, format));
	}
#else /*!HAVE_GETIMAGEMAGICK3*/
	/* The GM one returns a static string.
	 */
	return !magick_block(bytes, length) &&
		(magick_sniff(bytes, length) ||
		GetImageMagick(bytes, length));
#endif
}

int
magick_quantize_images(Image *images,
	const size_t depth, ExceptionInfo *exception)
{
	QuantizeInfo info;

	GetQuantizeInfo(&info);
	info.number_colors = (1 << depth);

	return QuantizeImages(&info, images);
}

#endif /*HAVE_MAGICK6*/

#if defined(HAVE_MAGICK6) || defined(HAVE_MAGICK7)

void
magick_set_image_option(ImageInfo *image_info,
	const char *name, const char *value)
{
#ifdef HAVE_SETIMAGEOPTION
	SetImageOption(image_info, name, value);
#endif /*HAVE_SETIMAGEOPTION*/
}

typedef struct _MagickColorspaceTypeNames {
	ColorspaceType colorspace;
	const char *name;
} MagickColorspaceTypeNames;

static MagickColorspaceTypeNames magick_colorspace_names[] = {
	{ UndefinedColorspace, "UndefinedColorspace" },
	{ CMYKColorspace, "CMYKColorspace" },
	{ GRAYColorspace, "GRAYColorspace" },
	{ HSLColorspace, "HSLColorspace" },
	{ HWBColorspace, "HWBColorspace" },
	{ OHTAColorspace, "OHTAColorspace" },
	{ Rec601YCbCrColorspace, "Rec601YCbCrColorspace" },
	{ Rec709YCbCrColorspace, "Rec709YCbCrColorspace" },
	{ RGBColorspace, "RGBColorspace" },
	{ sRGBColorspace, "sRGBColorspace" },
	{ TransparentColorspace, "TransparentColorspace" },
	{ XYZColorspace, "XYZColorspace" },
	{ YCbCrColorspace, "YCbCrColorspace" },
	{ YCCColorspace, "YCCColorspace" },
	{ YIQColorspace, "YIQColorspace" },
	{ YPbPrColorspace, "YPbPrColorspace" },
	{ YUVColorspace, "YUVColorspace" },

/* More recent imagemagicks add these.
 */
#ifdef HAVE_CMYCOLORSPACE
	{ CMYColorspace, "CMYColorspace" },
	{ HCLColorspace, "HCLColorspace" },
	{ HSBColorspace, "HSBColorspace" },
	{ LabColorspace, "LabColorspace" },
	{ LogColorspace, "LogColorspace" },
	{ LuvColorspace, "LuvColorspace" },
#endif /*HAVE_CMYCOLORSPACE*/

#ifdef HAVE_HCLPCOLORSPACE
	{ HCLpColorspace, "HCLpColorspace" },
	{ HSIColorspace, "HSIColorspace" },
	{ HSVColorspace, "HSVColorspace" },
	{ LCHColorspace, "LCHColorspace" },
	{ LCHabColorspace, "LCHabColorspace" },
	{ LCHuvColorspace, "LCHuvColorspace" },
	{ LMSColorspace, "LMSColorspace" },
	{ scRGBColorspace, "scRGBColorspace" },
	{ xyYColorspace, "xyYColorspace" },
	{ YDbDrColorspace, "YDbDrColorspace" },
#endif /*HAVE_HCLPCOLORSPACE*/

	/* im7 has this, I think
	 *
	{ LinearGRAYColorspace, "LinearGRAYColorspace" }
	 *
	 */
};

const char *
magick_ColorspaceType2str(ColorspaceType colorspace)
{
	int i;

	for (i = 0; i < VIPS_NUMBER(magick_colorspace_names); i++)
		if (magick_colorspace_names[i].colorspace == colorspace)
			return magick_colorspace_names[i].name;

	return "<unknown ColorspaceType>";
}

void
magick_vips_error(const char *domain, ExceptionInfo *exception)
{
	if (exception) {
		if (exception->reason &&
			exception->description)
			vips_error(domain, _("libMagick error: %s %s"),
				exception->reason, exception->description);
		else if (exception->reason)
			vips_error(domain, _("libMagick error: %s"),
				exception->reason);
		else
			vips_error(domain, "%s", _("libMagick error:"));
	}
}

static void *
magick_genesis_cb(void *client)
{
	ExceptionInfo *exception;

#ifdef DEBUG
	printf("magick_genesis_cb:\n");
#endif /*DEBUG*/

#if defined(HAVE_MAGICKCOREGENESIS) || defined(HAVE_MAGICK7)
	MagickCoreGenesis(vips_get_argv0(), MagickFalse);
#else  /*!HAVE_MAGICKCOREGENESIS*/
	InitializeMagick(vips_get_argv0());
#endif /*HAVE_MAGICKCOREGENESIS*/

	/* This forces *magick to init all loaders. We have to do this so we
	 * can sniff files with GetImageMagick().
	 *
	 * We don't care about errors from magickinit.
	 */
	exception = magick_acquire_exception();
	(void) GetMagickInfo("*", exception);
	magick_destroy_exception(exception);

	return NULL;
}

void
magick_genesis(void)
{
	static GOnce once = G_ONCE_INIT;

	VIPS_ONCE(&once, magick_genesis_cb, NULL);
}

/* Set vips metadata from a magick profile.
 */
static void *
magick_set_vips_profile_cb(Image *image,
	const char *name, const void *data, size_t length, void *a)
{
	VipsImage *im = (VipsImage *) a;

	char name_text[256];
	VipsBuf vips_name = VIPS_BUF_STATIC(name_text);

	if (g_ascii_strcasecmp(name, "XMP") == 0)
		vips_buf_appendf(&vips_name, VIPS_META_XMP_NAME);
	else if (g_ascii_strcasecmp(name, "IPTC") == 0)
		vips_buf_appendf(&vips_name, VIPS_META_IPTC_NAME);
	else if (g_ascii_strcasecmp(name, "ICC") == 0)
		vips_buf_appendf(&vips_name, VIPS_META_ICC_NAME);
	else if (g_ascii_strcasecmp(name, "EXIF") == 0)
		vips_buf_appendf(&vips_name, VIPS_META_EXIF_NAME);
	else
		vips_buf_appendf(&vips_name, "magickprofile-%s", name);

	vips_image_set_blob_copy(im,
		vips_buf_all(&vips_name), data, length);

	if (strcmp(name, "exif") == 0)
		(void) vips__exif_parse(im);

	return NULL;
}

/* Set vips metadata from ImageMagick profiles.
 */
int
magick_set_vips_profile(VipsImage *im, Image *image)
{
	if (magick_profile_map(image, magick_set_vips_profile_cb, im))
		return -1;

	return 0;
}

typedef struct {
	Image *image;
	ExceptionInfo *exception;
} CopyProfileInfo;

static void *
magick_set_magick_profile_cb(VipsImage *im,
	const char *name, GValue *value, CopyProfileInfo *info)
{
	char txt[256];
	VipsBuf buf = VIPS_BUF_STATIC(txt);
	const void *data;
	size_t length;

	if (strcmp(name, VIPS_META_XMP_NAME) == 0)
		vips_buf_appendf(&buf, "XMP");
	else if (strcmp(name, VIPS_META_IPTC_NAME) == 0)
		vips_buf_appendf(&buf, "IPTC");
	else if (strcmp(name, VIPS_META_ICC_NAME) == 0)
		vips_buf_appendf(&buf, "ICC");
	else if (strcmp(name, VIPS_META_EXIF_NAME) == 0)
		vips_buf_appendf(&buf, "EXIF");
	else if (vips_isprefix("magickprofile-", name))
		vips_buf_appendf(&buf,
			"%s", name + strlen("magickprofile-"));

	if (vips_buf_is_empty(&buf))
		return NULL;
	if (!vips_image_get_typeof(im, name))
		return NULL;
	if (vips_image_get_blob(im, name, &data, &length))
		return im;

	if (!magick_set_profile(info->image,
			vips_buf_all(&buf), data, length, info->exception))
		return im;

	return NULL;
}

/* Set magick metadata from a VipsImage.
 */
int
magick_set_magick_profile(Image *image,
	VipsImage *im, ExceptionInfo *exception)
{
	CopyProfileInfo info;

	info.image = image;
	info.exception = exception;
	if (vips_image_map(im,
			(VipsImageMapFn) magick_set_magick_profile_cb, &info))
		return -1;

	return 0;
}

#endif /*defined(HAVE_MAGICK6) || defined(HAVE_MAGICK7)*/
