Skip to content

Commit

Permalink
Lossless conversion for webp
Browse files Browse the repository at this point in the history
Propagating lossless conversion from libgd to our bundled gd.
Changing "quantization" to "quality" as in libgd.
Adding test.

IMG_WEBP_LOSSLESS is only defined, if lossless WebP encoding is
supported by the libgd used.

Closes GH-7348.
  • Loading branch information
morsssss authored and cmb69 committed Aug 12, 2021
1 parent b80767e commit eb6c9eb
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 11 deletions.
5 changes: 5 additions & 0 deletions UPGRADING
Expand Up @@ -562,6 +562,11 @@ PHP 8.1 UPGRADE NOTES
9. Other Changes to Extensions
========================================

- GD:
imagewebp() can do lossless WebP encoding by passing IMG_WEBP_LOSSLESS as
quality. This constant is only defined, if a libgd is used which supports
lossless WebP encoding.

- MySQLi:
. The mysqli_stmt::next_result() and mysqli::fetch_all() methods are now
available when linking against libmysqlclient.
Expand Down
2 changes: 1 addition & 1 deletion ext/gd/config.m4
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
5 changes: 5 additions & 0 deletions ext/gd/gd.c
Expand Up @@ -378,6 +378,11 @@ 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);

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

/* 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
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
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
10 changes: 9 additions & 1 deletion ext/gd/tests/webp_basic.phpt
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)

0 comments on commit eb6c9eb

Please sign in to comment.