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:
Daniel Löbl 2022-05-23 14:01:26 +02:00 committed by GitHub
parent 7d3aaf540c
commit f26f0b09b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 111 additions and 39 deletions

View File

@ -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]

View File

@ -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.
*

View File

@ -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;
}
}