diff --git a/ChangeLog b/ChangeLog index 5b5f57f2..25b0a583 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,7 +6,7 @@ im_icc_import*(), im_icc_export*(), im_icc_transform*(), im_dE_fromLab(), im_dECMC_fromLab(), im_dE00_from_Lab() as classes -- added vips_colour_convert(), replaces all derived conversions +- added vips_colourspace(), replaces all derived conversions - faster and more accurate sRGB <-> XYZ conversion - rename UCS colourspace as CMC - dzsave can write zoomify and google maps layout as well diff --git a/TODO b/TODO index 6eff1405..6827fbeb 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,9 @@ - add something to dzsave to control pyramid depth ... can then use it for straight tiling -- add mono as a colourspace? also rad? +- add mono as a colourspace? also rad? unpack to RGB with rad2float - move colour_convert into a separate file - -- im_icc_ac2rc needs doing once we have a general 'convert' operator - -- test new icc stuff with lcms1 and lcms2 + grey16 too? rgb16? - should we have a better thing to stop convert_saveable running for vips images? diff --git a/libvips/colour/Makefile.am b/libvips/colour/Makefile.am index 29982c46..0a3af785 100644 --- a/libvips/colour/Makefile.am +++ b/libvips/colour/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libcolour.la libcolour_la_SOURCES = \ colour.c \ + colourspace.c \ colour_dispatch.c \ dE76.c \ dE00.c \ diff --git a/libvips/colour/colour.c b/libvips/colour/colour.c index 8ad92e56..ea2990e6 100644 --- a/libvips/colour/colour.c +++ b/libvips/colour/colour.c @@ -494,12 +494,10 @@ vips_colour_difference_build( VipsObject *object ) right, 3 ) ) return( -1 ); - if( vips_colour_convert( left, &t[6], - difference->interpretation, NULL ) ) + if( vips_colourspace( left, &t[6], difference->interpretation, NULL ) ) return( -1 ); left = t[6]; - if( vips_colour_convert( right, &t[7], - difference->interpretation, NULL ) ) + if( vips_colourspace( right, &t[7], difference->interpretation, NULL ) ) return( -1 ); right = t[7]; @@ -585,250 +583,13 @@ vips_colour_difference_init( VipsColourDifference *difference ) colour->bands = 1; } -/* A colour-transforming function. - */ -typedef int (*VipsColourTransformFn)( VipsImage *in, VipsImage **out, ... ); - -/* Maximum number of steps we allow in a route. 10 steps should be enough - * for anyone. - */ -#define MAX_STEPS (10) - -/* A route between two colour spaces. - * - * 10 steps should be enough for anyone. - */ -typedef struct _VipsColourRoute { - VipsInterpretation from; - VipsInterpretation to; - VipsColourTransformFn route[MAX_STEPS + 1]; -} VipsColourRoute; - -/* Some defines to save typing. These are the colour spaces we support - * conversions between. - */ -#define XYZ VIPS_INTERPRETATION_XYZ -#define LAB VIPS_INTERPRETATION_LAB -#define LABQ VIPS_INTERPRETATION_LABQ -#define LCH VIPS_INTERPRETATION_LCH -#define CMC VIPS_INTERPRETATION_CMC -#define LABS VIPS_INTERPRETATION_LABS -#define sRGB VIPS_INTERPRETATION_sRGB -#define YXY VIPS_INTERPRETATION_YXY - -/* All the routes we know about. - */ -static VipsColourRoute vips_colour_routes[] = { - { XYZ, LAB, { vips_XYZ2Lab, NULL } }, - { XYZ, LABQ, { vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, - { XYZ, LCH, { vips_XYZ2Lab, vips_Lab2LCh, NULL } }, - { XYZ, CMC, { vips_XYZ2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, - { XYZ, LABS, { vips_XYZ2Lab, vips_Lab2LabS, NULL } }, - { XYZ, sRGB, { vips_XYZ2sRGB, NULL } }, - { XYZ, YXY, { vips_XYZ2Yxy, NULL } }, - - { LAB, XYZ, { vips_Lab2XYZ, NULL } }, - { LAB, LABQ, { vips_Lab2LabQ, NULL } }, - { LAB, LCH, { vips_Lab2LCh, NULL } }, - { LAB, CMC, { vips_Lab2LCh, vips_LCh2CMC, NULL } }, - { LAB, LABS, { vips_Lab2LabS, NULL } }, - { LAB, sRGB, { vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, - { LAB, YXY, { vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, - - { LABQ, XYZ, { vips_LabQ2Lab, vips_Lab2XYZ, NULL } }, - { LABQ, LAB, { vips_LabQ2Lab, NULL } }, - { LABQ, LCH, { vips_LabQ2Lab, vips_Lab2LCh, NULL } }, - { LABQ, CMC, { vips_LabQ2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, - { LABQ, LABS, { vips_LabQ2LabS, NULL } }, - { LABQ, sRGB, { vips_LabQ2sRGB, NULL } }, - { LABQ, YXY, { vips_LabQ2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, - - { LCH, XYZ, { vips_LCh2Lab, vips_Lab2XYZ, NULL } }, - { LCH, LAB, { vips_LCh2Lab, NULL } }, - { LCH, LABQ, { vips_LCh2Lab, vips_Lab2LabQ, NULL } }, - { LCH, CMC, { vips_LCh2CMC, NULL } }, - { LCH, LABS, { vips_LCh2Lab, vips_Lab2LabS, NULL } }, - { LCH, sRGB, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, - { LCH, YXY, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, - - { CMC, XYZ, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, NULL } }, - { CMC, LAB, { vips_CMC2LCh, vips_LCh2Lab, NULL } }, - { CMC, LABQ, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2LabQ, NULL } }, - { CMC, LCH, { vips_CMC2LCh, NULL } }, - { CMC, LABS, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2LabS, NULL } }, - { CMC, sRGB, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, - vips_XYZ2sRGB, NULL } }, - { CMC, YXY, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, - vips_XYZ2Yxy, NULL } }, - - { LABS, XYZ, { vips_LabS2Lab, vips_Lab2XYZ, NULL } }, - { LABS, LAB, { vips_LabS2Lab, NULL } }, - { LABS, LABQ, { vips_LabS2LabQ, NULL } }, - { LABS, LCH, { vips_LabS2Lab, vips_Lab2LCh, NULL } }, - { LABS, CMC, { vips_LabS2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, - { LABS, sRGB, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, - { LABS, YXY, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, - - { sRGB, XYZ, { vips_sRGB2XYZ, NULL } }, - { sRGB, LAB, { vips_sRGB2XYZ, vips_XYZ2Lab, NULL } }, - { sRGB, LABQ, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, - { sRGB, LCH, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, - { sRGB, CMC, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, - vips_LCh2CMC, NULL } }, - { sRGB, LABS, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, - { sRGB, YXY, { vips_sRGB2XYZ, vips_XYZ2Yxy, NULL } }, - - { YXY, XYZ, { vips_Yxy2XYZ, NULL } }, - { YXY, LAB, { vips_Yxy2XYZ, vips_XYZ2Lab, NULL } }, - { YXY, LABQ, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, - { YXY, LCH, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, - { YXY, CMC, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, - vips_LCh2CMC, NULL } }, - { YXY, LABS, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, - { YXY, sRGB, { vips_Yxy2XYZ, vips_XYZ2sRGB, NULL } }, -}; - -typedef struct _VipsColourConvert { - VipsOperation parent_instance; - - VipsImage *in; - VipsImage *out; - VipsInterpretation space; -} VipsColourConvert; - -typedef VipsOperationClass VipsColourConvertClass; - -G_DEFINE_TYPE( VipsColourConvert, vips_colour_convert, VIPS_TYPE_OPERATION ); - -static int -vips_colour_convert_build( VipsObject *object ) -{ - VipsColourConvert *convert = (VipsColourConvert *) object; - - int i, j; - VipsImage *x; - VipsImage **t; - - VipsInterpretation interpretation; - - t = (VipsImage **) vips_object_local_array( object, MAX_STEPS ); - - /* Verify that all input args have been set. - */ - if( VIPS_OBJECT_CLASS( vips_colour_convert_parent_class )-> - build( object ) ) - return( -1 ); - - interpretation = vips_image_guess_interpretation( convert->in ); - - /* No conversion necessary. - */ - if( interpretation == convert->space ) { - g_object_set( convert, "out", vips_image_new(), NULL ); - - return( vips_image_write( convert->in, convert->out ) ); - } - - x = convert->in; - - for( i = 0; i < VIPS_NUMBER( vips_colour_routes ); i++ ) - if( vips_colour_routes[i].from == interpretation && - vips_colour_routes[i].to == convert->space ) - break; - if( i == VIPS_NUMBER( vips_colour_routes ) ) { - vips_error( "vips_colour_convert", - _( "no known route between '%s' and '%s'" ), - vips_enum_nick( VIPS_TYPE_INTERPRETATION, - interpretation ), - vips_enum_nick( VIPS_TYPE_INTERPRETATION, - convert->space ) ); - return( -1 ); - } - - for( j = 0; vips_colour_routes[i].route[j]; j++ ) { - if( vips_colour_routes[i].route[j]( x, &t[j], NULL ) ) - return( -1 ); - x = t[j]; - } - - g_object_set( convert, "out", vips_image_new(), NULL ); - if( vips_image_write( x, convert->out ) ) - return( -1 ); - - return( 0 ); -} - -static void -vips_colour_convert_class_init( VipsColourConvertClass *class ) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); - VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); - - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - - vobject_class->nickname = "colour_convert"; - vobject_class->description = _( "convert to a new colourspace" ); - vobject_class->build = vips_colour_convert_build; - - VIPS_ARG_IMAGE( class, "in", 1, - _( "Output" ), - _( "Output image" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsColourConvert, in ) ); - - VIPS_ARG_IMAGE( class, "out", 2, - _( "Output" ), - _( "Output image" ), - VIPS_ARGUMENT_REQUIRED_OUTPUT, - G_STRUCT_OFFSET( VipsColourConvert, out ) ); - - VIPS_ARG_ENUM( class, "space", 6, - _( "Space" ), - _( "Destination colour space" ), - VIPS_ARGUMENT_REQUIRED_INPUT, - G_STRUCT_OFFSET( VipsColourConvert, space ), - VIPS_TYPE_INTERPRETATION, VIPS_INTERPRETATION_sRGB ); -} - -static void -vips_colour_convert_init( VipsColourConvert *convert ) -{ -} - -/** - * vips_colour_convert: - * @in: input image - * @out: output image - * @space: convert to this colour space - * - * This convenience function looks at the interpretation field of @in and runs - * a set of colourspace conversion functions to move it to @space. - * - * For example, given an image tagged as #VIPS_INTERPRETATION_YXY, running - * vips_colour_convert() with @space set to #VIPS_INTERPRETATION_LAB will - * convert with vips_Yxy2XYZ() and vips_XYZ2Lab(). - */ -int -vips_colour_convert( VipsImage *in, VipsImage **out, - VipsInterpretation space, ... ) -{ - va_list ap; - int result; - - va_start( ap, space ); - result = vips_call_split( "colour_convert", ap, in, out, space ); - va_end( ap ); - - return( result ); -} - /* Called from iofuncs to init all operations in this dir. Use a plugin system * instead? */ void vips_colour_operation_init( void ) { + extern GType vips_colourspace_get_type( void ); extern GType vips_Lab2XYZ_get_type( void ); extern GType vips_XYZ2Lab_get_type( void ); extern GType vips_Lab2LCh_get_type( void ); @@ -857,7 +618,7 @@ vips_colour_operation_init( void ) extern GType vips_dE00_get_type( void ); extern GType vips_dECMC_get_type( void ); - vips_colour_convert_get_type(); + vips_colourspace_get_type(); vips_Lab2XYZ_get_type(); vips_XYZ2Lab_get_type(); vips_Lab2LCh_get_type(); diff --git a/libvips/colour/colourspace.c b/libvips/colour/colourspace.c new file mode 100644 index 00000000..c892df4b --- /dev/null +++ b/libvips/colour/colourspace.c @@ -0,0 +1,284 @@ +/* convert between colourspaces + */ + +/* + + Copyright (C) 1991-2005 The National Gallery + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include + +#include "colour.h" + +/* A colour-transforming function. + */ +typedef int (*VipsColourTransformFn)( VipsImage *in, VipsImage **out, ... ); + +/* Maximum number of steps we allow in a route. 10 steps should be enough + * for anyone. + */ +#define MAX_STEPS (10) + +/* A route between two colour spaces. + * + * 10 steps should be enough for anyone. + */ +typedef struct _VipsColourRoute { + VipsInterpretation from; + VipsInterpretation to; + VipsColourTransformFn route[MAX_STEPS + 1]; +} VipsColourRoute; + +/* Some defines to save typing. These are the colour spaces we support + * conversions between. + */ +#define XYZ VIPS_INTERPRETATION_XYZ +#define LAB VIPS_INTERPRETATION_LAB +#define LABQ VIPS_INTERPRETATION_LABQ +#define LCH VIPS_INTERPRETATION_LCH +#define CMC VIPS_INTERPRETATION_CMC +#define LABS VIPS_INTERPRETATION_LABS +#define sRGB VIPS_INTERPRETATION_sRGB +#define YXY VIPS_INTERPRETATION_YXY + +/* All the routes we know about. + */ +static VipsColourRoute vips_colour_routes[] = { + { XYZ, LAB, { vips_XYZ2Lab, NULL } }, + { XYZ, LABQ, { vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { XYZ, LCH, { vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { XYZ, CMC, { vips_XYZ2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, + { XYZ, LABS, { vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { XYZ, sRGB, { vips_XYZ2sRGB, NULL } }, + { XYZ, YXY, { vips_XYZ2Yxy, NULL } }, + + { LAB, XYZ, { vips_Lab2XYZ, NULL } }, + { LAB, LABQ, { vips_Lab2LabQ, NULL } }, + { LAB, LCH, { vips_Lab2LCh, NULL } }, + { LAB, CMC, { vips_Lab2LCh, vips_LCh2CMC, NULL } }, + { LAB, LABS, { vips_Lab2LabS, NULL } }, + { LAB, sRGB, { vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LAB, YXY, { vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { LABQ, XYZ, { vips_LabQ2Lab, vips_Lab2XYZ, NULL } }, + { LABQ, LAB, { vips_LabQ2Lab, NULL } }, + { LABQ, LCH, { vips_LabQ2Lab, vips_Lab2LCh, NULL } }, + { LABQ, CMC, { vips_LabQ2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, + { LABQ, LABS, { vips_LabQ2LabS, NULL } }, + { LABQ, sRGB, { vips_LabQ2sRGB, NULL } }, + { LABQ, YXY, { vips_LabQ2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { LCH, XYZ, { vips_LCh2Lab, vips_Lab2XYZ, NULL } }, + { LCH, LAB, { vips_LCh2Lab, NULL } }, + { LCH, LABQ, { vips_LCh2Lab, vips_Lab2LabQ, NULL } }, + { LCH, CMC, { vips_LCh2CMC, NULL } }, + { LCH, LABS, { vips_LCh2Lab, vips_Lab2LabS, NULL } }, + { LCH, sRGB, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LCH, YXY, { vips_LCh2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { CMC, XYZ, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, NULL } }, + { CMC, LAB, { vips_CMC2LCh, vips_LCh2Lab, NULL } }, + { CMC, LABQ, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2LabQ, NULL } }, + { CMC, LCH, { vips_CMC2LCh, NULL } }, + { CMC, LABS, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2LabS, NULL } }, + { CMC, sRGB, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, + vips_XYZ2sRGB, NULL } }, + { CMC, YXY, { vips_CMC2LCh, vips_LCh2Lab, vips_Lab2XYZ, + vips_XYZ2Yxy, NULL } }, + + { LABS, XYZ, { vips_LabS2Lab, vips_Lab2XYZ, NULL } }, + { LABS, LAB, { vips_LabS2Lab, NULL } }, + { LABS, LABQ, { vips_LabS2LabQ, NULL } }, + { LABS, LCH, { vips_LabS2Lab, vips_Lab2LCh, NULL } }, + { LABS, CMC, { vips_LabS2Lab, vips_Lab2LCh, vips_LCh2CMC, NULL } }, + { LABS, sRGB, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2sRGB, NULL } }, + { LABS, YXY, { vips_LabS2Lab, vips_Lab2XYZ, vips_XYZ2Yxy, NULL } }, + + { sRGB, XYZ, { vips_sRGB2XYZ, NULL } }, + { sRGB, LAB, { vips_sRGB2XYZ, vips_XYZ2Lab, NULL } }, + { sRGB, LABQ, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { sRGB, LCH, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { sRGB, CMC, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LCh, + vips_LCh2CMC, NULL } }, + { sRGB, LABS, { vips_sRGB2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { sRGB, YXY, { vips_sRGB2XYZ, vips_XYZ2Yxy, NULL } }, + + { YXY, XYZ, { vips_Yxy2XYZ, NULL } }, + { YXY, LAB, { vips_Yxy2XYZ, vips_XYZ2Lab, NULL } }, + { YXY, LABQ, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabQ, NULL } }, + { YXY, LCH, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, NULL } }, + { YXY, CMC, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LCh, + vips_LCh2CMC, NULL } }, + { YXY, LABS, { vips_Yxy2XYZ, vips_XYZ2Lab, vips_Lab2LabS, NULL } }, + { YXY, sRGB, { vips_Yxy2XYZ, vips_XYZ2sRGB, NULL } }, +}; + +typedef struct _VipsColourspace { + VipsOperation parent_instance; + + VipsImage *in; + VipsImage *out; + VipsInterpretation space; +} VipsColourspace; + +typedef VipsOperationClass VipsColourspaceClass; + +G_DEFINE_TYPE( VipsColourspace, vips_colourspace, VIPS_TYPE_OPERATION ); + +static int +vips_colourspace_build( VipsObject *object ) +{ + VipsColourspace *colourspace = (VipsColourspace *) object; + + int i, j; + VipsImage *x; + VipsImage **t; + + VipsInterpretation interpretation; + + t = (VipsImage **) vips_object_local_array( object, MAX_STEPS ); + + /* Verify that all input args have been set. + */ + if( VIPS_OBJECT_CLASS( vips_colourspace_parent_class )-> + build( object ) ) + return( -1 ); + + interpretation = vips_image_guess_interpretation( colourspace->in ); + + /* No conversion necessary. + */ + if( interpretation == colourspace->space ) { + g_object_set( colourspace, "out", vips_image_new(), NULL ); + + return( vips_image_write( colourspace->in, colourspace->out ) ); + } + + x = colourspace->in; + + for( i = 0; i < VIPS_NUMBER( vips_colour_routes ); i++ ) + if( vips_colour_routes[i].from == interpretation && + vips_colour_routes[i].to == colourspace->space ) + break; + if( i == VIPS_NUMBER( vips_colour_routes ) ) { + vips_error( "vips_colourspace", + _( "no known route between '%s' and '%s'" ), + vips_enum_nick( VIPS_TYPE_INTERPRETATION, + interpretation ), + vips_enum_nick( VIPS_TYPE_INTERPRETATION, + colourspace->space ) ); + return( -1 ); + } + + for( j = 0; vips_colour_routes[i].route[j]; j++ ) { + if( vips_colour_routes[i].route[j]( x, &t[j], NULL ) ) + return( -1 ); + x = t[j]; + } + + g_object_set( colourspace, "out", vips_image_new(), NULL ); + if( vips_image_write( x, colourspace->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_colourspace_class_init( VipsColourspaceClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "colourspace"; + vobject_class->description = _( "convert to a new colourspace" ); + vobject_class->build = vips_colourspace_build; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsColourspace, in ) ); + + VIPS_ARG_IMAGE( class, "out", 2, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsColourspace, out ) ); + + VIPS_ARG_ENUM( class, "space", 6, + _( "Space" ), + _( "Destination colour space" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsColourspace, space ), + VIPS_TYPE_INTERPRETATION, VIPS_INTERPRETATION_sRGB ); +} + +static void +vips_colourspace_init( VipsColourspace *colourspace ) +{ +} + +/** + * vips_colourspace: + * @in: input image + * @out: output image + * @space: convert to this colour space + * + * This convenience function looks at the interpretation field of @in and runs + * a set of colourspace conversion functions to move it to @space. + * + * For example, given an image tagged as #VIPS_INTERPRETATION_YXY, running + * vips_colourspace() with @space set to #VIPS_INTERPRETATION_LAB will + * convert with vips_Yxy2XYZ() and vips_XYZ2Lab(). + */ +int +vips_colourspace( VipsImage *in, VipsImage **out, + VipsInterpretation space, ... ) +{ + va_list ap; + int result; + + va_start( ap, space ); + result = vips_call_split( "colourspace", ap, in, out, space ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/colour/dECMC.c b/libvips/colour/dECMC.c index 489d6a02..dc43190d 100644 --- a/libvips/colour/dECMC.c +++ b/libvips/colour/dECMC.c @@ -83,7 +83,7 @@ vips_dECMC_init( VipsdECMC *dECMC ) * transform the two source images to CMC yourself, scale the channels * appropriately, and call this function. * - * See also: vips_colour_convert() + * See also: vips_colourspace() * * Returns: 0 on success, -1 on error */ diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c index e5f2fcb4..0073fd1b 100644 --- a/libvips/colour/icc_transform.c +++ b/libvips/colour/icc_transform.c @@ -973,15 +973,14 @@ vips_icc_transform( VipsImage *in, VipsImage **out, * Returns: 0 on success, -1 on error. */ int -im_icc_ac2rc( VipsImage *in, VipsImage *out, const char *profile_filename ) +vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ) { + VipsImage *t; cmsHPROFILE profile; double X, Y, Z; - - double add[3]; - double mul[3]; - - IMAGE *t[2]; + double *add; + double *mul; + int i; if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) ) return( -1 ); @@ -1018,41 +1017,36 @@ im_icc_ac2rc( VipsImage *in, VipsImage *out, const char *profile_filename ) cmsCloseProfile( profile ); - add[0] = 0.0; - add[1] = 0.0; - add[2] = 0.0; + /* We need XYZ so we can adjust the white balance. + */ + if( vips_colourspace( in, &t, VIPS_INTERPRETATION_XYZ, NULL ) ) + return( -1 ); + in = t; + + if( !(add = VIPS_ARRAY( in, in->Bands, double )) || + !(mul = VIPS_ARRAY( in, in->Bands, double )) ) + return( -1 ); + + /* There might be extra bands off to the right somewhere. + */ + for( i = 0; i < in->Bands; i++ ) + add[i] = 0.0; mul[0] = VIPS_D50_X0 / (X * 100.0); mul[1] = VIPS_D50_Y0 / (Y * 100.0); mul[2] = VIPS_D50_Z0 / (Z * 100.0); - /* Do IM_CODING_LABQ too. - */ - if( in->Coding == IM_CODING_LABQ ) { - IMAGE *t1 = im_open_local( out, "im_icc_ac2rc-1", "p" ); + for( i = 3; i < in->Bands; i++ ) + mul[i] = 1.0; - if( !t1 || im_LabQ2Lab( in, t1 ) ) - return( -1 ); - - in = t1; - } - - /* Do IM_CODING_RAD. - */ - if( in->Coding == IM_CODING_RAD ) { - IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" ); - - if( !t1 || im_rad2float( in, t1 ) ) - return( -1 ); - - in = t1; - } - - if( im_open_local_array( out, t, 2, "im_icc_ac2rc-2", "p" ) || - im_Lab2XYZ_temp( in, t[0], IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) || - im_lintra_vec( 3, mul, t[0], add, t[1] ) || - im_XYZ2Lab_temp( t[1], out, IM_D50_X0, IM_D50_Y0, IM_D50_Z0 ) ) + if( vips_linear( in, &t, add, mul, in->Bands, NULL ) ) { + g_object_unref( in ); return( -1 ); + } + g_object_unref( in ); + in = t; + + *out = in; return( 0 ); } diff --git a/libvips/deprecated/vips7compat.c b/libvips/deprecated/vips7compat.c index 3475dae4..d80670e3 100644 --- a/libvips/deprecated/vips7compat.c +++ b/libvips/deprecated/vips7compat.c @@ -2777,3 +2777,19 @@ im_dE00_fromLab( IMAGE *in1, IMAGE *in2, IMAGE *out ) return( 0 ); } + +int +im_icc_ac2rc( VipsImage *in, VipsImage *out, const char *profile_filename ) +{ + VipsImage *x; + + if( vips_icc_ac2rc( in, &x, profile_filename ) ) + return( -1 ); + if( im_copy( x, out ) ) { + g_object_unref( x ); + return( -1 ); + } + g_object_unref( x ); + + return( 0 ); +} diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index c23e0595..53baccfa 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1241,7 +1241,7 @@ vips_foreign_convert_saveable( VipsForeignSave *save ) if( in->Bands == 3 ) { VipsImage *out; - if( vips_colour_convert( in, &out, + if( vips_colourspace( in, &out, VIPS_INTERPRETATION_sRGB, NULL ) ) { g_object_unref( in ); return( -1 ); diff --git a/libvips/include/vips/colour.h b/libvips/include/vips/colour.h index b9aa438c..0ede6c05 100644 --- a/libvips/include/vips/colour.h +++ b/libvips/include/vips/colour.h @@ -110,7 +110,7 @@ typedef enum { VIPS_INTENT_ABSOLUTE } VipsIntent; -int vips_colour_convert( VipsImage *in, VipsImage **out, +int vips_colourspace( VipsImage *in, VipsImage **out, VipsInterpretation space, ... ) __attribute__((sentinel)); @@ -165,7 +165,7 @@ int vips_icc_import( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_icc_export( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); -int vips_icc_ac2rc( VipsImage *in, VipsImage *out, +int vips_icc_ac2rc( VipsImage *in, VipsImage **out, const char *profile_filename ); int vips_dE76( VipsImage *left, VipsImage *right, VipsImage **out, ... )