cgifsave: palette change POC (#2824)
* cgifsave: compare palettes instead of frame sum * Add vips__quantise_image_quantize_fixed function
This commit is contained in:
parent
5106e9b49e
commit
19bef959d1
@ -61,8 +61,9 @@ typedef struct _VipsForeignSaveCgif {
|
|||||||
double dither;
|
double dither;
|
||||||
int effort;
|
int effort;
|
||||||
int bitdepth;
|
int bitdepth;
|
||||||
double maxerror;
|
double interframe_maxerror;
|
||||||
gboolean reoptimise;
|
gboolean reoptimise;
|
||||||
|
double interpalette_maxerror;
|
||||||
VipsTarget *target;
|
VipsTarget *target;
|
||||||
|
|
||||||
/* Derived write params.
|
/* Derived write params.
|
||||||
@ -85,8 +86,7 @@ typedef struct _VipsForeignSaveCgif {
|
|||||||
*/
|
*/
|
||||||
VipsQuantiseAttr *attr;
|
VipsQuantiseAttr *attr;
|
||||||
VipsQuantiseImage *input_image;
|
VipsQuantiseImage *input_image;
|
||||||
VipsQuantiseResult *quantisation_result;
|
VipsQuantiseResult *quantisation_result, *local_quantisation_result;
|
||||||
const VipsQuantisePalette *lp;
|
|
||||||
|
|
||||||
/* The current colourmap, updated on a significant frame change.
|
/* The current colourmap, updated on a significant frame change.
|
||||||
*/
|
*/
|
||||||
@ -133,6 +133,8 @@ vips_foreign_save_cgif_dispose( GObject *gobject )
|
|||||||
VIPS_FREEF( cgif_close, cgif->cgif_context );
|
VIPS_FREEF( cgif_close, cgif->cgif_context );
|
||||||
|
|
||||||
VIPS_FREEF( vips__quantise_result_destroy, cgif->quantisation_result );
|
VIPS_FREEF( vips__quantise_result_destroy, cgif->quantisation_result );
|
||||||
|
VIPS_FREEF( vips__quantise_result_destroy,
|
||||||
|
cgif->local_quantisation_result );
|
||||||
VIPS_FREEF( vips__quantise_image_destroy, cgif->input_image );
|
VIPS_FREEF( vips__quantise_image_destroy, cgif->input_image );
|
||||||
VIPS_FREEF( vips__quantise_attr_destroy, cgif->attr );
|
VIPS_FREEF( vips__quantise_attr_destroy, cgif->attr );
|
||||||
|
|
||||||
@ -182,6 +184,58 @@ vips_foreign_save_cgif_pixels_are_equal( const VipsPel *cur, const VipsPel *bef,
|
|||||||
return( dR * dR + dG * dG + dB * dB <= sq_maxerror );
|
return( dR * dR + dG * dG + dB * dB <= sq_maxerror );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
vips__cgif_compare_palettes(const VipsQuantisePalette *new,
|
||||||
|
const VipsQuantisePalette *old)
|
||||||
|
{
|
||||||
|
g_assert( new->count <= 256 );
|
||||||
|
g_assert( old->count <= 256 );
|
||||||
|
|
||||||
|
int i, j;
|
||||||
|
double best_dist, dist, rd, gd, bd;
|
||||||
|
double total_dist = 0.0;
|
||||||
|
|
||||||
|
for( i = 0; i < new->count; i++ ) {
|
||||||
|
best_dist = 255.0 * 255.0 * 3;
|
||||||
|
|
||||||
|
for( j = 0; j < old->count; j++ ) {
|
||||||
|
if( new->entries[i].a >= 128 ) {
|
||||||
|
/* The new entry is solid.
|
||||||
|
* If the old entry is transparent, ignore it.
|
||||||
|
* Otherwise, compare RGB.
|
||||||
|
*/
|
||||||
|
if( old->entries[j].a < 128 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rd = new->entries[i].r - old->entries[j].r;
|
||||||
|
gd = new->entries[i].g - old->entries[j].g;
|
||||||
|
bd = new->entries[i].b - old->entries[j].b;
|
||||||
|
dist = rd*rd + gd*gd + bd*bd;
|
||||||
|
|
||||||
|
best_dist = VIPS_MIN(best_dist, dist);
|
||||||
|
|
||||||
|
/* We found the closest entry
|
||||||
|
*/
|
||||||
|
if( best_dist == 0 )
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
/* The new entry is transparent.
|
||||||
|
* If the old entry is transparent too, it's
|
||||||
|
* the closest color. Otherwise, ignore it.
|
||||||
|
*/
|
||||||
|
if( old->entries[j].a < 128 ) {
|
||||||
|
best_dist = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total_dist += best_dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_dist / new->count;
|
||||||
|
}
|
||||||
|
|
||||||
/* We have a complete frame --- write!
|
/* We have a complete frame --- write!
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
@ -201,6 +255,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
VipsPel * restrict bef;
|
VipsPel * restrict bef;
|
||||||
gboolean has_alpha_constraint = FALSE;
|
gboolean has_alpha_constraint = FALSE;
|
||||||
VipsPel *rgb;
|
VipsPel *rgb;
|
||||||
|
VipsQuantiseResult *quantisation_result;
|
||||||
|
const VipsQuantisePalette *lp, *pal_global, *pal_local;
|
||||||
|
double pal_change_global, pal_change_local;
|
||||||
|
gboolean use_local_palette = FALSE;
|
||||||
CGIF_FrameConfig frame_config;
|
CGIF_FrameConfig frame_config;
|
||||||
|
|
||||||
#ifdef DEBUG_VERBOSE
|
#ifdef DEBUG_VERBOSE
|
||||||
@ -243,90 +301,123 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
cur += 4;
|
cur += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do we need to compute a new palette? Do it if the frame sum
|
/* Do we need to compute a new palette? Do it if the palette changes.
|
||||||
* changes.
|
|
||||||
*
|
|
||||||
* frame_checksum 0 means no current colourmap.
|
|
||||||
*/
|
*/
|
||||||
if( !cgif->global_colour_table ) {
|
if( cgif->global_colour_table ) {
|
||||||
gint64 checksum;
|
quantisation_result = cgif->quantisation_result;
|
||||||
|
lp = vips__quantise_get_palette( quantisation_result );
|
||||||
/* We need a checksum which detects colour changes, but
|
} else {
|
||||||
* doesn't care about pixel ordering.
|
if( vips__quantise_image_quantize_fixed( cgif->input_image,
|
||||||
*
|
cgif->attr, &quantisation_result ) ) {
|
||||||
* Scale RGBA differently so that changes like [0, 255, 0]
|
|
||||||
* to [255, 0, 0] are detected.
|
|
||||||
*/
|
|
||||||
checksum = 0;
|
|
||||||
cur = frame_bytes;
|
|
||||||
for( i = 0; i < n_pels; i++ ) {
|
|
||||||
checksum += cur[0] * 1000;
|
|
||||||
checksum += cur[1] * 100;
|
|
||||||
checksum += cur[2] * 10;
|
|
||||||
checksum += cur[3];
|
|
||||||
|
|
||||||
cur += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( cgif->frame_checksum == 0 ||
|
|
||||||
checksum != cgif->frame_checksum ) {
|
|
||||||
cgif->frame_checksum = checksum;
|
|
||||||
|
|
||||||
/* If this is not our first cmap, make a note that we
|
|
||||||
* need to attach it as a local cmap when we write.
|
|
||||||
*/
|
|
||||||
if( cgif->quantisation_result )
|
|
||||||
cgif->cgif_config.attrFlags |=
|
|
||||||
CGIF_ATTR_NO_GLOBAL_TABLE;
|
|
||||||
|
|
||||||
VIPS_FREEF( vips__quantise_result_destroy,
|
|
||||||
cgif->quantisation_result );
|
|
||||||
if( vips__quantise_image_quantize( cgif->input_image,
|
|
||||||
cgif->attr, &cgif->quantisation_result ) ) {
|
|
||||||
vips_error( class->nickname,
|
vips_error( class->nickname,
|
||||||
"%s", _( "quantisation failed" ) );
|
"%s", _( "quantisation failed" ) );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
lp = vips__quantise_get_palette( quantisation_result );
|
||||||
|
|
||||||
|
if( !cgif->quantisation_result ) {
|
||||||
|
/* This is the first frame, save global quantization
|
||||||
|
* result and palette
|
||||||
|
*/
|
||||||
|
cgif->quantisation_result = quantisation_result;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pal_global = vips__quantise_get_palette(
|
||||||
|
cgif->quantisation_result );
|
||||||
|
pal_change_global = vips__cgif_compare_palettes(
|
||||||
|
lp, pal_global );
|
||||||
|
|
||||||
|
if ( !cgif->local_quantisation_result )
|
||||||
|
pal_change_local = 255*255*3;
|
||||||
|
else {
|
||||||
|
pal_local = vips__quantise_get_palette(
|
||||||
|
cgif->local_quantisation_result );
|
||||||
|
pal_change_local = vips__cgif_compare_palettes(
|
||||||
|
lp, pal_local );
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
pal_change_local <= pal_change_global &&
|
||||||
|
pal_change_local <= cgif->interpalette_maxerror
|
||||||
|
) {
|
||||||
|
/* Local palette change is low, use previous
|
||||||
|
* local quantization result and palette
|
||||||
|
*/
|
||||||
|
VIPS_FREEF( vips__quantise_result_destroy,
|
||||||
|
quantisation_result );
|
||||||
|
quantisation_result =
|
||||||
|
cgif->local_quantisation_result;
|
||||||
|
lp = pal_local;
|
||||||
|
|
||||||
|
use_local_palette = 1;
|
||||||
|
|
||||||
|
} else if(
|
||||||
|
pal_change_global <= cgif->interpalette_maxerror
|
||||||
|
) {
|
||||||
|
/* Global palette change is low, use global
|
||||||
|
* quantization result and palette
|
||||||
|
*/
|
||||||
|
VIPS_FREEF( vips__quantise_result_destroy,
|
||||||
|
quantisation_result );
|
||||||
|
quantisation_result = cgif->quantisation_result;
|
||||||
|
lp = pal_global;
|
||||||
|
|
||||||
|
/* Also drop saved local result as it's usage
|
||||||
|
* doesn't make sense now and it's better to
|
||||||
|
* use a new local result if neeeded
|
||||||
|
*/
|
||||||
|
VIPS_FREEF( vips__quantise_result_destroy,
|
||||||
|
cgif->local_quantisation_result );
|
||||||
|
cgif->local_quantisation_result = NULL;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Palette change is high, use local
|
||||||
|
* quantization result and palette
|
||||||
|
*/
|
||||||
|
VIPS_FREEF( vips__quantise_result_destroy,
|
||||||
|
cgif->local_quantisation_result );
|
||||||
|
cgif->local_quantisation_result =
|
||||||
|
quantisation_result;
|
||||||
|
|
||||||
|
use_local_palette = 1;
|
||||||
#ifdef DEBUG_PERCENT
|
#ifdef DEBUG_PERCENT
|
||||||
cgif->n_cmaps_generated += 1;
|
cgif->n_cmaps_generated += 1;
|
||||||
cgif->lp = vips__quantise_get_palette(
|
|
||||||
cgif->quantisation_result );
|
|
||||||
printf( "frame %d, new %d item colourmap\n",
|
printf( "frame %d, new %d item colourmap\n",
|
||||||
page_index, cgif->lp->count );
|
page_index, lp->count );
|
||||||
#endif/*DEBUG_PERCENT*/
|
#endif/*DEBUG_PERCENT*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Dither frame.
|
/* Dither frame.
|
||||||
*/
|
*/
|
||||||
vips__quantise_set_dithering_level( cgif->quantisation_result,
|
vips__quantise_set_dithering_level( quantisation_result, cgif->dither );
|
||||||
cgif->dither );
|
if( vips__quantise_write_remapped_image( quantisation_result,
|
||||||
|
|
||||||
if( vips__quantise_write_remapped_image( cgif->quantisation_result,
|
|
||||||
cgif->input_image, cgif->index, n_pels ) ) {
|
cgif->input_image, cgif->index, n_pels ) ) {
|
||||||
vips_error( class->nickname, "%s", _( "dither failed" ) );
|
vips_error( class->nickname, "%s", _( "dither failed" ) );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call vips__quantise_get_palette() after
|
/* Call vips__quantise_get_palette() after vips__quantise_write_remapped_image(),
|
||||||
* vips__quantise_write_remapped_image(), as palette is improved
|
* as palette is improved during remapping.
|
||||||
* during remapping.
|
|
||||||
*/
|
*/
|
||||||
cgif->lp = vips__quantise_get_palette( cgif->quantisation_result );
|
lp = vips__quantise_get_palette( quantisation_result );
|
||||||
|
|
||||||
|
if (use_local_palette || !cgif->cgif_context ) {
|
||||||
rgb = cgif->palette_rgb;
|
rgb = cgif->palette_rgb;
|
||||||
g_assert( cgif->lp->count <= 256 );
|
g_assert( lp->count <= 256 );
|
||||||
for( i = 0; i < cgif->lp->count; i++ ) {
|
for( i = 0; i < lp->count; i++ ) {
|
||||||
rgb[0] = cgif->lp->entries[i].r;
|
rgb[0] = lp->entries[i].r;
|
||||||
rgb[1] = cgif->lp->entries[i].g;
|
rgb[1] = lp->entries[i].g;
|
||||||
rgb[2] = cgif->lp->entries[i].b;
|
rgb[2] = lp->entries[i].b;
|
||||||
|
|
||||||
rgb += 3;
|
rgb += 3;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* If there's a transparent pixel, it's always first.
|
/* If there's a transparent pixel, it's always first.
|
||||||
*/
|
*/
|
||||||
cgif->has_transparency = cgif->lp->entries[0].a == 0;
|
cgif->has_transparency = lp->entries[0].a == 0;
|
||||||
|
|
||||||
/* Set up cgif on first use, so we can set the first cmap as the global
|
/* Set up cgif on first use, so we can set the first cmap as the global
|
||||||
* one.
|
* one.
|
||||||
@ -344,7 +435,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
#endif/*HAVE_CGIF_ATTR_NO_LOOP*/
|
#endif/*HAVE_CGIF_ATTR_NO_LOOP*/
|
||||||
cgif->cgif_config.width = frame_rect->width;
|
cgif->cgif_config.width = frame_rect->width;
|
||||||
cgif->cgif_config.height = frame_rect->height;
|
cgif->cgif_config.height = frame_rect->height;
|
||||||
cgif->cgif_config.numGlobalPaletteEntries = cgif->lp->count;
|
cgif->cgif_config.numGlobalPaletteEntries = lp->count;
|
||||||
#ifdef HAVE_CGIF_ATTR_NO_LOOP
|
#ifdef HAVE_CGIF_ATTR_NO_LOOP
|
||||||
cgif->cgif_config.numLoops = cgif->loop > 1 ?
|
cgif->cgif_config.numLoops = cgif->loop > 1 ?
|
||||||
cgif->loop - 1 : cgif->loop;
|
cgif->loop - 1 : cgif->loop;
|
||||||
@ -392,14 +483,14 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
uint8_t trans_index;
|
uint8_t trans_index;
|
||||||
double sq_maxerror;
|
double sq_maxerror;
|
||||||
|
|
||||||
trans_index = cgif->lp->count;
|
trans_index = lp->count;
|
||||||
if( cgif->has_transparency ) {
|
if( cgif->has_transparency ) {
|
||||||
trans_index = 0;
|
trans_index = 0;
|
||||||
frame_config.attrFlags &=
|
frame_config.attrFlags &=
|
||||||
~CGIF_FRAME_ATTR_HAS_ALPHA;
|
~CGIF_FRAME_ATTR_HAS_ALPHA;
|
||||||
}
|
}
|
||||||
|
|
||||||
sq_maxerror = cgif->maxerror * cgif->maxerror;
|
sq_maxerror = cgif->interframe_maxerror * cgif->interframe_maxerror;
|
||||||
|
|
||||||
for( i = 0; i < n_pels; i++ ) {
|
for( i = 0; i < n_pels; i++ ) {
|
||||||
if( vips_foreign_save_cgif_pixels_are_equal(
|
if( vips_foreign_save_cgif_pixels_are_equal(
|
||||||
@ -435,10 +526,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
|
|
||||||
/* Attach a local palette, if we need one.
|
/* Attach a local palette, if we need one.
|
||||||
*/
|
*/
|
||||||
if( cgif->cgif_config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
|
if( use_local_palette ) {
|
||||||
frame_config.attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
|
frame_config.attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
|
||||||
frame_config.pLocalPalette = cgif->palette_rgb;
|
frame_config.pLocalPalette = cgif->palette_rgb;
|
||||||
frame_config.numLocalPaletteEntries = cgif->lp->count;
|
frame_config.numLocalPaletteEntries = lp->count;
|
||||||
}
|
}
|
||||||
|
|
||||||
cgif_addframe( cgif->cgif_context, &frame_config );
|
cgif_addframe( cgif->cgif_context, &frame_config );
|
||||||
@ -451,6 +542,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
|||||||
memcpy( cgif->frame_bytes_head, frame_bytes, 4 * n_pels );
|
memcpy( cgif->frame_bytes_head, frame_bytes, 4 * n_pels );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,7 +652,10 @@ vips_foreign_save_cgif_build( VipsObject *object )
|
|||||||
frame_rect.top = 0;
|
frame_rect.top = 0;
|
||||||
frame_rect.width = cgif->in->Xsize;
|
frame_rect.width = cgif->in->Xsize;
|
||||||
frame_rect.height = page_height;
|
frame_rect.height = page_height;
|
||||||
if( (guint64) frame_rect.width * frame_rect.height > 5000 * 5000 ) {
|
|
||||||
|
/* GIF has a limit of 64k per axis -- double-check this.
|
||||||
|
*/
|
||||||
|
if( frame_rect.width > 65535 || frame_rect.height > 65535 ) {
|
||||||
vips_error( class->nickname, "%s", _( "frame too large" ) );
|
vips_error( class->nickname, "%s", _( "frame too large" ) );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
@ -614,7 +709,7 @@ vips_foreign_save_cgif_build( VipsObject *object )
|
|||||||
tmp_image = vips__quantise_image_create_rgba( cgif->attr,
|
tmp_image = vips__quantise_image_create_rgba( cgif->attr,
|
||||||
tmp_gct, gct_length + 1, 1, 0 );
|
tmp_gct, gct_length + 1, 1, 0 );
|
||||||
|
|
||||||
if( vips__quantise_image_quantize( tmp_image,
|
if( vips__quantise_image_quantize_fixed( tmp_image,
|
||||||
cgif->attr, &cgif->quantisation_result ) ) {
|
cgif->attr, &cgif->quantisation_result ) ) {
|
||||||
vips_error( class->nickname,
|
vips_error( class->nickname,
|
||||||
"%s", _( "quantisation failed" ) );
|
"%s", _( "quantisation failed" ) );
|
||||||
@ -691,11 +786,11 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class )
|
|||||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, bitdepth ),
|
G_STRUCT_OFFSET( VipsForeignSaveCgif, bitdepth ),
|
||||||
1, 8, 8 );
|
1, 8, 8 );
|
||||||
|
|
||||||
VIPS_ARG_DOUBLE( class, "maxerror", 13,
|
VIPS_ARG_DOUBLE( class, "interframe_maxerror", 13,
|
||||||
_( "Maximum error" ),
|
_( "Maximum inter-frame error" ),
|
||||||
_( "Maximum inter-frame error for transparency" ),
|
_( "Maximum inter-frame error for transparency" ),
|
||||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, maxerror ),
|
G_STRUCT_OFFSET( VipsForeignSaveCgif, interframe_maxerror ),
|
||||||
0, 32, 0.0 );
|
0, 32, 0.0 );
|
||||||
|
|
||||||
VIPS_ARG_BOOL( class, "reoptimise", 14,
|
VIPS_ARG_BOOL( class, "reoptimise", 14,
|
||||||
@ -704,6 +799,13 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class )
|
|||||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, reoptimise ),
|
G_STRUCT_OFFSET( VipsForeignSaveCgif, reoptimise ),
|
||||||
FALSE );
|
FALSE );
|
||||||
|
|
||||||
|
VIPS_ARG_DOUBLE( class, "interpalette_maxerror", 15,
|
||||||
|
_( "Maximum inter-palette error" ),
|
||||||
|
_( "Maximum inter-palette error for palette reusage" ),
|
||||||
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsForeignSaveCgif, interpalette_maxerror ),
|
||||||
|
0, 256, 64.0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -712,8 +814,9 @@ vips_foreign_save_cgif_init( VipsForeignSaveCgif *gif )
|
|||||||
gif->dither = 1.0;
|
gif->dither = 1.0;
|
||||||
gif->effort = 7;
|
gif->effort = 7;
|
||||||
gif->bitdepth = 8;
|
gif->bitdepth = 8;
|
||||||
gif->maxerror = 0.0;
|
gif->interframe_maxerror = 0.0;
|
||||||
gif->reoptimise = FALSE;
|
gif->reoptimise = FALSE;
|
||||||
|
gif->interpalette_maxerror = 64.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct _VipsForeignSaveCgifTarget {
|
typedef struct _VipsForeignSaveCgifTarget {
|
||||||
@ -894,8 +997,10 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer )
|
|||||||
* * @dither: %gdouble, quantisation dithering level
|
* * @dither: %gdouble, quantisation dithering level
|
||||||
* * @effort: %gint, quantisation CPU effort
|
* * @effort: %gint, quantisation CPU effort
|
||||||
* * @bitdepth: %gint, number of bits per pixel
|
* * @bitdepth: %gint, number of bits per pixel
|
||||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||||
|
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||||
|
* reusage
|
||||||
*
|
*
|
||||||
* Write to a file in GIF format.
|
* Write to a file in GIF format.
|
||||||
*
|
*
|
||||||
@ -908,10 +1013,15 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer )
|
|||||||
* always reserved for transparency. For example, a bitdepth of
|
* always reserved for transparency. For example, a bitdepth of
|
||||||
* 4 will allow the output to contain up to 15 colours.
|
* 4 will allow the output to contain up to 15 colours.
|
||||||
*
|
*
|
||||||
* Use @maxerror to set the threshold below which pixels are considered equal.
|
* Use @interframe_maxerror to set the threshold below which pixels are
|
||||||
|
* considered equal.
|
||||||
* Pixels which don't change from frame to frame can be made transparent,
|
* Pixels which don't change from frame to frame can be made transparent,
|
||||||
* improving the compression rate. Default 0.
|
* improving the compression rate. Default 0.
|
||||||
*
|
*
|
||||||
|
* If @reoptimise is TRUE, new palettes will be generated. Use
|
||||||
|
* @interpalette_maxerror to set the threshold below which one of the previously
|
||||||
|
* generated palettes will be reused.
|
||||||
|
*
|
||||||
* See also: vips_image_new_from_file().
|
* See also: vips_image_new_from_file().
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, -1 on error.
|
* Returns: 0 on success, -1 on error.
|
||||||
@ -941,8 +1051,10 @@ vips_gifsave( VipsImage *in, const char *filename, ... )
|
|||||||
* * @dither: %gdouble, quantisation dithering level
|
* * @dither: %gdouble, quantisation dithering level
|
||||||
* * @effort: %gint, quantisation CPU effort
|
* * @effort: %gint, quantisation CPU effort
|
||||||
* * @bitdepth: %gint, number of bits per pixel
|
* * @bitdepth: %gint, number of bits per pixel
|
||||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||||
|
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||||
|
* reusage
|
||||||
*
|
*
|
||||||
* As vips_gifsave(), but save to a memory buffer.
|
* As vips_gifsave(), but save to a memory buffer.
|
||||||
*
|
*
|
||||||
@ -993,8 +1105,10 @@ vips_gifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
|
|||||||
* * @dither: %gdouble, quantisation dithering level
|
* * @dither: %gdouble, quantisation dithering level
|
||||||
* * @effort: %gint, quantisation CPU effort
|
* * @effort: %gint, quantisation CPU effort
|
||||||
* * @bitdepth: %gint, number of bits per pixel
|
* * @bitdepth: %gint, number of bits per pixel
|
||||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||||
|
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||||
|
* reusage
|
||||||
*
|
*
|
||||||
* As vips_gifsave(), but save to a target.
|
* As vips_gifsave(), but save to a target.
|
||||||
*
|
*
|
||||||
|
@ -87,6 +87,57 @@ vips__quantise_image_quantize( VipsQuantiseImage *const input_image,
|
|||||||
return liq_image_quantize( input_image, options, result_output );
|
return liq_image_quantize( input_image, options, result_output );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VipsQuantiseError
|
||||||
|
vips__quantise_image_quantize_fixed( VipsQuantiseImage *const input_image,
|
||||||
|
VipsQuantiseAttr *const options, VipsQuantiseResult **result_output )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
liq_result *result;
|
||||||
|
const liq_palette *palette;
|
||||||
|
liq_error err;
|
||||||
|
liq_image *fake_image;
|
||||||
|
void *fake_image_pixels;
|
||||||
|
|
||||||
|
/* First, quantize the image and get its palette
|
||||||
|
*/
|
||||||
|
err = liq_image_quantize( input_image, options, &result );
|
||||||
|
if( err != LIQ_OK )
|
||||||
|
return err;
|
||||||
|
|
||||||
|
palette = liq_get_palette( result );
|
||||||
|
|
||||||
|
/* Now, we need a fake 1 pixel image that will be quantized on the
|
||||||
|
* next step. Its pixel color doesn't matter since we'll add all the
|
||||||
|
* colors frm the palette further.
|
||||||
|
*/
|
||||||
|
fake_image_pixels = malloc( 4 );
|
||||||
|
fake_image = liq_image_create_rgba( options, fake_image_pixels, 1, 1, 0 );
|
||||||
|
if( !fake_image ) {
|
||||||
|
liq_result_destroy( result );
|
||||||
|
free( fake_image_pixels );
|
||||||
|
return LIQ_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add all the colors from the palette as fixed colors to the fake
|
||||||
|
* image. Since the fixed colors number is the same as required colors
|
||||||
|
* number, no new colors will be added.
|
||||||
|
*/
|
||||||
|
for( i = 0; i < palette->count; i++ )
|
||||||
|
liq_image_add_fixed_color( fake_image, palette->entries[i] );
|
||||||
|
|
||||||
|
liq_result_destroy( result );
|
||||||
|
|
||||||
|
/* Finally, quantize the fake image with fixed colors to get the result
|
||||||
|
* which palette won't be changed during remapping
|
||||||
|
*/
|
||||||
|
err = liq_image_quantize( fake_image, options, result_output );
|
||||||
|
|
||||||
|
liq_image_destroy( fake_image );
|
||||||
|
free( fake_image_pixels );
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
VipsQuantiseError
|
VipsQuantiseError
|
||||||
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||||
float dither_level )
|
float dither_level )
|
||||||
@ -174,6 +225,17 @@ vips__quantise_image_quantize( VipsQuantiseImage *const input_image,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VipsQuantiseError
|
||||||
|
vips__quantise_image_quantize_fixed( VipsQuantiseImage *const input_image,
|
||||||
|
VipsQuantiseAttr *const options, VipsQuantiseResult **result_output )
|
||||||
|
{
|
||||||
|
/* Quantizr doesn't change the palette during remapping, so we don't
|
||||||
|
* need a special implementation for this
|
||||||
|
*/
|
||||||
|
return vips__quantise_image_quantize( input_image, options,
|
||||||
|
result_output );
|
||||||
|
}
|
||||||
|
|
||||||
VipsQuantiseError
|
VipsQuantiseError
|
||||||
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||||
float dither_level )
|
float dither_level )
|
||||||
|
@ -69,6 +69,8 @@ VipsQuantiseImage *vips__quantise_image_create_rgba( const VipsQuantiseAttr *att
|
|||||||
const void *bitmap, int width, int height, double gamma );
|
const void *bitmap, int width, int height, double gamma );
|
||||||
VipsQuantiseError vips__quantise_image_quantize( VipsQuantiseImage *input_image,
|
VipsQuantiseError vips__quantise_image_quantize( VipsQuantiseImage *input_image,
|
||||||
VipsQuantiseAttr *options, VipsQuantiseResult **result_output );
|
VipsQuantiseAttr *options, VipsQuantiseResult **result_output );
|
||||||
|
VipsQuantiseError vips__quantise_image_quantize_fixed( VipsQuantiseImage *input_image,
|
||||||
|
VipsQuantiseAttr *options, VipsQuantiseResult **result_output );
|
||||||
VipsQuantiseError vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
VipsQuantiseError vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||||
float dither_level );
|
float dither_level );
|
||||||
const VipsQuantisePalette *vips__quantise_get_palette( VipsQuantiseResult *result );
|
const VipsQuantisePalette *vips__quantise_get_palette( VipsQuantiseResult *result );
|
||||||
|
Loading…
Reference in New Issue
Block a user