@@ -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,56 @@ 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 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
+
206
257
/* We have a complete frame --- write!
207
258
*/
208
259
static int
@@ -220,6 +271,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
220
271
VipsPel * restrict p ;
221
272
int i ;
222
273
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;
223
278
CGIF_FrameConfig frame_config ;
224
279
225
280
#ifdef DEBUG_VERBOSE
@@ -251,90 +306,125 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
251
306
p += 4 ;
252
307
}
253
308
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.
258
310
*/
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 );
277
320
}
321
+ lp = vips__quantise_get_palette ( quantisation_result );
278
322
279
- if ( cgif -> frame_checksum == 0 ||
280
- checksum != cgif -> frame_checksum ) {
281
- cgif -> frame_checksum = checksum ;
323
+ static double pal_change_threshold = 64.0 ;
282
324
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
285
328
*/
286
- if ( cgif -> quantisation_result )
287
- cgif -> cgif_config .attrFlags |=
288
- CGIF_ATTR_NO_GLOBAL_TABLE ;
329
+ cgif -> quantisation_result = quantisation_result ;
289
330
290
- VIPS_FREEF ( vips__quantise_result_destroy ,
331
+ } else {
332
+ pal_global = vips__quantise_get_palette (
291
333
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 );
297
344
}
298
345
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 ;
299
390
#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 );
305
394
#endif /*DEBUG_PERCENT*/
395
+ }
306
396
}
307
397
}
308
398
309
399
/* Dither frame.
310
400
*/
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 ,
315
403
cgif -> input_image , cgif -> index , n_pels ) ) {
316
404
vips_error ( class -> nickname , "%s" , _ ( "dither failed" ) );
317
405
return ( -1 );
318
406
}
319
407
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
+ }
333
423
}
334
424
335
425
/* If there's a transparent pixel, it's always first.
336
426
*/
337
- cgif -> has_transparency = cgif -> lp -> entries [0 ].a == 0 ;
427
+ cgif -> has_transparency = lp -> entries [0 ].a == 0 ;
338
428
339
429
/* Set up cgif on first use, so we can set the first cmap as the global
340
430
* one.
@@ -352,7 +442,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
352
442
#endif /*HAVE_CGIF_ATTR_NO_LOOP*/
353
443
cgif -> cgif_config .width = frame_rect -> width ;
354
444
cgif -> cgif_config .height = frame_rect -> height ;
355
- cgif -> cgif_config .numGlobalPaletteEntries = cgif -> lp -> count ;
445
+ cgif -> cgif_config .numGlobalPaletteEntries = lp -> count ;
356
446
#ifdef HAVE_CGIF_ATTR_NO_LOOP
357
447
cgif -> cgif_config .numLoops = cgif -> loop > 1 ?
358
448
cgif -> loop - 1 : cgif -> loop ;
@@ -406,7 +496,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
406
496
int i ;
407
497
uint8_t trans_index ;
408
498
409
- trans_index = cgif -> lp -> count ;
499
+ trans_index = lp -> count ;
410
500
if ( cgif -> has_transparency ) {
411
501
trans_index = 0 ;
412
502
frame_config .attrFlags &=
@@ -447,10 +537,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
447
537
448
538
/* Attach a local palette, if we need one.
449
539
*/
450
- if ( cgif -> cgif_config . attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
540
+ if ( use_local_palette ) {
451
541
frame_config .attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE ;
452
542
frame_config .pLocalPalette = cgif -> palette_rgb ;
453
- frame_config .numLocalPaletteEntries = cgif -> lp -> count ;
543
+ frame_config .numLocalPaletteEntries = lp -> count ;
454
544
}
455
545
456
546
cgif_addframe ( cgif -> cgif_context , & frame_config );
@@ -463,6 +553,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
463
553
memcpy ( cgif -> frame_bytes_head , frame_bytes , 4 * n_pels );
464
554
}
465
555
556
+
466
557
return ( 0 );
467
558
}
468
559
@@ -572,10 +663,6 @@ vips_foreign_save_cgif_build( VipsObject *object )
572
663
frame_rect .top = 0 ;
573
664
frame_rect .width = cgif -> in -> Xsize ;
574
665
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
666
580
667
/* Assemble frames here.
581
668
*/
0 commit comments