add lcms2 support
This commit is contained in:
parent
669dda83d2
commit
a3f8ddadfc
@ -8,6 +8,7 @@
|
||||
- im_divide spots /0
|
||||
- remove liboil dependency, we will use Orc instead
|
||||
- various small cleanups (thanks Tim)
|
||||
- add lcms2 support
|
||||
|
||||
12/5/10 started 7.22.2
|
||||
- the conditional image of ifthenelse can be any format, a (!=0) is added if
|
||||
|
7
TODO
7
TODO
@ -1,3 +1,8 @@
|
||||
|
||||
- use D65 in cmsCreateLab4Profile() ? not sure
|
||||
|
||||
- VImage("poop.v") should use "rd" as well? Python too?
|
||||
|
||||
- im_divide() can /0 for complex
|
||||
|
||||
- unlink temps earlier on *nix systems
|
||||
@ -21,8 +26,6 @@
|
||||
|
||||
|
||||
|
||||
- lcms2 needs testing
|
||||
|
||||
- tools subdirs are now pretty stupid :-( just have a single dir
|
||||
|
||||
- test
|
||||
|
20
configure.in
20
configure.in
@ -375,17 +375,23 @@ if test x"$with_magick" != "xno"; then
|
||||
LIBS=$save_LIBS
|
||||
fi
|
||||
|
||||
# lcms
|
||||
# lcms ... look for lcms2 first, it has better threading support
|
||||
AC_ARG_WITH([lcms],
|
||||
AS_HELP_STRING([--without-lcms], [build without lcms (default: test)]))
|
||||
|
||||
if test x"$with_lcms" != "xno"; then
|
||||
PKG_CHECK_MODULES(LCMS, lcms,
|
||||
[AC_DEFINE(HAVE_LCMS,1,[define if you have lcms installed.])
|
||||
PKG_CHECK_MODULES(LCMS, lcms2,
|
||||
[AC_DEFINE(HAVE_LCMS2,1,[define if you have lcms2 installed.])
|
||||
with_lcms=yes
|
||||
PACKAGES_USED="$PACKAGES_USED lcms"],
|
||||
[AC_MSG_WARN([lcms not found; disabling lcms support])
|
||||
with_lcms=no
|
||||
with_lcms_ver=lcms2
|
||||
PACKAGES_USED="$PACKAGES_USED lcms2"],
|
||||
[PKG_CHECK_MODULES(LCMS, lcms,
|
||||
[AC_DEFINE(HAVE_LCMS,1,[define if you have lcms installed.])
|
||||
with_lcms=yes
|
||||
with_lcms_ver=lcms
|
||||
PACKAGES_USED="$PACKAGES_USED lcms"],
|
||||
[AC_MSG_WARN([lcms2/lcms not found; disabling lcms support])
|
||||
with_lcms=no])
|
||||
])
|
||||
fi
|
||||
|
||||
@ -640,7 +646,7 @@ build docs with gtkdoc $enable_gtk_doc
|
||||
use fftw3 for FFT: $with_fftw3
|
||||
Magick package: $with_magickpackage
|
||||
file import with libMagick: $with_magick
|
||||
ICC profile support with lcms: $with_lcms
|
||||
ICC profile support with lcms: $with_lcms (version $with_lcms_ver)
|
||||
file import with OpenEXR: $with_OpenEXR
|
||||
file import with matio: $with_matio
|
||||
text rendering with pangoft2: $with_pangoft2
|
||||
|
@ -15,6 +15,8 @@
|
||||
* - small cleanups
|
||||
* - call attach_profile() before im_wrapone() so the profile will get
|
||||
* written if we are wrinting to a file
|
||||
* 2/8/10
|
||||
* - add lcms2
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -48,68 +50,7 @@
|
||||
#endif /*HAVE_CONFIG_H*/
|
||||
#include <vips/intl.h>
|
||||
|
||||
#ifndef HAVE_LCMS
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/thread.h>
|
||||
|
||||
int
|
||||
im_icc_present( void )
|
||||
{
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_transform( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename,
|
||||
const char *output_profile_filename,
|
||||
VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_transform", "%s",
|
||||
_( "lcms library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_import( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_import", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_import", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_export_depth( IMAGE *in, IMAGE *out, int depth,
|
||||
const char *output_profile_filename, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_export_depth", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename )
|
||||
{
|
||||
im_error( "im_icc_ac2rc", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined( HAVE_LCMS )
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
@ -896,4 +837,862 @@ im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename )
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
#elif defined( HAVE_LCMS2 )
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <lcms2.h>
|
||||
|
||||
#include <vips/vips.h>
|
||||
#include <vips/colour.h>
|
||||
#include <vips/region.h>
|
||||
#include <vips/util.h>
|
||||
|
||||
#ifdef WITH_DMALLOC
|
||||
#include <dmalloc.h>
|
||||
#endif /*WITH_DMALLOC*/
|
||||
|
||||
/**
|
||||
* VipsIntent:
|
||||
* @IM_INTENT_PERCEPTUAL:
|
||||
* @IM_INTENT_RELATIVE_COLORIMETRIC:
|
||||
* @IM_INTENT_SATURATION:
|
||||
* @IM_INTENT_ABSOLUTE_COLORIMETRIC:
|
||||
*
|
||||
* The rendering intent. #IM_INTENT_ABSOLUTE_COLORIMETRIC is best for
|
||||
* scientific work, #IM_INTENT_RELATIVE_COLORIMETRIC is usually best for
|
||||
* accurate communication with other imaging libraries.
|
||||
*/
|
||||
|
||||
/* Call lcms with up to this many pixels at once.
|
||||
*/
|
||||
#define PIXEL_BUFFER_SIZE (10000)
|
||||
|
||||
static const char *
|
||||
decode_intent( VipsIntent intent )
|
||||
{
|
||||
switch( intent ) {
|
||||
case IM_INTENT_PERCEPTUAL: return( "PERCEPTUAL" );
|
||||
case IM_INTENT_RELATIVE_COLORIMETRIC: return( "RELATIVE" );
|
||||
case IM_INTENT_SATURATION: return( "SATURATION" );
|
||||
case IM_INTENT_ABSOLUTE_COLORIMETRIC: return( "ABSOLUTE" );
|
||||
default: return( "<unknown>" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_present:
|
||||
*
|
||||
* VIPS can optionally be built without the ICC library. Use this function to
|
||||
* test for its availability.
|
||||
*
|
||||
* Returns: non-zero if the ICC library is present.
|
||||
*/
|
||||
int
|
||||
im_icc_present( void )
|
||||
{
|
||||
return( 1 );
|
||||
}
|
||||
|
||||
/* Global state for a transform.
|
||||
*/
|
||||
typedef struct {
|
||||
IMAGE *in;
|
||||
IMAGE *out;
|
||||
const char *input_profile_filename;
|
||||
const char *output_profile_filename;
|
||||
VipsIntent intent;
|
||||
|
||||
cmsHPROFILE in_profile;
|
||||
cmsHPROFILE out_profile;
|
||||
cmsHTRANSFORM trans;
|
||||
|
||||
/* We need to single-thread calls to LCMS.
|
||||
*/
|
||||
GMutex *lock;
|
||||
} Icc;
|
||||
|
||||
/* Error from lcms.
|
||||
*/
|
||||
static void
|
||||
icc_error( cmsContext context, cmsUInt32Number code, const char *text )
|
||||
{
|
||||
im_error( "im_icc", "%s", text );
|
||||
}
|
||||
|
||||
static int
|
||||
icc_destroy( Icc *icc )
|
||||
{
|
||||
IM_FREEF( cmsDeleteTransform, icc->trans );
|
||||
IM_FREEF( cmsCloseProfile, icc->in_profile );
|
||||
IM_FREEF( cmsCloseProfile, icc->out_profile );
|
||||
IM_FREEF( g_mutex_free, icc->lock );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static Icc *
|
||||
icc_new( IMAGE *in, IMAGE *out, VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
|
||||
/* Ask lcms not to abort on error.
|
||||
*/
|
||||
cmsSetLogErrorHandler( icc_error );
|
||||
|
||||
if( !(icc = IM_NEW( out, Icc )) )
|
||||
return( NULL );
|
||||
|
||||
icc->in = in;
|
||||
icc->out = out;
|
||||
icc->input_profile_filename = NULL;
|
||||
icc->output_profile_filename = NULL;
|
||||
icc->intent = intent;
|
||||
icc->in_profile = 0;
|
||||
icc->out_profile = 0;
|
||||
icc->trans = 0;
|
||||
icc->lock = g_mutex_new();
|
||||
|
||||
if( im_add_close_callback( out,
|
||||
(im_callback_fn) icc_destroy, icc, NULL ) )
|
||||
return( NULL );
|
||||
|
||||
return( icc );
|
||||
}
|
||||
|
||||
static Icc *
|
||||
icc_new_file( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename,
|
||||
const char *output_profile_filename,
|
||||
VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
cmsCIExyY white;
|
||||
|
||||
if( !(icc = icc_new( in, out, intent )) )
|
||||
return( NULL );
|
||||
|
||||
if( input_profile_filename ) {
|
||||
icc->input_profile_filename =
|
||||
im_strdup( out, input_profile_filename );
|
||||
if( !(icc->in_profile = cmsOpenProfileFromFile(
|
||||
input_profile_filename, "r" )) )
|
||||
im_error( "im_icc_transform",
|
||||
_( "unable to open profile \"%s\"" ),
|
||||
input_profile_filename );
|
||||
}
|
||||
|
||||
if( output_profile_filename ) {
|
||||
icc->output_profile_filename =
|
||||
im_strdup( out, output_profile_filename );
|
||||
if( !(icc->out_profile = cmsOpenProfileFromFile(
|
||||
output_profile_filename, "r" )) )
|
||||
im_error( "im_icc_transform",
|
||||
_( "unable to open profile \"%s\"" ),
|
||||
output_profile_filename );
|
||||
}
|
||||
|
||||
cmsWhitePointFromTemp( &white, 6500 );
|
||||
if( !output_profile_filename )
|
||||
icc->out_profile = cmsCreateLab4Profile( &white );
|
||||
if( !input_profile_filename )
|
||||
icc->in_profile = cmsCreateLab4Profile( &white );
|
||||
|
||||
if( !icc->in_profile || !icc->out_profile ) {
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "unable to create profiles" ) );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
return( icc );
|
||||
}
|
||||
|
||||
static Icc *
|
||||
icc_new_mem( IMAGE *in, IMAGE *out,
|
||||
void *data, int data_length,
|
||||
VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
cmsCIExyY white;
|
||||
|
||||
if( !(icc = icc_new( in, out, intent )) )
|
||||
return( NULL );
|
||||
|
||||
if( !(icc->in_profile = cmsOpenProfileFromMem( data, data_length )) ) {
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "unable to read profile" ) );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
/* Use D65.
|
||||
*/
|
||||
cmsWhitePointFromTemp( &white, 6500 );
|
||||
icc->out_profile = cmsCreateLab4Profile( &white );
|
||||
|
||||
return( icc );
|
||||
}
|
||||
|
||||
/* Pack a buffer of floats into lcms's fixed-point formats. Cut from
|
||||
* lcms-1.0.8.
|
||||
*/
|
||||
static void
|
||||
encode_lab( float *lab, guint16 *fixed, int n )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i = 0; i < n; i++ ) {
|
||||
float L = lab[0];
|
||||
float a = lab[1];
|
||||
float b = lab[2];
|
||||
|
||||
if( L < 0 )
|
||||
L = 0;
|
||||
if( L > 100. )
|
||||
L = 100.;
|
||||
|
||||
if( a < -128. )
|
||||
a = -128;
|
||||
if( a > 127.9961 )
|
||||
a = 127.9961;
|
||||
if( b < -128. )
|
||||
b = -128;
|
||||
if( b > 127.9961 )
|
||||
b = 127.9961;
|
||||
|
||||
fixed[0] = L * 652.800 + 0.5;
|
||||
fixed[1] = (a + 128.0) * 256.0 + 0.5;
|
||||
fixed[2] = (b + 128.0) * 256.0 + 0.5;
|
||||
|
||||
lab += 3;
|
||||
fixed += 3;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
decode_lab( guint16 *fixed, float *lab, int n )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i = 0; i < n; i++ ) {
|
||||
lab[0] = (double) fixed[0] / 652.800;
|
||||
lab[1] = ((double) fixed[1] / 256.0) - 128.0;
|
||||
lab[2] = ((double) fixed[2] / 256.0) - 128.0;
|
||||
|
||||
lab += 3;
|
||||
fixed += 3;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
transform_buf( PEL *in, PEL *out, int n, Icc *icc )
|
||||
{
|
||||
g_mutex_lock( icc->lock );
|
||||
cmsDoTransform( icc->trans, in, out, n );
|
||||
g_mutex_unlock( icc->lock );
|
||||
}
|
||||
|
||||
static int
|
||||
attach_profile( IMAGE *im, const char *filename )
|
||||
{
|
||||
char *data;
|
||||
unsigned int data_length;
|
||||
|
||||
if( !(data = im__file_read_name( filename, VIPS_ICC_DIR,
|
||||
&data_length )) )
|
||||
return( -1 );
|
||||
if( im_meta_set_blob( im, IM_META_ICC_NAME,
|
||||
(im_callback_fn) im_free, data, data_length ) ) {
|
||||
im_free( data );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_transform:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @input_profile_filename: get the input profile from here
|
||||
* @output_profile_filename: get the output profile from here
|
||||
* @intent: transform with this intent
|
||||
*
|
||||
* Transform an image with the ICC library. The input image is moved to
|
||||
* profile-connection space with the input profile and then to the output
|
||||
* space with the output profile.
|
||||
*
|
||||
* Use im_icc_import() and im_icc_export_depth() to do either the first or
|
||||
* second half of this operation in isolation.
|
||||
*
|
||||
* See also: im_icc_import(), im_icc_export_depth().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_icc_transform( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename,
|
||||
const char *output_profile_filename,
|
||||
VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
cmsUInt32Number in_icc_format;
|
||||
cmsUInt32Number out_icc_format;
|
||||
|
||||
if( im_check_uncoded( "im_icc_transform", in ) )
|
||||
return( -1 );
|
||||
|
||||
if( !(icc = icc_new_file( in, out,
|
||||
input_profile_filename, output_profile_filename, intent )) )
|
||||
return( -1 );
|
||||
|
||||
if( !cmsIsIntentSupported( icc->in_profile,
|
||||
intent, LCMS_USED_AS_INPUT ) )
|
||||
im_warn( "im_icc_transform",
|
||||
_( "intent %d (%s) not supported by "
|
||||
"profile \"%s\"; falling back to default intent "
|
||||
"(usually PERCEPTUAL)" ),
|
||||
intent, decode_intent( intent ),
|
||||
input_profile_filename );
|
||||
|
||||
if( !cmsIsIntentSupported( icc->out_profile,
|
||||
intent, LCMS_USED_AS_OUTPUT ) )
|
||||
im_warn( "im_icc_transform",
|
||||
_( "intent %d (%s) not supported by "
|
||||
"profile \"%s\"; falling back to default intent "
|
||||
"(usually PERCEPTUAL)" ),
|
||||
intent, decode_intent( intent ),
|
||||
output_profile_filename );
|
||||
|
||||
switch( cmsGetColorSpace( icc->in_profile ) ) {
|
||||
case cmsSigCmykData:
|
||||
if( in->Bands != 4 ) {
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "CMYK input profile "
|
||||
"needs a 4 band input image" ) );
|
||||
return( -1 );
|
||||
}
|
||||
in_icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 );
|
||||
break;
|
||||
|
||||
case cmsSigRgbData:
|
||||
if( in->Bands != 3 ) {
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "RGB input profile "
|
||||
"needs a 3 band input image" ) );
|
||||
return( -1 );
|
||||
}
|
||||
in_icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 );
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_transform",
|
||||
_( "unimplemented input color space 0x%x" ),
|
||||
cmsGetColorSpace( icc->in_profile ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Prepare the output image.
|
||||
*/
|
||||
if( im_cp_desc( out, in ) )
|
||||
return( -1 );
|
||||
|
||||
switch( cmsGetColorSpace( icc->out_profile ) ) {
|
||||
case cmsSigCmykData:
|
||||
out->Type = IM_TYPE_CMYK;
|
||||
out->BandFmt = IM_BANDFMT_UCHAR;
|
||||
out->Bands = 4;
|
||||
out_icc_format = TYPE_CMYK_8;
|
||||
break;
|
||||
|
||||
case cmsSigRgbData:
|
||||
out->Type = IM_TYPE_RGB;
|
||||
out->BandFmt = IM_BANDFMT_UCHAR;
|
||||
out->Bands = 3;
|
||||
out_icc_format = TYPE_RGB_8;
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_transform",
|
||||
_( "unimplemented output color space 0x%x" ),
|
||||
cmsGetColorSpace( icc->out_profile ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
switch( in->BandFmt ) {
|
||||
case IM_BANDFMT_UCHAR:
|
||||
in_icc_format |= BYTES_SH( 1 );
|
||||
break;
|
||||
|
||||
case IM_BANDFMT_USHORT:
|
||||
in_icc_format |= BYTES_SH( 2 );
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "uchar or ushort input only" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make
|
||||
* calling cmsDoTrsnaform() from multiple threads safe.
|
||||
*/
|
||||
if( !(icc->trans = cmsCreateTransform( icc->in_profile, in_icc_format,
|
||||
icc->out_profile, out_icc_format, intent,
|
||||
cmsFLAGS_NOCACHE )) )
|
||||
return( -1 );
|
||||
|
||||
if( attach_profile( out, output_profile_filename ) )
|
||||
return( -1 );
|
||||
|
||||
/* Process!
|
||||
*/
|
||||
if( im_wrapone( in, out,
|
||||
(im_wrapone_fn) transform_buf, icc, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
import_buf( PEL *in, float *out, int n, Icc *icc )
|
||||
{
|
||||
/* Buffer of encoded 16-bit pixels we write to.
|
||||
*/
|
||||
guint16 encoded[3 * PIXEL_BUFFER_SIZE];
|
||||
|
||||
while( n > 0 ) {
|
||||
const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE );
|
||||
|
||||
cmsDoTransform( icc->trans, in, encoded, chunk );
|
||||
decode_lab( encoded, out, chunk );
|
||||
|
||||
in += chunk * IM_IMAGE_SIZEOF_PEL( icc->in );
|
||||
out += chunk * 3;
|
||||
n -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
icc_import( IMAGE *in, IMAGE *out, Icc *icc )
|
||||
{
|
||||
cmsUInt32Number icc_format;
|
||||
|
||||
if( im_check_uncoded( "im_icc_import", in ) )
|
||||
return( -1 );
|
||||
|
||||
if( !cmsIsIntentSupported( icc->in_profile,
|
||||
icc->intent, LCMS_USED_AS_INPUT ) )
|
||||
im_warn( "im_icc_import",
|
||||
_( "intent %d (%s) not supported by "
|
||||
"profile; falling back to default intent "
|
||||
"(usually PERCEPTUAL)" ),
|
||||
icc->intent, decode_intent( icc->intent ) );
|
||||
|
||||
/* Prepare the output image.
|
||||
*/
|
||||
if( im_cp_desc( out, in ) )
|
||||
return( -1 );
|
||||
out->Type = IM_TYPE_LAB;
|
||||
out->BandFmt = IM_BANDFMT_FLOAT;
|
||||
out->Bands = 3;
|
||||
|
||||
switch( cmsGetColorSpace( icc->in_profile ) ) {
|
||||
case cmsSigCmykData:
|
||||
if( in->Bands != 4 ) {
|
||||
im_error( "im_icc_import",
|
||||
"%s", _( "CMYK profile needs a "
|
||||
"4 band input image" ) );
|
||||
return( -1 );
|
||||
}
|
||||
icc_format = COLORSPACE_SH( PT_CMYK ) | CHANNELS_SH( 4 );
|
||||
break;
|
||||
|
||||
case cmsSigRgbData:
|
||||
if( in->Bands != 3 ) {
|
||||
im_error( "im_icc_import",
|
||||
"%s", _( "RGB profile needs a "
|
||||
"3 band input image" ) );
|
||||
return( -1 );
|
||||
}
|
||||
icc_format = COLORSPACE_SH( PT_RGB ) | CHANNELS_SH( 3 );
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_import", _( "unimplemented input color "
|
||||
"space 0x%x" ), cmsGetColorSpace( icc->in_profile ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
switch( in->BandFmt ) {
|
||||
case IM_BANDFMT_UCHAR:
|
||||
icc_format |= BYTES_SH( 1 );
|
||||
break;
|
||||
|
||||
case IM_BANDFMT_USHORT:
|
||||
icc_format |= BYTES_SH( 2 );
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_transform",
|
||||
"%s", _( "uchar or ushort input only" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make
|
||||
* calling cmsDoTrsnaform() from multiple threads safe.
|
||||
*/
|
||||
if( !(icc->trans = cmsCreateTransform( icc->in_profile, icc_format,
|
||||
icc->out_profile, TYPE_Lab_16, icc->intent,
|
||||
cmsFLAGS_NOCACHE )) )
|
||||
return( -1 );
|
||||
|
||||
/* Process!
|
||||
*/
|
||||
if( im_wrapone( in, out, (im_wrapone_fn) import_buf, icc, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_import:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @input_profile_filename: get the input profile from here
|
||||
* @intent: transform with this intent
|
||||
*
|
||||
* Import an image with the ICC library. The input image in device space
|
||||
* is moved to D65 LAB with the input profile.
|
||||
*
|
||||
* See also: im_icc_transform(), im_icc_import_embedded().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_icc_import( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename, VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
|
||||
if( !(icc = icc_new_file( in, out,
|
||||
input_profile_filename, NULL, intent )) ||
|
||||
icc_import( in, out, icc ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_import_embedded:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @intent: transform with this intent
|
||||
*
|
||||
* Import an image with the ICC library. The input image in device space
|
||||
* is moved to D65 LAB with the input profile attached to the image under the
|
||||
* name #IM_META_ICC_NAME.
|
||||
*
|
||||
* See also: im_icc_transform(), im_icc_import().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
void *data;
|
||||
size_t data_length;
|
||||
|
||||
if( im_header_get_typeof( in, IM_META_ICC_NAME ) == 0 ) {
|
||||
im_error( "im_icc_import_embedded",
|
||||
"%s", _( "no embedded profile" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( im_meta_get_blob( in, IM_META_ICC_NAME, &data, &data_length ) ||
|
||||
!(icc = icc_new_mem( in, out, data, data_length, intent )) ||
|
||||
icc_import( in, out, icc ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void
|
||||
export_buf( float *in, PEL *out, int n, Icc *icc )
|
||||
{
|
||||
/* Buffer of encoded 16-bit pixels we transform.
|
||||
*/
|
||||
guint16 encoded[3 * PIXEL_BUFFER_SIZE];
|
||||
|
||||
while( n > 0 ) {
|
||||
const int chunk = IM_MIN( n, PIXEL_BUFFER_SIZE );
|
||||
|
||||
encode_lab( in, encoded, chunk );
|
||||
cmsDoTransform( icc->trans, encoded, out, chunk );
|
||||
|
||||
in += chunk * 3;
|
||||
out += chunk * IM_IMAGE_SIZEOF_PEL( icc->out );
|
||||
n -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_export_depth:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @depth: depth to export at
|
||||
* @output_profile_filename: use this profile
|
||||
* @intent: transform with this intent
|
||||
*
|
||||
* Export an image with the ICC library. The input image in
|
||||
* D65 LAB is transformed to device space using the supplied profile.
|
||||
* @depth can be 8 or 16, for 8 or 16-bit image export.
|
||||
*
|
||||
* See also: im_icc_transform(), im_icc_import().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_icc_export_depth( IMAGE *in, IMAGE *out, int depth,
|
||||
const char *output_profile_filename, VipsIntent intent )
|
||||
{
|
||||
Icc *icc;
|
||||
cmsUInt32Number icc_format;
|
||||
|
||||
/* Do IM_CODING_LABQ too.
|
||||
*/
|
||||
if( in->Coding == IM_CODING_LABQ ) {
|
||||
IMAGE *t1 = im_open_local( out, "im_icc_export:1", "p" );
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Check input image.
|
||||
*/
|
||||
if( im_check_uncoded( "im_icc_export", in ) ||
|
||||
im_check_bands( "im_icc_export", in, 3 ) ||
|
||||
im_check_format( "im_icc_export", in, IM_BANDFMT_FLOAT ) )
|
||||
return( -1 );
|
||||
|
||||
if( depth != 8 && depth != 16 ) {
|
||||
im_error( "im_icc_export", "%s", _( "unsupported bit depth" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( !(icc = icc_new_file( in, out,
|
||||
NULL, output_profile_filename, intent )) )
|
||||
return( -1 );
|
||||
|
||||
if( !cmsIsIntentSupported( icc->out_profile,
|
||||
intent, LCMS_USED_AS_OUTPUT ) )
|
||||
im_warn( "im_icc_export",
|
||||
_( "intent %d (%s) not supported by "
|
||||
"profile \"%s\"; falling back to default intent "
|
||||
"(usually PERCEPTUAL)" ),
|
||||
intent, decode_intent( intent ),
|
||||
output_profile_filename );
|
||||
|
||||
/* Prepare the output image.
|
||||
*/
|
||||
if( im_cp_desc( out, in ) )
|
||||
return( -1 );
|
||||
|
||||
switch( cmsGetColorSpace( icc->out_profile ) ) {
|
||||
case cmsSigCmykData:
|
||||
out->Type = IM_TYPE_CMYK;
|
||||
out->BandFmt = depth == 8 ?
|
||||
IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT;
|
||||
out->Bands = 4;
|
||||
icc_format = depth == 8 ? TYPE_CMYK_8 : TYPE_CMYK_16;
|
||||
break;
|
||||
|
||||
case cmsSigRgbData:
|
||||
out->Type = depth == 8 ?
|
||||
IM_TYPE_RGB : IM_TYPE_RGB16;
|
||||
out->BandFmt = depth == 8 ?
|
||||
IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT;
|
||||
out->Bands = 3;
|
||||
icc_format = depth == 8 ? TYPE_RGB_8 : TYPE_RGB_16;
|
||||
break;
|
||||
|
||||
default:
|
||||
im_error( "im_icc_export", _( "unimplemented output color "
|
||||
"space 0x%x" ), cmsGetColorSpace( icc->out_profile ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make
|
||||
* calling cmsDoTrsnaform() from multiple threads safe.
|
||||
*/
|
||||
if( !(icc->trans = cmsCreateTransform( icc->in_profile, TYPE_Lab_16,
|
||||
icc->out_profile, icc_format, intent,
|
||||
cmsFLAGS_NOCACHE )) )
|
||||
return( -1 );
|
||||
|
||||
if( attach_profile( out, output_profile_filename ) )
|
||||
return( -1 );
|
||||
|
||||
/* Process!
|
||||
*/
|
||||
if( im_wrapone( in, out, (im_wrapone_fn) export_buf, icc, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_icc_ac2rc:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @profile_filename: use this profile
|
||||
*
|
||||
* Transform an image from absolute to relative colorimetry using the
|
||||
* MediaWhitePoint stored in the ICC profile.
|
||||
*
|
||||
* See also: im_icc_transform(), im_icc_import().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename )
|
||||
{
|
||||
cmsHPROFILE profile;
|
||||
cmsCIEXYZ *media;
|
||||
double X, Y, Z;
|
||||
|
||||
double add[3];
|
||||
double mul[3];
|
||||
|
||||
IMAGE *t[2];
|
||||
|
||||
if( !(profile = cmsOpenProfileFromFile( profile_filename, "r" )) )
|
||||
return( -1 );
|
||||
|
||||
if( !(media = cmsReadTag( profile, cmsSigMediaWhitePointTag )) ) {
|
||||
im_error( "im_icc_ac2rc", "%s", _( "unable to get media "
|
||||
"white point" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
X = media->X;
|
||||
Y = media->Y;
|
||||
Z = media->Z;
|
||||
|
||||
cmsCloseProfile( profile );
|
||||
|
||||
add[0] = 0.0;
|
||||
add[1] = 0.0;
|
||||
add[2] = 0.0;
|
||||
|
||||
mul[0] = IM_D50_X0 / (X * 100.0);
|
||||
mul[1] = IM_D50_Y0 / (Y * 100.0);
|
||||
mul[2] = IM_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" );
|
||||
|
||||
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 ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
#else /*no lcms*/
|
||||
|
||||
#include <vips/vips.h>
|
||||
|
||||
int
|
||||
im_icc_present( void )
|
||||
{
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_transform( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename,
|
||||
const char *output_profile_filename,
|
||||
VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_transform", "%s",
|
||||
_( "lcms library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_import( IMAGE *in, IMAGE *out,
|
||||
const char *input_profile_filename, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_import", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_import_embedded( IMAGE *in, IMAGE *out, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_import", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_export_depth( IMAGE *in, IMAGE *out, int depth,
|
||||
const char *output_profile_filename, VipsIntent intent )
|
||||
{
|
||||
im_error( "im_icc_export_depth", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
int
|
||||
im_icc_ac2rc( IMAGE *in, IMAGE *out, const char *profile_filename )
|
||||
{
|
||||
im_error( "im_icc_ac2rc", "%s",
|
||||
_( "lmcs library not linked to this VIPS" ) );
|
||||
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#endif /*HAVE_LCMS*/
|
||||
|
Loading…
Reference in New Issue
Block a user