From 886cf9e675f139a081bd2b55129fe282089e99f0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 27 Sep 2022 15:46:07 +0100 Subject: [PATCH 1/6] start hacking --- libvips/conversion/palette.c | 165 +++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 libvips/conversion/palette.c diff --git a/libvips/conversion/palette.c b/libvips/conversion/palette.c new file mode 100644 index 0000000000..c965c357bf --- /dev/null +++ b/libvips/conversion/palette.c @@ -0,0 +1,165 @@ +/* find a palette + * + * 25/9/22 + * - from palette.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include +#include + +#include "pconversion.h" + +typedef struct _VipsPalette { + VipsConversion parent_instance; + + VipsImage *in; + int bitdepth; +} VipsPalette; + +typedef VipsConversionClass VipsPaletteClass; + +G_DEFINE_TYPE( VipsPalette, vips_palette, VIPS_TYPE_CONVERSION ); + +static int +vips_palette_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsConversion *conversion = VIPS_CONVERSION( object ); + VipsPalette *palette = (VipsPalette *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 ); + + VipsQuantiseResult *result; + VipsQuantiseAttr *attr; + + if( VIPS_OBJECT_CLASS( vips_palette_parent_class )-> + build( object ) ) + return( -1 ); + + /* We only work for 8-bit images. + */ + if( vips_check_uncoded( class->nickname, conversion->in ) || + vips_check_format( class->nickname, + conversion->in, VIPS_FORMAT_UCHAR ) ) + return( -1 ); + + /* We need the whole thing in memory. + */ + if( vips_image_wio_input( conversion->in ) ) + return( -1 ); + + attr = vips__quantise_attr_create(); + vips__quantise_set_max_colors( cgif->attr, + VIPS_MIN( 255, 1 << cgif->bitdepth ) ); + vips__quantise_set_quality( cgif->attr, 0, 100 ); + vips__quantise_set_speed( cgif->attr, 11 - cgif->effort ); + + if( vips__quantise_image_quantize_fixed( conversion->in, cgif->attr, + &result ) ) { + vips_error( class->nickname, "%s", _( "quantisation failed" ) ); + return( -1 ); + } + + VIPS_FREEF( vips__quantise_result_destroy, result ); + VIPS_FREEF( vips__quantise_attr_destroy, attr ); + + return( 0 ); +} + +static void +vips_palette_class_init( VipsPaletteClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "palette"; + vobject_class->description = _( "compute image palette" ); + vobject_class->build = vips_palette_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "in", 0, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsPalette, in ) ); + + VIPS_ARG_INT( class, "bitdepth", 1, + _( "Bit depth" ), + _( "Compute an N bit palette" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsPalette, bitdepth ), + 0, 16, 8 ); + +} + +static void +vips_palette_init( VipsPalette *palette ) +{ +} + +/** + * vips_palette: (method) + * @in: input image + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Find the most common colours in an 8-bit image. + * + * Set @bitdepth to control the size of the computed palette. By default it + * finds an 8-bit palette, or 256 colours. + * + * See also: vips_dither(), vips_maplut(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_palette( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "palette", ap, in, out ); + va_end( ap ); + + return( result ); +} From b811381c75d86e3bda6b67ab6dcb3b2db258dc85 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Oct 2022 16:45:04 +0100 Subject: [PATCH 2/6] vips_palette works --- ChangeLog | 1 + libvips/conversion/conversion.c | 150 +++++++++++++++--------------- libvips/conversion/meson.build | 59 ++++++------ libvips/conversion/palette.c | 110 ++++++++++++++++++---- libvips/foreign/cgifsave.c | 4 +- libvips/include/vips/conversion.h | 2 + 6 files changed, 205 insertions(+), 121 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1a318b9d0e..621710e1bf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ master - threadpools size dynamically with load - operations can hint threadpool size - support for N-colour ICC profiles +- add vips_palette() to compute a palette for an RGBA image 11/10/22 started 8.13.3 - improve rules for 16-bit heifsave [johntrunc] diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 5886b617d1..08f64be9f3 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -359,101 +359,103 @@ vips_conversion_init( VipsConversion *conversion ) void vips_conversion_operation_init( void ) { - extern GType vips_copy_get_type( void ); - extern GType vips_tile_cache_get_type( void ); - extern GType vips_line_cache_get_type( void ); - extern GType vips_sequential_get_type( void ); + extern GType vips_arrayjoin_get_type( void ); + extern GType vips_autorot_get_type( void ); + extern GType vips_bandbool_get_type( void ); + extern GType vips_bandfold_get_type( void ); + extern GType vips_bandjoin_const_get_type( void ); + extern GType vips_bandjoin_get_type( void ); + extern GType vips_bandmean_get_type( void ); + extern GType vips_bandrank_get_type( void ); + extern GType vips_bandunfold_get_type( void ); + extern GType vips_black_get_type( void ); + extern GType vips_byteswap_get_type( void ); extern GType vips_cache_get_type( void ); + extern GType vips_cast_get_type( void ); + extern GType vips_composite2_get_type( void ); + extern GType vips_composite_get_type( void ); + extern GType vips_copy_get_type( void ); + extern GType vips_crop_get_type( void ); extern GType vips_embed_get_type( void ); - extern GType vips_gravity_get_type( void ); + extern GType vips_extract_area_get_type( void ); + extern GType vips_extract_band_get_type( void ); + extern GType vips_falsecolour_get_type( void ); + extern GType vips_flatten_get_type( void ); extern GType vips_flip_get_type( void ); + extern GType vips_gamma_get_type( void ); + extern GType vips_gaussnoise_get_type( void ); + extern GType vips_gravity_get_type( void ); + extern GType vips_grid_get_type( void ); + extern GType vips_ifthenelse_get_type( void ); extern GType vips_insert_get_type( void ); extern GType vips_join_get_type( void ); - extern GType vips_arrayjoin_get_type( void ); - extern GType vips_extract_area_get_type( void ); - extern GType vips_crop_get_type( void ); - extern GType vips_smartcrop_get_type( void ); - extern GType vips_extract_band_get_type( void ); + extern GType vips_line_cache_get_type( void ); + extern GType vips_msb_get_type( void ); + extern GType vips_palette_get_type( void ); + extern GType vips_premultiply_get_type( void ); + extern GType vips_recomb_get_type( void ); extern GType vips_replicate_get_type( void ); - extern GType vips_cast_get_type( void ); - extern GType vips_bandjoin_get_type( void ); - extern GType vips_bandjoin_const_get_type( void ); - extern GType vips_bandrank_get_type( void ); - extern GType vips_black_get_type( void ); - extern GType vips_rot_get_type( void ); extern GType vips_rot45_get_type( void ); - extern GType vips_autorot_get_type( void ); - extern GType vips_ifthenelse_get_type( void ); + extern GType vips_rot_get_type( void ); + extern GType vips_scale_get_type( void ); + extern GType vips_sequential_get_type( void ); + extern GType vips_smartcrop_get_type( void ); + extern GType vips_subsample_get_type( void ); extern GType vips_switch_get_type( void ); - extern GType vips_recomb_get_type( void ); - extern GType vips_bandmean_get_type( void ); - extern GType vips_bandfold_get_type( void ); - extern GType vips_bandunfold_get_type( void ); - extern GType vips_flatten_get_type( void ); - extern GType vips_premultiply_get_type( void ); - extern GType vips_unpremultiply_get_type( void ); - extern GType vips_bandbool_get_type( void ); - extern GType vips_gaussnoise_get_type( void ); - extern GType vips_grid_get_type( void ); + extern GType vips_tile_cache_get_type( void ); extern GType vips_transpose3d_get_type( void ); - extern GType vips_scale_get_type( void ); + extern GType vips_unpremultiply_get_type( void ); extern GType vips_wrap_get_type( void ); - extern GType vips_zoom_get_type( void ); - extern GType vips_subsample_get_type( void ); - extern GType vips_msb_get_type( void ); - extern GType vips_byteswap_get_type( void ); extern GType vips_xyz_get_type( void ); - extern GType vips_falsecolour_get_type( void ); - extern GType vips_gamma_get_type( void ); - extern GType vips_composite_get_type( void ); - extern GType vips_composite2_get_type( void ); + extern GType vips_zoom_get_type( void ); - vips_copy_get_type(); - vips_tile_cache_get_type(); - vips_line_cache_get_type(); - vips_sequential_get_type(); + vips_arrayjoin_get_type(); + vips_autorot_get_type(); + vips_bandbool_get_type(); + vips_bandfold_get_type(); + vips_bandjoin_const_get_type(); + vips_bandjoin_get_type(); + vips_bandmean_get_type(); + vips_bandrank_get_type(); + vips_bandunfold_get_type(); + vips_black_get_type(); + vips_byteswap_get_type(); vips_cache_get_type(); + vips_cast_get_type(); + vips_composite2_get_type(); + vips_composite_get_type(); + vips_copy_get_type(); + vips_crop_get_type(); vips_embed_get_type(); - vips_gravity_get_type(); + vips_extract_area_get_type(); + vips_extract_band_get_type(); + vips_falsecolour_get_type(); + vips_flatten_get_type(); vips_flip_get_type(); + vips_gamma_get_type(); + vips_gaussnoise_get_type(); + vips_gravity_get_type(); + vips_grid_get_type(); + vips_ifthenelse_get_type(); vips_insert_get_type(); vips_join_get_type(); - vips_arrayjoin_get_type(); - vips_extract_area_get_type(); - vips_crop_get_type(); - vips_smartcrop_get_type(); - vips_extract_band_get_type(); + vips_line_cache_get_type(); + vips_msb_get_type(); + vips_palette_get_type(); + vips_premultiply_get_type(); + vips_recomb_get_type(); vips_replicate_get_type(); - vips_cast_get_type(); - vips_bandjoin_get_type(); - vips_bandjoin_const_get_type(); - vips_bandrank_get_type(); - vips_black_get_type(); - vips_rot_get_type(); vips_rot45_get_type(); - vips_autorot_get_type(); - vips_ifthenelse_get_type(); + vips_rot_get_type(); + vips_scale_get_type(); + vips_sequential_get_type(); + vips_smartcrop_get_type(); + vips_subsample_get_type(); vips_switch_get_type(); - vips_recomb_get_type(); - vips_bandmean_get_type(); - vips_bandfold_get_type(); - vips_bandunfold_get_type(); - vips_flatten_get_type(); - vips_premultiply_get_type(); - vips_unpremultiply_get_type(); - vips_bandbool_get_type(); - vips_gaussnoise_get_type(); - vips_grid_get_type(); + vips_tile_cache_get_type(); vips_transpose3d_get_type(); - vips_scale_get_type(); + vips_unpremultiply_get_type(); vips_wrap_get_type(); - vips_zoom_get_type(); - vips_subsample_get_type(); - vips_msb_get_type(); - vips_byteswap_get_type(); vips_xyz_get_type(); - vips_falsecolour_get_type(); - vips_gamma_get_type(); - vips_composite_get_type(); - vips_composite2_get_type(); + vips_zoom_get_type(); } diff --git a/libvips/conversion/meson.build b/libvips/conversion/meson.build index 361b0711e9..aa92cf2080 100644 --- a/libvips/conversion/meson.build +++ b/libvips/conversion/meson.build @@ -1,44 +1,45 @@ conversion_sources = files( - 'switch.c', - 'transpose3d.c', - 'composite.cpp', - 'smartcrop.c', - 'conversion.c', - 'tilecache.c', - 'gamma.c', - 'sequential.c', - 'flatten.c', - 'premultiply.c', - 'unpremultiply.c', + 'arrayjoin.c', + 'autorot.c', + 'bandary.c', + 'bandbool.c', + 'bandfold.c', + 'bandjoin.c', + 'bandmean.c', + 'bandrank.c', + 'bandunfold.c', 'byteswap.c', 'cache.c', + 'cast.c', + 'composite.cpp', + 'conversion.c', 'copy.c', 'embed.c', + 'extract.c', + 'falsecolour.c', + 'flatten.c', 'flip.c', + 'gamma.c', + 'grid.c', + 'ifthenelse.c', 'insert.c', 'join.c', - 'arrayjoin.c', - 'extract.c', - 'replicate.c', - 'cast.c', - 'bandjoin.c', - 'bandrank.c', + 'msb.c', + 'palette.c', + 'premultiply.c', 'recomb.c', - 'bandmean.c', - 'bandfold.c', - 'bandunfold.c', - 'bandbool.c', - 'bandary.c', - 'rot.c', + 'replicate.c', 'rot45.c', - 'autorot.c', - 'ifthenelse.c', - 'falsecolour.c', - 'msb.c', - 'grid.c', + 'rot.c', 'scale.c', - 'wrap.c', + 'sequential.c', + 'smartcrop.c', 'subsample.c', + 'switch.c', + 'tilecache.c', + 'transpose3d.c', + 'unpremultiply.c', + 'wrap.c', 'zoom.c', ) diff --git a/libvips/conversion/palette.c b/libvips/conversion/palette.c index c965c357bf..6cd017d8a0 100644 --- a/libvips/conversion/palette.c +++ b/libvips/conversion/palette.c @@ -1,7 +1,7 @@ /* find a palette * * 25/9/22 - * - from palette.c + * - from cgifsave.c */ /* @@ -43,11 +43,13 @@ #include #include "pconversion.h" +#include "../foreign/quantise.h" typedef struct _VipsPalette { VipsConversion parent_instance; VipsImage *in; + int effort; int bitdepth; } VipsPalette; @@ -55,6 +57,36 @@ typedef VipsConversionClass VipsPaletteClass; G_DEFINE_TYPE( VipsPalette, vips_palette, VIPS_TYPE_CONVERSION ); +static int +vips_palette_write( VipsPalette *palette, VipsImage *out, + const VipsQuantisePalette *lp ) +{ + VipsPel line[256 * 4]; + VipsPel *rgba; + + g_assert( lp->count <= 256 ); + + rgba = line; + for( int i = 0; i < lp->count; i++ ) { + rgba[0] = lp->entries[i].r; + rgba[1] = lp->entries[i].g; + rgba[2] = lp->entries[i].b; + rgba[3] = lp->entries[i].a; + + rgba += 4; + } + + vips_image_init_fields( out, + lp->count, 1, 4, VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, + 1.0, 1.0 ); + + if( vips_image_write_line( out, 0, line ) ) + return( -1 ); + + return( 0 ); +} + static int vips_palette_build( VipsObject *object ) { @@ -63,40 +95,69 @@ vips_palette_build( VipsObject *object ) VipsPalette *palette = (VipsPalette *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 ); + VipsImage *in; VipsQuantiseResult *result; VipsQuantiseAttr *attr; + VipsQuantiseImage *image; if( VIPS_OBJECT_CLASS( vips_palette_parent_class )-> build( object ) ) return( -1 ); - /* We only work for 8-bit images. + /* We only work for 8-bit RGBA images. */ - if( vips_check_uncoded( class->nickname, conversion->in ) || - vips_check_format( class->nickname, - conversion->in, VIPS_FORMAT_UCHAR ) ) + in = palette->in; + if( vips_check_uncoded( class->nickname, in ) || + vips_check_format( class->nickname, in, VIPS_FORMAT_UCHAR ) || + vips_check_bands_atleast( class->nickname, in, 3 ) ) return( -1 ); - /* We need the whole thing in memory. - */ - if( vips_image_wio_input( conversion->in ) ) + /* To RGBA. + */ + if( in->Bands == 3 ) { + if( vips_addalpha( in, &t[0], NULL ) ) + return( -1 ); + in = t[0]; + } + else if( in->Bands > 4 ) { + if( vips_extract_band( in, &t[0], 0, "n", 4, NULL ) ) + return( -1 ); + in = t[0]; + } + + /* We need the whole thing in memory. + */ + if( vips_image_wio_input( in ) ) return( -1 ); attr = vips__quantise_attr_create(); - vips__quantise_set_max_colors( cgif->attr, - VIPS_MIN( 255, 1 << cgif->bitdepth ) ); - vips__quantise_set_quality( cgif->attr, 0, 100 ); - vips__quantise_set_speed( cgif->attr, 11 - cgif->effort ); - - if( vips__quantise_image_quantize_fixed( conversion->in, cgif->attr, - &result ) ) { + vips__quantise_set_max_colors( attr, + VIPS_MIN( 255, 1 << palette->bitdepth ) ); + vips__quantise_set_quality( attr, 0, 100 ); + vips__quantise_set_speed( attr, 11 - palette->effort ); + + image = vips__quantise_image_create_rgba( attr, + VIPS_IMAGE_ADDR( in, 0, 0 ), in->Xsize, in->Ysize, 0.0 ); + + result = NULL; + if( vips__quantise_image_quantize_fixed( image, attr, &result ) ) { + VIPS_FREEF( vips__quantise_image_destroy, image ); + VIPS_FREEF( vips__quantise_attr_destroy, attr ); vips_error( class->nickname, "%s", _( "quantisation failed" ) ); return( -1 ); } - VIPS_FREEF( vips__quantise_result_destroy, result ); + if( vips_palette_write( palette, conversion->out, + vips__quantise_get_palette( result ) ) ) { + VIPS_FREEF( vips__quantise_result_destroy, result ); + return( -1 ); + } + + VIPS_FREEF( vips__quantise_image_destroy, image ); VIPS_FREEF( vips__quantise_attr_destroy, attr ); + VIPS_FREEF( vips__quantise_result_destroy, result ); + return( 0 ); } @@ -122,6 +183,13 @@ vips_palette_class_init( VipsPaletteClass *class ) VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsPalette, in ) ); + VIPS_ARG_INT( class, "effort", 11, + _( "Effort" ), + _( "Quantisation effort" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsPalette, effort ), + 1, 10, 7 ); + VIPS_ARG_INT( class, "bitdepth", 1, _( "Bit depth" ), _( "Compute an N bit palette" ), @@ -142,11 +210,19 @@ vips_palette_init( VipsPalette *palette ) * @out: (out): output image * @...: %NULL-terminated list of optional named arguments * - * Find the most common colours in an 8-bit image. + * Optional arguments: + * + * * @effort: %gint, how much effort to spend on the search + * * @bitdepth: %gint, bitdepth for generated palette + * + * Find the most common colours in an 8-bit RGB or RGBA image. * * Set @bitdepth to control the size of the computed palette. By default it * finds an 8-bit palette, or 256 colours. * + * Set @effort to control the CPU effort (1 is the fastest, + * 10 is the slowest, 7 is the default). + * * See also: vips_dither(), vips_maplut(). * * Returns: 0 on success, -1 on error diff --git a/libvips/foreign/cgifsave.c b/libvips/foreign/cgifsave.c index 1b7fbd838a..b03890d797 100644 --- a/libvips/foreign/cgifsave.c +++ b/libvips/foreign/cgifsave.c @@ -484,8 +484,10 @@ vips_foreign_save_cgif_write_frame( VipsForeignSaveCgif *cgif ) /* Local mode. Pick the global, this or previous palette. */ if( vips_foreign_save_cgif_pick_quantiser( cgif, - image, &quantisation_result, &use_local ) ) + image, &quantisation_result, &use_local ) ) { + VIPS_FREEF( vips__quantise_image_destroy, image ); return( -1 ); + } } lp = vips__quantise_get_palette( quantisation_result ); diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index b3dd66aae6..db67572470 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -336,6 +336,8 @@ VIPS_API int vips_unpremultiply( VipsImage *in, VipsImage **out, ... ) G_GNUC_NULL_TERMINATED; VIPS_API +int vips_palette( VipsImage *in, VipsImage **out, ... ); +VIPS_API int vips_composite( VipsImage **in, VipsImage **out, int n, int *mode, ... ) G_GNUC_NULL_TERMINATED; VIPS_API From 34e34ab8c6eb0ce7521e38d8043410ce9ddf3bcf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 30 Oct 2022 14:03:09 +0000 Subject: [PATCH 3/6] add vips_dither compiles! --- libvips/conversion/conversion.c | 2 + libvips/conversion/dither.c | 281 ++++++++++++++++++++++++++++++++ libvips/conversion/meson.build | 1 + 3 files changed, 284 insertions(+) create mode 100644 libvips/conversion/dither.c diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 08f64be9f3..a6369ebca4 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -376,6 +376,7 @@ vips_conversion_operation_init( void ) extern GType vips_composite_get_type( void ); extern GType vips_copy_get_type( void ); extern GType vips_crop_get_type( void ); + extern GType vips_dither_get_type( void ); extern GType vips_embed_get_type( void ); extern GType vips_extract_area_get_type( void ); extern GType vips_extract_band_get_type( void ); @@ -426,6 +427,7 @@ vips_conversion_operation_init( void ) vips_composite_get_type(); vips_copy_get_type(); vips_crop_get_type(); + vips_dither_get_type(); vips_embed_get_type(); vips_extract_area_get_type(); vips_extract_band_get_type(); diff --git a/libvips/conversion/dither.c b/libvips/conversion/dither.c new file mode 100644 index 0000000000..3b68057642 --- /dev/null +++ b/libvips/conversion/dither.c @@ -0,0 +1,281 @@ +/* find a dither + * + * 25/9/22 + * - from cgifsave.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include +#include + +#include "pconversion.h" +#include "../foreign/quantise.h" + +typedef struct _VipsDither { + VipsConversion parent_instance; + + VipsImage *in; + VipsImage *palette; + double dither; + + VipsQuantiseAttr *attr; + VipsQuantiseImage *image; + VipsQuantiseResult *result; + +} VipsDither; + +typedef VipsConversionClass VipsDitherClass; + +G_DEFINE_TYPE( VipsDither, vips_dither, VIPS_TYPE_CONVERSION ); + +static void +vips_dither_dispose( GObject *gobject ) +{ + VipsDither *dither = (VipsDither *) gobject; + + VIPS_FREEF( vips__quantise_result_destroy, dither->result ); + VIPS_FREEF( vips__quantise_image_destroy, dither->image ); + VIPS_FREEF( vips__quantise_attr_destroy, dither->attr ); + + G_OBJECT_CLASS( vips_dither_parent_class )->dispose( gobject ); +} + +static int +vips_dither_to_rgba( VipsDither *dither, VipsImage *in, VipsImage **out ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( dither ); + VipsImage **t = (VipsImage **) + vips_object_local_array( VIPS_OBJECT( dither ), 1 ); + + VipsImage *rgba; + + rgba = in; + g_object_ref( rgba ); + if( vips_check_uncoded( class->nickname, rgba ) || + vips_check_format( class->nickname, rgba, VIPS_FORMAT_UCHAR ) || + vips_check_bands_atleast( class->nickname, rgba, 3 ) ) { + VIPS_UNREF( rgba ); + return( -1 ); + } + + if( rgba->Bands == 3 ) { + if( vips_addalpha( rgba, &t[0], NULL ) ) { + VIPS_UNREF( rgba ); + return( -1 ); + } + rgba = t[0]; + } + else if( rgba->Bands > 4 ) { + if( vips_extract_band( rgba, &t[0], 0, "n", 4, NULL ) ) { + VIPS_UNREF( rgba ); + return( -1 ); + } + rgba = t[0]; + } + + *out = rgba; + + return( 0 ); +} + +static int +vips_dither_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsConversion *conversion = VIPS_CONVERSION( object ); + VipsDither *dither = (VipsDither *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 ); + + VipsImage *in; + VipsImage *palette; + guint32 fake_image[257]; + int n_colours; + + if( VIPS_OBJECT_CLASS( vips_dither_parent_class )->build( object ) ) + return( -1 ); + + in = dither->in; + palette = dither->palette; + + /* The palette can't have more than 256 entries. + */ + if( palette->Xsize != 1 && + palette->Ysize != 1 ) { + vips_error( class->nickname, "%s", + _( "palettes must have width or height 1" ) ); + return( -1 ); + } + if( VIPS_IMAGE_N_PELS( palette ) > 256 ) { + vips_error( class->nickname, "%s", + _( "palettes must have not have more than " + "256 elements" ) ); + return( -1 ); + } + n_colours = palette->Xsize * palette->Ysize; + + /* We only work for 8-bit RGBA images. + */ + if( vips_dither_to_rgba( dither, in, &t[0] ) || + vips_dither_to_rgba( dither, palette, &t[1] ) ) + return( -1 ); + in = t[0]; + palette = t[1]; + + /* We need the whole thing in memory. + */ + if( vips_image_wio_input( in ) || + vips_image_wio_input( palette ) ) + return( -1 ); + + dither->attr = vips__quantise_attr_create(); + vips__quantise_set_max_colors( dither->attr, n_colours ); + vips__quantise_set_quality( dither->attr, 0, 100 ); + + /* Make a fake image from the input palette and quantise that to get + * the context we use for dithering. + */ + memcpy( fake_image, VIPS_IMAGE_ADDR( palette, 0, 0 ), + n_colours * sizeof( int ) ); + dither->image = vips__quantise_image_create_rgba( dither->attr, + fake_image, n_colours, 1, 0.0 ); + if( vips__quantise_image_quantize_fixed( dither->image, + dither->attr, &dither->result ) ) { + vips_error( class->nickname, + "%s", _( "quantisation failed" ) ); + return( -1 ); + } + VIPS_FREEF( vips__quantise_image_destroy, dither->image ); + + /* The frame index buffer. + */ + vips_image_init_fields( conversion->out, + in->Xsize, in->Ysize, 1, + VIPS_FORMAT_UCHAR, + VIPS_CODING_NONE, VIPS_INTERPRETATION_B_W, 1.0, 1.0 ); + if( vips_image_write_prepare( conversion->out ) ) + return( -1 ); + dither->image = vips__quantise_image_create_rgba( dither->attr, + VIPS_IMAGE_ADDR( conversion->out, 0, 0 ), + conversion->out->Xsize, conversion->out->Ysize, 0.0 ); + + /* Now dither! + */ + vips__quantise_set_dithering_level( dither->result, dither->dither ); + if( vips__quantise_write_remapped_image( dither->result, dither->image, + VIPS_IMAGE_ADDR( conversion->out, 0, 0 ), + VIPS_IMAGE_N_PELS( conversion->out ) ) ) { + vips_error( class->nickname, "%s", _( "dither failed" ) ); + return( -1 ); + } + + return( 0 ); +} + +static void +vips_dither_class_init( VipsDitherClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->dispose = vips_dither_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "dither"; + vobject_class->description = _( "dither image into palette" ); + vobject_class->build = vips_dither_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "in", 0, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsDither, in ) ); + + VIPS_ARG_IMAGE( class, "palette", 0, + _( "Palette" ), + _( "Palette image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsDither, palette ) ); + + VIPS_ARG_DOUBLE( class, "dither", 10, + _( "Dithering" ), + _( "Amount of dithering" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsDither, dither ), + 0.0, 1.0, 1.0 ); + +} + +static void +vips_dither_init( VipsDither *dither ) +{ +} + +/** + * vips_dither: (method) + * @in: input image + * @out: (out): output image + * @palette: (in): palette image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * * @dither: %gdouble, dithering level + * + * Dither @in using @palette. + * + * Use @dither to set the degree of Floyd-Steinberg dithering. + * + * See also: vips_palette(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_dither( VipsImage *in, VipsImage **out, VipsImage *palette, ... ) +{ + va_list ap; + int result; + + va_start( ap, palette ); + result = vips_call_split( "dither", ap, in, out, palette ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/conversion/meson.build b/libvips/conversion/meson.build index aa92cf2080..cac8cc534c 100644 --- a/libvips/conversion/meson.build +++ b/libvips/conversion/meson.build @@ -14,6 +14,7 @@ conversion_sources = files( 'composite.cpp', 'conversion.c', 'copy.c', + 'dither.c', 'embed.c', 'extract.c', 'falsecolour.c', From 2de1dcd2bceb58ff32d6b6d875ddf8eb9b49ab78 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 31 Oct 2022 11:17:09 +0000 Subject: [PATCH 4/6] renumber arg --- libvips/conversion/dither.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/conversion/dither.c b/libvips/conversion/dither.c index 3b68057642..d0a2117a09 100644 --- a/libvips/conversion/dither.c +++ b/libvips/conversion/dither.c @@ -228,7 +228,7 @@ vips_dither_class_init( VipsDitherClass *class ) VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsDither, in ) ); - VIPS_ARG_IMAGE( class, "palette", 0, + VIPS_ARG_IMAGE( class, "palette", 3, _( "Palette" ), _( "Palette image" ), VIPS_ARGUMENT_REQUIRED_INPUT, From b642f033bbd73a30f5fc01b7c3d0b282bc5828dd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 31 Oct 2022 15:52:39 +0000 Subject: [PATCH 5/6] almost works --- libvips/conversion/dither.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/libvips/conversion/dither.c b/libvips/conversion/dither.c index d0a2117a09..2ab8ad48ef 100644 --- a/libvips/conversion/dither.c +++ b/libvips/conversion/dither.c @@ -84,29 +84,24 @@ vips_dither_to_rgba( VipsDither *dither, VipsImage *in, VipsImage **out ) VipsImage *rgba; rgba = in; - g_object_ref( rgba ); + if( vips_check_uncoded( class->nickname, rgba ) || vips_check_format( class->nickname, rgba, VIPS_FORMAT_UCHAR ) || - vips_check_bands_atleast( class->nickname, rgba, 3 ) ) { - VIPS_UNREF( rgba ); + vips_check_bands_atleast( class->nickname, rgba, 3 ) ) return( -1 ); - } if( rgba->Bands == 3 ) { - if( vips_addalpha( rgba, &t[0], NULL ) ) { - VIPS_UNREF( rgba ); + if( vips_addalpha( rgba, &t[0], NULL ) ) return( -1 ); - } rgba = t[0]; } else if( rgba->Bands > 4 ) { - if( vips_extract_band( rgba, &t[0], 0, "n", 4, NULL ) ) { - VIPS_UNREF( rgba ); + if( vips_extract_band( rgba, &t[0], 0, "n", 4, NULL ) ) return( -1 ); - } rgba = t[0]; } + g_object_ref( rgba ); *out = rgba; return( 0 ); @@ -189,8 +184,7 @@ vips_dither_build( VipsObject *object ) if( vips_image_write_prepare( conversion->out ) ) return( -1 ); dither->image = vips__quantise_image_create_rgba( dither->attr, - VIPS_IMAGE_ADDR( conversion->out, 0, 0 ), - conversion->out->Xsize, conversion->out->Ysize, 0.0 ); + VIPS_IMAGE_ADDR( in, 0, 0 ), in->Xsize, in->Ysize, 0.0 ); /* Now dither! */ From 966bef7bb3347590e276c14c4688ee2895fd3b05 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 31 Oct 2022 17:09:53 +0000 Subject: [PATCH 6/6] a little dbg output --- libvips/conversion/dither.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libvips/conversion/dither.c b/libvips/conversion/dither.c index 2ab8ad48ef..750575cf90 100644 --- a/libvips/conversion/dither.c +++ b/libvips/conversion/dither.c @@ -175,6 +175,19 @@ vips_dither_build( VipsObject *object ) } VIPS_FREEF( vips__quantise_image_destroy, dither->image ); +{ + const VipsQuantisePalette *lp = + vips__quantise_get_palette( dither->result ); + + for( int i = 0; i < lp->count; i++ ) + printf( "%d) r = %d, g = %d, b = %d, a = %d\n", + i, + lp->entries[i].r, + lp->entries[i].g, + lp->entries[i].b, + lp->entries[i].a ); +} + /* The frame index buffer. */ vips_image_init_fields( conversion->out,