Skip to content

Lossless conversion for webp #7348

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion ext/gd/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ AC_DEFUN([PHP_GD_AVIF],[

AC_DEFUN([PHP_GD_WEBP],[
if test "$PHP_WEBP" != "no"; then
PKG_CHECK_MODULES([WEBP], [libwebp])
PKG_CHECK_MODULES([WEBP], [libwebp >= 0.2.0])
PHP_EVAL_LIBLINE($WEBP_LIBS, GD_SHARED_LIBADD)
PHP_EVAL_INCLINE($WEBP_CFLAGS)
AC_DEFINE(HAVE_LIBWEBP, 1, [ ])
Expand Down
3 changes: 3 additions & 0 deletions ext/gd/gd.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ PHP_MINIT_FUNCTION(gd)
REGISTER_LONG_CONSTANT("IMG_BMP", PHP_IMG_BMP, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMG_TGA", PHP_IMG_TGA, CONST_CS | CONST_PERSISTENT);

/* constant for webp encoding */
REGISTER_LONG_CONSTANT("IMG_WEBP_LOSSLESS", PHP_IMG_WEBP_LOSSLESS, CONST_CS | CONST_PERSISTENT);

Copy link
Member

Choose a reason for hiding this comment

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

What happens if ext/gd is compiled against a libgd which does not support lossless WebP encoding? Furthermore, there is currently no way to detect whether it is supported. So maybe:

Suggested change
/* constant for webp encoding */
REGISTER_LONG_CONSTANT("IMG_WEBP_LOSSLESS", PHP_IMG_WEBP_LOSSLESS, CONST_CS | CONST_PERSISTENT);
#ifdef gdWebpLossless
/* constant for webp encoding */
REGISTER_LONG_CONSTANT("IMG_WEBP_LOSSLESS", gdWebpLossless, CONST_CS | CONST_PERSISTENT);
#endif

(and ditch the definition of PHP_IMG_WEBP_LOSSLESS)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I get it. Going forward, libgd will require a version of libwebp that's recent enough to support lossless WebP encoding. But your concern is that someone might use an older version of libgd. That older version wouldn't support losselss WebP... but it also wouldn't have the new gdWebpLossless defined.

If I'm getting that right, I think this solution is pretty clever.

Copy link
Member

Choose a reason for hiding this comment

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

That older version wouldn't support losselss WebP... but it also wouldn't have the new gdWebpLossless defined.

Right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now I see precedent for this in the code:

#ifdef gdEffectMultiply
	REGISTER_LONG_CONSTANT("IMG_EFFECT_MULTIPLY", gdEffectMultiply, CONST_CS | CONST_PERSISTENT);
#endif

Copy link
Member

Choose a reason for hiding this comment

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

Interesting! gdEffectMultiply is defined as of libgd 2.1.1, but we're still supporting 2.1.0. Still, something that can be removed sometime in the future.

/* special colours for gd */
REGISTER_LONG_CONSTANT("IMG_COLOR_TILED", gdTiled, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMG_COLOR_STYLED", gdStyled, CONST_CS | CONST_PERSISTENT);
Expand Down
15 changes: 14 additions & 1 deletion ext/gd/libgd/gd.h
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,20 @@ void *gdImageWBMPPtr(gdImagePtr im, int *size, int fg);
void gdImageJpeg(gdImagePtr im, FILE *out, int quality);
void gdImageJpegCtx(gdImagePtr im, gdIOCtx *out, int quality);

void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quantization);
/**
* Group: WebP
*
* Constant: gdWebpLossless
*
* Lossless quality threshold. When image quality is greater than or equal to
* <gdWebpLossless>, the image will be written in the lossless WebP format.
*
* See also:
* - <gdImageWebpCtx>
*/
#define gdWebpLossless 101

void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality);

/* Best to free this memory with gdFree(), not free() */
void *gdImageJpegPtr(gdImagePtr im, int *size, int quality);
Expand Down
22 changes: 14 additions & 8 deletions ext/gd/libgd/gd_webp.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ gdImagePtr gdImageCreateFromWebpCtx (gdIOCtx * infile)
return im;
}

void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quantization)
void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality)
{
uint8_t *argb;
int x, y;
Expand All @@ -117,8 +117,8 @@ void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quantization)
return;
}

if (quantization == -1) {
quantization = 80;
if (quality == -1) {
quality = 80;
}

if (overflow2(gdImageSX(im), 4)) {
Expand Down Expand Up @@ -151,7 +151,13 @@ void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quantization)
*(p++) = a;
}
}
out_size = WebPEncodeRGBA(argb, gdImageSX(im), gdImageSY(im), gdImageSX(im) * 4, quantization, &out);

if (quality >= gdWebpLossless) {
out_size = WebPEncodeLosslessRGBA(argb, gdImageSX(im), gdImageSY(im), gdImageSX(im) * 4, &out);
} else {
out_size = WebPEncodeRGBA(argb, gdImageSX(im), gdImageSY(im), gdImageSX(im) * 4, quality, &out);
}

if (out_size == 0) {
zend_error(E_ERROR, "gd-webp encoding failed");
goto freeargb;
Expand All @@ -163,10 +169,10 @@ void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quantization)
gdFree(argb);
}

void gdImageWebpEx (gdImagePtr im, FILE * outFile, int quantization)
void gdImageWebpEx (gdImagePtr im, FILE * outFile, int quality)
{
gdIOCtx *out = gdNewFileCtx(outFile);
gdImageWebpCtx(im, out, quantization);
gdImageWebpCtx(im, out, quality);
out->gd_free(out);
}

Expand All @@ -188,11 +194,11 @@ void * gdImageWebpPtr (gdImagePtr im, int *size)
return rv;
}

void * gdImageWebpPtrEx (gdImagePtr im, int *size, int quantization)
void * gdImageWebpPtrEx (gdImagePtr im, int *size, int quality)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx(2048, NULL);
gdImageWebpCtx(im, out, quantization);
gdImageWebpCtx(im, out, quality);
rv = gdDPExtractData(out, size);
out->gd_free(out);
return rv;
Expand Down
3 changes: 3 additions & 0 deletions ext/gd/php_gd.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
#define PHP_IMG_TGA 128
#define PHP_IMG_AVIF 256

/* constant for webp encoding. Matches gdWebpLossless in gd.h */
#define PHP_IMG_WEBP_LOSSLESS 101

#ifdef PHP_WIN32
# ifdef PHP_GD_EXPORTS
# define PHP_GD_API __declspec(dllexport)
Expand Down
10 changes: 9 additions & 1 deletion ext/gd/tests/webp_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@ imagewebp($im1, $filename);

$im2 = imagecreatefromwebp($filename);
imagewebp($im2, $filename);
echo 'Is lossy conversion close enough? ';
var_dump(calc_image_dissimilarity($im1, $im2) < 10e5);

imagewebp($im1, $filename, IMG_WEBP_LOSSLESS);
$im_lossless = imagecreatefromwebp($filename);
echo 'Does lossless conversion work? ';
var_dump(calc_image_dissimilarity($im1, $im_lossless) == 0);

?>
--CLEAN--
<?php
@unlink(__DIR__ . '/webp_basic.webp');
?>
--EXPECT--
bool(true)
Is lossy conversion close enough? bool(true)
Does lossless conversion work? bool(true)