diff --git a/.github/actions/configure-x32/action.yml b/.github/actions/configure-x32/action.yml index a5c5df4f7971d..aa43ed2c1e7bb 100644 --- a/.github/actions/configure-x32/action.yml +++ b/.github/actions/configure-x32/action.yml @@ -12,7 +12,7 @@ runs: export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/lib/i386-linux-gnu/pkgconfig" ./buildconf --force - export CFLAGS="-m32 -msse2" + export CFLAGS="-m32 -msse2 -DGD_TEST_HELPERS" export CXXFLAGS="-m32 -msse2" export LDFLAGS=-L/usr/lib/i386-linux-gnu ./configure ${{ inputs.configurationParameters }} \ diff --git a/.github/scripts/windows/build_task.bat b/.github/scripts/windows/build_task.bat index fd9a956bd38fb..f499b50fb2141 100644 --- a/.github/scripts/windows/build_task.bat +++ b/.github/scripts/windows/build_task.bat @@ -32,7 +32,7 @@ if "%THREAD_SAFE%" equ "0" set ADD_CONF=%ADD_CONF% --disable-zts if "%INTRINSICS%" neq "" set ADD_CONF=%ADD_CONF% --enable-native-intrinsics=%INTRINSICS% if "%ASAN%" equ "1" set ADD_CONF=%ADD_CONF% --enable-sanitizer --enable-debug-pack -set CFLAGS=/W1 /WX /w14013 +set CFLAGS=/W1 /WX /w14013 /DGD_TEST_HELPERS cmd /c configure.bat ^ --enable-snapshot-build ^ diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 17bda3d65e2dc..f64fd465481cc 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -4326,6 +4326,73 @@ PHP_FUNCTION(imageresolution) } /* }}} */ +#ifdef GD_TEST_HELPERS +static PHP_FUNCTION(imagechangedpixels) +{ + zval *IM1, *IM2; + gdImagePtr im1, im2; + int i, j, result = 0; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(IM1, gd_image_ce) + Z_PARAM_OBJECT_OF_CLASS(IM2, gd_image_ce) + ZEND_PARSE_PARAMETERS_END(); + + im1 = php_gd_libgdimageptr_from_zval_p(IM1); + im2 = php_gd_libgdimageptr_from_zval_p(IM2); + + ZEND_ASSERT(gdImageTrueColor(im1) && gdImageTrueColor(im2)); + ZEND_ASSERT(gdImageSX(im1) == gdImageSX(im2) && gdImageSY(im1) == gdImageSY(im2)); + + for (j = gdImageSY(im1) - 1; j >= 0; --j) { + for (i = gdImageSX(im1) - 1; i >= 0; --i) { + if (gdImageTrueColorPixel(im1, i, j) != gdImageTrueColorPixel(im2, i, j)) { + result++; + } + } + } + + RETURN_LONG(result); +} + +static double calc_pixel_distance(gdImagePtr im1, gdImagePtr im2, int x, int y) +{ + int c1 = gdImageGetPixel(im1, x, y); + int c2 = gdImageGetPixel(im2, x, y); +# define SQR(a) ((a) * (a)) + return sqrt( + SQR(gdImageRed(im1, c1) - gdImageRed(im2, c2)) + + SQR(gdImageGreen(im1, c1) - gdImageGreen(im2, c2)) + + SQR(gdImageBlue(im1, c1) - gdImageBlue(im2, c2))); +# undef SQR +} + +static PHP_FUNCTION(calc_image_dissimilarity) +{ + zval *IM1, *IM2; + gdImagePtr im1, im2; + int i, j; + double result = 0; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(IM1, gd_image_ce) + Z_PARAM_OBJECT_OF_CLASS(IM2, gd_image_ce) + ZEND_PARSE_PARAMETERS_END(); + + im1 = php_gd_libgdimageptr_from_zval_p(IM1); + im2 = php_gd_libgdimageptr_from_zval_p(IM2); + + ZEND_ASSERT(gdImageSX(im1) == gdImageSX(im2) && gdImageSY(im1) == gdImageSY(im2)); + + for (j = gdImageSY(im2) - 1; j >= 0; --j) { + for (i = gdImageSX(im1) - 1; i >= 0; --i) { + result += calc_pixel_distance(im1, im2, i, j); + } + } + + RETURN_DOUBLE(result); +} +#endif /********************************************************* * diff --git a/ext/gd/gd.stub.php b/ext/gd/gd.stub.php index 347e43e728b87..e387b19f12f18 100644 --- a/ext/gd/gd.stub.php +++ b/ext/gd/gd.stub.php @@ -794,3 +794,9 @@ function imagesetinterpolation(GdImage $image, int $method = IMG_BILINEAR_FIXED) * @refcount 1 */ function imageresolution(GdImage $image, ?int $resolution_x = null, ?int $resolution_y = null): array|bool {} + +#ifdef GD_TEST_HELPERS +function imagechangedpixels(GdImage $im1, GdImage $im2): int {} + +function calc_image_dissimilarity(GdImage $im1, GdImage $im2): float {} +#endif diff --git a/ext/gd/gd_arginfo.h b/ext/gd/gd_arginfo.h index 02f57e52ba940..061ed64f43e1b 100644 --- a/ext/gd/gd_arginfo.h +++ b/ext/gd/gd_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0f8a22bff1d123313f37da400500e573baace837 */ + * Stub hash: d80b05c9e734f5690e5cf302dc190cef7d9ce702 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_gd_info, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -567,6 +567,18 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_imageresolution, 0, 1, MAY_BE_AR ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, resolution_y, IS_LONG, 1, "null") ZEND_END_ARG_INFO() +#if defined(GD_TEST_HELPERS) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_imagechangedpixels, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, im1, GdImage, 0) + ZEND_ARG_OBJ_INFO(0, im2, GdImage, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_calc_image_dissimilarity, 0, 2, IS_DOUBLE, 0) + ZEND_ARG_OBJ_INFO(0, im1, GdImage, 0) + ZEND_ARG_OBJ_INFO(0, im2, GdImage, 0) +ZEND_END_ARG_INFO() +#endif + ZEND_FUNCTION(gd_info); ZEND_FUNCTION(imageloadfont); ZEND_FUNCTION(imagesetstyle); @@ -701,6 +713,10 @@ ZEND_FUNCTION(imageaffinematrixconcat); ZEND_FUNCTION(imagegetinterpolation); ZEND_FUNCTION(imagesetinterpolation); ZEND_FUNCTION(imageresolution); +#if defined(GD_TEST_HELPERS) +ZEND_FUNCTION(imagechangedpixels); +ZEND_FUNCTION(calc_image_dissimilarity); +#endif static const zend_function_entry ext_functions[] = { ZEND_FE(gd_info, arginfo_gd_info) @@ -839,6 +855,10 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(imagegetinterpolation, arginfo_imagegetinterpolation) ZEND_FE(imagesetinterpolation, arginfo_imagesetinterpolation) ZEND_FE(imageresolution, arginfo_imageresolution) +#if defined(GD_TEST_HELPERS) + ZEND_FE(imagechangedpixels, arginfo_imagechangedpixels) + ZEND_FE(calc_image_dissimilarity, arginfo_calc_image_dissimilarity) +#endif ZEND_FE_END }; diff --git a/ext/gd/tests/func.inc b/ext/gd/tests/func.inc index 0f10aa7d83dee..cb62adf148578 100644 --- a/ext/gd/tests/func.inc +++ b/ext/gd/tests/func.inc @@ -59,6 +59,23 @@ function get_libxpm_version() return $version; } +if (!function_exists("imagechangedpixels")) { + function imagechangedpixels(GdImage $im1, GdImage $im2): int + { + $pixels_changed = 0; + for ($y = imagesy($im1) - 1; $y >= 0; --$y) { + for ($x = imagesx($im1) - 1; $x >= 0; --$x) { + $c1 = imagecolorat($im1, $x, $y); + $c2 = imagecolorat($im2, $x, $y); + if ($c1 != $c2) { + $pixels_changed++; + } + } + } + return $pixels_changed; + } +} + /** * Tests that an in-memory image equals a PNG file. * @@ -106,16 +123,7 @@ function test_image_equals_image(GdImage $expected, GdImage $actual, bool $save_ } return; } - $pixels_changed = 0; - for ($y = 0; $y < $exp_y; $y++) { - for ($x = 0; $x < $exp_x; $x ++) { - $exp_c = imagecolorat($expected, $x, $y); - $act_c = imagecolorat($actual, $x, $y); - if ($exp_c != $act_c) { - $pixels_changed++; - } - } - } + $pixels_changed = imagechangedpixels($expected, $actual); if (!$pixels_changed) { echo "The images are equal.\n"; } else { diff --git a/ext/gd/tests/similarity.inc b/ext/gd/tests/similarity.inc index cb0dba77f2064..41fc5629a5d89 100644 --- a/ext/gd/tests/similarity.inc +++ b/ext/gd/tests/similarity.inc @@ -23,42 +23,36 @@ function get_rgb($color, &$red, &$green, &$blue) $blue = $color & 0xFF; } -/** - * Calculates the euclidean distance of two RGB values. - * - * @param int $color1 - * @param int $color2 - * - * @return int - */ -function calc_pixel_distance($color1, $color2) -{ - get_rgb($color1, $red1, $green1, $blue1); - get_rgb($color2, $red2, $green2, $blue2); - return sqrt( - pow($red1 - $red2, 2) + pow($green1 - $green2, 2) + pow($blue1 - $blue2, 2) - ); -} +if (!function_exists("calc_image_dissimilarity")) { + /** + * Calculates the euclidean distance of two RGB values. + */ + function calc_pixel_distance(int $color1, int $color2): float + { + get_rgb($color1, $red1, $green1, $blue1); + get_rgb($color2, $red2, $green2, $blue2); + return sqrt( + pow($red1 - $red2, 2) + pow($green1 - $green2, 2) + pow($blue1 - $blue2, 2) + ); + } -/** - * Calculates dissimilarity of two images. - * - * @param resource $image1 - * @param resource $image2 - * - * @return int The dissimilarity. 0 means the images are identical. The higher - * the value, the more dissimilar are the images. - */ -function calc_image_dissimilarity($image1, $image2) -{ - // assumes image1 and image2 have same width and height - $dissimilarity = 0; - for ($i = 0, $n = imagesx($image1); $i < $n; $i++) { - for ($j = 0, $m = imagesy($image1); $j < $m; $j++) { - $color1 = imagecolorat($image1, $i, $j); - $color2 = imagecolorat($image2, $i, $j); - $dissimilarity += calc_pixel_distance($color1, $color2); + /** + * Calculates dissimilarity of two images. + * + * 0 means the images are identical. The higher + * the value, the more dissimilar are the images. + */ + function calc_image_dissimilarity(GdImage $im1, GdImage $im2): float + { + // assumes image1 and image2 have same width and height + $dissimilarity = 0; + for ($j = imagesy($im1) - 1; $j >= 0; --$j) { + for ($i = imagesx($im1) - 1; $i >= 0; --$i) { + $color1 = imagecolorat($im1, $i, $j); + $color2 = imagecolorat($im2, $i, $j); + $dissimilarity += calc_pixel_distance($color1, $color2); + } } + return $dissimilarity; } - return $dissimilarity; }