cgifsave: palette change POC (#2824)
* cgifsave: compare palettes instead of frame sum * Add vips__quantise_image_quantize_fixed function
This commit is contained in:
parent
5106e9b49e
commit
19bef959d1
@ -61,8 +61,9 @@ typedef struct _VipsForeignSaveCgif {
|
||||
double dither;
|
||||
int effort;
|
||||
int bitdepth;
|
||||
double maxerror;
|
||||
double interframe_maxerror;
|
||||
gboolean reoptimise;
|
||||
double interpalette_maxerror;
|
||||
VipsTarget *target;
|
||||
|
||||
/* Derived write params.
|
||||
@ -85,8 +86,7 @@ typedef struct _VipsForeignSaveCgif {
|
||||
*/
|
||||
VipsQuantiseAttr *attr;
|
||||
VipsQuantiseImage *input_image;
|
||||
VipsQuantiseResult *quantisation_result;
|
||||
const VipsQuantisePalette *lp;
|
||||
VipsQuantiseResult *quantisation_result, *local_quantisation_result;
|
||||
|
||||
/* The current colourmap, updated on a significant frame change.
|
||||
*/
|
||||
@ -133,6 +133,8 @@ vips_foreign_save_cgif_dispose( GObject *gobject )
|
||||
VIPS_FREEF( cgif_close, cgif->cgif_context );
|
||||
|
||||
VIPS_FREEF( vips__quantise_result_destroy, cgif->quantisation_result );
|
||||
VIPS_FREEF( vips__quantise_result_destroy,
|
||||
cgif->local_quantisation_result );
|
||||
VIPS_FREEF( vips__quantise_image_destroy, cgif->input_image );
|
||||
VIPS_FREEF( vips__quantise_attr_destroy, cgif->attr );
|
||||
|
||||
@ -182,6 +184,58 @@ vips_foreign_save_cgif_pixels_are_equal( const VipsPel *cur, const VipsPel *bef,
|
||||
return( dR * dR + dG * dG + dB * dB <= sq_maxerror );
|
||||
}
|
||||
|
||||
static double
|
||||
vips__cgif_compare_palettes(const VipsQuantisePalette *new,
|
||||
const VipsQuantisePalette *old)
|
||||
{
|
||||
g_assert( new->count <= 256 );
|
||||
g_assert( old->count <= 256 );
|
||||
|
||||
int i, j;
|
||||
double best_dist, dist, rd, gd, bd;
|
||||
double total_dist = 0.0;
|
||||
|
||||
for( i = 0; i < new->count; i++ ) {
|
||||
best_dist = 255.0 * 255.0 * 3;
|
||||
|
||||
for( j = 0; j < old->count; j++ ) {
|
||||
if( new->entries[i].a >= 128 ) {
|
||||
/* The new entry is solid.
|
||||
* If the old entry is transparent, ignore it.
|
||||
* Otherwise, compare RGB.
|
||||
*/
|
||||
if( old->entries[j].a < 128 )
|
||||
continue;
|
||||
|
||||
rd = new->entries[i].r - old->entries[j].r;
|
||||
gd = new->entries[i].g - old->entries[j].g;
|
||||
bd = new->entries[i].b - old->entries[j].b;
|
||||
dist = rd*rd + gd*gd + bd*bd;
|
||||
|
||||
best_dist = VIPS_MIN(best_dist, dist);
|
||||
|
||||
/* We found the closest entry
|
||||
*/
|
||||
if( best_dist == 0 )
|
||||
break;
|
||||
} else {
|
||||
/* The new entry is transparent.
|
||||
* If the old entry is transparent too, it's
|
||||
* the closest color. Otherwise, ignore it.
|
||||
*/
|
||||
if( old->entries[j].a < 128 ) {
|
||||
best_dist = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total_dist += best_dist;
|
||||
}
|
||||
|
||||
return total_dist / new->count;
|
||||
}
|
||||
|
||||
/* We have a complete frame --- write!
|
||||
*/
|
||||
static int
|
||||
@ -201,6 +255,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
VipsPel * restrict bef;
|
||||
gboolean has_alpha_constraint = FALSE;
|
||||
VipsPel *rgb;
|
||||
VipsQuantiseResult *quantisation_result;
|
||||
const VipsQuantisePalette *lp, *pal_global, *pal_local;
|
||||
double pal_change_global, pal_change_local;
|
||||
gboolean use_local_palette = FALSE;
|
||||
CGIF_FrameConfig frame_config;
|
||||
|
||||
#ifdef DEBUG_VERBOSE
|
||||
@ -243,90 +301,123 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
cur += 4;
|
||||
}
|
||||
|
||||
/* Do we need to compute a new palette? Do it if the frame sum
|
||||
* changes.
|
||||
*
|
||||
* frame_checksum 0 means no current colourmap.
|
||||
/* Do we need to compute a new palette? Do it if the palette changes.
|
||||
*/
|
||||
if( !cgif->global_colour_table ) {
|
||||
gint64 checksum;
|
||||
|
||||
/* We need a checksum which detects colour changes, but
|
||||
* doesn't care about pixel ordering.
|
||||
*
|
||||
* Scale RGBA differently so that changes like [0, 255, 0]
|
||||
* to [255, 0, 0] are detected.
|
||||
*/
|
||||
checksum = 0;
|
||||
cur = frame_bytes;
|
||||
for( i = 0; i < n_pels; i++ ) {
|
||||
checksum += cur[0] * 1000;
|
||||
checksum += cur[1] * 100;
|
||||
checksum += cur[2] * 10;
|
||||
checksum += cur[3];
|
||||
|
||||
cur += 4;
|
||||
}
|
||||
|
||||
if( cgif->frame_checksum == 0 ||
|
||||
checksum != cgif->frame_checksum ) {
|
||||
cgif->frame_checksum = checksum;
|
||||
|
||||
/* 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 ) ) {
|
||||
if( cgif->global_colour_table ) {
|
||||
quantisation_result = cgif->quantisation_result;
|
||||
lp = vips__quantise_get_palette( quantisation_result );
|
||||
} else {
|
||||
if( vips__quantise_image_quantize_fixed( cgif->input_image,
|
||||
cgif->attr, &quantisation_result ) ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "quantisation failed" ) );
|
||||
return( -1 );
|
||||
}
|
||||
lp = vips__quantise_get_palette( quantisation_result );
|
||||
|
||||
if( !cgif->quantisation_result ) {
|
||||
/* This is the first frame, save global quantization
|
||||
* result and palette
|
||||
*/
|
||||
cgif->quantisation_result = quantisation_result;
|
||||
|
||||
} else {
|
||||
pal_global = vips__quantise_get_palette(
|
||||
cgif->quantisation_result );
|
||||
pal_change_global = vips__cgif_compare_palettes(
|
||||
lp, pal_global );
|
||||
|
||||
if ( !cgif->local_quantisation_result )
|
||||
pal_change_local = 255*255*3;
|
||||
else {
|
||||
pal_local = vips__quantise_get_palette(
|
||||
cgif->local_quantisation_result );
|
||||
pal_change_local = vips__cgif_compare_palettes(
|
||||
lp, pal_local );
|
||||
}
|
||||
|
||||
if(
|
||||
pal_change_local <= pal_change_global &&
|
||||
pal_change_local <= cgif->interpalette_maxerror
|
||||
) {
|
||||
/* Local palette change is low, use previous
|
||||
* local quantization result and palette
|
||||
*/
|
||||
VIPS_FREEF( vips__quantise_result_destroy,
|
||||
quantisation_result );
|
||||
quantisation_result =
|
||||
cgif->local_quantisation_result;
|
||||
lp = pal_local;
|
||||
|
||||
use_local_palette = 1;
|
||||
|
||||
} else if(
|
||||
pal_change_global <= cgif->interpalette_maxerror
|
||||
) {
|
||||
/* Global palette change is low, use global
|
||||
* quantization result and palette
|
||||
*/
|
||||
VIPS_FREEF( vips__quantise_result_destroy,
|
||||
quantisation_result );
|
||||
quantisation_result = cgif->quantisation_result;
|
||||
lp = pal_global;
|
||||
|
||||
/* Also drop saved local result as it's usage
|
||||
* doesn't make sense now and it's better to
|
||||
* use a new local result if neeeded
|
||||
*/
|
||||
VIPS_FREEF( vips__quantise_result_destroy,
|
||||
cgif->local_quantisation_result );
|
||||
cgif->local_quantisation_result = NULL;
|
||||
|
||||
} else {
|
||||
/* Palette change is high, use local
|
||||
* quantization result and palette
|
||||
*/
|
||||
VIPS_FREEF( vips__quantise_result_destroy,
|
||||
cgif->local_quantisation_result );
|
||||
cgif->local_quantisation_result =
|
||||
quantisation_result;
|
||||
|
||||
use_local_palette = 1;
|
||||
#ifdef DEBUG_PERCENT
|
||||
cgif->n_cmaps_generated += 1;
|
||||
cgif->lp = vips__quantise_get_palette(
|
||||
cgif->quantisation_result );
|
||||
printf( "frame %d, new %d item colourmap\n",
|
||||
page_index, cgif->lp->count );
|
||||
page_index, lp->count );
|
||||
#endif/*DEBUG_PERCENT*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dither frame.
|
||||
*/
|
||||
vips__quantise_set_dithering_level( cgif->quantisation_result,
|
||||
cgif->dither );
|
||||
|
||||
if( vips__quantise_write_remapped_image( cgif->quantisation_result,
|
||||
vips__quantise_set_dithering_level( quantisation_result, cgif->dither );
|
||||
if( vips__quantise_write_remapped_image( quantisation_result,
|
||||
cgif->input_image, cgif->index, n_pels ) ) {
|
||||
vips_error( class->nickname, "%s", _( "dither failed" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Call vips__quantise_get_palette() after
|
||||
* vips__quantise_write_remapped_image(), as palette is improved
|
||||
* during remapping.
|
||||
/* Call vips__quantise_get_palette() after vips__quantise_write_remapped_image(),
|
||||
* as palette is improved during remapping.
|
||||
*/
|
||||
cgif->lp = vips__quantise_get_palette( cgif->quantisation_result );
|
||||
lp = vips__quantise_get_palette( quantisation_result );
|
||||
|
||||
if (use_local_palette || !cgif->cgif_context ) {
|
||||
rgb = cgif->palette_rgb;
|
||||
g_assert( cgif->lp->count <= 256 );
|
||||
for( i = 0; i < cgif->lp->count; i++ ) {
|
||||
rgb[0] = cgif->lp->entries[i].r;
|
||||
rgb[1] = cgif->lp->entries[i].g;
|
||||
rgb[2] = cgif->lp->entries[i].b;
|
||||
g_assert( lp->count <= 256 );
|
||||
for( i = 0; i < lp->count; i++ ) {
|
||||
rgb[0] = lp->entries[i].r;
|
||||
rgb[1] = lp->entries[i].g;
|
||||
rgb[2] = lp->entries[i].b;
|
||||
|
||||
rgb += 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's a transparent pixel, it's always first.
|
||||
*/
|
||||
cgif->has_transparency = cgif->lp->entries[0].a == 0;
|
||||
cgif->has_transparency = lp->entries[0].a == 0;
|
||||
|
||||
/* Set up cgif on first use, so we can set the first cmap as the global
|
||||
* one.
|
||||
@ -344,7 +435,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
#endif/*HAVE_CGIF_ATTR_NO_LOOP*/
|
||||
cgif->cgif_config.width = frame_rect->width;
|
||||
cgif->cgif_config.height = frame_rect->height;
|
||||
cgif->cgif_config.numGlobalPaletteEntries = cgif->lp->count;
|
||||
cgif->cgif_config.numGlobalPaletteEntries = lp->count;
|
||||
#ifdef HAVE_CGIF_ATTR_NO_LOOP
|
||||
cgif->cgif_config.numLoops = cgif->loop > 1 ?
|
||||
cgif->loop - 1 : cgif->loop;
|
||||
@ -392,14 +483,14 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
uint8_t trans_index;
|
||||
double sq_maxerror;
|
||||
|
||||
trans_index = cgif->lp->count;
|
||||
trans_index = lp->count;
|
||||
if( cgif->has_transparency ) {
|
||||
trans_index = 0;
|
||||
frame_config.attrFlags &=
|
||||
~CGIF_FRAME_ATTR_HAS_ALPHA;
|
||||
}
|
||||
|
||||
sq_maxerror = cgif->maxerror * cgif->maxerror;
|
||||
sq_maxerror = cgif->interframe_maxerror * cgif->interframe_maxerror;
|
||||
|
||||
for( i = 0; i < n_pels; i++ ) {
|
||||
if( vips_foreign_save_cgif_pixels_are_equal(
|
||||
@ -435,10 +526,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
|
||||
/* Attach a local palette, if we need one.
|
||||
*/
|
||||
if( cgif->cgif_config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE ) {
|
||||
if( use_local_palette ) {
|
||||
frame_config.attrFlags |= CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
|
||||
frame_config.pLocalPalette = cgif->palette_rgb;
|
||||
frame_config.numLocalPaletteEntries = cgif->lp->count;
|
||||
frame_config.numLocalPaletteEntries = lp->count;
|
||||
}
|
||||
|
||||
cgif_addframe( cgif->cgif_context, &frame_config );
|
||||
@ -451,6 +542,7 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif )
|
||||
memcpy( cgif->frame_bytes_head, frame_bytes, 4 * n_pels );
|
||||
}
|
||||
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -560,7 +652,10 @@ vips_foreign_save_cgif_build( VipsObject *object )
|
||||
frame_rect.top = 0;
|
||||
frame_rect.width = cgif->in->Xsize;
|
||||
frame_rect.height = page_height;
|
||||
if( (guint64) frame_rect.width * frame_rect.height > 5000 * 5000 ) {
|
||||
|
||||
/* GIF has a limit of 64k per axis -- double-check this.
|
||||
*/
|
||||
if( frame_rect.width > 65535 || frame_rect.height > 65535 ) {
|
||||
vips_error( class->nickname, "%s", _( "frame too large" ) );
|
||||
return( -1 );
|
||||
}
|
||||
@ -614,7 +709,7 @@ vips_foreign_save_cgif_build( VipsObject *object )
|
||||
tmp_image = vips__quantise_image_create_rgba( cgif->attr,
|
||||
tmp_gct, gct_length + 1, 1, 0 );
|
||||
|
||||
if( vips__quantise_image_quantize( tmp_image,
|
||||
if( vips__quantise_image_quantize_fixed( tmp_image,
|
||||
cgif->attr, &cgif->quantisation_result ) ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "quantisation failed" ) );
|
||||
@ -691,11 +786,11 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class )
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, bitdepth ),
|
||||
1, 8, 8 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "maxerror", 13,
|
||||
_( "Maximum error" ),
|
||||
VIPS_ARG_DOUBLE( class, "interframe_maxerror", 13,
|
||||
_( "Maximum inter-frame error" ),
|
||||
_( "Maximum inter-frame error for transparency" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, maxerror ),
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, interframe_maxerror ),
|
||||
0, 32, 0.0 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "reoptimise", 14,
|
||||
@ -704,6 +799,13 @@ vips_foreign_save_cgif_class_init( VipsForeignSaveCgifClass *class )
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, reoptimise ),
|
||||
FALSE );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "interpalette_maxerror", 15,
|
||||
_( "Maximum inter-palette error" ),
|
||||
_( "Maximum inter-palette error for palette reusage" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveCgif, interpalette_maxerror ),
|
||||
0, 256, 64.0 );
|
||||
}
|
||||
|
||||
static void
|
||||
@ -712,8 +814,9 @@ vips_foreign_save_cgif_init( VipsForeignSaveCgif *gif )
|
||||
gif->dither = 1.0;
|
||||
gif->effort = 7;
|
||||
gif->bitdepth = 8;
|
||||
gif->maxerror = 0.0;
|
||||
gif->interframe_maxerror = 0.0;
|
||||
gif->reoptimise = FALSE;
|
||||
gif->interpalette_maxerror = 64.0;
|
||||
}
|
||||
|
||||
typedef struct _VipsForeignSaveCgifTarget {
|
||||
@ -894,8 +997,10 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer )
|
||||
* * @dither: %gdouble, quantisation dithering level
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||
* reusage
|
||||
*
|
||||
* Write to a file in GIF format.
|
||||
*
|
||||
@ -908,10 +1013,15 @@ vips_foreign_save_cgif_buffer_init( VipsForeignSaveCgifBuffer *buffer )
|
||||
* always reserved for transparency. For example, a bitdepth of
|
||||
* 4 will allow the output to contain up to 15 colours.
|
||||
*
|
||||
* Use @maxerror to set the threshold below which pixels are considered equal.
|
||||
* Use @interframe_maxerror to set the threshold below which pixels are
|
||||
* considered equal.
|
||||
* Pixels which don't change from frame to frame can be made transparent,
|
||||
* improving the compression rate. Default 0.
|
||||
*
|
||||
* If @reoptimise is TRUE, new palettes will be generated. Use
|
||||
* @interpalette_maxerror to set the threshold below which one of the previously
|
||||
* generated palettes will be reused.
|
||||
*
|
||||
* See also: vips_image_new_from_file().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
@ -941,8 +1051,10 @@ vips_gifsave( VipsImage *in, const char *filename, ... )
|
||||
* * @dither: %gdouble, quantisation dithering level
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||
* reusage
|
||||
*
|
||||
* As vips_gifsave(), but save to a memory buffer.
|
||||
*
|
||||
@ -993,8 +1105,10 @@ vips_gifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
|
||||
* * @dither: %gdouble, quantisation dithering level
|
||||
* * @effort: %gint, quantisation CPU effort
|
||||
* * @bitdepth: %gint, number of bits per pixel
|
||||
* * @maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @interframe_maxerror: %gdouble, maximum inter-frame error for transparency
|
||||
* * @reoptimise: %gboolean, reoptimise colour palettes
|
||||
* * @interpalette_maxerror: %gdouble, maximum inter-palette error for palette
|
||||
* reusage
|
||||
*
|
||||
* As vips_gifsave(), but save to a target.
|
||||
*
|
||||
|
@ -87,6 +87,57 @@ vips__quantise_image_quantize( VipsQuantiseImage *const input_image,
|
||||
return liq_image_quantize( input_image, options, result_output );
|
||||
}
|
||||
|
||||
VipsQuantiseError
|
||||
vips__quantise_image_quantize_fixed( VipsQuantiseImage *const input_image,
|
||||
VipsQuantiseAttr *const options, VipsQuantiseResult **result_output )
|
||||
{
|
||||
int i;
|
||||
liq_result *result;
|
||||
const liq_palette *palette;
|
||||
liq_error err;
|
||||
liq_image *fake_image;
|
||||
void *fake_image_pixels;
|
||||
|
||||
/* First, quantize the image and get its palette
|
||||
*/
|
||||
err = liq_image_quantize( input_image, options, &result );
|
||||
if( err != LIQ_OK )
|
||||
return err;
|
||||
|
||||
palette = liq_get_palette( result );
|
||||
|
||||
/* Now, we need a fake 1 pixel image that will be quantized on the
|
||||
* next step. Its pixel color doesn't matter since we'll add all the
|
||||
* colors frm the palette further.
|
||||
*/
|
||||
fake_image_pixels = malloc( 4 );
|
||||
fake_image = liq_image_create_rgba( options, fake_image_pixels, 1, 1, 0 );
|
||||
if( !fake_image ) {
|
||||
liq_result_destroy( result );
|
||||
free( fake_image_pixels );
|
||||
return LIQ_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/* Add all the colors from the palette as fixed colors to the fake
|
||||
* image. Since the fixed colors number is the same as required colors
|
||||
* number, no new colors will be added.
|
||||
*/
|
||||
for( i = 0; i < palette->count; i++ )
|
||||
liq_image_add_fixed_color( fake_image, palette->entries[i] );
|
||||
|
||||
liq_result_destroy( result );
|
||||
|
||||
/* Finally, quantize the fake image with fixed colors to get the result
|
||||
* which palette won't be changed during remapping
|
||||
*/
|
||||
err = liq_image_quantize( fake_image, options, result_output );
|
||||
|
||||
liq_image_destroy( fake_image );
|
||||
free( fake_image_pixels );
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
VipsQuantiseError
|
||||
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||
float dither_level )
|
||||
@ -174,6 +225,17 @@ vips__quantise_image_quantize( VipsQuantiseImage *const input_image,
|
||||
return 0;
|
||||
}
|
||||
|
||||
VipsQuantiseError
|
||||
vips__quantise_image_quantize_fixed( VipsQuantiseImage *const input_image,
|
||||
VipsQuantiseAttr *const options, VipsQuantiseResult **result_output )
|
||||
{
|
||||
/* Quantizr doesn't change the palette during remapping, so we don't
|
||||
* need a special implementation for this
|
||||
*/
|
||||
return vips__quantise_image_quantize( input_image, options,
|
||||
result_output );
|
||||
}
|
||||
|
||||
VipsQuantiseError
|
||||
vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||
float dither_level )
|
||||
|
@ -69,6 +69,8 @@ VipsQuantiseImage *vips__quantise_image_create_rgba( const VipsQuantiseAttr *att
|
||||
const void *bitmap, int width, int height, double gamma );
|
||||
VipsQuantiseError vips__quantise_image_quantize( VipsQuantiseImage *input_image,
|
||||
VipsQuantiseAttr *options, VipsQuantiseResult **result_output );
|
||||
VipsQuantiseError vips__quantise_image_quantize_fixed( VipsQuantiseImage *input_image,
|
||||
VipsQuantiseAttr *options, VipsQuantiseResult **result_output );
|
||||
VipsQuantiseError vips__quantise_set_dithering_level( VipsQuantiseResult *res,
|
||||
float dither_level );
|
||||
const VipsQuantisePalette *vips__quantise_get_palette( VipsQuantiseResult *result );
|
||||
|
Loading…
Reference in New Issue
Block a user