aboutsummaryrefslogblamecommitdiffstats
path: root/en_masse-template.c
blob: 8dc8bcce82d16822413c062b17a547287d31ce8e (plain) (tree)
1
2

                                                         






















































                                                                                               




















                                                                  





































































































































































                                                                                                                           





                                        


                                                 
                                         













                                                                    




























































                                                                                                         





                                          













                                                                             
                                             
















                                                                                                           













































































































                                                                                        





                                          

                           


                                                 
                                        







                                   




                                            

                             





                                                 
                                          







                               




                                          




                                     


                                                 
                                        







                                   




                                         








                                                 
                                          







                                   





                                            

                             


                                                 
                                          






































                                                                                           


















                                                 
 



                                                                                        



                                                                        
                                                

                                                

















                                                                          



                                                




                                                                         
                                   








                                           



                                    
                                                                       



                                                       
                                                                          




                                                                 
                                                                         




                                                       
                





                                                   




                              
                                 







                               

                        
                                                         

                              





                               


                      
/* See LICENSE file for copyright and license details. */

#define XPARAMETERS\
	size_t n, size_t width, TYPE *ch1, TYPE *ch2, TYPE *ch3

#define XARGUMENTS\
	(n), (width), (ch1), (ch2), (ch3)

#define PARAMETERS(FROM_SPACE, TO_SPACE)\
	const libcolour_##FROM_SPACE##_t *from, const libcolour_##TO_SPACE##_t *to, XPARAMETERS

#define ARGUMENTS(FROM, TO)\
	(FROM), (TO), XARGUMENTS


#define SIMPLE_LOOP(CH, INSTRUCTIONS)\
	do {\
		TYPE *ch = (CH);\
		size_t i__;\
		for (i__ = 0; i__ < n; i__++) {\
			INSTRUCTIONS;\
			(CH) += width;\
		}\
		(CH) = ch;\
	} while (0)

#define LOOP(INSTRUCTIONS)\
	do {\
		TYPE *c1 = ch1, *c2 = ch2, *c3 = ch3;\
		size_t i__;\
		for (i__ = 0; i__ < n; i__++) {\
			INSTRUCTIONS;\
			ch1 += width;\
			ch2 += width;\
			ch3 += width;\
		}\
		ch1 = c1;\
		ch2 = c2;\
		ch3 = c3;\
	} while (0)

#define CONV_0(MACRO)\
	LOOP(MACRO(*ch1, *ch2, *ch3, *ch1, *ch2, *ch3))

#define CONV_N(MACRO, ...)\
	LOOP(MACRO(*ch1, *ch2, *ch3, *ch1, *ch2, *ch3, __VA_ARGS__))


#define LINEAR_SRGB\
	do {\
		if (from->srgb.with_transfer) {\
			libcolour_srgb_t srgb__ = from->srgb;\
			srgb__.with_transfer = 0;\
			to_srgb(ARGUMENTS(from, &srgb__));\
		}\
	} while (0)

#define DEFAULT_CONVERSION(FROM, TO)\
	case LIBCOLOUR_##FROM:\
		conversion_by_matrix(XARGUMENTS, FROM##_TO_##TO);\
		break

#define DEFAULT_CONVERSION_FROM_SRGB(TO)\
	case LIBCOLOUR_SRGB:\
		LINEAR_SRGB;\
		conversion_by_matrix(XARGUMENTS, SRGB_TO_##TO);\
		break

#define DEFAULT_CONV_0(FROM, TO)\
	case LIBCOLOUR_##FROM:\
		CONV_0(FROM##_TO_##TO);\
		break

#define DEFAULT_CONV_N(FROM, TO, ...)\
	case LIBCOLOUR_##FROM:\
		CONV_N(FROM##_TO_##TO, __VA_ARGS__);\
		break



#define X(C, T, N) static void to_##N(const libcolour_colour_t *from, const T *to, XPARAMETERS);
LIST_MODELS(X)
#undef X



static void
conversion_by_matrix(XPARAMETERS, TYPE m11, TYPE m12, TYPE m13, TYPE m21, TYPE m22, TYPE m23, TYPE m31, TYPE m32, TYPE m33)
{
	TYPE c1, c2, c3;
	while (n--) {
		c1 = *ch1;
		c2 = *ch2;
		c3 = *ch3;
		*ch1 = m11 * c1 + m12 * c2 + m13 * c3;
		*ch2 = m21 * c1 + m22 * c2 + m23 * c3;
		*ch3 = m31 * c1 + m32 * c2 + m33 * c3;
		ch1 += width;
		ch2 += width;
		ch3 += width;
	}
}


static void
rgb_encode(XPARAMETERS, const libcolour_rgb_t *restrict space)
{
	TYPE r_sign, g_sign, b_sign;
	switch (space->encoding_type) {
	case LIBCOLOUR_ENCODING_TYPE_LINEAR:
		break;
	case LIBCOLOUR_ENCODING_TYPE_SIMPLE:
	case LIBCOLOUR_ENCODING_TYPE_REGULAR:
		LOOP(do {
				(r_sign = 1, g_sign = 1, b_sign = 1);
				if (*ch1 < 0) (r_sign = -1, *ch1 = -*ch1);
				if (*ch2 < 0) (g_sign = -1, *ch2 = -*ch2);
				if (*ch3 < 0) (b_sign = -1, *ch3 = -*ch3);
				if (space->encoding_type == LIBCOLOUR_ENCODING_TYPE_SIMPLE) {
					*ch1 = xpow(*ch1, 1 / space->GAMMA);
					*ch2 = xpow(*ch2, 1 / space->GAMMA);
					*ch3 = xpow(*ch3, 1 / space->GAMMA);
				} else {
					*ch1 = REGULAR(space, *ch1);
					*ch2 = REGULAR(space, *ch2);
					*ch3 = REGULAR(space, *ch3);
				}
				*ch1 *= r_sign;
				*ch2 *= g_sign;
				*ch3 *= b_sign;
			} while (0));
		break;
	case LIBCOLOUR_ENCODING_TYPE_CUSTOM:
		LOOP((*ch1 = (space->TO_ENCODED_RED)(*ch1),
		      *ch2 = (space->TO_ENCODED_GREEN)(*ch2),
		      *ch3 = (space->TO_ENCODED_BLUE)(*ch3)));
		break;
	default:
		fprintf(stderr, "libcolour: invalid encoding type\n");
		abort();
	}
}

static void
rgb_decode(XPARAMETERS, const libcolour_rgb_t *restrict space)
{
	TYPE r_sign, g_sign, b_sign;
	switch (space->encoding_type) {
	case LIBCOLOUR_ENCODING_TYPE_LINEAR:
		break;
	case LIBCOLOUR_ENCODING_TYPE_SIMPLE:
	case LIBCOLOUR_ENCODING_TYPE_REGULAR:
		LOOP(do {
				(r_sign = 1, g_sign = 1, b_sign = 1);
				if (*ch1 < 0) (r_sign = -1, *ch1 = -*ch1);
				if (*ch2 < 0) (g_sign = -1, *ch2 = -*ch2);
				if (*ch3 < 0) (b_sign = -1, *ch3 = -*ch3);
				if (space->encoding_type == LIBCOLOUR_ENCODING_TYPE_SIMPLE) {
					*ch1 = xpow(*ch1, space->GAMMA);
					*ch2 = xpow(*ch2, space->GAMMA);
					*ch3 = xpow(*ch3, space->GAMMA);
				} else {
					*ch1 = INVREGULAR(space, *ch1);
					*ch2 = INVREGULAR(space, *ch2);
					*ch3 = INVREGULAR(space, *ch3);
				}
				*ch1 *= r_sign;
				*ch2 *= g_sign;
				*ch3 *= b_sign;
			} while (0));
		break;
	case LIBCOLOUR_ENCODING_TYPE_CUSTOM:
		LOOP((*ch1 = (space->TO_DECODED_RED)(*ch1),
		      *ch2 = (space->TO_DECODED_GREEN)(*ch2),
		      *ch3 = (space->TO_DECODED_BLUE)(*ch3)));
		break;
	default:
		fprintf(stderr, "libcolour: invalid encoding type\n");
		abort();
	}
}

static int
rgb_same_transfer(const libcolour_rgb_t *a, const libcolour_rgb_t *b)
{
	if (a->encoding_type != b->encoding_type)
		return 0;
	switch (a->encoding_type) {
	case LIBCOLOUR_ENCODING_TYPE_SIMPLE:
		return a->GAMMA == b->GAMMA;
	case LIBCOLOUR_ENCODING_TYPE_REGULAR:
		return a->GAMMA      == b->GAMMA  &&
		       a->OFFSET     == b->OFFSET &&
		       a->SLOPE      == b->SLOPE  &&
		       a->TRANSITION == b->TRANSITION;
	case LIBCOLOUR_ENCODING_TYPE_CUSTOM:
		return a->TO_ENCODED_RED   == b->TO_ENCODED_RED   &&
		       a->TO_ENCODED_GREEN == b->TO_ENCODED_GREEN &&
		       a->TO_ENCODED_BLUE  == b->TO_ENCODED_BLUE  &&
		       a->TO_DECODED_RED   == b->TO_DECODED_RED   &&
		       a->TO_DECODED_GREEN == b->TO_DECODED_GREEN &&
		       a->TO_DECODED_BLUE  == b->TO_DECODED_BLUE;
	default:
		return 1;
	}
}

static void
to_rgb(PARAMETERS(colour, rgb))
{
	int have_transfer = 0, with_transfer = to->with_transfer;
	switch (from->model) {
	case LIBCOLOUR_RGB:
		if (!memcmp(from->rgb.M, to->M, sizeof(TYPE[3][3]))) {
			have_transfer = from->rgb.with_transfer;
			if (have_transfer && with_transfer && !rgb_same_transfer(&from->rgb, to)) {
				rgb_decode(XARGUMENTS, &from->rgb);
				have_transfer = 0;
			}
			break;
		}
		/* fall through */
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIEXYZ:
		conversion_by_matrix(XARGUMENTS, CIEXYZ_TO_RGB(to->Minv));
		break;
	}

	if (have_transfer != with_transfer) {
		if (with_transfer)
			rgb_encode(XARGUMENTS, to);
		else
			rgb_decode(XARGUMENTS, &from->rgb);
	}
}


static void
to_srgb(PARAMETERS(colour, srgb))
{
	libcolour_srgb_t tmp;
	switch (from->model) {
	DEFAULT_CONVERSION(YCGCO, SRGB);
	DEFAULT_CONVERSION(YDBDR, SRGB);
	DEFAULT_CONVERSION(YES, SRGB);
	DEFAULT_CONVERSION(YIQ, SRGB);
	DEFAULT_CONVERSION(YUV, SRGB);
	DEFAULT_CONV_0(YPBPR, SRGB);
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, SRGB);
	case LIBCOLOUR_SRGB:
	srgb_to_srgb:
		if (from->srgb.with_transfer != to->with_transfer) {
			if (to->with_transfer) {
				LOOP((*ch1 = srgb_encode(*ch1),
				      *ch2 = srgb_encode(*ch2),
				      *ch3 = srgb_encode(*ch3)));
			} else {
				LOOP((*ch1 = srgb_decode(*ch1),
				      *ch2 = srgb_decode(*ch2),
				      *ch3 = srgb_decode(*ch3)));
			}
		}
		return;
	}
	if (to->with_transfer) {
		tmp = *to;
		tmp.with_transfer = 0;
		from = (const void *)&tmp;
		goto srgb_to_srgb;
	}
}


static void
to_ciexyy(PARAMETERS(colour, ciexyy))
{
	libcolour_srgb_t tmp;
	size_t m, old_n;
beginning:
	m = old_n = n;
	switch (from->model) {
	case LIBCOLOUR_CIEXYY:
		break;
	case LIBCOLOUR_SRGB:
		tmp.model = LIBCOLOUR_SRGB;
		tmp.with_transfer = 0;
		if (from->srgb.with_transfer)
			to_srgb(ARGUMENTS((const libcolour_colour_t *)from, &tmp));
		if (!*ch1 && !*ch2 && !*ch3) {
			for (m = 1; m < n && !ch1[m * width] && !ch2[m * width] && !ch3[m * width]; m++);
			n = m;
			LOOP((*ch1 = D(0.31272660439158),
			      *ch2 = D(0.32902315240275),
			      *ch3 = 0));
			n = old_n;
			break;
		} else {
			for (m = 1; m < n && (ch1[m * width] || ch2[m * width] || ch3[m * width]); m++);
			n = m;
			from = (const void *)&tmp;
		}
		/* fall through */
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIEXYZ:
		CONV_0(CIEXYZ_TO_CIEXYY);
		break;
	}
	if (m != old_n) {
		n = old_n - m;
		ch1 += m * width;
		ch2 += m * width;
		ch3 += m * width;
		goto beginning;
	}
}


static void
to_ciexyz(PARAMETERS(colour, ciexyz))
{
	libcolour_colour_t tmp;
	switch (from->model) {
	DEFAULT_CONVERSION(YCGCO, CIEXYZ);
	DEFAULT_CONVERSION(YDBDR, CIEXYZ);
	DEFAULT_CONVERSION(YES, CIEXYZ);
	DEFAULT_CONVERSION(YIQ, CIEXYZ);
	DEFAULT_CONVERSION(YPBPR, CIEXYZ);
	DEFAULT_CONVERSION(YUV, CIEXYZ);
	case LIBCOLOUR_RGB:
		if (from->rgb.with_transfer) {
			tmp.rgb = from->rgb;
			tmp.rgb.with_transfer = 0;
			to_rgb(ARGUMENTS(from, &tmp.rgb));
		}
		conversion_by_matrix(XARGUMENTS, RGB_TO_CIEXYZ(from->rgb.M));
		break;
	default:
		tmp.srgb.model = LIBCOLOUR_SRGB;
		tmp.srgb.with_transfer = 0;
		to_srgb(ARGUMENTS(from, &tmp.srgb));
		from = (const void *)&tmp.srgb;
		/* fall through */
	DEFAULT_CONVERSION_FROM_SRGB(CIEXYZ);
	case LIBCOLOUR_CIEXYY:
		CONV_0(CIEXYY_TO_CIEXYZ);
		break;
	case LIBCOLOUR_CIEXYZ:
		break;
	case LIBCOLOUR_CIELAB:
		CONV_0(CIELAB_TO_CIEXYZ);
		break;
	case LIBCOLOUR_CIELCHUV:
		tmp.cieluv.model = LIBCOLOUR_CIELUV;
		tmp.cieluv.white = from->cielchuv.white;
		to_cieluv(ARGUMENTS(from, &tmp.cieluv));
		from = (const void *)&tmp.cieluv;
		/* fall through */
	case LIBCOLOUR_CIELUV:
		CONV_N(CIELUV_TO_CIEXYZ, from->cieluv.white.X, from->cieluv.white.Y, from->cieluv.white.Z);
		break;
	case LIBCOLOUR_CIEUVW:
		tmp.cie1960ucs.model = LIBCOLOUR_CIE1960UCS;
		to_cie1960ucs(ARGUMENTS(from, &tmp.cie1960ucs));
		from = (const void *)&tmp.cie1960ucs;
		/* fall through */
	case LIBCOLOUR_CIE1960UCS:
		CONV_0(CIE1960UCS_TO_CIEXYZ);
		break;
	}
}


static void
to_cielab(PARAMETERS(colour, cielab))
{
	switch (from->model) {
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIEXYZ:
		CONV_0(CIEXYZ_TO_CIELAB);
		break;
	case LIBCOLOUR_CIELAB:
		break;
	}
}


static void
to_cieluv(PARAMETERS(colour, cieluv))
{
	libcolour_ciexyz_t tmp;
	libcolour_cielchuv_t tmp2;
	TYPE m;
	switch (from->model) {
	case LIBCOLOUR_CIELCHUV:
		if (to->white.X != from->cielchuv.white.X ||
		    to->white.Y != from->cielchuv.white.Y ||
		    to->white.Z != from->cielchuv.white.Z) {
			tmp.model = LIBCOLOUR_CIEXYZ;
			tmp2.model = LIBCOLOUR_CIELCHUV;
			tmp2.white = to->white;
			tmp2.one_revolution = PI2;
			to_ciexyz(ARGUMENTS(from, &tmp));
			to_cielchuv(ARGUMENTS((const libcolour_colour_t *)&tmp, &tmp2));
		} else if (from->cielchuv.one_revolution != PI2) {
			m = PI2 / from->cielchuv.one_revolution;
			SIMPLE_LOOP(ch3, *ch3 *= m);
		}
		CONV_0(CIELCHUV_TO_CIELUV);
		break;
	case LIBCOLOUR_CIELUV:
		if (to->white.X == from->cieluv.white.X &&
		    to->white.Y == from->cieluv.white.Y &&
		    to->white.Z == from->cieluv.white.Z)
			break;
		/* fall through */
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIEXYZ:
		CONV_N(CIEXYZ_TO_CIELUV, to->white.X, to->white.Y, to->white.Z);
		break;
	}
}


static void
to_cielchuv(PARAMETERS(colour, cielchuv))
{
	libcolour_cieluv_t tmp1, tmp2;
	TYPE m;
	switch (from->model) {
	case LIBCOLOUR_CIELCHUV:
		if (to->white.X == from->cielchuv.white.X &&
		    to->white.Y == from->cielchuv.white.Y &&
		    to->white.Z == from->cielchuv.white.Z) {
			if (to->one_revolution != from->cielchuv.one_revolution) {
				m = to->one_revolution;
				m /= from->cielchuv.one_revolution;
				SIMPLE_LOOP(ch3, *ch3 *= m);
			}
			break;
		}
		/* fall through */
	default:
		tmp1.model = LIBCOLOUR_CIELUV;
		tmp1.white = to->white;
		to_cieluv(ARGUMENTS(from, &tmp1));
		from = (const void *)&tmp1;
		/* fall through */
	case LIBCOLOUR_CIELUV:
		if (to->white.X != from->cieluv.white.X ||
		    to->white.Y != from->cieluv.white.Y ||
		    to->white.Z != from->cieluv.white.Z) {
			tmp2.model = LIBCOLOUR_CIELUV;
			tmp2.white = to->white;
			to_cieluv(ARGUMENTS(from, &tmp2));
			from = (const void *)&tmp2;
		}
		CONV_N(CIELUV_TO_CIELCHUV, to->one_revolution);
		break;
	}
}


static void
to_yiq(PARAMETERS(colour, yiq))
{
	switch (from->model) {
	DEFAULT_CONVERSION_FROM_SRGB(YIQ);
	DEFAULT_CONVERSION(YCGCO, YIQ);
	DEFAULT_CONVERSION(YDBDR, YIQ);
	DEFAULT_CONVERSION(YES, YIQ);
	DEFAULT_CONVERSION(YPBPR, YIQ);
	DEFAULT_CONVERSION(YUV, YIQ);
	case LIBCOLOUR_YIQ:
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YIQ);
	}
}


static void
to_ydbdr(PARAMETERS(colour, ydbdr))
{
	switch (from->model) {
	DEFAULT_CONVERSION_FROM_SRGB(YDBDR);
	DEFAULT_CONVERSION(YCGCO, YDBDR);
	DEFAULT_CONVERSION(YES, YDBDR);
	DEFAULT_CONVERSION(YIQ, YDBDR);
	DEFAULT_CONVERSION(YPBPR, YDBDR);
	case LIBCOLOUR_YDBDR:
		break;
	case LIBCOLOUR_YUV:
		CONV_0(YUV_TO_YDBDR);
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YDBDR);
	}
}


static void
to_yuv(PARAMETERS(colour, yuv))
{
	switch (from->model) {
	DEFAULT_CONVERSION_FROM_SRGB(YUV);
	DEFAULT_CONVERSION(YCGCO, YUV);
	DEFAULT_CONVERSION(YES, YUV);
	DEFAULT_CONVERSION(YIQ, YUV);
	DEFAULT_CONVERSION(YPBPR, YUV);
	case LIBCOLOUR_YUV:
		break;
	case LIBCOLOUR_YDBDR:
		CONV_0(YDBDR_TO_YUV);
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YUV);
	}
}


static void
to_ypbpr(PARAMETERS(colour, ypbpr))
{
	switch (from->model) {
	DEFAULT_CONVERSION(YCGCO, YPBPR);
	DEFAULT_CONVERSION(YDBDR, YPBPR);
	DEFAULT_CONVERSION(YES, YPBPR);
	DEFAULT_CONVERSION(YIQ, YPBPR);
	DEFAULT_CONVERSION(YUV, YPBPR);
	case LIBCOLOUR_YPBPR:
		break;
	case LIBCOLOUR_SRGB:
		LINEAR_SRGB;
		CONV_0(SRGB_TO_YPBPR);
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YPBPR);
	}
}


static void
to_ycgco(PARAMETERS(colour, ycgco))
{
	switch (from->model) {
	DEFAULT_CONVERSION_FROM_SRGB(YCGCO);
	DEFAULT_CONVERSION(YDBDR, YCGCO);
	DEFAULT_CONVERSION(YES, YCGCO);
	DEFAULT_CONVERSION(YIQ, YCGCO);
	DEFAULT_CONVERSION(YPBPR, YCGCO);
	DEFAULT_CONVERSION(YUV, YCGCO);
	case LIBCOLOUR_YCGCO:
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YCGCO);
	}
}


static void
to_cie1960ucs(PARAMETERS(colour, cie1960ucs))
{
	switch (from->model) {
	case LIBCOLOUR_CIE1960UCS:
		break;
	case LIBCOLOUR_CIEUVW:
		CONV_N(CIEUVW_TO_CIE1960UCS, from->cieuvw.u0, from->cieuvw.v0);
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIEXYZ:
		CONV_0(CIEXYZ_TO_CIE1960UCS);
		break;
	}
}


static void
to_cieuvw(PARAMETERS(colour, cieuvw))
{
	switch (from->model) {
	case LIBCOLOUR_CIEUVW:
		CONV_N(CIEUVW_TO_CIEUVW, from->cieuvw.u0, from->cieuvw.v0, to->u0, to->v0);
		break;
	default:
		to_cie1960ucs(ARGUMENTS(from, NULL));
		/* fall through */
	case LIBCOLOUR_CIE1960UCS:
		CONV_N(CIE1960UCS_TO_CIEUVW, to->u0, to->v0);
		break;
	}
}

static void
to_yes(PARAMETERS(colour, yes))
{
	switch (from->model) {
	DEFAULT_CONVERSION_FROM_SRGB(YES);
	DEFAULT_CONVERSION(YCGCO, YES);
	DEFAULT_CONVERSION(YDBDR, YES);
	DEFAULT_CONVERSION(YIQ, YES);
	DEFAULT_CONVERSION(YPBPR, YES);
	DEFAULT_CONVERSION(YUV, YES);
	case LIBCOLOUR_YES:
		break;
	default:
		to_ciexyz(ARGUMENTS(from, NULL));
		/* fall through */
	DEFAULT_CONVERSION(CIEXYZ, YES);
	}
}


int
libcolour_convert_en_masse(const libcolour_colour_t *from, const libcolour_colour_t *to,
			   libcolour_convert_en_masse_mode_t mode, size_t n, ...)
{
	libcolour_convert_en_masse_mode_t alpha_mode = mode & 3;
	int on_cpu = mode & LIBCOLOUR_CONVERT_EN_MASSE_ON_CPU;
	int no_override = mode & LIBCOLOUR_CONVERT_EN_MASSE_NO_OVERRIDE;
	va_list args;
	TYPE *in1, *in2, *in3, *in_alpha = NULL;
	TYPE *ch1, *ch2, *ch3, *ch_alpha = NULL;
	size_t width;

	if ((unsigned int)mode > 15U) {
		errno = EINVAL;
		return -1;
	}
	if (n < 0) {
		errno = EINVAL;
		return -1;
	}

	va_start(args, n);

	if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_NO_ALPHA) {
		in1 = va_arg(args, TYPE *);
		in2 = in1 + 1;
		in3 = in1 + 2;
		width = 3;
	} else if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_ALPHA_FIRST) {
		in_alpha = va_arg(args, TYPE *);
		in1 = in_alpha + 1;
		in2 = in_alpha + 2;
		in3 = in_alpha + 3;
		width = 4;
	} else if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_ALPHA_LAST) {
		in1 = va_arg(args, TYPE *);
		in2 = in1 + 1;
		in3 = in1 + 2;
		in_alpha = in1 + 3;
		width = 4;
	} else {
		in1 = va_arg(args, TYPE *);
		in2 = va_arg(args, TYPE *);
		in3 = va_arg(args, TYPE *);
		width = 1;
	}

	if (!no_override) {
		ch1 = in1;
		ch2 = in2;
		ch3 = in3;
		ch_alpha = in_alpha;
	} else if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_NO_ALPHA) {
		ch1 = va_arg(args, TYPE *);
		ch2 = ch1 + 1;
		ch3 = ch1 + 2;
		memcpy(ch1, in1, n * 3 * sizeof(TYPE));
	} else if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_ALPHA_FIRST) {
		ch_alpha = va_arg(args, TYPE *);
		ch1 = ch_alpha + 1;
		ch2 = ch_alpha + 2;
		ch3 = ch_alpha + 3;
		memcpy(ch_alpha, in_alpha, n * 4 * sizeof(TYPE));
	} else if (alpha_mode == LIBCOLOUR_CONVERT_EN_MASSE_ALPHA_LAST) {
		ch1 = va_arg(args, TYPE *);
		ch2 = ch1 + 1;
		ch3 = ch1 + 2;
		ch_alpha = ch1 + 3;
		memcpy(ch1, in1, n * 4 * sizeof(TYPE));
	} else {
		ch1 = va_arg(args, TYPE *);
		ch2 = va_arg(args, TYPE *);
		ch3 = va_arg(args, TYPE *);
		memcpy(ch1, in1, n * sizeof(TYPE));
		memcpy(ch2, in2, n * sizeof(TYPE));
		memcpy(ch3, in3, n * sizeof(TYPE));
	}

	va_end(args);

	switch (from->model) {
#define X(C, T, N) case C: break;
	LIST_MODELS(X)
#undef X
	default:
		errno = EINVAL;
		return -1;
	}

	switch (to->model) {
#define X(C, T, N)\
		case C:\
			to_##N(ARGUMENTS(from, &to->N));\
			break;
		LIST_MODELS(X)
#undef X
	default:
		errno = EINVAL;
		return -1;
	}

	(void) on_cpu;
	return 0;
}