Skip to content

Commit 0ea45c8

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

File tree

1 file changed

+156
-69
lines changed

1 file changed

+156
-69
lines changed

libvips/foreign/cgifsave.c

Lines changed: 156 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,56 @@ 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 best_dist, dist, rd, gd, bd;
216+
double total_dist = 0.0;
217+
218+
for( i = 0; i < new->count; i++ ) {
219+
best_dist = 255.0 * 255.0 * 3;
220+
221+
for( j = 0; j < old->count; j++ ) {
222+
if( new->entries[i].a >= 128 ) {
223+
/* The new entry is solid.
224+
* If the old entry is transparent, ignore it.
225+
* Otherwise, compare RGB.
226+
*/
227+
if( old->entries[j].a < 128 )
228+
continue;
229+
230+
rd = new->entries[i].r - old->entries[j].r;
231+
gd = new->entries[i].g - old->entries[j].g;
232+
bd = new->entries[i].b - old->entries[j].b;
233+
dist = rd*rd + gd*gd + bd*bd;
234+
235+
best_dist = VIPS_MIN(best_dist, dist);
236+
237+
/* We fond the closest entry
238+
*/
239+
if( best_dist == 0 )
240+
break;
241+
} else {
242+
/* The new entry is transparent.
243+
* If the old entry is transparent too, it's
244+
* the closest color. Otherwise, ignore it.
245+
*/
246+
if( old->entries[j].a < 128 )
247+
best_dist = 0;
248+
}
249+
}
250+
251+
total_dist += best_dist;
252+
}
253+
254+
return total_dist / new->count;
255+
}
256+
206257
/* We have a complete frame --- write!
207258
*/
208259
static int
@@ -220,6 +271,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
220271
VipsPel * restrict p;
221272
int i;
222273
VipsPel *rgb;
274+
VipsQuantiseResult *quantisation_result;
275+
const VipsQuantisePalette *lp, *pal_global, *pal_local;
276+
double pal_change_global, pal_change_local;
277+
gboolean use_local_palette = FALSE;
223278
CGIF_FrameConfig frame_config;
224279

225280
#ifdef DEBUG_VERBOSE
@@ -251,90 +306,125 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
251306
p += 4;
252307
}
253308

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.
309+
/* Do we need to compute a new palette? Do it if the palette changes.
258310
*/
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;
311+
if( cgif->global_colour_table ) {
312+
quantisation_result = cgif->quantisation_result;
313+
lp = vips__quantise_get_palette( quantisation_result );
314+
} else {
315+
if( vips__quantise_image_quantize( cgif->input_image, cgif->attr,
316+
&quantisation_result ) ) {
317+
vips_error( class->nickname,
318+
"%s", _( "quantisation failed" ) );
319+
return( -1 );
277320
}
321+
lp = vips__quantise_get_palette( quantisation_result );
278322

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

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.
325+
if( !cgif->quantisation_result ) {
326+
/* This is the first frame, save global quantization
327+
* result and palette
285328
*/
286-
if( cgif->quantisation_result )
287-
cgif->cgif_config.attrFlags |=
288-
CGIF_ATTR_NO_GLOBAL_TABLE;
329+
cgif->quantisation_result = quantisation_result;
289330

290-
VIPS_FREEF( vips__quantise_result_destroy,
331+
} else {
332+
pal_global = vips__quantise_get_palette(
291333
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 );
334+
pal_change_global = vips__cgif_compare_palettes(
335+
lp, pal_global );
336+
337+
if ( !cgif->local_quantisation_result )
338+
pal_change_local = 255*255*3;
339+
else {
340+
pal_local = vips__quantise_get_palette(
341+
cgif->local_quantisation_result );
342+
pal_change_local = vips__cgif_compare_palettes(
343+
lp, pal_local );
297344
}
298345

346+
if(
347+
pal_change_local <= pal_change_global &&
348+
pal_change_local <= pal_change_threshold
349+
) {
350+
/* Local palette change is low, use previous
351+
* local quantization result and palette
352+
*/
353+
VIPS_FREEF( vips__quantise_result_destroy,
354+
quantisation_result );
355+
quantisation_result =
356+
cgif->local_quantisation_result;
357+
lp = pal_local;
358+
359+
use_local_palette = 1;
360+
361+
} else if(
362+
pal_change_global <= pal_change_threshold
363+
) {
364+
/* Global palette change is low, use global
365+
* quantization result and palette
366+
*/
367+
VIPS_FREEF( vips__quantise_result_destroy,
368+
quantisation_result );
369+
quantisation_result = cgif->quantisation_result;
370+
lp = pal_global;
371+
372+
/* Also drop saved local result as it's usage
373+
* doesn't make sense now and it's better to
374+
* use a new local result if neeeded
375+
*/
376+
VIPS_FREEF( vips__quantise_result_destroy,
377+
cgif->local_quantisation_result );
378+
cgif->local_quantisation_result = NULL;
379+
380+
} else {
381+
/* Palette change is high, use local
382+
* quantization result and palette
383+
*/
384+
VIPS_FREEF( vips__quantise_result_destroy,
385+
cgif->local_quantisation_result );
386+
cgif->local_quantisation_result =
387+
quantisation_result;
388+
389+
use_local_palette = 1;
299390
#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 );
391+
cgif->n_cmaps_generated += 1;
392+
printf( "frame %d, new %d item colourmap\n",
393+
page_index, lp->count );
305394
#endif/*DEBUG_PERCENT*/
395+
}
306396
}
307397
}
308398

309399
/* Dither frame.
310400
*/
311-
vips__quantise_set_dithering_level( cgif->quantisation_result,
312-
cgif->dither );
313-
314-
if( vips__quantise_write_remapped_image( cgif->quantisation_result,
401+
vips__quantise_set_dithering_level( quantisation_result, cgif->dither );
402+
if( vips__quantise_write_remapped_image( quantisation_result,
315403
cgif->input_image, cgif->index, n_pels ) ) {
316404
vips_error( class->nickname, "%s", _( "dither failed" ) );
317405
return( -1 );
318406
}
319407

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;
408+
/* Call vips__quantise_get_palette() after vips__quantise_write_remapped_image(),
409+
* as palette is improved during remapping.
410+
*/
411+
lp = vips__quantise_get_palette( quantisation_result );
412+
413+
if (use_local_palette || !cgif->cgif_context ) {
414+
rgb = cgif->palette_rgb;
415+
g_assert( lp->count <= 256 );
416+
for( i = 0; i < lp->count; i++ ) {
417+
rgb[0] = lp->entries[i].r;
418+
rgb[1] = lp->entries[i].g;
419+
rgb[2] = lp->entries[i].b;
420+
421+
rgb += 3;
422+
}
333423
}
334424

335425
/* If there's a transparent pixel, it's always first.
336426
*/
337-
cgif->has_transparency = cgif->lp->entries[0].a == 0;
427+
cgif->has_transparency = lp->entries[0].a == 0;
338428

339429
/* Set up cgif on first use, so we can set the first cmap as the global
340430
* one.
@@ -352,7 +442,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
352442
#endif/*HAVE_CGIF_ATTR_NO_LOOP*/
353443
cgif->cgif_config.width = frame_rect->width;
354444
cgif->cgif_config.height = frame_rect->height;
355-
cgif->cgif_config.numGlobalPaletteEntries = cgif->lp->count;
445+
cgif->cgif_config.numGlobalPaletteEntries = lp->count;
356446
#ifdef HAVE_CGIF_ATTR_NO_LOOP
357447
cgif->cgif_config.numLoops = cgif->loop > 1 ?
358448
cgif->loop - 1 : cgif->loop;
@@ -406,7 +496,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
406496
int i;
407497
uint8_t trans_index;
408498

409-
trans_index = cgif->lp->count;
499+
trans_index = lp->count;
410500
if( cgif->has_transparency ) {
411501
trans_index = 0;
412502
frame_config.attrFlags &=
@@ -447,10 +537,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
447537

448538
/* Attach a local palette, if we need one.
449539
*/
450-
if( cgif->cgif_config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
540+
if( use_local_palette ) {
451541
frame_config.attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
452542
frame_config.pLocalPalette = cgif->palette_rgb;
453-
frame_config.numLocalPaletteEntries = cgif->lp->count;
543+
frame_config.numLocalPaletteEntries = lp->count;
454544
}
455545

456546
cgif_addframe( cgif->cgif_context, &frame_config );
@@ -463,6 +553,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
463553
memcpy( cgif->frame_bytes_head, frame_bytes, 4 * n_pels );
464554
}
465555

556+
466557
return( 0 );
467558
}
468559

@@ -572,10 +663,6 @@ vips_foreign_save_cgif_build( VipsObject *object )
572663
frame_rect.top = 0;
573664
frame_rect.width = cgif->in->Xsize;
574665
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-
}
579666

580667
/* Assemble frames here.
581668
*/

0 commit comments

Comments
 (0)