cgifsave: reuse global palette, if possible (#2797)
* save GIF palette as metadata ... if there are no local colour tables. See https://github.com/libvips/libvips/issues/2576 * cgifsave: reuse global palette, if possible * add reuse_palette parameter * add reoptimise parameter and reuse palette by default * attach global palette even if local palettes are present * add check for presence of use-lct * Revert "add check for presence of use-lct" This reverts commit cd0f14e45e8bed8f108ee9666e2569c6355f17eb. * Revert "attach global palette even if local palettes are present" This reverts commit 4085b9e14b73c8990e9653ccc6f90477c2a43032. * move global palette quantization to cgif_build * rename member variable gct * update comments * improve error handling * update documentation Co-authored-by: John Cupitt <jcupitt@gmail.com>
This commit is contained in:
parent
7d3aaf540c
commit
f26f0b09b8
@ -27,6 +27,7 @@
|
||||
- add vips_tiffsave_target()
|
||||
- add vips_target_end(), deprecate vips_target_finish()
|
||||
- add "mixed" to webpsave [dloebl]
|
||||
- add "reoptimise" to gifsave [dloebl]
|
||||
|
||||
26/11/21 started 8.12.3
|
||||
- better arg checking for vips_hist_find_ndim() [travisbell]
|
||||
|
@ -63,6 +63,7 @@ typedef struct _VipsForeignSaveCgif {
|
||||
int effort;
|
||||
int bitdepth;
|
||||
double maxerror;
|
||||
gboolean reoptimise;
|
||||
VipsTarget *target;
|
||||
|
||||
/* Derived write params.
|
||||
@ -72,6 +73,8 @@ typedef struct _VipsForeignSaveCgif {
|
||||
int *delay;
|
||||
int delay_length;
|
||||
int loop;
|
||||
int *global_colour_table;
|
||||
int global_colour_table_length;
|
||||
|
||||
/* We save ->ready a frame at a time, regenerating the
|
||||
* palette if we see a significant frame to frame change.
|
||||
@ -248,50 +251,52 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
*
|
||||
* frame_sum 0 means no current colourmap.
|
||||
*/
|
||||
sum = 0;
|
||||
p = frame_bytes;
|
||||
for( i = 0; i < n_pels; i++ ) {
|
||||
/* Scale RGBA differently so that changes like [0, 255, 0]
|
||||
* to [255, 0, 0] are detected.
|
||||
*/
|
||||
sum += p[0] * 1000;
|
||||
sum += p[1] * 100;
|
||||
sum += p[2] * 10;
|
||||
sum += p[3];
|
||||
if( !cgif->global_colour_table ) {
|
||||
sum = 0;
|
||||
p = frame_bytes;
|
||||
for( i = 0; i < n_pels; i++ ) {
|
||||
/* Scale RGBA differently so that changes like [0, 255, 0]
|
||||
* to [255, 0, 0] are detected.
|
||||
*/
|
||||
sum += p[0] * 1000;
|
||||
sum += p[1] * 100;
|
||||
sum += p[2] * 10;
|
||||
sum += p[3];
|
||||
|
||||
p += 4;
|
||||
}
|
||||
change = VIPS_ABS( ((double) sum - cgif->frame_sum) ) / n_pels;
|
||||
|
||||
if( cgif->frame_sum == 0 ||
|
||||
change > 0 ) {
|
||||
cgif->frame_sum = sum;
|
||||
|
||||
/* 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,
|
||||
"%s", _( "quantisation failed" ) );
|
||||
return( -1 );
|
||||
p += 4;
|
||||
}
|
||||
change = VIPS_ABS( ((double) sum - cgif->frame_sum) ) / n_pels;
|
||||
|
||||
if( cgif->frame_sum == 0 ||
|
||||
change > 0 ) {
|
||||
cgif->frame_sum = sum;
|
||||
|
||||
/* 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,
|
||||
"%s", _( "quantisation failed" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PERCENT
|
||||
cgif->n_cmaps_generated += 1;
|
||||
cgif->n_cmaps_generated += 1;
|
||||
#endif/*DEBUG_PERCENT*/
|
||||
}
|
||||
}
|
||||
|
||||
/* Dither frame.
|
||||
*/
|
||||
vips__quantise_set_dithering_level( cgif->quantisation_result,
|
||||
cgif->dither );
|
||||
|
||||
if( vips__quantise_write_remapped_image( cgif->quantisation_result,
|
||||
cgif->input_image, cgif->index, n_pels ) ) {
|
||||
vips_error( class->nickname, "%s", _( "dither failed" ) );
|
||||
@ -585,6 +590,42 @@ vips_foreign_save_cgif_build( VipsObject *object )
|
||||
vips__quantise_set_quality( cgif->attr, 0, 100 );
|
||||
vips__quantise_set_speed( cgif->attr, 11 - cgif->effort );
|
||||
|
||||
/* Initialise quantization result using global palette.
|
||||
*/
|
||||
if( !cgif->reoptimise &&
|
||||
vips_image_get_typeof( cgif->in, "gif-palette" ) ) {
|
||||
VipsQuantiseImage *tmp_image;
|
||||
int *tmp_gct;
|
||||
int *gct;
|
||||
int gct_length;
|
||||
|
||||
vips_image_get_array_int( cgif->in, "gif-palette",
|
||||
&cgif->global_colour_table,
|
||||
&cgif->global_colour_table_length);
|
||||
gct = cgif->global_colour_table;
|
||||
gct_length = cgif->global_colour_table_length;
|
||||
|
||||
/* Attach fake alpha channel.
|
||||
* That's necessary, because we do not know whether there is an
|
||||
* alpha channel before processing the animation.
|
||||
*/
|
||||
tmp_gct = g_malloc((gct_length + 1) * sizeof(int));
|
||||
memcpy(tmp_gct, gct, gct_length * sizeof(int));
|
||||
tmp_gct[gct_length] = 0;
|
||||
tmp_image = vips__quantise_image_create_rgba( cgif->attr,
|
||||
tmp_gct, gct_length + 1, 1, 0 );
|
||||
|
||||
if( vips__quantise_image_quantize( tmp_image,
|
||||
cgif->attr, &cgif->quantisation_result ) ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "quantisation failed" ) );
|
||||
return( -1 );
|
||||
}
|
||||
VIPS_FREE( tmp_gct );
|
||||
VIPS_FREEF( vips__quantise_image_destroy, tmp_image );
|
||||
}
|
||||
|
||||
|
||||
/* Set up cgif on first use.
|
||||
*/
|
||||
|
||||
@ -657,6 +698,13 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class )
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, maxerror ),
|
||||
0, 32, 0.0 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "reoptimise", 14,
|
||||
_( "Reoptimise palettes" ),
|
||||
_( "Reoptimise colour palettes" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, reoptimise ),
|
||||
FALSE );
|
||||
}
|
||||
|
||||
static void
|
||||
@ -666,6 +714,7 @@ vips_foreign_save_cgif_init( VipsForeignSaveCgif *gif )
|
||||
gif->effort = 7;
|
||||
gif->bitdepth = 8;
|
||||
gif->maxerror = 0.0;
|
||||
gif->reoptimise = FALSE;
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveCgifTarget {
|
||||
@ -847,6 +896,7 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer )
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
*
|
||||
* Write to a file in GIF format.
|
||||
*
|
||||
@ -893,6 +943,7 @@ vips_gifsave( VipsImage *in, const char *filename, ... )
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
*
|
||||
* As vips_gifsave(), but save to a memory buffer.
|
||||
*
|
||||
@ -944,6 +995,7 @@ vips_gifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
*
|
||||
* As vips_gifsave(), but save to a target.
|
||||
*
|
||||
|
@ -4,6 +4,8 @@
|
||||
* - from gifload.c
|
||||
* 3/3/22 tlsa
|
||||
* - update libnsgif API
|
||||
*9/5/22
|
||||
- attach GIF palette as metadata
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -127,6 +129,10 @@ typedef struct _VipsForeignLoadNsgif {
|
||||
*/
|
||||
gboolean has_transparency;
|
||||
|
||||
/* If the GIF has any local palettes.
|
||||
*/
|
||||
gboolean local_palette;
|
||||
|
||||
/* The current frame bitmap and the frame number for it.
|
||||
*/
|
||||
nsgif_bitmap_t *bitmap;
|
||||
@ -289,6 +295,18 @@ vips_foreign_load_nsgif_set_header( VipsForeignLoadNsgif *gif,
|
||||
*/
|
||||
vips_image_set_int( image, "gif-delay", gif->gif_delay );
|
||||
|
||||
/* If there are no local palettes, we can attach the global palette as
|
||||
* metadata.
|
||||
*/
|
||||
if( !gif->local_palette ) {
|
||||
size_t entries;
|
||||
uint32_t table[NSGIF_MAX_COLOURS];
|
||||
|
||||
nsgif_global_palette( gif->anim, table, &entries );
|
||||
vips_image_set_array_int( image, "gif-palette",
|
||||
(const int *) table, entries );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -342,10 +360,11 @@ vips_foreign_load_nsgif_header( VipsForeignLoad *load )
|
||||
for( i = 0; i < gif->info->frame_count; i++ ) {
|
||||
const nsgif_frame_info_t *frame_info;
|
||||
|
||||
frame_info = nsgif_get_frame_info( gif->anim, i );
|
||||
if( frame_info != NULL && frame_info->transparency ) {
|
||||
gif->has_transparency = TRUE;
|
||||
break;
|
||||
if( (frame_info = nsgif_get_frame_info( gif->anim, i )) ) {
|
||||
if( frame_info->transparency )
|
||||
gif->has_transparency = TRUE;
|
||||
if( frame_info->local_palette )
|
||||
gif->local_palette = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user