Skip to content

Support alternative color quantization algorithms #17270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/gd/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ if test "$PHP_GD" != "no"; then
libgd/gd_io.c
libgd/gd_jpeg.c
libgd/gd_matrix.c
libgd/gd_nnquant.c
libgd/gd_pixelate.c
libgd/gd_png.c
libgd/gd_rotate.c
Expand Down
7 changes: 6 additions & 1 deletion ext/gd/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ if (PHP_GD != "no") {
AC_DEFINE('HAVE_XPM', 1, "Define to 1 if you have the xpm library.");
AC_DEFINE('HAVE_GD_XPM', 1, "Define to 1 if gd extension has XPM support.");
}
if (CHECK_LIB("imagequant.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("libimagequant.h", "CFLAGS_GD", PHP_GD)
) {
AC_DEFINE('HAVE_LIBIMAGEQUANT', 1, "Define to 1 if you have the libimagequant library.");
}
if (PHP_LIBWEBP != "no") {
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
Expand Down Expand Up @@ -58,7 +63,7 @@ if (PHP_GD != "no") {
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_pixelate.c gd_rotate.c gd_color_match.c gd_webp.c gd_avif.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c gd_nnquant.c", "gd");
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Define to 1 if gd extension uses GD library bundled in PHP.");
AC_DEFINE('HAVE_GD_PNG', 1, "Define to 1 if gd extension has PNG support.");
AC_DEFINE('HAVE_GD_BMP', 1, "Define to 1 if gd extension has BMP support.");
Expand Down
62 changes: 62 additions & 0 deletions ext/gd/gd.c
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,68 @@ PHP_FUNCTION(imagecolormatch)
}
/* }}} */

PHP_FUNCTION(imagetruecolortopalettesetmethod)
{
zval *IM;
zend_long method;
zend_long speed = 0;
gdImagePtr im;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_OBJECT_OF_CLASS(IM, gd_image_ce)
Z_PARAM_LONG(method)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(speed)
ZEND_PARSE_PARAMETERS_END();

im = php_gd_libgdimageptr_from_zval_p(IM);

if (method < GD_QUANT_DEFAULT || method > GD_QUANT_LIQ) {
zend_argument_value_error(2, "must be one of the IMG_QUANT_* constants");
RETURN_THROWS();
}

if (speed < 0 || speed > 10) {
zend_argument_value_error(3, "must be between 0 and 10");
RETURN_THROWS();
}

if (gdImageTrueColorToPaletteSetMethod(im, method, speed)) {
RETURN_TRUE;
} else {
php_error_docref(NULL, E_WARNING, "Couldn't set quantization method");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question you know better gd than I do, seems to me the only failure case left is when the method is not available correct ? so always will be unavailable then. dunno if we should raise an exception and this call only returns true.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree, but we should add info about the available quantization methods first.

RETURN_FALSE;
}
}

PHP_FUNCTION(imagetruecolortopalettesetquality)
{
zval *IM;
zend_long min_quality;
zend_long max_quality;
gdImagePtr im;

ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_OBJECT_OF_CLASS(IM, gd_image_ce)
Z_PARAM_LONG(min_quality)
Z_PARAM_LONG(max_quality)
ZEND_PARSE_PARAMETERS_END();

im = php_gd_libgdimageptr_from_zval_p(IM);

if (min_quality < 1 || min_quality > 100) {
zend_argument_value_error(2, "must be between 1 and 100");
RETURN_THROWS();
}

if (max_quality < 1 || max_quality > 100) {
zend_argument_value_error(3, "must be between 1 and 100");
RETURN_THROWS();
}

gdImageTrueColorToPaletteSetQuality (im, min_quality, max_quality);
}

/* {{{ Set line thickness for drawing lines, ellipses, rectangles, polygons etc. */
PHP_FUNCTION(imagesetthickness)
{
Expand Down
28 changes: 28 additions & 0 deletions ext/gd/gd.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,30 @@
const IMG_EFFECT_MULTIPLY = UNKNOWN;
#endif

/**
* @var int
* @cvalue GD_QUANT_DEFAULT
*/
const IMG_QUANT_DEFAULT = UNKNOWN;

/**
* @var int
* @cvalue GD_QUANT_JQUANT
*/
const IMG_QUANT_JQUANT = UNKNOWN;

/**
* @var int
* @cvalue GD_QUANT_NEUQUANT
*/
const IMG_QUANT_NEUQUANT = UNKNOWN;

/**
* @var int
* @cvalue GD_QUANT_LIQ
*/
const IMG_QUANT_LIQ = UNKNOWN;

/**
* @var int
* @cvalue GD_CROP_DEFAULT
Expand Down Expand Up @@ -498,6 +522,10 @@ function imagepalettetotruecolor(GdImage $image): bool {}

function imagecolormatch(GdImage $image1, GdImage $image2): bool {}

function imagetruecolortopalettesetmethod(GdImage $image, int $method, int $speed = 0): bool {}

function imagetruecolortopalettesetquality(GdImage $image, int $min_quality, int $max_quality): void {}

function imagesetthickness(GdImage $image, int $thickness): bool {}

function imagefilledellipse(GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): bool {}
Expand Down
22 changes: 21 additions & 1 deletion ext/gd/gd_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions ext/gd/libgd/gd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,11 @@ gdImagePtr gdImageClone (gdImagePtr src) {
dst->res_x = src->res_x;
dst->res_y = src->res_y;

dst->paletteQuantizationMethod = src->paletteQuantizationMethod;
dst->paletteQuantizationSpeed = src->paletteQuantizationSpeed;
dst->paletteQuantizationMinQuality = src->paletteQuantizationMinQuality;
dst->paletteQuantizationMaxQuality = src->paletteQuantizationMaxQuality;

dst->interpolation_id = src->interpolation_id;
dst->interpolation = src->interpolation;

Expand Down
59 changes: 59 additions & 0 deletions ext/gd/libgd/gd.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,33 @@ int gdAlphaBlend(int dest, int src);
int gdLayerOverlay(int dst, int src);
int gdLayerMultiply(int dest, int src);

/**
* Group: Color Quantization
*
* Enum: gdPaletteQuantizationMethod
*
* Constants:
* GD_QUANT_DEFAULT - GD_QUANT_LIQ if libimagequant is available,
* GD_QUANT_JQUANT otherwise.
* GD_QUANT_JQUANT - libjpeg's old median cut. Fast, but only uses 16-bit
* color.
* GD_QUANT_NEUQUANT - NeuQuant - approximation using Kohonen neural network.
* GD_QUANT_LIQ - A combination of algorithms used in libimagequant
* aiming for the highest quality at cost of speed.
*
* Note that GD_QUANT_JQUANT does not retain the alpha channel, and
* GD_QUANT_NEUQUANT does not support dithering.
*
* See also:
* - <gdImageTrueColorToPaletteSetMethod>
*/
enum gdPaletteQuantizationMethod {
GD_QUANT_DEFAULT = 0,
GD_QUANT_JQUANT = 1,
GD_QUANT_NEUQUANT = 2,
GD_QUANT_LIQ = 3
};

/**
* Group: Transform
*
Expand Down Expand Up @@ -241,6 +268,18 @@ typedef struct gdImageStruct {
int cy2;
unsigned int res_x;
unsigned int res_y;

/* Selects quantization method, see gdImageTrueColorToPaletteSetMethod() and gdPaletteQuantizationMethod enum. */
int paletteQuantizationMethod;
/* speed/quality trade-off. 1 = best quality, 10 = best speed. 0 = method-specific default.
Applicable to GD_QUANT_LIQ and GD_QUANT_NEUQUANT. */
int paletteQuantizationSpeed;
/* Image will remain true-color if conversion to palette cannot achieve given quality.
Value from 1 to 100, 1 = ugly, 100 = perfect. Applicable to GD_QUANT_LIQ.*/
int paletteQuantizationMinQuality;
/* Image will use minimum number of palette colors needed to achieve given quality. Must be higher than paletteQuantizationMinQuality
Value from 1 to 100, 1 = ugly, 100 = perfect. Applicable to GD_QUANT_LIQ.*/
int paletteQuantizationMaxQuality;
gdInterpolationMethod interpolation_id;
interpolation_method interpolation;
} gdImage;
Expand Down Expand Up @@ -575,6 +614,24 @@ int gdImagePaletteToTrueColor(gdImagePtr src);
and im2 is the palette version */
int gdImageColorMatch(gdImagePtr im1, gdImagePtr im2);

/* Selects quantization method used for subsequent gdImageTrueColorToPalette calls.
See gdPaletteQuantizationMethod enum (e.g. GD_QUANT_NEUQUANT, GD_QUANT_LIQ).
Speed is from 1 (highest quality) to 10 (fastest).
Speed 0 selects method-specific default (recommended).

Returns FALSE if the given method is invalid or not available.
*/
int gdImageTrueColorToPaletteSetMethod (gdImagePtr im, int method, int speed);

/*
Chooses quality range that subsequent call to gdImageTrueColorToPalette will aim for.
Min and max quality is in range 1-100 (1 = ugly, 100 = perfect). Max must be higher than min.
If palette cannot represent image with at least min_quality, then image will remain true-color.
If palette can represent image with quality better than max_quality, then lower number of colors will be used.
This function has effect only when GD_QUANT_LIQ method has been selected and the source image is true-color.
*/
void gdImageTrueColorToPaletteSetQuality (gdImagePtr im, int min_quality, int max_quality);

/* Specifies a color index (if a palette image) or an
RGB color (if a truecolor image) which should be
considered 100% transparent. FOR TRUECOLOR IMAGES,
Expand Down Expand Up @@ -739,6 +796,8 @@ void gdImageAlphaBlending(gdImagePtr im, int alphaBlendingArg);
void gdImageAntialias(gdImagePtr im, int antialias);
void gdImageSaveAlpha(gdImagePtr im, int saveAlphaArg);

gdImagePtr gdImageNeuQuant(gdImagePtr im, const int max_color, int sample_factor);

enum gdPixelateMode {
GD_PIXELATE_UPPERLEFT,
GD_PIXELATE_AVERAGE
Expand Down
Loading
Loading