Skip to content

Commit 712f057

Browse files
committed
cgifsave: compare palettes instead of frame sum
1 parent 2af2ca5 commit 712f057

File tree

1 file changed

+139
-69
lines changed

1 file changed

+139
-69
lines changed

libvips/foreign/cgifsave.c

Lines changed: 139 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ typedef struct _VipsForeignSaveCgif {
8686
*/
8787
VipsQuantiseAttr *attr;
8888
VipsQuantiseImage *input_image;
89-
VipsQuantiseResult *quantisation_result;
90-
const VipsQuantisePalette *lp;
89+
VipsQuantiseResult *quantisation_result, *local_quantisation_result;
9190

9291
/* The current colourmap, updated on a significant frame change.
9392
*/
@@ -134,6 +133,8 @@ vips_foreign_save_cgif_dispose( GObject *gobject )
134133
VIPS_FREEF( cgif_close, cgif->cgif_context );
135134

136135
VIPS_FREEF( vips__quantise_result_destroy, cgif->quantisation_result );
136+
VIPS_FREEF( vips__quantise_result_destroy,
137+
cgif->local_quantisation_result );
137138
VIPS_FREEF( vips__quantise_image_destroy, cgif->input_image );
138139
VIPS_FREEF( vips__quantise_attr_destroy, cgif->attr );
139140

@@ -203,6 +204,39 @@ vips_foreign_save_cgif_check_alpha_constraint( const VipsPel *cur,
203204
return FALSE;
204205
}
205206

207+
static double
208+
vips__cgif_compare_palettes(const VipsQuantisePalette *new,
209+
const VipsQuantisePalette *old)
210+
{
211+
g_assert( new->count <= 256 );
212+
g_assert( old->count <= 256 );
213+
214+
int i, j;
215+
double total_dist = 0.0;
216+
217+
for( i = 0; i < new->count; i++ ) {
218+
double best_dist = 255.0 * 255.0 * 3;
219+
220+
for( j = 0; j < old->count; j++ ) {
221+
if(
222+
(new->entries[i].a >= 128) !=
223+
(old->entries[j].a >= 128)
224+
) continue;
225+
226+
double rd = new->entries[i].r - old->entries[j].r;
227+
double gd = new->entries[i].g - old->entries[j].g;
228+
double bd = new->entries[i].b - old->entries[j].b;
229+
double dist = rd*rd + gd*gd + bd*bd;
230+
231+
best_dist = VIPS_MIN(best_dist, dist);
232+
}
233+
234+
total_dist += best_dist;
235+
}
236+
237+
return total_dist / new->count;
238+
}
239+
206240
/* We have a complete frame --- write!
207241
*/
208242
static int
@@ -220,6 +254,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
220254
VipsPel * restrict p;
221255
int i;
222256
VipsPel *rgb;
257+
VipsQuantiseResult *quantisation_result;
258+
const VipsQuantisePalette *lp, *pal_global, *pal_local;
259+
double pal_change_global, pal_change_local;
260+
gboolean use_local_palette = FALSE;
223261
CGIF_FrameConfig frame_config;
224262

225263
#ifdef DEBUG_VERBOSE
@@ -251,90 +289,124 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
251289
p += 4;
252290
}
253291

254-
/* Do we need to compute a new palette? Do it if the frame sum
255-
* changes.
256-
*
257-
* frame_checksum 0 means no current colourmap.
292+
/* Do we need to compute a new palette? Do it if the palette changes.
258293
*/
259-
if( !cgif->global_colour_table ) {
260-
gint64 checksum;
261-
262-
/* We need a checksum which detects colour changes, but
263-
* doesn't care about pixel ordering.
264-
*
265-
* Scale RGBA differently so that changes like [0, 255, 0]
266-
* to [255, 0, 0] are detected.
267-
*/
268-
checksum = 0;
269-
p = frame_bytes;
270-
for( i = 0; i < n_pels; i++ ) {
271-
checksum += p[0] * 1000;
272-
checksum += p[1] * 100;
273-
checksum += p[2] * 10;
274-
checksum += p[3];
275-
276-
p += 4;
294+
if( cgif->global_colour_table ) {
295+
quantisation_result = cgif->quantisation_result;
296+
lp = vips__quantise_get_palette( quantisation_result );
297+
} else {
298+
if( vips__quantise_image_quantize( cgif->input_image, cgif->attr,
299+
&quantisation_result ) ) {
300+
vips_error( class->nickname,
301+
"%s", _( "quantisation failed" ) );
302+
return( -1 );
277303
}
304+
vips__quantise_set_dithering_level( quantisation_result, cgif->dither );
305+
lp = vips__quantise_get_palette( quantisation_result );
278306

279-
if( cgif->frame_checksum == 0 ||
280-
checksum != cgif->frame_checksum ) {
281-
cgif->frame_checksum = checksum;
307+
static double pal_change_threshold = 64.0;
282308

283-
/* If this is not our first cmap, make a note that we
284-
* need to attach it as a local cmap when we write.
309+
if( !cgif->quantisation_result ) {
310+
/* This is the first frame, save global quantization
311+
* result and palette
285312
*/
286-
if( cgif->quantisation_result )
287-
cgif->cgif_config.attrFlags |=
288-
CGIF_ATTR_NO_GLOBAL_TABLE;
313+
cgif->quantisation_result = quantisation_result;
289314

290-
VIPS_FREEF( vips__quantise_result_destroy,
315+
} else {
316+
pal_global = vips__quantise_get_palette(
291317
cgif->quantisation_result );
292-
if( vips__quantise_image_quantize( cgif->input_image,
293-
cgif->attr, &cgif->quantisation_result ) ) {
294-
vips_error( class->nickname,
295-
"%s", _( "quantisation failed" ) );
296-
return( -1 );
318+
pal_change_global = vips__cgif_compare_palettes(
319+
lp, pal_global );
320+
321+
if ( !cgif->local_quantisation_result )
322+
pal_change_local = 255*255*3;
323+
else {
324+
pal_local = vips__quantise_get_palette(
325+
cgif->local_quantisation_result );
326+
pal_change_local = vips__cgif_compare_palettes(
327+
lp, pal_local );
297328
}
298329

330+
if(
331+
pal_change_local <= pal_change_global &&
332+
pal_change_local <= pal_change_threshold
333+
) {
334+
/* Local palette change is low, use previous
335+
* local quantization result and palette
336+
*/
337+
VIPS_FREEF( vips__quantise_result_destroy,
338+
quantisation_result );
339+
quantisation_result =
340+
cgif->local_quantisation_result;
341+
lp = pal_local;
342+
343+
use_local_palette = 1;
344+
345+
} else if(
346+
pal_change_global <= pal_change_threshold
347+
) {
348+
/* Global palette change is low, use global
349+
* quantization result and palette
350+
*/
351+
VIPS_FREEF( vips__quantise_result_destroy,
352+
quantisation_result );
353+
quantisation_result = cgif->quantisation_result;
354+
lp = pal_global;
355+
356+
/* Also drop saved local result as it's usage
357+
* doesn't make sense now and it's better to
358+
* use a new local result if neeeded
359+
*/
360+
VIPS_FREEF( vips__quantise_result_destroy,
361+
cgif->local_quantisation_result );
362+
cgif->local_quantisation_result = NULL;
363+
364+
} else {
365+
/* Palette change is high, use local
366+
* quantization result and palette
367+
*/
368+
VIPS_FREEF( vips__quantise_result_destroy,
369+
cgif->local_quantisation_result );
370+
cgif->local_quantisation_result =
371+
quantisation_result;
372+
373+
use_local_palette = 1;
299374
#ifdef DEBUG_PERCENT
300-
cgif->n_cmaps_generated += 1;
301-
cgif->lp = vips__quantise_get_palette(
302-
cgif->quantisation_result );
303-
printf( "frame %d, new %d item colourmap\n",
304-
page_index, cgif->lp->count );
375+
cgif->n_cmaps_generated += 1;
376+
printf( "frame %d, new %d item colourmap\n",
377+
page_index, lp->count );
305378
#endif/*DEBUG_PERCENT*/
379+
}
306380
}
307381
}
308382

309383
/* Dither frame.
310384
*/
311-
vips__quantise_set_dithering_level( cgif->quantisation_result,
312-
cgif->dither );
313-
314-
if( vips__quantise_write_remapped_image( cgif->quantisation_result,
385+
if( vips__quantise_write_remapped_image( quantisation_result,
315386
cgif->input_image, cgif->index, n_pels ) ) {
316387
vips_error( class->nickname, "%s", _( "dither failed" ) );
317388
return( -1 );
318389
}
319390

320-
/* Call vips__quantise_get_palette() after
321-
* vips__quantise_write_remapped_image(), as palette is improved
322-
* during remapping.
323-
*/
324-
cgif->lp = vips__quantise_get_palette( cgif->quantisation_result );
325-
rgb = cgif->palette_rgb;
326-
g_assert( cgif->lp->count <= 256 );
327-
for( i = 0; i < cgif->lp->count; i++ ) {
328-
rgb[0] = cgif->lp->entries[i].r;
329-
rgb[1] = cgif->lp->entries[i].g;
330-
rgb[2] = cgif->lp->entries[i].b;
331-
332-
rgb += 3;
391+
if (use_local_palette || !cgif->cgif_context ) {
392+
/* Call vips__quantise_get_palette() after vips__quantise_write_remapped_image(),
393+
* as palette is improved during remapping.
394+
*/
395+
lp = vips__quantise_get_palette( quantisation_result );
396+
rgb = cgif->palette_rgb;
397+
g_assert( lp->count <= 256 );
398+
for( i = 0; i < lp->count; i++ ) {
399+
rgb[0] = lp->entries[i].r;
400+
rgb[1] = lp->entries[i].g;
401+
rgb[2] = lp->entries[i].b;
402+
403+
rgb += 3;
404+
}
333405
}
334406

335407
/* If there's a transparent pixel, it's always first.
336408
*/
337-
cgif->has_transparency = cgif->lp->entries[0].a == 0;
409+
cgif->has_transparency = lp->entries[0].a == 0;
338410

339411
/* Set up cgif on first use, so we can set the first cmap as the global
340412
* one.
@@ -352,7 +424,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
352424
#endif/*HAVE_CGIF_ATTR_NO_LOOP*/
353425
cgif->cgif_config.width = frame_rect->width;
354426
cgif->cgif_config.height = frame_rect->height;
355-
cgif->cgif_config.numGlobalPaletteEntries = cgif->lp->count;
427+
cgif->cgif_config.numGlobalPaletteEntries = lp->count;
356428
#ifdef HAVE_CGIF_ATTR_NO_LOOP
357429
cgif->cgif_config.numLoops = cgif->loop > 1 ?
358430
cgif->loop - 1 : cgif->loop;
@@ -406,7 +478,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
406478
int i;
407479
uint8_t trans_index;
408480

409-
trans_index = cgif->lp->count;
481+
trans_index = lp->count;
410482
if( cgif->has_transparency ) {
411483
trans_index = 0;
412484
frame_config.attrFlags &=
@@ -447,10 +519,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
447519

448520
/* Attach a local palette, if we need one.
449521
*/
450-
if( cgif->cgif_config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
522+
if( use_local_palette ) {
451523
frame_config.attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
452524
frame_config.pLocalPalette = cgif->palette_rgb;
453-
frame_config.numLocalPaletteEntries = cgif->lp->count;
525+
frame_config.numLocalPaletteEntries = lp->count;
454526
}
455527

456528
cgif_addframe( cgif->cgif_context, &frame_config );
@@ -463,6 +535,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
463535
memcpy( cgif->frame_bytes_head, frame_bytes, 4 * n_pels );
464536
}
465537

538+
466539
return( 0 );
467540
}
468541

@@ -572,10 +645,6 @@ vips_foreign_save_cgif_build( VipsObject *object )
572645
frame_rect.top = 0;
573646
frame_rect.width = cgif->in->Xsize;
574647
frame_rect.height = page_height;
575-
if( (guint64) frame_rect.width * frame_rect.height > 5000 * 5000 ) {
576-
vips_error( class->nickname, "%s", _( "frame too large" ) );
577-
return( -1 );
578-
}
579648

580649
/* Assemble frames here.
581650
*/
@@ -632,6 +701,7 @@ vips_foreign_save_cgif_build( VipsObject *object )
632701
"%s", _( "quantisation failed" ) );
633702
return( -1 );
634703
}
704+
vips__quantise_set_dithering_level( cgif->quantisation_result, cgif->dither );
635705
VIPS_FREE( tmp_gct );
636706
VIPS_FREEF( vips__quantise_image_destroy, tmp_image );
637707
}

0 commit comments

Comments
 (0)