libvips/libvips/colour/LabQ2sRGB.c

571 lines
12 KiB
C

/* Turn Lab 32bit packed format into displayable rgb. Fast, but very
* inaccurate: for display only! Note especially that this dithers and will
* give different results on different runs.
*
* The XYZ <-> sRGB transform implemented is this one:
*
* http://en.wikipedia.org/wiki/SRGB
*
* 5/11/97 Steve Perry
* - adapted from old ip code
* 7/11/97 JC
* - small tidies, added to VIPS
* - LUT build split into separate function
* 21/9/12
* - redone as a class
* - sRGB only, support for other RGBs is now via lcms
* 1/11/12
* - faster and more accurate sRGB <-> XYZ conversion
* 6/11/12
* - added a 16-bit path
* 11/12/12
* - spot NaN, Inf in XYZ2RGB, they break LUT indexing
* - split sRGB <-> XYZ into sRGB <-> scRGB <-> XYZ so we can support
* scRGB as a colourspace
* 10/3/16 Lovell Fuller
* - move vips_col_make_tables_LabQ2sRGB() to first pixel processing
*/
/*
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 <math.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include "pcolour.h"
typedef VipsColourCode VipsLabQ2sRGB;
typedef VipsColourCodeClass VipsLabQ2sRGBClass;
G_DEFINE_TYPE( VipsLabQ2sRGB, vips_LabQ2sRGB, VIPS_TYPE_COLOUR_CODE );
/* 8-bit linear -> sRGB lut.
*
* There's an extra element at the end to let us do a +1 for interpolation.
*/
static int vips_Y2v_8[256 + 1];
/* 8-bit sRGB -> linear lut.
*/
float vips_v2Y_8[256];
/* 16-bit linear -> sRGB lut.
*
* There's an extra element at the end to let us do a +1 for interpolation.
*/
static int vips_Y2v_16[65536 + 1];
/* 16-bit sRGB -> linear lut.
*/
float vips_v2Y_16[65536];
/* Do our own indexing of the arrays below to make sure we get efficient mults.
*/
#define INDEX( L, A, B ) (L + (A << 6) + (B << 12))
/* A set of LUTs for quick LabQ->sRGB transforms.
*/
static VipsPel vips_red[64 * 64 * 64];
static VipsPel vips_green[64 * 64 * 64];
static VipsPel vips_blue[64 * 64 * 64];
/* sRGB to scRGB.
*
* @range is eg. 256 for 8-bit data.
*/
static int
vips_col_sRGB2scRGB( int range, float *lut,
int r, int g, int b, float *R, float *G, float *B )
{
int maxval = range - 1;
int i;
i = VIPS_CLIP( 0, r, maxval );
*R = lut[i];
i = VIPS_CLIP( 0, g, maxval );
*G = lut[i];
i = VIPS_CLIP( 0, b, maxval );
*B = lut[i];
return( 0 );
}
/* Create the sRGB linear and unlinear luts. @range is eg. 256 for 8-bit luts.
*/
static void
calcul_tables( int range, int *Y2v, float *v2Y )
{
int i;
for( i = 0; i < range; i++ ) {
float f = (float) i / (range - 1);
float v;
if( f <= 0.0031308 )
v = 12.92 * f;
else
v = (1.0 + 0.055) * pow( f, 1.0 / 2.4 ) - 0.055;
Y2v[i] = VIPS_RINT( (range - 1) * v );
}
/* Copy the final element. This is used in the piecewise linear
* interpolator below.
*/
Y2v[range] = Y2v[range - 1];
for( i = 0; i < range; i++ ) {
float f = (float) i / (range - 1);
if( f <= 0.04045 )
v2Y[i] = f / 12.92;
else
v2Y[i] = pow( (f + 0.055) / (1 + 0.055), 2.4 );
}
}
static void *
calcul_tables_8( void *client )
{
calcul_tables( 256, vips_Y2v_8, vips_v2Y_8 );
return( NULL );
}
void
vips_col_make_tables_RGB_8( void )
{
static GOnce once = G_ONCE_INIT;
VIPS_ONCE( &once, calcul_tables_8, NULL );
}
int
vips_col_sRGB2scRGB_8( int r, int g, int b, float *R, float *G, float *B )
{
vips_col_make_tables_RGB_8();
return( vips_col_sRGB2scRGB( 256, vips_v2Y_8, r, g, b, R, G, B ) );
}
static void *
calcul_tables_16( void *client )
{
calcul_tables( 65536, vips_Y2v_16, vips_v2Y_16 );
return( NULL );
}
void
vips_col_make_tables_RGB_16( void )
{
static GOnce once = G_ONCE_INIT;
VIPS_ONCE( &once, calcul_tables_16, NULL );
}
int
vips_col_sRGB2scRGB_16( int r, int g, int b, float *R, float *G, float *B )
{
vips_col_make_tables_RGB_16();
return( vips_col_sRGB2scRGB( 65536, vips_v2Y_16, r, g, b, R, G, B ) );
}
/* The matrix already includes the D65 channel weighting, so we just scale by
* Y.
*/
#define SCALE (VIPS_D65_Y0)
/**
* vips_col_scRGB2XYZ:
* @R: Input scRGB value
* @G: Input scRGB value
* @B: Input scRGB value
* @X: (out): Return XYZ colour
* @Y: (out): Return XYZ colour
* @Z: (out): Return XYZ colour
*
* Turn scRGB into XYZ.
*
* See also: vips_scRGB2XYZ().
*/
int
vips_col_scRGB2XYZ( float R, float G, float B, float *X, float *Y, float *Z )
{
*X = SCALE * 0.4124 * R + SCALE * 0.3576 * G + SCALE * 0.18056 * B;
*Y = SCALE * 0.2126 * R + SCALE * 0.7152 * G + SCALE * 0.07220 * B;
*Z = SCALE * 0.0193 * R + SCALE * 0.1192 * G + SCALE * 0.9505 * B;
return( 0 );
}
/**
* vips_col_XYZ2scRGB:
* @X: Input XYZ value
* @Y: Input XYZ value
* @Z: Input XYZ value
* @R: (out): Return scRGB colour
* @G: (out): Return scRGB colour
* @B: (out): Return scRGB colour
*
* Turn XYZ into scRGB.
*
* See also: vips_XYZ2scRGB().
*/
int
vips_col_XYZ2scRGB( float X, float Y, float Z, float *R, float *G, float *B )
{
*R = 3.2406 / SCALE * X + -1.5372 / SCALE * Y + -0.4986 / SCALE * Z;
*G = -0.9689 / SCALE * X + 1.8758 / SCALE * Y + 0.0415 / SCALE * Z;
*B = 0.0557 / SCALE * X + -0.2040 / SCALE * Y + 1.0570 / SCALE * Z;
return( 0 );
}
/* Turn scRGB into sRGB. Return og=1 for out of gamut - rgb will contain an
* approximation of the right colour.
*
* Return -1 for NaN.
*/
static int
vips_col_scRGB2sRGB( int range, int *lut,
float R, float G, float B,
int *r, int *g, int *b,
int *og_ret )
{
int maxval = range - 1;
int og;
float Yf;
int Yi;
float v;
/* RGB can be NaN. Throw those values out, they will break
* our clipping.
*
* Don't use isnormal(), it is false for 0.0 and for subnormal
* numbers.
*/
if( VIPS_ISNAN( R ) || VIPS_ISNAN( G ) || VIPS_ISNAN( B ) ) {
*r = 0;
*g = 0;
*b = 0;
return( -1 );
}
/* Clip range, set the out-of-gamut flag.
*/
#define CLIP( L, V, H ) { \
if( (V) < (L) ) { \
(V) = (L); \
og = 1; \
} \
else if( (V) > (H) ) { \
(V) = (H); \
og = 1; \
} \
}
/* Look up with a float index: interpolate between the nearest two
* points.
*
* The +1 on the index is safe, see above.
*/
og = 0;
Yf = R * maxval;
CLIP( 0, Yf, maxval );
Yi = (int) Yf;
v = lut[Yi] + (lut[Yi + 1] - lut[Yi]) * (Yf - Yi);
*r = VIPS_RINT( v );
Yf = G * maxval;
CLIP( 0, Yf, maxval );
Yi = (int) Yf;
v = lut[Yi] + (lut[Yi + 1] - lut[Yi]) * (Yf - Yi);
*g = VIPS_RINT( v );
Yf = B * maxval;
CLIP( 0, Yf, maxval );
Yi = (int) Yf;
v = lut[Yi] + (lut[Yi + 1] - lut[Yi]) * (Yf - Yi);
*b = VIPS_RINT( v );
if( og_ret )
*og_ret = og;
return( 0 );
}
int
vips_col_scRGB2sRGB_8( float R, float G, float B,
int *r, int *g, int *b, int *og )
{
vips_col_make_tables_RGB_8();
return( vips_col_scRGB2sRGB( 256, vips_Y2v_8, R, G, B, r, g, b, og ) );
}
int
vips_col_scRGB2sRGB_16( float R, float G, float B,
int *r, int *g, int *b, int *og )
{
vips_col_make_tables_RGB_16();
return( vips_col_scRGB2sRGB( 65536, vips_Y2v_16,
R, G, B, r, g, b, og ) );
}
/* Turn scRGB into BW. Return or=1 for out of gamut - g will contain an
* approximation of the right colour.
*
* Return -1 for NaN.
*/
static int
vips_col_scRGB2BW( int range, int *lut, float R, float G, float B,
int *g, int *og_ret )
{
int maxval = range - 1;
float Y;
int og;
float Yf;
int Yi;
float v;
/* The usual ratio. We do this in linear space before we gamma.
*/
Y = 0.2 * R + 0.7 * G + 0.1 * B;
/* Y can be Nan. Throw those values out, they will break
* our clipping.
*/
if( VIPS_ISNAN( Y ) ) {
*g = 0;
return( -1 );
}
/* Look up with a float index: interpolate between the nearest two
* points.
*
* The +1 on the index is safe, see above.
*/
og = 0;
Yf = Y * maxval;
CLIP( 0, Yf, maxval );
Yi = (int) Yf;
v = lut[Yi] + (lut[Yi + 1] - lut[Yi]) * (Yf - Yi);
*g = VIPS_RINT( v );
if( og_ret )
*og_ret = og;
return( 0 );
}
int
vips_col_scRGB2BW_16( float R, float G, float B, int *g, int *og )
{
vips_col_make_tables_RGB_16();
return( vips_col_scRGB2BW( 65536, vips_Y2v_16, R, G, B, g, og ) );
}
int
vips_col_scRGB2BW_8( float R, float G, float B, int *g, int *og )
{
vips_col_make_tables_RGB_8();
return( vips_col_scRGB2BW( 256, vips_Y2v_8, R, G, B, g, og ) );
}
/* Build Lab->disp dither tables.
*/
static void *
build_tables( void *client )
{
int l, a, b;
int t;
for( l = 0; l < 64; l++ ) {
for( a = 0; a < 64; a++ ) {
for( b = 0; b < 64; b++ ) {
/* Scale to lab space.
*/
float L = (l << 2) * (100.0/256.0);
float A = (signed char) (a << 2);
float B = (signed char) (b << 2);
float X, Y, Z;
float Rf, Gf, Bf;
int rb, gb, bb;
vips_col_Lab2XYZ( L, A, B, &X, &Y, &Z );
vips_col_XYZ2scRGB( X, Y, Z, &Rf, &Gf, &Bf );
vips_col_scRGB2sRGB_8( Rf, Gf, Bf,
&rb, &gb, &bb, NULL );
t = INDEX( l, a, b );
vips_red[t] = rb;
vips_green[t] = gb;
vips_blue[t] = bb;
}
}
}
return( NULL );
}
static void
vips_col_make_tables_LabQ2sRGB( void )
{
static GOnce once = G_ONCE_INIT;
VIPS_ONCE( &once, build_tables, NULL );
}
/* Process a buffer of data.
*/
static void
vips_LabQ2sRGB_line( VipsColour *colour, VipsPel *q, VipsPel **in, int width )
{
unsigned char *p = (unsigned char *) in[0];
int i, t;
/* Current error.
*/
int le = 0;
int ae = 0;
int be = 0;
vips_col_make_tables_LabQ2sRGB();
for( i = 0; i < width; i++ ) {
/* Get colour, add in error from previous pixel.
*/
int L = p[0] + le;
int A = (signed char) p[1] + ae;
int B = (signed char) p[2] + be;
p += 4;
/* Look out for overflow.
*/
L = VIPS_MIN( 255, L );
A = VIPS_MIN( 127, A );
B = VIPS_MIN( 127, B );
/* Find new quant error. This will always be +ve.
*/
le = L & 3;
ae = A & 3;
be = B & 3;
/* Scale to 0-63.
*/
L = (L >> 2) & 63;
A = (A >> 2) & 63;
B = (B >> 2) & 63;
/* Convert to RGB.
*/
t = INDEX( L, A, B );
q[0] = vips_red[t];
q[1] = vips_green[t];
q[2] = vips_blue[t];
q += 3;
}
}
static void
vips_LabQ2sRGB_class_init( VipsLabQ2sRGBClass *class )
{
VipsObjectClass *object_class = (VipsObjectClass *) class;
VipsColourClass *colour_class = VIPS_COLOUR_CLASS( class );
object_class->nickname = "LabQ2sRGB";
object_class->description = _( "convert a LabQ image to sRGB" );
colour_class->process_line = vips_LabQ2sRGB_line;
}
static void
vips_LabQ2sRGB_init( VipsLabQ2sRGB *LabQ2sRGB )
{
VipsColour *colour = VIPS_COLOUR( LabQ2sRGB );
VipsColourCode *code = VIPS_COLOUR_CODE( LabQ2sRGB );
colour->coding = VIPS_CODING_NONE;
colour->interpretation = VIPS_INTERPRETATION_sRGB;
colour->format = VIPS_FORMAT_UCHAR;
colour->bands = 3;
code->input_coding = VIPS_CODING_LABQ;
}
/**
* vips_LabQ2sRGB: (method)
* @in: input image
* @out: (out): output image
* @...: %NULL-terminated list of optional named arguments
*
* Unpack a LabQ (#VIPS_CODING_LABQ) image to a three-band short image.
*
* See also: vips_LabS2LabQ(), vips_LabQ2sRGB(), vips_rad2float().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_LabQ2sRGB( VipsImage *in, VipsImage **out, ... )
{
va_list ap;
int result;
va_start( ap, out );
result = vips_call_split( "LabQ2sRGB", ap, in, out );
va_end( ap );
return( result );
}