@@ -86,8 +86,7 @@ typedef struct _VipsForeignSaveCgif {
86
86
*/
87
87
VipsQuantiseAttr * attr ;
88
88
VipsQuantiseImage * input_image ;
89
- VipsQuantiseResult * quantisation_result ;
90
- const VipsQuantisePalette * lp ;
89
+ VipsQuantiseResult * quantisation_result , * local_quantisation_result ;
91
90
92
91
/* The current colourmap, updated on a significant frame change.
93
92
*/
@@ -134,6 +133,8 @@ vips_foreign_save_cgif_dispose( GObject *gobject )
134
133
VIPS_FREEF ( cgif_close , cgif -> cgif_context );
135
134
136
135
VIPS_FREEF ( vips__quantise_result_destroy , cgif -> quantisation_result );
136
+ VIPS_FREEF ( vips__quantise_result_destroy ,
137
+ cgif -> local_quantisation_result );
137
138
VIPS_FREEF ( vips__quantise_image_destroy , cgif -> input_image );
138
139
VIPS_FREEF ( vips__quantise_attr_destroy , cgif -> attr );
139
140
@@ -203,6 +204,39 @@ vips_foreign_save_cgif_check_alpha_constraint( const VipsPel *cur,
203
204
return FALSE;
204
205
}
205
206
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
+
206
240
/* We have a complete frame --- write!
207
241
*/
208
242
static int
@@ -220,6 +254,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
220
254
VipsPel * restrict p ;
221
255
int i ;
222
256
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;
223
261
CGIF_FrameConfig frame_config ;
224
262
225
263
#ifdef DEBUG_VERBOSE
@@ -251,90 +289,124 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
251
289
p += 4 ;
252
290
}
253
291
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.
258
293
*/
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 );
277
303
}
304
+ vips__quantise_set_dithering_level ( quantisation_result , cgif -> dither );
305
+ lp = vips__quantise_get_palette ( quantisation_result );
278
306
279
- if ( cgif -> frame_checksum == 0 ||
280
- checksum != cgif -> frame_checksum ) {
281
- cgif -> frame_checksum = checksum ;
307
+ static double pal_change_threshold = 64.0 ;
282
308
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
285
312
*/
286
- if ( cgif -> quantisation_result )
287
- cgif -> cgif_config .attrFlags |=
288
- CGIF_ATTR_NO_GLOBAL_TABLE ;
313
+ cgif -> quantisation_result = quantisation_result ;
289
314
290
- VIPS_FREEF ( vips__quantise_result_destroy ,
315
+ } else {
316
+ pal_global = vips__quantise_get_palette (
291
317
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 );
297
328
}
298
329
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 ;
299
374
#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 );
305
378
#endif /*DEBUG_PERCENT*/
379
+ }
306
380
}
307
381
}
308
382
309
383
/* Dither frame.
310
384
*/
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 ,
315
386
cgif -> input_image , cgif -> index , n_pels ) ) {
316
387
vips_error ( class -> nickname , "%s" , _ ( "dither failed" ) );
317
388
return ( -1 );
318
389
}
319
390
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
+ }
333
405
}
334
406
335
407
/* If there's a transparent pixel, it's always first.
336
408
*/
337
- cgif -> has_transparency = cgif -> lp -> entries [0 ].a == 0 ;
409
+ cgif -> has_transparency = lp -> entries [0 ].a == 0 ;
338
410
339
411
/* Set up cgif on first use, so we can set the first cmap as the global
340
412
* one.
@@ -352,7 +424,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
352
424
#endif /*HAVE_CGIF_ATTR_NO_LOOP*/
353
425
cgif -> cgif_config .width = frame_rect -> width ;
354
426
cgif -> cgif_config .height = frame_rect -> height ;
355
- cgif -> cgif_config .numGlobalPaletteEntries = cgif -> lp -> count ;
427
+ cgif -> cgif_config .numGlobalPaletteEntries = lp -> count ;
356
428
#ifdef HAVE_CGIF_ATTR_NO_LOOP
357
429
cgif -> cgif_config .numLoops = cgif -> loop > 1 ?
358
430
cgif -> loop - 1 : cgif -> loop ;
@@ -406,7 +478,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
406
478
int i ;
407
479
uint8_t trans_index ;
408
480
409
- trans_index = cgif -> lp -> count ;
481
+ trans_index = lp -> count ;
410
482
if ( cgif -> has_transparency ) {
411
483
trans_index = 0 ;
412
484
frame_config .attrFlags &=
@@ -447,10 +519,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
447
519
448
520
/* Attach a local palette, if we need one.
449
521
*/
450
- if ( cgif -> cgif_config . attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
522
+ if ( use_local_palette ) {
451
523
frame_config .attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE ;
452
524
frame_config .pLocalPalette = cgif -> palette_rgb ;
453
- frame_config .numLocalPaletteEntries = cgif -> lp -> count ;
525
+ frame_config .numLocalPaletteEntries = lp -> count ;
454
526
}
455
527
456
528
cgif_addframe ( cgif -> cgif_context , & frame_config );
@@ -463,6 +535,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
463
535
memcpy ( cgif -> frame_bytes_head , frame_bytes , 4 * n_pels );
464
536
}
465
537
538
+
466
539
return ( 0 );
467
540
}
468
541
@@ -572,10 +645,6 @@ vips_foreign_save_cgif_build( VipsObject *object )
572
645
frame_rect .top = 0 ;
573
646
frame_rect .width = cgif -> in -> Xsize ;
574
647
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
- }
579
648
580
649
/* Assemble frames here.
581
650
*/
@@ -632,6 +701,7 @@ vips_foreign_save_cgif_build( VipsObject *object )
632
701
"%s" , _ ( "quantisation failed" ) );
633
702
return ( -1 );
634
703
}
704
+ vips__quantise_set_dithering_level ( cgif -> quantisation_result , cgif -> dither );
635
705
VIPS_FREE ( tmp_gct );
636
706
VIPS_FREEF ( vips__quantise_image_destroy , tmp_image );
637
707
}
0 commit comments