diff --git a/ext/gd/config.m4 b/ext/gd/config.m4 index a997238b7f75..3688547bb7e9 100644 --- a/ext/gd/config.m4 +++ b/ext/gd/config.m4 @@ -20,6 +20,13 @@ PHP_ARG_WITH([avif], [no], [no]) +PHP_ARG_WITH([uhdr], + [for libuhdr], + [AS_HELP_STRING([--with-uhdr], + [GD: Enable UltraHDR support (only for bundled libgd)])], + [no], + [no]) + PHP_ARG_WITH([webp], [for libwebp], [AS_HELP_STRING([--with-webp], @@ -55,6 +62,27 @@ PHP_ARG_ENABLE([gd-jis-conv], [no], [no]) +PHP_ARG_WITH([heif], + [for libheif], + [AS_HELP_STRING([--with-heif], + [GD: Enable HEIF support (only for bundled libgd)])], + [no], + [no]) + +PHP_ARG_WITH([imagequant], + [for libimagequant], + [AS_HELP_STRING([--with-imagequant], + [GD: Enable libimagequant support (only for bundled libgd)])], + [no], + [no]) + +PHP_ARG_WITH([tiff], + [for libtiff], + [AS_HELP_STRING([--with-tiff], + [GD: Enable TIFF support (only for bundled libgd)])], + [no], + [no]) + dnl dnl Checks for the configure options dnl @@ -80,9 +108,39 @@ AC_DEFUN([PHP_GD_AVIF], [ ]) ]) +AC_DEFUN([PHP_GD_HEIF], [ + AS_VAR_IF([PHP_HEIF], [no],, [ + PKG_CHECK_MODULES([HEIF], [libheif >= 1.7.0]) + PHP_EVAL_LIBLINE([$HEIF_LIBS], [GD_SHARED_LIBADD]) + PHP_EVAL_INCLINE([$HEIF_CFLAGS]) + AC_DEFINE([HAVE_LIBHEIF], [1], + [Define to 1 if you have the libheif library.]) + AC_DEFINE([HAVE_GD_HEIF], [1], + [Define to 1 if gd extension has HEIF support.]) + ]) +]) + +AC_DEFUN([PHP_GD_UHDR], [ + AS_VAR_IF([PHP_UHDR], [no],, [ + PKG_CHECK_MODULES([UHDR], [libuhdr >= 1.4.0]) + PHP_EVAL_LIBLINE([$UHDR_LIBS], [GD_SHARED_LIBADD]) + PHP_EVAL_INCLINE([$UHDR_CFLAGS]) + + PKG_CHECK_VAR([UHDR_WRITE_XMP], [libuhdr], [UHDR_WRITE_XMP]) + AS_VAR_IF([UHDR_WRITE_XMP], [], [], + [AC_MSG_ERROR([libuhdr was compiled with UHDR_WRITE_XMP enabled, but this extension requires it to be OFF.])]) + + AC_DEFINE([HAVE_LIBUHDR], [1], + [Define to 1 if you have the libuhdr library.]) + AC_DEFINE([HAVE_GD_UHDR], [1], + [Define to 1 if gd extension has UltraHDR support.]) + ]) +]) + AC_DEFUN([PHP_GD_WEBP],[ AS_VAR_IF([PHP_WEBP], [no],, [ - PKG_CHECK_MODULES([WEBP], [libwebp >= 0.2.0]) + PKG_CHECK_MODULES([WEBP], + [libwebp >= 0.2.0 libwebpdemux libwebpmux]) PHP_EVAL_LIBLINE([$WEBP_LIBS], [GD_SHARED_LIBADD]) PHP_EVAL_INCLINE([$WEBP_CFLAGS]) AC_DEFINE([HAVE_LIBWEBP], [1], @@ -110,6 +168,7 @@ AC_DEFUN([PHP_GD_XPM],[ PHP_EVAL_LIBLINE([$XPM_LIBS], [GD_SHARED_LIBADD]) PHP_EVAL_INCLINE([$XPM_CFLAGS]) AC_DEFINE([HAVE_XPM], [1], [Define to 1 if you have the xpm library.]) + AC_DEFINE([HAVE_LIBXPM], [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.]) ]) @@ -136,6 +195,35 @@ AC_DEFUN([PHP_GD_JISX0208],[ ]) ]) +AC_DEFUN([PHP_GD_IMAGEQUANT], [ + AS_VAR_IF([PHP_IMAGEQUANT], [no],, [ + AC_CHECK_HEADER([libimagequant.h], [], + [AC_MSG_ERROR([libimagequant header not found])]) + PHP_CHECK_LIBRARY([imagequant], [liq_attr_create], + [ + PHP_ADD_LIBRARY([imagequant], [], [GD_SHARED_LIBADD]) + AC_DEFINE([HAVE_LIBIMAGEQUANT], [1], + [Define to 1 if you have the libimagequant library.]) + AC_DEFINE([HAVE_GD_IMAGEQUANT], [1], + [Define to 1 if gd extension has libimagequant support.]) + ], + [AC_MSG_ERROR([libimagequant library not found])], + [-limagequant]) + ]) +]) + +AC_DEFUN([PHP_GD_TIFF], [ + AS_VAR_IF([PHP_TIFF], [no],, [ + PKG_CHECK_MODULES([TIFF], [libtiff-4]) + PHP_EVAL_LIBLINE([$TIFF_LIBS], [GD_SHARED_LIBADD]) + PHP_EVAL_INCLINE([$TIFF_CFLAGS]) + AC_DEFINE([HAVE_LIBTIFF], [1], + [Define to 1 if you have the libtiff library.]) + AC_DEFINE([HAVE_GD_TIFF], [1], + [Define to 1 if gd extension has TIFF support.]) + ]) +]) + dnl dnl PHP_GD_CHECK_FORMAT(format, [action-if-found]) dnl @@ -194,6 +282,7 @@ AS_VAR_POPDEF([php_var]) AC_DEFUN([PHP_GD_CHECK_VERSION],[ PHP_GD_CHECK_FORMAT([Png], [AC_DEFINE([HAVE_GD_PNG], [1])]) PHP_GD_CHECK_FORMAT([Avif], [AC_DEFINE([HAVE_GD_AVIF], [1])]) + PHP_GD_CHECK_FORMAT([Heif], [AC_DEFINE([HAVE_GD_HEIF], [1])]) PHP_GD_CHECK_FORMAT([Webp], [AC_DEFINE([HAVE_GD_WEBP], [1])]) PHP_GD_CHECK_FORMAT([Jpeg], [AC_DEFINE([HAVE_GD_JPG], [1])]) PHP_GD_CHECK_FORMAT([Xpm], [AC_DEFINE([HAVE_GD_XPM], [1])]) @@ -206,6 +295,11 @@ AC_DEFUN([PHP_GD_CHECK_VERSION],[ [Define to 1 if GD library has the 'gdVersionString' function.])], [], [$GD_SHARED_LIBADD]) + PHP_CHECK_LIBRARY([gd], [gdPngGetVersionString], + [AC_DEFINE([HAVE_GD_PNG_GET_VERSION_STRING], [1], + [Define to 1 if GD library has the 'gdPngGetVersionString' function.])], + [], + [$GD_SHARED_LIBADD]) PHP_CHECK_LIBRARY([gd], [gdImageGetInterpolationMethod], [AC_DEFINE([HAVE_GD_GET_INTERPOLATION], [1], [Define to 1 if GD library has the 'gdImageGetInterpolationMethod' @@ -237,6 +331,7 @@ if test "$PHP_GD" != "no"; then libgd/gd_io.c libgd/gd_jpeg.c libgd/gd_matrix.c + libgd/gd_metadata.c libgd/gd_png.c libgd/gd_rotate.c libgd/gd_security.c @@ -260,6 +355,32 @@ if test "$PHP_GD" != "no"; then libgd/gdtables.c libgd/gdxpm.c libgd/wbmp.c + libgd/gd_qoi.c + libgd/gd_jxl.c + libgd/gd_color_map.c + libgd/gd_heif.c + libgd/gd_uhdr.c + libgd/gd_nnquant.c + libgd/gd_color.c + libgd/gd_tiff.c + libgd/gd_readimage.c + libgd/gd_filename.c + libgd/ftraster/gd_ft_math.c + libgd/ftraster/gd_ft_raster.c + libgd/ftraster/gd_ft_stroker.c + libgd/gd_array.c + libgd/gd_span_rle.c + libgd/gd_surface.c + libgd/gd_version.c + libgd/gd_compositor.c + libgd/gd_gradient.c + libgd/gd_path.c + libgd/gd_path_arc.c + libgd/gd_path_dash.c + libgd/gd_path_matrix.c + libgd/gd_path_stroke.c + libgd/gd_draw.c + libgd/gd_draw_blend.c "]) AC_DEFINE([HAVE_GD_BUNDLED], [1], @@ -269,21 +390,29 @@ if test "$PHP_GD" != "no"; then [Define to 1 if GD library has the 'gdImageGetInterpolationMethod' function.]) + AC_DEFINE([HAVE_GD_PNG_GET_VERSION_STRING], [1], + [Define to 1 if GD library has the 'gdPngGetVersionString' function.]) + dnl Various checks for GD features PHP_SETUP_ZLIB([GD_SHARED_LIBADD]) PHP_GD_PNG PHP_GD_AVIF + PHP_GD_HEIF + PHP_GD_UHDR PHP_GD_WEBP PHP_GD_JPEG PHP_GD_XPM PHP_GD_FREETYPE2 PHP_GD_JISX0208 + PHP_GD_IMAGEQUANT + PHP_GD_TIFF PHP_NEW_EXTENSION([gd], [gd.c $extra_sources], [$ext_shared],, [-Wno-strict-prototypes -I@ext_srcdir@/libgd]) PHP_ADD_BUILD_DIR([$ext_builddir/libgd]) + PHP_ADD_BUILD_DIR([$ext_builddir/libgd/ftraster]) PHP_INSTALL_HEADERS([ext/gd], [php_gd.h libgd/]) diff --git a/ext/gd/config.w32 b/ext/gd/config.w32 index 506bb05cf2e0..c43b768def8e 100644 --- a/ext/gd/config.w32 +++ b/ext/gd/config.w32 @@ -3,6 +3,10 @@ ARG_WITH("gd", "Bundled GD support", "yes,shared"); ARG_WITH("libwebp", "webp support", "yes"); ARG_WITH("libavif", "avif support", "yes"); +ARG_WITH("heif", "HEIF support", "no"); +ARG_WITH("uhdr", "UltraHDR support", "yes"); +ARG_WITH("imagequant", "libimagequant support", "no"); +ARG_WITH("tiff", "TIFF support", "no"); if (PHP_GD != "no") { if ( @@ -25,12 +29,17 @@ if (PHP_GD != "no") { CHECK_HEADER("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11") ) { AC_DEFINE('HAVE_XPM', 1, "Define to 1 if you have the xpm library."); + AC_DEFINE('HAVE_LIBXPM', 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 (PHP_LIBWEBP != "no") { if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) && + (CHECK_LIB("libwebpdemux_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebpdemux.lib", "gd", PHP_GD)) && + (CHECK_LIB("libwebpmux_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebpmux.lib", "gd", PHP_GD)) && CHECK_HEADER("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") && - CHECK_HEADER("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) { + CHECK_HEADER("demux.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") && + CHECK_HEADER("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") && + CHECK_HEADER("mux.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) { AC_DEFINE("HAVE_LIBWEBP", 1, "Define to 1 if you have the libwebp library."); AC_DEFINE("HAVE_GD_WEBP", 1, "Define to 1 if gd extension has WebP support."); } else { @@ -49,6 +58,44 @@ if (PHP_GD != "no") { WARNING("libavif not enabled; libraries and headers not found"); } } + + if (PHP_HEIF != "no") { + if (CHECK_LIB("heif.lib", "gd", PHP_GD) && + CHECK_HEADER("heif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\heif")) { + ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBHEIF /D HAVE_GD_HEIF"); + } else { + WARNING("libheif not enabled; library and headers not found"); + } + } + + // requires UHDR_WRITE_XMP=Off, for the windows build team, when adding/updating libuhdr + if (PHP_UHDR != "no") { + if (CHECK_LIB("uhdr.lib", "gd", PHP_GD) && + CHECK_HEADER("ultrahdr_api.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) { + ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBUHDR /D HAVE_GD_UHDR"); + } else { + WARNING("libuhdr not enabled; library and headers not found"); + } + } + + if (PHP_IMAGEQUANT != "no") { + if (CHECK_LIB("imagequant.lib", "gd", PHP_GD) && + CHECK_HEADER("libimagequant.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) { + ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBIMAGEQUANT /D HAVE_GD_IMAGEQUANT"); + } else { + WARNING("libimagequant not enabled; library and headers not found"); + } + } + + if (PHP_TIFF != "no") { + if (CHECK_LIB("tiff.lib", "gd", PHP_GD) && + CHECK_HEADER("tiff.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) { + ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBTIFF /D HAVE_GD_TIFF"); + } else { + WARNING("libtiff not enabled; library and headers not found"); + } + } + CHECK_LIB("User32.lib", "gd", PHP_GD); CHECK_LIB("Gdi32.lib", "gd", PHP_GD); @@ -56,11 +103,17 @@ if (PHP_GD != "no") { ADD_SOURCES("ext/gd/libgd", "gd2copypal.c gd.c \ gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \ gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \ - gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \ + gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_metadata.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_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_qoi.c gd_jxl.c gd_color_map.c \ + gd_heif.c gd_uhdr.c gd_nnquant.c gd_color.c gd_readimage.c gd_filename.c \ + gd_array.c gd_span_rle.c \ + gd_surface.c gd_version.c gd_compositor.c gd_gradient.c gd_path.c gd_path_arc.c gd_path_dash.c \ + gd_path_matrix.c gd_path_stroke.c gd_draw.c gd_draw_blend.c", "gd"); + ADD_SOURCES("ext/gd/libgd/ftraster", "gd_ft_math.c gd_ft_raster.c gd_ft_stroker.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_GET_VERSION_STRING', 1, "Define to 1 if GD library has the 'gdPngGetVersionString' function."); AC_DEFINE('HAVE_GD_PNG', 1, "Define to 1 if gd extension has PNG support."); AC_DEFINE('HAVE_LIBPNG', 1, "Define to 1 if you have the libpng library."); AC_DEFINE('HAVE_LIBJPEG', 1, "Define to 1 if you have the libjpeg library."); diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 70a379dbe9be..bcc43f371028 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -299,6 +299,15 @@ PHP_INI_END() /* {{{ php_gd_error_method */ void php_gd_error_method(int type, const char *format, va_list args) { + /* Keep PHP's historical PNG warning text while bundled gd_png.c stays + * identical to upstream libgd. */ + if (strcmp(format, "gd-png: fatal libpng error: %s\n") == 0) { + format = "gd-png: fatal libpng error: %s"; + } else if (strncmp(format, "gd-png error: setjmp returns error condition", + sizeof("gd-png error: setjmp returns error condition") - 1) == 0) { + format = "gd-png error: setjmp returns error condition"; + } + switch (type) { #ifndef PHP_WIN32 case GD_DEBUG: @@ -358,7 +367,7 @@ PHP_RSHUTDOWN_FUNCTION(gd) /* }}} */ #ifdef HAVE_GD_BUNDLED -#define PHP_GD_VERSION_STRING "bundled (2.1.0 compatible)" +#define PHP_GD_VERSION_STRING "bundled (2.4.0 compatible)" #else # define PHP_GD_VERSION_STRING GD_VERSION_STRING #endif @@ -413,7 +422,7 @@ PHP_MINFO_FUNCTION(gd) #ifdef HAVE_GD_PNG php_info_print_table_row(2, "PNG Support", "enabled"); -#ifdef HAVE_GD_BUNDLED +#ifdef HAVE_GD_PNG_GET_VERSION_STRING php_info_print_table_row(2, "libPNG Version", gdPngGetVersionString()); #endif #endif @@ -723,6 +732,10 @@ PHP_FUNCTION(imagetruecolortopalette) RETURN_THROWS(); } + /* Preserve PHP's historical palette conversion behavior regardless of + * whether bundled libgd was built with libimagequant support. */ + gdImageTrueColorToPaletteSetMethod(im, GD_QUANT_JQUANT, 0); + if (gdImageTrueColorToPalette(im, dither, (int)ncolors)) { RETURN_TRUE; } else { @@ -1880,6 +1893,8 @@ PHP_FUNCTION(imagegif) gdImagePtr im; gdIOCtx *ctx; zval *to_zval = NULL; + int quantization_method; + int quantization_speed; if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|z!", &imgind, gd_image_ce, &to_zval) == FAILURE) { RETURN_THROWS(); @@ -1892,7 +1907,18 @@ PHP_FUNCTION(imagegif) RETURN_FALSE; } + quantization_method = im->paletteQuantizationMethod; + quantization_speed = im->paletteQuantizationSpeed; + if (im->trueColor) { + /* GIF conversion historically used JQUANT in PHP. Keep output stable + * when bundled libgd has a build-dependent default such as LIQ. */ + gdImageTrueColorToPaletteSetMethod(im, GD_QUANT_JQUANT, 0); + } gdImageGifCtx(im, ctx); + if (im->trueColor) { + gdImageTrueColorToPaletteSetMethod(im, quantization_method, + quantization_speed); + } ctx->gd_free(ctx); @@ -1928,7 +1954,28 @@ PHP_FUNCTION(imagepng) } #ifdef HAVE_GD_BUNDLED - gdImagePngCtxEx(im, ctx, (int) quality, (int) basefilter); + { + gdPngWriteOptions options; + unsigned int filters = GD_PNG_FILTER_AUTO; + unsigned int unknown_filters; + + gdPngWriteOptionsInit(&options); + options.compression_level = (int) quality; + if (basefilter >= 0) { + unsigned long php_filters = (unsigned long) basefilter; + if (php_filters & 0x08) filters |= GD_PNG_FILTER_NONE; + if (php_filters & 0x10) filters |= GD_PNG_FILTER_SUB; + if (php_filters & 0x20) filters |= GD_PNG_FILTER_UP; + if (php_filters & 0x40) filters |= GD_PNG_FILTER_AVERAGE; + if (php_filters & 0x80) filters |= GD_PNG_FILTER_PAETH; + unknown_filters = (unsigned int) (php_filters & ~0xf8UL); + if (unknown_filters != 0) { + filters |= 1U << 31; + } + } + options.filters = filters; + (void) gdImagePngCtxWithOptions(im, ctx, &options); + } #else gdImagePngCtxEx(im, ctx, (int) quality); #endif diff --git a/ext/gd/libgd/bmp.h b/ext/gd/libgd/bmp.h index cecde0383f53..01857a840f6d 100644 --- a/ext/gd/libgd/bmp.h +++ b/ext/gd/libgd/bmp.h @@ -1,4 +1,6 @@ -/* $Id$ */ +#ifndef BMP_H +#define BMP_H 1 + #ifdef __cplusplus extern "C" { #endif @@ -19,14 +21,14 @@ extern "C" { ---------------------------------------------------------------------------- */ -#ifndef BMP_H -#define BMP_H 1 - #define BMP_PALETTE_3 1 #define BMP_PALETTE_4 2 #define BMP_WINDOWS_V3 40 +#define BMP_WINDOWS_V2 52 +#define BMP_WINDOWS_V3_ALPHA 56 #define BMP_OS2_V1 12 +#define BMP_OS2_V2_SHORT 16 #define BMP_OS2_V2 64 #define BMP_WINDOWS_V4 108 #define BMP_WINDOWS_V5 124 @@ -37,6 +39,7 @@ extern "C" { #define BMP_BI_BITFIELDS 3 #define BMP_BI_JPEG 4 #define BMP_BI_PNG 5 +#define BMP_BI_ALPHABITFIELDS 6 #define BMP_RLE_COMMAND 0 #define BMP_RLE_ENDOFLINE 0 @@ -100,9 +103,16 @@ extern "C" { /* 32bit - The number of color indices used by the bitmap. */ signed int numcolors; - /* 32bit - The number of color indices important for displaying the bitmap. */ + /* 32bit - The number of color indices important for displaying the bitmap. + */ signed int mincolors; + /* 32bit - Color masks for BI_BITFIELDS and v4/v5 headers. */ + unsigned int red_mask; + unsigned int green_mask; + unsigned int blue_mask; + unsigned int alpha_mask; + } bmp_info_t; #endif @@ -110,3 +120,7 @@ extern "C" { #ifdef __cplusplus } #endif + +#ifdef __cplusplus +} +#endif diff --git a/ext/gd/libgd/entities.h b/ext/gd/libgd/entities.h new file mode 100644 index 000000000000..7d5cd5208379 --- /dev/null +++ b/ext/gd/libgd/entities.h @@ -0,0 +1,88 @@ +/* + * Generated file - do not edit directly. + * + * This file was generated from: + * http://www.w3.org/TR/REC-html40/sgml/entities.html + * by means of the script: + * entities.tcl + */ + +#ifdef __cplusplus +extern "C" { +#endif + +static struct entities_s { + char *name; + int value; +} entities[] = { + {"AElig", 198}, {"Aacute", 193}, {"Acirc", 194}, {"Agrave", 192}, + {"Alpha", 913}, {"Aring", 197}, {"Atilde", 195}, {"Auml", 196}, + {"Beta", 914}, {"Ccedil", 199}, {"Chi", 935}, {"Dagger", 8225}, + {"Delta", 916}, {"ETH", 208}, {"Eacute", 201}, {"Ecirc", 202}, + {"Egrave", 200}, {"Epsilon", 917}, {"Eta", 919}, {"Euml", 203}, + {"Gamma", 915}, {"Iacute", 205}, {"Icirc", 206}, {"Igrave", 204}, + {"Iota", 921}, {"Iuml", 207}, {"Kappa", 922}, {"Lambda", 923}, + {"Mu", 924}, {"Ntilde", 209}, {"Nu", 925}, {"OElig", 338}, + {"Oacute", 211}, {"Ocirc", 212}, {"Ograve", 210}, {"Omega", 937}, + {"Omicron", 927}, {"Oslash", 216}, {"Otilde", 213}, {"Ouml", 214}, + {"Phi", 934}, {"Pi", 928}, {"Prime", 8243}, {"Psi", 936}, + {"Rho", 929}, {"Scaron", 352}, {"Sigma", 931}, {"THORN", 222}, + {"Tau", 932}, {"Theta", 920}, {"Uacute", 218}, {"Ucirc", 219}, + {"Ugrave", 217}, {"Upsilon", 933}, {"Uuml", 220}, {"Xi", 926}, + {"Yacute", 221}, {"Yuml", 376}, {"Zeta", 918}, {"aacute", 225}, + {"acirc", 226}, {"acute", 180}, {"aelig", 230}, {"agrave", 224}, + {"alefsym", 8501}, {"alpha", 945}, {"amp", 38}, {"and", 8743}, + {"ang", 8736}, {"aring", 229}, {"asymp", 8776}, {"atilde", 227}, + {"auml", 228}, {"bdquo", 8222}, {"beta", 946}, {"brvbar", 166}, + {"bull", 8226}, {"cap", 8745}, {"ccedil", 231}, {"cedil", 184}, + {"cent", 162}, {"chi", 967}, {"circ", 710}, {"clubs", 9827}, + {"cong", 8773}, {"copy", 169}, {"crarr", 8629}, {"cup", 8746}, + {"curren", 164}, {"dArr", 8659}, {"dagger", 8224}, {"darr", 8595}, + {"deg", 176}, {"delta", 948}, {"diams", 9830}, {"divide", 247}, + {"eacute", 233}, {"ecirc", 234}, {"egrave", 232}, {"empty", 8709}, + {"emsp", 8195}, {"ensp", 8194}, {"epsilon", 949}, {"equiv", 8801}, + {"eta", 951}, {"eth", 240}, {"euml", 235}, {"euro", 8364}, + {"exist", 8707}, {"fnof", 402}, {"forall", 8704}, {"frac12", 189}, + {"frac14", 188}, {"frac34", 190}, {"frasl", 8260}, {"gamma", 947}, + {"ge", 8805}, {"gt", 62}, {"hArr", 8660}, {"harr", 8596}, + {"hearts", 9829}, {"hellip", 8230}, {"iacute", 237}, {"icirc", 238}, + {"iexcl", 161}, {"igrave", 236}, {"image", 8465}, {"infin", 8734}, + {"int", 8747}, {"iota", 953}, {"iquest", 191}, {"isin", 8712}, + {"iuml", 239}, {"kappa", 954}, {"lArr", 8656}, {"lambda", 955}, + {"lang", 9001}, {"laquo", 171}, {"larr", 8592}, {"lceil", 8968}, + {"ldquo", 8220}, {"le", 8804}, {"lfloor", 8970}, {"lowast", 8727}, + {"loz", 9674}, {"lrm", 8206}, {"lsaquo", 8249}, {"lsquo", 8216}, + {"lt", 60}, {"macr", 175}, {"mdash", 8212}, {"micro", 181}, + {"middot", 183}, {"minus", 8722}, {"mu", 956}, {"nabla", 8711}, + {"nbsp", 160}, {"ndash", 8211}, {"ne", 8800}, {"ni", 8715}, + {"not", 172}, {"notin", 8713}, {"nsub", 8836}, {"ntilde", 241}, + {"nu", 957}, {"oacute", 243}, {"ocirc", 244}, {"oelig", 339}, + {"ograve", 242}, {"oline", 8254}, {"omega", 969}, {"omicron", 959}, + {"oplus", 8853}, {"or", 8744}, {"ordf", 170}, {"ordm", 186}, + {"oslash", 248}, {"otilde", 245}, {"otimes", 8855}, {"ouml", 246}, + {"para", 182}, {"part", 8706}, {"permil", 8240}, {"perp", 8869}, + {"phi", 966}, {"pi", 960}, {"piv", 982}, {"plusmn", 177}, + {"pound", 163}, {"prime", 8242}, {"prod", 8719}, {"prop", 8733}, + {"psi", 968}, {"quot", 34}, {"rArr", 8658}, {"radic", 8730}, + {"rang", 9002}, {"raquo", 187}, {"rarr", 8594}, {"rceil", 8969}, + {"rdquo", 8221}, {"real", 8476}, {"reg", 174}, {"rfloor", 8971}, + {"rho", 961}, {"rlm", 8207}, {"rsaquo", 8250}, {"rsquo", 8217}, + {"sbquo", 8218}, {"scaron", 353}, {"sdot", 8901}, {"sect", 167}, + {"shy", 173}, {"sigma", 963}, {"sigmaf", 962}, {"sim", 8764}, + {"spades", 9824}, {"sub", 8834}, {"sube", 8838}, {"sum", 8721}, + {"sup", 8835}, {"sup1", 185}, {"sup2", 178}, {"sup3", 179}, + {"supe", 8839}, {"szlig", 223}, {"tau", 964}, {"there4", 8756}, + {"theta", 952}, {"thetasym", 977}, {"thinsp", 8201}, {"thorn", 254}, + {"tilde", 732}, {"times", 215}, {"trade", 8482}, {"uArr", 8657}, + {"uacute", 250}, {"uarr", 8593}, {"ucirc", 251}, {"ugrave", 249}, + {"uml", 168}, {"upsih", 978}, {"upsilon", 965}, {"uuml", 252}, + {"weierp", 8472}, {"xi", 958}, {"yacute", 253}, {"yen", 165}, + {"yuml", 255}, {"zeta", 950}, {"zwj", 8205}, {"zwnj", 8204}, +}; + +#define ENTITY_NAME_LENGTH_MAX 8 +#define NR_OF_ENTITIES 252 + +#ifdef __cplusplus +} +#endif diff --git a/ext/gd/libgd/ftraster/gd_ft_math.c b/ext/gd/libgd/ftraster/gd_ft_math.c new file mode 100644 index 000000000000..5a98d9d22dfa --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_math.c @@ -0,0 +1,459 @@ +/***************************************************************************/ +/* */ +/* fttrigon.c */ +/* */ +/* FreeType trigonometric functions (body). */ +/* */ +/* Copyright 2001-2005, 2012-2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "gd_ft_math.h" +#include + +#if defined(_MSC_VER) +#include +static unsigned int __inline clz(unsigned int x) { + unsigned long r = 0; + if (x != 0) + { + _BitScanReverse(&r, x); + } + return r; +} +#define GD_FT_MSB(x) (clz(x)) +#elif defined(__GNUC__) +#define GD_FT_MSB(x) (31 - __builtin_clz(x)) +#else +static unsigned int __inline clz(unsigned int x) { + int c = 31; + x &= ~x + 1; + if (n & 0x0000FFFF) c -= 16; + if (n & 0x00FF00FF) c -= 8; + if (n & 0x0F0F0F0F) c -= 4; + if (n & 0x33333333) c -= 2; + if (n & 0x55555555) c -= 1; + return c; +} +#define GD_FT_MSB(x) (clz(x)) +#endif + + + + + +#define GD_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define GD_FT_PAD_ROUND(x, n) GD_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define GD_FT_PAD_CEIL(x, n) GD_FT_PAD_FLOOR((x) + ((n)-1), n) + +#define GD_FT_BEGIN_STMNT do { +#define GD_FT_END_STMNT \ + } \ + while (0) +/* transfer sign leaving a positive number */ +#define GD_FT_MOVE_SIGN(x, s) \ + GD_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + GD_FT_END_STMNT + +GD_FT_Long GD_FT_MulFix(GD_FT_Long a, GD_FT_Long b) +{ + GD_FT_Int s = 1; + GD_FT_Long c; + + GD_FT_MOVE_SIGN(a, s); + GD_FT_MOVE_SIGN(b, s); + + c = (GD_FT_Long)(((GD_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +GD_FT_Long GD_FT_MulDiv(GD_FT_Long a, GD_FT_Long b, GD_FT_Long c) +{ + GD_FT_Int s = 1; + GD_FT_Long d; + + GD_FT_MOVE_SIGN(a, s); + GD_FT_MOVE_SIGN(b, s); + GD_FT_MOVE_SIGN(c, s); + + d = (GD_FT_Long)(c > 0 ? ((GD_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +GD_FT_Long GD_FT_DivFix(GD_FT_Long a, GD_FT_Long b) +{ + GD_FT_Int s = 1; + GD_FT_Long q; + + GD_FT_MOVE_SIGN(a, s); + GD_FT_MOVE_SIGN(b, s); + + q = (GD_FT_Long)(b > 0 ? (((GD_FT_UInt64)a << 16) + (b >> 1)) / b + : 0x7FFFFFFFL); + + return (s < 0 ? -q : q); +} + +/*************************************************************************/ +/* */ +/* This is a fixed-point CORDIC implementation of trigonometric */ +/* functions as well as transformations between Cartesian and polar */ +/* coordinates. The angles are represented as 16.16 fixed-point values */ +/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ +/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ +/* discrete Cartesian grid can have the same or better angular */ +/* resolution. Therefore, to maintain this precision, some functions */ +/* require an interim upscaling of the vectors, whereas others operate */ +/* with 24-bit long vectors directly. */ +/* */ +/*************************************************************************/ + +/* the Cordic shrink factor 0.858785336480436 * 2^32 */ +#define GD_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define GD_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for GD_FT_PI = 180L << 16, i.e. degrees */ +#define GD_FT_TRIG_MAX_ITERS 23 + +static const GD_FT_Fixed ft_trig_arctan_table[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, + 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, + 29L, 14L, 7L, 4L, 2L, 1L}; + +/* multiply a given value by the CORDIC shrink factor */ +static GD_FT_Fixed ft_trig_downscale(GD_FT_Fixed val) +{ + GD_FT_Fixed s; + GD_FT_Int64 v; + + s = val; + val = GD_FT_ABS(val); + + v = (val * (GD_FT_Int64)GD_FT_TRIG_SCALE) + 0x100000000UL; + val = (GD_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static GD_FT_Int ft_trig_prenorm(GD_FT_Vector* vec) +{ + GD_FT_Pos x, y; + GD_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = GD_FT_MSB(GD_FT_ABS(x) | GD_FT_ABS(y)); + + if (shift <= GD_FT_TRIG_SAFE_MSB) { + shift = GD_FT_TRIG_SAFE_MSB - shift; + vec->x = (GD_FT_Pos)((GD_FT_ULong)x << shift); + vec->y = (GD_FT_Pos)((GD_FT_ULong)y << shift); + } else { + shift -= GD_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(GD_FT_Vector* vec, GD_FT_Angle theta) +{ + GD_FT_Int i; + GD_FT_Fixed x, y, xtemp, b; + const GD_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -GD_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += GD_FT_ANGLE_PI2; + } + + while (theta > GD_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= GD_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < GD_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + GD_FT_Fixed v1 = ((y + b) >> i); + GD_FT_Fixed v2 = ((x + b) >> i); + if (theta < 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + vec->x = x; + vec->y = y; +} + +static void ft_trig_pseudo_polarize(GD_FT_Vector* vec) +{ + GD_FT_Angle theta; + GD_FT_Int i; + GD_FT_Fixed x, y, xtemp, b; + const GD_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Get the vector into [-PI/4,PI/4] sector */ + if (y > x) { + if (y > -x) { + theta = GD_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? GD_FT_ANGLE_PI : -GD_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -GD_FT_ANGLE_PI2; + xtemp = -y; + y = x; + x = xtemp; + } else { + theta = 0; + } + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < GD_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + GD_FT_Fixed v1 = ((y + b) >> i); + GD_FT_Fixed v2 = ((x + b) >> i); + if (y > 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + /* round theta */ + if (theta >= 0) + theta = GD_FT_PAD_ROUND(theta, 32); + else + theta = -GD_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +GD_FT_Fixed GD_FT_Cos(GD_FT_Angle angle) +{ + GD_FT_Vector v; + + v.x = GD_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +GD_FT_Fixed GD_FT_Sin(GD_FT_Angle angle) +{ + return GD_FT_Cos(GD_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +GD_FT_Fixed GD_FT_Tan(GD_FT_Angle angle) +{ + GD_FT_Vector v; + + v.x = GD_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return GD_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +GD_FT_Angle GD_FT_Atan2(GD_FT_Fixed dx, GD_FT_Fixed dy) +{ + GD_FT_Vector v; + + if (dx == 0 && dy == 0) return 0; + + v.x = dx; + v.y = dy; + ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + return v.y; +} + +/* documentation is in fttrigon.h */ + +void GD_FT_Vector_Unit(GD_FT_Vector* vec, GD_FT_Angle angle) +{ + vec->x = GD_FT_TRIG_SCALE >> 8; + vec->y = 0; + ft_trig_pseudo_rotate(vec, angle); + vec->x = (vec->x + 0x80L) >> 8; + vec->y = (vec->y + 0x80L) >> 8; +} + +/* these macros return 0 for positive numbers, + and -1 for negative ones */ +#define GD_FT_SIGN_LONG(x) ((x) >> (GD_FT_SIZEOF_LONG * 8 - 1)) +#define GD_FT_SIGN_INT(x) ((x) >> (GD_FT_SIZEOF_INT * 8 - 1)) +#define GD_FT_SIGN_INT32(x) ((x) >> 31) +#define GD_FT_SIGN_INT16(x) ((x) >> 15) + +/* documentation is in fttrigon.h */ + +void GD_FT_Vector_Rotate(GD_FT_Vector* vec, GD_FT_Angle angle) +{ + GD_FT_Int shift; + GD_FT_Vector v; + + v.x = vec->x; + v.y = vec->y; + + if (angle && (v.x != 0 || v.y != 0)) { + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_rotate(&v, angle); + v.x = ft_trig_downscale(v.x); + v.y = ft_trig_downscale(v.y); + + if (shift > 0) { + GD_FT_Int32 half = (GD_FT_Int32)1L << (shift - 1); + + vec->x = (v.x + half + GD_FT_SIGN_LONG(v.x)) >> shift; + vec->y = (v.y + half + GD_FT_SIGN_LONG(v.y)) >> shift; + } else { + shift = -shift; + vec->x = (GD_FT_Pos)((GD_FT_ULong)v.x << shift); + vec->y = (GD_FT_Pos)((GD_FT_ULong)v.y << shift); + } + } +} + +/* documentation is in fttrigon.h */ + +GD_FT_Fixed GD_FT_Vector_Length(GD_FT_Vector* vec) +{ + GD_FT_Int shift; + GD_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return GD_FT_ABS(v.y); + } else if (v.y == 0) { + return GD_FT_ABS(v.x); + } + + /* general case */ + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; + + return (GD_FT_Fixed)((GD_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void GD_FT_Vector_Polarize(GD_FT_Vector* vec, GD_FT_Fixed* length, + GD_FT_Angle* angle) +{ + GD_FT_Int shift; + GD_FT_Vector v; + + v = *vec; + + if (v.x == 0 && v.y == 0) return; + + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + *length = (shift >= 0) ? (v.x >> shift) + : (GD_FT_Fixed)((GD_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void GD_FT_Vector_From_Polar(GD_FT_Vector* vec, GD_FT_Fixed length, + GD_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + GD_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +GD_FT_Angle GD_FT_Angle_Diff( GD_FT_Angle angle1, GD_FT_Angle angle2 ) +{ + GD_FT_Angle delta = angle2 - angle1; + + while ( delta <= -GD_FT_ANGLE_PI ) + delta += GD_FT_ANGLE_2PI; + + while ( delta > GD_FT_ANGLE_PI ) + delta -= GD_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/ext/gd/libgd/ftraster/gd_ft_math.h b/ext/gd/libgd/ftraster/gd_ft_math.h new file mode 100644 index 000000000000..940e62c7ef2d --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_math.h @@ -0,0 +1,438 @@ +#ifndef GD_FT_MATH_H +#define GD_FT_MATH_H + +/***************************************************************************/ +/* */ +/* fttrigon.h */ +/* */ +/* FreeType trigonometric functions (specification). */ +/* */ +/* Copyright 2001, 2003, 2005, 2007, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "gd_ft_types.h" + + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like GD_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define GD_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define GD_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define GD_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define GD_FT_HYPOT( x, y ) \ + ( x = GD_FT_ABS( x ), \ + y = GD_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_MulFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ +/* used to multiply a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. Use a 16.16 factor here whenever */ +/* possible (see note below). */ +/* */ +/* */ +/* The result of `(a*b)/0x10000'. */ +/* */ +/* */ +/* This function has been optimized for the case where the absolute */ +/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ +/* As this happens mainly when scaling from notional units to */ +/* fractional pixels in FreeType, it resulted in noticeable speed */ +/* improvements between versions 2.x and 1.x. */ +/* */ +/* As a conclusion, always try to place a 16.16 factor as the */ +/* _second_ argument of this function; this can make a great */ +/* difference. */ +/* */ +GD_FT_Long +GD_FT_MulFix( GD_FT_Long a, + GD_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_MulDiv */ +/* */ +/* */ +/* A very simple function used to perform the computation `(a*b)/c' */ +/* with maximum accuracy (it uses a 64-bit intermediate integer */ +/* whenever necessary). */ +/* */ +/* This function isn't necessarily as fast as some processor specific */ +/* operations, but is at least completely portable. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. */ +/* c :: The divisor. */ +/* */ +/* */ +/* The result of `(a*b)/c'. This function never traps when trying to */ +/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ +/* on the signs of `a' and `b'. */ +/* */ +GD_FT_Long +GD_FT_MulDiv( GD_FT_Long a, + GD_FT_Long b, + GD_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_DivFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ +/* used to divide a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The numerator. */ +/* b :: The denominator. Use a 16.16 factor here. */ +/* */ +/* */ +/* The result of `(a*0x10000)/b'. */ +/* */ +GD_FT_Long +GD_FT_DivFix( GD_FT_Long a, + GD_FT_Long b ); + + + + /*************************************************************************/ + /* */ + /*
*/ + /* computations */ + /* */ + /*************************************************************************/ + + + /************************************************************************* + * + * @type: + * GD_FT_Angle + * + * @description: + * This type is used to model angle values in FreeType. Note that the + * angle is a 16.16 fixed-point value expressed in degrees. + * + */ + typedef GD_FT_Fixed GD_FT_Angle; + + + /************************************************************************* + * + * @macro: + * GD_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @GD_FT_Angle units. + * + */ +#define GD_FT_ANGLE_PI ( 180L << 16 ) + + + /************************************************************************* + * + * @macro: + * GD_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @GD_FT_Angle units. + * + */ +#define GD_FT_ANGLE_2PI ( GD_FT_ANGLE_PI * 2 ) + + + /************************************************************************* + * + * @macro: + * GD_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @GD_FT_Angle units. + * + */ +#define GD_FT_ANGLE_PI2 ( GD_FT_ANGLE_PI / 2 ) + + + /************************************************************************* + * + * @macro: + * GD_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @GD_FT_Angle units. + * + */ +#define GD_FT_ANGLE_PI4 ( GD_FT_ANGLE_PI / 4 ) + + + /************************************************************************* + * + * @function: + * GD_FT_Sin + * + * @description: + * Return the sinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The sinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @GD_FT_Vector_Unit. + * + */ + GD_FT_Fixed + GD_FT_Sin( GD_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Cos + * + * @description: + * Return the cosinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The cosinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @GD_FT_Vector_Unit. + * + */ + GD_FT_Fixed + GD_FT_Cos( GD_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ + GD_FT_Fixed + GD_FT_Tan( GD_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Atan2 + * + * @description: + * Return the arc-tangent corresponding to a given vector (x,y) in + * the 2d plane. + * + * @input: + * x :: + * The horizontal vector coordinate. + * + * y :: + * The vertical vector coordinate. + * + * @return: + * The arc-tangent value (i.e. angle). + * + */ + GD_FT_Angle + GD_FT_Atan2( GD_FT_Fixed x, + GD_FT_Fixed y ); + + + /************************************************************************* + * + * @function: + * GD_FT_Angle_Diff + * + * @description: + * Return the difference between two angles. The result is always + * constrained to the ]-PI..PI] interval. + * + * @input: + * angle1 :: + * First angle. + * + * angle2 :: + * Second angle. + * + * @return: + * Constrained value of `value2-value1'. + * + */ + GD_FT_Angle + GD_FT_Angle_Diff( GD_FT_Angle angle1, + GD_FT_Angle angle2 ); + + + /************************************************************************* + * + * @function: + * GD_FT_Vector_Unit + * + * @description: + * Return the unit vector corresponding to a given angle. After the + * call, the value of `vec.x' will be `sin(angle)', and the value of + * `vec.y' will be `cos(angle)'. + * + * This function is useful to retrieve both the sinus and cosinus of a + * given angle quickly. + * + * @output: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + GD_FT_Vector_Unit( GD_FT_Vector* vec, + GD_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + GD_FT_Vector_Rotate( GD_FT_Vector* vec, + GD_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Vector_Length + * + * @description: + * Return the length of a given vector. + * + * @input: + * vec :: + * The address of target vector. + * + * @return: + * The vector length, expressed in the same units that the original + * vector coordinates. + * + */ + GD_FT_Fixed + GD_FT_Vector_Length( GD_FT_Vector* vec ); + + + /************************************************************************* + * + * @function: + * GD_FT_Vector_Polarize + * + * @description: + * Compute both the length and angle of a given vector. + * + * @input: + * vec :: + * The address of source vector. + * + * @output: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + GD_FT_Vector_Polarize( GD_FT_Vector* vec, + GD_FT_Fixed *length, + GD_FT_Angle *angle ); + + + /************************************************************************* + * + * @function: + * GD_FT_Vector_From_Polar + * + * @description: + * Compute vector coordinates from a length and angle. + * + * @output: + * vec :: + * The address of source vector. + * + * @input: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + GD_FT_Vector_From_Polar( GD_FT_Vector* vec, + GD_FT_Fixed length, + GD_FT_Angle angle ); + + +#endif // GD_FT_MATH_H diff --git a/ext/gd/libgd/ftraster/gd_ft_raster.c b/ext/gd/libgd/ftraster/gd_ft_raster.c new file mode 100644 index 000000000000..6bd4ced2c730 --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_raster.c @@ -0,0 +1,1708 @@ +/**************************************************************************** + * + * ftgrays.c + * + * A new `perfect' anti-aliasing renderer (body). + * + * Copyright (C) 2000-2020 by + * David Turner, Robert Wilhelm, and Werner Lemberg. + * + * This file is part of the FreeType project, and may only be used, + * modified, and distributed under the terms of the FreeType project + * license, LICENSE.TXT. By continuing to use, modify, or distribute + * this file you indicate that you have read the license and + * understand and accept it fully. + * + */ + +/*************************************************************************/ +/* */ +/* This is a new anti-aliasing scan-converter for FreeType 2. The */ +/* algorithm used here is _very_ different from the one in the standard */ +/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ +/* coverage of the outline on each pixel cell. */ +/* */ +/* It is based on ideas that I initially found in Raph Levien's */ +/* excellent LibArt graphics library (see http://www.levien.com/libart */ +/* for more information, though the web pages do not tell anything */ +/* about the renderer; you'll have to dive into the source code to */ +/* understand how it works). */ +/* */ +/* Note, however, that this is a _very_ different implementation */ +/* compared to Raph's. Coverage information is stored in a very */ +/* different way, and I don't use sorted vector paths. Also, it doesn't */ +/* use floating point values. */ +/* */ +/* This renderer has the following advantages: */ +/* */ +/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ +/* callback function that will be called by the renderer to draw gray */ +/* spans on any target surface. You can thus do direct composition on */ +/* any kind of bitmap, provided that you give the renderer the right */ +/* callback. */ +/* */ +/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ +/* each pixel cell. */ +/* */ +/* - It performs a single pass on the outline (the `standard' FT2 */ +/* renderer makes two passes). */ +/* */ +/* - It can easily be modified to render to _any_ number of gray levels */ +/* cheaply. */ +/* */ +/* - For small (< 20) pixel sizes, it is faster than the standard */ +/* renderer. */ +/* */ +/*************************************************************************/ + +#include "gd_ft_raster.h" +#include "gd_ft_math.h" +#include "gd_vector2d_private.h" +#include "gd_path_matrix.h" + +/* Auxiliary macros for token concatenation. */ +#define GD_FT_ERR_XCAT(x, y) x##y +#define GD_FT_ERR_CAT(x, y) GD_FT_ERR_XCAT(x, y) + +#define GD_FT_BEGIN_STMNT do { +#define GD_FT_END_STMNT \ + } \ + while (0) + +#include +#include +#include +#include +#define GD_FT_UINT_MAX UINT_MAX +#define GD_FT_INT_MAX INT_MAX +#define GD_FT_ULONG_MAX ULONG_MAX +#define GD_FT_CHAR_BIT CHAR_BIT + +#define ft_memset memset + +#define ft_setjmp setjmp +#define ft_longjmp longjmp +#define ft_jmp_buf jmp_buf + +typedef ptrdiff_t GD_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 + +#define GD_FT_BEGIN_HEADER +#define GD_FT_END_HEADER + +/* This macro is used to indicate that a function parameter is unused. */ +/* Its purpose is simply to reduce compiler warnings. Note also that */ +/* simply defining it as `(void)x' doesn't avoid warnings with certain */ +/* ANSI compilers (e.g. LCC). */ +#define GD_FT_UNUSED(x) (x) = (x) + +#define GD_FT_THROW(e) GD_FT_ERR_CAT(ErrRaster_, e) + +/* The size in bytes of the render pool used by the scan-line converter */ +/* to do all of its work. */ +#define GD_FT_RENDER_POOL_SIZE 16384L + +typedef int (*GD_FT_Outline_MoveToFunc)(const GD_FT_Vector* to, void* user); + +#define GD_FT_Outline_MoveTo_Func GD_FT_Outline_MoveToFunc + +typedef int (*GD_FT_Outline_LineToFunc)(const GD_FT_Vector* to, void* user); + +#define GD_FT_Outline_LineTo_Func GD_FT_Outline_LineToFunc + +typedef int (*GD_FT_Outline_ConicToFunc)(const GD_FT_Vector* control, + const GD_FT_Vector* to, void* user); + +#define GD_FT_Outline_ConicTo_Func GD_FT_Outline_ConicToFunc + +typedef int (*GD_FT_Outline_CubicToFunc)(const GD_FT_Vector* control1, + const GD_FT_Vector* control2, + const GD_FT_Vector* to, void* user); + +#define GD_FT_Outline_CubicTo_Func GD_FT_Outline_CubicToFunc + +typedef struct GD_FT_Outline_Funcs_ { + GD_FT_Outline_MoveToFunc move_to; + GD_FT_Outline_LineToFunc line_to; + GD_FT_Outline_ConicToFunc conic_to; + GD_FT_Outline_CubicToFunc cubic_to; + + int shift; + GD_FT_Pos delta; + +} GD_FT_Outline_Funcs; + +#define GD_FT_DEFINE_OUTLINE_FUNCS(class_, move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_) \ + static const GD_FT_Outline_Funcs class_ = {move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_}; + +#define GD_FT_DEFINE_RASTER_FUNCS(class_, raster_new_, raster_reset_, \ + raster_render_, raster_done_) \ + const GD_FT_Raster_Funcs class_ = {raster_new_, raster_reset_, \ + raster_render_, raster_done_}; + +#ifndef GD_FT_MEM_SET +#define GD_FT_MEM_SET(d, s, c) ft_memset(d, s, c) +#endif + +#ifndef GD_FT_MEM_ZERO +#define GD_FT_MEM_ZERO(dest, count) GD_FT_MEM_SET(dest, 0, count) +#endif + +/* as usual, for the speed hungry :-) */ + +#undef RAS_ARG +#undef RAS_ARG_ +#undef RAS_VAR +#undef RAS_VAR_ + +#ifndef GD_FT_STATIC_RASTER + +#define RAS_ARG gray_PWorker worker +#define RAS_ARG_ gray_PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#else /* GD_FT_STATIC_RASTER */ + +#define RAS_ARG /* empty */ +#define RAS_ARG_ /* empty */ +#define RAS_VAR /* empty */ +#define RAS_VAR_ /* empty */ + +#endif /* GD_FT_STATIC_RASTER */ + +/* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#undef FLOOR +#undef CEILING +#undef TRUNC +#undef SCALED + +#define ONE_PIXEL (1L << PIXEL_BITS) +#define PIXEL_MASK (-1L << PIXEL_BITS) +#define TRUNC(x) ((TCoord)((x) >> PIXEL_BITS)) +#define SUBPIXELS(x) ((TPos)(x) * ONE_PIXEL) +#define FLOOR(x) ((x) & -ONE_PIXEL) +#define CEILING(x) (((x) + ONE_PIXEL - 1) & -ONE_PIXEL) +#define ROUND(x) (((x) + ONE_PIXEL / 2) & -ONE_PIXEL) + +#if PIXEL_BITS >= 6 +#define UPSCALE(x) ((x) * (1L << (PIXEL_BITS - 6))) +#define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) +#else +#define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) +#define DOWNSCALE(x) ((x) * (1L << (6 - PIXEL_BITS))) +#endif + +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define GD_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + GD_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) % (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + GD_FT_END_STMNT + +#ifdef __arm__ +/* Work around a bug specific to GCC which make the compiler fail to */ +/* optimize a division and modulo operation on the same parameters */ +/* into a single call to `__aeabi_idivmod'. See */ +/* */ +/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */ +#undef GD_FT_DIV_MOD +#define GD_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + GD_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) - (quotient) * (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + GD_FT_END_STMNT +#endif /* __arm__ */ + +/* These macros speed up repetitive divisions by replacing them */ +/* with multiplications and right shifts. */ +#define GD_FT_UDIVPREP(b) \ + long b##_r = (long)(GD_FT_ULONG_MAX >> PIXEL_BITS) / (b) +#define GD_FT_UDIV(a, b) \ + (((unsigned long)(a) * (unsigned long)(b##_r)) >> \ + (sizeof(long) * GD_FT_CHAR_BIT - PIXEL_BITS)) + +/*************************************************************************/ +/* */ +/* TYPE DEFINITIONS */ +/* */ + +/* don't change the following types to GD_FT_Int or GD_FT_Pos, since we might */ +/* need to define them to "float" or "double" when experimenting with */ +/* new algorithms */ + +typedef long TCoord; /* integer scanline/pixel coordinate */ +typedef long TPos; /* sub-pixel coordinate */ + +/* determine the type used to store cell areas. This normally takes at */ +/* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ +/* `long' instead of `int', otherwise bad things happen */ + +#if PIXEL_BITS <= 7 + +typedef int TArea; + +#else /* PIXEL_BITS >= 8 */ + +/* approximately determine the size of integers using an ANSI-C header */ +#if GD_FT_UINT_MAX == 0xFFFFU +typedef long TArea; +#else +typedef int TArea; +#endif + +#endif /* PIXEL_BITS >= 8 */ + +/* maximum number of gray spans in a call to the span callback */ +#define GD_FT_MAX_GRAY_SPANS 256 + +typedef struct TCell_* PCell; + +typedef struct TCell_ { + TPos x; /* same with gray_TWorker.ex */ + TCoord cover; /* same with gray_TWorker.cover */ + TArea area; + PCell next; + +} TCell; + +#if defined(_MSC_VER) /* Visual C++ (and Intel C++) */ +/* We disable the warning `structure was padded due to */ +/* __declspec(align())' in order to compile cleanly with */ +/* the maximum level of warnings. */ +#pragma warning(push) +#pragma warning(disable : 4324) +#endif /* _MSC_VER */ + +typedef struct gray_TWorker_ { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + TCoord cover; + int invalid; + + PCell cells; + GD_FT_PtrDist max_cells; + GD_FT_PtrDist num_cells; + + TPos x, y; + + GD_FT_Vector bez_stack[32 * 3 + 1]; + int lev_stack[32]; + + GD_FT_Outline outline; + GD_FT_BBox clip_box; + + int bound_left; + int bound_top; + int bound_right; + int bound_bottom; + + GD_FT_Span gray_spans[GD_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + + GD_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + + /* Source abstraction for direct path rendering */ + struct gray_TSource_ *source; +} gray_TWorker, *gray_PWorker; + +typedef struct gray_TSource_ +{ + int (*get_cbox)(void *source, GD_FT_BBox *box); + int (*decompose)(void *source, void *worker); + void *source; +} gray_TSource; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#ifndef GD_FT_STATIC_RASTER +#define ras (*worker) +#else +static gray_TWorker ras; +#endif + +typedef struct gray_TRaster_ { + void* memory; + +} gray_TRaster, *gray_PRaster; + +/*************************************************************************/ +/* */ +/* Initialize the cells table. */ +/* */ +static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size) +{ + ras.buffer = buffer; + ras.buffer_size = byte_size; + + ras.ycells = (PCell*)buffer; + ras.cells = NULL; + ras.max_cells = 0; + ras.num_cells = 0; + ras.area = 0; + ras.cover = 0; + ras.invalid = 1; + + ras.bound_left = INT_MAX; + ras.bound_top = INT_MAX; + ras.bound_right = INT_MIN; + ras.bound_bottom = INT_MIN; +} + +/*************************************************************************/ +/* */ +/* Compute the outline bounding box. */ +/* */ +static void gray_compute_cbox(RAS_ARG) +{ + if (ras.source && ras.source->get_cbox) { + GD_FT_BBox box; + int error = ras.source->get_cbox(ras.source->source, &box); + if (!error) { + ras.min_ex = box.xMin >> 6; + ras.min_ey = box.yMin >> 6; + ras.max_ex = (box.xMax + 63) >> 6; + ras.max_ey = (box.yMax + 63) >> 6; + return; + } + } + + /* Fallback to outline */ + GD_FT_Outline* outline = &ras.outline; + GD_FT_Vector* vec = outline->points; + GD_FT_Vector* limit = vec + outline->n_points; + + if (outline->n_points <= 0) { + ras.min_ex = ras.max_ex = 0; + ras.min_ey = ras.max_ey = 0; + return; + } + + ras.min_ex = ras.max_ex = vec->x; + ras.min_ey = ras.max_ey = vec->y; + + vec++; + + for (; vec < limit; vec++) { + TPos x = vec->x; + TPos y = vec->y; + + if (x < ras.min_ex) ras.min_ex = x; + if (x > ras.max_ex) ras.max_ex = x; + if (y < ras.min_ey) ras.min_ey = y; + if (y > ras.max_ey) ras.max_ey = y; + } + + /* truncate the bounding box to integer pixels */ + ras.min_ex = ras.min_ex >> 6; + ras.min_ey = ras.min_ey >> 6; + ras.max_ex = (ras.max_ex + 63) >> 6; + ras.max_ey = (ras.max_ey + 63) >> 6; +} + +/*************************************************************************/ +/* */ +/* Record the current cell in the table. */ +/* */ +static PCell gray_find_cell(RAS_ARG) +{ + PCell *pcell, cell; + TPos x = ras.ex; + + if (x > ras.count_ex) x = ras.count_ex; + + pcell = &ras.ycells[ras.ey]; + for (;;) { + cell = *pcell; + if (cell == NULL || cell->x > x) break; + + if (cell->x == x) goto Exit; + + pcell = &cell->next; + } + + if (ras.num_cells >= ras.max_cells) ft_longjmp(ras.jump_buffer, 1); + + cell = ras.cells + ras.num_cells++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + + cell->next = *pcell; + *pcell = cell; + +Exit: + return cell; +} + +static void gray_record_cell(RAS_ARG) +{ + if (ras.area | ras.cover) { + PCell cell = gray_find_cell(RAS_VAR); + + cell->area += ras.area; + cell->cover += ras.cover; + } +} + +/*************************************************************************/ +/* */ +/* Set the current cell to a new position. */ +/* */ +static void gray_set_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if (ex > ras.max_ex) ex = ras.max_ex; + + ex -= ras.min_ex; + if (ex < 0) ex = -1; + + /* are we moving to a different cell ? */ + if (ex != ras.ex || ey != ras.ey) { + /* record the current one if it is valid */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex; + ras.ey = ey; + } + + ras.invalid = + ((unsigned)ey >= (unsigned)ras.count_ey || ex >= ras.count_ex); +} + +/*************************************************************************/ +/* */ +/* Start a new contour at a given cell. */ +/* */ +static void gray_start_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + if (ex > ras.max_ex) ex = (TCoord)(ras.max_ex); + + if (ex < ras.min_ex) ex = (TCoord)(ras.min_ex - 1); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex - ras.min_ex; + ras.ey = ey - ras.min_ey; + ras.invalid = 0; + + gray_set_cell(RAS_VAR_ ex, ey); +} + +/*************************************************************************/ +/* */ +/* Render a straight line across multiple cells in any direction. */ +/* */ +static void gray_render_line(RAS_ARG_ TPos to_x, TPos to_y) +{ + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + ex1 = TRUNC(ras.x); + ex2 = TRUNC(to_x); + ey1 = TRUNC(ras.y); + ey2 = TRUNC(to_y); + + /* perform vertical clipping */ + if ((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || + (ey1 < ras.min_ey && ey2 < ras.min_ey)) + goto End; + + dx = to_x - ras.x; + dy = to_y - ras.y; + + fx1 = ras.x - SUBPIXELS(ex1); + fy1 = ras.y - SUBPIXELS(ey1); + + if (ex1 == ex2 && ey1 == ey2) /* inside one cell */ + ; + else if (dy == 0) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell(RAS_VAR_ ex1, ey1); + } else if (dx == 0) { + if (dy > 0) /* vertical line up */ + do { + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + else /* vertical line down */ + do { + fy2 = 0; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + } else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + GD_FT_UDIVPREP(dx); + GD_FT_UDIVPREP(dy); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + if (prod <= 0 && prod - dx * ONE_PIXEL > 0) /* left */ + { + fx2 = 0; + fy2 = (TPos)GD_FT_UDIV(-prod, -dx); + prod -= dy * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } else if (prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)GD_FT_UDIV(-prod, dy); + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = 0; + ey1++; + } else if (prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)GD_FT_UDIV(prod, dx); + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = 0; + fy1 = fy2; + ex1++; + } else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)GD_FT_UDIV(prod, -dy); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ex1 != ex2 || ey1 != ey2); + } + + fx2 = to_x - SUBPIXELS(ex2); + fy2 = to_y - SUBPIXELS(ey2); + + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + +End: + ras.x = to_x; + ras.y = to_y; +} + +static void gray_split_conic(GD_FT_Vector* base) +{ + TPos a, b; + + base[4].x = base[2].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + base[3].x = b >> 1; + base[2].x = ( a + b ) >> 2; + base[1].x = a >> 1; + + base[4].y = base[2].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + base[3].y = b >> 1; + base[2].y = ( a + b ) >> 2; + base[1].y = a >> 1; +} + +static void gray_render_conic(RAS_ARG_ const GD_FT_Vector* control, + const GD_FT_Vector* to) +{ + TPos dx, dy; + TPos min, max, y; + int top, level; + int* levels; + GD_FT_Vector* arc; + + levels = ras.lev_stack; + + arc = ras.bez_stack; + arc[0].x = UPSCALE(to->x); + arc[0].y = UPSCALE(to->y); + arc[1].x = UPSCALE(control->x); + arc[1].y = UPSCALE(control->y); + arc[2].x = ras.x; + arc[2].y = ras.y; + top = 0; + + dx = GD_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); + dy = GD_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); + if (dx < dy) dx = dy; + + if (dx < ONE_PIXEL / 4) goto Draw; + + /* short-cut the arc that crosses the current band */ + min = max = arc[0].y; + + y = arc[1].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[2].y; + if (y < min) min = y; + if (y > max) max = y; + + if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; + + level = 0; + do { + dx >>= 2; + level++; + } while (dx > ONE_PIXEL / 4); + + levels[0] = level; + + do { + level = levels[top]; + if (level > 0) { + gray_split_conic(arc); + arc += 2; + top++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); + top--; + arc -= 2; + + } while (top >= 0); +} + +static void gray_split_cubic(GD_FT_Vector* base) +{ + TPos a, b, c; + + + base[6].x = base[3].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + c = base[2].x + base[3].x; + base[5].x = c >> 1; + c += b; + base[4].x = c >> 2; + base[1].x = a >> 1; + a += b; + base[2].x = a >> 2; + base[3].x = ( a + c ) >> 3; + + base[6].y = base[3].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + c = base[2].y + base[3].y; + base[5].y = c >> 1; + c += b; + base[4].y = c >> 2; + base[1].y = a >> 1; + a += b; + base[2].y = a >> 2; + base[3].y = ( a + c ) >> 3; +} + + +static void +gray_render_cubic(RAS_ARG_ const GD_FT_Vector* control1, + const GD_FT_Vector* control2, + const GD_FT_Vector* to) +{ + GD_FT_Vector* arc = ras.bez_stack; + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control2->x ); + arc[1].y = UPSCALE( control2->y ); + arc[2].x = UPSCALE( control1->x ); + arc[2].y = UPSCALE( control1->y ); + arc[3].x = ras.x; + arc[3].y = ras.y; + + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey && + TRUNC( arc[3].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey && + TRUNC( arc[3].y ) < ras.min_ey ) ) + { + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } + + for (;;) + { + /* with each split, control points quickly converge towards */ + /* chord trisection points and the vanishing distances below */ + /* indicate when the segment is flat enough to draw */ + if ( GD_FT_ABS( 2 * arc[0].x - 3 * arc[1].x + arc[3].x ) > ONE_PIXEL / 2 || + GD_FT_ABS( 2 * arc[0].y - 3 * arc[1].y + arc[3].y ) > ONE_PIXEL / 2 || + GD_FT_ABS( arc[0].x - 3 * arc[2].x + 2 * arc[3].x ) > ONE_PIXEL / 2 || + GD_FT_ABS( arc[0].y - 3 * arc[2].y + 2 * arc[3].y ) > ONE_PIXEL / 2 ) + goto Split; + + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == ras.bez_stack ) + return; + + arc -= 3; + continue; + + Split: + gray_split_cubic( arc ); + arc += 3; + } +} + +static int gray_move_to(const GD_FT_Vector* to, gray_PWorker worker) +{ + TPos x, y; + + /* record current cell, if any */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + /* start to a new position */ + x = UPSCALE(to->x); + y = UPSCALE(to->y); + + gray_start_cell(RAS_VAR_ TRUNC(x), TRUNC(y)); + + worker->x = x; + worker->y = y; + return 0; +} + +static int gray_line_to(const GD_FT_Vector* to, gray_PWorker worker) +{ + gray_render_line(RAS_VAR_ UPSCALE(to->x), UPSCALE(to->y)); + return 0; +} + +static int gray_conic_to(const GD_FT_Vector* control, const GD_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_conic(RAS_VAR_ control, to); + return 0; +} + +static int gray_cubic_to(const GD_FT_Vector* control1, + const GD_FT_Vector* control2, const GD_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_cubic(RAS_VAR_ control1, control2, to); + return 0; +} + +static void gray_hline(RAS_ARG_ TCoord x, TCoord y, TPos area, TCoord acount) +{ + int coverage; + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)(area >> (PIXEL_BITS * 2 + 1 - 8)); + /* use range 0..256 */ + if (coverage < 0) coverage = -coverage; + + if (ras.outline.flags & GD_FT_OUTLINE_EVEN_ODD_FILL) { + coverage &= 511; + + if (coverage > 256) + coverage = 512 - coverage; + else if (coverage == 256) + coverage = 255; + } else { + /* normal non-zero winding rule */ + if (coverage >= 256) coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* GD_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ + if (x >= 32767) x = 32767; + + /* GD_FT_Span.y is an integer, so limit our coordinates appropriately */ + if (y >= GD_FT_INT_MAX) y = GD_FT_INT_MAX; + + if (coverage) { + GD_FT_Span* span; + int count; + + // update bounding box. + if (x < ras.bound_left) ras.bound_left = x; + if (y < ras.bound_top) ras.bound_top = y; + if (y > ras.bound_bottom) ras.bound_bottom = y; + if (x + acount > ras.bound_right) ras.bound_right = x + acount; + + /* see whether we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if (count > 0 && span->y == y && (int)span->x + span->len == (int)x && + span->coverage == coverage) { + span->len = (unsigned short)(span->len + acount); + return; + } + + if (count >= GD_FT_MAX_GRAY_SPANS) { + if (ras.render_span && count > 0) + ras.render_span(count, ras.gray_spans, ras.render_span_data); + +#ifdef DEBUG_GRAYS + + if (1) { + int n; + + fprintf(stderr, "count = %3d ", count); + span = ras.gray_spans; + for (n = 0; n < count; n++, span++) + fprintf(stderr, "[%d , %d..%d] : %d ", span->y, span->x, + span->x + span->len - 1, span->coverage); + fprintf(stderr, "\n"); + } + +#endif /* DEBUG_GRAYS */ + + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->y = (short)y; + span->len = (unsigned short)acount; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } +} + +static void gray_sweep(RAS_ARG) +{ + int yindex; + + if (ras.num_cells == 0) return; + + ras.num_gray_spans = 0; + + for (yindex = 0; yindex < ras.ycount; yindex++) { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + for (; cell != NULL; cell = cell->next) { + TPos area; + + if (cell->x > x && cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + cell->x - x); + + cover += cell->cover; + area = cover * (ONE_PIXEL * 2) - cell->area; + + if (area != 0 && cell->x >= 0) + gray_hline(RAS_VAR_ cell->x, yindex, area, 1); + + x = cell->x + 1; + } + + if (cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + ras.count_ex - x); + } + + if (ras.render_span && ras.num_gray_spans > 0) + ras.render_span(ras.num_gray_spans, ras.gray_spans, + ras.render_span_data); +} + +/*************************************************************************/ +/* */ +/* The following function should only compile in stand-alone mode, */ +/* i.e., when building this component without the rest of FreeType. */ +/* */ +/*************************************************************************/ + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Outline_Decompose */ +/* */ +/* */ +/* Walk over an outline's structure to decompose it into individual */ +/* segments and Bézier arcs. This function is also able to emit */ +/* `move to' and `close to' operations to indicate the start and end */ +/* of new contours in the outline. */ +/* */ +/* */ +/* outline :: A pointer to the source target. */ +/* */ +/* func_interface :: A table of `emitters', i.e., function pointers */ +/* called during decomposition to indicate path */ +/* operations. */ +/* */ +/* */ +/* user :: A typeless pointer which is passed to each */ +/* emitter during the decomposition. It can be */ +/* used to store the state during the */ +/* decomposition. */ +/* */ +/* */ +/* Error code. 0 means success. */ +/* */ +static int GD_FT_Outline_Decompose(const GD_FT_Outline* outline, + const GD_FT_Outline_Funcs* func_interface, + void* user) +{ +#undef SCALED +#define SCALED(x) (((TPos)(x) * (1L << shift)) - delta) + + GD_FT_Vector v_last; + GD_FT_Vector v_control; + GD_FT_Vector v_start; + + GD_FT_Vector* point; + GD_FT_Vector* limit; + char* tags; + + int error; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + char tag; /* current point's state */ + + int shift; + TPos delta; + + if (!outline || !func_interface) return GD_FT_THROW(Invalid_Argument); + + shift = func_interface->shift; + delta = func_interface->delta; + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + int last; /* index of last point in contour */ + + last = outline->contours[n]; + if (last < 0) goto Invalid_Outline; + limit = outline->points + last; + + v_start = outline->points[first]; + v_start.x = SCALED(v_start.x); + v_start.y = SCALED(v_start.y); + + v_last = outline->points[last]; + v_last.x = SCALED(v_last.x); + v_last.y = SCALED(v_last.y); + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = GD_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == GD_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == GD_FT_CURVE_TAG_CONIC) { + /* first point is conic control. Yes, this happens. */ + if (GD_FT_CURVE_TAG(outline->tags[last]) == GD_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = func_interface->move_to(&v_start, user); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = GD_FT_CURVE_TAG(tags[0]); + switch (tag) { + case GD_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + GD_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->line_to(&vec, user); + if (error) goto Exit; + continue; + } + + case GD_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = SCALED(point->x); + v_control.y = SCALED(point->y); + + Do_Conic: + if (point < limit) { + GD_FT_Vector vec; + GD_FT_Vector v_middle; + + point++; + tags++; + tag = GD_FT_CURVE_TAG(tags[0]); + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + if (tag == GD_FT_CURVE_TAG_ON) { + error = + func_interface->conic_to(&v_control, &vec, user); + if (error) goto Exit; + continue; + } + + if (tag != GD_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + func_interface->conic_to(&v_control, &v_middle, user); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = func_interface->conic_to(&v_control, &v_start, user); + goto Close; + + default: /* GD_FT_CURVE_TAG_CUBIC */ + { + GD_FT_Vector vec1, vec2; + + if (point + 1 > limit || + GD_FT_CURVE_TAG(tags[1]) != GD_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1.x = SCALED(point[-2].x); + vec1.y = SCALED(point[-2].y); + + vec2.x = SCALED(point[-1].x); + vec2.y = SCALED(point[-1].y); + + if (point <= limit) { + GD_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->cubic_to(&vec1, &vec2, &vec, user); + if (error) goto Exit; + continue; + } + + error = func_interface->cubic_to(&vec1, &vec2, &v_start, user); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = func_interface->line_to(&v_start, user); + + Close: + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return GD_FT_THROW(Invalid_Outline); +} + +typedef struct gray_TBand_ { + TPos min, max; + +} gray_TBand; + +GD_FT_DEFINE_OUTLINE_FUNCS(func_interface, + (GD_FT_Outline_MoveTo_Func)gray_move_to, + (GD_FT_Outline_LineTo_Func)gray_line_to, + (GD_FT_Outline_ConicTo_Func)gray_conic_to, + (GD_FT_Outline_CubicTo_Func)gray_cubic_to, 0, 0) + +static int gray_convert_glyph_inner(RAS_ARG) +{ + volatile int error = 0; + + if (ft_setjmp(ras.jump_buffer) == 0) { + if (ras.source && ras.source->decompose) { + error = ras.source->decompose(ras.source->source, &ras); + } else { + error = GD_FT_Outline_Decompose(&ras.outline, &func_interface, &ras); + } + if (!ras.invalid) gray_record_cell(RAS_VAR); + } else + error = GD_FT_THROW(Memory_Overflow); + + return error; +} + +static int gray_convert_glyph(RAS_ARG) +{ + gray_TBand bands[40]; + gray_TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + GD_FT_BBox* clip; + + /* Set up state in the raster object */ + gray_compute_cbox(RAS_VAR); + + /* clip to target bitmap, exit if nothing to do */ + clip = &ras.clip_box; + + if (ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || + ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax) + return 0; + + if (ras.min_ex < clip->xMin) ras.min_ex = clip->xMin; + if (ras.min_ey < clip->yMin) ras.min_ey = clip->yMin; + + if (ras.max_ex > clip->xMax) ras.max_ex = clip->xMax; + if (ras.max_ey > clip->yMax) ras.max_ey = clip->yMax; + + ras.count_ex = ras.max_ex - ras.min_ex; + ras.count_ey = ras.max_ey - ras.min_ey; + + /* set up vertical bands */ + num_bands = (int)((ras.max_ey - ras.min_ey) / ras.band_size); + if (num_bands == 0) num_bands = 1; + if (num_bands >= 39) num_bands = 39; + + ras.band_shoot = 0; + + min = ras.min_ey; + max_y = ras.max_ey; + + for (n = 0; n < num_bands; n++, min = max) { + max = min + ras.band_size; + if (n == num_bands - 1 || max > max_y) max = max_y; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + TPos bottom, top, middle; + int error; + + { + PCell cells_max; + int yindex; + long cell_start, cell_end, cell_mod; + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof(PCell) * ras.ycount; + cell_mod = cell_start % sizeof(TCell); + if (cell_mod > 0) cell_start += sizeof(TCell) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof(TCell); + + cells_max = (PCell)((char*)ras.buffer + cell_end); + ras.cells = (PCell)((char*)ras.buffer + cell_start); + if (ras.cells >= cells_max) goto ReduceBands; + + ras.max_cells = cells_max - ras.cells; + if (ras.max_cells < 2) goto ReduceBands; + + for (yindex = 0; yindex < ras.ycount; yindex++) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner(RAS_VAR); + + if (!error) { + gray_sweep(RAS_VAR); + band--; + continue; + } else if (error != ErrRaster_Memory_Overflow) + return 1; + + ReduceBands: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if (middle == bottom) { + return 1; + } + + if (bottom - top >= ras.band_size) ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if (ras.band_shoot > 8 && ras.band_size > 16) + ras.band_size = ras.band_size / 2; + + return 0; +} + +static int gray_raster_render(gray_PRaster raster, + const GD_FT_Raster_Params* params) +{ + GD_FT_UNUSED(raster); + const GD_FT_Outline* outline = (const GD_FT_Outline*)params->source; + + gray_TWorker worker[1]; + + TCell buffer[GD_FT_RENDER_POOL_SIZE / sizeof(TCell)]; + long buffer_size = sizeof(buffer); + int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + if (!outline) return GD_FT_THROW(Invalid_Outline); + + /* return immediately if the outline is empty */ + if (outline->n_points == 0 || outline->n_contours <= 0) return 0; + + if (!outline->contours || !outline->points) + return GD_FT_THROW(Invalid_Outline); + + if (outline->n_points != outline->contours[outline->n_contours - 1] + 1) + return GD_FT_THROW(Invalid_Outline); + + + + if (params->flags & GD_FT_RASTER_FLAG_CLIP) + ras.clip_box = params->clip_box; + else { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + gray_init_cells(RAS_VAR_ buffer, buffer_size); + + ras.outline = *outline; + ras.source = NULL; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = band_size; + ras.num_gray_spans = 0; + + ras.render_span = (GD_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + gray_convert_glyph(RAS_VAR); + if (ras.bound_right > ras.bound_left && ras.bound_bottom > ras.bound_top) { + params->bbox_cb(ras.bound_left, ras.bound_top, + ras.bound_right - ras.bound_left, + ras.bound_bottom - ras.bound_top + 1, params->user); + } + return 1; +} + +/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/ +/**** a static object. *****/ + +static int gray_raster_new(GD_FT_Raster* araster) +{ + static gray_TRaster the_raster; + + *araster = (GD_FT_Raster)&the_raster; + GD_FT_MEM_ZERO(&the_raster, sizeof(the_raster)); + + return 0; +} + +static void gray_raster_done(GD_FT_Raster raster) +{ + /* nothing */ + GD_FT_UNUSED(raster); +} + +static void gray_raster_reset(GD_FT_Raster raster, char* pool_base, + long pool_size) +{ + GD_FT_UNUSED(raster); + GD_FT_UNUSED(pool_base); + GD_FT_UNUSED(pool_size); +} + +GD_FT_DEFINE_RASTER_FUNCS(gd_ft_grays_raster, + + (GD_FT_Raster_New_Func)gray_raster_new, + (GD_FT_Raster_Reset_Func)gray_raster_reset, + (GD_FT_Raster_Render_Func)gray_raster_render, + (GD_FT_Raster_Done_Func)gray_raster_done) + +typedef struct gdPathRasterSource_ +{ + gdPathPtr path; + gdPathMatrixPtr matrix; +} gdPathRasterSource; + +static void gdpath_include_point(GD_FT_BBox *box, int *first, + const gdPointF *point) +{ + TPos x = (TPos)(point->x * 64.0); + TPos y = (TPos)(point->y * 64.0); + + if (*first) { + box->xMin = box->xMax = x; + box->yMin = box->yMax = y; + *first = 0; + return; + } + + if (x < box->xMin) box->xMin = x; + if (x > box->xMax) box->xMax = x; + if (y < box->yMin) box->yMin = y; + if (y > box->yMax) box->yMax = y; +} + +static GD_FT_Vector gdpath_vector(const gdPointF *point) +{ + GD_FT_Vector vector = { + (GD_FT_Pos)(point->x * 64.0), + (GD_FT_Pos)(point->y * 64.0) + }; + return vector; +} + +static int gdpath_get_cbox(void *source, GD_FT_BBox *box) +{ + const gdPathRasterSource *path_source = source; + gdPathPtr path = path_source->path; + gdPathMatrixPtr matrix = path_source->matrix; + unsigned int numElements = gdArrayNumElements(&path->elements); + unsigned int pointsIndex = 0; + int first = 1; + gdPointF p[3]; + + for (unsigned int i = 0; i < numElements; i++) { + gdPathOpsPtr element = (gdPathOpsPtr)gdArrayIndex(&path->elements, i); + gdPointFPtr point = gdArrayIndex(&path->points, pointsIndex); + + switch (*element) { + case gdPathOpsMoveTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + gdpath_include_point(box, &first, &p[0]); + pointsIndex += 1; + break; + case gdPathOpsLineTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + gdpath_include_point(box, &first, &p[0]); + pointsIndex += 1; + break; + case gdPathOpsCubicTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + point = gdArrayIndex(&path->points, pointsIndex + 2); + gdPathMatrixMapPoint(matrix, point, &p[2]); + gdpath_include_point(box, &first, &p[0]); + gdpath_include_point(box, &first, &p[1]); + gdpath_include_point(box, &first, &p[2]); + pointsIndex += 3; + break; + case gdPathOpsQuadTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + gdpath_include_point(box, &first, &p[0]); + gdpath_include_point(box, &first, &p[1]); + pointsIndex += 2; + break; + case gdPathOpsClose: + pointsIndex += 1; + break; + } + } + + if (first) { + return -1; + } + + return 0; +} + +static int gdpath_decompose(void *source, void *worker_data) +{ + const gdPathRasterSource *path_source = source; + gdPathPtr path = path_source->path; + gdPathMatrixPtr matrix = path_source->matrix; + gray_PWorker worker = worker_data; + unsigned int pointsIndex = 0; + gdPointF p[3]; + GD_FT_Vector contour_start = {0, 0}; + int contour_open = 0; + + for (unsigned int i = 0; i < gdArrayNumElements(&path->elements); i++) { + gdPathOpsPtr element = (gdPathOpsPtr)gdArrayIndex(&path->elements, i); + gdPointFPtr point = gdArrayIndex(&path->points, pointsIndex); + + switch (*element) { + case gdPathOpsMoveTo: + if (contour_open) + gray_line_to(&contour_start, worker); + gdPathMatrixMapPoint(matrix, point, &p[0]); + { + GD_FT_Vector v = gdpath_vector(&p[0]); + gray_move_to(&v, worker); + contour_start = v; + contour_open = 1; + } + pointsIndex += 1; + break; + case gdPathOpsLineTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + { + GD_FT_Vector v = gdpath_vector(&p[0]); + if (contour_open) { + gray_line_to(&v, worker); + } else { + gray_move_to(&v, worker); + contour_start = v; + contour_open = 1; + } + } + pointsIndex += 1; + break; + case gdPathOpsCubicTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + point = gdArrayIndex(&path->points, pointsIndex + 2); + gdPathMatrixMapPoint(matrix, point, &p[2]); + { + GD_FT_Vector v1 = gdpath_vector(&p[0]); + GD_FT_Vector v2 = gdpath_vector(&p[1]); + GD_FT_Vector v3 = gdpath_vector(&p[2]); + if (contour_open) { + gray_cubic_to(&v1, &v2, &v3, worker); + } else { + gray_move_to(&v3, worker); + contour_start = v3; + contour_open = 1; + } + } + pointsIndex += 3; + break; + case gdPathOpsQuadTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + { + GD_FT_Vector v1 = gdpath_vector(&p[0]); + GD_FT_Vector v2 = gdpath_vector(&p[1]); + if (contour_open) { + gray_conic_to(&v1, &v2, worker); + } else { + gray_move_to(&v2, worker); + contour_start = v2; + contour_open = 1; + } + } + pointsIndex += 2; + break; + case gdPathOpsClose: + if (contour_open) { + gdPathMatrixMapPoint(matrix, point, &p[0]); + { + GD_FT_Vector v = gdpath_vector(&p[0]); + gray_line_to(&v, worker); + } + contour_open = 0; + } + pointsIndex += 1; + break; + } + } + + if (contour_open) + gray_line_to(&contour_start, worker); + return 0; +} + +/* END */ + +GD_FT_Error gd_ft_raster_render_path( + const gdPathPtr path, + gdPathMatrixPtr matrix, + GD_FT_Raster_Params *params, + int outline_flags) +{ + if (!path || !params) + return GD_FT_THROW(Invalid_Argument); + +#ifndef GD_FT_STATIC_RASTER + gray_TWorker worker_storage; + gray_PWorker worker = &worker_storage; +#else + gray_TWorker save_ras = ras; +#endif + + gdPathRasterSource path_source = {path, matrix}; + gray_TSource source = {gdpath_get_cbox, gdpath_decompose, &path_source}; + ras.source = &source; + + params->flags |= GD_FT_RASTER_FLAG_DIRECT | GD_FT_RASTER_FLAG_AA; + if (params->flags & GD_FT_RASTER_FLAG_CLIP) { + ras.clip_box = params->clip_box; + } else { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + TCell buffer[GD_FT_RENDER_POOL_SIZE / sizeof(TCell)]; + long buffer_size = sizeof(buffer); + int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + gray_init_cells(RAS_VAR_ buffer, buffer_size); + ras.outline.n_contours = 0; + ras.outline.n_points = 0; + ras.outline.flags = outline_flags; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = band_size; + ras.num_gray_spans = 0; + ras.render_span = (GD_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + int error = gray_convert_glyph(RAS_VAR); + int bound_left = ras.bound_left; + int bound_top = ras.bound_top; + int bound_right = ras.bound_right; + int bound_bottom = ras.bound_bottom; + +#ifdef GD_FT_STATIC_RASTER + ras = save_ras; +#endif + + if (!error && params->bbox_cb && + bound_right > bound_left && bound_bottom > bound_top) { + params->bbox_cb(bound_left, bound_top, + bound_right - bound_left, + bound_bottom - bound_top + 1, params->user); + } + + if (error) + return error; + return gdArrayNumElements(&path->elements) > 0 ? 1 : 0; +} diff --git a/ext/gd/libgd/ftraster/gd_ft_raster.h b/ext/gd/libgd/ftraster/gd_ft_raster.h new file mode 100644 index 000000000000..ad831f0bf42b --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_raster.h @@ -0,0 +1,618 @@ +#ifndef GD_FT_IMG_H +#define GD_FT_IMG_H +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2010, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* Note: A `raster' is simply a scan-line converter, used to render */ + /* GD_FT_Outlines into GD_FT_Bitmaps. */ + /* */ + /*************************************************************************/ + +#include "gd_ft_types.h" + +typedef struct gdPathStruct *gdPathPtr; +typedef struct gdPathMatrixStruct *gdPathMatrixPtr; + + /*************************************************************************/ + /* */ + /* */ + /* FT_BBox */ + /* */ + /* */ + /* A structure used to hold an outline's bounding box, i.e., the */ + /* coordinates of its extrema in the horizontal and vertical */ + /* directions. */ + /* */ + /* */ + /* xMin :: The horizontal minimum (left-most). */ + /* */ + /* yMin :: The vertical minimum (bottom-most). */ + /* */ + /* xMax :: The horizontal maximum (right-most). */ + /* */ + /* yMax :: The vertical maximum (top-most). */ + /* */ + /* */ + /* The bounding box is specified with the coordinates of the lower */ + /* left and the upper right corner. In PostScript, those values are */ + /* often called (llx,lly) and (urx,ury), respectively. */ + /* */ + /* If `yMin' is negative, this value gives the glyph's descender. */ + /* Otherwise, the glyph doesn't descend below the baseline. */ + /* Similarly, if `ymax' is positive, this value gives the glyph's */ + /* ascender. */ + /* */ + /* `xMin' gives the horizontal distance from the glyph's origin to */ + /* the left edge of the glyph's bounding box. If `xMin' is negative, */ + /* the glyph extends to the left of the origin. */ + /* */ + typedef struct GD_FT_BBox_ + { + GD_FT_Pos xMin, yMin; + GD_FT_Pos xMax, yMax; + + } GD_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Outline */ +/* */ +/* */ +/* This structure is used to describe an outline to the scan-line */ +/* converter. */ +/* */ +/* */ +/* n_contours :: The number of contours in the outline. */ +/* */ +/* n_points :: The number of points in the outline. */ +/* */ +/* points :: A pointer to an array of `n_points' @GD_FT_Vector */ +/* elements, giving the outline's point coordinates. */ +/* */ +/* tags :: A pointer to an array of `n_points' chars, giving */ +/* each outline point's type. */ +/* */ +/* If bit~0 is unset, the point is `off' the curve, */ +/* i.e., a Bézier control point, while it is `on' if */ +/* set. */ +/* */ +/* Bit~1 is meaningful for `off' points only. If set, */ +/* it indicates a third-order Bézier arc control point; */ +/* and a second-order control point if unset. */ +/* */ +/* If bit~2 is set, bits 5-7 contain the drop-out mode */ +/* (as defined in the OpenType specification; the value */ +/* is the same as the argument to the SCANMODE */ +/* instruction). */ +/* */ +/* Bits 3 and~4 are reserved for internal purposes. */ +/* */ +/* contours :: An array of `n_contours' shorts, giving the end */ +/* point of each contour within the outline. For */ +/* example, the first contour is defined by the points */ +/* `0' to `contours[0]', the second one is defined by */ +/* the points `contours[0]+1' to `contours[1]', etc. */ +/* */ +/* flags :: A set of bit flags used to characterize the outline */ +/* and give hints to the scan-converter and hinter on */ +/* how to convert/grid-fit it. See @GD_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct GD_FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + GD_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} GD_FT_Outline; + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_OUTLINE_FLAGS */ + /* */ + /* */ + /* A list of bit-field constants use for the flags in an outline's */ + /* `flags' field. */ + /* */ + /* */ + /* GD_FT_OUTLINE_NONE :: */ + /* Value~0 is reserved. */ + /* */ + /* GD_FT_OUTLINE_OWNER :: */ + /* If set, this flag indicates that the outline's field arrays */ + /* (i.e., `points', `flags', and `contours') are `owned' by the */ + /* outline object, and should thus be freed when it is destroyed. */ + /* */ + /* GD_FT_OUTLINE_EVEN_ODD_FILL :: */ + /* By default, outlines are filled using the non-zero winding rule. */ + /* If set to 1, the outline will be filled using the even-odd fill */ + /* rule (only works with the smooth rasterizer). */ + /* */ + /* GD_FT_OUTLINE_REVERSE_FILL :: */ + /* By default, outside contours of an outline are oriented in */ + /* clock-wise direction, as defined in the TrueType specification. */ + /* This flag is set if the outline uses the opposite direction */ + /* (typically for Type~1 fonts). This flag is ignored by the scan */ + /* converter. */ + /* */ + /* */ + /* */ + /* There exists a second mechanism to pass the drop-out mode to the */ + /* B/W rasterizer; see the `tags' field in @GD_FT_Outline. */ + /* */ + /* Please refer to the description of the `SCANTYPE' instruction in */ + /* the OpenType specification (in file `ttinst1.doc') how simple */ + /* drop-outs, smart drop-outs, and stubs are defined. */ + /* */ +#define GD_FT_OUTLINE_NONE 0x0 +#define GD_FT_OUTLINE_OWNER 0x1 +#define GD_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define GD_FT_OUTLINE_REVERSE_FILL 0x4 + + /* */ + +#define GD_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define GD_FT_CURVE_TAG_ON 1 +#define GD_FT_CURVE_TAG_CONIC 0 +#define GD_FT_CURVE_TAG_CUBIC 2 + + +#define GD_FT_Curve_Tag_On GD_FT_CURVE_TAG_ON +#define GD_FT_Curve_Tag_Conic GD_FT_CURVE_TAG_CONIC +#define GD_FT_Curve_Tag_Cubic GD_FT_CURVE_TAG_CUBIC + + /*************************************************************************/ + /* */ + /* A raster is a scan converter, in charge of rendering an outline into */ + /* a a bitmap. This section contains the public API for rasters. */ + /* */ + /* Note that in FreeType 2, all rasters are now encapsulated within */ + /* specific modules called `renderers'. See `ftrender.h' for more */ + /* details on renderers. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster */ + /* */ + /* */ + /* A handle (pointer) to a raster object. Each object can be used */ + /* independently to convert an outline into a bitmap or pixmap. */ + /* */ + typedef struct GD_FT_RasterRec_* GD_FT_Raster; + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Span */ + /* */ + /* */ + /* A structure used to model a single span of gray (or black) pixels */ + /* when rendering a monochrome or anti-aliased bitmap. */ + /* */ + /* */ + /* x :: The span's horizontal start position. */ + /* */ + /* len :: The span's length in pixels. */ + /* */ + /* coverage :: The span color/coverage, ranging from 0 (background) */ + /* to 255 (foreground). Only used for anti-aliased */ + /* rendering. */ + /* */ + /* */ + /* This structure is used by the span drawing callback type named */ + /* @GD_FT_SpanFunc that takes the y~coordinate of the span as a */ + /* parameter. */ + /* */ + /* The coverage value is always between 0 and 255. If you want less */ + /* gray values, the callback function has to reduce them. */ + /* */ + typedef struct GD_FT_Span_ + { + short x; + short y; + unsigned short len; + unsigned char coverage; + + } GD_FT_Span; + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_SpanFunc */ + /* */ + /* */ + /* A function used as a call-back by the anti-aliased renderer in */ + /* order to let client applications draw themselves the gray pixel */ + /* spans on each scan line. */ + /* */ + /* */ + /* y :: The scanline's y~coordinate. */ + /* */ + /* count :: The number of spans to draw on this scanline. */ + /* */ + /* spans :: A table of `count' spans to draw on the scanline. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* */ + /* This callback allows client applications to directly render the */ + /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ + /* */ + /* This can be used to write anti-aliased outlines directly to a */ + /* given background bitmap, and even perform translucency. */ + /* */ + /* Note that the `count' field cannot be greater than a fixed value */ + /* defined by the `GD_FT_MAX_GRAY_SPANS' configuration macro in */ + /* `ftoption.h'. By default, this value is set to~32, which means */ + /* that if there are more than 32~spans on a given scanline, the */ + /* callback is called several times with the same `y' parameter in */ + /* order to draw all callbacks. */ + /* */ + /* Otherwise, the callback is only called once per scan-line, and */ + /* only for those scanlines that do have `gray' pixels on them. */ + /* */ + typedef void + (*GD_FT_SpanFunc)( int count, + const GD_FT_Span* spans, + void* user ); + + typedef void + (*GD_FT_BboxFunc)( int x, int y, int w, int h, + void* user); + +#define GD_FT_Raster_Span_Func GD_FT_SpanFunc + + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_RASTER_FLAG_XXX */ + /* */ + /* */ + /* A list of bit flag constants as used in the `flags' field of a */ + /* @GD_FT_Raster_Params structure. */ + /* */ + /* */ + /* GD_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ + /* */ + /* GD_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ + /* anti-aliased glyph image should be */ + /* generated. Otherwise, it will be */ + /* monochrome (1-bit). */ + /* */ + /* GD_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ + /* rendering. In this mode, client */ + /* applications must provide their own span */ + /* callback. This lets them directly */ + /* draw or compose over an existing bitmap. */ + /* If this bit is not set, the target */ + /* pixmap's buffer _must_ be zeroed before */ + /* rendering. */ + /* */ + /* Note that for now, direct rendering is */ + /* only possible with anti-aliased glyphs. */ + /* */ + /* GD_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ + /* rendering mode. If set, the output will */ + /* be clipped to a box specified in the */ + /* `clip_box' field of the */ + /* @GD_FT_Raster_Params structure. */ + /* */ + /* Note that by default, the glyph bitmap */ + /* is clipped to the target pixmap, except */ + /* in direct rendering mode where all spans */ + /* are generated if no clipping box is set. */ + /* */ +#define GD_FT_RASTER_FLAG_DEFAULT 0x0 +#define GD_FT_RASTER_FLAG_AA 0x1 +#define GD_FT_RASTER_FLAG_DIRECT 0x2 +#define GD_FT_RASTER_FLAG_CLIP 0x4 + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_Params */ + /* */ + /* */ + /* A structure to hold the arguments used by a raster's render */ + /* function. */ + /* */ + /* */ + /* target :: The target bitmap. */ + /* */ + /* source :: A pointer to the source glyph image (e.g., an */ + /* @GD_FT_Outline). */ + /* */ + /* flags :: The rendering flags. */ + /* */ + /* gray_spans :: The gray span drawing callback. */ + /* */ + /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ + /* */ + /* bit_test :: The bit test callback. UNIMPLEMENTED! */ + /* */ + /* bit_set :: The bit set callback. UNIMPLEMENTED! */ + /* */ + /* user :: User-supplied data that is passed to each drawing */ + /* callback. */ + /* */ + /* clip_box :: An optional clipping box. It is only used in */ + /* direct rendering mode. Note that coordinates here */ + /* should be expressed in _integer_ pixels (and not in */ + /* 26.6 fixed-point units). */ + /* */ + /* */ + /* An anti-aliased glyph bitmap is drawn if the @GD_FT_RASTER_FLAG_AA */ + /* bit flag is set in the `flags' field, otherwise a monochrome */ + /* bitmap is generated. */ + /* */ + /* If the @GD_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ + /* raster will call the `gray_spans' callback to draw gray pixel */ + /* spans, in the case of an aa glyph bitmap, it will call */ + /* `black_spans', and `bit_test' and `bit_set' in the case of a */ + /* monochrome bitmap. This allows direct composition over a */ + /* pre-existing bitmap through user-provided callbacks to perform the */ + /* span drawing/composition. */ + /* */ + /* Note that the `bit_test' and `bit_set' callbacks are required when */ + /* rendering a monochrome bitmap, as they are crucial to implement */ + /* correct drop-out control as defined in the TrueType specification. */ + /* */ + typedef struct GD_FT_Raster_Params_ + { + const void* source; + int flags; + GD_FT_SpanFunc gray_spans; + GD_FT_BboxFunc bbox_cb; + void* user; + GD_FT_BBox clip_box; + + } GD_FT_Raster_Params; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +GD_FT_Error +GD_FT_Outline_Check( GD_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Outline_Get_CBox */ +/* */ +/* */ +/* Return an outline's `control box'. The control box encloses all */ +/* the outline's points, including Bézier control points. Though it */ +/* coincides with the exact bounding box for most glyphs, it can be */ +/* slightly larger in some situations (like when rotating an outline */ +/* that contains Bézier outside arcs). */ +/* */ +/* Computing the control box is very fast, while getting the bounding */ +/* box can take much more time as it needs to walk over all segments */ +/* and arcs in the outline. To get the latter, you can use the */ +/* `ftbbox' component, which is dedicated to this single task. */ +/* */ +/* */ +/* outline :: A pointer to the source outline descriptor. */ +/* */ +/* */ +/* acbox :: The outline's control box. */ +/* */ +/* */ +/* See @GD_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +GD_FT_Outline_Get_CBox( const GD_FT_Outline* outline, + GD_FT_BBox *acbox ); + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_NewFunc */ + /* */ + /* */ + /* A function used to create a new raster object. */ + /* */ + /* */ + /* memory :: A handle to the memory allocator. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The `memory' parameter is a typeless pointer in order to avoid */ + /* un-wanted dependencies on the rest of the FreeType code. In */ + /* practice, it is an @GD_FT_Memory object, i.e., a handle to the */ + /* standard FreeType memory allocator. However, this field can be */ + /* completely ignored by a given raster implementation. */ + /* */ + typedef int + (*GD_FT_Raster_NewFunc)( GD_FT_Raster* raster ); + +#define GD_FT_Raster_New_Func GD_FT_Raster_NewFunc + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_DoneFunc */ + /* */ + /* */ + /* A function used to destroy a given raster object. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + typedef void + (*GD_FT_Raster_DoneFunc)( GD_FT_Raster raster ); + +#define GD_FT_Raster_Done_Func GD_FT_Raster_DoneFunc + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_ResetFunc */ + /* */ + /* */ + /* FreeType provides an area of memory called the `render pool', */ + /* available to all registered rasters. This pool can be freely used */ + /* during a given scan-conversion but is shared by all rasters. Its */ + /* content is thus transient. */ + /* */ + /* This function is called each time the render pool changes, or just */ + /* after a new raster object is created. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* pool_base :: The address in memory of the render pool. */ + /* */ + /* pool_size :: The size in bytes of the render pool. */ + /* */ + /* */ + /* Rasters can ignore the render pool and rely on dynamic memory */ + /* allocation if they want to (a handle to the memory allocator is */ + /* passed to the raster constructor). However, this is not */ + /* recommended for efficiency purposes. */ + /* */ + typedef void + (*GD_FT_Raster_ResetFunc)( GD_FT_Raster raster, + unsigned char* pool_base, + unsigned long pool_size ); + +#define GD_FT_Raster_Reset_Func GD_FT_Raster_ResetFunc + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_RenderFunc */ + /* */ + /* */ + /* Invoke a given raster to scan-convert a given glyph image into a */ + /* target bitmap. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + /* params :: A pointer to an @GD_FT_Raster_Params structure used to */ + /* store the rendering parameters. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The exact format of the source image depends on the raster's glyph */ + /* format defined in its @GD_FT_Raster_Funcs structure. It can be an */ + /* @GD_FT_Outline or anything else in order to support a large array of */ + /* glyph formats. */ + /* */ + /* Note also that the render function can fail and return a */ + /* `GD_FT_Err_Unimplemented_Feature' error code if the raster used does */ + /* not support direct composition. */ + /* */ + /* XXX: For now, the standard raster doesn't support direct */ + /* composition but this should change for the final release (see */ + /* the files `demos/src/ftgrays.c' and `demos/src/ftgrays2.c' */ + /* for examples of distinct implementations that support direct */ + /* composition). */ + /* */ + typedef int + (*GD_FT_Raster_RenderFunc)( GD_FT_Raster raster, + const GD_FT_Raster_Params* params ); + +#define GD_FT_Raster_Render_Func GD_FT_Raster_RenderFunc + + + /*************************************************************************/ + /* */ + /* */ + /* GD_FT_Raster_Funcs */ + /* */ + /* */ + /* A structure used to describe a given raster class to the library. */ + /* */ + /* */ + /* glyph_format :: The supported glyph format for this raster. */ + /* */ + /* raster_new :: The raster constructor. */ + /* */ + /* raster_reset :: Used to reset the render pool within the raster. */ + /* */ + /* raster_render :: A function to render a glyph into a given bitmap. */ + /* */ + /* raster_done :: The raster destructor. */ + /* */ + typedef struct GD_FT_Raster_Funcs_ + { + GD_FT_Raster_NewFunc raster_new; + GD_FT_Raster_ResetFunc raster_reset; + GD_FT_Raster_RenderFunc raster_render; + GD_FT_Raster_DoneFunc raster_done; + + } GD_FT_Raster_Funcs; + + +extern const GD_FT_Raster_Funcs gd_ft_grays_raster; + +/* Direct path rendering without FT_Outline intermediate */ +GD_FT_Error gd_ft_raster_render_path( + const gdPathPtr path, + gdPathMatrixPtr matrix, + GD_FT_Raster_Params *params, + int outline_flags +); + +#endif // GD_FT_IMG_H diff --git a/ext/gd/libgd/ftraster/gd_ft_stroker.c b/ext/gd/libgd/ftraster/gd_ft_stroker.c new file mode 100644 index 000000000000..34fcb424250b --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_stroker.c @@ -0,0 +1,1934 @@ + +/***************************************************************************/ +/* */ +/* ftstroke.c */ +/* */ +/* FreeType path stroker (body). */ +/* */ +/* Copyright 2002-2006, 2008-2011, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "gd_ft_stroker.h" +#include +#include +#include +#include "gd_ft_math.h" + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define GD_FT_SMALL_CONIC_THRESHOLD (GD_FT_ANGLE_PI / 6) +#define GD_FT_SMALL_CUBIC_THRESHOLD (GD_FT_ANGLE_PI / 8) + +#define GD_FT_EPSILON 2 + +#define GD_FT_IS_SMALL(x) ((x) > -GD_FT_EPSILON && (x) < GD_FT_EPSILON) + +static inline GD_FT_Pos ft_pos_abs(GD_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(GD_FT_Vector* base) +{ + GD_FT_Pos a, b; + + base[4].x = base[2].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + base[3].x = b >> 1; + base[2].x = ( a + b ) >> 2; + base[1].x = a >> 1; + + base[4].y = base[2].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + base[3].y = b >> 1; + base[2].y = ( a + b ) >> 2; + base[1].y = a >> 1; +} + +static GD_FT_Bool ft_conic_is_small_enough(GD_FT_Vector* base, + GD_FT_Angle* angle_in, + GD_FT_Angle* angle_out) +{ + GD_FT_Vector d1, d2; + GD_FT_Angle theta; + GD_FT_Int close1, close2; + + d1.x = base[1].x - base[2].x; + d1.y = base[1].y - base[2].y; + d2.x = base[0].x - base[1].x; + d2.y = base[0].y - base[1].y; + + close1 = GD_FT_IS_SMALL(d1.x) && GD_FT_IS_SMALL(d1.y); + close2 = GD_FT_IS_SMALL(d2.x) && GD_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = GD_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = GD_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = GD_FT_Atan2(d1.x, d1.y); + *angle_out = GD_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(GD_FT_Angle_Diff(*angle_in, *angle_out)); + + return GD_FT_BOOL(theta < GD_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(GD_FT_Vector* base) +{ + GD_FT_Pos a, b, c; + + base[6].x = base[3].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + c = base[2].x + base[3].x; + base[5].x = c >> 1; + c += b; + base[4].x = c >> 2; + base[1].x = a >> 1; + a += b; + base[2].x = a >> 2; + base[3].x = ( a + c ) >> 3; + + base[6].y = base[3].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + c = base[2].y + base[3].y; + base[5].y = c >> 1; + c += b; + base[4].y = c >> 2; + base[1].y = a >> 1; + a += b; + base[2].y = a >> 2; + base[3].y = ( a + c ) >> 3; +} + +/* Return the average of `angle1' and `angle2'. */ +/* This gives correct result even if `angle1' and `angle2' */ +/* have opposite signs. */ +static GD_FT_Angle ft_angle_mean(GD_FT_Angle angle1, GD_FT_Angle angle2) +{ + return angle1 + GD_FT_Angle_Diff(angle1, angle2) / 2; +} + +static GD_FT_Bool ft_cubic_is_small_enough(GD_FT_Vector* base, + GD_FT_Angle* angle_in, + GD_FT_Angle* angle_mid, + GD_FT_Angle* angle_out) +{ + GD_FT_Vector d1, d2, d3; + GD_FT_Angle theta1, theta2; + GD_FT_Int close1, close2, close3; + + d1.x = base[2].x - base[3].x; + d1.y = base[2].y - base[3].y; + d2.x = base[1].x - base[2].x; + d2.y = base[1].y - base[2].y; + d3.x = base[0].x - base[1].x; + d3.y = base[0].y - base[1].y; + + close1 = GD_FT_IS_SMALL(d1.x) && GD_FT_IS_SMALL(d1.y); + close2 = GD_FT_IS_SMALL(d2.x) && GD_FT_IS_SMALL(d2.y); + close3 = GD_FT_IS_SMALL(d3.x) && GD_FT_IS_SMALL(d3.y); + + if (close1) { + if (close2) { + if (close3) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else /* !close3 */ + { + *angle_in = *angle_mid = *angle_out = GD_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = GD_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = GD_FT_Atan2(d2.x, d2.y); + *angle_out = GD_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = GD_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = GD_FT_Atan2(d1.x, d1.y); + *angle_out = GD_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = GD_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = GD_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = GD_FT_Atan2(d1.x, d1.y); + *angle_mid = GD_FT_Atan2(d2.x, d2.y); + *angle_out = GD_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(GD_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(GD_FT_Angle_Diff(*angle_mid, *angle_out)); + + return GD_FT_BOOL(theta1 < GD_FT_SMALL_CUBIC_THRESHOLD && + theta2 < GD_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum GD_FT_StrokeTags_ { + GD_FT_STROKE_TAG_ON = 1, /* on-curve point */ + GD_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + GD_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + GD_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} GD_FT_StrokeTags; + +#define GD_FT_STROKE_TAG_BEGIN_END \ + (GD_FT_STROKE_TAG_BEGIN | GD_FT_STROKE_TAG_END) + +typedef struct GD_FT_StrokeBorderRec_ { + GD_FT_UInt num_points; + GD_FT_UInt max_points; + GD_FT_Vector* points; + GD_FT_Byte* tags; + GD_FT_Bool movable; /* TRUE for ends of lineto borders */ + GD_FT_Int start; /* index of current sub-path start point */ + GD_FT_Bool valid; + +} GD_FT_StrokeBorderRec, *GD_FT_StrokeBorder; + +GD_FT_Error GD_FT_Outline_Check(GD_FT_Outline* outline) +{ + if (outline) { + GD_FT_Int n_points = outline->n_points; + GD_FT_Int n_contours = outline->n_contours; + GD_FT_Int end0, end; + GD_FT_Int n; + + /* empty glyph? */ + if (n_points == 0 && n_contours == 0) return 0; + + /* check point and contour counts */ + if (n_points <= 0 || n_contours <= 0) goto Bad; + + end0 = end = -1; + for (n = 0; n < n_contours; n++) { + end = outline->contours[n]; + + /* note that we don't accept empty contours */ + if (end <= end0 || end >= n_points) goto Bad; + + end0 = end; + } + + if (end != n_points - 1) goto Bad; + + /* XXX: check the tags array */ + return 0; + } + +Bad: + return -1; // GD_FT_THROW( Invalid_Argument ); +} + +void GD_FT_Outline_Get_CBox(const GD_FT_Outline* outline, GD_FT_BBox* acbox) +{ + GD_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + GD_FT_Vector* vec = outline->points; + GD_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + GD_FT_Pos x, y; + + x = vec->x; + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + + y = vec->y; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + acbox->xMin = xMin; + acbox->xMax = xMax; + acbox->yMin = yMin; + acbox->yMax = yMax; + } +} + +static GD_FT_Error ft_stroke_border_grow(GD_FT_StrokeBorder border, + GD_FT_UInt new_points) +{ + GD_FT_UInt old_max = border->max_points; + GD_FT_UInt new_max = border->num_points + new_points; + GD_FT_Error error = 0; + + if (new_max > old_max) { + GD_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (GD_FT_Vector*)realloc(border->points, + cur_max * sizeof(GD_FT_Vector)); + border->tags = + (GD_FT_Byte*)realloc(border->tags, cur_max * sizeof(GD_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(GD_FT_StrokeBorder border, + GD_FT_Bool reverse) +{ + GD_FT_UInt start = border->start; + GD_FT_UInt count = border->num_points; + + assert(border->start >= 0); + + /* don't record empty paths! */ + if (count <= start + 1U) + border->num_points = start; + else { + /* copy the last point to the start of this sub-path, since */ + /* it contains the `adjusted' starting coordinates */ + border->num_points = --count; + border->points[start] = border->points[count]; + + if (reverse) { + /* reverse the points */ + { + GD_FT_Vector* vec1 = border->points + start + 1; + GD_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + GD_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + GD_FT_Byte* tag1 = border->tags + start + 1; + GD_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + GD_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= GD_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= GD_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static GD_FT_Error ft_stroke_border_lineto(GD_FT_StrokeBorder border, + GD_FT_Vector* to, GD_FT_Bool movable) +{ + GD_FT_Error error = 0; + + assert(border->start >= 0); + + if (border->movable) { + /* move last point */ + border->points[border->num_points - 1] = *to; + } else { + /* don't add zero-length lineto */ + if (border->num_points > 0 && + GD_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + GD_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) + return error; + + /* add one point */ + error = ft_stroke_border_grow(border, 1); + if (!error) { + GD_FT_Vector* vec = border->points + border->num_points; + GD_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = GD_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static GD_FT_Error ft_stroke_border_conicto(GD_FT_StrokeBorder border, + GD_FT_Vector* control, + GD_FT_Vector* to) +{ + GD_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + GD_FT_Vector* vec = border->points + border->num_points; + GD_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = GD_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static GD_FT_Error ft_stroke_border_cubicto(GD_FT_StrokeBorder border, + GD_FT_Vector* control1, + GD_FT_Vector* control2, + GD_FT_Vector* to) +{ + GD_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + GD_FT_Vector* vec = border->points + border->num_points; + GD_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = GD_FT_STROKE_TAG_CUBIC; + tag[1] = GD_FT_STROKE_TAG_CUBIC; + tag[2] = GD_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define GD_FT_ARC_CUBIC_ANGLE (GD_FT_ANGLE_PI / 2) + + +static GD_FT_Error +ft_stroke_border_arcto( GD_FT_StrokeBorder border, + GD_FT_Vector* center, + GD_FT_Fixed radius, + GD_FT_Angle angle_start, + GD_FT_Angle angle_diff ) +{ + GD_FT_Fixed coef; + GD_FT_Vector a0, a1, a2, a3; + GD_FT_Int i, arcs = 1; + GD_FT_Error error = 0; + + + /* number of cubic arcs to draw */ + while ( angle_diff > GD_FT_ARC_CUBIC_ANGLE * arcs || + -angle_diff > GD_FT_ARC_CUBIC_ANGLE * arcs ) + arcs++; + + /* control tangents */ + coef = GD_FT_Tan( angle_diff / ( 4 * arcs ) ); + coef += coef / 3; + + /* compute start and first control point */ + GD_FT_Vector_From_Polar( &a0, radius, angle_start ); + a1.x = GD_FT_MulFix( -a0.y, coef ); + a1.y = GD_FT_MulFix( a0.x, coef ); + + a0.x += center->x; + a0.y += center->y; + a1.x += a0.x; + a1.y += a0.y; + + for ( i = 1; i <= arcs; i++ ) + { + /* compute end and second control point */ + GD_FT_Vector_From_Polar( &a3, radius, + angle_start + i * angle_diff / arcs ); + a2.x = GD_FT_MulFix( a3.y, coef ); + a2.y = GD_FT_MulFix( -a3.x, coef ); + + a3.x += center->x; + a3.y += center->y; + a2.x += a3.x; + a2.y += a3.y; + + /* add cubic arc */ + error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); + if ( error ) + break; + + /* a0 = a3; */ + a1.x = a3.x - a2.x + a3.x; + a1.y = a3.y - a2.y + a3.y; + } + + return error; +} + +static GD_FT_Error ft_stroke_border_moveto(GD_FT_StrokeBorder border, + GD_FT_Vector* to) +{ + /* close current open path if any ? */ + if (border->start >= 0) ft_stroke_border_close(border, FALSE); + + border->start = border->num_points; + border->movable = FALSE; + + return ft_stroke_border_lineto(border, to, FALSE); +} + +static void ft_stroke_border_init(GD_FT_StrokeBorder border) +{ + border->points = NULL; + border->tags = NULL; + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_reset(GD_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(GD_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static GD_FT_Error ft_stroke_border_get_counts(GD_FT_StrokeBorder border, + GD_FT_UInt* anum_points, + GD_FT_UInt* anum_contours) +{ + GD_FT_Error error = 0; + GD_FT_UInt num_points = 0; + GD_FT_UInt num_contours = 0; + + GD_FT_UInt count = border->num_points; + GD_FT_Vector* point = border->points; + GD_FT_Byte* tags = border->tags; + GD_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & GD_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & GD_FT_STROKE_TAG_END) { + in_contour = 0; + num_contours++; + } + } + + if (in_contour != 0) goto Fail; + + border->valid = TRUE; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; + +Fail: + num_points = 0; + num_contours = 0; + goto Exit; +} + +static void ft_stroke_border_export(GD_FT_StrokeBorder border, + GD_FT_Outline* outline) +{ + /* copy point locations */ + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(GD_FT_Vector)); + + /* copy tags */ + { + GD_FT_UInt count = border->num_points; + GD_FT_Byte* read = border->tags; + GD_FT_Byte* write = (GD_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & GD_FT_STROKE_TAG_ON) + *write = GD_FT_CURVE_TAG_ON; + else if (*read & GD_FT_STROKE_TAG_CUBIC) + *write = GD_FT_CURVE_TAG_CUBIC; + else + *write = GD_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + { + GD_FT_UInt count = border->num_points; + GD_FT_Byte* tags = border->tags; + GD_FT_Short* write = outline->contours + outline->n_contours; + GD_FT_Short idx = (GD_FT_Short)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & GD_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (short)(outline->n_points + border->num_points); + + assert(GD_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define GD_FT_SIDE_TO_ROTATE(s) (GD_FT_ANGLE_PI2 - (s)*GD_FT_ANGLE_PI) + +typedef struct GD_FT_StrokerRec_ { + GD_FT_Angle angle_in; /* direction into curr join */ + GD_FT_Angle angle_out; /* direction out of join */ + GD_FT_Vector center; /* current position */ + GD_FT_Fixed line_length; /* length of last lineto */ + GD_FT_Bool first_point; /* is this the start? */ + GD_FT_Bool subpath_open; /* is the subpath open? */ + GD_FT_Angle subpath_angle; /* subpath start direction */ + GD_FT_Vector subpath_start; /* subpath start position */ + GD_FT_Fixed subpath_line_length; /* subpath start lineto len */ + GD_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + GD_FT_Stroker_LineCap line_cap; + GD_FT_Stroker_LineJoin line_join; + GD_FT_Stroker_LineJoin line_join_saved; + GD_FT_Fixed miter_limit; + GD_FT_Fixed radius; + + GD_FT_StrokeBorderRec borders[2]; +} GD_FT_StrokerRec; + +/* documentation is in ftstroke.h */ +GD_FT_Error GD_FT_Stroker_New(GD_FT_Stroker* astroker) { + GD_FT_Error error = 0; /* assigned in SW_FT_NEW */ + GD_FT_Stroker stroker = NULL; + + stroker = (GD_FT_StrokerRec *) calloc(1, sizeof(GD_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void GD_FT_Stroker_Rewind(GD_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_reset(&stroker->borders[0]); + ft_stroke_border_reset(&stroker->borders[1]); + } +} + +/* documentation is in ftstroke.h */ + +void GD_FT_Stroker_Set(GD_FT_Stroker stroker, GD_FT_Fixed radius, + GD_FT_Stroker_LineCap line_cap, + GD_FT_Stroker_LineJoin line_join, + GD_FT_Fixed miter_limit) +{ + stroker->radius = radius; + stroker->line_cap = line_cap; + stroker->line_join = line_join; + stroker->miter_limit = miter_limit; + + /* ensure miter limit has sensible value */ + if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; + + /* save line join style: */ + /* line join style can be temporarily changed when stroking curves */ + stroker->line_join_saved = line_join; + + GD_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void GD_FT_Stroker_Done(GD_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_done(&stroker->borders[0]); + ft_stroke_border_done(&stroker->borders[1]); + + free(stroker); + } +} + +/* create a circular arc at a corner or cap */ +static GD_FT_Error ft_stroker_arcto(GD_FT_Stroker stroker, GD_FT_Int side) +{ + GD_FT_Angle total, rotate; + GD_FT_Fixed radius = stroker->radius; + GD_FT_Error error = 0; + GD_FT_StrokeBorder border = stroker->borders + side; + + rotate = GD_FT_SIDE_TO_ROTATE(side); + + total = GD_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == GD_FT_ANGLE_PI) total = -rotate * 2; + + error = ft_stroke_border_arcto(border, &stroker->center, radius, + stroker->angle_in + rotate, total); + border->movable = FALSE; + return error; +} + +/* add a cap at the end of an opened path */ +static GD_FT_Error +ft_stroker_cap(GD_FT_Stroker stroker, + GD_FT_Angle angle, + GD_FT_Int side) +{ + GD_FT_Error error = 0; + + if (stroker->line_cap == GD_FT_STROKER_LINECAP_ROUND) + { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + GD_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } + else + { + /* add a square or butt cap */ + GD_FT_Vector middle, delta; + GD_FT_Fixed radius = stroker->radius; + GD_FT_StrokeBorder border = stroker->borders + side; + + /* compute middle point and first angle point */ + GD_FT_Vector_From_Polar( &middle, radius, angle ); + delta.x = side ? middle.y : -middle.y; + delta.y = side ? -middle.x : middle.x; + + if ( stroker->line_cap == GD_FT_STROKER_LINECAP_SQUARE ) + { + middle.x += stroker->center.x; + middle.y += stroker->center.y; + } + else /* GD_FT_STROKER_LINECAP_BUTT */ + { + middle.x = stroker->center.x; + middle.y = stroker->center.y; + } + + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + +Exit: + return error; +} + +/* process an inside corner, i.e. compute intersection */ +static GD_FT_Error ft_stroker_inside(GD_FT_Stroker stroker, GD_FT_Int side, + GD_FT_Fixed line_length) +{ + GD_FT_StrokeBorder border = stroker->borders + side; + GD_FT_Angle phi, theta, rotate; + GD_FT_Fixed length; + GD_FT_Vector sigma, delta; + GD_FT_Error error = 0; + GD_FT_Bool intersect; /* use intersection of lines? */ + + rotate = GD_FT_SIDE_TO_ROTATE(side); + + theta = GD_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; + + /* Only intersect borders if between two lineto's and both */ + /* lines are long enough (line_length is zero for curves). */ + if (!border->movable || line_length == 0 || + theta > 0x59C000 || theta < -0x59C000 ) + intersect = FALSE; + else { + /* compute minimum required length of lines */ + GD_FT_Fixed min_length; + + + GD_FT_Vector_Unit( &sigma, theta ); + min_length = + ft_pos_abs( GD_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); + + intersect = GD_FT_BOOL( min_length && + stroker->line_length >= min_length && + line_length >= min_length ); + } + + if (!intersect) { + GD_FT_Vector_From_Polar(&delta, stroker->radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + } else { + /* compute median angle */ + phi = stroker->angle_in + theta + rotate; + + length = GD_FT_DivFix( stroker->radius, sigma.x ); + + GD_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + } + + error = ft_stroke_border_lineto(border, &delta, FALSE); + + return error; +} + + /* process an outside corner, i.e. compute bevel/miter/round */ +static GD_FT_Error +ft_stroker_outside( GD_FT_Stroker stroker, + GD_FT_Int side, + GD_FT_Fixed line_length ) +{ + GD_FT_StrokeBorder border = stroker->borders + side; + GD_FT_Error error; + GD_FT_Angle rotate; + + + if ( stroker->line_join == GD_FT_STROKER_LINEJOIN_ROUND ) + error = ft_stroker_arcto( stroker, side ); + else + { + /* this is a mitered (pointed) or beveled (truncated) corner */ + GD_FT_Fixed radius = stroker->radius; + GD_FT_Vector sigma; + GD_FT_Angle theta = 0, phi = 0; + GD_FT_Bool bevel, fixed_bevel; + + + rotate = GD_FT_SIDE_TO_ROTATE( side ); + + bevel = + GD_FT_BOOL( stroker->line_join == GD_FT_STROKER_LINEJOIN_BEVEL ); + + fixed_bevel = + GD_FT_BOOL( stroker->line_join != GD_FT_STROKER_LINEJOIN_MITER_VARIABLE ); + + /* check miter limit first */ + if ( !bevel ) + { + theta = GD_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; + + if ( theta == GD_FT_ANGLE_PI2 ) + theta = -rotate; + + phi = stroker->angle_in + theta + rotate; + + GD_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); + + /* is miter limit exceeded? */ + if ( sigma.x < 0x10000L ) + { + /* don't create variable bevels for very small deviations; */ + /* FT_Sin(x) = 0 for x <= 57 */ + if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) + bevel = TRUE; + } + } + + if ( bevel ) /* this is a bevel (broken angle) */ + { + if ( fixed_bevel ) + { + /* the outer corners are simply joined together */ + GD_FT_Vector delta; + + + /* add bevel */ + GD_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + else /* variable bevel or clipped miter */ + { + /* the miter is truncated */ + GD_FT_Vector middle, delta; + GD_FT_Fixed coef; + + + /* compute middle point and first angle point */ + GD_FT_Vector_From_Polar( &middle, + GD_FT_MulFix( radius, stroker->miter_limit ), + phi ); + + coef = GD_FT_DivFix( 0x10000L - sigma.x, sigma.y ); + delta.x = GD_FT_MulFix( middle.y, coef ); + delta.y = GD_FT_MulFix( -middle.x, coef ); + + middle.x += stroker->center.x; + middle.y += stroker->center.y; + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* finally, add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + GD_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + else /* this is a miter (intersection) */ + { + GD_FT_Fixed length; + GD_FT_Vector delta; + + + length = GD_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); + + GD_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* now add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + GD_FT_Vector_From_Polar( &delta, + stroker->radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + + Exit: + return error; +} + +static GD_FT_Error ft_stroker_process_corner(GD_FT_Stroker stroker, + GD_FT_Fixed line_length) +{ + GD_FT_Error error = 0; + GD_FT_Angle turn; + GD_FT_Int inside_side; + + turn = GD_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn == 0) goto Exit; + + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + /* process the inside side */ + error = ft_stroker_inside(stroker, inside_side, line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, line_length); + +Exit: + return error; +} + +/* add two points to the left and right borders corresponding to the */ +/* start of the subpath */ +static GD_FT_Error ft_stroker_subpath_start(GD_FT_Stroker stroker, + GD_FT_Angle start_angle, + GD_FT_Fixed line_length) +{ + GD_FT_Vector delta; + GD_FT_Vector point; + GD_FT_Error error; + GD_FT_StrokeBorder border; + + GD_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + GD_FT_ANGLE_PI2); + + point.x = stroker->center.x + delta.x; + point.y = stroker->center.y + delta.y; + + border = stroker->borders; + error = ft_stroke_border_moveto(border, &point); + if (error) goto Exit; + + point.x = stroker->center.x - delta.x; + point.y = stroker->center.y - delta.y; + + border++; + error = ft_stroke_border_moveto(border, &point); + + /* save angle, position, and line length for last join */ + /* (line_length is zero for curves) */ + stroker->subpath_angle = start_angle; + stroker->first_point = FALSE; + stroker->subpath_line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_LineTo(GD_FT_Stroker stroker, GD_FT_Vector* to) +{ + GD_FT_Error error = 0; + GD_FT_StrokeBorder border; + GD_FT_Vector delta; + GD_FT_Angle angle; + GD_FT_Int side; + GD_FT_Fixed line_length; + + delta.x = to->x - stroker->center.x; + delta.y = to->y - stroker->center.y; + + /* a zero-length lineto is a no-op; avoid creating a spurious corner */ + if (delta.x == 0 && delta.y == 0) goto Exit; + + /* compute length of line */ + line_length = GD_FT_Vector_Length(&delta); + + angle = GD_FT_Atan2(delta.x, delta.y); + GD_FT_Vector_From_Polar(&delta, stroker->radius, angle + GD_FT_ANGLE_PI2); + + /* process corner if necessary */ + if (stroker->first_point) { + /* This is the first segment of a subpath. We need to */ + /* add a point to each border at their respective starting */ + /* point locations. */ + error = ft_stroker_subpath_start(stroker, angle, line_length); + if (error) goto Exit; + } else { + /* process the current corner */ + stroker->angle_out = angle; + error = ft_stroker_process_corner(stroker, line_length); + if (error) goto Exit; + } + + /* now add a line segment to both the `inside' and `outside' paths */ + for (border = stroker->borders, side = 1; side >= 0; side--, border++) { + GD_FT_Vector point; + + point.x = to->x + delta.x; + point.y = to->y + delta.y; + + /* the ends of lineto borders are movable */ + error = ft_stroke_border_lineto(border, &point, TRUE); + if (error) goto Exit; + + delta.x = -delta.x; + delta.y = -delta.y; + } + + stroker->angle_in = angle; + stroker->center = *to; + stroker->line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_ConicTo(GD_FT_Stroker stroker, GD_FT_Vector* control, + GD_FT_Vector* to) +{ + GD_FT_Error error = 0; + GD_FT_Vector bez_stack[34]; + GD_FT_Vector* arc; + GD_FT_Vector* limit = bez_stack + 30; + GD_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (GD_FT_IS_SMALL(stroker->center.x - control->x) && + GD_FT_IS_SMALL(stroker->center.y - control->y) && + GD_FT_IS_SMALL(control->x - to->x) && + GD_FT_IS_SMALL(control->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control; + arc[2] = stroker->center; + + while (arc >= bez_stack) { + GD_FT_Angle angle_in, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = stroker->angle_in; + + if (arc < limit && + !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_conic_split(arc); + arc += 2; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(GD_FT_Angle_Diff(stroker->angle_in, angle_in)) > + GD_FT_SMALL_CONIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[2]; + stroker->angle_out = angle_in; + stroker->line_join = GD_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + GD_FT_Vector ctrl, end; + GD_FT_Angle theta, phi, rotate, alpha0 = 0; + GD_FT_Fixed length; + GD_FT_StrokeBorder border; + GD_FT_Int side; + + theta = GD_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = GD_FT_DivFix(stroker->radius, GD_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = GD_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = GD_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + GD_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + GD_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + GD_FT_Vector start; + GD_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = GD_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(GD_FT_Angle_Diff(alpha0, alpha1)) > + GD_FT_ANGLE_PI / 2) { + GD_FT_Angle beta, gamma; + GD_FT_Vector bvec, delta; + GD_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + GD_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = GD_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = GD_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(GD_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(GD_FT_Sin(beta - gamma)); + + alen = GD_FT_MulDiv(blen, sinA, sinB); + + GD_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_conicto(border, &ctrl, &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_conicto(border, &ctrl, &end); + if (error) goto Exit; + } + } + + arc -= 2; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_CubicTo(GD_FT_Stroker stroker, GD_FT_Vector* control1, + GD_FT_Vector* control2, GD_FT_Vector* to) +{ + GD_FT_Error error = 0; + GD_FT_Vector bez_stack[37]; + GD_FT_Vector* arc; + GD_FT_Vector* limit = bez_stack + 32; + GD_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (GD_FT_IS_SMALL(stroker->center.x - control1->x) && + GD_FT_IS_SMALL(stroker->center.y - control1->y) && + GD_FT_IS_SMALL(control1->x - control2->x) && + GD_FT_IS_SMALL(control1->y - control2->y) && + GD_FT_IS_SMALL(control2->x - to->x) && + GD_FT_IS_SMALL(control2->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control2; + arc[2] = *control1; + arc[3] = stroker->center; + + while (arc >= bez_stack) { + GD_FT_Angle angle_in, angle_mid, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = angle_mid = stroker->angle_in; + + if (arc < limit && + !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_cubic_split(arc); + arc += 3; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(GD_FT_Angle_Diff(stroker->angle_in, angle_in)) > + GD_FT_SMALL_CUBIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[3]; + stroker->angle_out = angle_in; + stroker->line_join = GD_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + GD_FT_Vector ctrl1, ctrl2, end; + GD_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + GD_FT_Fixed length1, length2; + GD_FT_StrokeBorder border; + GD_FT_Int side; + + theta1 = GD_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = GD_FT_Angle_Diff(angle_mid, angle_out) / 2; + phi1 = ft_angle_mean(angle_in, angle_mid); + phi2 = ft_angle_mean(angle_mid, angle_out); + length1 = GD_FT_DivFix(stroker->radius, GD_FT_Cos(theta1)); + length2 = GD_FT_DivFix(stroker->radius, GD_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = GD_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = GD_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + GD_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + GD_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + GD_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + GD_FT_Vector start; + GD_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = GD_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(GD_FT_Angle_Diff(alpha0, alpha1)) > + GD_FT_ANGLE_PI / 2) { + GD_FT_Angle beta, gamma; + GD_FT_Vector bvec, delta; + GD_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + GD_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = GD_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = GD_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(GD_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(GD_FT_Sin(beta - gamma)); + + alen = GD_FT_MulDiv(blen, sinA, sinB); + + GD_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, + &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); + if (error) goto Exit; + } + } + + arc -= 3; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_BeginSubPath(GD_FT_Stroker stroker, GD_FT_Vector* to, + GD_FT_Bool open) +{ + /* We cannot process the first point, because there is not enough */ + /* information regarding its corner/cap. The latter will be processed */ + /* in the `GD_FT_Stroker_EndSubPath' routine. */ + /* */ + stroker->first_point = TRUE; + stroker->center = *to; + stroker->subpath_open = open; + + /* Determine if we need to check whether the border radius is greater */ + /* than the radius of curvature of a curve, to handle this case */ + /* specially. This is only required if bevel joins or butt caps may */ + /* be created, because round & miter joins and round & square caps */ + /* cover the negative sector created with wide strokes. */ + stroker->handle_wide_strokes = + GD_FT_BOOL(stroker->line_join != GD_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == GD_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static GD_FT_Error ft_stroker_add_reverse_left(GD_FT_Stroker stroker, + GD_FT_Bool open) +{ + GD_FT_StrokeBorder right = stroker->borders + 0; + GD_FT_StrokeBorder left = stroker->borders + 1; + GD_FT_Int new_points; + GD_FT_Error error = 0; + + assert(left->start >= 0); + + new_points = left->num_points - left->start; + if (new_points > 0) { + error = ft_stroke_border_grow(right, (GD_FT_UInt)new_points); + if (error) goto Exit; + + { + GD_FT_Vector* dst_point = right->points + right->num_points; + GD_FT_Byte* dst_tag = right->tags + right->num_points; + GD_FT_Vector* src_point = left->points + left->num_points - 1; + GD_FT_Byte* src_tag = left->tags + left->num_points - 1; + + while (src_point >= left->points + left->start) { + *dst_point = *src_point; + *dst_tag = *src_tag; + + if (open) + dst_tag[0] &= ~GD_FT_STROKE_TAG_BEGIN_END; + else { + GD_FT_Byte ttag = + (GD_FT_Byte)(dst_tag[0] & GD_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == GD_FT_STROKE_TAG_BEGIN || + ttag == GD_FT_STROKE_TAG_END) + dst_tag[0] ^= GD_FT_STROKE_TAG_BEGIN_END; + } + + src_point--; + src_tag--; + dst_point++; + dst_tag++; + } + } + + left->num_points = left->start; + right->num_points += new_points; + + right->movable = FALSE; + left->movable = FALSE; + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +/* there's a lot of magic in this function! */ +GD_FT_Error GD_FT_Stroker_EndSubPath(GD_FT_Stroker stroker) +{ + GD_FT_Error error = 0; + + if (stroker->subpath_open) { + GD_FT_StrokeBorder right = stroker->borders; + + /* All right, this is an opened path, we need to add a cap between */ + /* right & left, add the reverse of left, then add a final cap */ + /* between left & right. */ + error = ft_stroker_cap(stroker, stroker->angle_in, 0); + if (error) goto Exit; + + /* add reversed points from `left' to `right' */ + error = ft_stroker_add_reverse_left(stroker, TRUE); + if (error) goto Exit; + + /* now add the final cap */ + stroker->center = stroker->subpath_start; + error = + ft_stroker_cap(stroker, stroker->subpath_angle + GD_FT_ANGLE_PI, 0); + if (error) goto Exit; + + /* Now end the right subpath accordingly. The left one is */ + /* rewind and doesn't need further processing. */ + ft_stroke_border_close(right, FALSE); + } else { + GD_FT_Angle turn; + GD_FT_Int inside_side; + + /* close the path if needed */ + if (stroker->center.x != stroker->subpath_start.x || + stroker->center.y != stroker->subpath_start.y) { + error = GD_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = GD_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn != 0) { + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + error = ft_stroker_inside(stroker, inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + } + + /* then end our two subpaths */ + ft_stroke_border_close(stroker->borders + 0, FALSE); + ft_stroke_border_close(stroker->borders + 1, TRUE); + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_GetBorderCounts(GD_FT_Stroker stroker, + GD_FT_StrokerBorder border, + GD_FT_UInt* anum_points, + GD_FT_UInt* anum_contours) +{ + GD_FT_UInt num_points = 0, num_contours = 0; + GD_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // GD_FT_THROW( Invalid_Argument ); + goto Exit; + } + + error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, + &num_contours); +Exit: + if (anum_points) *anum_points = num_points; + + if (anum_contours) *anum_contours = num_contours; + + return error; +} + +/* documentation is in ftstroke.h */ + +GD_FT_Error GD_FT_Stroker_GetCounts(GD_FT_Stroker stroker, + GD_FT_UInt* anum_points, + GD_FT_UInt* anum_contours) +{ + GD_FT_UInt count1, count2, num_points = 0; + GD_FT_UInt count3, count4, num_contours = 0; + GD_FT_Error error; + + error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); + if (error) goto Exit; + + error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); + if (error) goto Exit; + + num_points = count1 + count3; + num_contours = count2 + count4; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; +} + +/* documentation is in ftstroke.h */ + +void GD_FT_Stroker_ExportBorder(GD_FT_Stroker stroker, + GD_FT_StrokerBorder border, + GD_FT_Outline* outline) +{ + if (border == GD_FT_STROKER_BORDER_LEFT || + border == GD_FT_STROKER_BORDER_RIGHT) { + GD_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void GD_FT_Stroker_Export(GD_FT_Stroker stroker, GD_FT_Outline* outline) +{ + GD_FT_Stroker_ExportBorder(stroker, GD_FT_STROKER_BORDER_LEFT, outline); + GD_FT_Stroker_ExportBorder(stroker, GD_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to GD_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +GD_FT_Error GD_FT_Stroker_ParseOutline(GD_FT_Stroker stroker, + const GD_FT_Outline* outline) +{ + GD_FT_Vector v_last; + GD_FT_Vector v_control; + GD_FT_Vector v_start; + + GD_FT_Vector* point; + GD_FT_Vector* limit; + char* tags; + + GD_FT_Error error; + + GD_FT_Int n; /* index of contour in outline */ + GD_FT_UInt first; /* index of first point in contour */ + GD_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // GD_FT_THROW( Invalid_Argument ); + + GD_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + GD_FT_UInt last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + /* skip empty points; we don't stroke these */ + if (last <= first) { + first = last + 1; + continue; + } + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = GD_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == GD_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == GD_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (GD_FT_CURVE_TAG(outline->tags[last]) == GD_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = GD_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = GD_FT_CURVE_TAG(tags[0]); + switch (tag) { + case GD_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + GD_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = GD_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case GD_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + GD_FT_Vector vec; + GD_FT_Vector v_middle; + + point++; + tags++; + tag = GD_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == GD_FT_CURVE_TAG_ON) { + error = + GD_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != GD_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + GD_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = GD_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* GD_FT_CURVE_TAG_CUBIC */ + { + GD_FT_Vector vec1, vec2; + + if (point + 1 > limit || + GD_FT_CURVE_TAG(tags[1]) != GD_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + GD_FT_Vector vec; + + vec = point[0]; + + error = GD_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = GD_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + Close: + if (error) goto Exit; + + /* don't try to end the path if no segments have been generated */ + if (!stroker->first_point) { + error = GD_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + } + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // GD_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/ext/gd/libgd/ftraster/gd_ft_stroker.h b/ext/gd/libgd/ftraster/gd_ft_stroker.h new file mode 100644 index 000000000000..328ff056d1f1 --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_stroker.h @@ -0,0 +1,346 @@ +#ifndef GD_FT_STROKER_H +#define GD_FT_STROKER_H +/***************************************************************************/ +/* */ +/* ftstroke.h */ +/* */ +/* FreeType path stroker (specification). */ +/* */ +/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "gd_ft_raster.h" + + /************************************************************** + * + * @type: + * GD_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ + typedef struct GD_FT_StrokerRec_* GD_FT_Stroker; + + + /************************************************************** + * + * @enum: + * GD_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * GD_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * GD_FT_STROKER_LINEJOIN_BEVEL :: + * Used to render beveled line joins. The outer corner of + * the joined lines is filled by enclosing the triangular + * region of the corner with a straight line between the + * outer corners of each stroke. + * + * GD_FT_STROKER_LINEJOIN_MITER_FIXED :: + * Used to render mitered line joins, with fixed bevels if the + * miter limit is exceeded. The outer edges of the strokes + * for the two segments are extended until they meet at an + * angle. If the segments meet at too sharp an angle (such + * that the miter would extend from the intersection of the + * segments a distance greater than the product of the miter + * limit value and the border radius), then a bevel join (see + * above) is used instead. This prevents long spikes being + * created. GD_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * GD_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * GD_FT_STROKER_LINEJOIN_MITER :: + * Used to render mitered line joins, with variable bevels if + * the miter limit is exceeded. The intersection of the + * strokes is clipped at a line perpendicular to the bisector + * of the angle between the strokes, at the distance from the + * intersection of the segments equal to the product of the + * miter limit value and the border radius. This prevents + * long spikes being created. + * GD_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. GD_FT_STROKER_LINEJOIN_MITER is an alias + * for GD_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ + typedef enum GD_FT_Stroker_LineJoin_ + { + GD_FT_STROKER_LINEJOIN_ROUND = 0, + GD_FT_STROKER_LINEJOIN_BEVEL = 1, + GD_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + GD_FT_STROKER_LINEJOIN_MITER = GD_FT_STROKER_LINEJOIN_MITER_VARIABLE, + GD_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + + } GD_FT_Stroker_LineJoin; + + + /************************************************************** + * + * @enum: + * GD_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * GD_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * GD_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * GD_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ + typedef enum GD_FT_Stroker_LineCap_ + { + GD_FT_STROKER_LINECAP_BUTT = 0, + GD_FT_STROKER_LINECAP_ROUND, + GD_FT_STROKER_LINECAP_SQUARE + + } GD_FT_Stroker_LineCap; + + + /************************************************************** + * + * @enum: + * GD_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @GD_FT_Stroker_GetBorderCounts and @GD_FT_Stroker_ExportBorder. + * + * @values: + * GD_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * GD_FT_STROKER_BORDER_RIGHT :: + * Select the right border, relative to the drawing direction. + * + * @note: + * Applications are generally interested in the `inside' and `outside' + * borders. However, there is no direct mapping between these and the + * `left' and `right' ones, since this really depends on the glyph's + * drawing orientation, which varies between font formats. + * + * You can however use @GD_FT_Outline_GetInsideBorder and + * @GD_FT_Outline_GetOutsideBorder to get these. + */ + typedef enum GD_FT_StrokerBorder_ + { + GD_FT_STROKER_BORDER_LEFT = 0, + GD_FT_STROKER_BORDER_RIGHT + + } GD_FT_StrokerBorder; + + + /************************************************************** + * + * @function: + * GD_FT_Stroker_New + * + * @description: + * Create a new stroker object. + * + * @input: + * library :: + * FreeType library handle. + * + * @output: + * astroker :: + * A new stroker object handle. NULL in case of error. + * + * @return: + * FreeType error code. 0~means success. + */ + GD_FT_Error + GD_FT_Stroker_New( GD_FT_Stroker *astroker ); + + + /************************************************************** + * + * @function: + * GD_FT_Stroker_Set + * + * @description: + * Reset a stroker object's attributes. + * + * @input: + * stroker :: + * The target stroker handle. + * + * radius :: + * The border radius. + * + * line_cap :: + * The line cap style. + * + * line_join :: + * The line join style. + * + * miter_limit :: + * The miter limit for the GD_FT_STROKER_LINEJOIN_MITER_FIXED and + * GD_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, + * expressed as 16.16 fixed-point value. + * + * @note: + * The radius is expressed in the same units as the outline + * coordinates. + */ + void + GD_FT_Stroker_Set( GD_FT_Stroker stroker, + GD_FT_Fixed radius, + GD_FT_Stroker_LineCap line_cap, + GD_FT_Stroker_LineJoin line_join, + GD_FT_Fixed miter_limit ); + + /************************************************************** + * + * @function: + * GD_FT_Stroker_ParseOutline + * + * @description: + * A convenience function used to parse a whole outline with + * the stroker. The resulting outline(s) can be retrieved + * later by functions like @GD_FT_Stroker_GetCounts and @GD_FT_Stroker_Export. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The source outline. + * + * + * @return: + * FreeType error code. 0~means success. + * + * @note: + * If `opened' is~0 (the default), the outline is treated as a closed + * path, and the stroker generates two distinct `border' outlines. + * + * + * This function calls @GD_FT_Stroker_Rewind automatically. + */ + GD_FT_Error + GD_FT_Stroker_ParseOutline( GD_FT_Stroker stroker, + const GD_FT_Outline* outline); + + + /************************************************************** + * + * @function: + * GD_FT_Stroker_GetCounts + * + * @description: + * Call this function once you have finished parsing your paths + * with the stroker. It returns the number of points and + * contours necessary to export all points/borders from the stroked + * outline/path. + * + * @input: + * stroker :: + * The target stroker handle. + * + * @output: + * anum_points :: + * The number of points. + * + * anum_contours :: + * The number of contours. + * + * @return: + * FreeType error code. 0~means success. + */ + GD_FT_Error + GD_FT_Stroker_GetCounts( GD_FT_Stroker stroker, + GD_FT_UInt *anum_points, + GD_FT_UInt *anum_contours ); + + +/************************************************************** + * + * @function: + * GD_FT_Stroker_ExportBorder + * + * @description: + * Export a single border of a stroked outline. + * + * @input: + * stroker :: + * The target stroker handle. + * + * border :: + * The border to export (LEFT or RIGHT). + * + * outline :: + * The target outline handle. + * + * @note: + * Call this after GD_FT_Stroker_GetCounts to get individual borders. + * The outline must be pre-allocated with sufficient space. + */ +void +GD_FT_Stroker_ExportBorder( GD_FT_Stroker stroker, + GD_FT_StrokerBorder border, + GD_FT_Outline* outline ); + +/************************************************************** + * + * @function: + * GD_FT_Stroker_Export + * + * @description: + * Call this function after @GD_FT_Stroker_GetBorderCounts to + * export all borders to your own @GD_FT_Outline structure. + * + * Note that this function appends the border points and + * contours to your outline, but does not try to resize its + * arrays. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The target outline handle. + */ + void + GD_FT_Stroker_Export( GD_FT_Stroker stroker, + GD_FT_Outline* outline ); + + + /************************************************************** + * + * @function: + * GD_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ + void + GD_FT_Stroker_Done( GD_FT_Stroker stroker ); + + +#endif // GD_FT_STROKER_H diff --git a/ext/gd/libgd/ftraster/gd_ft_types.h b/ext/gd/libgd/ftraster/gd_ft_types.h new file mode 100644 index 000000000000..12d08f13c598 --- /dev/null +++ b/ext/gd/libgd/ftraster/gd_ft_types.h @@ -0,0 +1,160 @@ +#ifndef GD_FT_TYPES_H +#define GD_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long GD_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int GD_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int GD_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long GD_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long GD_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short GD_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char GD_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Bool */ +/* */ +/* */ +/* A typedef of unsigned char, used for simple booleans. As usual, */ +/* values 1 and~0 represent true and false, respectively. */ +/* */ +typedef unsigned char GD_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int GD_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Pos */ +/* */ +/* */ +/* The type GD_FT_Pos is used to store vectorial coordinates. Depending */ +/* on the context, these can represent distances in integer font */ +/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ +/* */ +typedef signed long GD_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* GD_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the GD_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct GD_FT_Vector_ +{ + GD_FT_Pos x; + GD_FT_Pos y; + +} GD_FT_Vector; + + +typedef long long int GD_FT_Int64; +typedef unsigned long long int GD_FT_UInt64; + +typedef signed int GD_FT_Int32; +typedef unsigned int GD_FT_UInt32; + + +#define GD_FT_BOOL( x ) ( (GD_FT_Bool)( x ) ) + +#define GD_FT_SIZEOF_LONG 4 + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +#endif // GD_FT_TYPES_H diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 47c4dbc0d770..cbeaf9e32413 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -1,12 +1,24 @@ #include -#include +#include +#include #include +#include #include "gd.h" -#include "gdhelpers.h" +#include "gd_color.h" #include "gd_errors.h" +#include "gdhelpers.h" +#include "gd_intern.h" + +#if ENABLE_CORRECTED_LEGACY_COMPOSITING +#include "gd_compositor.h" +#endif #include "php.h" +/* 2.0.12: this now checks the clipping rectangle */ +#define gdImageBoundsSafeMacro(im, x, y) \ + (!((((y) < (im)->cy1) || ((y) > (im)->cy2)) || \ + (((x) < (im)->cx1) || ((x) > (im)->cx2)))) #ifdef _OSD_POSIX /* BS2000 uses the EBCDIC char set instead of ASCII */ #define CHARSET_EBCDIC @@ -18,56 +30,52 @@ #define ASC(ch) ch #else /*CHARSET_EBCDIC */ #define ASC(ch) gd_toascii[(unsigned char)ch] -static const unsigned char gd_toascii[256] = -{ -/*00 */ 0x00, 0x01, 0x02, 0x03, 0x85, 0x09, 0x86, 0x7f, - 0x87, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /*................ */ -/*10 */ 0x10, 0x11, 0x12, 0x13, 0x8f, 0x0a, 0x08, 0x97, - 0x18, 0x19, 0x9c, 0x9d, 0x1c, 0x1d, 0x1e, 0x1f, /*................ */ -/*20 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x92, 0x17, 0x1b, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07, /*................ */ -/*30 */ 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, - 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a, /*................ */ -/*40 */ 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, - 0xe7, 0xf1, 0x60, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, /* .........`.<(+| */ -/*50 */ 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, - 0xec, 0xdf, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x9f, /*&.........!$*);. */ -/*60 */ 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, - 0xc7, 0xd1, 0x5e, 0x2c, 0x25, 0x5f, 0x3e, 0x3f, +static const unsigned char gd_toascii[256] = { + /*00 */ 0x00, 0x01, 0x02, 0x03, 0x85, 0x09, 0x86, 0x7f, 0x87, 0x8d, 0x8e, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /*................ */ + /*10 */ 0x10, 0x11, 0x12, 0x13, 0x8f, 0x0a, 0x08, 0x97, 0x18, 0x19, 0x9c, + 0x9d, 0x1c, 0x1d, 0x1e, 0x1f, /*................ */ + /*20 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x92, 0x17, 0x1b, 0x88, 0x89, 0x8a, + 0x8b, 0x8c, 0x05, 0x06, 0x07, /*................ */ + /*30 */ 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, 0x98, 0x99, 0x9a, + 0x9b, 0x14, 0x15, 0x9e, 0x1a, /*................ */ + /*40 */ 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xf1, 0x60, + 0x2e, 0x3c, 0x28, 0x2b, 0x7c, /* .........`.<(+| */ + /*50 */ 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, 0xec, 0xdf, 0x21, + 0x24, 0x2a, 0x29, 0x3b, 0x9f, /*&.........!$*);. */ + /*60 */ 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, 0xc7, 0xd1, 0x5e, + 0x2c, 0x25, 0x5f, 0x3e, 0x3f, /*-/........^,%_>?*/ -/*70 */ 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, - 0xcc, 0xa8, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, /*..........:#@'=" */ -/*80 */ 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, - 0x68, 0x69, 0xab, 0xbb, 0xf0, 0xfd, 0xfe, 0xb1, /*.abcdefghi...... */ -/*90 */ 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, - 0x71, 0x72, 0xaa, 0xba, 0xe6, 0xb8, 0xc6, 0xa4, /*.jklmnopqr...... */ -/*a0 */ 0xb5, 0xaf, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x79, 0x7a, 0xa1, 0xbf, 0xd0, 0xdd, 0xde, 0xae, /*..stuvwxyz...... */ -/*b0 */ 0xa2, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, - 0xbd, 0xbe, 0xac, 0x5b, 0x5c, 0x5d, 0xb4, 0xd7, /*...........[\].. */ -/*c0 */ 0xf9, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0xad, 0xf4, 0xf6, 0xf2, 0xf3, 0xf5, /*.ABCDEFGHI...... */ -/*d0 */ 0xa6, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, - 0x51, 0x52, 0xb9, 0xfb, 0xfc, 0xdb, 0xfa, 0xff, /*.JKLMNOPQR...... */ -/*e0 */ 0xd9, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0xb2, 0xd4, 0xd6, 0xd2, 0xd3, 0xd5, /*..STUVWXYZ...... */ -/*f0 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0xb3, 0x7b, 0xdc, 0x7d, 0xda, 0x7e /*0123456789.{.}.~ */ + /*70 */ 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0xa8, 0x3a, + 0x23, 0x40, 0x27, 0x3d, 0x22, /*..........:#@'=" */ + /*80 */ 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xab, + 0xbb, 0xf0, 0xfd, 0xfe, 0xb1, /*.abcdefghi...... */ + /*90 */ 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xaa, + 0xba, 0xe6, 0xb8, 0xc6, 0xa4, /*.jklmnopqr...... */ + /*a0 */ 0xb5, 0xaf, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xa1, + 0xbf, 0xd0, 0xdd, 0xde, 0xae, /*..stuvwxyz...... */ + /*b0 */ 0xa2, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, 0xbd, 0xbe, 0xac, + 0x5b, 0x5c, 0x5d, 0xb4, 0xd7, /*...........[\].. */ + /*c0 */ 0xf9, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0xad, + 0xf4, 0xf6, 0xf2, 0xf3, 0xf5, /*.ABCDEFGHI...... */ + /*d0 */ 0xa6, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0xb9, + 0xfb, 0xfc, 0xdb, 0xfa, 0xff, /*.JKLMNOPQR...... */ + /*e0 */ 0xd9, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xb2, + 0xd4, 0xd6, 0xd2, 0xd3, 0xd5, /*..STUVWXYZ...... */ + /*f0 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xb3, + 0x7b, 0xdc, 0x7d, 0xda, 0x7e /*0123456789.{.}.~ */ }; #endif /*CHARSET_EBCDIC */ - /* 2.0.10: cast instead of floor() yields 35% performance improvement. Thanks to John Buckman. */ #define floor_cast(exp) ((long) exp) - -extern int gdCosT[]; -extern int gdSinT[]; +extern const int gdCosT[]; +extern const int gdSinT[]; /** * Group: Error Handling */ -void gd_stderr_error(int priority, const char *format, va_list args) -{ +static void gd_stderr_error(int priority, const char *format, va_list args) { switch (priority) { case GD_ERROR: fputs("GD Error: ", stderr); @@ -93,23 +101,20 @@ void gd_stderr_error(int priority, const char *format, va_list args) static gdErrorMethod gd_error_method = gd_stderr_error; -static void _gd_error_ex(int priority, const char *format, va_list args) -{ +static void _gd_error_ex(int priority, const char *format, va_list args) { if (gd_error_method) { gd_error_method(priority, format, args); } } -void gd_error(const char *format, ...) -{ +void gd_error(const char *format, ...) { va_list args; va_start(args, format); _gd_error_ex(GD_WARNING, format, args); va_end(args); } -void gd_error_ex(int priority, const char *format, ...) -{ +void gd_error_ex(int priority, const char *format, ...) { va_list args; va_start(args, format); @@ -120,32 +125,66 @@ void gd_error_ex(int priority, const char *format, ...) /* Function: gdSetErrorMethod */ -void gdSetErrorMethod(gdErrorMethod error_method) -{ +BGD_DECLARE(void) gdSetErrorMethod(gdErrorMethod error_method) { gd_error_method = error_method; } /* Function: gdClearErrorMethod */ -void gdClearErrorMethod(void) -{ +BGD_DECLARE(void) gdClearErrorMethod(void) { gd_error_method = gd_stderr_error; } static void gdImageBrushApply(gdImagePtr im, int x, int y); static void gdImageTileApply(gdImagePtr im, int x, int y); static int gdAlphaOverlayColor(int src, int dst, int max); -int gdImageGetTrueColorPixel(gdImagePtr im, int x, int y); -gdImagePtr gdImageCreate (int sx, int sy) -{ +BGD_DECLARE(int) gdImageGetTrueColorPixel(gdImagePtr im, int x, int y); + +/** + * Group: Creation and Destruction + */ + +/* + Function: gdImageCreate + + gdImageCreate is called to create palette-based images, with no + more than 256 colors. The image must eventually be destroyed using + gdImageDestroy(). + + Parameters: + + sx - The image width. + sy - The image height. + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + im = gdImageCreate(64, 64); + // ... Use the image ... + gdImageDestroy(im); + + (end code) + + See Also: + + + + */ +BGD_DECLARE(gdImagePtr) gdImageCreate(int sx, int sy) { int i; gdImagePtr im; if (overflow2(sx, sy)) { return NULL; } + if (overflow2(sizeof(unsigned char *), sy)) { return NULL; } @@ -154,17 +193,33 @@ gdImagePtr gdImageCreate (int sx, int sy) } im = (gdImage *) gdCalloc(1, sizeof(gdImage)); + if (!im) { + return NULL; + } /* Row-major ever since gd 1.3 */ im->pixels = (unsigned char **) gdMalloc(sizeof(unsigned char *) * sy); + if (!im->pixels) { + gdFree(im); + return NULL; + } + im->polyInts = 0; im->polyAllocated = 0; im->brush = 0; im->tile = 0; im->style = 0; - for (i = 0; i < sy; i++) { + for (i = 0; (i < sy); i++) { /* Row-major ever since gd 1.3 */ im->pixels[i] = (unsigned char *) gdCalloc(sx, sizeof(unsigned char)); + if (!im->pixels[i]) { + for (--i; i >= 0; i--) { + gdFree(im->pixels[i]); + } + gdFree(im->pixels); + gdFree(im); + return NULL; + } } im->sx = sx; im->sy = sy; @@ -173,12 +228,12 @@ gdImagePtr gdImageCreate (int sx, int sy) im->interlace = 0; im->thick = 1; im->AA = 0; - for (i = 0; i < gdMaxColors; i++) { + for (i = 0; (i < gdMaxColors); i++) { im->open[i] = 1; im->red[i] = 0; im->green[i] = 0; im->blue[i] = 0; - } + }; im->trueColor = 0; im->tpixels = 0; im->cx1 = 0; @@ -192,8 +247,44 @@ gdImagePtr gdImageCreate (int sx, int sy) return im; } -gdImagePtr gdImageCreateTrueColor (int sx, int sy) -{ +/* + Function: gdImageCreateTrueColor + + is called to create truecolor images, + with an essentially unlimited number of colors. Invoke + with the x and y dimensions of the + desired image. returns a + to the new image, or NULL if unable to allocate the image. The + image must eventually be destroyed using (). + + Truecolor images are always filled with black at creation + time. There is no concept of a "background" color index. + + Parameters: + + sx - The image width. + sy - The image height. + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + im = gdImageCreateTrueColor(64, 64); + // ... Use the image ... + gdImageDestroy(im); + + (end code) + + See Also: + + + +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateTrueColor(int sx, int sy) { int i; gdImagePtr im; @@ -208,15 +299,34 @@ gdImagePtr gdImageCreateTrueColor (int sx, int sy) } im = (gdImage *) gdMalloc(sizeof(gdImage)); + if (!im) { + return 0; + } memset(im, 0, sizeof(gdImage)); + im->tpixels = (int **) gdMalloc(sizeof(int *) * sy); + if (!im->tpixels) { + gdFree(im); + return 0; + } im->polyInts = 0; im->polyAllocated = 0; im->brush = 0; im->tile = 0; im->style = 0; - for (i = 0; i < sy; i++) { + for (i = 0; (i < sy); i++) { im->tpixels[i] = (int *) gdCalloc(sx, sizeof(int)); + if (!im->tpixels[i]) { + /* 2.0.34 */ + i--; + while (i >= 0) { + gdFree(im->tpixels[i]); + i--; + } + gdFree(im->tpixels); + gdFree(im); + return 0; + } } im->sx = sx; im->sy = sy; @@ -224,11 +334,10 @@ gdImagePtr gdImageCreateTrueColor (int sx, int sy) im->interlace = 0; im->trueColor = 1; /* 2.0.2: alpha blending is now on by default, and saving of alpha is - * off by default. This allows font antialiasing to work as expected - * on the first try in JPEGs -- quite important -- and also allows - * for smaller PNGs when saving of alpha channel is not really - * desired, which it usually isn't! - */ + off by default. This allows font antialiasing to work as expected + on the first try in JPEGs -- quite important -- and also allows + for smaller PNGs when saving of alpha channel is not really + desired, which it usually isn't! */ im->saveAlphaFlag = 0; im->alphaBlendingFlag = 1; im->thick = 1; @@ -244,17 +353,44 @@ gdImagePtr gdImageCreateTrueColor (int sx, int sy) return im; } -void gdImageDestroy (gdImagePtr im) -{ +/* + Function: gdImageDestroy + + is used to free the memory associated with an + image. It is important to invoke before exiting + your program or assigning a new image to a variable. + + Parameters: + + im - Pointer to the gdImage to delete. + + Returns: + + Nothing. + + Example: + (start code) + + gdImagePtr im; + im = gdImageCreate(10, 10); + // ... Use the image ... + // Now destroy it + gdImageDestroy(im); + + (end code) + +*/ + +BGD_DECLARE(void) gdImageDestroy(gdImagePtr im) { int i; if (im->pixels) { - for (i = 0; i < im->sy; i++) { + for (i = 0; (i < im->sy); i++) { gdFree(im->pixels[i]); } gdFree(im->pixels); } if (im->tpixels) { - for (i = 0; i < im->sy; i++) { + for (i = 0; (i < im->sy); i++) { gdFree(im->tpixels[i]); } gdFree(im->tpixels); @@ -268,13 +404,56 @@ void gdImageDestroy (gdImagePtr im) gdFree(im); } -int gdImageColorClosest (gdImagePtr im, int r, int g, int b) -{ +/** + * Group: Color + */ + +/** + * Function: gdImageColorClosest + * + * Gets the closest color of the image + * + * This is a simplified variant of where the alpha + * channel is always opaque. + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * + * Returns: + * The closest color already available in the palette for palette images; + * the color value of the given components for truecolor images. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageColorClosest(gdImagePtr im, int r, int g, int b) { return gdImageColorClosestAlpha (im, r, g, b, gdAlphaOpaque); } -int gdImageColorClosestAlpha (gdImagePtr im, int r, int g, int b, int a) -{ +/** + * Function: gdImageColorClosestAlpha + * + * Gets the closest color of the image + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * a - The value of the alpha component. + * + * Returns: + * The closest color already available in the palette for palette images; + * the color value of the given components for truecolor images. + * + * See also: + * - + */ +BGD_DECLARE(int) +gdImageColorClosestAlpha(gdImagePtr im, int r, int g, int b, int a) { int i; long rd, gd, bd, ad; int ct = (-1); @@ -284,16 +463,18 @@ int gdImageColorClosestAlpha (gdImagePtr im, int r, int g, int b, int a) if (im->trueColor) { return gdTrueColorAlpha(r, g, b, a); } - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < (im->colorsTotal)); i++) { long dist; if (im->open[i]) { continue; } - rd = im->red[i] - r; - gd = im->green[i] - g; - bd = im->blue[i] - b; + rd = (im->red[i] - r); + gd = (im->green[i] - g); + bd = (im->blue[i] - b); /* gd 2.02: whoops, was - b (thanks to David Marwood) */ - ad = im->alpha[i] - a; + /* gd 2.16: was blue rather than alpha! Geez! Thanks to + Artur Jakub Jerzak */ + ad = (im->alpha[i] - a); dist = rd * rd + gd * gd + bd * bd + ad * ad; if (first || (dist < mindist)) { mindist = dist; @@ -304,44 +485,47 @@ int gdImageColorClosestAlpha (gdImagePtr im, int r, int g, int b, int a) return ct; } -/* This code is taken from http://www.acm.org/jgt/papers/SmithLyons96/hwb_rgb.html, an article - * on colour conversion to/from RBG and HWB colour systems. - * It has been modified to return the converted value as a * parameter. +/* This code is taken from + * http://www.acm.org/jgt/papers/SmithLyons96/hwb_rgb.html, an article on colour + * conversion to/from RBG and HWB colour systems. It has been modified to return + * the converted value as a * parameter. */ - -#define RETURN_HWB(h, w, b) {HWB->H = h; HWB->W = w; HWB->B = b; return HWB;} -#define RETURN_RGB(r, g, b) {RGB->R = r; RGB->G = g; RGB->B = b; return RGB;} +#define RETURN_HWB(h, w, b) \ + { \ + HWB->H = h; \ + HWB->W = w; \ + HWB->B = b; \ + return HWB; \ + } +#define RETURN_RGB(r, g, b) \ + { \ + RGB->R = r; \ + RGB->G = g; \ + RGB->B = b; \ + return RGB; \ + } #define HWB_UNDEFINED -1 -#define SETUP_RGB(s, r, g, b) {s.R = r/255.0f; s.G = g/255.0f; s.B = b/255.0f;} - -#ifndef MIN -#define MIN(a,b) ((a)<(b)?(a):(b)) -#endif -#define MIN3(a,b,c) ((a)<(b)?(MIN(a,c)):(MIN(b,c))) -#ifndef MAX -#define MAX(a,b) ((a)<(b)?(b):(a)) -#endif -#define MAX3(a,b,c) ((a)<(b)?(MAX(b,c)):(MAX(a,c))) - +#define SETUP_RGB(s, r, g, b) \ + { \ + s.R = r / 255.0; \ + s.G = g / 255.0; \ + s.B = b / 255.0; \ + } /* - * Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms. Pure - * red always maps to 6 in this implementation. Therefore UNDEFINED can be + * Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms. + * Pure red always maps to 6 in this implementation. Therefore UNDEFINED can be * defined as 0 in situations where only unsigned numbers are desired. */ -typedef struct -{ +typedef struct { float R, G, B; -} -RGBType; -typedef struct -{ +} RGBType; +typedef struct { float H, W, B; -} -HWBType; +} HWBType; + +static HWBType *RGB_to_HWB(RGBType RGB, HWBType *HWB) { -static HWBType * RGB_to_HWB (RGBType RGB, HWBType * HWB) -{ /* * RGB are each on [0, 1]. W and B are returned on [0, 1] and H is * returned on [0, 6]. Exception: H is returned UNDEFINED if W == 1 - B. @@ -353,17 +537,15 @@ static HWBType * RGB_to_HWB (RGBType RGB, HWBType * HWB) w = MIN3 (R, G, B); v = MAX3 (R, G, B); b = 1 - v; - if (v == w) { + if (v == w) RETURN_HWB(HWB_UNDEFINED, w, b); - } f = (R == w) ? G - B : ((G == w) ? B - R : R - G); i = (R == w) ? 3 : ((G == w) ? 5 : 1); RETURN_HWB(i - f / (v - w), w, b); } -static float HWB_Diff (int r1, int g1, int b1, int r2, int g2, int b2) -{ +static float HWB_Diff(int r1, int g1, int b1, int r2, int g2, int b2) { RGBType RGB1, RGB2; HWBType HWB1, HWB2; float diff; @@ -380,67 +562,24 @@ static float HWB_Diff (int r1, int g1, int b1, int r2, int g2, int b2) */ if ((HWB1.H == HWB_UNDEFINED) || (HWB2.H == HWB_UNDEFINED)) { - diff = 0.0f; /* Undefined hues always match... */ + diff = 0; /* Undefined hues always match... */ } else { - diff = fabsf(HWB1.H - HWB2.H); - if (diff > 3.0f) { - diff = 6.0f - diff; /* Remember, it's a colour circle */ + diff = fabs(HWB1.H - HWB2.H); + if (diff > 3) { + diff = 6 - diff; /* Remember, it's a colour circle */ } } - diff = diff * diff + (HWB1.W - HWB2.W) * (HWB1.W - HWB2.W) + (HWB1.B - HWB2.B) * (HWB1.B - HWB2.B); + diff = diff * diff + (HWB1.W - HWB2.W) * (HWB1.W - HWB2.W) + + (HWB1.B - HWB2.B) * (HWB1.B - HWB2.B); return diff; } - -#if 0 /* - * This is not actually used, but is here for completeness, in case someone wants to - * use the HWB stuff for anything else... - */ -static RGBType * HWB_to_RGB (HWBType HWB, RGBType * RGB) -{ - /* - * H is given on [0, 6] or UNDEFINED. W and B are given on [0, 1]. - * RGB are each returned on [0, 1]. - */ - - float h = HWB.H, w = HWB.W, b = HWB.B, v, n, f; - int i; - - v = 1 - b; - if (h == HWB_UNDEFINED) { - RETURN_RGB(v, v, v); - } - i = floor(h); - f = h - i; - if (i & 1) { - f = 1 - f; /* if i is odd */ - } - n = w + f * (v - w); /* linear interpolation between w and v */ - switch (i) { - case 6: - case 0: - RETURN_RGB(v, n, w); - case 1: - RETURN_RGB(n, v, w); - case 2: - RETURN_RGB(w, v, n); - case 3: - RETURN_RGB(w, n, v); - case 4: - RETURN_RGB(n, w, v); - case 5: - RETURN_RGB(v, w, n); - } - - return RGB; -} -#endif - -int gdImageColorClosestHWB (gdImagePtr im, int r, int g, int b) -{ + Function: gdImageColorClosestHWB +*/ +BGD_DECLARE(int) gdImageColorClosestHWB(gdImagePtr im, int r, int g, int b) { int i; /* long rd, gd, bd; */ int ct = (-1); @@ -449,7 +588,7 @@ int gdImageColorClosestHWB (gdImagePtr im, int r, int g, int b) if (im->trueColor) { return gdTrueColor(r, g, b); } - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < (im->colorsTotal)); i++) { float dist; if (im->open[i]) { continue; @@ -464,41 +603,123 @@ int gdImageColorClosestHWB (gdImagePtr im, int r, int g, int b) return ct; } -int gdImageColorExact (gdImagePtr im, int r, int g, int b) -{ +/** + * Function: gdImageColorExact + * + * Gets the exact color of the image + * + * This is a simplified variant of where the alpha + * channel is always opaque. + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * + * Returns: + * The exact color already available in the palette for palette images; if + * there is no exact color, -1 is returned. + * For truecolor images the color value of the given components is returned. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageColorExact(gdImagePtr im, int r, int g, int b) { return gdImageColorExactAlpha (im, r, g, b, gdAlphaOpaque); } -int gdImageColorExactAlpha (gdImagePtr im, int r, int g, int b, int a) -{ +/** + * Function: gdImageColorExactAlpha + * + * Gets the exact color of the image + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * a - The value of the alpha component. + * + * Returns: + * The exact color already available in the palette for palette images; if + * there is no exact color, -1 is returned. + * For truecolor images the color value of the given components is returned. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) +gdImageColorExactAlpha(gdImagePtr im, int r, int g, int b, int a) { int i; if (im->trueColor) { return gdTrueColorAlpha(r, g, b, a); } - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < (im->colorsTotal)); i++) { if (im->open[i]) { continue; } - if ((im->red[i] == r) && (im->green[i] == g) && (im->blue[i] == b) && (im->alpha[i] == a)) { + if ((im->red[i] == r) && (im->green[i] == g) && (im->blue[i] == b) && + (im->alpha[i] == a)) { return i; } } return -1; } -int gdImageColorAllocate (gdImagePtr im, int r, int g, int b) -{ +/** + * Function: gdImageColorAllocate + * + * Allocates a color + * + * This is a simplified variant of where the alpha + * channel is always opaque. + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * + * Returns: + * The color value. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageColorAllocate(gdImagePtr im, int r, int g, int b) { return gdImageColorAllocateAlpha (im, r, g, b, gdAlphaOpaque); } -int gdImageColorAllocateAlpha (gdImagePtr im, int r, int g, int b, int a) -{ +/** + * Function: gdImageColorAllocateAlpha + * + * Allocates a color + * + * This is typically used for palette images, but can be used for truecolor + * images as well. + * + * Parameters: + * im - The image. + * r - The value of the red component. + * g - The value of the green component. + * b - The value of the blue component. + * + * Returns: + * The color value. + * + * See also: + * - + */ +BGD_DECLARE(int) +gdImageColorAllocateAlpha(gdImagePtr im, int r, int g, int b, int a) { int i; int ct = (-1); if (im->trueColor) { return gdTrueColorAlpha(r, g, b, a); } - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < (im->colorsTotal)); i++) { if (im->open[i]) { ct = i; break; @@ -521,71 +742,67 @@ int gdImageColorAllocateAlpha (gdImagePtr im, int r, int g, int b, int a) } /* - * gdImageColorResolve is an alternative for the code fragment: - * - * if ((color=gdImageColorExact(im,R,G,B)) < 0) - * if ((color=gdImageColorAllocate(im,R,G,B)) < 0) - * color=gdImageColorClosest(im,R,G,B); - * - * in a single function. Its advantage is that it is guaranteed to - * return a color index in one search over the color table. + Function: gdImageColorResolve + + gdImageColorResolve is an alternative for the code fragment + (start code) + if ((color=gdImageColorExact(im,R,G,B)) < 0) + if ((color=gdImageColorAllocate(im,R,G,B)) < 0) + color=gdImageColorClosest(im,R,G,B); + (end code) + in a single function. Its advantage is that it is guaranteed to + return a color index in one search over the color table. */ -int gdImageColorResolve (gdImagePtr im, int r, int g, int b) -{ +BGD_DECLARE(int) gdImageColorResolve(gdImagePtr im, int r, int g, int b) { return gdImageColorResolveAlpha(im, r, g, b, gdAlphaOpaque); } -int gdImageColorResolveAlpha (gdImagePtr im, int r, int g, int b, int a) -{ +/* + Function: gdImageColorResolveAlpha +*/ +BGD_DECLARE(int) gdImageColorResolveAlpha(gdImagePtr im, int r, int g, int b, int a) { int c; int ct = -1; int op = -1; long rd, gd, bd, ad, dist; long mindist = 4 * 255 * 255; /* init to max poss dist */ - if (im->trueColor) - { + if (im->trueColor) { return gdTrueColorAlpha (r, g, b, a); } - for (c = 0; c < im->colorsTotal; c++) - { - if (im->open[c]) - { + for (c = 0; c < im->colorsTotal; c++) { + if (im->open[c]) { op = c; /* Save open slot */ continue; /* Color not in use */ } - if (c == im->transparent) - { - /* don't ever resolve to the color that has - * been designated as the transparent color */ - continue; - } - rd = (long) (im->red[c] - r); - gd = (long) (im->green[c] - g); - bd = (long) (im->blue[c] - b); - ad = (long) (im->alpha[c] - a); - dist = rd * rd + gd * gd + bd * bd + ad * ad; - if (dist < mindist) - { - if (dist == 0) - { - return c; /* Return exact match color */ - } - mindist = dist; - ct = c; + if (c == im->transparent) { + /* don't ever resolve to the color that has + * been designated as the transparent color */ + continue; + } + rd = (long) (im->red[c] - r); + gd = (long) (im->green[c] - g); + bd = (long) (im->blue[c] - b); + ad = (long) (im->alpha[c] - a); + dist = rd * rd + gd * gd + bd * bd + ad * ad; + if (dist < mindist) { + if (dist == 0) { + return c; /* Return exact match color */ + } + mindist = dist; + ct = c; } } - /* no exact match. We now know closest, but first try to allocate exact */ - if (op == -1) - { + /* no exact match. We now know closest, but first try to allocate exact */ + if (op == -1) { op = im->colorsTotal; - if (op == gdMaxColors) - { /* No room for more colors */ - return ct; /* Return closest available color */ - } - im->colorsTotal++; + if (op == gdMaxColors) { + /* No room for more colors */ + return ct; /* Return closest available color */ } + im->colorsTotal++; + } im->red[op] = r; im->green[op] = g; im->blue[op] = b; @@ -594,136 +811,379 @@ int gdImageColorResolveAlpha (gdImagePtr im, int r, int g, int b, int a) return op; /* Return newly allocated color */ } -void gdImageColorDeallocate (gdImagePtr im, int color) -{ - if (im->trueColor) { +/** + * Function: gdImageColorDeallocate + * + * Removes a palette entry + * + * This is a no-op for truecolor images. + * The function does not alter the image data nor the transparent color or any + * other places where this color index could have been referenced. + * The index is marked as open and will be used too for any subsequent + * or calls. Other lower + * index may be open as well, the fist open index found will be used. + * + * Parameters: + * im - The image. + * color - The palette index. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) gdImageColorDeallocate(gdImagePtr im, int color) { + if (im->trueColor || (color >= gdMaxColors) || (color < 0)) { return; } /* Mark it open. */ im->open[color] = 1; } -void gdImageColorTransparent (gdImagePtr im, int color) -{ - if (color < 0) { +/** + * Function: gdImageColorTransparent + * + * Sets the transparent color of the image + * + * Parameter: + * im - The image. + * color - The color. + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageColorTransparent(gdImagePtr im, int color) { + // Reset ::transparent + if (color == -1) { + im->transparent = -1; return; } - if (!im->trueColor) { - if((color >= im->colorsTotal)) { - return; - } - /* Make the old transparent color opaque again */ - if (im->transparent != -1) { - im->alpha[im->transparent] = gdAlphaOpaque; - } - im->alpha[color] = gdAlphaTransparent; + if (color < -1) { + return; + } + + if (im->trueColor) { + im->transparent = color; + return; } + + // Palette Image + if (color >= gdMaxColors) { + return; + } + if (im->transparent != -1) { + im->alpha[im->transparent] = gdAlphaOpaque; + } + im->alpha[color] = gdAlphaTransparent; im->transparent = color; } -void gdImagePaletteCopy (gdImagePtr to, gdImagePtr from) -{ +/* + Function: gdImagePaletteCopy +*/ +BGD_DECLARE(void) gdImagePaletteCopy(gdImagePtr to, gdImagePtr from) { int i; int x, y, p; int xlate[256]; if (to->trueColor || from->trueColor) { return; } - for (i = 0; i < 256; i++) { xlate[i] = -1; - } + }; - for (y = 0; y < to->sy; y++) { - for (x = 0; x < to->sx; x++) { - p = gdImageGetPixel(to, x, y); + for (y = 0; y < (to->sy); y++) { + for (x = 0; x < (to->sx); x++) { + /* Optimization: no gdImageGetPixel */ + p = to->pixels[y][x]; if (xlate[p] == -1) { - /* This ought to use HWB, but we don't have an alpha-aware version of that yet. */ - xlate[p] = gdImageColorClosestAlpha (from, to->red[p], to->green[p], to->blue[p], to->alpha[p]); - } - gdImageSetPixel(to, x, y, xlate[p]); - } - } - - for (i = 0; i < from->colorsTotal; i++) { + /* This ought to use HWB, but we don't have an alpha-aware + version of that yet. */ + xlate[p] = gdImageColorClosestAlpha( + from, to->red[p], to->green[p], to->blue[p], to->alpha[p]); + /*printf("Mapping %d (%d, %d, %d, %d) to %d (%d, %d, %d, %d)\n", + */ + /* p, to->red[p], to->green[p], to->blue[p], to->alpha[p], + */ + /* xlate[p], from->red[xlate[p]], from->green[xlate[p]], + * from->blue[xlate[p]], from->alpha[xlate[p]]); */ + }; + /* Optimization: no gdImageSetPixel */ + to->pixels[y][x] = xlate[p]; + }; + }; + + for (i = 0; (i < (from->colorsTotal)); i++) { to->red[i] = from->red[i]; to->blue[i] = from->blue[i]; to->green[i] = from->green[i]; to->alpha[i] = from->alpha[i]; to->open[i] = 0; - } + }; - for (i = from->colorsTotal; i < to->colorsTotal; i++) { + for (i = from->colorsTotal; (i < to->colorsTotal); i++) { to->open[i] = 1; - } + }; to->colorsTotal = from->colorsTotal; } -/* 2.0.10: before the drawing routines, some code to clip points that are - * outside the drawing window. Nick Atty (nick@canalplan.org.uk) - * - * This is the Sutherland Hodgman Algorithm, as implemented by - * Duvanenko, Robbins and Gyurcsik - SH(DRG) for short. See Dr Dobb's - * Journal, January 1996, pp107-110 and 116-117 - * - * Given the end points of a line, and a bounding rectangle (which we - * know to be from (0,0) to (SX,SY)), adjust the endpoints to be on - * the edges of the rectangle if the line should be drawn at all, - * otherwise return a failure code - */ +/* + Function: gdImageColorReplace +*/ +BGD_DECLARE(int) gdImageColorReplace(gdImagePtr im, int src, int dst) { + register int x, y; + int n = 0; -/* this does "one-dimensional" clipping: note that the second time it - * is called, all the x parameters refer to height and the y to width - * - the comments ignore this (if you can understand it when it's - * looking at the X parameters, it should become clear what happens on - * the second call!) The code is simplified from that in the article, - * as we know that gd images always start at (0,0) - */ -/* 2.0.26, TBB: we now have to respect a clipping rectangle, it won't - necessarily start at 0. */ + if (src == dst) { + return 0; + } -static int clip_1d(int *x0, int *y0, int *x1, int *y1, int mindim, int maxdim) { - double m; /* gradient of line */ +#define REPLACING_LOOP(pixel) \ + do { \ + for (y = im->cy1; y <= im->cy2; y++) { \ + for (x = im->cx1; x <= im->cx2; x++) { \ + if (pixel(im, x, y) == src) { \ + gdImageSetPixel(im, x, y, dst); \ + n++; \ + } \ + } \ + } \ + } while (0) - if (*x0 < mindim) { /* start of line is left of window */ - if(*x1 < mindim) { /* as is the end, so the line never cuts the window */ - return 0; - } - m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ - /* adjust x0 to be on the left boundary (ie to be zero), and y0 to match */ - *y0 -= (int)(m * (*x0 - mindim)); - *x0 = mindim; - /* now, perhaps, adjust the far end of the line as well */ - if (*x1 > maxdim) { - *y1 += (int)(m * (maxdim - *x1)); - *x1 = maxdim; - } - return 1; - } - if (*x0 > maxdim) { /* start of line is right of window - complement of above */ - if (*x1 > maxdim) { /* as is the end, so the line misses the window */ - return 0; - } - m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ - *y0 += (int)(m * (maxdim - *x0)); /* adjust so point is on the right boundary */ - *x0 = maxdim; - /* now, perhaps, adjust the end of the line */ - if (*x1 < mindim) { - *y1 -= (int)(m * (*x1 - mindim)); - *x1 = mindim; - } - return 1; + if (im->trueColor) { + REPLACING_LOOP(gdImageTrueColorPixel); + } else { + REPLACING_LOOP(gdImagePalettePixel); } - /* the final case - the start of the line is inside the window */ - if (*x1 > maxdim) { /* other end is outside to the right */ - m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + +#undef REPLACING_LOOP + + return n; +} + +/* + Function: gdImageColorReplaceThreshold + Note: threshold semantics changed in versions >=2.3.4 — the value now scales + linearly with perceptual color distance. Callers using threshold values + tuned against the old behavior should apply new_t = sqrt(old_t / 100) * 100 + to approximate the previous behavior. This is due to a bug fix in the color + distance calculation, which previously did not take the square root + of the sum of squares, and thus returned a value that was the square + of the actual perceptual color distance. + The new behavior is more intuitive and consistent with common color distance + metrics +*/ +BGD_DECLARE(int) +gdImageColorReplaceThreshold(gdImagePtr im, int src, int dst, float threshold) { + register int x, y; + int n = 0; + + if (src == dst) { + return 0; + } + +#define REPLACING_LOOP(pixel) \ + do { \ + for (y = im->cy1; y <= im->cy2; y++) { \ + for (x = im->cx1; x <= im->cx2; x++) { \ + if (gdColorMatch(im, src, pixel(im, x, y), threshold)) { \ + gdImageSetPixel(im, x, y, dst); \ + n++; \ + } \ + } \ + } \ + } while (0) + + if (im->trueColor) { + REPLACING_LOOP(gdImageTrueColorPixel); + } else { + REPLACING_LOOP(gdImagePalettePixel); + } + +#undef REPLACING_LOOP + + return n; +} + +static int colorCmp(const void *x, const void *y) { + int a = *(int const *)x; + int b = *(int const *)y; + return (a > b) - (a < b); +} + +/* + Function: gdImageColorReplaceArray +*/ +BGD_DECLARE(int) +gdImageColorReplaceArray(gdImagePtr im, int len, int *src, int *dst) { + register int x, y; + int c, *d, *base; + int i, n = 0; + + if (len <= 0 || src == dst) { + return 0; + } + if (len == 1) { + return gdImageColorReplace(im, src[0], dst[0]); + } + if (overflow2(len, sizeof(int) << 1)) { + return -1; + } + base = (int *)gdMalloc(len * (sizeof(int) << 1)); + if (!base) { + return -1; + } + for (i = 0; i < len; i++) { + base[(i << 1)] = src[i]; + base[(i << 1) + 1] = dst[i]; + } + qsort(base, len, sizeof(int) << 1, colorCmp); + +#define REPLACING_LOOP(pixel) \ + do { \ + for (y = im->cy1; y <= im->cy2; y++) { \ + for (x = im->cx1; x <= im->cx2; x++) { \ + c = pixel(im, x, y); \ + if ((d = (int *)bsearch(&c, base, len, sizeof(int) << 1, \ + colorCmp))) { \ + gdImageSetPixel(im, x, y, d[1]); \ + n++; \ + } \ + } \ + } \ + } while (0) + + if (im->trueColor) { + REPLACING_LOOP(gdImageTrueColorPixel); + } else { + REPLACING_LOOP(gdImagePalettePixel); + } + +#undef REPLACING_LOOP + + gdFree(base); + return n; +} + +/* + Function: gdImageColorReplaceCallback +*/ +BGD_DECLARE(int) +gdImageColorReplaceCallback(gdImagePtr im, gdCallbackImageColor callback) { + int c, d, n = 0; + + if (!callback) { + return 0; + } + if (im->trueColor) { + register int x, y; + + for (y = im->cy1; y <= im->cy2; y++) { + for (x = im->cx1; x <= im->cx2; x++) { + c = gdImageTrueColorPixel(im, x, y); + if ((d = callback(im, c)) != c) { + gdImageSetPixel(im, x, y, d); + n++; + } + } + } + } else { /* palette */ + int *sarr, *darr; + int k, len = 0; + + sarr = (int *)gdCalloc(im->colorsTotal, sizeof(int)); + if (!sarr) { + return -1; + } + for (c = 0; c < im->colorsTotal; c++) { + if (!im->open[c]) { + sarr[len++] = c; + } + } + darr = (int *)gdCalloc(len, sizeof(int)); + if (!darr) { + gdFree(sarr); + return -1; + } + for (k = 0; k < len; k++) { + darr[k] = callback(im, sarr[k]); + } + n = gdImageColorReplaceArray(im, k, sarr, darr); + gdFree(darr); + gdFree(sarr); + } + return n; +} + +/* 2.0.10: before the drawing routines, some code to clip points that are + * outside the drawing window. Nick Atty (nick@canalplan.org.uk) + * + * This is the Sutherland Hodgman Algorithm, as implemented by + * Duvanenko, Robbins and Gyurcsik - SH(DRG) for short. See Dr Dobb's + * Journal, January 1996, pp107-110 and 116-117 + * + * Given the end points of a line, and a bounding rectangle (which we + * know to be from (0,0) to (SX,SY)), adjust the endpoints to be on + * the edges of the rectangle if the line should be drawn at all, + * otherwise return a failure code */ + +/* this does "one-dimensional" clipping: note that the second time it + is called, all the x parameters refer to height and the y to width + - the comments ignore this (if you can understand it when it's + looking at the X parameters, it should become clear what happens on + the second call!) The code is simplified from that in the article, + as we know that gd images always start at (0,0) */ + +/* 2.0.26, TBB: we now have to respect a clipping rectangle, it won't + necessarily start at 0. */ + +static int clip_1d(int *x0, int *y0, int *x1, int *y1, int mindim, int maxdim) { + double m; /* gradient of line */ + if (*x0 < mindim) { + /* start of line is left of window */ + if (*x1 < mindim) /* as is the end, so the line never cuts the window */ + return 0; + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + /* adjust x0 to be on the left boundary (ie to be zero), and y0 to match + */ + *y0 -= (int)(m * (*x0 - mindim)); + *x0 = mindim; + /* now, perhaps, adjust the far end of the line as well */ + if (*x1 > maxdim) { + *y1 += (int)(m * (maxdim - *x1)); + *x1 = maxdim; + } + return 1; + } + if (*x0 > maxdim) { + /* start of line is right of window - + complement of above */ + if (*x1 > maxdim) /* as is the end, so the line misses the window */ + return 0; + m = (*y1 - *y0) / + (double)(*x1 - *x0); /* calculate the slope of the line */ + *y0 += (int)(m * (maxdim - *x0)); /* adjust so point is on the right + boundary */ + *x0 = maxdim; + /* now, perhaps, adjust the end of the line */ + if (*x1 < mindim) { + *y1 -= (int)(m * (*x1 - mindim)); + *x1 = mindim; + } + return 1; + } + /* the final case - the start of the line is inside the window */ + if (*x1 > maxdim) { + /* other end is outside to the right */ + m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ *y1 += (int)(m * (maxdim - *x1)); *x1 = maxdim; return 1; } - if (*x1 < mindim) { /* other end is outside to the left */ - m = (*y1 - *y0)/(double)(*x1 - *x0); /* calculate the slope of the line */ + if (*x1 < mindim) { + /* other end is outside to the left */ + m = (*y1 - *y0) / + (double)(*x1 - *x0); /* calculate the slope of the line */ *y1 -= (int)(m * (*x1 - mindim)); *x1 = mindim; return 1; @@ -732,8 +1192,14 @@ static int clip_1d(int *x0, int *y0, int *x1, int *y1, int mindim, int maxdim) { return 1; } -void gdImageSetPixel (gdImagePtr im, int x, int y, int color) -{ +/** + * Group: Pixels + */ + +/* + Function: gdImageSetPixel +*/ +BGD_DECLARE(void) gdImageSetPixel(gdImagePtr im, int x, int y, int color) { int p; switch (color) { case gdStyled: @@ -743,7 +1209,7 @@ void gdImageSetPixel (gdImagePtr im, int x, int y, int color) } else { p = im->style[im->stylePos++]; } - if (p != gdTransparent) { + if (p != (gdTransparent)) { gdImageSetPixel(im, x, y, p); } im->stylePos = im->stylePos % im->styleLength; @@ -754,7 +1220,7 @@ void gdImageSetPixel (gdImagePtr im, int x, int y, int color) return; } p = im->style[im->stylePos++]; - if (p != gdTransparent && p != 0) { + if ((p != gdTransparent) && (p != 0)) { gdImageSetPixel(im, x, y, gdBrushed); } im->stylePos = im->stylePos % im->styleLength; @@ -767,11 +1233,11 @@ void gdImageSetPixel (gdImagePtr im, int x, int y, int color) break; case gdAntiAliased: /* This shouldn't happen (2.0.26) because we just call - gdImageAALine now, but do something sane. */ + gdImageAALine now, but do something sane. */ gdImageSetPixel(im, x, y, im->AA_color); break; default: - if (gdImageBoundsSafe(im, x, y)) { + if (gdImageBoundsSafeMacro(im, x, y)) { if (im->trueColor) { switch (im->alphaBlendingFlag) { default: @@ -797,28 +1263,16 @@ void gdImageSetPixel (gdImagePtr im, int x, int y, int color) } } -int gdImageGetTrueColorPixel (gdImagePtr im, int x, int y) -{ - int p = gdImageGetPixel(im, x, y); - - if (!im->trueColor) { - return gdTrueColorAlpha(im->red[p], im->green[p], im->blue[p], (im->transparent == p) ? gdAlphaTransparent : im->alpha[p]); - } else { - return p; - } -} - -static void gdImageBrushApply (gdImagePtr im, int x, int y) -{ +static void gdImageBrushApply(gdImagePtr im, int x, int y) { int lx, ly; - int hy, hx; + int hy; + int hx; int x1, y1, x2, y2; int srcx, srcy; if (!im->brush) { return; } - hy = gdImageSY(im->brush) / 2; y1 = y - hy; y2 = y1 + gdImageSY(im->brush); @@ -826,10 +1280,9 @@ static void gdImageBrushApply (gdImagePtr im, int x, int y) x1 = x - hx; x2 = x1 + gdImageSX(im->brush); srcy = 0; - if (im->trueColor) { if (im->brush->trueColor) { - for (ly = y1; ly < y2; ly++) { + for (ly = y1; (ly < y2); ly++) { srcx = 0; for (lx = x1; (lx < x2); lx++) { int p; @@ -844,9 +1297,9 @@ static void gdImageBrushApply (gdImagePtr im, int x, int y) } } else { /* 2.0.12: Brush palette, image truecolor (thanks to Thorben Kundinger for pointing out the issue) */ - for (ly = y1; ly < y2; ly++) { + for (ly = y1; (ly < y2); ly++) { srcx = 0; - for (lx = x1; lx < x2; lx++) { + for (lx = x1; (lx < x2); lx++) { int p, tc; p = gdImageGetPixel(im->brush, srcx, srcy); tc = gdImageGetTrueColorPixel(im->brush, srcx, srcy); @@ -860,19 +1313,19 @@ static void gdImageBrushApply (gdImagePtr im, int x, int y) } } } else { - for (ly = y1; ly < y2; ly++) { + for (ly = y1; (ly < y2); ly++) { srcx = 0; - for (lx = x1; lx < x2; lx++) { + for (lx = x1; (lx < x2); lx++) { int p; p = gdImageGetPixel(im->brush, srcx, srcy); /* Allow for non-square brushes! */ if (p != gdImageGetTransparent(im->brush)) { - /* Truecolor brush. Very slow on a palette destination. */ if (im->brush->trueColor) { - gdImageSetPixel(im, lx, ly, gdImageColorResolveAlpha(im, gdTrueColorGetRed(p), - gdTrueColorGetGreen(p), - gdTrueColorGetBlue(p), - gdTrueColorGetAlpha(p))); + gdImageSetPixel(im, lx, ly, + gdImageColorResolveAlpha(im, gdTrueColorGetRed(p), + gdTrueColorGetGreen(p), + gdTrueColorGetBlue(p), + gdTrueColorGetAlpha(p))); } else { gdImageSetPixel(im, lx, ly, im->brushColorMap[p]); } @@ -884,8 +1337,7 @@ static void gdImageBrushApply (gdImagePtr im, int x, int y) } } -static void gdImageTileApply (gdImagePtr im, int x, int y) -{ +static void gdImageTileApply(gdImagePtr im, int x, int y) { gdImagePtr tile = im->tile; int srcx, srcy; int p; @@ -908,11 +1360,7 @@ static void gdImageTileApply (gdImagePtr im, int x, int y) if (p != gdImageGetTransparent(tile)) { if (tile->trueColor) { /* Truecolor tile. Very slow on a palette destination. */ - gdImageSetPixel(im, x, y, gdImageColorResolveAlpha(im, - gdTrueColorGetRed(p), - gdTrueColorGetGreen(p), - gdTrueColorGetBlue(p), - gdTrueColorGetAlpha(p))); + gdImageSetPixel(im, x, y, gdImageColorResolveAlpha(im, gdTrueColorGetRed(p), gdTrueColorGetGreen(p), gdTrueColorGetBlue(p), gdTrueColorGetAlpha(p))); } else { gdImageSetPixel(im, x, y, im->tileColorMap[p]); } @@ -920,41 +1368,23 @@ static void gdImageTileApply (gdImagePtr im, int x, int y) } } - -static int gdImageTileGet (gdImagePtr im, int x, int y) -{ - int srcx, srcy; - int tileColor,p; - if (!im->tile) { - return -1; - } - srcx = x % gdImageSX(im->tile); - srcy = y % gdImageSY(im->tile); - p = gdImageGetPixel(im->tile, srcx, srcy); - - if (p == im->tile->transparent) { - tileColor = im->transparent; - } else if (im->trueColor) { - if (im->tile->trueColor) { - tileColor = p; - } else { - tileColor = gdTrueColorAlpha( gdImageRed(im->tile,p), gdImageGreen(im->tile,p), gdImageBlue (im->tile,p), gdImageAlpha (im->tile,p)); - } - } else { - if (im->tile->trueColor) { - tileColor = gdImageColorResolveAlpha(im, gdTrueColorGetRed (p), gdTrueColorGetGreen (p), gdTrueColorGetBlue (p), gdTrueColorGetAlpha (p)); - } else { - tileColor = p; - tileColor = gdImageColorResolveAlpha(im, gdImageRed (im->tile,p), gdImageGreen (im->tile,p), gdImageBlue (im->tile,p), gdImageAlpha (im->tile,p)); - } - } - return tileColor; -} - - -int gdImageGetPixel (gdImagePtr im, int x, int y) -{ - if (gdImageBoundsSafe(im, x, y)) { +/** + * Function: gdImageGetPixel + * + * Gets a pixel color as stored in the image. + * + * Parameters: + * im - The image. + * x - The x-coordinate. + * y - The y-coordinate. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(int) gdImageGetPixel(gdImagePtr im, int x, int y) { + if (gdImageBoundsSafeMacro(im, x, y)) { if (im->trueColor) { return im->tpixels[y][x]; } else { @@ -965,99 +1395,45 @@ int gdImageGetPixel (gdImagePtr im, int x, int y) } } -void gdImageAABlend (gdImagePtr im) -{ - (void)im; -} - -static void _gdImageFilledHRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color); - -gdImagePtr gdImageClone (gdImagePtr src) { - gdImagePtr dst; - register int i, x; - - if (src->trueColor) { - dst = gdImageCreateTrueColor(src->sx , src->sy); - } else { - dst = gdImageCreate(src->sx , src->sy); - } - - if (dst == NULL) { - return NULL; - } - - if (src->trueColor == 0) { - dst->colorsTotal = src->colorsTotal; - for (i = 0; i < gdMaxColors; i++) { - dst->red[i] = src->red[i]; - dst->green[i] = src->green[i]; - dst->blue[i] = src->blue[i]; - dst->alpha[i] = src->alpha[i]; - dst->open[i] = src->open[i]; - } - for (i = 0; i < src->sy; i++) { - for (x = 0; x < src->sx; x++) { - dst->pixels[i][x] = src->pixels[i][x]; - } - } +/** + * Function: gdImageGetTrueColorPixel + * + * Gets a pixel color always as truecolor value. + * + * Parameters: + * im - The image. + * x - The x-coordinate. + * y - The y-coordinate. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) gdImageGetTrueColorPixel(gdImagePtr im, int x, int y) { + int p = gdImageGetPixel(im, x, y); + if (!im->trueColor) { + return gdTrueColorAlpha(im->red[p], im->green[p], im->blue[p], + (im->transparent == p) ? gdAlphaTransparent + : im->alpha[p]); } else { - for (i = 0; i < src->sy; i++) { - for (x = 0; x < src->sx; x++) { - dst->tpixels[i][x] = src->tpixels[i][x]; - } - } - } - - dst->interlace = src->interlace; - - dst->alphaBlendingFlag = src->alphaBlendingFlag; - dst->saveAlphaFlag = src->saveAlphaFlag; - dst->AA = src->AA; - dst->AA_color = src->AA_color; - dst->AA_dont_blend = src->AA_dont_blend; - - dst->cx1 = src->cx1; - dst->cy1 = src->cy1; - dst->cx2 = src->cx2; - dst->cy2 = src->cy2; - - dst->res_x = src->res_x; - dst->res_y = src->res_y; - - dst->interpolation_id = src->interpolation_id; - dst->interpolation = src->interpolation; - - if (src->brush) { - dst->brush = gdImageClone(src->brush); - } - - if (src->tile) { - dst->tile = gdImageClone(src->tile); + return p; } +} - if (src->style) { - gdImageSetStyle(dst, src->style, src->styleLength); - dst->stylePos = src->stylePos; - } - for (i = 0; i < gdMaxColors; i++) { - dst->brushColorMap[i] = src->brushColorMap[i]; - dst->tileColorMap[i] = src->tileColorMap[i]; - } +/** + * Group: Primitives + */ - if (src->polyAllocated > 0 && overflow2(sizeof(int), src->polyAllocated) == 0) { - dst->polyInts = gdMalloc (sizeof (int) * src->polyAllocated); - dst->polyAllocated = src->polyAllocated; - for (i = 0; i < src->polyAllocated; i++) { - dst->polyInts[i] = src->polyInts[i]; - } - } +/* + Function: gdImageAABlend - return dst; -} + NO-OP, kept for library compatibility. +*/ +BGD_DECLARE(void) gdImageAABlend(gdImagePtr im) { (void)im; } +static void _gdImageFilledHRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color); -static void gdImageHLine(gdImagePtr im, int y, int x1, int x2, int col) -{ +static void gdImageHLine(gdImagePtr im, int y, int x1, int x2, int col) { if (im->thick > 1) { int thickhalf = im->thick >> 1; _gdImageFilledHRectangle(im, x1, y - thickhalf, x2, y + im->thick - thickhalf - 1, col); @@ -1075,8 +1451,7 @@ static void gdImageHLine(gdImagePtr im, int y, int x1, int x2, int col) return; } -static void gdImageVLine(gdImagePtr im, int x, int y1, int y2, int col) -{ +static void gdImageVLine(gdImagePtr im, int x, int y1, int y2, int col) { if (im->thick > 1) { int thickhalf = im->thick >> 1; gdImageFilledRectangle(im, x - thickhalf, y1, x + im->thick - thickhalf - 1, y2, col); @@ -1094,9 +1469,12 @@ static void gdImageVLine(gdImagePtr im, int x, int y1, int y2, int col) return; } -/* Bresenham as presented in Foley & Van Dam */ -void gdImageLine (gdImagePtr im, int x1, int y1, int x2, int y2, int color) -{ +/* + Function: gdImageLine + + Bresenham as presented in Foley & Van Dam. +*/ +BGD_DECLARE(void) gdImageLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { int dx, dy, incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag; int wid; int w, wstart; @@ -1268,121 +1646,12 @@ TBB: but watch out for /0! */ } } +static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert); /* - * Added on 2003/12 by Pierre-Alain Joye (pajoye@pearfr.org) - * */ -#define BLEND_COLOR(a, nc, c, cc) \ -nc = (cc) + (((((c) - (cc)) * (a)) + ((((c) - (cc)) * (a)) >> 8) + 0x80) >> 8); - -inline static void gdImageSetAAPixelColor(gdImagePtr im, int x, int y, int color, int t) -{ - int dr,dg,db,p,r,g,b; - dr = gdTrueColorGetRed(color); - dg = gdTrueColorGetGreen(color); - db = gdTrueColorGetBlue(color); - - p = gdImageGetPixel(im,x,y); - r = gdTrueColorGetRed(p); - g = gdTrueColorGetGreen(p); - b = gdTrueColorGetBlue(p); - - BLEND_COLOR(t, dr, r, dr); - BLEND_COLOR(t, dg, g, dg); - BLEND_COLOR(t, db, b, db); - im->tpixels[y][x]=gdTrueColorAlpha(dr, dg, db, gdAlphaOpaque); -} - -/* - * Added on 2003/12 by Pierre-Alain Joye (pajoye@pearfr.org) - **/ -void gdImageAALine (gdImagePtr im, int x1, int y1, int x2, int y2, int col) -{ - /* keep them as 32bits */ - long x, y, inc, frac; - long dx, dy,tmp; - - if (!im->trueColor) { - /* TBB: don't crash when the image is of the wrong type */ - gdImageLine(im, x1, y1, x2, y2, col); - return; - } - - /* TBB: use the clipping rectangle */ - if (clip_1d (&x1, &y1, &x2, &y2, im->cx1, im->cx2) == 0) - return; - if (clip_1d (&y1, &x1, &y2, &x2, im->cy1, im->cy2) == 0) - return; - - dx = x2 - x1; - dy = y2 - y1; - - if (dx == 0 && dy == 0) { - return; - } - if (abs((int)dx) > abs((int)dy)) { - if (dx < 0) { - tmp = x1; - x1 = x2; - x2 = tmp; - tmp = y1; - y1 = y2; - y2 = tmp; - dx = x2 - x1; - dy = y2 - y1; - } - y = y1; - inc = (dy * 65536) / dx; - frac = 0; - for (x = x1; x <= x2; x++) { - gdImageSetAAPixelColor(im, x, y, col, (frac >> 8) & 0xFF); - if (y + 1 < im->sy) { - gdImageSetAAPixelColor(im, x, y + 1, col, (~frac >> 8) & 0xFF); - } - frac += inc; - if (frac >= 65536) { - frac -= 65536; - y++; - } else if (frac < 0) { - frac += 65536; - y--; - } - } - } else { - if (dy < 0) { - tmp = x1; - x1 = x2; - x2 = tmp; - tmp = y1; - y1 = y2; - y2 = tmp; - dx = x2 - x1; - dy = y2 - y1; - } - x = x1; - inc = (dx * 65536) / dy; - frac = 0; - for (y = y1; y <= y2; y++) { - gdImageSetAAPixelColor(im, x, y, col, (frac >> 8) & 0xFF); - if (x + 1 < im->sx) { - gdImageSetAAPixelColor(im, x + 1, y, col, (~frac >> 8) & 0xFF); - } - frac += inc; - if (frac >= 65536) { - frac -= 65536; - x++; - } else if (frac < 0) { - frac += 65536; - x--; - } - } - } -} - -static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert); - -void gdImageDashedLine (gdImagePtr im, int x1, int y1, int x2, int y2, int color) -{ + Function: gdImageDashedLine +*/ +BGD_DECLARE(void) gdImageDashedLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { int dx, dy, incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag; int dashStep = 0; int on = 1; @@ -1494,8 +1763,7 @@ void gdImageDashedLine (gdImagePtr im, int x1, int y1, int x2, int y2, int color } } -static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert) -{ +static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *dashStepP, int wid, int vert) { int dashStep = *dashStepP; int on = *onP; int w, wstart; @@ -1522,8 +1790,27 @@ static void dashedSet (gdImagePtr im, int x, int y, int color, int *onP, int *da *onP = on; } -void gdImageChar (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) -{ + +/** + * Function: gdImageChar + * + * Draws a single character. + * + * Parameters: + * im - The image to draw onto. + * f - The raster font. + * x - The x coordinate of the upper left pixel. + * y - The y coordinate of the upper left pixel. + * c - The character. + * color - The color. + * + * Variants: + * - + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageChar(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) { int cx, cy; int px, py; int fline; @@ -1550,8 +1837,10 @@ void gdImageChar (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) } } -void gdImageCharUp (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) -{ +/** + * Function: gdImageCharUp + */ +BGD_DECLARE(void) gdImageCharUp(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) { int cx, cy; int px, py; int fline; @@ -1578,8 +1867,30 @@ void gdImageCharUp (gdImagePtr im, gdFontPtr f, int x, int y, int c, int color) } } -void gdImageString (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) -{ +/** + * Function: gdImageString + * + * Draws a character string. + * + * Parameters: + * im - The image to draw onto. + * f - The raster font. + * x - The x coordinate of the upper left pixel. + * y - The y coordinate of the upper left pixel. + * c - The character string. + * color - The color. + * + * Variants: + * - + * - + * - + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageString(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) { int i; int l; l = strlen ((char *) s); @@ -1589,8 +1900,10 @@ void gdImageString (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, } } -void gdImageStringUp (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) -{ +/** + * Function: gdImageStringUp + */ +BGD_DECLARE(void) gdImageStringUp(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color) { int i; int l; l = strlen ((char *) s); @@ -1602,8 +1915,10 @@ void gdImageStringUp (gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s static int strlen16 (unsigned short *s); -void gdImageString16 (gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) -{ +/** + * Function: gdImageString16 + */ +BGD_DECLARE(void) gdImageString16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) { int i; int l; l = strlen16(s); @@ -1613,19 +1928,20 @@ void gdImageString16 (gdImagePtr im, gdFontPtr f, int x, int y, unsigned short * } } -void gdImageStringUp16 (gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) -{ +/** + * Function: gdImageStringUp16 + */ +BGD_DECLARE(void) gdImageStringUp16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color) { int i; int l; l = strlen16(s); - for (i = 0; i < l; i++) { + for (i = 0; (i < l); i++) { gdImageCharUp(im, f, x, y, s[i], color); y -= f->w; } } -static int strlen16 (unsigned short *s) -{ +static int strlen16(unsigned short *s) { int len = 0; while (*s) { s++; @@ -1650,13 +1966,18 @@ long lsqrt (long n) cx and cy are the center in pixels; w and h are the horizontal and vertical diameter in pixels. */ -void gdImageArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color) -{ +/* + Function: gdImageArc +*/ +BGD_DECLARE(void) +gdImageArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color) { gdImageFilledArc(im, cx, cy, w, h, s, e, color, gdNoFill); } -void gdImageFilledArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color, int style) -{ +/* + Function: gdImageFilledArc +*/ +BGD_DECLARE(void) gdImageFilledArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color, int style) { gdPoint pts[363]; int i, pti; int lx = 0, ly = 0; @@ -1664,7 +1985,8 @@ void gdImageFilledArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e int startx = -1, starty = -1, endx = -1, endy = -1; if ((s % 360) == (e % 360)) { - s = 0; e = 360; + s = 0; + e = 360; } else { if (s > 360) { s = s % 360; @@ -1682,11 +2004,12 @@ void gdImageFilledArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e e += 360; } if (s == e) { - s = 0; e = 360; + s = 0; + e = 360; } } - for (i = s, pti = 1; i <= e; i++, pti++) { + for (i = s, pti = 1; (i <= e); i++, pti++) { int x, y; x = endx = ((long) gdCosT[i % 360] * (long) w / (2 * 1024)) + cx; y = endy = ((long) gdSinT[i % 360] * (long) h / (2 * 1024)) + cy; @@ -1769,15 +2092,30 @@ void gdImageFilledArc (gdImagePtr im, int cx, int cy, int w, int h, int s, int e } } -/** - * Integer Ellipse functions (gdImageEllipse and gdImageFilledEllipse) - * Function added by Pierre-Alain Joye 02/08/2003 (paj@pearfr.org) - * See the ellipse function simplification for the equation - * as well as the midpoint algorithm. +/* + * Function: gdImageEllipse + * + * Draw an ellipse, stroke only. + * + * Note: + * This function does not support . GD 3.0 supports + * actual 2D vectors operation, you may rely on it if you need better 2D drawing + * operations. + * + * Parameters: + * im - The destination image. + * src - The source image. + * mx - x-coordinate of the center. + * my - y-coordinate of the center. + * w - The ellipse width. + * h - The ellipse height. + * c - The color of the ellipse. A color identifier created with one of the + * image color allocate functions. + * + * See also: + * - */ - -void gdImageEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) -{ +BGD_DECLARE(void) gdImageEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) { int x=0,mx1=0,mx2=0,my1=0,my2=0; int64_t aq,bq,dx,dy,r,rx,ry,a,b; @@ -1788,8 +2126,10 @@ void gdImageEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) } gdImageSetPixel(im,mx+a, my, c); gdImageSetPixel(im,mx-a, my, c); - mx1 = mx-a;my1 = my; - mx2 = mx+a;my2 = my; + mx1 = mx - a; + my1 = my; + mx2 = mx + a; + my2 = my; aq = a * a; bq = b * b; @@ -1801,13 +2141,15 @@ void gdImageEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) x = a; while (x > 0){ if (r > 0) { - my1++;my2--; + my1++; + my2--; ry +=dx; r -=ry; } if (r <= 0){ x--; - mx1++;mx2--; + mx1++; + mx2--; rx -=dy; r +=rx; } @@ -1818,8 +2160,11 @@ void gdImageEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) } } -void gdImageFilledEllipse (gdImagePtr im, int mx, int my, int w, int h, int c) -{ +/* + Function: gdImageFilledEllipse +*/ +BGD_DECLARE(void) +gdImageFilledEllipse(gdImagePtr im, int mx, int my, int w, int h, int c) { int x=0,mx1=0,mx2=0,my1=0,my2=0; int64_t aq,bq,dx,dy,r,rx,ry,a,b; int i; @@ -1834,8 +2179,10 @@ void gdImageFilledEllipse (gdImagePtr im, int mx, int my, int w, int h, int c) gdImageSetPixel(im, x, my, c); } - mx1 = mx-a;my1 = my; - mx2 = mx+a;my2 = my; + mx1 = mx - a; + my1 = my; + mx2 = mx + a; + my2 = my; aq = a * a; bq = b * b; @@ -1848,13 +2195,15 @@ void gdImageFilledEllipse (gdImagePtr im, int mx, int my, int w, int h, int c) old_y2=-2; while (x > 0){ if (r > 0) { - my1++;my2--; + my1++; + my2--; ry +=dx; r -=ry; } if (r <= 0){ x--; - mx1++;mx2--; + mx1++; + mx2--; rx -=dy; r +=rx; } @@ -1868,12 +2217,15 @@ void gdImageFilledEllipse (gdImagePtr im, int mx, int my, int w, int h, int c) } } -void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) -{ +/* + Function: gdImageFillToBorder +*/ +BGD_DECLARE(void) gdImageFillToBorder(gdImagePtr im, int x, int y, int border, int color) { int lastBorder; /* Seek left */ - int leftLimit = -1, rightLimit; - int i, restoreAlphaBlending = 0; + int leftLimit, rightLimit; + int i; + int restoreAlphaBlending = 0; if (border < 0 || color < 0) { /* Refuse to fill to a non-solid border */ @@ -1886,6 +2238,8 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) } } + leftLimit = (-1); + restoreAlphaBlending = im->alphaBlendingFlag; im->alphaBlendingFlag = 0; @@ -1900,20 +2254,20 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) y = 0; } - for (i = x; i >= 0; i--) { + for (i = x; (i >= 0); i--) { if (gdImageGetPixel(im, i, y) == border) { break; } gdImageSetPixel(im, i, y, color); leftLimit = i; } - if (leftLimit == -1) { + if (leftLimit == (-1)) { im->alphaBlendingFlag = restoreAlphaBlending; return; } /* Seek right */ rightLimit = x; - for (i = (x + 1); i < im->sx; i++) { + for (i = (x + 1); (i < im->sx); i++) { if (gdImageGetPixel(im, i, y) == border) { break; } @@ -1924,7 +2278,7 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) /* Above */ if (y > 0) { lastBorder = 1; - for (i = leftLimit; i <= rightLimit; i++) { + for (i = leftLimit; (i <= rightLimit); i++) { int c = gdImageGetPixel(im, i, y - 1); if (lastBorder) { if ((c != border) && (c != color)) { @@ -1936,13 +2290,11 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) } } } - /* Below */ if (y < ((im->sy) - 1)) { lastBorder = 1; - for (i = leftLimit; i <= rightLimit; i++) { + for (i = leftLimit; (i <= rightLimit); i++) { int c = gdImageGetPixel(im, i, y + 1); - if (lastBorder) { if ((c != border) && (c != color)) { gdImageFillToBorder(im, i, y + 1, border, color); @@ -1966,22 +2318,69 @@ void gdImageFillToBorder (gdImagePtr im, int x, int y, int border, int color) * code I added a 2nd private function. */ +static int gdImageTileGet(gdImagePtr im, int x, int y) { + int srcx, srcy; + int tileColor, p; + if (!im->tile) { + return -1; + } + srcx = x % gdImageSX(im->tile); + srcy = y % gdImageSY(im->tile); + p = gdImageGetPixel(im->tile, srcx, srcy); + if (p == im->tile->transparent) { + tileColor = im->transparent; + } else if (im->trueColor) { + if (im->tile->trueColor) { + tileColor = p; + } else { + tileColor = gdTrueColorAlpha( + gdImageRed(im->tile, p), gdImageGreen(im->tile, p), + gdImageBlue(im->tile, p), gdImageAlpha(im->tile, p)); + } + } else { + if (im->tile->trueColor) { + tileColor = gdImageColorResolveAlpha( + im, gdTrueColorGetRed(p), gdTrueColorGetGreen(p), + gdTrueColorGetBlue(p), gdTrueColorGetAlpha(p)); + } else { + tileColor = gdImageColorResolveAlpha( + im, gdImageRed(im->tile, p), gdImageGreen(im->tile, p), + gdImageBlue(im->tile, p), gdImageAlpha(im->tile, p)); + } + } + return tileColor; +} + /* horizontal segment of scan line y */ -struct seg {int y, xl, xr, dy;}; +struct seg { + int y, xl, xr, dy; +}; /* max depth of stack */ #define FILL_MAX ((int)(im->sy*im->sx)/4) #define FILL_PUSH(Y, XL, XR, DY) \ - if (sp=0 && Y+(DY)y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; sp++;} + if (sp < stack + FILL_MAX && Y + (DY) >= 0 && Y + (DY) < wy2) { \ + sp->y = Y; \ + sp->xl = XL; \ + sp->xr = XR; \ + sp->dy = DY; \ + sp++; \ + } #define FILL_POP(Y, XL, XR, DY) \ - {sp--; Y = sp->y+(DY = sp->dy); XL = sp->xl; XR = sp->xr;} + { \ + sp--; \ + Y = sp->y + (DY = sp->dy); \ + XL = sp->xl; \ + XR = sp->xr; \ + } static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc); -void gdImageFill(gdImagePtr im, int x, int y, int nc) -{ +/* + Function: gdImageFill +*/ +BGD_DECLARE(void) gdImageFill(gdImagePtr im, int x, int y, int nc) { int l, x1, x2, dy; int oc; /* old pixel value */ int wx2,wy2; @@ -2006,7 +2405,8 @@ void gdImageFill(gdImagePtr im, int x, int y, int nc) return; } - wx2=im->sx;wy2=im->sy; + wx2 = im->sx; + wy2 = im->sy; oc = gdImageGetPixel(im, x, y); if (oc==nc || x<0 || x>wx2 || y<0 || y>wy2) { im->alphaBlendingFlag = alphablending_bak; @@ -2039,7 +2439,10 @@ void gdImageFill(gdImagePtr im, int x, int y, int nc) return; } - stack = (struct seg *)safe_emalloc(sizeof(struct seg), ((int)(im->sy*im->sx)/4), 1); + stack = (struct seg *)gdMalloc(sizeof(struct seg) *((int)(im->sy * im->sx) / 4)); + if (!stack) { + return; + } sp = stack; /* required! */ @@ -2078,14 +2481,13 @@ void gdImageFill(gdImagePtr im, int x, int y, int nc) } while (x<=x2); } - efree(stack); + gdFree(stack); done: im->alphaBlendingFlag = alphablending_bak; } -static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) -{ +static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) { int l, x1, x2, dy; int oc; /* old pixel value */ int wx2,wy2; @@ -2098,9 +2500,12 @@ static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) return; } - wx2=im->sx;wy2=im->sy; - - nc = gdImageTileGet(im,x,y); + wx2 = im->sx; + wy2 = im->sy; + oc = gdImageGetPixel(im, x, y); + if (oc == nc || x < 0 || x > wx2 || y < 0 || y > wy2) { + return; + } if(overflow2(im->sy, im->sx)) { return; @@ -2110,9 +2515,17 @@ static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) return; } - pts = (char *) ecalloc(im->sy * im->sx, sizeof(char)); + pts = (char *)gdCalloc(im->sy * im->sx, sizeof(char)); + if (!pts) { + return; + } - stack = (struct seg *)safe_emalloc(sizeof(struct seg), ((int)(im->sy*im->sx)/4), 1); + stack = (struct seg *)gdMalloc(sizeof(struct seg) * + ((int)(im->sy * im->sx) / 4)); + if (!stack) { + gdFree(pts); + return; + } sp = stack; oc = gdImageGetPixel(im, x, y); @@ -2140,6 +2553,10 @@ static void _gdImageFillTiled(gdImagePtr im, int x, int y, int nc) x = x1+1; do { for(; x + */ +BGD_DECLARE(void) +gdImageRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { int thick = im->thick; - int t; if (x1 == x2 && y1 == y2 && thick == 1) { gdImageSetPixel(im, x1, y1, color); @@ -2172,13 +2602,13 @@ void gdImageRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) } if (y2 < y1) { - t=y1; + int t = y1; y1 = y2; y2 = t; } if (x2 < x1) { - t = x1; + int t = x1; x1 = x2; x2 = t; } @@ -2238,8 +2668,7 @@ void gdImageRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) } } -static void _gdImageFilledHRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) -{ +static void _gdImageFilledHRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { int x, y; if (x1 == x2 && y1 == y2) { @@ -2282,8 +2711,7 @@ static void _gdImageFilledHRectangle (gdImagePtr im, int x1, int y1, int x2, int } } -static void _gdImageFilledVRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) -{ +static void _gdImageFilledVRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { int x, y; if (x1 == x2 && y1 == y2) { @@ -2326,59 +2754,188 @@ static void _gdImageFilledVRectangle (gdImagePtr im, int x1, int y1, int x2, int } } -void gdImageFilledRectangle (gdImagePtr im, int x1, int y1, int x2, int y2, int color) -{ +/* + Function: gdImageFilledRectangle +*/ +BGD_DECLARE(void) gdImageFilledRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color) { _gdImageFilledVRectangle(im, x1, y1, x2, y2, color); } -void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) -{ - int c; - int x, y; - int tox, toy; - int i; - int colorMap[gdMaxColors]; +/** + * Group: Cloning and Copying + */ - if (dst->trueColor) { - /* 2.0: much easier when the destination is truecolor. */ - /* 2.0.10: needs a transparent-index check that is still valid if - * the source is not truecolor. Thanks to Frank Warmerdam. - */ +/** + * Function: gdImageClone + * + * Clones an image + * + * Creates an exact duplicate of the given image. + * + * Parameters: + * src - The source image. + * + * Returns: + * The cloned image on success, NULL on failure. + */ +BGD_DECLARE(gdImagePtr) gdImageClone(gdImagePtr src) { + gdImagePtr dst; + register int i, x; - if (src->trueColor) { - for (y = 0; (y < h); y++) { - for (x = 0; (x < w); x++) { - int c = gdImageGetTrueColorPixel (src, srcX + x, srcY + y); - if (c != src->transparent) { - gdImageSetPixel (dst, dstX + x, dstY + y, c); - } - } - } - } else { - /* source is palette based */ - for (y = 0; (y < h); y++) { - for (x = 0; (x < w); x++) { - int c = gdImageGetPixel (src, srcX + x, srcY + y); - if (c != src->transparent) { - gdImageSetPixel(dst, dstX + x, dstY + y, gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c])); - } - } - } - } - return; + if (src->trueColor) { + dst = gdImageCreateTrueColor(src->sx , src->sy); + } else { + dst = gdImageCreate(src->sx , src->sy); } - /* Palette based to palette based */ - for (i = 0; i < gdMaxColors; i++) { - colorMap[i] = (-1); + if (dst == NULL) { + return NULL; } - toy = dstY; - for (y = srcY; y < (srcY + h); y++) { - tox = dstX; - for (x = srcX; x < (srcX + w); x++) { - int nc; - int mapTo; - c = gdImageGetPixel (src, x, y); + + if (src->trueColor == 0) { + dst->colorsTotal = src->colorsTotal; + for (i = 0; i < gdMaxColors; i++) { + dst->red[i] = src->red[i]; + dst->green[i] = src->green[i]; + dst->blue[i] = src->blue[i]; + dst->alpha[i] = src->alpha[i]; + dst->open[i] = src->open[i]; + } + for (i = 0; i < src->sy; i++) { + for (x = 0; x < src->sx; x++) { + dst->pixels[i][x] = src->pixels[i][x]; + } + } + } else { + for (i = 0; i < src->sy; i++) { + for (x = 0; x < src->sx; x++) { + dst->tpixels[i][x] = src->tpixels[i][x]; + } + } + } + + dst->interlace = src->interlace; + + dst->alphaBlendingFlag = src->alphaBlendingFlag; + dst->saveAlphaFlag = src->saveAlphaFlag; + dst->AA = src->AA; + dst->AA_color = src->AA_color; + dst->AA_dont_blend = src->AA_dont_blend; + + dst->cx1 = src->cx1; + dst->cy1 = src->cy1; + dst->cx2 = src->cx2; + dst->cy2 = src->cy2; + + 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; + + if (src->brush) { + dst->brush = gdImageClone(src->brush); + } + + if (src->tile) { + dst->tile = gdImageClone(src->tile); + } + + if (src->style) { + gdImageSetStyle(dst, src->style, src->styleLength); + dst->stylePos = src->stylePos; + } + + for (i = 0; i < gdMaxColors; i++) { + dst->brushColorMap[i] = src->brushColorMap[i]; + dst->tileColorMap[i] = src->tileColorMap[i]; + } + + if (src->polyAllocated > 0 && overflow2(sizeof(int), src->polyAllocated) == 0) { + dst->polyInts = gdMalloc (sizeof (int) * src->polyAllocated); + dst->polyAllocated = src->polyAllocated; + for (i = 0; i < src->polyAllocated; i++) { + dst->polyInts[i] = src->polyInts[i]; + } + } + + return dst; +} + +/** + * Function: gdImageCopy + * + * Copy an area of an image to another image + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the upper left corner to copy to. + * dstY - The y-coordinate of the upper left corner to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * w - The width of the area to copy. + * h - The height of the area to copy. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageCopy(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h) { + int c; + int x, y; + int tox, toy; + int i; + int colorMap[gdMaxColors]; + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } + if (dst->trueColor) { + /* 2.0: much easier when the destination is truecolor. */ + /* 2.0.10: needs a transparent-index check that is still valid if + * the source is not truecolor. Thanks to Frank Warmerdam. + */ + + if (src->trueColor) { + for (y = 0; (y < h); y++) { + for (x = 0; (x < w); x++) { + int c = gdImageGetTrueColorPixel (src, srcX + x, srcY + y); + if (c != src->transparent) { + gdImageSetPixel (dst, dstX + x, dstY + y, c); + } + } + } + } else { + /* source is palette based */ + for (y = 0; (y < h); y++) { + for (x = 0; (x < w); x++) { + int c = gdImageGetPixel (src, srcX + x, srcY + y); + if (c != src->transparent) { + gdImageSetPixel(dst, dstX + x, dstY + y, gdTrueColorAlpha(src->red[c], src->green[c], src->blue[c], src->alpha[c])); + } + } + } + } + return; + } + + /* Palette based to palette based */ + for (i = 0; i < gdMaxColors; i++) { + colorMap[i] = (-1); + } + toy = dstY; + for (y = srcY; (y < (srcY + h)); y++) { + tox = dstX; + for (x = srcX; (x < (srcX + w)); x++) { + int nc; + int mapTo; + c = gdImageGetPixel (src, x, y); /* Added 7/24/95: support transparent copies */ if (gdImageGetTransparent (src) == c) { tox++; @@ -2410,19 +2967,45 @@ void gdImageCopy (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, } } -/* This function is a substitute for real alpha channel operations, - so it doesn't pay attention to the alpha channel. */ -void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) -{ +/** + * Function: gdImageCopyMerge + * + * Copy an area of an image to another image ignoring alpha + * + * The source area will be copied to the destination are by merging the pixels. + * + * Note: + * This function is a substitute for real alpha channel operations, + * so it doesn't pay attention to the alpha channel. + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the upper left corner to copy to. + * dstY - The y-coordinate of the upper left corner to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * w - The width of the area to copy. + * h - The height of the area to copy. + * pct - The percentage in range 0..100. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageCopyMerge(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { int c, dc; int x, y; int tox, toy; int ncR, ncG, ncB; + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } toy = dstY; - - for (y = srcY; y < (srcY + h); y++) { + for (y = srcY; (y < (srcY + h)); y++) { tox = dstX; - for (x = srcX; x < (srcX + w); x++) { + for (x = srcX; (x < (srcX + w)); x++) { int nc; c = gdImageGetPixel(src, x, y); /* Added 7/24/95: support transparent copies */ @@ -2436,9 +3019,9 @@ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int s } else { dc = gdImageGetPixel(dst, tox, toy); - ncR = (int)(gdImageRed (src, c) * (pct / 100.0) + gdImageRed (dst, dc) * ((100 - pct) / 100.0)); - ncG = (int)(gdImageGreen (src, c) * (pct / 100.0) + gdImageGreen (dst, dc) * ((100 - pct) / 100.0)); - ncB = (int)(gdImageBlue (src, c) * (pct / 100.0) + gdImageBlue (dst, dc) * ((100 - pct) / 100.0)); + ncR = gdImageRed(src, c) * (pct / 100.0) + gdImageRed(dst, dc) * ((100 - pct) / 100.0); + ncG = gdImageGreen(src, c) * (pct / 100.0) + gdImageGreen(dst, dc) * ((100 - pct) / 100.0); + ncB = gdImageBlue(src, c) * (pct / 100.0) + gdImageBlue(dst, dc) * ((100 - pct) / 100.0); /* Find a reasonable color */ nc = gdImageColorResolve (dst, ncR, ncG, ncB); @@ -2450,17 +3033,46 @@ void gdImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int s } } -/* This function is a substitute for real alpha channel operations, - so it doesn't pay attention to the alpha channel. */ -void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) -{ +/** + * Function: gdImageCopyMergeGray + * + * Copy an area of an image to another image ignoring alpha + * + * The source area will be copied to the grayscaled destination area by merging + * the pixels. + * + * Note: + * This function is a substitute for real alpha channel operations, + * so it doesn't pay attention to the alpha channel. + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the upper left corner to copy to. + * dstY - The y-coordinate of the upper left corner to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * w - The width of the area to copy. + * h - The height of the area to copy. + * pct - The percentage of the source color intensity in range 0..100. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageCopyMergeGray(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { int c, dc; int x, y; int tox, toy; int ncR, ncG, ncB; float g; - toy = dstY; + if (!gdImageClipCopy(dst, &dstX, &dstY, &srcX, &srcY, &w, &h)) { + return; + } + + toy = dstY; for (y = srcY; (y < (srcY + h)); y++) { tox = dstX; for (x = srcX; (x < (srcX + w)); x++) { @@ -2471,7 +3083,6 @@ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i tox++; continue; } - /* * If it's the same image, mapping is NOT trivial since we * merge with greyscale target, but if pct is 100, the grey @@ -2481,12 +3092,11 @@ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i nc = c; } else { dc = gdImageGetPixel(dst, tox, toy); - g = (0.29900f * gdImageRed(dst, dc)) + (0.58700f * gdImageGreen(dst, dc)) + (0.11400f * gdImageBlue(dst, dc)); - - ncR = (int)(gdImageRed (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); - ncG = (int)(gdImageGreen (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); - ncB = (int)(gdImageBlue (src, c) * (pct / 100.0f) + g * ((100 - pct) / 100.0)); + g = 0.29900 * gdImageRed(dst, dc) + 0.58700 * gdImageGreen(dst, dc) + 0.11400 * gdImageBlue(dst, dc); + ncR = gdImageRed(src, c) * (pct / 100.0) + g * ((100 - pct) / 100.0); + ncG = gdImageGreen(src, c) * (pct / 100.0) + g * ((100 - pct) / 100.0); + ncB = gdImageBlue(src, c) * (pct / 100.0) + g * ((100 - pct) / 100.0); /* First look for an exact match */ nc = gdImageColorExact(dst, ncR, ncG, ncB); @@ -2506,8 +3116,32 @@ void gdImageCopyMergeGray (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i } } -void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) -{ +/** + * Function: gdImageCopyResized + * + * Copy a resized area from an image to another image + * + * If the source and destination area differ in size, the area will be resized + * using nearest-neighbor interpolation. + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the upper left corner to copy to. + * dstY - The y-coordinate of the upper left corner to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * dstW - The width of the area to copy to. + * dstH - The height of the area to copy to. + * srcW - The width of the area to copy from. + * srcH - The height of the area to copy from. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageCopyResized(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { int c; int x, y; int tox, toy; @@ -2515,17 +3149,30 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int int i; int colorMap[gdMaxColors]; /* Stretch vectors */ - int *stx, *sty; - + int *stx; + int *sty; + /* We only need to use floating point to determine the correct + stretch vector for one line's worth. */ if (overflow2(sizeof(int), srcW)) { return; } if (overflow2(sizeof(int), srcH)) { return; } + if (!gdImageClipCopyResized(dst, &dstX, &dstY, &dstW, &dstH, &srcX, &srcY, + &srcW, &srcH)) { + return; + } + stx = (int *)gdMalloc(sizeof(int) * srcW); + if (!stx) { + return; + } - stx = (int *) gdMalloc (sizeof (int) * srcW); sty = (int *) gdMalloc (sizeof (int) * srcH); + if (!sty) { + gdFree(stx); + return; + } /* Fixed by Mao Morimoto 2.0.16 */ for (i = 0; (i < srcW); i++) { @@ -2548,7 +3195,6 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int continue; } if (dst->trueColor) { - /* 2.0.9: Thorben Kundinger: Maybe the source image is not a truecolor image */ if (!src->trueColor) { int tmp = gdImageGetPixel (src, x, y); mapTo = gdImageGetTrueColorPixel (src, x, y); @@ -2576,10 +3222,7 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int } if (src->trueColor) { /* Remap to the palette available in the destination image. This is slow and works badly. */ - mapTo = gdImageColorResolveAlpha(dst, gdTrueColorGetRed(c), - gdTrueColorGetGreen(c), - gdTrueColorGetBlue(c), - gdTrueColorGetAlpha (c)); + mapTo = gdImageColorResolveAlpha(dst, gdTrueColorGetRed(c), gdTrueColorGetGreen(c), gdTrueColorGetBlue(c), gdTrueColorGetAlpha(c)); } else { /* Have we established a mapping for this color? */ if (colorMap[c] == (-1)) { @@ -2588,11 +3231,7 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int nc = c; } else { /* Find or create the best match */ - /* 2.0.5: can't use gdTrueColorGetRed, etc with palette */ - nc = gdImageColorResolveAlpha(dst, gdImageRed(src, c), - gdImageGreen(src, c), - gdImageBlue(src, c), - gdImageAlpha(src, c)); + nc = gdImageColorResolveAlpha(dst, gdImageRed(src, c), gdImageGreen(src, c), gdImageBlue(src, c), gdImageAlpha(src, c)); } colorMap[c] = nc; } @@ -2611,62 +3250,189 @@ void gdImageCopyResized (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int gdFree (sty); } +/** + * Function: gdImageCopyRotated + * + * Copy a rotated area from an image to another image + * + * The area is counter-clockwise rotated using nearest-neighbor interpolation. + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the center of the area to copy to. + * dstY - The y-coordinate of the center of the area to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * srcW - The width of the area to copy from. + * srcH - The height of the area to copy from. + * angle - The angle in degrees. + * + * See also: + * - + */ +BGD_DECLARE(void) +gdImageCopyRotated(gdImagePtr dst, gdImagePtr src, double dstX, double dstY, + int srcX, int srcY, int srcWidth, int srcHeight, int angle) { + double dx, dy; + double radius = sqrt(srcWidth * srcWidth + srcHeight * srcHeight); + double aCos = cos(angle * .0174532925); + double aSin = sin(angle * .0174532925); + double scX = srcX + ((double)srcWidth) / 2; + double scY = srcY + ((double)srcHeight) / 2; + int cmap[gdMaxColors]; + int i; + const double safe_max = (double)INT_MAX / 2.0; + const double safe_min = (double)INT_MIN / 2.0; + double dyMin, dyMax, dxMin, dxMax; + + /* + 2.0.34: transparency preservation. The transparentness of + the transparent color is more important than its hue. + */ + if (src->transparent != -1) { + if (dst->transparent == -1) { + dst->transparent = src->transparent; + } + } + + for (i = 0; (i < gdMaxColors); i++) { + cmap[i] = (-1); + } + + dyMin = dstY - radius; + dyMax = dstY + radius; + dxMin = dstX - radius; + dxMax = dstX + radius; + + /* clamp loop bounds so sx/sy casts are always safe */ + if (dxMin < safe_min + radius + scX) + dxMin = safe_min + radius + scX; + if (dxMax > safe_max - radius - scX) + dxMax = safe_max - radius - scX; + if (dyMin < safe_min + radius + scY) + dyMin = safe_min + radius + scY; + if (dyMax > safe_max - radius - scY) + dyMax = safe_max - radius - scY; + + for (dy = dyMin; dy <= dyMax; dy++) { + for (dx = dxMin; dx <= dxMax; dx++) { + // for (dy = dstY - radius; (dy <= dstY + radius); dy++) { + // for (dx = dstX - radius; (dx <= dstX + radius); dx++) { + double sxd = (dx - dstX) * aCos - (dy - dstY) * aSin; + double syd = (dy - dstY) * aCos + (dx - dstX) * aSin; + int sx = sxd + scX; + int sy = syd + scY; + if ((sx >= srcX) && (sx < srcX + srcWidth) && (sy >= srcY) && + (sy < srcY + srcHeight)) { + int c = gdImageGetPixel(src, sx, sy); + /* 2.0.34: transparency wins */ + if (c == src->transparent) { + gdImageSetPixel(dst, dx, dy, dst->transparent); + } else if (!src->trueColor) { + /* Use a table to avoid an expensive + lookup on every single pixel */ + if (cmap[c] == -1) { + cmap[c] = gdImageColorResolveAlpha( + dst, gdImageRed(src, c), gdImageGreen(src, c), + gdImageBlue(src, c), gdImageAlpha(src, c)); + } + gdImageSetPixel(dst, dx, dy, cmap[c]); + } else { + gdImageSetPixel( + dst, dx, dy, + gdImageColorResolveAlpha( + dst, gdImageRed(src, c), gdImageGreen(src, c), + gdImageBlue(src, c), gdImageAlpha(src, c))); + } + } + } + } +} + /* When gd 1.x was first created, floating point was to be avoided. These days it is often faster than table lookups or integer arithmetic. The routine below is shamelessly, gloriously floating point. TBB */ - -void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) -{ +#define floor2(exp) ((long)exp) +/** + * Function: gdImageCopyResampled + * + * Copy a resampled area from an image to another image + * + * If the source and destination area differ in size, the area will be resized + * using bilinear interpolation for truecolor images, and nearest-neighbor + * interpolation for palette images. + * + * Parameters: + * dst - The destination image. + * src - The source image. + * dstX - The x-coordinate of the upper left corner to copy to. + * dstY - The y-coordinate of the upper left corner to copy to. + * srcX - The x-coordinate of the upper left corner to copy from. + * srcY - The y-coordinate of the upper left corner to copy from. + * dstW - The width of the area to copy to. + * dstH - The height of the area to copy to. + * srcW - The width of the area to copy from. + * srcH - The height of the area to copy from. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageCopyResampled(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH) { int x, y; - double sy1, sy2, sx1, sx2; - if (!dst->trueColor) { gdImageCopyResized (dst, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH); return; } + if (!gdImageClipCopyResized(dst, &dstX, &dstY, &dstW, &dstH, &srcX, &srcY, &srcW, &srcH)) { + return; + } for (y = dstY; (y < dstY + dstH); y++) { - sy1 = ((double) y - (double) dstY) * (double) srcH / (double) dstH; - sy2 = ((double) (y + 1) - (double) dstY) * (double) srcH / (double) dstH; for (x = dstX; (x < dstX + dstW); x++) { - double sx, sy; - double spixels = 0; - double red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0; - double alpha_factor, alpha_sum = 0.0, contrib_sum = 0.0; - sx1 = ((double) x - (double) dstX) * (double) srcW / dstW; - sx2 = ((double) (x + 1) - (double) dstX) * (double) srcW / dstW; + float sy1, sy2, sx1, sx2; + float sx, sy; + float spixels = 0.0; + float red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0; + float alpha_factor, alpha_sum = 0.0, contrib_sum = 0.0; + sy1 = ((float)(y - dstY)) * (float)srcH / (float)dstH; + sy2 = ((float)(y + 1 - dstY)) * (float)srcH / (float)dstH; sy = sy1; do { - double yportion; - if (floor_cast(sy) == floor_cast(sy1)) { - yportion = 1.0f - (sy - floor_cast(sy)); + float yportion; + if (floorf(sy) == floorf(sy1)) { + yportion = 1.0 - (sy - floorf(sy)); if (yportion > sy2 - sy1) { yportion = sy2 - sy1; } - sy = floor_cast(sy); + sy = floorf(sy); } else if (sy == floorf(sy2)) { - yportion = sy2 - floor_cast(sy2); + yportion = sy2 - floorf(sy2); } else { - yportion = 1.0f; + yportion = 1.0; } + sx1 = ((float)(x - dstX)) * (float)srcW / dstW; + sx2 = ((float)(x + 1 - dstX)) * (float)srcW / dstW; sx = sx1; do { - double xportion; - double pcontribution; + float xportion; + float pcontribution; int p; - if (floorf(sx) == floor_cast(sx1)) { - xportion = 1.0f - (sx - floor_cast(sx)); + if (floorf(sx) == floorf(sx1)) { + xportion = 1.0 - (sx - floorf(sx)); if (xportion > sx2 - sx1) { xportion = sx2 - sx1; } - sx = floor_cast(sx); + sx = floorf(sx); } else if (sx == floorf(sx2)) { - xportion = sx2 - floor_cast(sx2); + xportion = sx2 - floorf(sx2); } else { - xportion = 1.0f; + xportion = 1.0; } pcontribution = xportion * yportion; - p = gdImageGetTrueColorPixel(src, (int) sx + srcX, (int) sy + srcY); + p = gdImageGetTrueColorPixel(src, (int)sx + srcX, (int)sy + srcY); alpha_factor = ((gdAlphaMax - gdTrueColorGetAlpha(p))) * pcontribution; red += gdTrueColorGetRed (p) * alpha_factor; @@ -2676,23 +3442,19 @@ void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i alpha_sum += alpha_factor; contrib_sum += pcontribution; spixels += xportion * yportion; - sx += 1.0f; - } - while (sx < sx2); - + sx += 1.0; + } while (sx < sx2); sy += 1.0f; - } - - while (sy < sy2); + } while (sy < sy2); - if (spixels != 0.0f) { + if (spixels != 0.0) { red /= spixels; green /= spixels; blue /= spixels; alpha /= spixels; } - if ( alpha_sum != 0.0f) { - if( contrib_sum != 0.0f) { + if (alpha_sum != 0.0) { + if (contrib_sum != 0.0) { alpha_sum /= contrib_sum; } red /= alpha_sum; @@ -2709,19 +3471,49 @@ void gdImageCopyResampled (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, i } } -void gdImagePolygon (gdImagePtr im, gdPointPtr p, int n, int c) -{ +/** + * Group: Polygons + */ + +/** + * Function: gdImagePolygon + * + * Draws a closed polygon + * + * Parameters: + * im - The image. + * p - The vertices as array of s. + * n - The number of vertices. + * c - The color. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) gdImagePolygon(gdImagePtr im, gdPointPtr p, int n, int c) { if (n <= 0) { return; } - gdImageLine (im, p->x, p->y, p[n - 1].x, p[n - 1].y, c); gdImageOpenPolygon (im, p, n, c); } -void gdImageOpenPolygon (gdImagePtr im, gdPointPtr p, int n, int c) -{ +/** + * Function: gdImageOpenPolygon + * + * Draws an open polygon + * + * Parameters: + * im - The image. + * p - The vertices as array of s. + * n - The number of vertices. + * c - The color + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageOpenPolygon(gdImagePtr im, gdPointPtr p, int n, int c) { int i; int lx, ly; @@ -2731,7 +3523,7 @@ void gdImageOpenPolygon (gdImagePtr im, gdPointPtr p, int n, int c) lx = p->x; ly = p->y; - for (i = 1; i < n; i++) { + for (i = 1; (i < n); i++) { p++; gdImageLine(im, lx, ly, p->x, p->y, c); lx = p->x; @@ -2741,14 +3533,30 @@ void gdImageOpenPolygon (gdImagePtr im, gdPointPtr p, int n, int c) /* THANKS to Kirsten Schulz for the polygon fixes! */ -/* The intersection finding technique of this code could be improved - * by remembering the previous intertersection, and by using the slope. - * That could help to adjust intersections to produce a nice - * interior_extrema. - */ +/* The intersection finding technique of this code could be improved */ +/* by remembering the previous intersection, and by using the slope. */ +/* That could help to adjust intersections to produce a nice */ +/* interior_extrema. */ -void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) -{ +/** + * Function: gdImageFilledPolygon + * + * Draws a filled polygon + * + * The polygon is filled using the even-odd fillrule what can leave unfilled + * regions inside of self-intersecting polygons. This behavior might change in + * a future version. + * + * Parameters: + * im - The image. + * p - The vertices as array of s. + * n - The number of vertices. + * c - The color + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageFilledPolygon(gdImagePtr im, gdPointPtr p, int n, int c) { int i; int j; int index; @@ -2764,18 +3572,19 @@ void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) return; } - if (overflow2(sizeof(int), n)) { - return; - } - if (c == gdAntiAliased) { fill_color = im->AA_color; } else { fill_color = c; } - if (!im->polyAllocated) { + if (overflow2(sizeof(int), n)) { + return; + } im->polyInts = (int *) gdMalloc(sizeof(int) * n); + if (!im->polyInts) { + return; + } im->polyAllocated = n; } if (im->polyAllocated < n) { @@ -2785,11 +3594,14 @@ void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) if (overflow2(sizeof(int), im->polyAllocated)) { return; } - im->polyInts = (int *) gdRealloc(im->polyInts, sizeof(int) * im->polyAllocated); + im->polyInts = (int *)gdReallocEx(im->polyInts, sizeof(int) * im->polyAllocated); + if (!im->polyInts) { + return; + } } miny = p[0].y; maxy = p[0].y; - for (i = 1; i < n; i++) { + for (i = 1; (i < n); i++) { if (p[i].y < miny) { miny = p[i].y; } @@ -2820,12 +3632,9 @@ void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) maxy = im->cy2; } /* Fix in 1.3: count a vertex only once */ - for (y = miny; y <= maxy; y++) { - /*1.4 int interLast = 0; */ - /* int dirLast = 0; */ - /* int interFirst = 1; */ + for (y = miny; (y <= maxy); y++) { ints = 0; - for (i = 0; i < n; i++) { + for (i = 0; (i < n); i++) { if (!i) { ind1 = n - 1; ind2 = 0; @@ -2846,13 +3655,14 @@ void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) } else { continue; } - /* Do the following math as float intermediately, and round to ensure - * that Polygon and FilledPolygon for the same set of points have the - * same footprint. - */ - if (y >= y1 && y < y2) { - im->polyInts[ints++] = (float) ((y - y1) * (x2 - x1)) / (float) (y2 - y1) + 0.5 + x1; - } else if (y == pmaxy && y == y2) { + + /* Do the following math as float intermediately, and round to + * ensure that Polygon and FilledPolygon for the same set of points + * have the same footprint. */ + + if ((y >= y1) && (y < y2)) { + im->polyInts[ints++] = (int)((float)((y - y1) * (x2 - x1)) / (float)(y2 - y1) + 0.5 + x1); + } else if ((y == pmaxy) && (y == y2)) { im->polyInts[ints++] = x2; } } @@ -2871,19 +3681,34 @@ void gdImageFilledPolygon (gdImagePtr im, gdPointPtr p, int n, int c) } im->polyInts[j] = index; } - for (i = 0; i < ints - 1; i += 2) { + for (i = 0; (i < (ints - 1)); i += 2) { gdImageLine(im, im->polyInts[i], y, im->polyInts[i + 1], y, fill_color); } } - /* If we are drawing this AA, then redraw the border with AA lines. */ if (c == gdAntiAliased) { gdImagePolygon(im, p, n, c); } } -void gdImageSetStyle (gdImagePtr im, int *style, int noOfPixels) -{ +/** + * Group: other + */ + +static void gdImageSetAAPixelColor(gdImagePtr im, int x, int y, int color, + int t); + +/** + * Function: gdImageSetStyle + * + * Sets the style for following drawing operations + * + * Parameters: + * im - The image. + * style - An array of color values. + * noOfPixel - The number of color values. + */ +BGD_DECLARE(void) gdImageSetStyle(gdImagePtr im, int *style, int noOfPixels) { if (overflow2(sizeof (int), noOfPixels)) { return; } @@ -2891,22 +3716,41 @@ void gdImageSetStyle (gdImagePtr im, int *style, int noOfPixels) gdFree(im->style); } im->style = (int *) gdMalloc(sizeof(int) * noOfPixels); + if (!im->style) { + return; + } memcpy(im->style, style, sizeof(int) * noOfPixels); im->styleLength = noOfPixels; im->stylePos = 0; } -void gdImageSetThickness (gdImagePtr im, int thickness) -{ +/** + * Function: gdImageSetThickness + * + * Sets the thickness for following drawing operations + * + * Parameters: + * im - The image. + * thickness - The thickness in pixels. + */ +BGD_DECLARE(void) gdImageSetThickness(gdImagePtr im, int thickness) { im->thick = thickness; } -void gdImageSetBrush (gdImagePtr im, gdImagePtr brush) -{ +/** + * Function: gdImageSetBrush + * + * Sets the brush for following drawing operations + * + * Parameters: + * im - The image. + * brush - The brush image. + */ +BGD_DECLARE(void) gdImageSetBrush(gdImagePtr im, gdImagePtr brush) { int i; im->brush = brush; - if (!im->trueColor && !im->brush->trueColor) { - for (i = 0; i < gdImageColorsTotal(brush); i++) { + if ((!im->trueColor) && (!im->brush->trueColor)) { + for (i = 0; (i < gdImageColorsTotal(brush)); i++) { int index; index = gdImageColorResolveAlpha(im, gdImageRed(brush, i), gdImageGreen(brush, i), gdImageBlue(brush, i), gdImageAlpha(brush, i)); im->brushColorMap[i] = index; @@ -2914,12 +3758,14 @@ void gdImageSetBrush (gdImagePtr im, gdImagePtr brush) } } -void gdImageSetTile (gdImagePtr im, gdImagePtr tile) -{ +/* + Function: gdImageSetTile +*/ +BGD_DECLARE(void) gdImageSetTile(gdImagePtr im, gdImagePtr tile) { int i; im->tile = tile; - if (!im->trueColor && !im->tile->trueColor) { - for (i = 0; i < gdImageColorsTotal(tile); i++) { + if ((!im->trueColor) && (!im->tile->trueColor)) { + for (i = 0; (i < gdImageColorsTotal(tile)); i++) { int index; index = gdImageColorResolveAlpha(im, gdImageRed(tile, i), gdImageGreen(tile, i), gdImageBlue(tile, i), gdImageAlpha(tile, i)); im->tileColorMap[i] = index; @@ -2927,28 +3773,85 @@ void gdImageSetTile (gdImagePtr im, gdImagePtr tile) } } -void gdImageSetAntiAliased (gdImagePtr im, int c) -{ +/** + * Function: gdImageSetAntiAliased + * + * Set the color for subsequent anti-aliased drawing + * + * If is passed as color to drawing operations that support + * anti-aliased drawing (such as and ), the actual + * color to be used can be set with this function. + * + * Example: draw an anti-aliased blue line: + * | gdImageSetAntiAliased(im, gdTrueColorAlpha(0, 0, gdBlueMax, + * gdAlphaOpaque)); | gdImageLine(im, 10,10, 20,20, gdAntiAliased); + * + * Parameters: + * im - The image. + * c - The color. + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageSetAntiAliased(gdImagePtr im, int c) { im->AA = 1; im->AA_color = c; im->AA_dont_blend = -1; } -void gdImageSetAntiAliasedDontBlend (gdImagePtr im, int c, int dont_blend) -{ +/** + * Function: gdImageSetAntiAliasedDontBlend + * + * Set the color and "dont_blend" color for subsequent anti-aliased drawing + * + * This extended variant of allows to also specify a + * (background) color that will not be blended in anti-aliased drawing + * operations. + * + * Parameters: + * im - The image. + * c - The color. + * dont_blend - Whether to blend. + */ +BGD_DECLARE(void) gdImageSetAntiAliasedDontBlend(gdImagePtr im, int c, int dont_blend) { im->AA = 1; im->AA_color = c; im->AA_dont_blend = dont_blend; } - -void gdImageInterlace (gdImagePtr im, int interlaceArg) -{ +/** + * Function: gdImageInterlace + * + * Sets whether an image is interlaced + * + * This is relevant only when saving the image in a format that supports + * interlacing. + * + * Parameters: + * im - The image. + * interlaceArg - Whether the image is interlaced. + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageInterlace(gdImagePtr im, int interlaceArg) { im->interlace = interlaceArg; } -int gdImageCompare (gdImagePtr im1, gdImagePtr im2) -{ +/** + * Function: gdImageCompare + * + * Compare two images + * + * Parameters: + * im1 - An image. + * im2 - Another image. + * + * Returns: + * A bitmask of flags where each set flag signals + * which attributes of the images are different. + */ +BGD_DECLARE(int) gdImageCompare(gdImagePtr im1, gdImagePtr im2) { int x, y; int p1, p2; int cmpStatus = 0; @@ -2986,11 +3889,10 @@ int gdImageCompare (gdImagePtr im1, gdImagePtr im2) cmpStatus |= GD_CMP_NUM_COLORS; } - for (y = 0; y < sy; y++) { - for (x = 0; x < sx; x++) { + for (y = 0; (y < sy); y++) { + for (x = 0; (x < sx); x++) { p1 = im1->trueColor ? gdImageTrueColorPixel(im1, x, y) : gdImagePalettePixel(im1, x, y); p2 = im2->trueColor ? gdImageTrueColorPixel(im2, x, y) : gdImagePalettePixel(im2, x, y); - if (gdImageRed(im1, p1) != gdImageRed(im2, p2)) { cmpStatus |= GD_CMP_COLOR + GD_CMP_IMAGE; break; @@ -3013,13 +3915,35 @@ int gdImageCompare (gdImagePtr im1, gdImagePtr im2) } if (cmpStatus & GD_CMP_COLOR) { break; - } + }; } return cmpStatus; } -int gdAlphaBlend (int dst, int src) { +/* Thanks to Frank Warmerdam for this superior implementation + of gdAlphaBlend(), which merges alpha in the + destination color much better. */ + +/** + * Function: gdAlphaBlend + * + * Blend two colors + * + * Parameters: + * dst - The color to blend onto. + * src - The color to blend. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(int) gdAlphaBlend (int dst, int src) { +#if ENABLE_CORRECTED_LEGACY_COMPOSITING + return gdCompositePixelToGd(gdCompositePixel( + GD_OP_OVER, gdCompositePixelFromGd(src), gdCompositePixelFromGd(dst), 1.0f)); +#else int src_alpha = gdTrueColorGetAlpha(src); int dst_alpha, alpha, red, green, blue; int src_weight, dst_weight, tot_weight; @@ -3050,53 +3974,65 @@ int gdAlphaBlend (int dst, int src) { /* -------------------------------------------------------------------- */ alpha = src_alpha * dst_alpha / gdAlphaMax; - red = (gdTrueColorGetRed(src) * src_weight - + gdTrueColorGetRed(dst) * dst_weight) / tot_weight; - green = (gdTrueColorGetGreen(src) * src_weight - + gdTrueColorGetGreen(dst) * dst_weight) / tot_weight; - blue = (gdTrueColorGetBlue(src) * src_weight - + gdTrueColorGetBlue(dst) * dst_weight) / tot_weight; + red = (gdTrueColorGetRed(src) * src_weight + + gdTrueColorGetRed(dst) * dst_weight) / + tot_weight; + green = (gdTrueColorGetGreen(src) * src_weight + + gdTrueColorGetGreen(dst) * dst_weight) / + tot_weight; + blue = (gdTrueColorGetBlue(src) * src_weight + + gdTrueColorGetBlue(dst) * dst_weight) / + tot_weight; /* -------------------------------------------------------------------- */ /* Return merged result. */ /* -------------------------------------------------------------------- */ return ((alpha << 24) + (red << 16) + (green << 8) + blue); - -} - -void gdImageAlphaBlending (gdImagePtr im, int alphaBlendingArg) -{ - im->alphaBlendingFlag = alphaBlendingArg; +#endif } -void gdImageSaveAlpha (gdImagePtr im, int saveAlphaArg) -{ - im->saveAlphaFlag = saveAlphaArg; -} +#if !ENABLE_CORRECTED_LEGACY_COMPOSITING +static int gdAlphaOverlayColor(int src, int dst, int max); +#endif -int gdLayerOverlay (int dst, int src) -{ +/** + * Function: gdLayerOverlay + * + * Overlay two colors + * + * Parameters: + * dst - The color to overlay onto. + * src - The color to overlay. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(int) gdLayerOverlay(int dst, int src) { +#if ENABLE_CORRECTED_LEGACY_COMPOSITING + return gdCompositePixelToGd(gdCompositePixel( + GD_OP_OVERLAY, gdCompositePixelFromGd(src), gdCompositePixelFromGd(dst), 1.0f)); +#else int a1, a2; a1 = gdAlphaMax - gdTrueColorGetAlpha(dst); a2 = gdAlphaMax - gdTrueColorGetAlpha(src); return ( ((gdAlphaMax - a1*a2/gdAlphaMax) << 24) + - (gdAlphaOverlayColor( gdTrueColorGetRed(src), gdTrueColorGetRed(dst), gdRedMax ) << 16) + - (gdAlphaOverlayColor( gdTrueColorGetGreen(src), gdTrueColorGetGreen(dst), gdGreenMax ) << 8) + - (gdAlphaOverlayColor( gdTrueColorGetBlue(src), gdTrueColorGetBlue(dst), gdBlueMax )) - ); + (gdAlphaOverlayColor(gdTrueColorGetRed(src), gdTrueColorGetRed(dst), + gdRedMax) + << 16) + + (gdAlphaOverlayColor(gdTrueColorGetGreen(src), + gdTrueColorGetGreen(dst), gdGreenMax) + << 8) + + (gdAlphaOverlayColor(gdTrueColorGetBlue(src), + gdTrueColorGetBlue(dst), gdBlueMax))); +#endif } -static int gdAlphaOverlayColor (int src, int dst, int max ) -{ - /* this function implements the algorithm - * - * for dst[rgb] < 0.5, - * c[rgb] = 2.src[rgb].dst[rgb] - * and for dst[rgb] > 0.5, - * c[rgb] = -2.src[rgb].dst[rgb] + 2.dst[rgb] + 2.src[rgb] - 1 - * - */ - +/* Apply 'overlay' effect - background pixels are colourised by the foreground + * colour */ +#if !ENABLE_CORRECTED_LEGACY_COMPOSITING +static int gdAlphaOverlayColor(int src, int dst, int max) { dst = dst << 1; if( dst > max ) { /* in the "light" zone */ @@ -3106,9 +4042,27 @@ static int gdAlphaOverlayColor (int src, int dst, int max ) return dst * src / max; } } +#endif -int gdLayerMultiply (int dst, int src) -{ +/** + * Function: gdLayerMultiply + * + * Overlay two colors with multiply effect + * + * Parameters: + * dst - The color to overlay onto. + * src - The color to overlay. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(int) gdLayerMultiply(int dst, int src) { +#if ENABLE_CORRECTED_LEGACY_COMPOSITING + return gdCompositePixelToGd(gdCompositePixel( + GD_OP_MULTIPLY, gdCompositePixelFromGd(src), gdCompositePixelFromGd(dst), 1.0f)); +#else int a1, a2, r1, r2, g1, g2, b1, b2; a1 = gdAlphaMax - gdTrueColorGetAlpha(src); a2 = gdAlphaMax - gdTrueColorGetAlpha(dst); @@ -3122,15 +4076,62 @@ int gdLayerMultiply (int dst, int src) a1 = gdAlphaMax - a1; a2 = gdAlphaMax - a2; - return ( ((a1*a2/gdAlphaMax) << 24) + - ((r1*r2/gdRedMax) << 16) + - ((g1*g2/gdGreenMax) << 8) + - ((b1*b2/gdBlueMax)) - ); + return (((a1 * a2 / gdAlphaMax) << 24) + ((r1 * r2 / gdRedMax) << 16) + + ((g1 * g2 / gdGreenMax) << 8) + ((b1 * b2 / gdBlueMax))); +#endif } -void gdImageSetClip (gdImagePtr im, int x1, int y1, int x2, int y2) -{ +/** + * Function: gdImageAlphaBlending + * + * Set the effect for subsequent drawing operations + * + * Note that the effect is used for truecolor images only. + * + * Parameters: + * im - The image. + * alphaBlendingArg - The effect. + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageAlphaBlending(gdImagePtr im, int alphaBlendingArg) { + im->alphaBlendingFlag = alphaBlendingArg; +} + +/** + * Function: gdImageSaveAlpha + * + * Sets the save alpha flag + * + * The save alpha flag specifies whether the alpha channel of the pixels should + * be saved. This is supported only for image formats that support full alpha + * transparency, e.g. PNG. + */ +BGD_DECLARE(void) gdImageSaveAlpha(gdImagePtr im, int saveAlphaArg) { + im->saveAlphaFlag = saveAlphaArg; +} + +/** + * Function: gdImageSetClip + * + * Sets the clipping rectangle + * + * The clipping rectangle restricts the drawing area for following drawing + * operations. + * + * Parameters: + * im - The image. + * x1 - The x-coordinate of the upper left corner. + * y1 - The y-coordinate of the upper left corner. + * x2 - The x-coordinate of the lower right corner. + * y2 - The y-coordinate of the lower right corner. + * + * See also: + * - + */ +BGD_DECLARE(void) +gdImageSetClip(gdImagePtr im, int x1, int y1, int x2, int y2) { if (x1 < 0) { x1 = 0; } @@ -3161,23 +4162,214 @@ void gdImageSetClip (gdImagePtr im, int x1, int y1, int x2, int y2) im->cy2 = y2; } -void gdImageGetClip (gdImagePtr im, int *x1P, int *y1P, int *x2P, int *y2P) -{ +/** + * Function: gdImageGetClip + * + * Gets the current clipping rectangle + * + * Parameters: + * im - The image. + * x1P - (out) The x-coordinate of the upper left corner. + * y1P - (out) The y-coordinate of the upper left corner. + * x2P - (out) The x-coordinate of the lower right corner. + * y2P - (out) The y-coordinate of the lower right corner. + * + * See also: + * - + */ +BGD_DECLARE(void) gdImageGetClip(gdImagePtr im, int *x1P, int *y1P, int *x2P, int *y2P) { *x1P = im->cx1; *y1P = im->cy1; *x2P = im->cx2; *y2P = im->cy2; } -void gdImageSetResolution(gdImagePtr im, const unsigned int res_x, const unsigned int res_y) -{ - if (res_x > 0) im->res_x = res_x; - if (res_y > 0) im->res_y = res_y; +/** + * Function: gdImageSetResolution + * + * Sets the resolution of an image. + * + * Parameters: + * im - The image. + * res_x - The horizontal resolution in DPI. + * res_y - The vertical resolution in DPI. + * + * See also: + * - + * - + */ +BGD_DECLARE(void) +gdImageSetResolution(gdImagePtr im, const unsigned int res_x, + const unsigned int res_y) { + if (res_x > 0) + im->res_x = res_x; + if (res_y > 0) + im->res_y = res_y; } -/* convert a palette image to true color */ -int gdImagePaletteToTrueColor(gdImagePtr src) -{ +#define BLEND_COLOR(a, nc, c, cc) \ + nc = (cc) + \ + (((((c) - (cc)) * (a)) + ((((c) - (cc)) * (a)) >> 8) + 0x80) >> 8); + +static void gdImageSetAAPixelColor(gdImagePtr im, int x, int y, int color, + int t) { + int dr, dg, db, p, r, g, b; + + /* 2.0.34: watch out for out of range calls */ + if (!gdImageBoundsSafeMacro(im, x, y)) { + return; + } + p = gdImageGetPixel(im, x, y); + /* TBB: we have to implement the dont_blend stuff to provide + the full feature set of the old implementation */ + if ((p == color) || ((p == im->AA_dont_blend) && (t != 0x00))) { + return; + } + dr = gdTrueColorGetRed(color); + dg = gdTrueColorGetGreen(color); + db = gdTrueColorGetBlue(color); + + r = gdTrueColorGetRed(p); + g = gdTrueColorGetGreen(p); + b = gdTrueColorGetBlue(p); + + BLEND_COLOR(t, dr, r, dr); + BLEND_COLOR(t, dg, g, dg); + BLEND_COLOR(t, db, b, db); + im->tpixels[y][x] = gdTrueColorAlpha(dr, dg, db, gdAlphaOpaque); +} + +BGD_DECLARE(void) gdImageAALine(gdImagePtr im, int x1, int y1, int x2, int y2, + int col) { + /* keep them as 32bits */ + long x, y, inc, frac; + long dx, dy, tmp; + int w, wid, wstart; + int thick = im->thick; + + if (!im->trueColor) { + /* TBB: don't crash when the image is of the wrong type */ + gdImageLine(im, x1, y1, x2, y2, col); + return; + } + + /* TBB: use the clipping rectangle */ + if (clip_1d(&x1, &y1, &x2, &y2, im->cx1, im->cx2) == 0) + return; + if (clip_1d(&y1, &x1, &y2, &x2, im->cy1, im->cy2) == 0) + return; + + dx = x2 - x1; + dy = y2 - y1; + + if (dx == 0 && dy == 0) { + /* TBB: allow setting points */ + gdImageSetPixel(im, x1, y1, col); + return; + } else { + double ag; + /* Cast the long to an int to avoid compiler warnings about truncation. + * This isn't a problem as computed dy/dx values came from ints above. + */ + ag = fabs(abs((int)dy) < abs((int)dx) ? cos(atan2(dy, dx)) + : sin(atan2(dy, dx))); + if (ag != 0) { + wid = thick / ag; + } else { + wid = 1; + } + if (wid == 0) { + wid = 1; + } + } + + /* Axis aligned lines */ + if (dx == 0) { + gdImageVLine(im, x1, y1, y2, col); + return; + } else if (dy == 0) { + gdImageHLine(im, y1, x1, x2, col); + return; + } + + if (abs((int)dx) > abs((int)dy)) { + if (dx < 0) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + dx = x2 - x1; + dy = y2 - y1; + } + y = y1; + inc = (dy * 65536) / dx; + frac = 0; + /* TBB: set the last pixel for consistency (<=) */ + for (x = x1; x <= x2; x++) { + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetAAPixelColor(im, x, w, col, (frac >> 8) & 0xFF); + gdImageSetAAPixelColor(im, x, w + 1, col, (~frac >> 8) & 0xFF); + } + frac += inc; + if (frac >= 65536) { + frac -= 65536; + y++; + } else if (frac < 0) { + frac += 65536; + y--; + } + } + } else { + if (dy < 0) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + dx = x2 - x1; + dy = y2 - y1; + } + x = x1; + inc = (dx * 65536) / dy; + frac = 0; + /* TBB: set the last pixel for consistency (<=) */ + for (y = y1; y <= y2; y++) { + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) { + gdImageSetAAPixelColor(im, w, y, col, (frac >> 8) & 0xFF); + gdImageSetAAPixelColor(im, w + 1, y, col, (~frac >> 8) & 0xFF); + } + frac += inc; + if (frac >= 65536) { + frac -= 65536; + x++; + } else if (frac < 0) { + frac += 65536; + x--; + } + } + } +} + +/** + * Function: gdImagePaletteToTrueColor + * + * Convert a palette image to true color + * + * Parameters: + * src - The image. + * + * Returns: + * Non-zero if the conversion succeeded, zero otherwise. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImagePaletteToTrueColor(gdImagePtr src) { unsigned int y; unsigned int yy; diff --git a/ext/gd/libgd/gd.h b/ext/gd/libgd/gd.h index 5325a6370925..139468b60f89 100644 --- a/ext/gd/libgd/gd.h +++ b/ext/gd/libgd/gd.h @@ -11,6 +11,20 @@ extern "C" { #include "php_compat.h" +/* Bundled libgd has no separate symbol visibility requirements. */ +#ifndef BGD_EXPORT_DATA_PROT +#define BGD_EXPORT_DATA_PROT +#endif +#ifndef BGD_STDCALL +#define BGD_STDCALL +#endif +#ifndef BGD_DECLARE +#define BGD_DECLARE(rt) rt +#endif +#ifndef ARG_NOT_USED +#define ARG_NOT_USED(arg) (void)(arg) +#endif + #define GD_MAJOR_VERSION 2 #define GD_MINOR_VERSION 0 #define GD_RELEASE_VERSION 35 @@ -29,11 +43,9 @@ extern "C" { * documentation. */ /* stdio is needed for file I/O. */ -#include #include "gd_io.h" - -/* va_list needed in gdErrorMethod */ #include +#include /* The maximum number of palette entries in palette-based images. In the wonderful new world of gd 2.0, you can of course have @@ -50,7 +62,7 @@ extern "C" { pixels are represented by integers, which must be 32 bits wide or more. - True colors are repsented as follows: + True colors are represented as follows: ARGB @@ -68,10 +80,85 @@ extern "C" { #define gdRedMax 255 #define gdGreenMax 255 #define gdBlueMax 255 + +/** + * Group: Color Decomposition + */ + +/** + * Macro: gdTrueColorGetAlpha + * + * Gets the alpha channel value + * + * Parameters: + * c - The color + * + * See also: + * - + */ #define gdTrueColorGetAlpha(c) (((c) & 0x7F000000) >> 24) + +/** + * Macro: gdTrueColorGetRed + * + * Gets the red channel value + * + * Parameters: + * c - The color + * + * See also: + * - + */ #define gdTrueColorGetRed(c) (((c) & 0xFF0000) >> 16) + +/** + * Macro: gdTrueColorGetGreen + * + * Gets the green channel value + * + * Parameters: + * c - The color + * + * See also: + * - + */ #define gdTrueColorGetGreen(c) (((c) & 0x00FF00) >> 8) + +/** + * Macro: gdTrueColorGetBlue + * + * Gets the blue channel value + * + * Parameters: + * c - The color + * + * See also: + * - + */ #define gdTrueColorGetBlue(c) ((c) & 0x0000FF) + +/** + * Group: Effects + * + * The layering effect + * + * When pixels are drawn the new colors are "mixed" with the background + * depending on the effect. + * + * Note that the effect does not apply to palette images, where pixels + * are always replaced. + * + * Modes: + * gdEffectReplace - replace pixels + * gdEffectAlphaBlend - blend pixels, see + * gdEffectNormal - default mode; same as gdEffectAlphaBlend + * gdEffectOverlay - overlay pixels, see + * gdEffectMultiply - overlay pixels with multiply effect, see + * + * + * See also: + * - + */ #define gdEffectReplace 0 #define gdEffectAlphaBlend 1 #define gdEffectNormal 2 @@ -82,46 +169,50 @@ extern "C" { #define GD_FALSE 0 #define GD_EPSILON 1e-6 +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* This function accepts truecolor pixel values only. The source color is composited with the destination color based on the alpha channel value of the source color. The resulting color is opaque. */ -int gdAlphaBlend(int dest, int src); -int gdLayerOverlay(int dst, int src); -int gdLayerMultiply(int dest, int src); +BGD_DECLARE(int) gdAlphaBlend(int dest, int src); +BGD_DECLARE(int) gdLayerOverlay(int dest, int src); +BGD_DECLARE(int) gdLayerMultiply(int dest, int src); /** * Group: Transform * * Constants: gdInterpolationMethod - - * GD_BELL - Bell + * + * GD_BELL - Bell * GD_BESSEL - Bessel - * GD_BILINEAR_FIXED - fixed point bilinear + * GD_BILINEAR_FIXED - compatibility alias for bilinear * GD_BICUBIC - Bicubic - * GD_BICUBIC_FIXED - fixed point bicubic integer + * GD_BICUBIC_FIXED - compatibility alias for bicubic * GD_BLACKMAN - Blackman - * GD_BOX - Box + * GD_BOX - Box * GD_BSPLINE - BSpline * GD_CATMULLROM - Catmullrom * GD_GAUSSIAN - Gaussian - * GD_GENERALIZED_CUBIC - Generalized cubic + * GD_GENERALIZED_CUBIC - Generalized cubic * GD_HERMITE - Hermite * GD_HAMMING - Hamming * GD_HANNING - Hannig * GD_MITCHELL - Mitchell - * GD_NEAREST_NEIGHBOUR - Nearest neighbour interpolation + * GD_NEAREST_NEIGHBOUR - Nearest neighbour interpolation * GD_POWER - Power * GD_QUADRATIC - Quadratic - * GD_SINC - Sinc + * GD_SINC - Sinc * GD_TRIANGLE - Triangle * GD_WEIGHTED4 - 4 pixels weighted bilinear interpolation + * GD_LINEAR - bilinear interpolation * * See also: - * - **/ + * - + */ typedef enum { GD_DEFAULT = 0, GD_BELL, @@ -146,14 +237,46 @@ typedef enum { GD_TRIANGLE, GD_WEIGHTED4, GD_LINEAR, - GD_METHOD_COUNT = 22 + GD_LANCZOS3, + GD_LANCZOS8, + GD_BLACKMAN_BESSEL, + GD_BLACKMAN_SINC, + GD_QUADRATIC_BSPLINE, + GD_CUBIC_SPLINE, + GD_COSINE, + GD_WELSH, + GD_METHOD_COUNT = 30 } gdInterpolationMethod; -/* define struct with name and func ptr and add it to gdImageStruct gdInterpolationMethod interpolation; */ +/* define struct with name and func ptr and add it to gdImageStruct + * gdInterpolationMethod interpolation; */ /* Interpolation function ptr */ -typedef double (* interpolation_method )(double); +typedef double (*interpolation_method)(double, double); + +/* + Group: Types + + typedef: gdImage + typedef: gdImagePtr + + The data structure in which gd stores images. , + and the various image file-loading functions + return a pointer to this type, and the other functions expect to + receive a pointer to this type as their first argument. + + *gdImagePtr* is a pointer to *gdImage*. + + See also: + + + (Previous versions of this library encouraged directly manipulating + the contents of the struct but we are attempting to move away from + this practice so the fields are no longer documented here. If you + need to poke at the internals of this struct, feel free to look at + *gd.h*.) +*/ typedef struct gdImageStruct { /* Palette-based image pixels */ unsigned char ** pixels; @@ -215,25 +338,85 @@ typedef struct gdImageStruct { have that capability. JPEG doesn't. */ int saveAlphaFlag; + /* There should NEVER BE ACCESSOR MACROS FOR ITEMS BELOW HERE, so this + part of the structure can be safely changed in new releases. */ + /* 2.0.12: anti-aliased globals. 2.0.26: just a few vestiges after switching to the fast, memory-cheap implementation from PHP-gd. */ int AA; int AA_color; int AA_dont_blend; - /* 2.0.12: simple clipping rectangle. These values must be checked for safety when set; please use gdImageSetClip */ + /* 2.0.12: simple clipping rectangle. These values + must be checked for safety when set; please use + gdImageSetClip */ int cx1; int cy1; int cx2; int cy2; + + /* 2.1.0: allows to specify resolution in dpi */ 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; typedef gdImage * gdImagePtr; +typedef struct gdImageMetadata gdImageMetadata; + +#define GD_META_OK 0 +#define GD_META_ERR_FORMAT -1 +#define GD_META_ERR_PARSE -2 +#define GD_META_ERR_NOMEM -3 +#define GD_META_ERR_LIMIT -4 +#define GD_META_ERR_UNSUPPORTED -5 +#define GD_META_ERR_INVALID -6 + +#define GD_METADATA_DEFAULT_MAX_PROFILE_SIZE ((size_t)64 * 1024 * 1024) +#define GD_METADATA_DEFAULT_MAX_TOTAL_SIZE ((size_t)256 * 1024 * 1024) + +BGD_DECLARE(gdImageMetadata *) gdImageMetadataCreate(void); +BGD_DECLARE(void) gdImageMetadataFree(gdImageMetadata *metadata); +BGD_DECLARE(void) gdImageMetadataReset(gdImageMetadata *metadata); +BGD_DECLARE(int) +gdImageMetadataSetLimits(gdImageMetadata *metadata, size_t max_profile_size, + size_t max_total_size); +BGD_DECLARE(void) +gdImageMetadataGetLimits(const gdImageMetadata *metadata, + size_t *max_profile_size, size_t *max_total_size); +BGD_DECLARE(int) +gdImageMetadataSetProfile(gdImageMetadata *metadata, const char *key, + const unsigned char *data, size_t size); +BGD_DECLARE(const unsigned char *) +gdImageMetadataGetProfile(const gdImageMetadata *metadata, const char *key, + size_t *size); +BGD_DECLARE(int) +gdImageMetadataRemoveProfile(gdImageMetadata *metadata, const char *key); +BGD_DECLARE(size_t) +gdImageMetadataGetProfileCount(const gdImageMetadata *metadata); +BGD_DECLARE(int) +gdImageMetadataGetProfileAt(const gdImageMetadata *metadata, size_t index, + const char **key, const unsigned char **data, + size_t *size); + /* Point type for use in polygon drawing. */ /** @@ -251,12 +434,50 @@ typedef gdImage * gdImagePtr; * See also: * , , **/ -typedef struct -{ +typedef struct { double x, y; -} -gdPointF, *gdPointFPtr; +} gdPointF, *gdPointFPtr; + +/* + Group: Types + + typedef: gdFont + + typedef: gdFontPtr + + A font structure, containing the bitmaps of all characters in a + font. Used to declare the characteristics of a font. Text-output + functions expect these as their second argument, following the + argument. and both + return one. + + You can provide your own font data by providing such a structure and + the associated pixel array. You can determine the width and height + of a single character in a font by examining the w and h members of + the structure. If you will not be creating your own fonts, you will + not need to concern yourself with the rest of the components of this + structure. + + Please see the files gdfontl.c and gdfontl.h for an example of + the proper declaration of this structure. + + > typedef struct { + > // # of characters in font + > int nchars; + > // First character is numbered... (usually 32 = space) + > int offset; + > // Character width and height + > int w; + > int h; + > // Font data; array of characters, one row after another. + > // Easily included in code, also easily loaded from + > // data files. + > char *data; + > } gdFont; + + gdFontPtr is a pointer to gdFont. +*/ typedef struct { /* # of characters in font */ int nchars; @@ -276,39 +497,41 @@ typedef gdFont *gdFontPtr; typedef void(*gdErrorMethod)(int, const char *, va_list); -void gdSetErrorMethod(gdErrorMethod); -void gdClearErrorMethod(void); - -/** - * Group: Types - * - * typedef: gdRect - * Defines a rectilinear region. - * - * x - left position - * y - right position - * width - Rectangle width - * height - Rectangle height - * - * typedef: gdRectPtr - * Pointer to a - * - * See also: - * - **/ -typedef struct -{ - int x, y; - int width, height; -} -gdRect, *gdRectPtr; +BGD_DECLARE(void) gdSetErrorMethod(gdErrorMethod); +BGD_DECLARE(void) gdClearErrorMethod(void); /* For backwards compatibility only. Use gdImageSetStyle() for MUCH more flexible line drawing. Also see gdImageSetBrush(). */ #define gdDashSize 4 -/* Special colors. */ +/** + * Group: Colors + * + * Colors are always of type int which is supposed to be at least 32 bit large. + * + * Kinds of colors: + * true colors - ARGB values where the alpha channel is stored as most + * significant, and the blue channel as least significant + * byte. Note that the alpha channel only uses the 7 least + * significant bits. + * Don't rely on the internal representation, though, and + * use to compose a truecolor value, and + * , , + * and to access + * the respective channels. + * palette indexes - The index of a color palette entry (0-255). + * special colors - As listed in the following section. + * + * Constants: Special Colors + * gdStyled - use the current style, see + * gdBrushed - use the current brush, see + * gdStyledBrushed - use the current style and brush + * gdTiled - use the current tile, see + * gdTransparent - indicate transparency, what is not the same as the + * transparent color index; used for lines only + * gdAntiAliased - draw anti aliased + */ #define gdStyled (-2) #define gdBrushed (-3) @@ -324,275 +547,748 @@ gdRect, *gdRectPtr; /* Functions to manipulate images. */ /* Creates a palette-based image (up to 256 colors). */ -gdImagePtr gdImageCreate(int sx, int sy); +BGD_DECLARE(gdImagePtr) gdImageCreate(int sx, int sy); /* An alternate name for the above (2.0). */ #define gdImageCreatePalette gdImageCreate /* Creates a truecolor image (millions of colors). */ -gdImagePtr gdImageCreateTrueColor(int sx, int sy); +BGD_DECLARE(gdImagePtr) gdImageCreateTrueColor(int sx, int sy); /* Creates an image from various file types. These functions return a palette or truecolor image based on the nature of the file being loaded. Truecolor PNG stays truecolor; palette PNG stays palette-based; JPEG is always truecolor. */ -gdImagePtr gdImageCreateFromPng(FILE *fd); -gdImagePtr gdImageCreateFromPngCtx(gdIOCtxPtr in); -gdImagePtr gdImageCreateFromWBMP(FILE *inFile); -gdImagePtr gdImageCreateFromWBMPCtx(gdIOCtx *infile); -gdImagePtr gdImageCreateFromJpeg(FILE *infile); -gdImagePtr gdImageCreateFromJpegEx(FILE *infile, int ignore_warning); -gdImagePtr gdImageCreateFromJpegCtx(gdIOCtx *infile); -gdImagePtr gdImageCreateFromJpegCtxEx(gdIOCtx *infile, int ignore_warning); -gdImagePtr gdImageCreateFromJpegPtr (int size, void *data); -gdImagePtr gdImageCreateFromJpegPtrEx (int size, void *data, int ignore_warning); -gdImagePtr gdImageCreateFromWebp(FILE *fd); -gdImagePtr gdImageCreateFromWebpCtx(gdIOCtxPtr in); -gdImagePtr gdImageCreateFromWebpPtr (int size, void *data); - -gdImagePtr gdImageCreateFromAvif(FILE *infile); -gdImagePtr gdImageCreateFromAvifPtr(int size, void *data); -gdImagePtr gdImageCreateFromAvifCtx(gdIOCtx *infile); - -gdImagePtr gdImageCreateFromTga( FILE * fp ); -gdImagePtr gdImageCreateFromTgaCtx(gdIOCtx* ctx); -gdImagePtr gdImageCreateFromTgaPtr(int size, void *data); - -gdImagePtr gdImageCreateFromBmp (FILE * inFile); -gdImagePtr gdImageCreateFromBmpPtr (int size, void *data); -gdImagePtr gdImageCreateFromBmpCtx (gdIOCtxPtr infile); - -const char * gdPngGetVersionString(void); - -const char * gdJpegGetVersionString(void); - -/* A custom data source. */ -/* The source function must return -1 on error, otherwise the number - of bytes fetched. 0 is EOF, not an error! */ -/* context will be passed to your source function. */ +/* PNG */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPng(FILE *fd); +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngCtx(gdIOCtxPtr in); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngCtxWithMetadata(gdIOCtxPtr in, gdImageMetadata *metadata); +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngPtrWithMetadata(int size, void *data, gdImageMetadata *metadata); +BGD_DECLARE(void) gdImagePng(gdImagePtr im, FILE *out); +BGD_DECLARE(void) gdImagePngCtx(gdImagePtr im, gdIOCtxPtr out); +/* 2.0.12: Compression level: 0-9 or -1, where 0 is NO COMPRESSION at all, + 1 is FASTEST but produces larger files, 9 provides the best + compression (smallest files) but takes a long time to compress, and + -1 selects the default compiled into the zlib library. */ +BGD_DECLARE(void) gdImagePngEx(gdImagePtr im, FILE *out, int level); +BGD_DECLARE(void) gdImagePngCtxEx(gdImagePtr im, gdIOCtxPtr out, int level); +BGD_DECLARE(void) +gdImagePngCtxWithMetadata(gdImagePtr im, gdIOCtxPtr out, const gdImageMetadata *metadata); +BGD_DECLARE(void) +gdImagePngCtxExWithMetadata(gdImagePtr im, gdIOCtxPtr out, int level, const gdImageMetadata *metadata); + +/* Best to free this memory with gdFree(), not free() */ +BGD_DECLARE(void *) gdImagePngPtr(gdImagePtr im, int *size); +BGD_DECLARE(void *) gdImagePngPtrEx(gdImagePtr im, int *size, int level); +BGD_DECLARE(void *) +gdImagePngPtrWithMetadata(gdImagePtr im, int *size, + const gdImageMetadata *metadata); +BGD_DECLARE(void *) +gdImagePngPtrExWithMetadata(gdImagePtr im, int *size, int level, + const gdImageMetadata *metadata); +BGD_DECLARE(int) +gdImageMetadataInjectPng(void **data, int *size, const gdImageMetadata *metadata); + +#define GD_PNG_FILTER_AUTO 0U +#define GD_PNG_FILTER_NONE (1U << 0) +#define GD_PNG_FILTER_SUB (1U << 1) +#define GD_PNG_FILTER_UP (1U << 2) +#define GD_PNG_FILTER_AVERAGE (1U << 3) +#define GD_PNG_FILTER_PAETH (1U << 4) +#define GD_PNG_FILTER_ALL \ + (GD_PNG_FILTER_NONE | GD_PNG_FILTER_SUB | GD_PNG_FILTER_UP | \ + GD_PNG_FILTER_AVERAGE | GD_PNG_FILTER_PAETH) + +enum { + GD_PNG_COMPRESSION_STRATEGY_DEFAULT = 0, + GD_PNG_COMPRESSION_STRATEGY_FILTERED, + GD_PNG_COMPRESSION_STRATEGY_HUFFMAN_ONLY, + GD_PNG_COMPRESSION_STRATEGY_RLE, + GD_PNG_COMPRESSION_STRATEGY_FIXED +}; typedef struct { - int (*source) (void *context, char *buffer, int len); - void *context; -} gdSource, *gdSourcePtr; + size_t struct_size; + int compression_level; + unsigned int filters; + int compression_strategy; + const gdImageMetadata *metadata; +} gdPngWriteOptions; + +BGD_DECLARE(void) gdPngWriteOptionsInit(gdPngWriteOptions *options); +BGD_DECLARE(int) gdImagePngWithOptions(gdImagePtr im, FILE *out, const gdPngWriteOptions *options); +BGD_DECLARE(int) gdImagePngCtxWithOptions(gdImagePtr im, gdIOCtxPtr out, const gdPngWriteOptions *options); +BGD_DECLARE(void *) gdImagePngPtrWithOptions(gdImagePtr im, int *size, const gdPngWriteOptions *options); +BGD_DECLARE(const char *) gdPngGetVersionString(void); + +/* QOI */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoi(FILE *fd); +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiCtx(gdIOCtxPtr in); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromQoiCtxWithMetadata(gdIOCtxPtr in, gdImageMetadata *metadata); +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromQoiPtrWithMetadata(int size, void *data, gdImageMetadata *metadata); +BGD_DECLARE(void *) gdImageQoiPtr(gdImagePtr im, int *size); +BGD_DECLARE(void *) gdImageQoiPtrEx(gdImagePtr im, int *size, int colorspace); +BGD_DECLARE(void *) gdImageQoiPtrWithMetadata(gdImagePtr im, int *size, const gdImageMetadata *metadata); +BGD_DECLARE(void *) gdImageQoiPtrExWithMetadata(gdImagePtr im, int *size, int colorspace, const gdImageMetadata *metadata); +BGD_DECLARE(int) gdImageMetadataInjectQoi(void **data, int *size, const gdImageMetadata *metadata); + +BGD_DECLARE(void) gdImageQoi(gdImagePtr im, FILE *out); +BGD_DECLARE(void) gdImageQoiCtx(gdImagePtr im, gdIOCtxPtr out); +BGD_DECLARE(void) gdImageQoiCtxWithMetadata(gdImagePtr im, gdIOCtxPtr out, const gdImageMetadata *metadata); +enum { GD_QOI_SRGB = 0, GD_QOI_LINEAR = 1 }; + +BGD_DECLARE(void) gdImageQoi(gdImagePtr im, FILE *out); +BGD_DECLARE(void) gdImageQoiCtx(gdImagePtr im, gdIOCtxPtr out); +BGD_DECLARE(void) +gdImageQoiCtxWithMetadata(gdImagePtr im, gdIOCtxPtr out, + const gdImageMetadata *metadata); +BGD_DECLARE(void) gdImageQoiEx(gdImagePtr im, FILE *out, int colorspace); +BGD_DECLARE(void) +gdImageQoiCtxEx(gdImagePtr im, gdIOCtxPtr out, int colorspace); +BGD_DECLARE(void) +gdImageQoiCtxExWithMetadata(gdImagePtr im, gdIOCtxPtr out, int colorspace, + const gdImageMetadata *metadata); +/* GIF */ +/* These read the first frame only */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGif(FILE *fd); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGifCtx(gdIOCtxPtr in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGifPtr(int size, void *data); +BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out); +BGD_DECLARE(void) gdImageGif(gdImagePtr im, FILE *out); +/* Best to free this memory with gdFree(), not free() */ +BGD_DECLARE(void *) gdImageGifPtr(gdImagePtr im, int *size); -gdImagePtr gdImageCreateFromPngSource(gdSourcePtr in); -gdImagePtr gdImageCreateFromGd(FILE *in); -gdImagePtr gdImageCreateFromGdCtx(gdIOCtxPtr in); +/* Anims, frames or compositions */ +typedef struct gdGifReadStruct *gdGifReadPtr; -gdImagePtr gdImageCreateFromGd2(FILE *in); -gdImagePtr gdImageCreateFromGd2Ctx(gdIOCtxPtr in); +typedef struct { + int width; + int height; + int backgroundIndex; + int globalColorTable; + int loopCount; +} gdGifInfo; -gdImagePtr gdImageCreateFromGd2Part(FILE *in, int srcx, int srcy, int w, int h); -gdImagePtr gdImageCreateFromGd2PartCtx(gdIOCtxPtr in, int srcx, int srcy, int w, int h); +typedef struct { + int frameIndex; + int x; + int y; + int width; + int height; + int delay; + int disposal; + int transparentIndex; + int localColorTable; + int interlace; +} gdGifFrameInfo; + +BGD_DECLARE(int) gdGifIsAnimated(FILE *fd); +BGD_DECLARE(int) gdGifIsAnimatedCtx(gdIOCtxPtr in); +BGD_DECLARE(int) gdGifIsAnimatedPtr(int size, void *data); +BGD_DECLARE(gdGifReadPtr) gdGifReadOpen(FILE *fd); +BGD_DECLARE(gdGifReadPtr) gdGifReadOpenCtx(gdIOCtxPtr in); +BGD_DECLARE(gdGifReadPtr) gdGifReadOpenPtr(int size, void *data); +BGD_DECLARE(void) gdGifReadClose(gdGifReadPtr gif); +BGD_DECLARE(int) gdGifReadGetInfo(gdGifReadPtr gif, gdGifInfo *info); +BGD_DECLARE(int) +gdGifReadNextFrame(gdGifReadPtr gif, gdGifFrameInfo *info, gdImagePtr *frame); +BGD_DECLARE(int) +gdGifReadNextImage(gdGifReadPtr gif, gdGifFrameInfo *info, gdImagePtr *image); +BGD_DECLARE(gdImagePtr) gdGifReadCloneImage(gdGifReadPtr gif); -gdImagePtr gdImageCreateFromXbm(FILE *fd); -void gdImageXbmCtx(gdImagePtr image, char* file_name, int fg, gdIOCtx * out); +/** + * Group: GifAnim + * + * Legal values for Disposal. gdDisposalNone is always used by + * the built-in optimizer if previm is passed. + * + * Constants: gdImageGifAnim + * + * gdDisposalUnknown - Not recommended + * gdDisposalNone - Preserve previous frame + * gdDisposalRestoreBackground - First allocated color of palette + * gdDisposalRestorePrevious - Restore to before start of frame + * + * See also: + * - + */ +enum { + gdDisposalUnknown, + gdDisposalNone, + gdDisposalRestoreBackground, + gdDisposalRestorePrevious +}; -gdImagePtr gdImageCreateFromXpm (char *filename); +BGD_DECLARE(void) +gdImageGifAnimBegin(gdImagePtr im, FILE *outFile, int GlobalCM, int Loops); +BGD_DECLARE(void) +gdImageGifAnimAdd(gdImagePtr im, FILE *outFile, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm); +BGD_DECLARE(void) gdImageGifAnimEnd(FILE *outFile); +BGD_DECLARE(void) +gdImageGifAnimBeginCtx(gdImagePtr im, gdIOCtxPtr out, int GlobalCM, int Loops); +BGD_DECLARE(void) +gdImageGifAnimAddCtx(gdImagePtr im, gdIOCtxPtr out, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm); +BGD_DECLARE(void) gdImageGifAnimEndCtx(gdIOCtxPtr out); +BGD_DECLARE(void *) +gdImageGifAnimBeginPtr(gdImagePtr im, int *size, int GlobalCM, int Loops); +BGD_DECLARE(void *) +gdImageGifAnimAddPtr(gdImagePtr im, int *size, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm); +BGD_DECLARE(void *) gdImageGifAnimEndPtr(int *size); + + +/* WBMP */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMP(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMPCtx(gdIOCtxPtr infile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMPPtr(int size, void *data); + +/* JPEG */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpeg(FILE *infile); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegEx(FILE *infile, int ignore_warning); +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegCtx(gdIOCtxPtr infile); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxEx(gdIOCtxPtr infile, int ignore_warning); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxWithMetadata(gdIOCtxPtr infile, + gdImageMetadata *metadata); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxExWithMetadata(gdIOCtxPtr infile, int ignore_warning, + gdImageMetadata *metadata); +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrEx(int size, void *data, int ignore_warning); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrWithMetadata(int size, void *data, gdImageMetadata *metadata); +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegPtrExWithMetadata(int size, void *data, int ignore_warning, gdImageMetadata *metadata); +BGD_DECLARE(const char *) gdJpegGetVersionString(); + +/* WEBP */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebp(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpCtx(gdIOCtxPtr infile); + +typedef struct gdWebpReadStruct *gdWebpReadPtr; +typedef struct gdWebpWriteStruct *gdWebpWritePtr; -void gdImageDestroy(gdImagePtr im); +typedef struct { + int width; + int height; + int frameCount; + int loopCount; + int backgroundColor; + int formatFlags; +} gdWebpInfo; -/* Replaces or blends with the background depending on the - most recent call to gdImageAlphaBlending and the - alpha channel value of 'color'; default is to overwrite. - Tiling and line styling are also implemented - here. All other gd drawing functions pass through this call, - allowing for many useful effects. */ +typedef struct { + int frameIndex; + int x; + int y; + int width; + int height; + int duration; + int timestamp; + int dispose; + int blend; + int hasAlpha; + int complete; +} gdWebpFrameInfo; -void gdImageSetPixel(gdImagePtr im, int x, int y, int color); +typedef struct { + int canvasWidth; + int canvasHeight; + int loopCount; + int backgroundColor; + int quality; + int lossless; + int method; + int minimizeSize; + int kmin; + int kmax; + int allowMixed; +} gdWebpWriteOptions; + +enum { gdWebpDisposeNone, gdWebpDisposeBackground }; +enum { gdWebpBlendAlpha, gdWebpBlendNone }; + +BGD_DECLARE(int) gdWebpIsAnimated(FILE *fd); +BGD_DECLARE(int) gdWebpIsAnimatedCtx(gdIOCtxPtr in); +BGD_DECLARE(int) gdWebpIsAnimatedPtr(int size, void *data); +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpen(FILE *fd); +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenCtx(gdIOCtxPtr in); +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenPtr(int size, void *data); +BGD_DECLARE(void) gdWebpReadClose(gdWebpReadPtr webp); +BGD_DECLARE(int) gdWebpReadGetInfo(gdWebpReadPtr webp, gdWebpInfo *info); +BGD_DECLARE(int) +gdWebpReadNextFrame(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *frame); +BGD_DECLARE(int) +gdWebpReadNextImage(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *image); +BGD_DECLARE(gdImagePtr) gdWebpReadCloneImage(gdWebpReadPtr webp); +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpen(FILE *outFile, const gdWebpWriteOptions *options); +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenCtx(gdIOCtxPtr out, const gdWebpWriteOptions *options); +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenPtr(const gdWebpWriteOptions *options); +BGD_DECLARE(int) +gdWebpWriteAddImage(gdWebpWritePtr webp, gdImagePtr image, int durationMs); +BGD_DECLARE(void) gdWebpWriteClose(gdWebpWritePtr webp); +BGD_DECLARE(void *) gdWebpWritePtrFinish(gdWebpWritePtr webp, int *size); + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxl(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlCtx(gdIOCtxPtr infile); + +BGD_DECLARE(void) gdImageJxl(gdImagePtr im, FILE *outFile); +BGD_DECLARE(void) gdImageJxlEx(gdImagePtr im, FILE *outFile, + int lossless, float distance, int effort); +BGD_DECLARE(void *) gdImageJxlPtr(gdImagePtr im, int *size); +BGD_DECLARE(void *) gdImageJxlPtrEx(gdImagePtr im, int *size, + int lossless, float distance, int effort); +BGD_DECLARE(void) gdImageJxlCtx(gdImagePtr im, gdIOCtxPtr outfile); +BGD_DECLARE(void) gdImageJxlCtxEx(gdImagePtr im, gdIOCtxPtr outfile, + int lossless, float distance, int effort); + +/* Animation API */ +typedef struct gdJxlAnimReader *gdJxlAnimReaderPtr; +typedef struct gdJxlAnim *gdJxlAnimPtr; -int gdImageGetTrueColorPixel (gdImagePtr im, int x, int y); -int gdImageGetPixel(gdImagePtr im, int x, int y); +typedef struct { + int delay_ms; + int x_offset; + int y_offset; + int width; + int height; + int blend_mode; + int is_last; +} gdJxlFrameInfo; + +#define GD_JXL_BLEND_REPLACE 0 +#define GD_JXL_BLEND_ADD 1 +#define GD_JXL_BLEND_BLEND 2 +#define GD_JXL_BLEND_MULADD 3 +#define GD_JXL_BLEND_MUL 4 + +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreate(FILE *inFile); +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreatePtr(int size, void *data); +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateCtx(gdIOCtxPtr inCtx); + +BGD_DECLARE(gdImagePtr) gdJxlReadNextImage( + gdJxlAnimReaderPtr reader, + int *delay_ms); + +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateRaw(FILE *inFile); +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateRawPtr(int size, void *data); +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateRawCtx(gdIOCtxPtr inCtx); + +BGD_DECLARE(gdImagePtr) gdJxlReadNextFrame( + gdJxlAnimReaderPtr reader, + gdJxlFrameInfo *info); + +BGD_DECLARE(void) gdImageJxlAnimReaderDestroy(gdJxlAnimReaderPtr reader); + +BGD_DECLARE(gdJxlAnimPtr) gdImageJxlAnimBegin( + FILE *outFile, + int width, int height, + int lossless, float distance, int effort); + +BGD_DECLARE(gdJxlAnimPtr) gdImageJxlAnimBeginCtx( + gdIOCtxPtr outCtx, + int width, int height, + int lossless, float distance, int effort); + +BGD_DECLARE(gdJxlAnimPtr) gdImageJxlAnimBeginPtr( + int width, int height, + int lossless, float distance, int effort); + +BGD_DECLARE(int) gdImageJxlAnimAddFrame( + gdJxlAnimPtr anim, + gdImagePtr im, + int delay_ms); + +BGD_DECLARE(int) gdImageJxlAnimEnd(gdJxlAnimPtr anim); +BGD_DECLARE(void *) gdImageJxlAnimEndPtr(gdJxlAnimPtr anim, int *size); + +/* HEIF */ -void gdImageAABlend(gdImagePtr im); +/** + * Group: HEIF Coding Format + * + * Values that select the HEIF coding format. + * + * Constants: gdHeifCodec + * + * GD_HEIF_CODEC_UNKNOWN + * GD_HEIF_CODEC_HEVC + * GD_HEIF_CODEC_AV1 + * + * See also: + * - + */ +typedef enum { + GD_HEIF_CODEC_UNKNOWN = 0, + GD_HEIF_CODEC_HEVC, + GD_HEIF_CODEC_AV1 = 4, +} gdHeifCodec; -void gdImageLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); -void gdImageAALine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); +/** + * Group: HEIF Chroma Subsampling + * + * Values that select the HEIF chroma subsampling. + * + * Constants: gdHeifCompression + * + * GD_HEIF_CHROMA_420 + * GD_HEIF_CHROMA_422 + * GD_HEIF_CHROMA_444 + * + * See also: + * - + */ +typedef const char *gdHeifChroma; -/* For backwards compatibility only. Use gdImageSetStyle() - for much more flexible line drawing. */ -void gdImageDashedLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); -/* Corners specified (not width and height). Upper left first, lower right - second. */ -void gdImageRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color); -/* Solid bar. Upper left corner first, lower right corner second. */ -void gdImageFilledRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color); -void gdImageSetClip(gdImagePtr im, int x1, int y1, int x2, int y2); -void gdImageGetClip(gdImagePtr im, int *x1P, int *y1P, int *x2P, int *y2P); -void gdImageSetResolution(gdImagePtr im, const unsigned int res_x, const unsigned int res_y); -void gdImageChar(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color); -void gdImageCharUp(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color); -void gdImageString(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color); -void gdImageStringUp(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color); -void gdImageString16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color); -void gdImageStringUp16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color); +#define GD_HEIF_CHROMA_420 "420" +#define GD_HEIF_CHROMA_422 "422" +#define GD_HEIF_CHROMA_444 "444" -/* - * The following functions are required to be called prior to the - * use of any sort of threads in a module load / shutdown function - * respectively. - */ -void gdFontCacheMutexSetup(void); -void gdFontCacheMutexShutdown(void); +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtxPtr infile); +BGD_DECLARE(void) gdImageHeif(gdImagePtr im, FILE *outFile); -/* 2.0.16: for thread-safe use of gdImageStringFT and friends, - * call this before allowing any thread to call gdImageStringFT. - * Otherwise it is invoked by the first thread to invoke - * gdImageStringFT, with a very small but real risk of a race condition. - * Return 0 on success, nonzero on failure to initialize freetype. - */ -int gdFontCacheSetup(void); +/* AVIF */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx(gdIOCtxPtr infile); -/* Optional: clean up after application is done using fonts in gdImageStringFT(). */ -void gdFontCacheShutdown(void); +/* TIFF */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiff(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffCtx(gdIOCtxPtr infile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffPtr(int size, void *data); -/* Calls gdImageStringFT. Provided for backwards compatibility only. */ -char *gdImageStringTTF(gdImage *im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string); +typedef struct gdTiffReadStruct *gdTiffReadPtr; -/* FreeType 2 text output */ -char *gdImageStringFT(gdImage *im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string); +typedef struct { + int width; + int height; + int pageCount; + int bitsPerSample; + int samplesPerPixel; + int compression; + int photometric; + float xResolution; + float yResolution; + int resolutionUnit; +} gdTiffInfo; typedef struct { - double linespacing; /* fine tune line spacing for '\n' */ - int flags; /* Logical OR of gdFTEX_ values */ - int charmap; /* TBB: 2.0.12: may be gdFTEX_Unicode, - gdFTEX_Shift_JIS, gdFTEX_Big5 or gdFTEX_MacRoman; - when not specified, maps are searched - for in the above order. */ - int hdpi; - int vdpi; -} - gdFTStringExtra, *gdFTStringExtraPtr; + int pageIndex; + int width; + int height; + int bitsPerSample; + int samplesPerPixel; + int compression; + int photometric; + int planar; + int hasAlpha; + int isTiled; + float xResolution; + float yResolution; + int resolutionUnit; +} gdTiffPageInfo; + +BGD_DECLARE(int) gdTiffIsMultiPage(FILE *fd); +BGD_DECLARE(int) gdTiffIsMultiPageCtx(gdIOCtxPtr in); +BGD_DECLARE(int) gdTiffIsMultiPagePtr(int size, void *data); +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpen(FILE *fd); +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenCtx(gdIOCtxPtr in); +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenPtr(int size, void *data); +BGD_DECLARE(void) gdTiffReadClose(gdTiffReadPtr tiff); +BGD_DECLARE(int) gdTiffReadGetInfo(gdTiffReadPtr tiff, gdTiffInfo *info); +BGD_DECLARE(int) +gdTiffReadNextImage(gdTiffReadPtr tiff, gdTiffPageInfo *info, + gdImagePtr *image); +BGD_DECLARE(gdImagePtr) gdTiffReadCloneImage(gdTiffReadPtr tiff); + +/* TIFF Write API */ +#define GD_TIFF_RGB 1 +#define GD_TIFF_RGBA 2 +#define GD_TIFF_GRAY 3 +#define GD_TIFF_BILEVEL 4 + +#define GD_TIFF_RESUNIT_NONE 1 +#define GD_TIFF_RESUNIT_INCH 2 +#define GD_TIFF_RESUNIT_CENTIMETER 3 + +#define GD_TIFF_ALPHA_UNASSOCIATED 1 +#define GD_TIFF_ALPHA_ASSOCIATED 2 -#define gdFTEX_LINESPACE 1 -#define gdFTEX_CHARMAP 2 -#define gdFTEX_RESOLUTION 4 +typedef struct { + int bitDepth; + int colorspace; + int compression; + int jpegQuality; + int minIsWhite; + int resolutionUnit; + float xResolution; + float yResolution; + int alphaType; +} gdTiffWriteOptions; + +typedef struct gdTiffWriteStruct *gdTiffWritePtr; + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpen(FILE *outFile, const gdTiffWriteOptions *options); +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenCtx(gdIOCtxPtr out, const gdTiffWriteOptions *options); +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenPtr(const gdTiffWriteOptions *options); +BGD_DECLARE(int) gdTiffWriteAddImage(gdTiffWritePtr write, gdImagePtr image); +BGD_DECLARE(void) gdTiffWriteClose(gdTiffWritePtr write); +BGD_DECLARE(void *) gdTiffWritePtrFinish(gdTiffWritePtr write, int *size); + +BGD_DECLARE(void) gdImageTiff(gdImagePtr im, FILE *outFile); +BGD_DECLARE(void *) gdImageTiffPtr(gdImagePtr im, int *size); +BGD_DECLARE(void) gdImageTiffCtx(gdImagePtr image, gdIOCtxPtr out); + +/* TGA */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTga(FILE *fp); +BGD_DECLARE(gdImagePtr) gdImageCreateFromTgaCtx(gdIOCtxPtr ctx); +BGD_DECLARE(gdImagePtr) gdImageCreateFromTgaPtr(int size, void *data); + +/* BMP */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmp(FILE *inFile); +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpPtr(int size, void *data); +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpCtx(gdIOCtxPtr infile); + +/* UltraHDR */ -/* These are NOT flags; set one in 'charmap' if you set the gdFTEX_CHARMAP bit in 'flags'. */ -#define gdFTEX_Unicode 0 -#define gdFTEX_Shift_JIS 1 -#define gdFTEX_Big5 2 -#define gdFTEX_MacRoman 3 +/** + * Group: UltraHDR + * + * UltraHDR (gain map) APIs are separate from . The UltraHDR handle + * type is opaque and cannot be passed to existing functions. + */ -/* FreeType 2 text output with fine tuning */ -char * -gdImageStringFTEx(gdImage * im, int *brect, int fg, char * fontlist, - double ptsize, double angle, int x, int y, char * string, - gdFTStringExtraPtr strex); +/** + * Constants: gdUhdrStatus + * + * Return status values used by UltraHDR APIs. + * + * GD_UHDR_SUCCESS - operation succeeded + * GD_UHDR_NOT_AVAILABLE - libgd was built without UltraHDR support + * GD_UHDR_E_INVALID - invalid argument or state + * GD_UHDR_E_UNSUPPORTED - unsupported format or operation + * GD_UHDR_E_ENCODE - encode failure + * GD_UHDR_E_DECODE - decode failure + */ +#define GD_UHDR_SUCCESS 0 +#define GD_UHDR_NOT_AVAILABLE -1 +#define GD_UHDR_E_INVALID -2 +#define GD_UHDR_E_UNSUPPORTED -3 +#define GD_UHDR_E_ENCODE -4 +#define GD_UHDR_E_DECODE -5 +/** + * Constants: gdUhdrMirrorAxis + * + * Mirror axis values used by . + * + * GD_UHDR_MIRROR_HORIZONTAL + * GD_UHDR_MIRROR_VERTICAL + */ +#define GD_UHDR_MIRROR_HORIZONTAL 0 +#define GD_UHDR_MIRROR_VERTICAL 1 -/* Point type for use in polygon drawing. */ -typedef struct { - int x, y; -} gdPoint, *gdPointPtr; +/** + * Enum: gdUhdrFormat + * + * UltraHDR container format selector. + * + * GD_UHDR_FORMAT_JPEG - UltraHDR JPEG (currently supported) + * GD_UHDR_FORMAT_WEBP - reserved for future support + * GD_UHDR_FORMAT_HEIF - reserved for future support + */ +typedef enum { + GD_UHDR_FORMAT_JPEG = 0, + GD_UHDR_FORMAT_WEBP = 1, + GD_UHDR_FORMAT_HEIF = 2 +} gdUhdrFormat; -void gdImagePolygon(gdImagePtr im, gdPointPtr p, int n, int c); -void gdImageOpenPolygon(gdImagePtr im, gdPointPtr p, int n, int c); -void gdImageFilledPolygon(gdImagePtr im, gdPointPtr p, int n, int c); +/** + * Typedef: gdUhdrImage + * + * Opaque UltraHDR image handle. + */ +typedef struct gdUhdrImageStruct gdUhdrImage; -/* These functions still work with truecolor images, - for which they never return error. */ -int gdImageColorAllocate(gdImagePtr im, int r, int g, int b); -/* gd 2.0: palette entries with non-opaque transparency are permitted. */ -int gdImageColorAllocateAlpha(gdImagePtr im, int r, int g, int b, int a); -/* Assumes opaque is the preferred alpha channel value */ -int gdImageColorClosest(gdImagePtr im, int r, int g, int b); -/* Closest match taking all four parameters into account. - A slightly different color with the same transparency - beats the exact same color with radically different - transparency */ -int gdImageColorClosestAlpha(gdImagePtr im, int r, int g, int b, int a); -/* An alternate method */ -int gdImageColorClosestHWB(gdImagePtr im, int r, int g, int b); -/* Returns exact, 100% opaque matches only */ -int gdImageColorExact(gdImagePtr im, int r, int g, int b); -/* Returns an exact match only, including alpha */ -int gdImageColorExactAlpha(gdImagePtr im, int r, int g, int b, int a); -/* Opaque only */ -int gdImageColorResolve(gdImagePtr im, int r, int g, int b); -/* Based on gdImageColorExactAlpha and gdImageColorClosestAlpha */ -int gdImageColorResolveAlpha(gdImagePtr im, int r, int g, int b, int a); +/** + * Typedef: gdUhdrImagePtr + * + * Pointer to . + */ +typedef gdUhdrImage *gdUhdrImagePtr; -/* A simpler way to obtain an opaque truecolor value for drawing on a - truecolor image. Not for use with palette images! */ +/** + * Typedef: gdUhdrError + * + * Structured error details for UltraHDR APIs. + * + * Fields: + * code - libgd UltraHDR status code (GD_UHDR_*) + * provider_code - underlying provider error code, if any + * message - optional human-readable detail string + */ +typedef struct { + int code; + int provider_code; + char message[128]; +} gdUhdrError; -#define gdTrueColor(r, g, b) (((r) << 16) + \ - ((g) << 8) + \ - (b)) +/** + * Typedef: gdUhdrErrorPtr + * + * Pointer to . + */ +typedef gdUhdrError *gdUhdrErrorPtr; -/* Returns a truecolor value with an alpha channel component. - gdAlphaMax (127, **NOT 255**) is transparent, 0 is completely - opaque. */ +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromFile(const char *filename, int format, gdUhdrErrorPtr err); +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromCtx(gdIOCtxPtr ctx, int format, gdUhdrErrorPtr err); +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromPtr(int size, void *data, int format, gdUhdrErrorPtr err); +BGD_DECLARE(void) gdUhdrImageDestroy(gdUhdrImagePtr im); -#define gdTrueColorAlpha(r, g, b, a) (((a) << 24) + \ - ((r) << 16) + \ - ((g) << 8) + \ - (b)) +BGD_DECLARE(gdImagePtr) gdImageCreateFromFile(const char *filename); +BGD_DECLARE(gdImagePtr) gdImageReadFile(const char *filename); +BGD_DECLARE(gdImagePtr) gdImageReadCtx(gdIOCtxPtr ctx); -void gdImageColorDeallocate(gdImagePtr im, int color); -/* Converts a truecolor image to a palette-based image, - using a high-quality two-pass quantization routine - which attempts to preserve alpha channel information - as well as R/G/B color information when creating - a palette. If ditherFlag is set, the image will be - dithered to approximate colors better, at the expense - of some obvious "speckling." colorsWanted can be - anything up to 256. If the original source image - includes photographic information or anything that - came out of a JPEG, 256 is strongly recommended. +/* + Group: Types - Better yet, don't use this function -- write real - truecolor PNGs and JPEGs. The disk space gain of - conversion to palette is not great (for small images - it can be negative) and the quality loss is ugly. */ + typedef: gdSource -gdImagePtr gdImageCreatePaletteFromTrueColor (gdImagePtr im, int ditherFlag, int colorsWanted); + typedef: gdSourcePtr -int gdImageTrueColorToPalette(gdImagePtr im, int ditherFlag, int colorsWanted); -int gdImagePaletteToTrueColor(gdImagePtr src); + *Note:* This interface is *obsolete* and kept only for + *compatibility. Use instead. -/* An attempt at getting the results of gdImageTrueColorToPalette - to look a bit more like the original (im1 is the original - and im2 is the palette version */ -int gdImageColorMatch(gdImagePtr im1, gdImagePtr im2); + Represents a source from which a PNG can be read. Programmers who + do not wish to read PNGs from a file can provide their own + alternate input mechanism, using the + function. See the documentation of that function for an example of + the proper use of this type. -/* 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, - THIS IS IGNORED IF AN ALPHA CHANNEL IS BEING - SAVED. Use gdImageSaveAlpha(im, 0); to - turn off the saving of a full alpha channel in - a truecolor image. Note that gdImageColorTransparent - is usually compatible with older browsers that - do not understand full alpha channels well. TBB */ -void gdImageColorTransparent(gdImagePtr im, int color); + > typedef struct { + > int (*source) (void *context, char *buffer, int len); + > void *context; + > } gdSource, *gdSourcePtr; -void gdImagePaletteCopy(gdImagePtr dst, gdImagePtr src); -void gdImagePng(gdImagePtr im, FILE *out); -void gdImagePngCtx(gdImagePtr im, gdIOCtx *out); -void gdImageGif(gdImagePtr im, FILE *out); -void gdImageGifCtx(gdImagePtr im, gdIOCtx *out); + The source function must return -1 on error, otherwise the number + of bytes fetched. 0 is EOF, not an error! -void * gdImageBmpPtr(gdImagePtr im, int *size, int compression); -void gdImageBmp(gdImagePtr im, FILE *outFile, int compression); -void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression); + 'context' will be passed to your source function. -/* 2.0.12: Compression level: 0-9 or -1, where 0 is NO COMPRESSION at all, - * 1 is FASTEST but produces larger files, 9 provides the best - * compression (smallest files) but takes a long time to compress, and - * -1 selects the default compiled into the zlib library. - */ -void gdImagePngEx(gdImagePtr im, FILE * out, int level, int basefilter); -void gdImagePngCtxEx(gdImagePtr im, gdIOCtx * out, int level, int basefilter); +*/ +typedef struct { + int (*source) (void *context, char *buffer, int len); + void *context; +} gdSource, *gdSourcePtr; -void gdImageWBMP(gdImagePtr image, int fg, FILE *out); -void gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out); +/* Deprecated in favor of gdImageCreateFromPngCtx */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngSource(gdSourcePtr in); +/* for completeness with Sink 2.x APIs, will be removed in 3.0 with all Sink APIs */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiSource(gdSourcePtr in); + +BGD_DECLARE(gdImagePtr) gdImageCreateFromGifSource(gdSourcePtr in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd(FILE *in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGdCtx(gdIOCtxPtr in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGdPtr(int size, void *data); +/* Best to free this memory with gdFree(), not free() */ +BGD_DECLARE(void *) gdImageGdPtr(gdImagePtr im, int *size); +BGD_DECLARE(void) gdImageGd(gdImagePtr im, FILE *out); + +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2(FILE *in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ctx(gdIOCtxPtr in); +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ptr(int size, void *data); + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromGd2Part(FILE *in, int srcx, int srcy, int w, int h); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromGd2PartCtx(gdIOCtxPtr in, int srcx, int srcy, int w, int h); +BGD_DECLARE(gdImagePtr) +gdImageCreateFromGd2PartPtr(int size, void *data, int srcx, int srcy, int w, int h); + +BGD_DECLARE(gdImagePtr) gdImageCreateFromXbm(FILE *in); +BGD_DECLARE(void) +gdImageXbmCtx(gdImagePtr image, char *file_name, int fg, gdIOCtxPtr out); + +/* NOTE: filename, not FILE */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromXpm(char *filename); + +BGD_DECLARE(void *) gdImageBmpPtr(gdImagePtr im, int *size, int compression); +BGD_DECLARE(void) gdImageBmp(gdImagePtr im, FILE *outFile, int compression); +BGD_DECLARE(void) gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression); + +#define GD_BMP_COMPRESS_NONE 0 +#define GD_BMP_COMPRESS_RLE8 1 +#define GD_BMP_COMPRESS_RLE4 2 + +#define GD_BMP_FLAG_NONE 0 +#define GD_BMP_FLAG_FORCE_V4HDR (1 << 0) +#define GD_BMP_FLAG_QUANTIZE (1 << 1) +#define GD_BMP_FLAG_RGB555 (1 << 2) + +BGD_DECLARE(void *) +gdImageBmpPtrEx(gdImagePtr im, int *size, int bpp, int compression, int flags); +BGD_DECLARE(void) +gdImageBmpEx(gdImagePtr im, FILE *outFile, int bpp, int compression, int flags); +BGD_DECLARE(void) +gdImageBmpCtxEx(gdImagePtr im, gdIOCtxPtr out, int bpp, int compression, + int flags); + +BGD_DECLARE(void) gdImageWBMP(gdImagePtr image, int fg, FILE *out); +BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtxPtr out); + + +BGD_DECLARE(int) gdUhdrIsAvailable(void); +BGD_DECLARE(int) gdUhdrImageWidth(gdUhdrImagePtr im); +BGD_DECLARE(int) gdUhdrImageHeight(gdUhdrImagePtr im); +BGD_DECLARE(int) gdUhdrImageHasGainMap(gdUhdrImagePtr im); +BGD_DECLARE(int) +gdUhdrImageResize(gdUhdrImagePtr im, int width, int height, gdUhdrErrorPtr err); +BGD_DECLARE(int) +gdUhdrImageCrop(gdUhdrImagePtr im, int left, int top, int width, int height, + gdUhdrErrorPtr err); +BGD_DECLARE(int) +gdUhdrImageRotate(gdUhdrImagePtr im, int degrees, gdUhdrErrorPtr err); +BGD_DECLARE(int) +gdUhdrImageMirror(gdUhdrImagePtr im, int axis, gdUhdrErrorPtr err); +BGD_DECLARE(int) +gdUhdrImageFile(gdUhdrImagePtr im, const char *filename, int format, + int quality, gdUhdrErrorPtr err); +BGD_DECLARE(int) +gdUhdrImageCtx(gdUhdrImagePtr im, gdIOCtxPtr ctx, int format, int quality, + gdUhdrErrorPtr err); +BGD_DECLARE(void *) +gdUhdrImageWritePtr(gdUhdrImagePtr im, int *size, int format, int quality, + gdUhdrErrorPtr err); +BGD_DECLARE(gdImagePtr) +gdUhdrImageGetSdr(gdUhdrImagePtr im, gdUhdrErrorPtr err); /* Guaranteed to correctly free memory returned by the gdImage*Ptr functions */ @@ -603,8 +1299,11 @@ void *gdImageWBMPPtr(gdImagePtr im, int *size, int fg); /* 100 is highest quality (there is always a little loss with JPEG). 0 is lowest. 10 is about the lowest useful setting. */ -void gdImageJpeg(gdImagePtr im, FILE *out, int quality); -void gdImageJpegCtx(gdImagePtr im, gdIOCtx *out, int quality); +BGD_DECLARE(void) gdImageJpeg(gdImagePtr im, FILE *out, int quality); +BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtxPtr out, int quality); +BGD_DECLARE(void) +gdImageJpegCtxWithMetadata(gdImagePtr im, gdIOCtxPtr out, int quality, + const gdImageMetadata *metadata); /** * Group: WebP @@ -620,13 +1319,14 @@ void gdImageJpegCtx(gdImagePtr im, gdIOCtx *out, int quality); #define gdWebpLossless 101 void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality); +BGD_DECLARE(void) gdImageWebp(gdImagePtr im, FILE *outFile); /* Best to free this memory with gdFree(), not free() */ -void *gdImageJpegPtr(gdImagePtr im, int *size, int quality); +BGD_DECLARE(void *) gdImageJpegPtr(gdImagePtr im, int *size, int quality); +BGD_DECLARE(void *) +gdImageJpegPtrWithMetadata(gdImagePtr im, int *size, int quality, + const gdImageMetadata *metadata); -gdImagePtr gdImageCreateFromGif(FILE *fd); -gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr in); -gdImagePtr gdImageCreateFromGifSource(gdSourcePtr in); void gdImageAvif(gdImagePtr im, FILE *outfile); void gdImageAvifEx(gdImagePtr im, FILE *outfile, int quality, int speed); @@ -644,31 +1344,330 @@ typedef struct { void *context; } gdSink, *gdSinkPtr; -void gdImagePngToSink(gdImagePtr im, gdSinkPtr out); +BGD_DECLARE(void) gdImagePngToSink(gdImagePtr im, gdSinkPtr out); +BGD_DECLARE(void) gdImageQoiToSink(gdImagePtr im, gdSinkPtr out); -void gdImageGd(gdImagePtr im, FILE *out); -void gdImageGd2(gdImagePtr im, FILE *out, int cs, int fmt); - -/* Best to free this memory with gdFree(), not free() */ -void* gdImagePngPtr(gdImagePtr im, int *size); - -/* Best to free this memory with gdFree(), not free() */ -void* gdImageGdPtr(gdImagePtr im, int *size); -void *gdImagePngPtrEx(gdImagePtr im, int *size, int level, int basefilter); +BGD_DECLARE(void) gdImageGd2(gdImagePtr im, FILE *out, int cs, int fmt); /* Best to free this memory with gdFree(), not free() */ -void* gdImageGd2Ptr(gdImagePtr im, int cs, int fmt, int *size); +BGD_DECLARE(void *) gdImageGd2Ptr(gdImagePtr im, int cs, int fmt, int *size); -void gdImageEllipse(gdImagePtr im, int cx, int cy, int w, int h, int c); +BGD_DECLARE(void) gdImageDestroy(gdImagePtr im); -/* Style is a bitwise OR ( | operator ) of these. - gdArc and gdChord are mutually exclusive; - gdChord just connects the starting and ending - angles with a straight line, while gdArc produces - a rounded edge. gdPie is a synonym for gdArc. - gdNoFill indicates that the arc or chord should be - outlined, not filled. gdEdged, used together with - gdNoFill, indicates that the beginning and ending +/* These functions still work with truecolor images, + for which they never return error. */ +int gdImageColorAllocate(gdImagePtr im, int r, int g, int b); +/* gd 2.0: palette entries with non-opaque transparency are permitted. */ +int gdImageColorAllocateAlpha(gdImagePtr im, int r, int g, int b, int a); +/* Assumes opaque is the preferred alpha channel value */ +int gdImageColorClosest(gdImagePtr im, int r, int g, int b); +/* Closest match taking all four parameters into account. + A slightly different color with the same transparency + beats the exact same color with radically different + transparency */ +BGD_DECLARE(int) +gdImageColorClosestAlpha(gdImagePtr im, int r, int g, int b, int a); +/* An alternate method */ +BGD_DECLARE(int) gdImageColorClosestHWB(gdImagePtr im, int r, int g, int b); +/* Returns exact, 100% opaque matches only */ +BGD_DECLARE(int) gdImageColorExact(gdImagePtr im, int r, int g, int b); +/* Returns an exact match only, including alpha */ +BGD_DECLARE(int) +gdImageColorExactAlpha(gdImagePtr im, int r, int g, int b, int a); +/* Opaque only */ +BGD_DECLARE(int) gdImageColorResolve(gdImagePtr im, int r, int g, int b); +/* Based on gdImageColorExactAlpha and gdImageColorClosestAlpha */ +BGD_DECLARE(int) +gdImageColorResolveAlpha(gdImagePtr im, int r, int g, int b, int a); + +/* A simpler way to obtain an opaque truecolor value for drawing on a + truecolor image. Not for use with palette images! */ + +#define gdTrueColor(r, g, b) (((r) << 16) + ((g) << 8) + (b)) + +/** + * Group: Color Composition + * + * Macro: gdTrueColorAlpha + * + * Compose a truecolor value from its components + * + * Parameters: + * r - The red channel (0-255) + * g - The green channel (0-255) + * b - The blue channel (0-255) + * a - The alpha channel (0-127, where 127 is fully transparent, and 0 is + * completely opaque). + * + * See also: + * - + * - + * - + * - + * - + */ +#define gdTrueColorAlpha(r, g, b, a) \ + (((a) << 24) + ((r) << 16) + ((g) << 8) + (b)) + +BGD_DECLARE(void) gdImageColorDeallocate(gdImagePtr im, int color); + +/* Converts a truecolor image to a palette-based image, + using a high-quality two-pass quantization routine + which attempts to preserve alpha channel information + as well as R/G/B color information when creating + a palette. If ditherFlag is set, the image will be + dithered to approximate colors better, at the expense + of some obvious "speckling." colorsWanted can be + anything up to 256. If the original source image + includes photographic information or anything that + came out of a JPEG, 256 is strongly recommended. + + Better yet, don't use these function -- write real + truecolor PNGs and JPEGs. The disk space gain of + conversion to palette is not great (for small images + it can be negative) and the quality loss is ugly. + + DIFFERENCES: gdImageCreatePaletteFromTrueColor creates and + returns a new image. gdImageTrueColorToPalette modifies + an existing image, and the truecolor pixels are discarded. + + gdImageTrueColorToPalette() returns TRUE on success, FALSE on failure. +*/ +BGD_DECLARE(gdImagePtr) +gdImageCreatePaletteFromTrueColor(gdImagePtr im, int ditherFlag, int colorsWanted); + +BGD_DECLARE(int) +gdImageTrueColorToPalette(gdImagePtr im, int ditherFlag, int colorsWanted); + +BGD_DECLARE(int) gdImagePaletteToTrueColor(gdImagePtr src); + +/* An attempt at getting the results of gdImageTrueColorToPalette to + * look a bit more like the original (im1 is the original and im2 is + * the palette version */ + +BGD_DECLARE(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. +*/ +BGD_DECLARE(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. +*/ +BGD_DECLARE(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, + THIS IS IGNORED IF AN ALPHA CHANNEL IS BEING + SAVED. Use gdImageSaveAlpha(im, 0); to + turn off the saving of a full alpha channel in + a truecolor image. Note that gdImageColorTransparent + is usually compatible with older browsers that + do not understand full alpha channels well. TBB */ +BGD_DECLARE(void) gdImageColorTransparent(gdImagePtr im, int color); + +BGD_DECLARE(void) gdImagePaletteCopy(gdImagePtr dst, gdImagePtr src); + +typedef int (*gdCallbackImageColor)(gdImagePtr im, int src); + +BGD_DECLARE(int) gdImageColorReplace(gdImagePtr im, int src, int dst); +BGD_DECLARE(int) +gdImageColorReplaceThreshold(gdImagePtr im, int src, int dst, float threshold); +BGD_DECLARE(int) +gdImageColorReplaceArray(gdImagePtr im, int len, int *src, int *dst); +BGD_DECLARE(int) +gdImageColorReplaceCallback(gdImagePtr im, gdCallbackImageColor callback); + +/* Replaces or blends with the background depending on the + most recent call to gdImageAlphaBlending and the + alpha channel value of 'color'; default is to overwrite. + Tiling and line styling are also implemented + here. All other gd drawing functions pass through this call, + allowing for many useful effects. + Overlay and multiply effects are used when gdImageAlphaBlending + is passed gdEffectOverlay and gdEffectMultiply */ + +BGD_DECLARE(void) gdImageSetPixel(gdImagePtr im, int x, int y, int color); + +BGD_DECLARE(int) gdImageGetPixel(gdImagePtr im, int x, int y); +BGD_DECLARE(int) gdImageGetTrueColorPixel(gdImagePtr im, int x, int y); + +void gdImageAABlend(gdImagePtr im); + +BGD_DECLARE(void) gdImageLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); +BGD_DECLARE(void) gdImageAALine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); + +/* For backwards compatibility only. Use gdImageSetStyle() + for much more flexible line drawing. */ +BGD_DECLARE(void) gdImageDashedLine(gdImagePtr im, int x1, int y1, int x2, int y2, int color); +/* Corners specified (not width and height). Upper left first, lower right + second. */ +BGD_DECLARE(void) gdImageRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color); +/* Solid bar. Upper left corner first, lower right corner second. */ +BGD_DECLARE(void) gdImageFilledRectangle(gdImagePtr im, int x1, int y1, int x2, int y2, int color); +BGD_DECLARE(void) gdImageSetClip(gdImagePtr im, int x1, int y1, int x2, int y2); +BGD_DECLARE(void) gdImageGetClip(gdImagePtr im, int *x1P, int *y1P, int *x2P, int *y2P); +BGD_DECLARE(void) gdImageSetResolution(gdImagePtr im, const unsigned int res_x, const unsigned int res_y); +BGD_DECLARE(void) gdImageChar(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color); +BGD_DECLARE(void) gdImageCharUp(gdImagePtr im, gdFontPtr f, int x, int y, int c, int color); +BGD_DECLARE(void) gdImageString(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color); +BGD_DECLARE(void) gdImageStringUp(gdImagePtr im, gdFontPtr f, int x, int y, unsigned char *s, int color); +BGD_DECLARE(void) gdImageString16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color); +BGD_DECLARE(void) gdImageStringUp16(gdImagePtr im, gdFontPtr f, int x, int y, unsigned short *s, int color); + +/* + * The following functions are required to be called prior to the + * use of any sort of threads in a module load / shutdown function + * respectively. + */ +void gdFontCacheMutexSetup(void); +void gdFontCacheMutexShutdown(void); + +/* 2.0.16: for thread-safe use of gdImageStringFT and friends, + * call this before allowing any thread to call gdImageStringFT. + * Otherwise it is invoked by the first thread to invoke + * gdImageStringFT, with a very small but real risk of a race condition. + * Return 0 on success, nonzero on failure to initialize freetype. + */ +int gdFontCacheSetup(void); + +/* Optional: clean up after application is done using fonts in gdImageStringFT(). */ +void gdFontCacheShutdown(void); + +BGD_DECLARE(void) gdFreeFontCache(void); + +/* Calls gdImageStringFT. Provided for backwards compatibility only. */ +BGD_DECLARE(char *) gdImageStringTTF(gdImagePtr im, int *brect, int fg, const char *fontlist, + double ptsize, double angle, int x, int y, const char *string); + +/* FreeType 2 text output */ +BGD_DECLARE(char *) gdImageStringFT(gdImagePtr im, int *brect, int fg, const char *fontlist, double ptsize, double angle, int x, int y, const char *string); +/* + Group: Types + + typedef: gdFTStringExtra + + typedef: gdFTStringExtraPtr + + A structure and associated pointer type used to pass additional + parameters to the function. See + for the structure definition. + + Thanks to Wez Furlong. +*/ + +/* 2.0.5: provides an extensible way to pass additional parameters. + Thanks to Wez Furlong, sorry for the delay. */ +typedef struct { + int flags; /* Logical OR of gdFTEX_ values */ + double linespacing; /* fine tune line spacing for '\n' */ + int charmap; /* gdFTEX_Unicode, gdFTEX_Shift_JIS, gdFTEX_Big5, + or gdFTEX_Adobe_Custom/gdFTEX_MacRoman */ + int hdpi; /* if (flags & gdFTEX_RESOLUTION) */ + int vdpi; /* if (flags & gdFTEX_RESOLUTION) */ + char *xshow; /* gdMalloc'ed result if gdFTEX_XSHOW is set */ + char *fontpath; /* gdMalloc'ed result if gdFTEX_RETURNFONTPATHNAME is set */ +} gdFTStringExtra, *gdFTStringExtraPtr; + +#define gdFTEX_LINESPACE 1 +#define gdFTEX_CHARMAP 2 +#define gdFTEX_RESOLUTION 4 +#define gdFTEX_DISABLE_KERNING 8 +#define gdFTEX_XSHOW 16 +#define gdFTEX_FONTPATHNAME 32 +#define gdFTEX_FONTCONFIG 64 +#define gdFTEX_RETURNFONTPATHNAME 128 + +BGD_DECLARE(int) gdFTUseFontConfig(int flag); + +/* These are NOT flags; set one in 'charmap' if you set the gdFTEX_CHARMAP bit in 'flags'. */ +#define gdFTEX_Unicode 0 +#define gdFTEX_Shift_JIS 1 +#define gdFTEX_Big5 2 +#define gdFTEX_Adobe_Custom 3 +#define gdFTEX_MacRoman gdFTEX_Adobe_Custom + +/* FreeType 2 text output with fine tuning */ +BGD_DECLARE(char *) gdImageStringFTEx(gdImagePtr im, int *brect, int fg, const char *fontlist, double ptsize, double angle, int x, int y, const char *string, gdFTStringExtraPtr strex); +/* + Group: Types + + typedef: gdPoint + + typedef: gdPointPtr + + Represents a point in the coordinate space of the image; used by + , and + for polygon drawing. + + > typedef struct { + > int x, y; + > } gdPoint, *gdPointPtr; + +*/ +typedef struct { + int x, y; +} gdPoint, *gdPointPtr; + +/** + * Typedef: gdRect + * + * A rectangle in the coordinate space of the image + * + * Members: + * x - The x-coordinate of the upper left corner. + * y - The y-coordinate of the upper left corner. + * width - The width. + * height - The height. + * + * Typedef: gdRectPtr + * + * A pointer to a + */ +typedef struct { + int x, y; + int width, height; +} gdRect, *gdRectPtr; + +BGD_DECLARE(void) gdImagePolygon(gdImagePtr im, gdPointPtr p, int n, int c); +BGD_DECLARE(void) gdImageOpenPolygon(gdImagePtr im, gdPointPtr p, int n, int c); +BGD_DECLARE(void) +gdImageFilledPolygon(gdImagePtr im, gdPointPtr p, int n, int c); + +BGD_DECLARE(void) +gdImageFilledArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, + int color, int style); +BGD_DECLARE(void) +gdImageArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, + int color); +BGD_DECLARE(void) +gdImageEllipse(gdImagePtr im, int cx, int cy, int w, int h, int color); +BGD_DECLARE(void) +gdImageFilledEllipse(gdImagePtr im, int cx, int cy, int w, int h, int color); + + +/* Style is a bitwise OR ( | operator ) of these. + gdArc and gdChord are mutually exclusive; + gdChord just connects the starting and ending + angles with a straight line, while gdArc produces + a rounded edge. gdPie is a synonym for gdArc. + gdNoFill indicates that the arc or chord should be + outlined, not filled. gdEdged, used together with + gdNoFill, indicates that the beginning and ending angles should be connected to the center; this is a good way to outline (rather than fill) a 'pie slice'. */ @@ -678,9 +1677,6 @@ void gdImageEllipse(gdImagePtr im, int cx, int cy, int w, int h, int c); #define gdNoFill 2 #define gdEdged 4 -void gdImageFilledArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color, int style); -void gdImageArc(gdImagePtr im, int cx, int cy, int w, int h, int s, int e, int color); -void gdImageFilledEllipse(gdImagePtr im, int cx, int cy, int w, int h, int color); void gdImageFillToBorder(gdImagePtr im, int x, int y, int border, int color); void gdImageFill(gdImagePtr im, int x, int y, int color); void gdImageCopy(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h); @@ -692,7 +1688,8 @@ void gdImageCopyMergeGray(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, /* Stretches or shrinks to fit, as needed. Does NOT attempt to average the entire set of source pixels that scale down onto the destination pixel. */ -void gdImageCopyResized(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH); +BGD_DECLARE(void) +gdImageCopyResized(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH); /* gd 2.0: stretches or shrinks to fit, as needed. When called with a truecolor destination image, this function averages the @@ -703,30 +1700,76 @@ void gdImageCopyResized(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int on modern hardware, except for some embedded devices. If the destination is a palette image, gdImageCopyResized is substituted automatically. */ -void gdImageCopyResampled(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH); +BGD_DECLARE(void) +gdImageCopyResampled(gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH); + +/* Source is a rectangle, with its upper left corner at + srcX and srcY. Destination is the *center* of + the rotated copy. Angle is in degrees, same as + gdImageArc. Floating point destination center + coordinates allow accurate rotation of + objects of odd-numbered width or height. */ +BGD_DECLARE(void) gdImageCopyRotated(gdImagePtr dst, gdImagePtr src, double dstX, double dstY, int srcX, int srcY, int srcWidth, int srcHeight, int angle); + +BGD_DECLARE(gdImagePtr) gdImageClone(gdImagePtr src); -gdImagePtr gdImageClone(gdImagePtr src); +BGD_DECLARE(void) gdImageSetBrush(gdImagePtr im, gdImagePtr brush); +BGD_DECLARE(void) gdImageSetTile(gdImagePtr im, gdImagePtr tile); +BGD_DECLARE(void) gdImageSetAntiAliased(gdImagePtr im, int c); +BGD_DECLARE(void) gdImageSetAntiAliasedDontBlend(gdImagePtr im, int c, int dont_blend); +BGD_DECLARE(void) gdImageSetStyle(gdImagePtr im, int *style, int noOfPixels); -void gdImageSetBrush(gdImagePtr im, gdImagePtr brush); -void gdImageSetTile(gdImagePtr im, gdImagePtr tile); -void gdImageSetAntiAliased(gdImagePtr im, int c); -void gdImageSetAntiAliasedDontBlend(gdImagePtr im, int c, int dont_blend); -void gdImageSetStyle(gdImagePtr im, int *style, int noOfPixels); /* Line thickness (defaults to 1). Affects lines, ellipses, - rectangles, polygons and so forth. */ -void gdImageSetThickness(gdImagePtr im, int thickness); + rectangles, polygons and so forth. */ +BGD_DECLARE(void) gdImageSetThickness(gdImagePtr im, int thickness); /* On or off (1 or 0) for all three of these. */ -void gdImageInterlace(gdImagePtr im, int interlaceArg); -void gdImageAlphaBlending(gdImagePtr im, int alphaBlendingArg); -void gdImageAntialias(gdImagePtr im, int antialias); -void gdImageSaveAlpha(gdImagePtr im, int saveAlphaArg); - -enum gdPixelateMode { - GD_PIXELATE_UPPERLEFT, - GD_PIXELATE_AVERAGE +BGD_DECLARE(void) gdImageInterlace(gdImagePtr im, int interlaceArg); +BGD_DECLARE(void) gdImageAlphaBlending(gdImagePtr im, int alphaBlendingArg); +BGD_DECLARE(void) gdImageSaveAlpha(gdImagePtr im, int saveAlphaArg); + + +/** + * 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: + * - + */ +enum gdPaletteQuantizationMethod { + GD_QUANT_DEFAULT = 0, + GD_QUANT_JQUANT = 1, + GD_QUANT_NEUQUANT = 2, + GD_QUANT_LIQ = 3 }; +BGD_DECLARE(gdImagePtr) +gdImageNeuQuant(gdImagePtr im, const int max_color, int sample_factor); + + + +/* filters section + * + * Negate the imag src, white becomes black, + * The red, green, and blue intensities of an image are negated. + * White becomes black, yellow becomes blue, etc. + */ -int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode); +enum gdPixelateMode { GD_PIXELATE_UPPERLEFT, GD_PIXELATE_AVERAGE }; + +BGD_DECLARE(int) +gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode); typedef struct { int sub; @@ -736,48 +1779,244 @@ typedef struct { unsigned int seed; } gdScatter, *gdScatterPtr; -int gdImageScatter(gdImagePtr im, int sub, int plus); -int gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], unsigned int num_colors); -int gdImageScatterEx(gdImagePtr im, gdScatterPtr s); - -/* Macros to access information about images. */ +BGD_DECLARE(int) gdImageScatter(gdImagePtr im, int sub, int plus); +BGD_DECLARE(int) +gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], + unsigned int num_colors); +BGD_DECLARE(int) gdImageScatterEx(gdImagePtr im, gdScatterPtr s); +BGD_DECLARE(int) gdImageSmooth(gdImagePtr im, float weight); +BGD_DECLARE(int) gdImageMeanRemoval(gdImagePtr im); +BGD_DECLARE(int) gdImageEmboss(gdImagePtr im); +BGD_DECLARE(int) gdImageGaussianBlur(gdImagePtr im); +BGD_DECLARE(int) gdImageEdgeDetectQuick(gdImagePtr src); +BGD_DECLARE(int) gdImageSelectiveBlur(gdImagePtr src); +BGD_DECLARE(int) +gdImageConvolution(gdImagePtr src, float filter[3][3], float filter_div, + float offset); +BGD_DECLARE(int) +gdImageColor(gdImagePtr src, const int red, const int green, const int blue, + const int alpha); +BGD_DECLARE(int) gdImageContrast(gdImagePtr src, double contrast); +BGD_DECLARE(int) gdImageBrightness(gdImagePtr src, int brightness); +BGD_DECLARE(int) gdImageGrayScale(gdImagePtr src); +BGD_DECLARE(int) gdImageNegate(gdImagePtr src); + +BGD_DECLARE(gdImagePtr) +gdImageCopyGaussianBlurred(gdImagePtr src, int radius, double sigma); -/* Returns nonzero if the image is a truecolor image, - zero for a palette image. */ +/** + * Group: Accessor Macros + */ +/** + * Macro: gdImageTrueColor + * + * Whether an image is a truecolor image. + * + * Parameters: + * im - The image. + * + * Returns: + * Non-zero if the image is a truecolor image, zero for palette images. + */ #define gdImageTrueColor(im) ((im)->trueColor) +/** + * Macro: gdImageSX + * + * Gets the width (in pixels) of an image. + * + * Parameters: + * im - The image. + */ #define gdImageSX(im) ((im)->sx) + +/** + * Macro: gdImageSY + * + * Gets the height (in pixels) of an image. + * + * Parameters: + * im - The image. + */ #define gdImageSY(im) ((im)->sy) + +/** + * Macro: gdImageColorsTotal + * + * Gets the number of colors in the palette. + * + * This macro is only valid for palette images. + * + * Parameters: + * im - The image + */ #define gdImageColorsTotal(im) ((im)->colorsTotal) -#define gdImageRed(im, c) ((im)->trueColor ? gdTrueColorGetRed(c) : \ - (im)->red[(c)]) -#define gdImageGreen(im, c) ((im)->trueColor ? gdTrueColorGetGreen(c) : \ - (im)->green[(c)]) -#define gdImageBlue(im, c) ((im)->trueColor ? gdTrueColorGetBlue(c) : \ - (im)->blue[(c)]) -#define gdImageAlpha(im, c) ((im)->trueColor ? gdTrueColorGetAlpha(c) : \ - (im)->alpha[(c)]) + +/** + * Macro: gdImageRed + * + * Gets the red component value of a given color. + * + * Parameters: + * im - The image. + * c - The color. + */ +#define gdImageRed(im, c) \ + ((im)->trueColor ? gdTrueColorGetRed(c) : (im)->red[(c)]) + +/** + * Macro: gdImageGreen + * + * Gets the green component value of a given color. + * + * Parameters: + * im - The image. + * c - The color. + */ +#define gdImageGreen(im, c) \ + ((im)->trueColor ? gdTrueColorGetGreen(c) : (im)->green[(c)]) + +/** + * Macro: gdImageBlue + * + * Gets the blue component value of a given color. + * + * Parameters: + * im - The image. + * c - The color. + */ +#define gdImageBlue(im, c) \ + ((im)->trueColor ? gdTrueColorGetBlue(c) : (im)->blue[(c)]) + +/** + * Macro: gdImageAlpha + * + * Gets the alpha component value of a given color. + * + * Parameters: + * im - The image. + * c - The color. + */ +#define gdImageAlpha(im, c) \ + ((im)->trueColor ? gdTrueColorGetAlpha(c) : (im)->alpha[(c)]) + +/** + * Macro: gdImageGetTransparent + * + * Gets the transparent color of the image. + * + * Parameters: + * im - The image. + * + * See also: + * - + */ #define gdImageGetTransparent(im) ((im)->transparent) + +/** + * Macro: gdImageGetInterlaced + * + * Whether an image is interlaced. + * + * Parameters: + * im - The image. + * + * Returns: + * Non-zero for interlaced images, zero otherwise. + * + * See also: + * - + */ #define gdImageGetInterlaced(im) ((im)->interlace) -/* These macros provide direct access to pixels in - palette-based and truecolor images, respectively. - If you use these macros, you must perform your own - bounds checking. Use of the macro for the correct type - of image is also your responsibility. */ +/** + * Macro: gdImagePalettePixel + * + * Gets the color of a pixel. + * + * Calling this macro is only valid for palette images. + * No bounds checking is done for the coordinates. + * + * Parameters: + * im - The image. + * x - The x-coordinate. + * y - The y-coordinate. + * + * See also: + * - + * - + */ #define gdImagePalettePixel(im, x, y) (im)->pixels[(y)][(x)] + +/** + * Macro: gdImageTrueColorPixel + * + * Gets the color of a pixel. + * + * Calling this macro is only valid for truecolor images. + * No bounds checking is done for the coordinates. + * + * Parameters: + * im - The image. + * x - The x-coordinate. + * y - The y-coordinate. + * + * See also: + * - + * - + */ #define gdImageTrueColorPixel(im, x, y) (im)->tpixels[(y)][(x)] + +/** + * Macro: gdImageResolutionX + * + * Gets the horizontal resolution in DPI. + * + * Parameters: + * im - The image. + * + * See also: + * - + * - + */ #define gdImageResolutionX(im) (im)->res_x + +/** + * Macro: gdImageResolutionY + * + * Gets the vertical resolution in DPI. + * + * Parameters: + * im - The image. + * + * See also: + * - + * - + */ #define gdImageResolutionY(im) (im)->res_y /* I/O Support routines. */ -gdIOCtx* gdNewFileCtx(FILE*); -gdIOCtx* gdNewDynamicCtx(int, void*); -gdIOCtx *gdNewDynamicCtxEx(int size, void *data, int freeFlag); -gdIOCtx* gdNewSSCtx(gdSourcePtr in, gdSinkPtr out); -void* gdDPExtractData(struct gdIOCtx* ctx, int *size); +BGD_DECLARE(gdIOCtxPtr) gdNewFileCtx(FILE *); +/* If data is null, size is ignored and an initial data buffer is + allocated automatically. NOTE: this function assumes gd has the right + to free or reallocate "data" at will! Also note that gd will free + "data" when the IO context is freed. If data is not null, it must point + to memory allocated with gdMalloc, or by a call to gdImage[something]Ptr. + If not, see gdNewDynamicCtxEx for an alternative. */ +BGD_DECLARE(gdIOCtxPtr) gdNewDynamicCtx(int size, void *data); +/* 2.0.21: if freeFlag is nonzero, gd will free and/or reallocate "data" as + needed as described above. If freeFlag is zero, gd will never free + or reallocate "data", which means that the context should only be used + for *reading* an image from a memory buffer, or writing an image to a + memory buffer which is already large enough. If the memory buffer is + not large enough and an image write is attempted, the write operation + will fail. Those wishing to write an image to a buffer in memory have + a much simpler alternative in the gdImage[something]Ptr functions. */ +BGD_DECLARE(gdIOCtxPtr) gdNewDynamicCtxEx(int size, void *data, int freeFlag); +BGD_DECLARE(gdIOCtxPtr) gdNewSSCtx(gdSourcePtr in, gdSinkPtr out); +BGD_DECLARE(void *) gdDPExtractData(gdIOCtxPtr ctx, int *size); #define GD2_CHUNKSIZE 128 #define GD2_CHUNKSIZE_MIN 64 @@ -788,65 +2027,25 @@ void* gdDPExtractData(struct gdIOCtx* ctx, int *size); #define GD2_FMT_RAW 1 #define GD2_FMT_COMPRESSED 2 - -/* filters section - * - * Negate the imag src, white becomes black, - * The red, green, and blue intensities of an image are negated. - * White becomes black, yellow becomes blue, etc. - */ -int gdImageNegate(gdImagePtr src); - -/* Convert the image src to a grayscale image */ -int gdImageGrayScale(gdImagePtr src); - -/* Set the brightness level for the image src */ -int gdImageBrightness(gdImagePtr src, int brightness); - -/* Set the contrast level for the image */ -int gdImageContrast(gdImagePtr src, double contrast); - -/* Simply adds or subtracts respectively red, green or blue to a pixel */ -int gdImageColor(gdImagePtr src, const int red, const int green, const int blue, const int alpha); - -/* Image convolution by a 3x3 custom matrix */ -int gdImageConvolution(gdImagePtr src, float ft[3][3], float filter_div, float offset); - -int gdImageEdgeDetectQuick(gdImagePtr src); - -int gdImageGaussianBlur(gdImagePtr im); - -int gdImageSelectiveBlur( gdImagePtr src); - -int gdImageEmboss(gdImagePtr im); - -int gdImageMeanRemoval(gdImagePtr im); - -int gdImageSmooth(gdImagePtr im, float weight); - /* Image comparison definitions */ -int gdImageCompare(gdImagePtr im1, gdImagePtr im2); +BGD_DECLARE(int) gdImageCompare(gdImagePtr im1, gdImagePtr im2); -void gdImageFlipHorizontal(gdImagePtr im); -void gdImageFlipVertical(gdImagePtr im); -void gdImageFlipBoth(gdImagePtr im); - -#define GD_FLIP_HORIZONTAL 1 -#define GD_FLIP_VERTICAL 2 -#define GD_FLIP_BOTH 3 +BGD_DECLARE(void) gdImageFlipHorizontal(gdImagePtr im); +BGD_DECLARE(void) gdImageFlipVertical(gdImagePtr im); +BGD_DECLARE(void) gdImageFlipBoth(gdImagePtr im); /** * Group: Crop * * Constants: gdCropMode - * GD_CROP_DEFAULT - Default crop mode (4 corners or background) + * GD_CROP_DEFAULT - Same as GD_CROP_TRANSPARENT * GD_CROP_TRANSPARENT - Crop using the transparent color * GD_CROP_BLACK - Crop black borders * GD_CROP_WHITE - Crop white borders * GD_CROP_SIDES - Crop using colors of the 4 corners * * See also: - * + * - **/ enum gdCropMode { GD_CROP_DEFAULT = 0, @@ -857,62 +2056,109 @@ enum gdCropMode { GD_CROP_THRESHOLD }; -gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop); -gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode); -gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold); +BGD_DECLARE(gdImagePtr) gdImageCrop(gdImagePtr src, const gdRect *crop); +BGD_DECLARE(gdImagePtr) gdImageCropAuto(gdImagePtr im, const unsigned int mode); +BGD_DECLARE(gdImagePtr) +gdImageCropThreshold(gdImagePtr im, const unsigned int color, + const float threshold); -int gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id); -gdInterpolationMethod gdImageGetInterpolationMethod(gdImagePtr im); +BGD_DECLARE(int) +gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id); +BGD_DECLARE(gdInterpolationMethod) gdImageGetInterpolationMethod(gdImagePtr im); -gdImagePtr gdImageScale(const gdImagePtr src, const unsigned int new_width, const unsigned int new_height); +BGD_DECLARE(gdImagePtr) +gdImageScale(const gdImagePtr src, const unsigned int new_width, + const unsigned int new_height); -gdImagePtr gdImageRotateInterpolated(const gdImagePtr src, const float angle, int bgcolor); +BGD_DECLARE(gdImagePtr) +gdImageRotateInterpolated(const gdImagePtr src, const float angle, int bgcolor); typedef enum { GD_AFFINE_TRANSLATE = 0, GD_AFFINE_SCALE, GD_AFFINE_ROTATE, GD_AFFINE_SHEAR_HORIZONTAL, - GD_AFFINE_SHEAR_VERTICAL, + GD_AFFINE_SHEAR_VERTICAL } gdAffineStandardMatrix; -int gdAffineApplyToPointF (gdPointFPtr dst, const gdPointFPtr src, const double affine[6]); -int gdAffineInvert (double dst[6], const double src[6]); -int gdAffineFlip (double dst_affine[6], const double src_affine[6], const int flip_h, const int flip_v); -int gdAffineConcat (double dst[6], const double m1[6], const double m2[6]); - -int gdAffineIdentity (double dst[6]); -int gdAffineScale (double dst[6], const double scale_x, const double scale_y); -int gdAffineRotate (double dst[6], const double angle); -int gdAffineShearHorizontal (double dst[6], const double angle); -int gdAffineShearVertical(double dst[6], const double angle); -int gdAffineTranslate (double dst[6], const double offset_x, const double offset_y); -double gdAffineExpansion (const double src[6]); -int gdAffineRectilinear (const double src[6]); -int gdAffineEqual (const double matrix1[6], const double matrix2[6]); -int gdTransformAffineGetImage(gdImagePtr *dst, const gdImagePtr src, gdRectPtr src_area, const double affine[6]); -int gdTransformAffineCopy(gdImagePtr dst, int dst_x, int dst_y, const gdImagePtr src, gdRectPtr src_region, const double affine[6]); +BGD_DECLARE(int) +gdAffineApplyToPointF(gdPointFPtr dst, const gdPointFPtr src, + const double affine[6]); +BGD_DECLARE(int) gdAffineInvert(double dst[6], const double src[6]); +BGD_DECLARE(int) +gdAffineFlip(double dst_affine[6], const double src_affine[6], const int flip_h, + const int flip_v); +BGD_DECLARE(int) +gdAffineConcat(double dst[6], const double m1[6], const double m2[6]); + +BGD_DECLARE(int) gdAffineIdentity(double dst[6]); +BGD_DECLARE(int) +gdAffineScale(double dst[6], const double scale_x, const double scale_y); +BGD_DECLARE(int) gdAffineRotate(double dst[6], const double angle); +BGD_DECLARE(int) gdAffineShearHorizontal(double dst[6], const double angle); +BGD_DECLARE(int) gdAffineShearVertical(double dst[6], const double angle); +BGD_DECLARE(int) +gdAffineTranslate(double dst[6], const double offset_x, const double offset_y); +BGD_DECLARE(double) gdAffineExpansion(const double src[6]); +BGD_DECLARE(int) gdAffineRectilinear(const double src[6]); +BGD_DECLARE(int) +gdAffineEqual(const double matrix1[6], const double matrix2[6]); +BGD_DECLARE(int) +gdTransformAffineGetImage(gdImagePtr *dst, const gdImagePtr src, + gdRectPtr src_area, const double affine[6]); +BGD_DECLARE(int) +gdTransformAffineCopy(gdImagePtr dst, int dst_x, int dst_y, + const gdImagePtr src, gdRectPtr src_region, + const double affine[6]); /* gdTransformAffineCopy(gdImagePtr dst, int x0, int y0, int x1, int y1, const gdImagePtr src, int src_width, int src_height, const double affine[6]); */ -int gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], gdRectPtr bbox); - +BGD_DECLARE(int) +gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], + gdRectPtr bbox); -#define GD_CMP_IMAGE 1 /* Actual image IS different */ -#define GD_CMP_NUM_COLORS 2 /* Number of Colours in palette differ */ -#define GD_CMP_COLOR 4 /* Image colours differ */ -#define GD_CMP_SIZE_X 8 /* Image width differs */ -#define GD_CMP_SIZE_Y 16 /* Image heights differ */ -#define GD_CMP_TRANSPARENT 32 /* Transparent colour */ -#define GD_CMP_BACKGROUND 64 /* Background colour */ -#define GD_CMP_INTERLACE 128 /* Interlaced setting */ -#define GD_CMP_TRUECOLOR 256 /* Truecolor vs palette differs */ +/** + * Group: Image Comparison + * + * Constants: + * GD_CMP_IMAGE - Actual image IS different + * GD_CMP_NUM_COLORS - Number of colors in pallette differ + * GD_CMP_COLOR - Image colors differ + * GD_CMP_SIZE_X - Image width differs + * GD_CMP_SIZE_Y - Image heights differ + * GD_CMP_TRANSPARENT - Transparent color differs + * GD_CMP_BACKGROUND - Background color differs + * GD_CMP_INTERLACE - Interlaced setting differs + * GD_CMP_TRUECOLOR - Truecolor vs palette differs + * + * See also: + * - + */ +#define GD_CMP_IMAGE 1 +#define GD_CMP_NUM_COLORS 2 +#define GD_CMP_COLOR 4 +#define GD_CMP_SIZE_X 8 +#define GD_CMP_SIZE_Y 16 +#define GD_CMP_TRANSPARENT 32 +#define GD_CMP_BACKGROUND 64 +#define GD_CMP_INTERLACE 128 +#define GD_CMP_TRUECOLOR 256 /* resolution affects ttf font rendering, particularly hinting */ #define GD_RESOLUTION 96 /* pixels per inch */ +/* Version information functions */ +BGD_DECLARE(int) gdMajorVersion(void); +BGD_DECLARE(int) gdMinorVersion(void); +BGD_DECLARE(int) gdReleaseVersion(void); +BGD_DECLARE(const char *) gdExtraVersion(void); +BGD_DECLARE(const char *) gdVersionString(void); + +/* newfangled special effects */ +#include "gdfx.h" + #ifdef __cplusplus } #endif diff --git a/ext/gd/libgd/gd_array.c b/ext/gd/libgd/gd_array.c new file mode 100644 index 000000000000..18b7558d3026 --- /dev/null +++ b/ext/gd/libgd/gd_array.c @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd.h" +#include "gdhelpers.h" +#include "gd_color.h" +#include "gd_errors.h" +#include "gd_array.h" + +void gdArrayInit(gdArrayPtr array, unsigned int element_size) +{ + array->size = 0; + array->cnt_elements = 0; + array->element_size = element_size; + array->elements = NULL; +} + +void gdArrayDestroy(gdArrayPtr array) +{ + gdFree(array->elements); +} + +void gdArrayTruncate(gdArrayPtr array, unsigned int cnt_elements) +{ + if (cnt_elements < array->cnt_elements) + array->cnt_elements = cnt_elements; +} + +void * +gdArrayIndex(gdArrayPtr array, unsigned int index) +{ + if (index == 0 && array->cnt_elements == 0) + return NULL; + + return (unsigned char *)array->elements + index * array->element_size; +} + +const void * +gdArrayIndexConst(gdArrayPtr array, unsigned int index) +{ + if (index == 0 && array->cnt_elements == 0) + return NULL; + + if (index >= array->cnt_elements) return NULL; + + return (const unsigned char *)array->elements + index * array->element_size; +} + +int gdArrayReallocBy(gdArrayPtr array, unsigned int additional) +{ + char *new_elements; + unsigned int old_size = array->size; + unsigned int required_size = array->cnt_elements + additional; + unsigned int new_size; + + /* check for integer overflow */ + if (required_size > INT_MAX || required_size < array->cnt_elements) + return 0; + + if (required_size <= old_size) + return 1; + + if (old_size == 0) + new_size = 1; + else + new_size = old_size * 2; + + while (new_size <= required_size) { + if (new_size > (unsigned int)INT_MAX / 2) { + new_size = required_size; + break; + } + new_size *= 2; + } + + array->size = new_size; + + if (array->size > (unsigned int)INT_MAX + || array->element_size > (unsigned int)INT_MAX + || overflow2((int)array->size, (int)array->element_size)) { + array->size = old_size; + return 0; + } + new_elements = gdRealloc(array->elements, + (size_t)array->size * array->element_size); + + if (new_elements == NULL) + { + array->size = old_size; + return 0; + } + + array->elements = new_elements; + + return 1; +} + +int gdArrayAppend(gdArrayPtr array, + const void *element) +{ + return gdArrayAppendMultiple(array, element, 1); +} + +int gdArrayAppendMultiple(gdArrayPtr array, + const void *elements, + unsigned int cnt_elements) +{ + int status; + void *dest; + + status = gdArrayAlloc(array, cnt_elements, &dest); + if (!status) + return status; + memcpy(dest, elements, (size_t)cnt_elements * array->element_size); + + return 1; +} + +int gdArrayAlloc(gdArrayPtr array, + unsigned int cnt_elements, + void **elements) +{ + int status; + + status = gdArrayReallocBy(array, cnt_elements); + if (!status) + return status; + + *elements = (unsigned char *)array->elements + + (size_t)array->cnt_elements * array->element_size; + + array->cnt_elements += cnt_elements; + + return 1; +} + +unsigned int +gdArrayNumElements(const gdArrayPtr array) +{ + return array->cnt_elements; +} + +unsigned int +gdArraySize(const gdArrayPtr array) +{ + return array->size; +} + +void * +gdArrayGetData(const gdArrayPtr array) +{ + return array->elements; +} diff --git a/ext/gd/libgd/gd_array.h b/ext/gd/libgd/gd_array.h new file mode 100644 index 000000000000..747c68704a20 --- /dev/null +++ b/ext/gd/libgd/gd_array.h @@ -0,0 +1,57 @@ +#ifndef GD_ARRAY_H +#define GD_ARRAY_H 1 + +typedef struct gdArrayStruct { + unsigned int size; + unsigned int cnt_elements; + unsigned int element_size; + void *elements; +} gdArray; +typedef gdArray* gdArrayPtr; + +/* +C++ Vect like for C. +element_size is f.e. sizeof(gdPath) equivalent to +gdPathPtr path; +or +gdPathPtr path[12]; for a fixed length + +Each function takes a pointers to the array. +*/ + +/* Initialize a gdArray, gdArrayarray allocation and freed is the caller's responsability */ +void gdArrayInit (gdArrayPtr array, unsigned int element_size); + +/* Destroy the internal data of a gdArray, gdArrayarray has to be freed by the caller */ +void gdArrayDestroy (gdArrayPtr array); + +/* Growth the internal data storage to be able to store the additional requested amount (of elements) */ +int gdArrayReallocBy(gdArrayPtr array, unsigned int additional); + +/* Reduce the elements to cnt_elements (storage will remain the same until more are +requested) */ +void gdArrayTruncate(gdArrayPtr array, unsigned int cnt_elements); + +/* Append an element at the end of the array */ +int gdArrayAppend(gdArrayPtr array, const void *element); + +/* Append cnt_elements elements to the array. +*elements is the actual gdSpanPtr[12] for 12 elements */ +int gdArrayAppendMultiple(gdArrayPtr array, const void *elements, unsigned int cnt_elements); + +/* Returns the elements at the given index */ +void * gdArrayIndex(gdArrayPtr array, unsigned int index); + +/* Same as gdArrayIndex but immutable pointer */ +const void * gdArrayIndexConst(gdArrayPtr array, unsigned int index); + +/* Return the current storage size. Storage size is the available space, not the actual +amount of elements stored. Use gdArrayNumElements to know the actual stored elements count */ +unsigned int gdArraySize(const gdArrayPtr array); +unsigned int gdArrayNumElements(const gdArrayPtr array); + + +/* Private for internal usage in gdArray */ +int gdArrayAlloc(gdArrayPtr array, unsigned int cnt_elements, void **elements); +void *gdArrayGetData(const gdArrayPtr array); +#endif /* GD_ARRAY_H */ diff --git a/ext/gd/libgd/gd_avif.c b/ext/gd/libgd/gd_avif.c index 9c1ffdc34bc6..0852d17d9e75 100644 --- a/ext/gd/libgd/gd_avif.c +++ b/ext/gd/libgd/gd_avif.c @@ -1,33 +1,43 @@ +/** + * File: AVIF IO + * + * Read and write AVIF images using libavif + * (https://github.com/AOMediaCodec/libavif) . Currently, the only ICC profile + * we support is sRGB. Since that's what web browsers use, it's sufficient for + * now. + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include +#include #include #include #include -#include -#include #include "gd.h" #include "gd_errors.h" -#include "gdhelpers.h" #include "gd_intern.h" +#include "gdhelpers.h" #ifdef HAVE_LIBAVIF #include /* Define defaults for encoding images: - CHROMA_SUBSAMPLING_DEFAULT: 4:2:0 is commonly used for Chroma subsampling. - CHROMA_SUBAMPLING_HIGH_QUALITY: Use 4:4:4, or no subsampling, when a sufficient high quality is requested. - SUBAMPLING_HIGH_QUALITY_THRESHOLD: At or above this value, use CHROMA_SUBAMPLING_HIGH_QUALITY - QUANTIZER_DEFAULT: - We need more testing to really know what quantizer settings are optimal, - but teams at Google have been using maximum=30 as a starting point. - QUALITY_DEFAULT: following gd conventions, -1 indicates the default. - SPEED_DEFAULT: - AVIF_SPEED_DEFAULT is simply the default encoding speed of the AV1 codec. - This could be as slow as 0. So we use 6, which is currently considered to be a fine default. + CHROMA_SUBSAMPLING_DEFAULT: 4:2:0 is commonly used for Chroma + subsampling. CHROMA_SUBAMPLING_HIGH_QUALITY: Use 4:4:4, or no subsampling, + when a sufficient high quality is requested. + SUBAMPLING_HIGH_QUALITY_THRESHOLD: At or above this value, use + CHROMA_SUBAMPLING_HIGH_QUALITY QUANTIZER_DEFAULT: We need more testing to + really know what quantizer settings are optimal, but teams at Google have + been using maximum=30 as a starting point. QUALITY_DEFAULT: following gd + conventions, -1 indicates the default. SPEED_DEFAULT: AVIF_SPEED_DEFAULT is + simply the default encoding speed of the AV1 codec. This could be as slow as + 0. So we use 6, which is currently considered to be a fine default. + */ #define CHROMA_SUBSAMPLING_DEFAULT AVIF_PIXEL_FORMAT_YUV420 @@ -37,15 +47,18 @@ #define QUALITY_DEFAULT -1 #define SPEED_DEFAULT 6 -// This initial size for the gdIOCtx is standard among GD image conversion functions. +// This initial size for the gdIOCtx is standard among GD image conversion +// functions. #define NEW_DYNAMIC_CTX_SIZE 2048 // Our quality param ranges from 0 to 100. -// To calculate quality, we convert from AVIF's quantizer scale, which runs from 63 to 0. +// To calculate quality, we convert from AVIF's quantizer scale, which runs from +// 63 to 0. #define MAX_QUALITY 100 -// These constants are for computing the number of tiles and threads to use during encoding. -// Maximum threads are from libavif/contrib/gkd-pixbuf/loader.c. +// These constants are for computing the number of tiles and threads to use +// during encoding. Maximum threads are from +// libavif/contrib/gkd-pixbuf/loader.c. #define MIN_TILE_AREA (512 * 512) #define MAX_TILES 8 #define MAX_THREADS 64 @@ -61,20 +74,19 @@ PNG's convention in which 255 is opaque. */ #define alpha7BitTo8Bit(alpha7Bit) \ - (alpha7Bit == 127 ? \ - 0 : \ - 255 - ((alpha7Bit << 1) + (alpha7Bit >> 6))) + (alpha7Bit == 127 ? 0 : 255 - ((alpha7Bit << 1) + (alpha7Bit >> 6))) #define alpha8BitTo7Bit(alpha8Bit) (gdAlphaMax - (alpha8Bit >> 1)) - /*** Helper functions ***/ /* Convert the quality param we expose to the quantity params used by libavif. - The *Quantizer* params values can range from 0 to 63, with 0 = highest quality and 63 = worst. - We make the scale 0-100, and we reverse this, so that 0 = worst quality and 100 = highest. + The *Quantizer* params values can range from 0 to 63, with 0 = highest + quality and 63 = worst. We make the scale 0-100, and we reverse this, so that + 0 = worst quality and 100 = highest. - Values below 0 are set to 0, and values below MAX_QUALITY are set to MAX_QUALITY. + Values below 0 are set to 0, and values below MAX_QUALITY are set to + MAX_QUALITY. */ static int quality2Quantizer(int quality) { int clampedQuality = CLAMP(quality, 0, MAX_QUALITY); @@ -85,23 +97,26 @@ static int quality2Quantizer(int quality) { } /* - As of February 2021, this algorithm reflects the latest research on how many tiles - and threads to include for a given image size. - This is subject to change as research continues. + As of February 2021, this algorithm reflects the latest research on how + many tiles and threads to include for a given image size. This is subject to + change as research continues. Returns false if there was an error, true if all was well. */ -static avifBool setEncoderTilesAndThreads(avifEncoder *encoder, avifRGBImage *rgb) { +static avifBool setEncoderTilesAndThreads(avifEncoder *encoder, + avifRGBImage *rgb) { int imageArea, tiles, tilesLog2, encoderTiles; - // _gdImageAvifCtx(), the calling function, checks this operation for overflow + // _gdImageAvifCtx(), the calling function, checks this operation for + // overflow imageArea = rgb->width * rgb->height; tiles = (int) ceil((double) imageArea / MIN_TILE_AREA); tiles = MIN(tiles, MAX_TILES); tiles = MIN(tiles, MAX_THREADS); - // The number of tiles in any dimension will always be a power of 2. We can only specify log(2)tiles. + // The number of tiles in any dimension will always be a power of 2. We can + // only specify log(2)tiles. tilesLog2 = floor(log2(tiles)); @@ -124,15 +139,16 @@ static avifBool setEncoderTilesAndThreads(avifEncoder *encoder, avifRGBImage *rg } /* - We can handle AVIF images whose color profile is sRGB, or whose color profile isn't set. + We can handle AVIF images whose color profile is sRGB, or whose color + profile isn't set. */ static avifBool isAvifSrgbImage(avifImage *avifIm) { - return - (avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 || + return (avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_BT709 || avifIm->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED) && - (avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_SRGB || - avifIm->transferCharacteristics == AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) - ; + (avifIm->transferCharacteristics == + AVIF_TRANSFER_CHARACTERISTICS_SRGB || + avifIm->transferCharacteristics == + AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED); } /* @@ -149,27 +165,28 @@ static avifBool isAvifError(avifResult result, const char *msg) { return AVIF_FALSE; } - typedef struct avifIOCtxReader { avifIO io; // this must be the first member for easy casting to avifIO* avifROData rodata; } avifIOCtxReader; /* - implements the avifIOReadFunc interface by calling the relevant functions - in the gdIOCtx. Our logic is inspired by avifIOMemoryReaderRead() and avifIOFileReaderRead(). - We don't know whether we're reading from a file or from memory. We don't have to know, - since we rely on the helper functions in the gdIOCtx. - We assume we've stashed the gdIOCtx in io->data, as we do in createAvifIOFromCtx(). + implements the avifIOReadFunc interface by calling the + relevant functions in the gdIOCtx. Our logic is inspired by + avifIOMemoryReaderRead() and avifIOFileReaderRead(). We don't know whether + we're reading from a file or from memory. We don't have to know, since we + rely on the helper functions in the gdIOCtx. We assume we've stashed the + gdIOCtx in io->data, as we do in createAvifIOFromCtx(). We ignore readFlags, just as the avifIO*ReaderRead() functions do. If there's a problem, this returns an avifResult error. If things go well, return AVIF_RESULT_OK. - Of course these AVIF codes shouldn't be returned by any top-level GD function. + Of course these AVIF codes shouldn't be returned by any top-level GD + function. */ -static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, size_t size, avifROData *out) -{ +static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, + size_t size, avifROData *out) { gdIOCtx *ctx = (gdIOCtx *) io->data; avifIOCtxReader *reader = (avifIOCtxReader *) io; @@ -183,7 +200,8 @@ static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, s if (offset > INT_MAX || size > INT_MAX) return AVIF_RESULT_IO_ERROR; - // Try to seek offset bytes forward. If we pass the end of the buffer, throw an error. + // Try to seek offset bytes forward. If we pass the end of the buffer, throw + // an error. if (!ctx->seek(ctx, (int) offset)) return AVIF_RESULT_IO_ERROR; @@ -218,10 +236,10 @@ static void destroyAvifIO(struct avifIO *io) { } /* Set up an avifIO object. - The functions in the gdIOCtx struct may point either to a file or a memory buffer. - To us, that's immaterial. - Our task is simply to assign avifIO functions to the proper functions from gdIOCtx. - The destroy function needs to destroy the avifIO object and anything else it uses. + The functions in the gdIOCtx struct may point either to a file or a memory + buffer. To us, that's immaterial. Our task is simply to assign avifIO + functions to the proper functions from gdIOCtx. The destroy function needs to + destroy the avifIO object and anything else it uses. Returns NULL if memory for the object can't be allocated. */ @@ -234,12 +252,14 @@ static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) { if (reader == NULL) return NULL; - // TODO: setting persistent=FALSE is safe, but it's less efficient. Is it necessary? + // TODO: setting persistent=FALSE is safe, but it's less efficient. Is it + // necessary? reader->io.persistent = AVIF_FALSE; reader->io.read = readFromCtx; reader->io.write = NULL; // this function is currently unused; see avif.h reader->io.destroy = destroyAvifIO; - reader->io.sizeHint = 0; // sadly, we don't get this information from the gdIOCtx. + reader->io.sizeHint = + 0; // sadly, we don't get this information from the gdIOCtx. reader->io.data = ctx; reader->rodata.data = NULL; reader->rodata.size = 0; @@ -247,7 +267,6 @@ static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) { return (avifIO *) reader; } - /*** Decoding functions ***/ /* @@ -261,9 +280,10 @@ static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) { because the file is corrupt or does not contain a AVIF image). does not close the file. - This function creates a gdIOCtx struct from the file pointer it's passed. - And then it relies on to do the real decoding work. - If the file contains an image sequence, we simply read the first one, discarding the rest. + This function creates a gdIOCtx struct from the file pointer it's + passed. And then it relies on to do the real + decoding work. If the file contains an image sequence, we simply read the + first one, discarding the rest. Variants: @@ -284,8 +304,7 @@ static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) { On error, returns 0. */ -gdImagePtr gdImageCreateFromAvif(FILE *infile) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *infile) { gdImagePtr im; gdIOCtx *ctx = gdNewFileCtx(infile); @@ -308,8 +327,7 @@ gdImagePtr gdImageCreateFromAvif(FILE *infile) size - size of Avif data in bytes. data - pointer to Avif data. */ -gdImagePtr gdImageCreateFromAvifPtr(int size, void *data) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data) { gdImagePtr im; gdIOCtx *ctx = gdNewDynamicCtxEx(size, data, 0); @@ -327,13 +345,16 @@ gdImagePtr gdImageCreateFromAvifPtr(int size, void *data) See . - Additional details: the AVIF library comes with functions to create an IO object from - a file and from a memory pointer. Of course, it doesn't have a way to create an IO object - from a gdIOCtx. So, here, we use our own helper function, . + Additional details: the AVIF library comes with functions to create an + IO object from a file and from a memory pointer. Of course, it doesn't have a + way to create an IO object from a gdIOCtx. So, here, we use our own helper + function, . - Otherwise, we create the image by calling AVIF library functions in order: + Otherwise, we create the image by calling AVIF library functions in + order: * avifDecoderCreate(), to create the decoder - * avifDecoderSetIO(), to tell libavif how to read from our data structure + * avifDecoderSetIO(), to tell libavif how to read from our data + structure * avifDecoderParse(), to parse the image * avifDecoderNextImage(), to read the first image from the decoder * avifRGBImageSetDefaults(), to create the avifRGBImage @@ -346,8 +367,7 @@ gdImagePtr gdImageCreateFromAvifPtr(int size, void *data) ctx - a gdIOCtx struct */ -gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx(gdIOCtx *ctx) { uint32_t x, y; gdImage *im = NULL; avifResult result; @@ -360,10 +380,11 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) decoder = avifDecoderCreate(); - // Check if libavif version is >= 0.9.1 - // If so, allow the PixelInformationProperty ('pixi') to be missing in AV1 image - // items. libheif v1.11.0 or older does not add the 'pixi' item property to - // AV1 image items. (This issue has been corrected in libheif v1.12.0.) + // Check if libavif version is >= 0.9.1. + // If so, allow the PixelInformationProperty ('pixi') to be missing in AV1 + // image items. libheif v1.11.0 or older does not add the 'pixi' item + // property to AV1 image items. (This issue has been corrected in libheif + // v1.12.0.) #if AVIF_VERSION >= 90100 decoder->strictFlags &= ~AVIF_STRICT_PIXI_REQUIRED; @@ -381,16 +402,18 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) if (isAvifError(result, "Could not parse image")) goto cleanup; - // Note again that, for an image sequence, we read only the first image, ignoring the rest. + // Note again that, for an image sequence, we read only the first image, + // ignoring the rest. result = avifDecoderNextImage(decoder); if (isAvifError(result, "Could not decode image")) goto cleanup; if (!isAvifSrgbImage(decoder->image)) - gd_error_ex(GD_NOTICE, "Image's color profile is not sRGB"); + gd_error_ex(LOG_NOTICE, "Image's color profile is not sRGB"); // Set up the avifRGBImage, and convert it from YUV to an 8-bit RGB image. - // (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor images are 8-bit.) + // (While AVIF image pixel depth can be 8, 10, or 12 bits, GD truecolor + // images are 8-bit.) avifRGBImageSetDefaults(&rgb, decoder->image); rgb.depth = 8; #if AVIF_VERSION >= 1000000 @@ -437,7 +460,6 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) return im; } - /*** Encoding functions ***/ /* @@ -452,11 +474,13 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) Variants: - writes the image to a file, encoding with the default quality and speed. + writes the image to a file, encoding with the default + quality and speed. stores the image in RAM. - stores the image in RAM, encoding with the default quality and speed. + stores the image in RAM, encoding with the default + quality and speed. stores the image using a struct. @@ -464,89 +488,102 @@ gdImagePtr gdImageCreateFromAvifCtx (gdIOCtx *ctx) im - The image to save. outFile - The FILE pointer to write to. - quality - Compression quality (0-100). 0 is lowest-quality, 100 is highest. - speed - The speed of compression (0-10). 0 is slowest, 10 is fastest. + quality - Compression quality (0-100). 0 is lowest-quality, 100 is + highest. speed - The speed of compression (0-10). 0 is slowest, 10 is + fastest. Notes on parameters: - quality - If quality = -1, we use a default quality as defined in QUALITY_DEFAULT. - For information on how we convert this quality to libavif's quantity param, see . + quality - If quality = -1, we use a default quality as defined in + QUALITY_DEFAULT. For information on how we convert this quality to libavif's + quantity param, see . speed - At slower speeds, encoding may be quite slow. Use judiciously. - Qualities or speeds that are lower than the minimum value get clamped to the minimum value, - and qualities or speeds that are lower than the maximum value get clamped to the maxmum value. - Note that AVIF_SPEED_DEFAULT is -1. If we ever set SPEED_DEFAULT = AVIF_SPEED_DEFAULT, - we'd want to add a conditional to ensure that value doesn't get clamped. + Qualities or speeds that are lower than the minimum value get clamped to + the minimum value, and qualities or speeds that are lower than the maximum + value get clamped to the maximum value. Note that AVIF_SPEED_DEFAULT is -1. + If we ever set SPEED_DEFAULT = AVIF_SPEED_DEFAULT, we'd want to add a + conditional to ensure that value doesn't get clamped. Returns: * for , , and , nothing. - * for and , a pointer to the image in memory. + * for and , a pointer to the image in + memory. */ /* - If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to QUANTIZER_DEFAULT. + Private subobject + Function: _gdImageAvifCtx + + We need this underscored function because gdImageAvifCtx() can't return + anything. And our functions that operate on a memory buffer need to know + whether the encoding has succeeded. + + If we're passed the QUALITY_DEFAULT of -1, set the quantizer params to + QUANTIZER_DEFAULT. + + This function returns 0 on success, or 1 on failure. */ -void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) -{ +static avifBool _gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, + int speed) { avifResult result; - avifRGBImage rgb; + avifRGBImage rgb = {0}; avifRWData avifOutput = AVIF_DATA_EMPTY; + avifBool failed = AVIF_FALSE; avifBool lossless = quality == 100; avifEncoder *encoder = NULL; + avifImage *avifIm = NULL; uint32_t val; uint8_t *p; uint32_t x, y; if (im == NULL) - return; + return 1; if (!gdImageTrueColor(im)) { - gd_error("avif error - avif doesn't support palette images"); - return; + gd_error("avif doesn't support palette images"); + return 1; } if (!gdImageSX(im) || !gdImageSY(im)) { - gd_error("avif error - image dimensions must not be zero"); - return; + gd_error("image dimensions must not be zero"); + return 1; } if (overflow2(gdImageSX(im), gdImageSY(im))) { - gd_error("avif error - image dimensions are too large"); - return; + gd_error("image dimensions are too large"); + return 1; } speed = CLAMP(speed, AVIF_SPEED_SLOWEST, AVIF_SPEED_FASTEST); - avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD ? - CHROMA_SUBAMPLING_HIGH_QUALITY : CHROMA_SUBSAMPLING_DEFAULT; + avifPixelFormat subsampling = quality >= HIGH_QUALITY_SUBSAMPLING_THRESHOLD + ? CHROMA_SUBAMPLING_HIGH_QUALITY + : CHROMA_SUBSAMPLING_DEFAULT; // Create the AVIF image. // Set the ICC to sRGB, as that's what gd supports right now. - // Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from RGB to YUV. + // Note that MATRIX_COEFFICIENTS_IDENTITY enables lossless conversion from + // RGB to YUV. - avifImage *avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling); -#if AVIF_VERSION >= 1000000 + avifIm = avifImageCreate(gdImageSX(im), gdImageSY(im), 8, subsampling); if (avifIm == NULL) { gd_error("avif error - Creating image failed\n"); goto cleanup; } -#endif + avifIm->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709; avifIm->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; avifIm->matrixCoefficients = lossless ? AVIF_MATRIX_COEFFICIENTS_IDENTITY : AVIF_MATRIX_COEFFICIENTS_BT709; avifRGBImageSetDefaults(&rgb, avifIm); // this allocates memory, and sets rgb.rowBytes and rgb.pixels. -#if AVIF_VERSION >= 1000000 result = avifRGBImageAllocatePixels(&rgb); if (isAvifError(result, "Allocating RGB pixels failed")) goto cleanup; -#else - avifRGBImageAllocatePixels(&rgb); -#endif // Parse RGB data from the GD image, and copy it into the AVIF RGB image. // Convert 7-bit GD alpha channel values to 8-bit AVIF values. @@ -566,20 +603,21 @@ void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) // Convert the RGB image to YUV. result = avifImageRGBToYUV(avifIm, &rgb); - if (isAvifError(result, "Could not convert image to YUV")) + failed = isAvifError(result, "Could not convert image to YUV"); + if (failed) goto cleanup; // Encode the image in AVIF format. encoder = avifEncoderCreate(); -#if AVIF_VERSION >= 1000000 if (encoder == NULL) { gd_error("avif error - Creating encoder failed\n"); goto cleanup; } -#endif - int quantizerQuality = quality == QUALITY_DEFAULT ? - QUANTIZER_DEFAULT : quality2Quantizer(quality); + + int quantizerQuality = quality == QUALITY_DEFAULT + ? QUANTIZER_DEFAULT + : quality2Quantizer(quality); encoder->minQuantizer = quantizerQuality; encoder->maxQuantizer = quantizerQuality; @@ -587,16 +625,20 @@ void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) encoder->maxQuantizerAlpha = quantizerQuality; encoder->speed = speed; - if (!setEncoderTilesAndThreads(encoder, &rgb)) + failed = !setEncoderTilesAndThreads(encoder, &rgb); + if (failed) goto cleanup; //TODO: is there a reason to use timeSscales != 1? - result = avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE); - if (isAvifError(result, "Could not encode image")) + result = + avifEncoderAddImage(encoder, avifIm, 1, AVIF_ADD_IMAGE_FLAG_SINGLE); + failed = isAvifError(result, "Could not encode image"); + if (failed) goto cleanup; result = avifEncoderFinish(encoder, &avifOutput); - if (isAvifError(result, "Could not finish encoding")) + failed = isAvifError(result, "Could not finish encoding"); + if (failed) goto cleanup; // Write the AVIF image bytes to the GD ctx. @@ -615,25 +657,27 @@ void gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) if (avifIm) avifImageDestroy(avifIm); + + return failed; } -void gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed) -{ +BGD_DECLARE(void) +gdImageAvifEx(gdImagePtr im, FILE *outFile, int quality, int speed) { gdIOCtx *out = gdNewFileCtx(outFile); - if (out != NULL) { - gdImageAvifCtx(im, out, quality, speed); - out->gd_free(out); - } + if (out == NULL) + return; + + gdImageAvifCtx(im, out, quality, speed); + out->gd_free(out); } -void gdImageAvif(gdImagePtr im, FILE *outFile) -{ +BGD_DECLARE(void) gdImageAvif(gdImagePtr im, FILE *outFile) { gdImageAvifEx(im, outFile, QUALITY_DEFAULT, SPEED_DEFAULT); } -void * gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed) -{ +BGD_DECLARE(void *) +gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed) { void *rv; gdIOCtx *out = gdNewDynamicCtx(NEW_DYNAMIC_CTX_SIZE, NULL); @@ -641,16 +685,85 @@ void * gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed) return NULL; } - gdImageAvifCtx(im, out, quality, speed); + if (_gdImageAvifCtx(im, out, quality, speed)) + rv = NULL; + else rv = gdDPExtractData(out, size); out->gd_free(out); return rv; } -void * gdImageAvifPtr(gdImagePtr im, int *size) -{ +BGD_DECLARE(void *) gdImageAvifPtr(gdImagePtr im, int *size) { return gdImageAvifPtrEx(im, size, QUALITY_DEFAULT, AVIF_SPEED_DEFAULT); } +BGD_DECLARE(void) +gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) { + _gdImageAvifCtx(im, outfile, quality, speed); +} + +#else /* !HAVE_LIBAVIF */ + +static void *_noAvifError(void) { + gd_error("AVIF image support has been disabled\n"); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvif(FILE *ctx) { + ARG_NOT_USED(ctx); + return _noAvifError(); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + return _noAvifError(); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromAvifCtx(gdIOCtx *ctx) { + ARG_NOT_USED(ctx); + return _noAvifError(); +} + +BGD_DECLARE(void) +gdImageAvifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, int speed) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(quality); + ARG_NOT_USED(speed); + _noAvifError(); +} + +BGD_DECLARE(void) +gdImageAvifEx(gdImagePtr im, FILE *outfile, int quality, int speed) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(quality); + ARG_NOT_USED(speed); + _noAvifError(); +} + +BGD_DECLARE(void) gdImageAvif(gdImagePtr im, FILE *outfile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + _noAvifError(); +} + +BGD_DECLARE(void *) gdImageAvifPtr(gdImagePtr im, int *size) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + + return _noAvifError(); +} + +BGD_DECLARE(void *) +gdImageAvifPtrEx(gdImagePtr im, int *size, int quality, int speed) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(quality); + ARG_NOT_USED(speed); + return _noAvifError(); +} + #endif /* HAVE_LIBAVIF */ diff --git a/ext/gd/libgd/gd_bmp.c b/ext/gd/libgd/gd_bmp.c index c7c9cc9d8c1e..df2f49987017 100644 --- a/ext/gd/libgd/gd_bmp.c +++ b/ext/gd/libgd/gd_bmp.c @@ -12,21 +12,41 @@ ---------------------------------------------------------------------------- */ -/* $Id$ */ + +/** + * File: BMP IO + * + * Read and write BMP images. + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include -#include -#include -#include +#include "bmp.h" #include "gd.h" +#include "gd_errors.h" #include "gdhelpers.h" -#include "bmp.h" +#include +#include +#include +#include +#include +#if defined(__has_builtin) && __has_builtin(__builtin_assume) +#define GD_ASSUME(expr) __builtin_assume(expr) +#elif defined(__GNUC__) +#define GD_ASSUME(expr) \ + do { \ + if (!(expr)) \ + __builtin_unreachable(); \ + } while (0) +#else +#define GD_ASSUME(expr) ((void)(expr)) +#endif static int compress_row(unsigned char *uncompressed_row, int length); -static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data); +static int build_rle_packet(unsigned char *row, int packet_type, int length, + unsigned char *data); static int bmp_read_header(gdIOCtxPtr infile, bmp_hdr_t *hdr); static int bmp_read_info(gdIOCtxPtr infile, bmp_info_t *info); @@ -34,26 +54,88 @@ static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info); static int bmp_read_os2_v1_info(gdIOCtxPtr infile, bmp_info_t *info); static int bmp_read_os2_v2_info(gdIOCtxPtr infile, bmp_info_t *info); -static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header); -static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header); -static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header); -static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header); +static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header); +static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header); +static int bmp_read_2bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header); +static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header); +static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header); static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info); -static int _gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression); +typedef struct { + int bpp; + int compression; + int header_ver; + int row_stride; + int bitmap_size; + int info_size; + int palette_size; + int mask_size; + unsigned int red_mask; + unsigned int green_mask; + unsigned int blue_mask; + unsigned int alpha_mask; +} bmp_write_ctx_t; + +static int _gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int bpp, + int compression, int flags); +static int bmp_resolve_write_ctx(gdImagePtr im, int bpp_hint, int compression, + int flags, bmp_write_ctx_t *ctx); +static int bmp_auto_bpp(gdImagePtr im); +static int bmp_has_alpha(gdImagePtr im); +static int bmp_prepare_write_image(gdImagePtr im, int bpp, int flags, + gdImagePtr *write_im); +static void bmp_write_file_header(gdIOCtxPtr out, bmp_write_ctx_t *ctx); +static void bmp_write_info_header(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static void bmp_write_palette(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_1bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_4bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_8bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_16bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_24bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_pixels_32bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx); +static int bmp_write_rle4_row(gdIOCtxPtr out, gdImagePtr im, int row); +static int bmp_write_padding(gdIOCtxPtr out, int count); +static int bmp_write_tmpfile_to_ctx(gdIOCtxPtr out, gdIOCtxPtr out_original); + +static int bmp_validate_info(bmp_info_t *info, bmp_hdr_t *hdr); +static int bmp_read_bitfield_masks(gdIOCtxPtr infile, bmp_info_t *info, + int read_alpha); +static int bmp_skip_bytes(gdIOCtxPtr infile, int count); +static int bmp_check_palette_index(gdImagePtr im, int index); +static int bmp_row_padding(int width, int depth, int *padding); +static int bmp_image_size(int width, int height, int depth, int *size); +static int bmp_get_mask_shift(unsigned int mask); +static int bmp_get_mask_bits(unsigned int mask); +static int bmp_extract_mask(unsigned int pixel, unsigned int mask); +static unsigned int bmp_pack_mask(int channel_8bit, unsigned int mask); +static int bmp_alpha_to_gd(int alpha); +static int bmp_gd_to_alpha(int gd_alpha); #define BMP_DEBUG(s) -static int gdBMPPutWord(gdIOCtx *out, int w) -{ +static int gdBMPPutWord(gdIOCtx *out, int w) { /* Byte order is little-endian */ gdPutC(w & 0xFF, out); gdPutC((w >> 8) & 0xFF, out); return 0; } -static int gdBMPPutInt(gdIOCtx *out, int w) -{ +static int gdBMPPutInt(gdIOCtx *out, int w) { /* Byte order is little-endian */ gdPutC(w & 0xFF, out); gdPutC((w >> 8) & 0xFF, out); @@ -64,13 +146,57 @@ static int gdBMPPutInt(gdIOCtx *out, int w) /* Function: gdImageBmpPtr + + Outputs the given image as BMP data, but using a instead + of a file. See . + + This is the legacy BMP memory API. A zero value writes + uncompressed BMP data. A nonzero value requests legacy + RLE output when the automatically selected BMP bit depth supports RLE. + For explicit bit depth, compression, and conversion control, use + . + + Parameters: + im - the image to save. + size - Output: size in bytes of the result. + compression - whether to apply RLE or not. + + Returns: + + A pointer to memory containing the image data or NULL on error. */ -void * gdImageBmpPtr(gdImagePtr im, int *size, int compression) -{ +BGD_DECLARE(void *) gdImageBmpPtr(gdImagePtr im, int *size, int compression) { + return gdImageBmpPtrEx(im, size, 0, compression ? -1 : GD_BMP_COMPRESS_NONE, + GD_BMP_FLAG_NONE); +} + +/* + Function: gdImageBmpPtrEx + + outputs the given image as BMP data in memory. + See for the meaning of , , and + . + + Parameters: + + im - The image to save. + size - Output: size in bytes of the returned data. + bpp - Requested output bit depth, or 0 for automatic selection. + compression - Requested BMP compression mode. + flags - BMP writer option flags. + + Returns: + + A pointer to memory containing the image data, or NULL on error. + The returned memory must be freed with . +*/ +BGD_DECLARE(void *) +gdImageBmpPtrEx(gdImagePtr im, int *size, int bpp, int compression, int flags) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); - if (out == NULL) return NULL; - if (!_gdImageBmpCtx(im, out, compression)) + if (out == NULL) + return NULL; + if (!_gdImageBmpCtx(im, out, bpp, compression, flags)) rv = gdDPExtractData(out, size); else rv = NULL; @@ -80,232 +206,800 @@ void * gdImageBmpPtr(gdImagePtr im, int *size, int compression) /* Function: gdImageBmp + + outputs the specified image to the specified file in + BMP format. The file must be open for writing. Under MSDOS and all + versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + In addition, allows to specify whether RLE compression + should be applied. + + This is the legacy BMP file API. A zero value writes + uncompressed BMP data. A nonzero value requests legacy + RLE output when the automatically selected BMP bit depth supports RLE. + For explicit bit depth, compression, and conversion control, use + . + + Variants: + + write via a instead of a file handle. + + store the image file to memory. + + Parameters: + + im - the image to save. + outFile - the output FILE* object. + compression - whether to apply RLE or not. + + Returns: + nothing */ -void gdImageBmp(gdImagePtr im, FILE *outFile, int compression) -{ +BGD_DECLARE(void) gdImageBmp(gdImagePtr im, FILE *outFile, int compression) { + gdImageBmpEx(im, outFile, 0, compression ? -1 : GD_BMP_COMPRESS_NONE, + GD_BMP_FLAG_NONE); +} + +/* + Function: gdImageBmpEx + + outputs the specified image to the specified file in + BMP format. The file must be open for writing. Under MSDOS and all + versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + This extended BMP writer supports 1, 4, 8, 16, 24, and 32 bits per + pixel. It writes Windows BMP headers only: BITMAPINFOHEADER for most + outputs, BITMAPV4HEADER for 32 bpp alpha output or when + is set. It does not write OS/2 BMP + headers, 2 bpp BMPs, top-down BMPs, custom bit masks, V5 color + profiles, embedded JPEG/PNG BMP data, or non-standard high-depth + bitfield formats. + + Bit depth: + + Pass 0 for automatic bit depth selection. Automatic selection writes + truecolor images with non-opaque pixels as 32 bpp, opaque truecolor + images as 24 bpp, palette images with 2 or fewer colors as 1 bpp, + palette images with 16 or fewer colors as 4 bpp, and other palette + images as 8 bpp. + + Explicit values must be one of 1, 4, 8, 16, 24, or 32. + Indexed output at 1, 4, or 8 bpp requires a palette image with no + more colors than the selected bit depth can store. If the source is + truecolor, explicit indexed output is a lossy conversion and fails + unless is set. With that flag, the writer + clones the image, converts the clone with , + writes the clone, and leaves the caller's image unchanged. + + Compression: + + writes uncompressed BMP pixels. + is valid only for 4 bpp output and writes + BI_RLE4. is valid only for 8 bpp output and + writes BI_RLE8. Any invalid bit depth/compression combination fails + before producing a valid BMP. + + 16 bpp and 32 bpp output use BI_BITFIELDS automatically, regardless + of the requested compression. 16 bpp defaults to RGB565 masks; set + to write RGB555 masks instead. 32 bpp writes + red, green, blue, and alpha masks and stores alpha as BMP opacity + (255 is opaque), converted from gd's alpha representation. + + Palette alpha is not preserved in 1, 4, or 8 bpp BMP output. The + writer stores indexed palettes as B, G, R, reserved byte entries, + and writes the reserved byte as zero. Truecolor images that need + alpha preservation should be written as 32 bpp, either explicitly or + by using automatic bit depth selection on an image with non-opaque + pixels. If a truecolor image is quantized to indexed BMP with + , alpha is not preserved in the indexed BMP. + + Flags: + + selects default behavior. + allows explicit lossy truecolor to indexed + conversion for 1, 4, or 8 bpp output. + selects RGB555 masks for 16 bpp output. + writes a BITMAPV4HEADER even when it is + not otherwise required. + + Variants: + + stores the image in RAM. + + writes using a struct. + + , , and are legacy + wrappers using automatic bit depth selection and no flags. + + Parameters: + + im - The image to save. + outFile - The FILE pointer to write to. + bpp - Requested bit depth, or 0 for automatic selection. + compression - One of , + , or . + flags - A bitwise OR of , + , , + and . + + Returns: + + For and , nothing. + For , a pointer to the image in memory, or NULL + on error. +*/ +BGD_DECLARE(void) +gdImageBmpEx(gdImagePtr im, FILE *outFile, int bpp, int compression, + int flags) { gdIOCtx *out = gdNewFileCtx(outFile); - if (out == NULL) return; - gdImageBmpCtx(im, out, compression); + if (out == NULL) + return; + gdImageBmpCtxEx(im, out, bpp, compression, flags); out->gd_free(out); } /* Function: gdImageBmpCtx + + Outputs the given image as BMP data, but using a instead + of a file. See . + + This is the legacy BMP context API. A zero value writes + uncompressed BMP data. A nonzero value requests legacy + RLE output when the automatically selected BMP bit depth supports RLE. + For explicit bit depth, compression, and conversion control, use + . + + Parameters: + im - the image to save. + out - the to write to. + compression - whether to apply RLE or not. +*/ +BGD_DECLARE(void) +gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression) { + gdImageBmpCtxEx(im, out, 0, compression ? -1 : GD_BMP_COMPRESS_NONE, + GD_BMP_FLAG_NONE); +} + +/* + Function: gdImageBmpCtxEx + + outputs the given image as BMP data using a + structure. See for the meaning of , + , and , including automatic bit depth selection, + RLE restrictions, quantization behavior, bitfield output, and + unsupported BMP variants. + + Parameters: + + im - The image to save. + out - The to write to. + bpp - Requested output bit depth, or 0 for automatic selection. + compression - Requested BMP compression mode. + flags - BMP writer option flags. */ -void gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression) -{ - _gdImageBmpCtx(im, out, compression); +BGD_DECLARE(void) +gdImageBmpCtxEx(gdImagePtr im, gdIOCtxPtr out, int bpp, int compression, + int flags) { + _gdImageBmpCtx(im, out, bpp, compression, flags); } -static int _gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int compression){ - int bitmap_size = 0, info_size, total_size, padding; - int i, row, xpos, pixel; +static int _gdImageBmpCtx(gdImagePtr im, gdIOCtxPtr out, int bpp, + int compression, int flags) { + bmp_write_ctx_t ctx; int error = 0; - unsigned char *uncompressed_row = NULL, *uncompressed_row_start = NULL; FILE *tmpfile_for_compression = NULL; gdIOCtxPtr out_original = NULL; int ret = 1; + gdImagePtr write_im = im; - /* No compression if its true colour or we don't support seek */ - if (im->trueColor) { - compression = 0; + if (im == NULL || out == NULL) { + return 1; + } + + if (gdImageSX(im) <= 0 || gdImageSY(im) <= 0) { + gd_error("image dimensions must be greater than 0"); + return 1; + } + + if (overflow2(gdImageSX(im), gdImageSY(im))) { + gd_error("image dimensions are too large"); + return 1; } - if (compression == 1 && !out->seek) { + if (bmp_prepare_write_image(im, bpp, flags, &write_im)) { + return 1; + } + + if (bmp_resolve_write_ctx(write_im, bpp, compression, flags, &ctx)) { + goto cleanup; + } + + if ((ctx.compression == BMP_BI_RLE8 || ctx.compression == BMP_BI_RLE4) && + !out->seek) { /* Try to create a temp file where we can seek */ if ((tmpfile_for_compression = tmpfile()) == NULL) { - compression = 0; + ctx.compression = BMP_BI_RGB; + if (ctx.bpp == 4 || ctx.bpp == 8) { + ctx.bitmap_size = ctx.row_stride * write_im->sy; + } } else { out_original = out; - if ((out = (gdIOCtxPtr)gdNewFileCtx(tmpfile_for_compression)) == NULL) { + if ((out = (gdIOCtxPtr)gdNewFileCtx(tmpfile_for_compression)) == + NULL) { out = out_original; out_original = NULL; - compression = 0; + ctx.compression = BMP_BI_RGB; + if (ctx.bpp == 4 || ctx.bpp == 8) { + ctx.bitmap_size = ctx.row_stride * write_im->sy; + } + } + } + } + + bmp_write_file_header(out, &ctx); + bmp_write_info_header(out, write_im, &ctx); + if (ctx.palette_size) { + bmp_write_palette(out, write_im, &ctx); + } + + error = bmp_write_pixels(out, write_im, &ctx); + + if (!error && + (ctx.compression == BMP_BI_RLE8 || ctx.compression == BMP_BI_RLE4)) { + gdPutC(BMP_RLE_COMMAND, out); + gdPutC(BMP_RLE_ENDOFBITMAP, out); + ctx.bitmap_size += 2; + + gdSeek(out, 2); + gdBMPPutInt(out, 14 + ctx.info_size + ctx.bitmap_size); + + gdSeek(out, 34); + gdBMPPutInt(out, ctx.bitmap_size); + } + + /* If we needed a tmpfile for compression copy it over to out_original */ + if (!error && tmpfile_for_compression && + bmp_write_tmpfile_to_ctx(out, out_original)) { + error = 1; + } + if (tmpfile_for_compression && out_original) { + out->gd_free(out); + out = out_original; + out_original = NULL; + } + + ret = error; +cleanup: + if (tmpfile_for_compression) { +#ifdef _WIN32 + _rmtmp(); +#else + fclose(tmpfile_for_compression); +#endif + tmpfile_for_compression = NULL; + } + + if (out_original) { + out_original->gd_free(out_original); + } + if (write_im != im) { + gdImageDestroy(write_im); + } + return ret; +} + +static int bmp_prepare_write_image(gdImagePtr im, int bpp, int flags, + gdImagePtr *write_im) { + gdImagePtr clone; + + *write_im = im; + if ((bpp == 1 || bpp == 4 || bpp == 8) && im->trueColor) { + if (!(flags & GD_BMP_FLAG_QUANTIZE)) { + return 1; + } + clone = gdImageClone(im); + if (clone == NULL) { + return 1; + } + if (!gdImageTrueColorToPalette(clone, 0, 1 << bpp)) { + gdImageDestroy(clone); + return 1; + } + *write_im = clone; + } + return 0; +} + +static int bmp_auto_bpp(gdImagePtr im) { + if (im->trueColor) { + return bmp_has_alpha(im) ? 32 : 24; + } + if (im->colorsTotal <= 2) { + return 1; + } + if (im->colorsTotal <= 16) { + return 4; + } + return 8; +} + +static int bmp_has_alpha(gdImagePtr im) { + int x, y; + + for (y = 0; y < im->sy; y++) { + for (x = 0; x < im->sx; x++) { + if (gdTrueColorGetAlpha(gdImageGetPixel(im, x, y)) != + gdAlphaOpaque) { + return 1; } } } + return 0; +} + +static int bmp_resolve_write_ctx(gdImagePtr im, int bpp_hint, int compression, + int flags, bmp_write_ctx_t *ctx) { + memset(ctx, 0, sizeof(*ctx)); + + ctx->bpp = (bpp_hint > 0) ? bpp_hint : bmp_auto_bpp(im); + switch (ctx->bpp) { + case 1: + case 4: + case 8: + case 16: + case 24: + case 32: + break; + default: + return 1; + } - bitmap_size = ((im->sx * (im->trueColor ? 24 : 8)) / 8) * im->sy; + if ((ctx->bpp == 1 || ctx->bpp == 4 || ctx->bpp == 8) && im->trueColor) { + return 1; + } + if ((ctx->bpp == 1 || ctx->bpp == 4 || ctx->bpp == 8) && + im->colorsTotal > (1 << ctx->bpp)) { + return 1; + } - /* 40 byte Windows v3 header */ - info_size = BMP_WINDOWS_V3; + if (compression == -1) { + if (ctx->bpp == 8) { + ctx->compression = BMP_BI_RLE8; + } else if (ctx->bpp == 4) { + ctx->compression = BMP_BI_RLE4; + } else { + ctx->compression = BMP_BI_RGB; + } + } else { + switch (compression) { + case GD_BMP_COMPRESS_NONE: + ctx->compression = BMP_BI_RGB; + break; + case GD_BMP_COMPRESS_RLE8: + if (ctx->bpp != 8) { + return 1; + } + ctx->compression = BMP_BI_RLE8; + break; + case GD_BMP_COMPRESS_RLE4: + if (ctx->bpp != 4) { + return 1; + } + ctx->compression = BMP_BI_RLE4; + break; + default: + return 1; + } + } - /* data for the palette */ - if (!im->trueColor) { - info_size += im->colorsTotal * 4; - if (compression) { - bitmap_size = 0; + if (ctx->bpp == 16) { + if (flags & GD_BMP_FLAG_RGB555) { + ctx->red_mask = 0x7C00; + ctx->green_mask = 0x03E0; + ctx->blue_mask = 0x001F; + } else { + ctx->red_mask = 0xF800; + ctx->green_mask = 0x07E0; + ctx->blue_mask = 0x001F; } + ctx->compression = BMP_BI_BITFIELDS; + } else if (ctx->bpp == 32) { + ctx->red_mask = 0x00FF0000; + ctx->green_mask = 0x0000FF00; + ctx->blue_mask = 0x000000FF; + ctx->alpha_mask = 0xFF000000; + ctx->compression = BMP_BI_BITFIELDS; } - /* bitmap header + info header + data */ - total_size = 14 + info_size + bitmap_size; + ctx->header_ver = ((ctx->bpp == 32 && ctx->alpha_mask) || + (flags & GD_BMP_FLAG_FORCE_V4HDR)) + ? BMP_WINDOWS_V4 + : BMP_WINDOWS_V3; + ctx->palette_size = (ctx->bpp <= 8) ? (1 << ctx->bpp) * 4 : 0; + ctx->mask_size = (ctx->header_ver == BMP_WINDOWS_V3 && + ctx->compression == BMP_BI_BITFIELDS) + ? 12 + : 0; + ctx->info_size = ctx->header_ver + ctx->mask_size + ctx->palette_size; + ctx->row_stride = (((ctx->bpp * im->sx) + 31) / 32) * 4; + if (ctx->compression != BMP_BI_RLE8 && ctx->compression != BMP_BI_RLE4) { + ctx->bitmap_size = ctx->row_stride * im->sy; + } + return 0; +} - /* write bmp header info */ +static void bmp_write_file_header(gdIOCtxPtr out, bmp_write_ctx_t *ctx) { gdPutBuf("BM", 2, out); - gdBMPPutInt(out, total_size); + gdBMPPutInt(out, 14 + ctx->info_size + ctx->bitmap_size); gdBMPPutWord(out, 0); gdBMPPutWord(out, 0); - gdBMPPutInt(out, 14 + info_size); - - /* write Windows v3 headers */ - gdBMPPutInt(out, BMP_WINDOWS_V3); /* header size */ - gdBMPPutInt(out, im->sx); /* width */ - gdBMPPutInt(out, im->sy); /* height */ - gdBMPPutWord(out, 1); /* colour planes */ - gdBMPPutWord(out, (im->trueColor ? 24 : 8)); /* bit count */ - gdBMPPutInt(out, (compression ? BMP_BI_RLE8 : BMP_BI_RGB)); /* compression */ - gdBMPPutInt(out, bitmap_size); /* image size */ - gdBMPPutInt(out, 0); /* H resolution */ - gdBMPPutInt(out, 0); /* V ressolution */ - gdBMPPutInt(out, im->colorsTotal); /* colours used */ - gdBMPPutInt(out, 0); /* important colours */ - - /* The line must be divisible by 4, else it's padded with NULLs */ - padding = ((int)(im->trueColor ? 3 : 1) * im->sx) % 4; - if (padding) { - padding = 4 - padding; - } - - /* 8-bit colours */ - if (!im->trueColor) { - for(i = 0; i< im->colorsTotal; ++i) { - Putchar(gdImageBlue(im, i), out); - Putchar(gdImageGreen(im, i), out); - Putchar(gdImageRed(im, i), out); - Putchar(0, out); - } - - if (compression) { - /* Can potentially change this to X + ((X / 128) * 3) */ - uncompressed_row = uncompressed_row_start = (unsigned char *) gdCalloc(gdImageSX(im) * 2, sizeof(char)); - if (!uncompressed_row) { - /* malloc failed */ - goto cleanup; + gdBMPPutInt(out, 14 + ctx->info_size); +} + +static void bmp_write_info_header(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int i; + + gdBMPPutInt(out, ctx->header_ver); + gdBMPPutInt(out, im->sx); + gdBMPPutInt(out, im->sy); + gdBMPPutWord(out, 1); + gdBMPPutWord(out, ctx->bpp); + gdBMPPutInt(out, ctx->compression); + gdBMPPutInt(out, ctx->bitmap_size); + gdBMPPutInt(out, 0); + gdBMPPutInt(out, 0); + gdBMPPutInt(out, ctx->palette_size ? (ctx->palette_size / 4) : 0); + gdBMPPutInt(out, 0); + + if (ctx->header_ver == BMP_WINDOWS_V4) { + gdBMPPutInt(out, ctx->red_mask); + gdBMPPutInt(out, ctx->green_mask); + gdBMPPutInt(out, ctx->blue_mask); + gdBMPPutInt(out, ctx->alpha_mask); + gdBMPPutInt(out, 0x73524742); + for (i = 0; i < 9; i++) { + gdBMPPutInt(out, 0); + } + gdBMPPutInt(out, 0); + gdBMPPutInt(out, 0); + gdBMPPutInt(out, 0); + } else if (ctx->mask_size) { + gdBMPPutInt(out, ctx->red_mask); + gdBMPPutInt(out, ctx->green_mask); + gdBMPPutInt(out, ctx->blue_mask); + } + } + +static void bmp_write_palette(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int i, count; + + count = 1 << ctx->bpp; + for (i = 0; i < count; i++) { + if (i < im->colorsTotal) { + gdPutC(gdImageBlue(im, i), out); + gdPutC(gdImageGreen(im, i), out); + gdPutC(gdImageRed(im, i), out); + } else { + gdPutC(0, out); + gdPutC(0, out); + gdPutC(0, out); + } + gdPutC(0, out); + } + } + +static int bmp_write_pixels(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + switch (ctx->bpp) { + case 1: + return bmp_write_pixels_1bit(out, im, ctx); + case 4: + return bmp_write_pixels_4bit(out, im, ctx); + case 8: + return bmp_write_pixels_8bit(out, im, ctx); + case 16: + return bmp_write_pixels_16bit(out, im, ctx); + case 24: + return bmp_write_pixels_24bit(out, im, ctx); + case 32: + return bmp_write_pixels_32bit(out, im, ctx); + default: + return 1; } } - for (row = (im->sy - 1); row >= 0; row--) { - if (compression) { - memset (uncompressed_row, 0, gdImageSX(im)); +static int bmp_write_padding(gdIOCtxPtr out, int count) { + while (count-- > 0) { + gdPutC(0, out); + } + return 0; } - for (xpos = 0; xpos < im->sx; xpos++) { - if (compression) { - *uncompressed_row++ = (unsigned char)gdImageGetPixel(im, xpos, row); +static int bmp_write_pixels_1bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos, bit, byte, bytes_written, index; + + for (row = im->sy - 1; row >= 0; row--) { + bytes_written = 0; + for (xpos = 0; xpos < im->sx; xpos += 8) { + byte = 0; + for (bit = 0; bit < 8 && xpos + bit < im->sx; bit++) { + index = gdImageGetPixel(im, xpos + bit, row); + if (index < 0 || index > 1) { + return 1; + } + byte |= (index & 0x01) << (7 - bit); + } + gdPutC(byte, out); + bytes_written++; + } + bmp_write_padding(out, ctx->row_stride - bytes_written); + } + return 0; +} + +static int bmp_write_pixels_4bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos, high, low, bytes_written; + + if (ctx->compression == BMP_BI_RLE4) { + for (row = im->sy - 1; row >= 0; row--) { + int compressed_size = bmp_write_rle4_row(out, im, row); + if (compressed_size < 0) { + return 1; + } + ctx->bitmap_size += compressed_size; + ctx->bitmap_size += 2; + gdPutC(BMP_RLE_COMMAND, out); + gdPutC(BMP_RLE_ENDOFLINE, out); + } + return 0; + } + + for (row = im->sy - 1; row >= 0; row--) { + bytes_written = 0; + for (xpos = 0; xpos < im->sx; xpos += 2) { + high = gdImageGetPixel(im, xpos, row); + if (high < 0 || high > 15) { + return 1; + } + if (xpos + 1 < im->sx) { + low = gdImageGetPixel(im, xpos + 1, row); + if (low < 0 || low > 15) { + return 1; + } } else { - Putchar(gdImageGetPixel(im, xpos, row), out); + low = 0; } + gdPutC(((high & 0x0f) << 4) | (low & 0x0f), out); + bytes_written++; + } + bmp_write_padding(out, ctx->row_stride - bytes_written); + } + return 0; } - if (!compression) { - /* Add padding to make sure we have n mod 4 == 0 bytes per row */ - for (xpos = padding; xpos > 0; --xpos) { - Putchar('\0', out); +static int bmp_write_rle4_row(gdIOCtxPtr out, gdImagePtr im, int row) { + int xpos, chunk, i, value, next; + int bytes; + int total = 0; + + for (xpos = 0; xpos < im->sx;) { + chunk = im->sx - xpos; + if (chunk > 255) { + chunk = 255; + } + if (chunk < 3) { + value = gdImageGetPixel(im, xpos, row); + if (value < 0 || value > 15) { + return 1; + } + next = value; + if (chunk == 2) { + next = gdImageGetPixel(im, xpos + 1, row); + if (next < 0 || next > 15) { + return 1; + } + } + gdPutC(chunk, out); + gdPutC(((value & 0x0f) << 4) | (next & 0x0f), out); + total += 2; + xpos += chunk; + continue; + } + gdPutC(BMP_RLE_COMMAND, out); + gdPutC(chunk, out); + total += 2; + bytes = (chunk + 1) / 2; + for (i = 0; i < chunk; i += 2) { + value = gdImageGetPixel(im, xpos + i, row); + if (value < 0 || value > 15) { + return 1; + } + if (i + 1 < chunk) { + next = gdImageGetPixel(im, xpos + i + 1, row); + if (next < 0 || next > 15) { + return 1; } } else { - int compressed_size = 0; - uncompressed_row = uncompressed_row_start; - if ((compressed_size = compress_row(uncompressed_row, gdImageSX(im))) < 0) { - error = 1; - break; + next = 0; } - bitmap_size += compressed_size; + gdPutC(((value & 0x0f) << 4) | (next & 0x0f), out); + total++; + } + if (bytes & 1) { + gdPutC(0, out); + total++; + } + xpos += chunk; + } + return total; +} +static int bmp_write_pixels_8bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos; + unsigned char *uncompressed_row = NULL; - gdPutBuf(uncompressed_row, compressed_size, out); - Putchar(BMP_RLE_COMMAND, out); - Putchar(BMP_RLE_ENDOFLINE, out); - bitmap_size += 2; + if (ctx->compression == BMP_BI_RLE8) { + uncompressed_row = + (unsigned char *)gdCalloc(gdImageSX(im) * 2, sizeof(char)); + if (!uncompressed_row) { + return 1; } + for (row = im->sy - 1; row >= 0; row--) { + int compressed_size; + for (xpos = 0; xpos < im->sx; xpos++) { + uncompressed_row[xpos] = + (unsigned char)gdImageGetPixel(im, xpos, row); } - - if (compression && uncompressed_row) { + compressed_size = compress_row(uncompressed_row, gdImageSX(im)); + if (compressed_size < 0) { gdFree(uncompressed_row); - if (error != 0) { - goto cleanup; + return 1; + } + ctx->bitmap_size += compressed_size; + if (gdPutBuf(uncompressed_row, compressed_size, out) != + compressed_size) { + gd_error("gd-bmp write error\n"); + gdFree(uncompressed_row); + return 1; } - /* Update filesize based on new values and set compression flag */ - Putchar(BMP_RLE_COMMAND, out); - Putchar(BMP_RLE_ENDOFBITMAP, out); - bitmap_size += 2; + gdPutC(BMP_RLE_COMMAND, out); + gdPutC(BMP_RLE_ENDOFLINE, out); + ctx->bitmap_size += 2; + } + gdFree(uncompressed_row); + return 0; + } - /* Write new total bitmap size */ - gdSeek(out, 2); - gdBMPPutInt(out, total_size + bitmap_size); + for (row = im->sy - 1; row >= 0; row--) { + for (xpos = 0; xpos < im->sx; xpos++) { + gdPutC(gdImageGetPixel(im, xpos, row), out); + } + bmp_write_padding(out, ctx->row_stride - im->sx); + } + return 0; +} - /* Total length of image data */ - gdSeek(out, 34); - gdBMPPutInt(out, bitmap_size); +static int bmp_write_pixels_16bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos, pixel, red, green, blue; + unsigned int packed; + + for (row = im->sy - 1; row >= 0; row--) { + for (xpos = 0; xpos < im->sx; xpos++) { + pixel = gdImageGetPixel(im, xpos, row); + if (im->trueColor) { + red = gdTrueColorGetRed(pixel); + green = gdTrueColorGetGreen(pixel); + blue = gdTrueColorGetBlue(pixel); + } else { + red = gdImageRed(im, pixel); + green = gdImageGreen(im, pixel); + blue = gdImageBlue(im, pixel); + } + packed = bmp_pack_mask(red, ctx->red_mask) | + bmp_pack_mask(green, ctx->green_mask) | + bmp_pack_mask(blue, ctx->blue_mask); + gdBMPPutWord(out, (int)packed); + } + bmp_write_padding(out, ctx->row_stride - im->sx * 2); + } + return 0; } - } else { - for (row = (im->sy - 1); row >= 0; row--) { +static int bmp_write_pixels_24bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos, pixel; + + for (row = im->sy - 1; row >= 0; row--) { for (xpos = 0; xpos < im->sx; xpos++) { pixel = gdImageGetPixel(im, xpos, row); - - Putchar(gdTrueColorGetBlue(pixel), out); - Putchar(gdTrueColorGetGreen(pixel), out); - Putchar(gdTrueColorGetRed(pixel), out); + if (im->trueColor) { + gdPutC(gdTrueColorGetBlue(pixel), out); + gdPutC(gdTrueColorGetGreen(pixel), out); + gdPutC(gdTrueColorGetRed(pixel), out); + } else { + gdPutC(gdImageBlue(im, pixel), out); + gdPutC(gdImageGreen(im, pixel), out); + gdPutC(gdImageRed(im, pixel), out); + } + } + bmp_write_padding(out, ctx->row_stride - im->sx * 3); + } + return 0; } - /* Add padding to make sure we have n mod 4 == 0 bytes per row */ - for (xpos = padding; xpos > 0; --xpos) { - Putchar('\0', out); +static int bmp_write_pixels_32bit(gdIOCtxPtr out, gdImagePtr im, + bmp_write_ctx_t *ctx) { + int row, xpos, pixel, red, green, blue, alpha; + unsigned int packed; + + for (row = im->sy - 1; row >= 0; row--) { + for (xpos = 0; xpos < im->sx; xpos++) { + pixel = gdImageGetPixel(im, xpos, row); + if (im->trueColor) { + red = gdTrueColorGetRed(pixel); + green = gdTrueColorGetGreen(pixel); + blue = gdTrueColorGetBlue(pixel); + alpha = bmp_gd_to_alpha(gdTrueColorGetAlpha(pixel)); + } else { + red = gdImageRed(im, pixel); + green = gdImageGreen(im, pixel); + blue = gdImageBlue(im, pixel); + alpha = bmp_gd_to_alpha(im->alpha[pixel]); } + packed = bmp_pack_mask(red, ctx->red_mask) | + bmp_pack_mask(green, ctx->green_mask) | + bmp_pack_mask(blue, ctx->blue_mask) | + bmp_pack_mask(alpha, ctx->alpha_mask); + gdBMPPutInt(out, (int)packed); } } + return 0; +} - - /* If we needed a tmpfile for compression copy it over to out_original */ - if (tmpfile_for_compression) { +static int bmp_write_tmpfile_to_ctx(gdIOCtxPtr out, gdIOCtxPtr out_original) { unsigned char* copy_buffer = NULL; int buffer_size = 0; gdSeek(out, 0); copy_buffer = (unsigned char *) gdMalloc(1024 * sizeof(unsigned char)); if (copy_buffer == NULL) { - goto cleanup; + return 1; } while ((buffer_size = gdGetBuf(copy_buffer, 1024, out)) != EOF) { if (buffer_size == 0) { - break; - } - gdPutBuf(copy_buffer , buffer_size, out_original); - } - gdFree(copy_buffer); - - /* Replace the temp with the original which now has data */ - out->gd_free(out); - out = out_original; - out_original = NULL; - } - - ret = 0; -cleanup: - if (tmpfile_for_compression) { -#ifdef _WIN32 - _rmtmp(); -#else - fclose(tmpfile_for_compression); -#endif - tmpfile_for_compression = NULL; + break; + } + if (gdPutBuf(copy_buffer, buffer_size, out_original) != buffer_size) { + gd_error("gd-bmp write error\n"); + gdFree(copy_buffer); + return 1; } - - if (out_original) { - out_original->gd_free(out_original); } - return ret; + gdFree(copy_buffer); + return 0; } -static int compress_row(unsigned char *row, int length) -{ +static int compress_row(unsigned char *row, int length) { int rle_type = 0; int compressed_length = 0; int pixel = 0, compressed_run = 0, rle_compression = 0; - unsigned char *uncompressed_row = NULL, *uncompressed_rowp = NULL, *uncompressed_start = NULL; - + unsigned char *uncompressed_row = NULL, *uncompressed_rowp = NULL, + *uncompressed_start = NULL; + GD_ASSUME(length > 0); uncompressed_row = (unsigned char *) gdMalloc(length); if (!uncompressed_row) { return -1; @@ -331,9 +1025,12 @@ static int compress_row(unsigned char *row, int length) } if (rle_type == BMP_RLE_TYPE_RLE) { - if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) != 0) { - /* more than what we can store in a single run or run is over due to non match, force write */ - rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row); + if (compressed_run >= 128 || + memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) != 0) { + /* more than what we can store in a single run or run is over + * due to non match, force write */ + rle_compression = build_rle_packet( + row, rle_type, compressed_run, uncompressed_row); row += rle_compression; compressed_length += rle_compression; compressed_run = 0; @@ -343,9 +1040,12 @@ static int compress_row(unsigned char *row, int length) uncompressed_rowp++; } } else { - if (compressed_run >= 128 || memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) { - /* more than what we can store in a single run or run is over due to match, force write */ - rle_compression = build_rle_packet(row, rle_type, compressed_run, uncompressed_row); + if (compressed_run >= 128 || + memcmp(uncompressed_rowp, uncompressed_rowp - 1, 1) == 0) { + /* more than what we can store in a single run or run is over + * due to match, force write */ + rle_compression = build_rle_packet( + row, rle_type, compressed_run, uncompressed_row); row += rle_compression; compressed_length += rle_compression; compressed_run = 0; @@ -360,7 +1060,8 @@ static int compress_row(unsigned char *row, int length) } if (compressed_run) { - compressed_length += build_rle_packet(row, rle_type, compressed_run, uncompressed_row); + compressed_length += + build_rle_packet(row, rle_type, compressed_run, uncompressed_row); } gdFree(uncompressed_start); @@ -368,14 +1069,15 @@ static int compress_row(unsigned char *row, int length) return compressed_length; } -static int build_rle_packet(unsigned char *row, int packet_type, int length, unsigned char *data) -{ +static int build_rle_packet(unsigned char *row, int packet_type, int length, + unsigned char *data) { int compressed_size = 0; if (length < 1 || length > 128) { return 0; } - /* Bitmap specific cases is that we can't have uncompressed rows of length 1 or 2 */ + /* Bitmap specific cases is that we can't have uncompressed rows of length 1 + * or 2 */ if (packet_type == BMP_RLE_TYPE_RAW && length < 3) { int i = 0; for (i = 0; i < length; i++) { @@ -417,11 +1119,11 @@ static int build_rle_packet(unsigned char *row, int packet_type, int length, uns /* Function: gdImageCreateFromBmp */ -gdImagePtr gdImageCreateFromBmp(FILE * inFile) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmp(FILE *inFile) { gdImagePtr im = 0; gdIOCtx *in = gdNewFileCtx(inFile); - if (in == NULL) return NULL; + if (in == NULL) + return NULL; im = gdImageCreateFromBmpCtx(in); in->gd_free(in); return im; @@ -430,11 +1132,11 @@ gdImagePtr gdImageCreateFromBmp(FILE * inFile) /* Function: gdImageCreateFromBmpPtr */ -gdImagePtr gdImageCreateFromBmpPtr(int size, void *data) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpPtr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); - if (in == NULL) return NULL; + if (in == NULL) + return NULL; im = gdImageCreateFromBmpCtx(in); in->gd_free(in); return im; @@ -443,8 +1145,7 @@ gdImagePtr gdImageCreateFromBmpPtr(int size, void *data) /* Function: gdImageCreateFromBmpCtx */ -gdImagePtr gdImageCreateFromBmpCtx(gdIOCtxPtr infile) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpCtx(gdIOCtxPtr infile) { bmp_hdr_t *hdr; bmp_info_t *info; gdImagePtr im = NULL; @@ -475,6 +1176,12 @@ gdImagePtr gdImageCreateFromBmpCtx(gdIOCtxPtr infile) return NULL; } + if (bmp_validate_info(info, hdr)) { + gdFree(hdr); + gdFree(info); + return NULL; + } + BMP_DEBUG(printf("Numcolours: %d\n", info->numcolors)); BMP_DEBUG(printf("Width: %d\n", info->width)); BMP_DEBUG(printf("Height: %d\n", info->height)); @@ -499,6 +1206,10 @@ gdImagePtr gdImageCreateFromBmpCtx(gdIOCtxPtr infile) BMP_DEBUG(printf("1-bit image\n")); error = bmp_read_1bit(im, infile, info, hdr); break; + case 2: + BMP_DEBUG(printf("2-bit image\n")); + error = bmp_read_2bit(im, infile, info, hdr); + break; case 4: BMP_DEBUG(printf("4-bit image\n")); error = bmp_read_4bit(im, infile, info, hdr); @@ -529,22 +1240,18 @@ gdImagePtr gdImageCreateFromBmpCtx(gdIOCtxPtr infile) return im; } -static int bmp_read_header(gdIOCtx *infile, bmp_hdr_t *hdr) -{ - if( - !gdGetWordLSB(&hdr->magic, infile) || +static int bmp_read_header(gdIOCtx *infile, bmp_hdr_t *hdr) { + if (!gdGetWordLSB(&hdr->magic, infile) || !gdGetIntLSB(&hdr->size, infile) || !gdGetWordLSB(&hdr->reserved1, infile) || !gdGetWordLSB(&hdr->reserved2 , infile) || - !gdGetIntLSB(&hdr->off , infile) - ) { + !gdGetIntLSB(&hdr->off, infile)) { return 1; } return 0; } -static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info) -{ +static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info) { /* read BMP length so we can work out the version */ if (!gdGetIntLSB(&info->len, infile)) { return 1; @@ -553,6 +1260,8 @@ static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info) switch (info->len) { /* For now treat Windows v4 + v5 as v3 */ case BMP_WINDOWS_V3: + case BMP_WINDOWS_V2: + case BMP_WINDOWS_V3_ALPHA: case BMP_WINDOWS_V4: case BMP_WINDOWS_V5: BMP_DEBUG(printf("Reading Windows Header\n")); @@ -565,6 +1274,7 @@ static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info) return 1; } break; + case BMP_OS2_V2_SHORT: case BMP_OS2_V2: if (bmp_read_os2_v2_info(infile, info)) { return 1; @@ -577,10 +1287,105 @@ static int bmp_read_info(gdIOCtx *infile, bmp_info_t *info) return 0; } -static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info) -{ - if ( - !gdGetIntLSB(&info->width, infile) || +static int bmp_skip_bytes(gdIOCtxPtr infile, int count) { + int i, c; + + for (i = 0; i < count; i++) { + if (!gdGetByte(&c, infile)) { + return 1; + } + } + return 0; +} + +static int bmp_read_bitfield_masks(gdIOCtxPtr infile, bmp_info_t *info, + int read_alpha) { + int red, green, blue, alpha; + + if (!gdGetIntLSB(&red, infile) || !gdGetIntLSB(&green, infile) || + !gdGetIntLSB(&blue, infile)) { + return 1; + } + + info->red_mask = (unsigned int)red; + info->green_mask = (unsigned int)green; + info->blue_mask = (unsigned int)blue; + + if (read_alpha) { + if (!gdGetIntLSB(&alpha, infile)) { + return 1; + } + info->alpha_mask = (unsigned int)alpha; + } + + return 0; +} + +static int bmp_validate_info(bmp_info_t *info, bmp_hdr_t *hdr) { + int image_size; + int min_size; + + if (hdr->off < 14 + info->len || hdr->size < 0 || hdr->off < 0) { + return 1; + } + + if (info->numplanes != 1) { + return 1; + } + + if (info->depth != 1 && info->depth != 2 && info->depth != 4 && + info->depth != 8 && info->depth != 16 && info->depth != 24 && + info->depth != 32) { + return 1; + } + + if (info->topdown && + (info->enctype == BMP_BI_RLE4 || info->enctype == BMP_BI_RLE8)) { + return 1; + } + + if (overflow2(info->width, info->height)) { + return 1; + } + + if (info->hres < 0 || info->vres < 0) { + return 1; + } + if (info->hres > 0 && info->vres > 0 && + (info->hres / info->vres > 1000 || info->vres / info->hres > 1000)) { + return 1; + } + + if (info->enctype == BMP_BI_RGB || info->enctype == BMP_BI_BITFIELDS || + info->enctype == BMP_BI_ALPHABITFIELDS) { + if (bmp_image_size(info->width, info->height, info->depth, + &image_size)) { + return 1; + } + if (info->size != 0 && info->size != image_size) { + return 1; + } + if (hdr->size != 0) { + min_size = hdr->off + image_size; + if (min_size < hdr->off) { + return 1; + } + if (hdr->size < min_size && hdr->size != 14 + info->len) { + return 1; + } + if (hdr->size > min_size && hdr->size - min_size > 1024 * 1024) { + return 1; + } + } + } + + return 0; +} + +static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info) { + int extra = info->len - BMP_WINDOWS_V3; + + if (!gdGetIntLSB(&info->width, infile) || !gdGetIntLSB(&info->height, infile) || !gdGetWordLSB(&info->numplanes, infile) || !gdGetWordLSB(&info->depth, infile) || @@ -589,11 +1394,13 @@ static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info) !gdGetIntLSB(&info->hres, infile) || !gdGetIntLSB(&info->vres, infile) || !gdGetIntLSB(&info->numcolors, infile) || - !gdGetIntLSB(&info->mincolors, infile) - ) { + !gdGetIntLSB(&info->mincolors, infile)) { return 1; } + /* Unlikely, but possible -- largest signed value won't fit in unsigned. */ + if (info->height == 0 || info->height == INT_MIN) + return 1; if (info->height < 0) { info->topdown = 1; info->height = -info->height; @@ -603,62 +1410,89 @@ static int bmp_read_windows_v3_info(gdIOCtxPtr infile, bmp_info_t *info) info->type = BMP_PALETTE_4; - if (info->width <= 0 || info->height <= 0 || info->numplanes <= 0 || - info->depth <= 0 || info->numcolors < 0 || info->mincolors < 0) { + if (extra > 0) { + if (extra < 12 || bmp_read_bitfield_masks(infile, info, extra >= 16)) { + return 1; + } + extra -= (extra >= 16) ? 16 : 12; + if (bmp_skip_bytes(infile, extra)) { + return 1; + } + } + + /* Height was checked above. */ + if (info->width <= 0 || info->numplanes <= 0 || info->depth <= 0 || + info->numcolors < 0 || info->mincolors < 0) { return 1; } return 0; } -static int bmp_read_os2_v1_info(gdIOCtxPtr infile, bmp_info_t *info) -{ - if ( - !gdGetWordLSB((signed short int *)&info->width, infile) || +static int bmp_read_os2_v1_info(gdIOCtxPtr infile, bmp_info_t *info) { + if (!gdGetWordLSB((signed short int *)&info->width, infile) || !gdGetWordLSB((signed short int *)&info->height, infile) || !gdGetWordLSB(&info->numplanes, infile) || - !gdGetWordLSB(&info->depth, infile) - ) { + !gdGetWordLSB(&info->depth, infile)) { return 1; } /* OS2 v1 doesn't support topdown */ info->topdown = 0; - info->numcolors = 1 << info->depth; + /* The spec says the depth can only be a few value values. */ + if (info->depth != 1 && info->depth != 4 && info->depth != 8 && + info->depth != 16 && info->depth != 24) { + return 1; + } + + info->numcolors = (info->depth <= 8) ? (1 << info->depth) : 0; info->type = BMP_PALETTE_3; - if (info->width <= 0 || info->height <= 0 || info->numplanes <= 0 || - info->depth <= 0 || info->numcolors < 0) { + if (info->width <= 0 || info->height <= 0 || info->numplanes <= 0) { return 1; } return 0; } -static int bmp_read_os2_v2_info(gdIOCtxPtr infile, bmp_info_t *info) -{ +static int bmp_read_os2_v2_info(gdIOCtxPtr infile, bmp_info_t *info) { char useless_bytes[24]; - if ( - !gdGetIntLSB(&info->width, infile) || + if (!gdGetIntLSB(&info->width, infile) || !gdGetIntLSB(&info->height, infile) || !gdGetWordLSB(&info->numplanes, infile) || - !gdGetWordLSB(&info->depth, infile) || - !gdGetIntLSB(&info->enctype, infile) || + !gdGetWordLSB(&info->depth, infile)) { + return 1; + } + + if (info->len == BMP_OS2_V2_SHORT) { + info->enctype = BMP_BI_RGB; + info->size = 0; + info->hres = 0; + info->vres = 0; + info->numcolors = (info->depth <= 8) ? (1 << info->depth) : 0; + info->mincolors = 0; + goto done; + } + + if (!gdGetIntLSB(&info->enctype, infile) || !gdGetIntLSB(&info->size, infile) || !gdGetIntLSB(&info->hres, infile) || !gdGetIntLSB(&info->vres, infile) || !gdGetIntLSB(&info->numcolors, infile) || - !gdGetIntLSB(&info->mincolors, infile) - ) { + !gdGetIntLSB(&info->mincolors, infile)) { return 1; } - /* Let's seek the next 24 pointless bytes, we don't care too much about it */ + /* Lets seek the next 24 pointless bytes, we don't care too much about it */ if (!gdGetBuf(useless_bytes, 24, infile)) { return 1; } +done: + /* Unlikely, but possible -- largest signed value won't fit in unsigned. */ + if (info->height == 0 || info->height == INT_MIN) + return 1; if (info->height < 0) { info->topdown = 1; info->height = -info->height; @@ -668,20 +1502,33 @@ static int bmp_read_os2_v2_info(gdIOCtxPtr infile, bmp_info_t *info) info->type = BMP_PALETTE_4; - if (info->width <= 0 || info->height <= 0 || info->numplanes <= 0 || - info->depth <= 0 || info->numcolors < 0 || info->mincolors < 0) { + /* Height was checked above. */ + if (info->width <= 0 || info->numplanes <= 0 || info->depth <= 0 || + info->numcolors < 0 || info->mincolors < 0) { return 1; } - return 0; } -static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header) -{ +static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header) { int ypos = 0, xpos = 0, row = 0; - int padding = 0, alpha = 0, red = 0, green = 0, blue = 0; - signed short int data = 0; + int padding = 0, red = 0, green = 0, blue = 0; + int alpha = gdAlphaOpaque; + signed short int data16 = 0; + int data32 = 0; + unsigned int mask = 0; + + if (info->depth == 16 && info->enctype == BMP_BI_RGB) { + info->red_mask = 0x7C00; + info->green_mask = 0x03E0; + info->blue_mask = 0x001F; + } else if (info->depth == 32 && info->enctype == BMP_BI_RGB) { + info->red_mask = 0x00FF0000; + info->green_mask = 0x0000FF00; + info->blue_mask = 0x000000FF; + } switch(info->enctype) { case BMP_BI_RGB: @@ -690,11 +1537,23 @@ static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, b case BMP_BI_BITFIELDS: if (info->depth == 24) { - BMP_DEBUG(printf("Bitfield compression isn't supported for 24-bit\n")); + BMP_DEBUG( + printf("Bitfield compression isn't supported for 24-bit\n")); return 1; } - BMP_DEBUG(printf("Currently no bitfield support\n")); + if (info->len == BMP_WINDOWS_V3 && + bmp_read_bitfield_masks(infile, info, 0)) { return 1; + } + break; + case BMP_BI_ALPHABITFIELDS: + if (info->depth != 16 && info->depth != 32) { + return 1; + } + if (info->len == BMP_WINDOWS_V3 && + bmp_read_bitfield_masks(infile, info, 1)) { + return 1; + } break; case BMP_BI_RLE8: @@ -716,7 +1575,24 @@ static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, b return 1; } - /* There is a chance the data isn't until later, would be weird but it is possible */ + if (info->depth == 16 || info->depth == 32) { + mask = info->red_mask | info->green_mask | info->blue_mask; + if (info->red_mask == 0 || info->green_mask == 0 || + info->blue_mask == 0) { + return 1; + } + if ((info->red_mask & info->green_mask) || + (info->red_mask & info->blue_mask) || + (info->green_mask & info->blue_mask)) { + return 1; + } + if (info->depth == 16 && (mask & 0xFFFF0000U)) { + return 1; + } + } + + /* There is a chance the data isn't until later, would be weird but it is + * possible */ if (gdTell(infile) != header->off) { /* Should make sure we don't seek past the file size */ if (!gdSeek(infile, header->off)) { @@ -724,13 +1600,11 @@ static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, b } } - /* The line must be divisible by 4, else it's padded with NULLs */ - padding = ((int)(info->depth / 8) * info->width) % 4; - if (padding) { - padding = 4 - padding; + /* The line must be divisible by 4, else its padded with NULLs */ + if (bmp_row_padding(info->width, info->depth, &padding)) { + return 1; } - for (ypos = 0; ypos < info->height; ++ypos) { if (info->topdown) { row = ypos; @@ -740,25 +1614,41 @@ static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, b for (xpos = 0; xpos < info->width; xpos++) { if (info->depth == 16) { - if (!gdGetWordLSB(&data, infile)) { + if (!gdGetWordLSB(&data16, infile)) { return 1; } - BMP_DEBUG(printf("Data: %X\n", data)); - red = ((data & 0x7C00) >> 10) << 3; - green = ((data & 0x3E0) >> 5) << 3; - blue = (data & 0x1F) << 3; + BMP_DEBUG(printf("Data: %X\n", data16)); + red = bmp_extract_mask((unsigned short)data16, info->red_mask); + green = + bmp_extract_mask((unsigned short)data16, info->green_mask); + blue = + bmp_extract_mask((unsigned short)data16, info->blue_mask); + alpha = info->alpha_mask + ? bmp_alpha_to_gd(bmp_extract_mask( + (unsigned short)data16, info->alpha_mask)) + : gdAlphaOpaque; BMP_DEBUG(printf("R: %d, G: %d, B: %d\n", red, green, blue)); } else if (info->depth == 24) { - if (!gdGetByte(&blue, infile) || !gdGetByte(&green, infile) || !gdGetByte(&red, infile)) { + if (!gdGetByte(&blue, infile) || !gdGetByte(&green, infile) || + !gdGetByte(&red, infile)) { return 1; } + alpha = gdAlphaOpaque; } else { - if (!gdGetByte(&blue, infile) || !gdGetByte(&green, infile) || !gdGetByte(&red, infile) || !gdGetByte(&alpha, infile)) { + if (!gdGetIntLSB(&data32, infile)) { return 1; } + red = bmp_extract_mask((unsigned int)data32, info->red_mask); + green = + bmp_extract_mask((unsigned int)data32, info->green_mask); + blue = bmp_extract_mask((unsigned int)data32, info->blue_mask); + alpha = info->alpha_mask + ? bmp_alpha_to_gd(bmp_extract_mask( + (unsigned int)data32, info->alpha_mask)) + : gdAlphaOpaque; } - /*alpha = gdAlphaMax - (alpha >> 1);*/ - gdImageSetPixel(im, xpos, row, gdTrueColor(red, green, blue)); + gdImageSetPixel(im, xpos, row, + gdTrueColorAlpha(red, green, blue, alpha)); } for (xpos = padding; xpos > 0; --xpos) { if (!gdGetByte(&red, infile)) { @@ -770,18 +1660,14 @@ static int bmp_read_direct(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, b return 0; } -static int bmp_read_palette(gdImagePtr im, gdIOCtxPtr infile, int count, int read_four) -{ +static int bmp_read_palette(gdImagePtr im, gdIOCtxPtr infile, int count, + int read_four) { int i; int r, g, b, z; for (i = 0; i < count; i++) { - if ( - !gdGetByte(&b, infile) || - !gdGetByte(&g, infile) || - !gdGetByte(&r, infile) || - (read_four && !gdGetByte(&z, infile)) - ) { + if (!gdGetByte(&b, infile) || !gdGetByte(&g, infile) || + !gdGetByte(&r, infile) || (read_four && !gdGetByte(&z, infile))) { return 1; } im->red[i] = r; @@ -792,8 +1678,139 @@ static int bmp_read_palette(gdImagePtr im, gdIOCtxPtr infile, int count, int rea return 0; } -static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header) -{ +static int bmp_check_palette_index(gdImagePtr im, int index) { + return index >= 0 && index < im->colorsTotal; +} + +static int bmp_row_padding(int width, int depth, int *padding) { + int bits_per_row; + int bytes_per_row; + + if (overflow2(width, depth)) { + return 1; + } + bits_per_row = width * depth; + if (bits_per_row > INT_MAX - 31) { + return 1; + } + bytes_per_row = ((bits_per_row + 31) / 32) * 4; + *padding = bytes_per_row - ((bits_per_row + 7) / 8); + return 0; +} + +static int bmp_image_size(int width, int height, int depth, int *size) { + int bits_per_row; + int bytes_per_row; + + if (overflow2(width, depth)) { + return 1; + } + bits_per_row = width * depth; + if (bits_per_row > INT_MAX - 31) { + return 1; + } + bytes_per_row = ((bits_per_row + 31) / 32) * 4; + if (overflow2(bytes_per_row, height)) { + return 1; + } + *size = bytes_per_row * height; + return 0; +} + +static int bmp_get_mask_shift(unsigned int mask) { + int shift = 0; + + if (mask == 0) { + return 0; + } + while ((mask & 1U) == 0) { + mask >>= 1; + shift++; + } + return shift; +} + +static int bmp_get_mask_bits(unsigned int mask) { + int bits = 0; + + if (mask == 0) { + return 0; + } + mask >>= bmp_get_mask_shift(mask); + while (mask & 1U) { + bits++; + mask >>= 1; + } + return bits; +} + +static int bmp_extract_mask(unsigned int pixel, unsigned int mask) { + unsigned int value; + int bits; + + if (mask == 0) { + return 0; + } + value = (pixel & mask) >> bmp_get_mask_shift(mask); + bits = bmp_get_mask_bits(mask); + if (bits <= 0) { + return 0; + } + if (bits >= 32) { + return (int)(value >> 24); + } + if (bits >= 8) { + return (int)((value * 255U) / ((1U << bits) - 1U)); + } + return (int)((value * 255U + ((1U << bits) - 1U) / 2U) / + ((1U << bits) - 1U)); +} + +static unsigned int bmp_pack_mask(int channel_8bit, unsigned int mask) { + int bits; + int shift; + unsigned int max_val; + unsigned int value; + + if (mask == 0) { + return 0; + } + bits = bmp_get_mask_bits(mask); + shift = bmp_get_mask_shift(mask); + if (bits <= 0) { + return 0; + } + if (bits >= 32) { + value = (unsigned int)channel_8bit << 24; + } else { + max_val = (1U << bits) - 1U; + value = ((unsigned int)channel_8bit * max_val + 127U) / 255U; + } + return (value << shift) & mask; +} + +static int bmp_alpha_to_gd(int alpha) { + if (alpha <= 0) { + return gdAlphaTransparent; + } + if (alpha >= 255) { + return gdAlphaOpaque; + } + return gdAlphaMax - (alpha * gdAlphaMax + 127) / 255; +} + +static int bmp_gd_to_alpha(int gd_alpha) { + if (gd_alpha <= gdAlphaOpaque) { + return 255; + } + if (gd_alpha >= gdAlphaTransparent) { + return 0; + } + return (gdAlphaMax - gd_alpha) * 255 / gdAlphaMax; +} + +static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header) { int ypos = 0, xpos = 0, row = 0, index = 0; int padding = 0, current_byte = 0, bit = 0; @@ -807,13 +1824,15 @@ static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 1; } - if (bmp_read_palette(im, infile, info->numcolors, (info->type == BMP_PALETTE_4))) { + if (bmp_read_palette(im, infile, info->numcolors, + (info->type == BMP_PALETTE_4))) { return 1; } im->colorsTotal = info->numcolors; - /* There is a chance the data isn't until later, would be weird but it is possible */ + /* There is a chance the data isn't until later, would be weird but it is + * possible */ if (gdTell(infile) != header->off) { /* Should make sure we don't seek past the file size */ if (!gdSeek(infile, header->off)) { @@ -821,10 +1840,8 @@ static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp } } - /* The line must be aligned on a 32 bits word, else it is padded with zeros */ - padding = (info->width + 7) / 8 % 4; - if (padding) { - padding = 4 - padding; + if (bmp_row_padding(info->width, info->depth, &padding)) { + return 1; } for (ypos = 0; ypos < info->height; ++ypos) { @@ -842,14 +1859,85 @@ static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp for (bit = 0; bit < 8; bit++) { index = ((current_byte & (0x80 >> bit)) != 0 ? 0x01 : 0x00); + /* No need to read anything extra */ + if ((xpos + bit) >= info->width) { + break; + } + if (!bmp_check_palette_index(im, index)) { + return 1; + } if (im->open[index]) { im->open[index] = 0; } gdImageSetPixel(im, xpos + bit, row, index); - /* No need to read anything extra */ - if ((xpos + bit) >= info->width) { + } + } + + for (xpos = padding; xpos > 0; --xpos) { + if (!gdGetByte(&index, infile)) { + return 1; + } + } + } + return 0; +} + +static int bmp_read_2bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header) { + int ypos = 0, xpos = 0, row = 0, index = 0; + int padding = 0, current_byte = 0, shift = 0; + + if (info->enctype != BMP_BI_RGB) { + return 1; + } + + if (!info->numcolors) { + info->numcolors = 4; + } else if (info->numcolors < 0 || info->numcolors > 4) { + return 1; + } + + if (bmp_read_palette(im, infile, info->numcolors, + (info->type == BMP_PALETTE_4))) { + return 1; + } + + im->colorsTotal = info->numcolors; + + if (gdTell(infile) != header->off) { + if (!gdSeek(infile, header->off)) { + return 1; + } + } + + if (bmp_row_padding(info->width, info->depth, &padding)) { + return 1; + } + + for (ypos = 0; ypos < info->height; ++ypos) { + if (info->topdown) { + row = ypos; + } else { + row = info->height - ypos - 1; + } + + for (xpos = 0; xpos < info->width; xpos += 4) { + if (!gdGetByte(¤t_byte, infile)) { + return 1; + } + + for (shift = 6; shift >= 0; shift -= 2) { + if (xpos + ((6 - shift) / 2) >= info->width) { break; } + index = (current_byte >> shift) & 0x03; + if (!bmp_check_palette_index(im, index)) { + return 1; + } + if (im->open[index]) { + im->open[index] = 0; + } + gdImageSetPixel(im, xpos + ((6 - shift) / 2), row, index); } } @@ -862,8 +1950,8 @@ static int bmp_read_1bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 0; } -static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header) -{ +static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header) { int ypos = 0, xpos = 0, row = 0, index = 0; int padding = 0, current_byte = 0; @@ -877,13 +1965,15 @@ static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 1; } - if (bmp_read_palette(im, infile, info->numcolors, (info->type == BMP_PALETTE_4))) { + if (bmp_read_palette(im, infile, info->numcolors, + (info->type == BMP_PALETTE_4))) { return 1; } im->colorsTotal = info->numcolors; - /* There is a chance the data isn't until later, would be weird but it is possible */ + /* There is a chance the data isn't until later, would be weird but it is + * possible */ if (gdTell(infile) != header->off) { /* Should make sure we don't seek past the file size */ if (!gdSeek(infile, header->off)) { @@ -891,10 +1981,8 @@ static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp } } - /* The line must be divisible by 4, else it's padded with NULLs */ - padding = ((int)ceil(0.5 * info->width)) % 4; - if (padding) { - padding = 4 - padding; + if (bmp_row_padding(info->width, info->depth, &padding)) { + return 1; } switch (info->enctype) { @@ -912,17 +2000,24 @@ static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp } index = (current_byte >> 4) & 0x0f; + if (!bmp_check_palette_index(im, index)) { + return 1; + } if (im->open[index]) { im->open[index] = 0; } gdImageSetPixel(im, xpos, row, index); - /* This condition may get called often, potential optimsations */ - if (xpos >= info->width) { + /* This condition may get called often, potential optimsations + */ + if (xpos + 1 >= info->width) { break; } index = current_byte & 0x0f; + if (!bmp_check_palette_index(im, index)) { + return 1; + } if (im->open[index]) { im->open[index] = 0; } @@ -949,10 +2044,12 @@ static int bmp_read_4bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 0; } -static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp_hdr_t *header) -{ +static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, + bmp_hdr_t *header) { int ypos = 0, xpos = 0, row = 0, index = 0; int padding = 0; + int palette_entries = 0; + int palette_entry_size = (info->type == BMP_PALETTE_4) ? 4 : 3; if (info->enctype != BMP_BI_RGB && info->enctype != BMP_BI_RLE8) { return 1; @@ -960,17 +2057,30 @@ static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp if (!info->numcolors) { info->numcolors = 256; - } else if (info->numcolors < 0 || info->numcolors > 256) { + } else if (info->numcolors < 0 || info->numcolors > 1024) { + return 1; + } + + palette_entries = (header->off - (14 + info->len)) / palette_entry_size; + if (palette_entries <= 0) { return 1; } + if (info->numcolors > palette_entries) { + info->numcolors = palette_entries; + } + if (info->numcolors > gdMaxColors) { + info->numcolors = gdMaxColors; + } - if (bmp_read_palette(im, infile, info->numcolors, (info->type == BMP_PALETTE_4))) { + if (bmp_read_palette(im, infile, info->numcolors, + (info->type == BMP_PALETTE_4))) { return 1; } im->colorsTotal = info->numcolors; - /* There is a chance the data isn't until later, would be weird but it is possible */ + /* There is a chance the data isn't until later, would be weird but it is + * possible */ if (gdTell(infile) != header->off) { /* Should make sure we don't seek past the file size */ if (!gdSeek(infile, header->off)) { @@ -978,10 +2088,8 @@ static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp } } - /* The line must be divisible by 4, else it's padded with NULLs */ - padding = (1 * info->width) % 4; - if (padding) { - padding = 4 - padding; + if (bmp_row_padding(info->width, info->depth, &padding)) { + return 1; } switch (info->enctype) { @@ -998,6 +2106,9 @@ static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 1; } + if (!bmp_check_palette_index(im, index)) { + return 1; + } if (im->open[index]) { im->open[index] = 0; } @@ -1024,28 +2135,39 @@ static int bmp_read_8bit(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info, bmp return 0; } -static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info) -{ +static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info) { int ypos = 0, xpos = 0, row = 0, index = 0; int rle_length = 0, rle_data = 0; int padding = 0; int i = 0, j = 0; int pixels_per_byte = 8 / info->depth; + if (info->topdown) { + return 1; + } + for (ypos = 0; ypos < info->height && xpos <= info->width;) { if (!gdGetByte(&rle_length, infile) || !gdGetByte(&rle_data, infile)) { return 1; } row = info->height - ypos - 1; + if (row < 0 || row >= info->height) { + return 1; + } if (rle_length != BMP_RLE_COMMAND) { - if (im->open[rle_data]) { - im->open[rle_data] = 0; + for (i = 0; i < rle_length;) { + for (j = 1; (j <= pixels_per_byte) && (i < rle_length); + j++, xpos++, i++) { + index = (rle_data & (((1 << info->depth) - 1) + << (8 - (j * info->depth)))) >> + (8 - (j * info->depth)); + if (xpos >= info->width) { + return 1; } - - for (i = 0; (i < rle_length) && (xpos < info->width);) { - for (j = 1; (j <= pixels_per_byte) && (xpos < info->width) && (i < rle_length); j++, xpos++, i++) { - index = (rle_data & (((1 << info->depth) - 1) << (8 - (j * info->depth)))) >> (8 - (j * info->depth)); + if (!bmp_check_palette_index(im, index)) { + return 1; + } if (im->open[index]) { im->open[index] = 0; } @@ -1055,7 +2177,7 @@ static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info) } else if (rle_length == BMP_RLE_COMMAND && rle_data > 2) { /* Uncompressed RLE needs to be even */ padding = 0; - for (i = 0; (i < rle_data) && (xpos < info->width); i += pixels_per_byte) { + for (i = 0; i < rle_data; i += pixels_per_byte) { int max_pixels = pixels_per_byte; if (!gdGetByte(&index, infile)) { @@ -1067,12 +2189,20 @@ static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info) max_pixels = rle_data - i; } - for (j = 1; (j <= max_pixels) && (xpos < info->width); j++, xpos++) { - int temp = (index >> (8 - (j * info->depth))) & ((1 << info->depth) - 1); + for (j = 1; j <= max_pixels; j++) { + int temp = (index >> (8 - (j * info->depth))) & + ((1 << info->depth) - 1); + if (xpos >= info->width) { + return 1; + } + if (!bmp_check_palette_index(im, temp)) { + return 1; + } if (im->open[temp]) { im->open[temp] = 0; } gdImageSetPixel(im, xpos, row, temp); + xpos++; } } @@ -1080,18 +2210,25 @@ static int bmp_read_rle(gdImagePtr im, gdIOCtxPtr infile, bmp_info_t *info) if (padding % 2 && !gdGetByte(&index, infile)) { return 1; } - } else if (rle_length == BMP_RLE_COMMAND && rle_data == BMP_RLE_ENDOFLINE) { + } else if (rle_length == BMP_RLE_COMMAND && + rle_data == BMP_RLE_ENDOFLINE) { /* Next Line */ xpos = 0; ypos++; } else if (rle_length == BMP_RLE_COMMAND && rle_data == BMP_RLE_DELTA) { /* Delta Record, used for bmp files that contain other data*/ - if (!gdGetByte(&rle_length, infile) || !gdGetByte(&rle_data, infile)) { + if (!gdGetByte(&rle_length, infile) || + !gdGetByte(&rle_data, infile)) { + return 1; + } + if (xpos + rle_length > info->width || + ypos + rle_data >= info->height) { return 1; } xpos += rle_length; ypos += rle_data; - } else if (rle_length == BMP_RLE_COMMAND && rle_data == BMP_RLE_ENDOFBITMAP) { + } else if (rle_length == BMP_RLE_COMMAND && + rle_data == BMP_RLE_ENDOFBITMAP) { /* End of bitmap */ break; } diff --git a/ext/gd/libgd/gd_color.c b/ext/gd/libgd/gd_color.c new file mode 100644 index 000000000000..569a01fdcf03 --- /dev/null +++ b/ext/gd/libgd/gd_color.c @@ -0,0 +1,37 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd.h" +#include "gd_color.h" +#define GD_MAX_COLOR_DIST_SQ 260100 + +/** + * The threshold method works relatively well for border cropping purposes. + * A perceptually uniform metric (L*a*b* + Delta-E) would give better + * color discrimination but at significant computational cost per pixel, + * which is hard to justify for this use case. + */ +int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold) { + const int dr = gdImageRed(im, col1) - gdImageRed(im, col2); + const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2); + const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2); + const int da = (gdImageAlpha(im, col1) - gdImageAlpha(im, col2)) * 2; + const int dist = dr * dr + dg * dg + db * db + da * da; + + const float t = threshold / 100.0f; + return dist <= (int)(t * t * GD_MAX_COLOR_DIST_SQ); +} + +/* + * To be implemented when we have more image formats. + * Buffer like gray8 gray16 or rgb8 will require some tweak + * and can be done in this function (called from the autocrop + * function. (Pierre) + */ +#if 0 +static int colors_equal (const int col1, const in col2) +{ + +} +#endif diff --git a/ext/gd/libgd/gd_color.h b/ext/gd/libgd/gd_color.h new file mode 100644 index 000000000000..834f45ec8adc --- /dev/null +++ b/ext/gd/libgd/gd_color.h @@ -0,0 +1,14 @@ +#ifndef GD_COLOR_H +#define GD_COLOR_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/gd/libgd/gd_color_map.c b/ext/gd/libgd/gd_color_map.c new file mode 100644 index 000000000000..c6a68ee42097 --- /dev/null +++ b/ext/gd/libgd/gd_color_map.c @@ -0,0 +1,793 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gd.h" +#include "gd_color_map.h" + +static const gdColorMapEntry GD_COLOR_MAP_X11_ENTRIES[] = { + {"AliceBlue", 240, 248, 255}, + {"AntiqueWhite", 250, 235, 215}, + {"AntiqueWhite1", 255, 239, 219}, + {"AntiqueWhite2", 238, 223, 204}, + {"AntiqueWhite3", 205, 192, 176}, + {"AntiqueWhite4", 139, 131, 120}, + {"BlanchedAlmond", 255, 235, 205}, + {"BlueViolet", 138, 43, 226}, + {"CadetBlue", 95, 158, 160}, + {"CadetBlue1", 152, 245, 255}, + {"CadetBlue2", 142, 229, 238}, + {"CadetBlue3", 122, 197, 205}, + {"CadetBlue4", 83, 134, 139}, + {"CornflowerBlue", 100, 149, 237}, + {"DarkBlue", 0, 0, 139}, + {"DarkCyan", 0, 139, 139}, + {"DarkGoldenrod", 184, 134, 11}, + {"DarkGoldenrod1", 255, 185, 15}, + {"DarkGoldenrod2", 238, 173, 14}, + {"DarkGoldenrod3", 205, 149, 12}, + {"DarkGoldenrod4", 139, 101, 8}, + {"DarkGray", 169, 169, 169}, + {"DarkGreen", 0, 100, 0}, + {"DarkGrey", 169, 169, 169}, + {"DarkKhaki", 189, 183, 107}, + {"DarkMagenta", 139, 0, 139}, + {"DarkOliveGreen", 85, 107, 47}, + {"DarkOliveGreen1", 202, 255, 112}, + {"DarkOliveGreen2", 188, 238, 104}, + {"DarkOliveGreen3", 162, 205, 90}, + {"DarkOliveGreen4", 110, 139, 61}, + {"DarkOrange", 255, 140, 0}, + {"DarkOrange1", 255, 127, 0}, + {"DarkOrange2", 238, 118, 0}, + {"DarkOrange3", 205, 102, 0}, + {"DarkOrange4", 139, 69, 0}, + {"DarkOrchid", 153, 50, 204}, + {"DarkOrchid1", 191, 62, 255}, + {"DarkOrchid2", 178, 58, 238}, + {"DarkOrchid3", 154, 50, 205}, + {"DarkOrchid4", 104, 34, 139}, + {"DarkRed", 139, 0, 0}, + {"DarkSalmon", 233, 150, 122}, + {"DarkSeaGreen", 143, 188, 143}, + {"DarkSeaGreen1", 193, 255, 193}, + {"DarkSeaGreen2", 180, 238, 180}, + {"DarkSeaGreen3", 155, 205, 155}, + {"DarkSeaGreen4", 105, 139, 105}, + {"DarkSlateBlue", 72, 61, 139}, + {"DarkSlateGray", 47, 79, 79}, + {"DarkSlateGray1", 151, 255, 255}, + {"DarkSlateGray2", 141, 238, 238}, + {"DarkSlateGray3", 121, 205, 205}, + {"DarkSlateGray4", 82, 139, 139}, + {"DarkSlateGrey", 47, 79, 79}, + {"DarkTurquoise", 0, 206, 209}, + {"DarkViolet", 148, 0, 211}, + {"DeepPink", 255, 20, 147}, + {"DeepPink1", 255, 20, 147}, + {"DeepPink2", 238, 18, 137}, + {"DeepPink3", 205, 16, 118}, + {"DeepPink4", 139, 10, 80}, + {"DeepSkyBlue", 0, 191, 255}, + {"DeepSkyBlue1", 0, 191, 255}, + {"DeepSkyBlue2", 0, 178, 238}, + {"DeepSkyBlue3", 0, 154, 205}, + {"DeepSkyBlue4", 0, 104, 139}, + {"DimGray", 105, 105, 105}, + {"DimGrey", 105, 105, 105}, + {"DodgerBlue", 30, 144, 255}, + {"DodgerBlue1", 30, 144, 255}, + {"DodgerBlue2", 28, 134, 238}, + {"DodgerBlue3", 24, 116, 205}, + {"DodgerBlue4", 16, 78, 139}, + {"FloralWhite", 255, 250, 240}, + {"ForestGreen", 34, 139, 34}, + {"GhostWhite", 248, 248, 255}, + {"GreenYellow", 173, 255, 47}, + {"HotPink", 255, 105, 180}, + {"HotPink1", 255, 110, 180}, + {"HotPink2", 238, 106, 167}, + {"HotPink3", 205, 96, 144}, + {"HotPink4", 139, 58, 98}, + {"IndianRed", 205, 92, 92}, + {"IndianRed1", 255, 106, 106}, + {"IndianRed2", 238, 99, 99}, + {"IndianRed3", 205, 85, 85}, + {"IndianRed4", 139, 58, 58}, + {"LavenderBlush", 255, 240, 245}, + {"LavenderBlush1", 255, 240, 245}, + {"LavenderBlush2", 238, 224, 229}, + {"LavenderBlush3", 205, 193, 197}, + {"LavenderBlush4", 139, 131, 134}, + {"LawnGreen", 124, 252, 0}, + {"LemonChiffon", 255, 250, 205}, + {"LemonChiffon1", 255, 250, 205}, + {"LemonChiffon2", 238, 233, 191}, + {"LemonChiffon3", 205, 201, 165}, + {"LemonChiffon4", 139, 137, 112}, + {"LightBlue", 173, 216, 230}, + {"LightBlue1", 191, 239, 255}, + {"LightBlue2", 178, 223, 238}, + {"LightBlue3", 154, 192, 205}, + {"LightBlue4", 104, 131, 139}, + {"LightCoral", 240, 128, 128}, + {"LightCyan", 224, 255, 255}, + {"LightCyan1", 224, 255, 255}, + {"LightCyan2", 209, 238, 238}, + {"LightCyan3", 180, 205, 205}, + {"LightCyan4", 122, 139, 139}, + {"LightGoldenrod", 238, 221, 130}, + {"LightGoldenrod1", 255, 236, 139}, + {"LightGoldenrod2", 238, 220, 130}, + {"LightGoldenrod3", 205, 190, 112}, + {"LightGoldenrod4", 139, 129, 76}, + {"LightGoldenrodYellow", 250, 250, 210}, + {"LightGray", 211, 211, 211}, + {"LightGreen", 144, 238, 144}, + {"LightGrey", 211, 211, 211}, + {"LightPink", 255, 182, 193}, + {"LightPink1", 255, 174, 185}, + {"LightPink2", 238, 162, 173}, + {"LightPink3", 205, 140, 149}, + {"LightPink4", 139, 95, 101}, + {"LightSalmon", 255, 160, 122}, + {"LightSalmon1", 255, 160, 122}, + {"LightSalmon2", 238, 149, 114}, + {"LightSalmon3", 205, 129, 98}, + {"LightSalmon4", 139, 87, 66}, + {"LightSeaGreen", 32, 178, 170}, + {"LightSkyBlue", 135, 206, 250}, + {"LightSkyBlue1", 176, 226, 255}, + {"LightSkyBlue2", 164, 211, 238}, + {"LightSkyBlue3", 141, 182, 205}, + {"LightSkyBlue4", 96, 123, 139}, + {"LightSlateBlue", 132, 112, 255}, + {"LightSlateGray", 119, 136, 153}, + {"LightSlateGrey", 119, 136, 153}, + {"LightSteelBlue", 176, 196, 222}, + {"LightSteelBlue1", 202, 225, 255}, + {"LightSteelBlue2", 188, 210, 238}, + {"LightSteelBlue3", 162, 181, 205}, + {"LightSteelBlue4", 110, 123, 139}, + {"LightYellow", 255, 255, 224}, + {"LightYellow1", 255, 255, 224}, + {"LightYellow2", 238, 238, 209}, + {"LightYellow3", 205, 205, 180}, + {"LightYellow4", 139, 139, 122}, + {"LimeGreen", 50, 205, 50}, + {"MediumAquamarine", 102, 205, 170}, + {"MediumBlue", 0, 0, 205}, + {"MediumOrchid", 186, 85, 211}, + {"MediumOrchid1", 224, 102, 255}, + {"MediumOrchid2", 209, 95, 238}, + {"MediumOrchid3", 180, 82, 205}, + {"MediumOrchid4", 122, 55, 139}, + {"MediumPurple", 147, 112, 219}, + {"MediumPurple1", 171, 130, 255}, + {"MediumPurple2", 159, 121, 238}, + {"MediumPurple3", 137, 104, 205}, + {"MediumPurple4", 93, 71, 139}, + {"MediumSeaGreen", 60, 179, 113}, + {"MediumSlateBlue", 123, 104, 238}, + {"MediumSpringGreen", 0, 250, 154}, + {"MediumTurquoise", 72, 209, 204}, + {"MediumVioletRed", 199, 21, 133}, + {"MidnightBlue", 25, 25, 112}, + {"MintCream", 245, 255, 250}, + {"MistyRose", 255, 228, 225}, + {"MistyRose1", 255, 228, 225}, + {"MistyRose2", 238, 213, 210}, + {"MistyRose3", 205, 183, 181}, + {"MistyRose4", 139, 125, 123}, + {"NavajoWhite", 255, 222, 173}, + {"NavajoWhite1", 255, 222, 173}, + {"NavajoWhite2", 238, 207, 161}, + {"NavajoWhite3", 205, 179, 139}, + {"NavajoWhite4", 139, 121, 94}, + {"NavyBlue", 0, 0, 128}, + {"OldLace", 253, 245, 230}, + {"OliveDrab", 107, 142, 35}, + {"OliveDrab1", 192, 255, 62}, + {"OliveDrab2", 179, 238, 58}, + {"OliveDrab3", 154, 205, 50}, + {"OliveDrab4", 105, 139, 34}, + {"OrangeRed", 255, 69, 0}, + {"OrangeRed1", 255, 69, 0}, + {"OrangeRed2", 238, 64, 0}, + {"OrangeRed3", 205, 55, 0}, + {"OrangeRed4", 139, 37, 0}, + {"PaleGoldenrod", 238, 232, 170}, + {"PaleGreen", 152, 251, 152}, + {"PaleGreen1", 154, 255, 154}, + {"PaleGreen2", 144, 238, 144}, + {"PaleGreen3", 124, 205, 124}, + {"PaleGreen4", 84, 139, 84}, + {"PaleTurquoise", 175, 238, 238}, + {"PaleTurquoise1", 187, 255, 255}, + {"PaleTurquoise2", 174, 238, 238}, + {"PaleTurquoise3", 150, 205, 205}, + {"PaleTurquoise4", 102, 139, 139}, + {"PaleVioletRed", 219, 112, 147}, + {"PaleVioletRed1", 255, 130, 171}, + {"PaleVioletRed2", 238, 121, 159}, + {"PaleVioletRed3", 205, 104, 137}, + {"PaleVioletRed4", 139, 71, 93}, + {"PapayaWhip", 255, 239, 213}, + {"PeachPuff", 255, 218, 185}, + {"PeachPuff1", 255, 218, 185}, + {"PeachPuff2", 238, 203, 173}, + {"PeachPuff3", 205, 175, 149}, + {"PeachPuff4", 139, 119, 101}, + {"PowderBlue", 176, 224, 230}, + {"RosyBrown", 188, 143, 143}, + {"RosyBrown1", 255, 193, 193}, + {"RosyBrown2", 238, 180, 180}, + {"RosyBrown3", 205, 155, 155}, + {"RosyBrown4", 139, 105, 105}, + {"RoyalBlue", 65, 105, 225}, + {"RoyalBlue1", 72, 118, 255}, + {"RoyalBlue2", 67, 110, 238}, + {"RoyalBlue3", 58, 95, 205}, + {"RoyalBlue4", 39, 64, 139}, + {"SaddleBrown", 139, 69, 19}, + {"SandyBrown", 244, 164, 96}, + {"SeaGreen", 46, 139, 87}, + {"SeaGreen1", 84, 255, 159}, + {"SeaGreen2", 78, 238, 148}, + {"SeaGreen3", 67, 205, 128}, + {"SeaGreen4", 46, 139, 87}, + {"SkyBlue", 135, 206, 235}, + {"SkyBlue1", 135, 206, 255}, + {"SkyBlue2", 126, 192, 238}, + {"SkyBlue3", 108, 166, 205}, + {"SkyBlue4", 74, 112, 139}, + {"SlateBlue", 106, 90, 205}, + {"SlateBlue1", 131, 111, 255}, + {"SlateBlue2", 122, 103, 238}, + {"SlateBlue3", 105, 89, 205}, + {"SlateBlue4", 71, 60, 139}, + {"SlateGray", 112, 128, 144}, + {"SlateGray1", 198, 226, 255}, + {"SlateGray2", 185, 211, 238}, + {"SlateGray3", 159, 182, 205}, + {"SlateGray4", 108, 123, 139}, + {"SlateGrey", 112, 128, 144}, + {"SpringGreen", 0, 255, 127}, + {"SpringGreen1", 0, 255, 127}, + {"SpringGreen2", 0, 238, 118}, + {"SpringGreen3", 0, 205, 102}, + {"SpringGreen4", 0, 139, 69}, + {"SteelBlue", 70, 130, 180}, + {"SteelBlue1", 99, 184, 255}, + {"SteelBlue2", 92, 172, 238}, + {"SteelBlue3", 79, 148, 205}, + {"SteelBlue4", 54, 100, 139}, + {"VioletRed", 208, 32, 144}, + {"VioletRed1", 255, 62, 150}, + {"VioletRed2", 238, 58, 140}, + {"VioletRed3", 205, 50, 120}, + {"VioletRed4", 139, 34, 82}, + {"WhiteSmoke", 245, 245, 245}, + {"YellowGreen", 154, 205, 50}, + {"alice blue", 240, 248, 255}, + {"antique white", 250, 235, 215}, + {"aquamarine", 127, 255, 212}, + {"aquamarine1", 127, 255, 212}, + {"aquamarine2", 118, 238, 198}, + {"aquamarine3", 102, 205, 170}, + {"aquamarine4", 69, 139, 116}, + {"azure", 240, 255, 255}, + {"azure1", 240, 255, 255}, + {"azure2", 224, 238, 238}, + {"azure3", 193, 205, 205}, + {"azure4", 131, 139, 139}, + {"beige", 245, 245, 220}, + {"bisque", 255, 228, 196}, + {"bisque1", 255, 228, 196}, + {"bisque2", 238, 213, 183}, + {"bisque3", 205, 183, 158}, + {"bisque4", 139, 125, 107}, + {"black", 0, 0, 0}, + {"blanched almond", 255, 235, 205}, + {"blue", 0, 0, 255}, + {"blue violet", 138, 43, 226}, + {"blue1", 0, 0, 255}, + {"blue2", 0, 0, 238}, + {"blue3", 0, 0, 205}, + {"blue4", 0, 0, 139}, + {"brown", 165, 42, 42}, + {"brown1", 255, 64, 64}, + {"brown2", 238, 59, 59}, + {"brown3", 205, 51, 51}, + {"brown4", 139, 35, 35}, + {"burlywood", 222, 184, 135}, + {"burlywood1", 255, 211, 155}, + {"burlywood2", 238, 197, 145}, + {"burlywood3", 205, 170, 125}, + {"burlywood4", 139, 115, 85}, + {"cadet blue", 95, 158, 160}, + {"chartreuse", 127, 255, 0}, + {"chartreuse1", 127, 255, 0}, + {"chartreuse2", 118, 238, 0}, + {"chartreuse3", 102, 205, 0}, + {"chartreuse4", 69, 139, 0}, + {"chocolate", 210, 105, 30}, + {"chocolate1", 255, 127, 36}, + {"chocolate2", 238, 118, 33}, + {"chocolate3", 205, 102, 29}, + {"chocolate4", 139, 69, 19}, + {"coral", 255, 127, 80}, + {"coral1", 255, 114, 86}, + {"coral2", 238, 106, 80}, + {"coral3", 205, 91, 69}, + {"coral4", 139, 62, 47}, + {"cornflower blue", 100, 149, 237}, + {"cornsilk", 255, 248, 220}, + {"cornsilk1", 255, 248, 220}, + {"cornsilk2", 238, 232, 205}, + {"cornsilk3", 205, 200, 177}, + {"cornsilk4", 139, 136, 120}, + {"cyan", 0, 255, 255}, + {"cyan1", 0, 255, 255}, + {"cyan2", 0, 238, 238}, + {"cyan3", 0, 205, 205}, + {"cyan4", 0, 139, 139}, + {"dark blue", 0, 0, 139}, + {"dark cyan", 0, 139, 139}, + {"dark goldenrod", 184, 134, 11}, + {"dark gray", 169, 169, 169}, + {"dark green", 0, 100, 0}, + {"dark grey", 169, 169, 169}, + {"dark khaki", 189, 183, 107}, + {"dark magenta", 139, 0, 139}, + {"dark olive green", 85, 107, 47}, + {"dark orange", 255, 140, 0}, + {"dark orchid", 153, 50, 204}, + {"dark red", 139, 0, 0}, + {"dark salmon", 233, 150, 122}, + {"dark sea green", 143, 188, 143}, + {"dark slate blue", 72, 61, 139}, + {"dark slate gray", 47, 79, 79}, + {"dark slate grey", 47, 79, 79}, + {"dark turquoise", 0, 206, 209}, + {"dark violet", 148, 0, 211}, + {"deep pink", 255, 20, 147}, + {"deep sky blue", 0, 191, 255}, + {"dim gray", 105, 105, 105}, + {"dim grey", 105, 105, 105}, + {"dodger blue", 30, 144, 255}, + {"firebrick", 178, 34, 34}, + {"firebrick1", 255, 48, 48}, + {"firebrick2", 238, 44, 44}, + {"firebrick3", 205, 38, 38}, + {"firebrick4", 139, 26, 26}, + {"floral white", 255, 250, 240}, + {"forest green", 34, 139, 34}, + {"gainsboro", 220, 220, 220}, + {"ghost white", 248, 248, 255}, + {"gold", 255, 215, 0}, + {"gold1", 255, 215, 0}, + {"gold2", 238, 201, 0}, + {"gold3", 205, 173, 0}, + {"gold4", 139, 117, 0}, + {"goldenrod", 218, 165, 32}, + {"goldenrod1", 255, 193, 37}, + {"goldenrod2", 238, 180, 34}, + {"goldenrod3", 205, 155, 29}, + {"goldenrod4", 139, 105, 20}, + {"gray", 190, 190, 190}, + {"gray0", 0, 0, 0}, + {"gray1", 3, 3, 3}, + {"gray10", 26, 26, 26}, + {"gray100", 255, 255, 255}, + {"gray11", 28, 28, 28}, + {"gray12", 31, 31, 31}, + {"gray13", 33, 33, 33}, + {"gray14", 36, 36, 36}, + {"gray15", 38, 38, 38}, + {"gray16", 41, 41, 41}, + {"gray17", 43, 43, 43}, + {"gray18", 46, 46, 46}, + {"gray19", 48, 48, 48}, + {"gray2", 5, 5, 5}, + {"gray20", 51, 51, 51}, + {"gray21", 54, 54, 54}, + {"gray22", 56, 56, 56}, + {"gray23", 59, 59, 59}, + {"gray24", 61, 61, 61}, + {"gray25", 64, 64, 64}, + {"gray26", 66, 66, 66}, + {"gray27", 69, 69, 69}, + {"gray28", 71, 71, 71}, + {"gray29", 74, 74, 74}, + {"gray3", 8, 8, 8}, + {"gray30", 77, 77, 77}, + {"gray31", 79, 79, 79}, + {"gray32", 82, 82, 82}, + {"gray33", 84, 84, 84}, + {"gray34", 87, 87, 87}, + {"gray35", 89, 89, 89}, + {"gray36", 92, 92, 92}, + {"gray37", 94, 94, 94}, + {"gray38", 97, 97, 97}, + {"gray39", 99, 99, 99}, + {"gray4", 10, 10, 10}, + {"gray40", 102, 102, 102}, + {"gray41", 105, 105, 105}, + {"gray42", 107, 107, 107}, + {"gray43", 110, 110, 110}, + {"gray44", 112, 112, 112}, + {"gray45", 115, 115, 115}, + {"gray46", 117, 117, 117}, + {"gray47", 120, 120, 120}, + {"gray48", 122, 122, 122}, + {"gray49", 125, 125, 125}, + {"gray5", 13, 13, 13}, + {"gray50", 127, 127, 127}, + {"gray51", 130, 130, 130}, + {"gray52", 133, 133, 133}, + {"gray53", 135, 135, 135}, + {"gray54", 138, 138, 138}, + {"gray55", 140, 140, 140}, + {"gray56", 143, 143, 143}, + {"gray57", 145, 145, 145}, + {"gray58", 148, 148, 148}, + {"gray59", 150, 150, 150}, + {"gray6", 15, 15, 15}, + {"gray60", 153, 153, 153}, + {"gray61", 156, 156, 156}, + {"gray62", 158, 158, 158}, + {"gray63", 161, 161, 161}, + {"gray64", 163, 163, 163}, + {"gray65", 166, 166, 166}, + {"gray66", 168, 168, 168}, + {"gray67", 171, 171, 171}, + {"gray68", 173, 173, 173}, + {"gray69", 176, 176, 176}, + {"gray7", 18, 18, 18}, + {"gray70", 179, 179, 179}, + {"gray71", 181, 181, 181}, + {"gray72", 184, 184, 184}, + {"gray73", 186, 186, 186}, + {"gray74", 189, 189, 189}, + {"gray75", 191, 191, 191}, + {"gray76", 194, 194, 194}, + {"gray77", 196, 196, 196}, + {"gray78", 199, 199, 199}, + {"gray79", 201, 201, 201}, + {"gray8", 20, 20, 20}, + {"gray80", 204, 204, 204}, + {"gray81", 207, 207, 207}, + {"gray82", 209, 209, 209}, + {"gray83", 212, 212, 212}, + {"gray84", 214, 214, 214}, + {"gray85", 217, 217, 217}, + {"gray86", 219, 219, 219}, + {"gray87", 222, 222, 222}, + {"gray88", 224, 224, 224}, + {"gray89", 227, 227, 227}, + {"gray9", 23, 23, 23}, + {"gray90", 229, 229, 229}, + {"gray91", 232, 232, 232}, + {"gray92", 235, 235, 235}, + {"gray93", 237, 237, 237}, + {"gray94", 240, 240, 240}, + {"gray95", 242, 242, 242}, + {"gray96", 245, 245, 245}, + {"gray97", 247, 247, 247}, + {"gray98", 250, 250, 250}, + {"gray99", 252, 252, 252}, + {"green", 0, 255, 0}, + {"green yellow", 173, 255, 47}, + {"green1", 0, 255, 0}, + {"green2", 0, 238, 0}, + {"green3", 0, 205, 0}, + {"green4", 0, 139, 0}, + {"grey", 190, 190, 190}, + {"grey0", 0, 0, 0}, + {"grey1", 3, 3, 3}, + {"grey10", 26, 26, 26}, + {"grey100", 255, 255, 255}, + {"grey11", 28, 28, 28}, + {"grey12", 31, 31, 31}, + {"grey13", 33, 33, 33}, + {"grey14", 36, 36, 36}, + {"grey15", 38, 38, 38}, + {"grey16", 41, 41, 41}, + {"grey17", 43, 43, 43}, + {"grey18", 46, 46, 46}, + {"grey19", 48, 48, 48}, + {"grey2", 5, 5, 5}, + {"grey20", 51, 51, 51}, + {"grey21", 54, 54, 54}, + {"grey22", 56, 56, 56}, + {"grey23", 59, 59, 59}, + {"grey24", 61, 61, 61}, + {"grey25", 64, 64, 64}, + {"grey26", 66, 66, 66}, + {"grey27", 69, 69, 69}, + {"grey28", 71, 71, 71}, + {"grey29", 74, 74, 74}, + {"grey3", 8, 8, 8}, + {"grey30", 77, 77, 77}, + {"grey31", 79, 79, 79}, + {"grey32", 82, 82, 82}, + {"grey33", 84, 84, 84}, + {"grey34", 87, 87, 87}, + {"grey35", 89, 89, 89}, + {"grey36", 92, 92, 92}, + {"grey37", 94, 94, 94}, + {"grey38", 97, 97, 97}, + {"grey39", 99, 99, 99}, + {"grey4", 10, 10, 10}, + {"grey40", 102, 102, 102}, + {"grey41", 105, 105, 105}, + {"grey42", 107, 107, 107}, + {"grey43", 110, 110, 110}, + {"grey44", 112, 112, 112}, + {"grey45", 115, 115, 115}, + {"grey46", 117, 117, 117}, + {"grey47", 120, 120, 120}, + {"grey48", 122, 122, 122}, + {"grey49", 125, 125, 125}, + {"grey5", 13, 13, 13}, + {"grey50", 127, 127, 127}, + {"grey51", 130, 130, 130}, + {"grey52", 133, 133, 133}, + {"grey53", 135, 135, 135}, + {"grey54", 138, 138, 138}, + {"grey55", 140, 140, 140}, + {"grey56", 143, 143, 143}, + {"grey57", 145, 145, 145}, + {"grey58", 148, 148, 148}, + {"grey59", 150, 150, 150}, + {"grey6", 15, 15, 15}, + {"grey60", 153, 153, 153}, + {"grey61", 156, 156, 156}, + {"grey62", 158, 158, 158}, + {"grey63", 161, 161, 161}, + {"grey64", 163, 163, 163}, + {"grey65", 166, 166, 166}, + {"grey66", 168, 168, 168}, + {"grey67", 171, 171, 171}, + {"grey68", 173, 173, 173}, + {"grey69", 176, 176, 176}, + {"grey7", 18, 18, 18}, + {"grey70", 179, 179, 179}, + {"grey71", 181, 181, 181}, + {"grey72", 184, 184, 184}, + {"grey73", 186, 186, 186}, + {"grey74", 189, 189, 189}, + {"grey75", 191, 191, 191}, + {"grey76", 194, 194, 194}, + {"grey77", 196, 196, 196}, + {"grey78", 199, 199, 199}, + {"grey79", 201, 201, 201}, + {"grey8", 20, 20, 20}, + {"grey80", 204, 204, 204}, + {"grey81", 207, 207, 207}, + {"grey82", 209, 209, 209}, + {"grey83", 212, 212, 212}, + {"grey84", 214, 214, 214}, + {"grey85", 217, 217, 217}, + {"grey86", 219, 219, 219}, + {"grey87", 222, 222, 222}, + {"grey88", 224, 224, 224}, + {"grey89", 227, 227, 227}, + {"grey9", 23, 23, 23}, + {"grey90", 229, 229, 229}, + {"grey91", 232, 232, 232}, + {"grey92", 235, 235, 235}, + {"grey93", 237, 237, 237}, + {"grey94", 240, 240, 240}, + {"grey95", 242, 242, 242}, + {"grey96", 245, 245, 245}, + {"grey97", 247, 247, 247}, + {"grey98", 250, 250, 250}, + {"grey99", 252, 252, 252}, + {"honeydew", 240, 255, 240}, + {"honeydew1", 240, 255, 240}, + {"honeydew2", 224, 238, 224}, + {"honeydew3", 193, 205, 193}, + {"honeydew4", 131, 139, 131}, + {"hot pink", 255, 105, 180}, + {"indian red", 205, 92, 92}, + {"ivory", 255, 255, 240}, + {"ivory1", 255, 255, 240}, + {"ivory2", 238, 238, 224}, + {"ivory3", 205, 205, 193}, + {"ivory4", 139, 139, 131}, + {"khaki", 240, 230, 140}, + {"khaki1", 255, 246, 143}, + {"khaki2", 238, 230, 133}, + {"khaki3", 205, 198, 115}, + {"khaki4", 139, 134, 78}, + {"lavender", 230, 230, 250}, + {"lavender blush", 255, 240, 245}, + {"lawn green", 124, 252, 0}, + {"lemon chiffon", 255, 250, 205}, + {"light blue", 173, 216, 230}, + {"light coral", 240, 128, 128}, + {"light cyan", 224, 255, 255}, + {"light goldenrod", 238, 221, 130}, + {"light goldenrod yellow", 250, 250, 210}, + {"light gray", 211, 211, 211}, + {"light green", 144, 238, 144}, + {"light grey", 211, 211, 211}, + {"light pink", 255, 182, 193}, + {"light salmon", 255, 160, 122}, + {"light sea green", 32, 178, 170}, + {"light sky blue", 135, 206, 250}, + {"light slate blue", 132, 112, 255}, + {"light slate gray", 119, 136, 153}, + {"light slate grey", 119, 136, 153}, + {"light steel blue", 176, 196, 222}, + {"light yellow", 255, 255, 224}, + {"lime green", 50, 205, 50}, + {"linen", 250, 240, 230}, + {"magenta", 255, 0, 255}, + {"magenta1", 255, 0, 255}, + {"magenta2", 238, 0, 238}, + {"magenta3", 205, 0, 205}, + {"magenta4", 139, 0, 139}, + {"maroon", 176, 48, 96}, + {"maroon1", 255, 52, 179}, + {"maroon2", 238, 48, 167}, + {"maroon3", 205, 41, 144}, + {"maroon4", 139, 28, 98}, + {"medium aquamarine", 102, 205, 170}, + {"medium blue", 0, 0, 205}, + {"medium orchid", 186, 85, 211}, + {"medium purple", 147, 112, 219}, + {"medium sea green", 60, 179, 113}, + {"medium slate blue", 123, 104, 238}, + {"medium spring green", 0, 250, 154}, + {"medium turquoise", 72, 209, 204}, + {"medium violet red", 199, 21, 133}, + {"midnight blue", 25, 25, 112}, + {"mint cream", 245, 255, 250}, + {"misty rose", 255, 228, 225}, + {"moccasin", 255, 228, 181}, + {"navajo white", 255, 222, 173}, + {"navy", 0, 0, 128}, + {"navy blue", 0, 0, 128}, + {"old lace", 253, 245, 230}, + {"olive drab", 107, 142, 35}, + {"orange", 255, 165, 0}, + {"orange red", 255, 69, 0}, + {"orange1", 255, 165, 0}, + {"orange2", 238, 154, 0}, + {"orange3", 205, 133, 0}, + {"orange4", 139, 90, 0}, + {"orchid", 218, 112, 214}, + {"orchid1", 255, 131, 250}, + {"orchid2", 238, 122, 233}, + {"orchid3", 205, 105, 201}, + {"orchid4", 139, 71, 137}, + {"pale goldenrod", 238, 232, 170}, + {"pale green", 152, 251, 152}, + {"pale turquoise", 175, 238, 238}, + {"pale violet red", 219, 112, 147}, + {"papaya whip", 255, 239, 213}, + {"peach puff", 255, 218, 185}, + {"peru", 205, 133, 63}, + {"pink", 255, 192, 203}, + {"pink1", 255, 181, 197}, + {"pink2", 238, 169, 184}, + {"pink3", 205, 145, 158}, + {"pink4", 139, 99, 108}, + {"plum", 221, 160, 221}, + {"plum1", 255, 187, 255}, + {"plum2", 238, 174, 238}, + {"plum3", 205, 150, 205}, + {"plum4", 139, 102, 139}, + {"powder blue", 176, 224, 230}, + {"purple", 160, 32, 240}, + {"purple1", 155, 48, 255}, + {"purple2", 145, 44, 238}, + {"purple3", 125, 38, 205}, + {"purple4", 85, 26, 139}, + {"red", 255, 0, 0}, + {"red1", 255, 0, 0}, + {"red2", 238, 0, 0}, + {"red3", 205, 0, 0}, + {"red4", 139, 0, 0}, + {"rosy brown", 188, 143, 143}, + {"royal blue", 65, 105, 225}, + {"saddle brown", 139, 69, 19}, + {"salmon", 250, 128, 114}, + {"salmon1", 255, 140, 105}, + {"salmon2", 238, 130, 98}, + {"salmon3", 205, 112, 84}, + {"salmon4", 139, 76, 57}, + {"sandy brown", 244, 164, 96}, + {"sea green", 46, 139, 87}, + {"seashell", 255, 245, 238}, + {"seashell1", 255, 245, 238}, + {"seashell2", 238, 229, 222}, + {"seashell3", 205, 197, 191}, + {"seashell4", 139, 134, 130}, + {"sienna", 160, 82, 45}, + {"sienna1", 255, 130, 71}, + {"sienna2", 238, 121, 66}, + {"sienna3", 205, 104, 57}, + {"sienna4", 139, 71, 38}, + {"sky blue", 135, 206, 235}, + {"slate blue", 106, 90, 205}, + {"slate gray", 112, 128, 144}, + {"slate grey", 112, 128, 144}, + {"snow", 255, 250, 250}, + {"snow1", 255, 250, 250}, + {"snow2", 238, 233, 233}, + {"snow3", 205, 201, 201}, + {"snow4", 139, 137, 137}, + {"spring green", 0, 255, 127}, + {"steel blue", 70, 130, 180}, + {"tan", 210, 180, 140}, + {"tan1", 255, 165, 79}, + {"tan2", 238, 154, 73}, + {"tan3", 205, 133, 63}, + {"tan4", 139, 90, 43}, + {"thistle", 216, 191, 216}, + {"thistle1", 255, 225, 255}, + {"thistle2", 238, 210, 238}, + {"thistle3", 205, 181, 205}, + {"thistle4", 139, 123, 139}, + {"tomato", 255, 99, 71}, + {"tomato1", 255, 99, 71}, + {"tomato2", 238, 92, 66}, + {"tomato3", 205, 79, 57}, + {"tomato4", 139, 54, 38}, + {"turquoise", 64, 224, 208}, + {"turquoise1", 0, 245, 255}, + {"turquoise2", 0, 229, 238}, + {"turquoise3", 0, 197, 205}, + {"turquoise4", 0, 134, 139}, + {"violet", 238, 130, 238}, + {"violet red", 208, 32, 144}, + {"wheat", 245, 222, 179}, + {"wheat1", 255, 231, 186}, + {"wheat2", 238, 216, 174}, + {"wheat3", 205, 186, 150}, + {"wheat4", 139, 126, 102}, + {"white", 255, 255, 255}, + {"white smoke", 245, 245, 245}, + {"yellow", 255, 255, 0}, + {"yellow green", 154, 205, 50}, + {"yellow1", 255, 255, 0}, + {"yellow2", 238, 238, 0}, + {"yellow3", 205, 205, 0}, + {"yellow4", 139, 139, 0}, +}; + +BGD_EXPORT_DATA_PROT gdColorMap GD_COLOR_MAP_X11 = { + sizeof(GD_COLOR_MAP_X11_ENTRIES) / sizeof(gdColorMapEntry), + (gdColorMapEntry *)GD_COLOR_MAP_X11_ENTRIES}; + +/* + Function: gdColorMapLookup +*/ +BGD_DECLARE(int) +gdColorMapLookup(const gdColorMap color_map, const char *color_name, int *r, + int *g, int *b) { + gdColorMapEntry *entries = color_map.entries; + int low = 0; + int high = color_map.num_entries - 1; + while (low <= high) { + int i = (low + high) / 2; + int result = strcmp(color_name, entries[i].color_name); + if (result == 0) { + *r = entries[i].red; + *g = entries[i].green; + *b = entries[i].blue; + return 1; + } else if (result < 0) { + high = i - 1; + } else { + low = i + 1; + } + } + return 0; +} diff --git a/ext/gd/libgd/gd_color_map.h b/ext/gd/libgd/gd_color_map.h new file mode 100644 index 000000000000..5bfbedc32bbd --- /dev/null +++ b/ext/gd/libgd/gd_color_map.h @@ -0,0 +1,32 @@ +#ifndef GD_COLOR_MAP_H +#define GD_COLOR_MAP_H 1 + +#include "gd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *color_name; + int red; + int green; + int blue; +} gdColorMapEntry; + +typedef struct { + int num_entries; + gdColorMapEntry *entries; +} gdColorMap; + +extern BGD_EXPORT_DATA_PROT gdColorMap GD_COLOR_MAP_X11; + +BGD_DECLARE(int) +gdColorMapLookup(const gdColorMap color_map, const char *color_name, int *r, + int *g, int *b); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/gd/libgd/gd_color_match.c b/ext/gd/libgd/gd_color_match.c index 19fdfe6d031d..682a9801d4e3 100644 --- a/ext/gd/libgd/gd_color_match.c +++ b/ext/gd/libgd/gd_color_match.c @@ -1,14 +1,16 @@ #include "gd.h" #include "gdhelpers.h" +#include #include "gd_intern.h" #include "php.h" -/* bring the palette colors in im2 to be closer to im1 - * +/* + Function: gdImageColorMatch + + Bring the palette colors in im2 to be closer to im1. */ -int gdImageColorMatch (gdImagePtr im1, gdImagePtr im2) -{ +BGD_DECLARE(int) gdImageColorMatch(gdImagePtr im1, gdImagePtr im2) { unsigned long *buf; /* stores our calculations */ unsigned long *bp; /* buf ptr */ int color, rgb; @@ -27,8 +29,8 @@ int gdImageColorMatch (gdImagePtr im1, gdImagePtr im2) if (im2->colorsTotal<1) { return -4; /* At least 1 color must be allocated */ } - - buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * gdMaxColors, 0); + // gdMaxColors == 255 + buf = (unsigned long *)gdMalloc(sizeof(unsigned long) * 5 * gdMaxColors); memset( buf, 0, sizeof(unsigned long) * 5 * gdMaxColors ); for (x=0; xsx; x++) { diff --git a/ext/gd/libgd/gd_compositor.c b/ext/gd/libgd/gd_compositor.c new file mode 100644 index 000000000000..4f559f78b5ab --- /dev/null +++ b/ext/gd/libgd/gd_compositor.c @@ -0,0 +1,304 @@ +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd_vector2d_private.h" +#include "gd_compositor.h" + +typedef enum { + FACTOR_ZERO, FACTOR_ONE, FACTOR_SRC_ALPHA, FACTOR_DST_ALPHA, + FACTOR_ONE_MINUS_SRC_ALPHA, FACTOR_ONE_MINUS_DST_ALPHA, + FACTOR_SATURATE +} FactorKind; + +typedef struct { FactorKind src, dst; } Factors; + +static const Factors porter_duff[] = { + {FACTOR_ZERO, FACTOR_ZERO}, + {FACTOR_ONE, FACTOR_ZERO}, + {FACTOR_ONE, FACTOR_ONE_MINUS_SRC_ALPHA}, + {FACTOR_DST_ALPHA, FACTOR_ZERO}, + {FACTOR_ONE_MINUS_DST_ALPHA, FACTOR_ZERO}, + {FACTOR_DST_ALPHA, FACTOR_ONE_MINUS_SRC_ALPHA}, + {FACTOR_ZERO, FACTOR_ONE}, + {FACTOR_ONE_MINUS_DST_ALPHA, FACTOR_ONE}, + {FACTOR_ZERO, FACTOR_SRC_ALPHA}, + {FACTOR_ZERO, FACTOR_ONE_MINUS_SRC_ALPHA}, + {FACTOR_ONE_MINUS_DST_ALPHA, FACTOR_SRC_ALPHA}, + {FACTOR_ONE_MINUS_DST_ALPHA, FACTOR_ONE_MINUS_SRC_ALPHA}, + {FACTOR_ONE, FACTOR_ONE}, + {FACTOR_SATURATE, FACTOR_ONE} +}; + +static float clamp01(float v) +{ + return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v); +} + +static gdPremulPixelF clamp_pixel(gdPremulPixelF p) +{ + p.a = clamp01(p.a); + p.r = fminf(clamp01(p.r), p.a); + p.g = fminf(clamp01(p.g), p.a); + p.b = fminf(clamp01(p.b), p.a); + return p; +} + +static gdPremulPixelF scale_pixel(gdPremulPixelF p, float scale) +{ + p.r *= scale; p.g *= scale; p.b *= scale; p.a *= scale; + return p; +} + +static gdPremulPixelF lerp_pixel(gdPremulPixelF a, gdPremulPixelF b, float t) +{ + gdPremulPixelF r; + t = clamp01(t); + r.r = a.r + (b.r - a.r) * t; + r.g = a.g + (b.g - a.g) * t; + r.b = a.b + (b.b - a.b) * t; + r.a = a.a + (b.a - a.a) * t; + return clamp_pixel(r); +} + +static float factor(FactorKind kind, float sa, float da) +{ + switch (kind) { + case FACTOR_ZERO: return 0.0f; + case FACTOR_ONE: return 1.0f; + case FACTOR_SRC_ALPHA: return sa; + case FACTOR_DST_ALPHA: return da; + case FACTOR_ONE_MINUS_SRC_ALPHA: return 1.0f - sa; + case FACTOR_ONE_MINUS_DST_ALPHA: return 1.0f - da; + case FACTOR_SATURATE: + return sa > 0.0f ? fminf(1.0f, (1.0f - da) / sa) : 0.0f; + } + return 0.0f; +} + +static gdPremulPixelF composite_porter_duff(gdCompositeOperator op, + gdPremulPixelF s, + gdPremulPixelF d) +{ + const Factors f = porter_duff[op]; + const float sf = factor(f.src, s.a, d.a); + const float df = factor(f.dst, s.a, d.a); + gdPremulPixelF r; + r.r = s.r * sf + d.r * df; + r.g = s.g * sf + d.g * df; + r.b = s.b * sf + d.b * df; + r.a = s.a * sf + d.a * df; + return clamp_pixel(r); +} + +static float blend_channel(gdCompositeOperator op, float s, float d) +{ + switch (op) { + case GD_OP_MULTIPLY: return s * d; + case GD_OP_SCREEN: return s + d - s * d; + case GD_OP_OVERLAY: + return d <= 0.5f ? 2.0f * s * d + : 1.0f - 2.0f * (1.0f - s) * (1.0f - d); + case GD_OP_DARKEN: return fminf(s, d); + case GD_OP_LIGHTEN: return fmaxf(s, d); + case GD_OP_COLOR_DODGE: + return s >= 1.0f ? 1.0f : fminf(1.0f, d / (1.0f - s)); + case GD_OP_COLOR_BURN: + return s <= 0.0f ? 0.0f : 1.0f - fminf(1.0f, (1.0f - d) / s); + case GD_OP_HARD_LIGHT: + return s <= 0.5f ? 2.0f * s * d + : 1.0f - 2.0f * (1.0f - s) * (1.0f - d); + case GD_OP_SOFT_LIGHT: + if (s <= 0.5f) + return d - (1.0f - 2.0f * s) * d * (1.0f - d); + else { + float g = d <= 0.25f ? ((16.0f * d - 12.0f) * d + 4.0f) * d + : sqrtf(d); + return d + (2.0f * s - 1.0f) * (g - d); + } + case GD_OP_DIFFERENCE: return fabsf(d - s); + case GD_OP_EXCLUSION: return s + d - 2.0f * s * d; + default: return s; + } +} + +typedef struct { float r, g, b; } StraightColor; + +static float color_lum(StraightColor c) +{ + return 0.30f * c.r + 0.59f * c.g + 0.11f * c.b; +} + +static float color_sat(StraightColor c) +{ + return fmaxf(c.r, fmaxf(c.g, c.b)) - fminf(c.r, fminf(c.g, c.b)); +} + +static StraightColor clip_color(StraightColor c) +{ + float l = color_lum(c); + float n = fminf(c.r, fminf(c.g, c.b)); + float x = fmaxf(c.r, fmaxf(c.g, c.b)); + if (n < 0.0f) { + float k = l / (l - n); + c.r = l + (c.r - l) * k; + c.g = l + (c.g - l) * k; + c.b = l + (c.b - l) * k; + } + x = fmaxf(c.r, fmaxf(c.g, c.b)); + if (x > 1.0f) { + float k = (1.0f - l) / (x - l); + c.r = l + (c.r - l) * k; + c.g = l + (c.g - l) * k; + c.b = l + (c.b - l) * k; + } + return c; +} + +static StraightColor set_lum(StraightColor c, float l) +{ + float d = l - color_lum(c); + c.r += d; c.g += d; c.b += d; + return clip_color(c); +} + +static StraightColor set_sat(StraightColor c, float s) +{ + float *v[3] = {&c.r, &c.g, &c.b}; + float *tmp; + if (*v[0] > *v[1]) { tmp = v[0]; v[0] = v[1]; v[1] = tmp; } + if (*v[1] > *v[2]) { tmp = v[1]; v[1] = v[2]; v[2] = tmp; } + if (*v[0] > *v[1]) { tmp = v[0]; v[0] = v[1]; v[1] = tmp; } + if (*v[2] > *v[0]) { + *v[1] = (*v[1] - *v[0]) * s / (*v[2] - *v[0]); + *v[2] = s; + } else { + *v[1] = *v[2] = 0.0f; + } + *v[0] = 0.0f; + return c; +} + +static StraightColor blend_hsl(gdCompositeOperator op, StraightColor s, + StraightColor d) +{ + switch (op) { + case GD_OP_HSL_HUE: return set_lum(set_sat(s, color_sat(d)), color_lum(d)); + case GD_OP_HSL_SATURATION: return set_lum(set_sat(d, color_sat(s)), color_lum(d)); + case GD_OP_HSL_COLOR: return set_lum(s, color_lum(d)); + case GD_OP_HSL_LUMINOSITY: return set_lum(d, color_lum(s)); + default: return s; + } +} + +static gdPremulPixelF composite_blend(gdCompositeOperator op, + gdPremulPixelF s, gdPremulPixelF d) +{ + StraightColor cs = {0, 0, 0}, cd = {0, 0, 0}, b; + gdPremulPixelF r; + if (s.a > 0.0f) { cs.r = s.r/s.a; cs.g = s.g/s.a; cs.b = s.b/s.a; } + if (d.a > 0.0f) { cd.r = d.r/d.a; cd.g = d.g/d.a; cd.b = d.b/d.a; } + if (op >= GD_OP_HSL_HUE) + b = blend_hsl(op, cs, cd); + else { + b.r = blend_channel(op, cs.r, cd.r); + b.g = blend_channel(op, cs.g, cd.g); + b.b = blend_channel(op, cs.b, cd.b); + } + r.a = s.a + d.a * (1.0f - s.a); + r.r = (1.0f-d.a)*s.r + (1.0f-s.a)*d.r + s.a*d.a*b.r; + r.g = (1.0f-d.a)*s.g + (1.0f-s.a)*d.g + s.a*d.a*b.g; + r.b = (1.0f-d.a)*s.b + (1.0f-s.a)*d.b + s.a*d.a*b.b; + return clamp_pixel(r); +} + +int gdCompositeOperatorIsValid(gdCompositeOperator op) +{ + return op >= GD_OP_CLEAR && op < GD_OP_COUNT; +} + +int gdCompositeOperatorIsUnbounded(gdCompositeOperator op) +{ + return op == GD_OP_IN || op == GD_OP_OUT || + op == GD_OP_DEST_IN || op == GD_OP_DEST_ATOP; +} + +gdPremulPixelF gdCompositePixel(gdCompositeOperator op, gdPremulPixelF src, + gdPremulPixelF dst, float coverage) +{ + gdPremulPixelF result; + coverage = clamp01(coverage); + src = clamp_pixel(src); + dst = clamp_pixel(dst); + if (!gdCompositeOperatorIsValid(op)) + return dst; + if (op == GD_OP_CLEAR || op == GD_OP_SOURCE) { + result = composite_porter_duff(op, src, dst); + return lerp_pixel(dst, result, coverage); + } + src = scale_pixel(src, coverage); + if (op <= GD_OP_SATURATE) + return composite_porter_duff(op, src, dst); + return composite_blend(op, src, dst); +} + +void gdCompositeSpan(gdCompositeOperator op, const gdPremulPixelF *src, + ptrdiff_t src_stride, gdPremulPixelF *dst, + const float *coverage, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) { + float c = coverage ? coverage[i] : 1.0f; + dst[i] = gdCompositePixel(op, *src, dst[i], c); + src = (const gdPremulPixelF *)((const char *)src + src_stride); + } +} + +gdPremulPixelF gdCompositePixelFromArgb32(uint32_t p) +{ + gdPremulPixelF r; + r.a = ((p >> 24) & 255) / 255.0f; + r.r = ((p >> 16) & 255) / 255.0f; + r.g = ((p >> 8) & 255) / 255.0f; + r.b = (p & 255) / 255.0f; + return clamp_pixel(r); +} + +uint32_t gdCompositePixelToArgb32(gdPremulPixelF p) +{ + uint32_t a, r, g, b; + p = clamp_pixel(p); + a = (uint32_t)floorf(p.a * 255.0f + 0.5f); + r = (uint32_t)floorf(p.r * 255.0f + 0.5f); + g = (uint32_t)floorf(p.g * 255.0f + 0.5f); + b = (uint32_t)floorf(p.b * 255.0f + 0.5f); + return (a << 24) | (r << 16) | (g << 8) | b; +} + +gdPremulPixelF gdCompositePixelFromGd(int p) +{ + float a = (gdAlphaMax - gdTrueColorGetAlpha(p)) / (float)gdAlphaMax; + gdPremulPixelF r; + r.a = a; + r.r = gdTrueColorGetRed(p) / 255.0f * a; + r.g = gdTrueColorGetGreen(p) / 255.0f * a; + r.b = gdTrueColorGetBlue(p) / 255.0f * a; + return clamp_pixel(r); +} + +int gdCompositePixelToGd(gdPremulPixelF p) +{ + int a, r = 0, g = 0, b = 0; + p = clamp_pixel(p); + a = (int)floorf((1.0f - p.a) * gdAlphaMax + 0.5f); + if (p.a > 0.0f) { + r = (int)floorf(p.r / p.a * 255.0f + 0.5f); + g = (int)floorf(p.g / p.a * 255.0f + 0.5f); + b = (int)floorf(p.b / p.a * 255.0f + 0.5f); + } + return gdTrueColorAlpha(r, g, b, a); +} diff --git a/ext/gd/libgd/gd_compositor.h b/ext/gd/libgd/gd_compositor.h new file mode 100644 index 000000000000..dac7b36b1c58 --- /dev/null +++ b/ext/gd/libgd/gd_compositor.h @@ -0,0 +1,24 @@ +#ifndef GD_COMPOSITOR_H +#define GD_COMPOSITOR_H + +#include +#include +#include "gd_vector2d_private.h" + +typedef struct { + float r, g, b, a; +} gdPremulPixelF; + +int gdCompositeOperatorIsValid(gdCompositeOperator op); +int gdCompositeOperatorIsUnbounded(gdCompositeOperator op); +gdPremulPixelF gdCompositePixel(gdCompositeOperator op, gdPremulPixelF src, + gdPremulPixelF dst, float coverage); +void gdCompositeSpan(gdCompositeOperator op, const gdPremulPixelF *src, + ptrdiff_t src_stride, gdPremulPixelF *dst, + const float *coverage, size_t n); +gdPremulPixelF gdCompositePixelFromArgb32(uint32_t pixel); +uint32_t gdCompositePixelToArgb32(gdPremulPixelF pixel); +gdPremulPixelF gdCompositePixelFromGd(int pixel); +int gdCompositePixelToGd(gdPremulPixelF pixel); + +#endif diff --git a/ext/gd/libgd/gd_crop.c b/ext/gd/libgd/gd_crop.c index 676545c4dbc9..ff594b6b03f1 100644 --- a/ext/gd/libgd/gd_crop.c +++ b/ext/gd/libgd/gd_crop.c @@ -1,19 +1,18 @@ /** - * Title: Crop + * File: Cropping * - * A couple of functions to crop images, automatically (auto detection of - * the borders color), using a given color (with or without tolerance) - * or using a selection. + * Crop an image * - * The threshold method works relatively well but it can be improved. - * Maybe L*a*b* and Delta-E will give better results (and a better - * granularity). + * Some functions to crop images, automatically (auto detection of the border + * color), using a given color (with or without tolerance) or using a given + * rectangle. * * Example: * (start code) * im2 = gdImageAutoCrop(im, GD_CROP_SIDES); * if (im2) { - + * gdImageDestroy(im); // unless you need the original image subsequently + * // do something with the cropped image * } * gdImageDestroy(im2); * (end code) @@ -24,25 +23,28 @@ #include #include "gd.h" +#include "gd_color.h" +#include static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color); -static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold); /** * Function: gdImageCrop - * Crops the src image using the area defined by the rectangle. - * The result is returned as a new image. * + * Crop an image to a given rectangle * * Parameters: - * src - Source image - * crop - Rectangular region to crop + * src - The image. + * crop - The cropping rectangle, see . * * Returns: - * on success or NULL + * The newly created cropped image, or NULL on failure. + * + * See also: + * - + * - */ -gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop) -{ +BGD_DECLARE(gdImagePtr) gdImageCrop(gdImagePtr src, const gdRect *crop) { gdImagePtr dst; int alphaBlendingFlag; @@ -51,7 +53,8 @@ gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop) } else { dst = gdImageCreate(crop->width, crop->height); } - if (!dst) return NULL; + if (!dst) + return NULL; alphaBlendingFlag = dst->alphaBlendingFlag; gdImageAlphaBlending(dst, gdEffectReplace); gdImageCopy(dst, src, 0, 0, crop->x, crop->y, crop->width, crop->height); @@ -61,28 +64,30 @@ gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop) } /** - * Function: gdImageAutoCrop - * Automatic croping of the src image using the given mode - * (see ) + * Function: gdImageCropAuto + * + * Crop an image automatically * + * This function detects the cropping area according to the given _mode_. * * Parameters: - * im - Source image - * mode - crop mode + * im - The image. + * mode - The cropping mode, see . * * Returns: - * on success or NULL + * The newly created cropped image, or NULL on failure. * * See also: - * + * - + * - */ -gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode) -{ +BGD_DECLARE(gdImagePtr) +gdImageCropAuto(gdImagePtr im, const unsigned int mode) { const int width = gdImageSX(im); const int height = gdImageSY(im); int x,y; - int color, match; + int color; gdRect crop; crop.x = 0; @@ -113,78 +118,86 @@ gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode) break; } - /* TODO: Add gdImageGetRowPtr and works with ptr at the row level - * for the true color and palette images - * new formats will simply work with ptr - */ - match = 1; - for (y = 0; match && y < height; y++) { - for (x = 0; match && x < width; x++) { - int c2 = gdImageGetPixel(im, x, y); - match = (color == c2); + for (x = 0, y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + if (color != gdImageGetPixel(im, x, y)) { + goto break1; } } + } +break1: /* Whole image would be cropped > bye */ - if (match) { + if (y == height && x == width) { return NULL; } - crop.y = y - 1; + crop.y = y; - match = 1; - for (y = height - 1; match && y >= 0; y--) { - for (x = 0; match && x < width; x++) { - match = (color == gdImageGetPixel(im, x,y)); + for (y = height - 1; y >= 0; y--) { + for (x = 0; x < width; x++) { + if (color != gdImageGetPixel(im, x, y)) { + goto break2; } } - crop.height = y - crop.y + 2; + } +break2: - match = 1; - for (x = 0; match && x < width; x++) { - for (y = 0; match && y < crop.y + crop.height; y++) { - match = (color == gdImageGetPixel(im, x,y)); + crop.height = y - crop.y + 1; + + for (x = 0; x < width; x++) { + for (y = crop.y; y < crop.y + crop.height; y++) { + if (color != gdImageGetPixel(im, x, y)) { + goto break3; } } - crop.x = x - 1; + } +break3: - match = 1; - for (x = width - 1; match && x >= 0; x--) { - for (y = 0; match && y < crop.y + crop.height; y++) { - match = (color == gdImageGetPixel(im, x,y)); + crop.x = x; + + for (x = width - 1; x >= 0; x--) { + for (y = crop.y; y < crop.y + crop.height; y++) { + if (color != gdImageGetPixel(im, x, y)) { + goto break4; } } - crop.width = x - crop.x + 2; + } +break4: + + crop.width = x - crop.x + 1; return gdImageCrop(im, &crop); } -/*TODOs: Implement DeltaE instead, way better perceptual differences */ + /** - * Function: gdImageThresholdCrop - * Crop an image using a given color. The threshold argument defines - * the tolerance to be used while comparing the image color and the - * color to crop. The method used to calculate the color difference - * is based on the color distance in the RGB(a) cube. + * Function: gdImageCropThreshold + * + * Crop an image using a given color * + * The _threshold_ defines the tolerance to be used while comparing the image + * color and the color to crop. The method used to calculate the color + * difference is based on the color distance in the RGB(A) cube. * * Parameters: - * im - Source image - * color - color to crop - * threshold - tolerance (0..100) + * im - The image. + * color - The crop color. + * threshold - The crop threshold. * * Returns: - * on success or NULL + * The newly created cropped image, or NULL on failure. * * See also: - * , or + * - + * - */ -gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold) -{ +BGD_DECLARE(gdImagePtr) +gdImageCropThreshold(gdImagePtr im, const unsigned int color, + const float threshold) { const int width = gdImageSX(im); const int height = gdImageSY(im); int x,y; - int match; gdRect crop; crop.x = 0; @@ -192,56 +205,68 @@ gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const f crop.width = 0; crop.height = 0; - /* Pierre: crop everything sounds bad */ + /* To crop everything sounds bad */ if (threshold > 100.0) { return NULL; } - if (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im)) { + if (!gdImageTrueColor(im) && + color >= (unsigned int)gdImageColorsTotal(im)) { return NULL; } - /* TODO: Add gdImageGetRowPtr and works with ptr at the row level - * for the true color and palette images - * new formats will simply work with ptr - */ - match = 1; - for (y = 0; match && y < height; y++) { - for (x = 0; match && x < width; x++) { - match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0; + for (x = 0, y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + if (!gdColorMatch(im, color, gdImageGetPixel(im, x, y), + threshold)) { + goto break1; } } + } +break1: /* Whole image would be cropped > bye */ - if (match) { + if (y == height && x == width) { return NULL; } - crop.y = y - 1; + crop.y = y; - match = 1; - for (y = height - 1; match && y >= 0; y--) { - for (x = 0; match && x < width; x++) { - match = (gdColorMatch(im, color, gdImageGetPixel(im, x, y), threshold)) > 0; + for (y = height - 1; y >= 0; y--) { + for (x = 0; x < width; x++) { + if (!gdColorMatch(im, color, gdImageGetPixel(im, x, y), + threshold)) { + goto break2; } } - crop.height = y - crop.y + 2; + } +break2: - match = 1; - for (x = 0; match && x < width; x++) { - for (y = 0; match && y < crop.y + crop.height; y++) { - match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0; + crop.height = y - crop.y + 1; + + for (x = 0; x < width; x++) { + for (y = crop.y; y < crop.y + crop.height; y++) { + if (!gdColorMatch(im, color, gdImageGetPixel(im, x, y), + threshold)) { + goto break3; } } - crop.x = x - 1; + } +break3: - match = 1; - for (x = width - 1; match && x >= 0; x--) { - for (y = 0; match && y < crop.y + crop.height; y++) { - match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0; + crop.x = x; + + for (x = width - 1; x >= 0; x--) { + for (y = crop.y; y < crop.y + crop.height; y++) { + if (!gdColorMatch(im, color, gdImageGetPixel(im, x, y), + threshold)) { + goto break4; } } - crop.width = x - crop.x + 2; + } +break4: + + crop.width = x - crop.x + 1; return gdImageCrop(im, &crop); } @@ -252,8 +277,7 @@ gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const f * - if two are equal. * - Last solution: average the colors */ -static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color) -{ +static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color) { const int tl = gdImageGetPixel(im, 0, 0); const int tr = gdImageGetPixel(im, gdImageSX(im) - 1, 0); const int bl = gdImageGetPixel(im, 0, gdImageSY(im) -1); @@ -283,35 +307,19 @@ static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color) } else { register int r,b,g,a; - r = (int)(0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4); - g = (int)(0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4); - b = (int)(0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4); - a = (int)(0.5f + (gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / 4); + r = (2 + gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + + gdImageRed(im, br)) / + 4; + g = (2 + gdImageGreen(im, tl) + gdImageGreen(im, tr) + + gdImageGreen(im, bl) + gdImageGreen(im, br)) / + 4; + b = (2 + gdImageBlue(im, tl) + gdImageBlue(im, tr) + + gdImageBlue(im, bl) + gdImageBlue(im, br)) / + 4; + a = (2 + gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / + 4; *color = gdImageColorClosestAlpha(im, r, g, b, a); return 0; } } - -static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold) -{ - const int dr = gdImageRed(im, col1) - gdImageRed(im, col2); - const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2); - const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2); - const int da = gdImageAlpha(im, col1) - gdImageAlpha(im, col2); - const int dist = dr * dr + dg * dg + db * db + da * da; - - return (100.0 * dist / 195075) < threshold; -} - -/* - * To be implemented when we have more image formats. - * Buffer like gray8 gray16 or rgb8 will require some tweak - * and can be done in this function (called from the autocrop - * function. (Pierre) - */ -#if 0 -static int colors_equal (const int col1, const in col2) -{ - -} -#endif diff --git a/ext/gd/libgd/gd_draw.c b/ext/gd/libgd/gd_draw.c new file mode 100644 index 000000000000..41eb833720ed --- /dev/null +++ b/ext/gd/libgd/gd_draw.c @@ -0,0 +1,421 @@ + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd_intern.h" + +/* 2.03: don't include zlib here or we can't build without PNG */ +#include "gd_vector2d_private.h" +#include "gdhelpers.h" +#include "gd_color.h" +#include "gd_errors.h" +#include "gd_path.h" +#include "gd_span_rle.h" +#include "gd_draw_blend.h" +#include "gd_path_matrix.h" +#include "gd_path_dash.h" +#include "gd_compositor.h" + +/* Conversion helpers: legacy gdImage truecolor <-> premultiplied ARGB32 */ +static inline uint32_t gdcolor_to_premul(int gdcolor) +{ + return gdCompositePixelToArgb32(gdCompositePixelFromGd(gdcolor)); +} + +static inline int premul_to_gdcolor(uint32_t pm) +{ + return gdCompositePixelToGd(gdCompositePixelFromArgb32(pm)); +} + +BGD_DECLARE(void) +gdContextSetSourceRgba(gdContextPtr context, double r, double g, double b, double a) +{ + gdPaintPtr source = gdPaintCreateRgba(r, g, b, a); + gdContextSetSource(context, source); + gdPaintDestroy(source); +} + +BGD_DECLARE(void) +gdContextSetSourceRgb(gdContextPtr context, double r, double g, double b) +{ + gdPaintPtr source = gdPaintCreateRgba(r, g, b, 1.0); + gdContextSetSource(context, source); + gdPaintDestroy(source); +} + +GD_VECTOR2D_INTERNAL void +gdContextSetSourceSurface(gdContextPtr context, gdSurfacePtr surface, double x, double y) +{ + gdPaintSetSourceSurface(context, surface, x, y); +} + +BGD_DECLARE(void) +gdContextSetSourceImage(gdContextPtr context, gdImagePtr image, double x, double y) +{ + gdPathPatternPtr pattern; + gdPaintPtr paint; + gdPathMatrix matrix; + + if (!context || !image) + return; + pattern = gdPathPatternCreateForImage(image); + if (!pattern) + return; + gdPathMatrixInitTranslate(&matrix, x, y); + gdPathPatternSetMatrix(pattern, &matrix); + paint = gdPaintCreateFromPattern(pattern); + gdPathPatternDestroy(pattern); + if (!paint) + return; + gdContextSetSource(context, paint); + gdPaintDestroy(paint); +} + +BGD_DECLARE(void) +gdContextSetOperator(gdContextPtr context, gdCompositeOperator op) +{ + if (!context) + return; + if (!gdCompositeOperatorIsValid(op)) { + gd_error("gdContextSetOperator: invalid operator %d.\n", (int)op); + return; + } + context->state->op = op; +} + +BGD_DECLARE(void) +gdContextSetOpacity(gdContextPtr context, double opacity) +{ + if (!context || !isfinite(opacity)) + return; + context->state->opacity = CLAMP(opacity, 0.0, 1.0); +} + +BGD_DECLARE(void) +gdContextNewPath(gdContextPtr context) +{ + gdPathClear(context->path); +} + +BGD_DECLARE(void) gdContextAppendPath(gdContextPtr cr, gdPathPtr source) +{ + gdPathAppendPath(cr->path, source); +} + +GD_VECTOR2D_INTERNAL gdContextPtr +gdContextCreate(gdSurfacePtr surface) +{ + gdContextPtr context = gdMalloc(sizeof(gdContext)); + if (!context) + { + return NULL; + } + context->state = gdStateCreate(); + if (!context->state) + { + goto failState; + } + context->path = gdPathCreate(); + if (!context->path) + { + goto failPath; + } + context->rle = gdSpanRleCreate(); + if (!context->rle) + { + goto failRle; + } + context->ref = 1; + context->surface = gdSurfaceAddRef(surface); + context->image = NULL; + context->clippath= NULL; + context->clip.x = 0.0; + context->clip.y = 0.0; + context->clip.w = surface->width; + context->clip.h = surface->height; + return context; +failRle: + gdFree(context->path); +failPath: + gdFree(context->state); +failState: + gdFree(context); + return NULL; +} + +BGD_DECLARE(gdContextPtr) +gdContextCreateForImage(gdImagePtr im) +{ + if (!im || !im->trueColor) + { + return NULL; + } + + gdSurfacePtr scratch = gdSurfaceCreate(im->sx, im->sy, GD_SURFACE_ARGB32); + if (!scratch) + { + return NULL; + } + + for (int y = 0; y < im->sy; y++) + { + uint32_t *dst = (uint32_t *)(scratch->data + y * scratch->stride); + for (int x = 0; x < im->sx; x++) + { + dst[x] = gdcolor_to_premul(im->tpixels[y][x]); + } + } + + gdContextPtr ctx = gdContextCreate(scratch); + if (!ctx) + { + gdSurfaceDestroy(scratch); + return NULL; + } + gdSurfaceDestroy(scratch); + + ctx->image = im; + ctx->imageOwned = 0; + return ctx; +} + +BGD_DECLARE(void) +gdContextFlushImage(gdContextPtr ctx) +{ + if (!ctx || !ctx->image) + { + return; + } + gdImagePtr im = ctx->image; + gdSurfacePtr scratch = ctx->surface; + + for (int y = 0; y < im->sy; y++) + { + uint32_t *src = (uint32_t *)(scratch->data + y * scratch->stride); + for (int x = 0; x < im->sx; x++) + { + im->tpixels[y][x] = premul_to_gdcolor(src[x]); + } + } +} + +BGD_DECLARE(gdImagePtr) +gdContextGetImage(gdContextPtr ctx) +{ + return ctx ? ctx->image : NULL; +} + +BGD_DECLARE(void) +gdContextStrokePreserve(gdContextPtr context) +{ + gdStatePtr state = context->state; + gdSpanRleClear(context->rle); + gdSpanRleRasterize(context->rle, context->path, &state->matrix, &context->clip, &state->stroke, gdFillRuleNonZero); + if (!gdCompositeOperatorIsUnbounded(state->op)) + gdSpanRlePathClip(context->rle, state->clippath); + gdPathBlend(context, context->rle); +} + +BGD_DECLARE(void) +gdContextFillPreserve(gdContextPtr context) +{ + gdStatePtr state = context->state; + gdSpanRleClear(context->rle); + //gdPathDumpPathTransform(context->path, NULL); + gdSpanRleRasterize(context->rle, context->path, &state->matrix, &context->clip, NULL, state->winding); + if (!gdCompositeOperatorIsUnbounded(state->op)) + gdSpanRlePathClip(context->rle, state->clippath); + gdPathBlend(context, context->rle); +} + +BGD_DECLARE(void) +gdContextFill(gdContextPtr context) +{ + gdContextFillPreserve(context); + gdContextNewPath(context); +} + +BGD_DECLARE(void) +gdContextClipPreserve(gdContextPtr context) +{ + unsigned int context_path_size; + const gdPathPtr currentPath = context->path; + gdStatePtr state = context->state; + context_path_size = gdArrayNumElements(¤tPath->elements); + + if (context_path_size == 0) + return; + + if (state->clippath) + { + gdSpanRleClear(context->rle); + gdSpanRleRasterize(context->rle, context->path, &state->matrix, &context->clip, NULL, state->winding); + gdSpanRlePathClip(state->clippath, context->rle); + } else { + state->clippath = gdSpanRleCreate(); + gdSpanRleRasterize(state->clippath, context->path, &state->matrix, &context->clip, NULL, state->winding); + } +} + +BGD_DECLARE(void) +gdContextPaint(gdContextPtr context) +{ + gdSpanRlePtr rle; + gdStatePtr state = context->state; + if(state->clippath==NULL && context->clippath == NULL) { + gdPathPtr path = gdPathCreate(); + gdPathAddRectangle(path, context->clip.x, context->clip.y, context->clip.w, context->clip.h); + context->clippath = gdSpanRleCreate(); + gdSpanRleRasterize(context->clippath, path, &state->matrix, &context->clip, NULL, gdFillRuleNonZero); + gdPathDestroy(path); + } + rle = state->clippath ? state->clippath : context->clippath; + gdPathBlend(context, rle); +} + +BGD_DECLARE(void) +gdContextClip(gdContextPtr context) +{ + gdContextClipPreserve(context); + gdContextNewPath(context); +} + +BGD_DECLARE(void) +gdContextDestroy(gdContextPtr context) +{ + if (context == NULL) + return; + context->ref--; + if (context->ref == 0) + { + if (context->image) + { + gdContextFlushImage(context); + } + gdSurfaceDestroy(context->surface); + gdPathDestroy(context->path); + gdStateDestroy(context->state); + gdSpanRleDestroy(context->clippath); + gdSpanRleDestroy(context->rle); + gdFree(context); + } +} + +BGD_DECLARE(void) +gdContextMoveTo(gdContextPtr context, double x, double y) +{ + gdPathMoveTo(context->path, x, y); +} + +BGD_DECLARE(void) +gdContextLineTo(gdContextPtr context, double x, double y) +{ + gdPathLineTo(context->path, x, y); +} + +BGD_DECLARE(void) +gdContextRelLineTo(gdContextPtr context, double x, double y) +{ + gdPathRelLineTo(context->path, x, y); +} + +BGD_DECLARE(void) +gdContextSetLineWidth(gdContextPtr context, double width) +{ + context->state->stroke.width = width; +} + +BGD_DECLARE(void) gdContextSetDash(gdContextPtr context, double offset, const double* data, int size) +{ + gdPathDashDestroy(context->state->stroke.dash); + context->state->stroke.dash = gdPathDashCreate(data, size, offset); +} + +BGD_DECLARE(void) +gdContextSetLineCap(gdContextPtr context, gdLineCap cap) +{ + context->state->stroke.cap = cap; +} + +BGD_DECLARE(void) +gdContextSetLineJoin(gdContextPtr context, gdLineJoin join) +{ + context->state->stroke.join = join; +} + +BGD_DECLARE(void) +gdContextCurveTo(gdContextPtr context, double x1, double y1, double x2, double y2, double x3, double y3) +{ + gdPathCurveTo(context->path, x1, y1, x2, y2, x3, y3); +} + +BGD_DECLARE(void) +gdContextQuadTo(gdContextPtr context, double x1, double y1, double x2, double y2) { + gdPathQuadTo(context->path, x1, y1, x2, y2); +} + +BGD_DECLARE(void) +gdContextArc(gdContextPtr context, double cx, double cy, double r, double a0, double a1) +{ + gdPathAddArc(context->path, cx, cy, r, a0, a1, 0); +} + +BGD_DECLARE(void) +gdContextNegativeArc(gdContextPtr context, double cx, double cy, double r, double a0, double a1) +{ + gdPathAddArc(context->path, cx, cy, r, a0, a1, 1); +} + +BGD_DECLARE(void) + gdContextRectangle(gdContextPtr context, double x, double y, double w, double h) +{ + gdPathAddRectangle(context->path, x, y, w, h); +} + +BGD_DECLARE(void) +gdContextClosePath(gdContextPtr context) +{ + if (!context) + return; + gdPathClose(context->path); +} + +BGD_DECLARE(void) +gdContextScale(gdContextPtr context, double x, double y) +{ + gdPathMatrixScale(&context->state->matrix, x, y); +} + +BGD_DECLARE(void) +gdContextTranslate(gdContextPtr context, double x, double y) +{ + gdPathMatrixTranslate(&context->state->matrix, x, y); +} + +BGD_DECLARE(void) +gdContextRotate(gdContextPtr context, double radians) +{ + gdPathMatrixRotate(&context->state->matrix, radians); +} + +BGD_DECLARE(void) +gdContextTransform(gdContextPtr context, const gdPathMatrixPtr matrix) +{ + gdPathMatrixMultiply(&context->state->matrix, matrix, &context->state->matrix); +} + +BGD_DECLARE(void) +gdContextSetFillRule(gdContextPtr context, gdFillRule winding) +{ + context->state->winding = winding; +} + +BGD_DECLARE(void) +gdContextStroke(gdContextPtr context) +{ + gdContextStrokePreserve(context); + gdContextNewPath(context); +} diff --git a/ext/gd/libgd/gd_draw_blend.c b/ext/gd/libgd/gd_draw_blend.c new file mode 100644 index 000000000000..04e76b8dbda4 --- /dev/null +++ b/ext/gd/libgd/gd_draw_blend.c @@ -0,0 +1,756 @@ + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gd_intern.h" +#include "gdhelpers.h" +#include "gd_errors.h" + +#include "gd_surface.h" +#include "gd_array.h" +#include "gd_path_matrix.h" +#include "gd_span_rle.h" + +#include "gd_fixed.h" +#include "gd_compositor.h" +#include "gd_gradient.h" + +#define BILINEAR_INTERPOLATION_BITS 7 +#define BILINEAR_INTERPOLATION_RANGE (1 << BILINEAR_INTERPOLATION_BITS) + +#define CLIP(v, low, high) ((v) < (low) ? (low) : ((v) > (high) ? (high) : (v))) +#define MOD(a, b) ((a) < 0 ? ((b) - ((-(a) - 1) % (b))) - 1 : (a) % (b)) + +static inline int _fixed_to_bilinear_weight (gd_fixed_t x) +{ + return (x >> (16 - BILINEAR_INTERPOLATION_BITS)) & + ((1 << BILINEAR_INTERPOLATION_BITS) - 1); +} + +static inline uint32_t fetch_pixel_general (gdSurfacePtr surface, int x, int y, int check_bounds) +{ + if (check_bounds && (x < 0 || x >= surface->width || y < 0 || y >= surface->height)) + { + return 0; + } + + uint32_t *src = (uint32_t *)(surface->data); + return src[y * surface->stride/4 + x]; +} + +static inline uint32_t bilinear_interpolation (uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br, int distx, int disty) +{ + uint64_t distxy, distxiy, distixy, distixiy; + uint64_t tl64, tr64, bl64, br64; + uint64_t f, r; + + if (tl == tr && tl == bl && tl == br) + return tl; + + distx <<= (8 - BILINEAR_INTERPOLATION_BITS); + disty <<= (8 - BILINEAR_INTERPOLATION_BITS); + + distxy = (uint64_t)distx * disty; + distxiy = (uint64_t)distx * (256 - disty); + distixy = (uint64_t)(256 - distx) * disty; + distixiy = (uint64_t)(256 - distx) * (256 - disty); + + /* Alpha and Blue */ + tl64 = tl & 0xff0000ff; + tr64 = tr & 0xff0000ff; + bl64 = bl & 0xff0000ff; + br64 = br & 0xff0000ff; + + f = tl64 * distixiy + tr64 * distxiy + bl64 * distixy + br64 * distxy; + r = f & 0x0000ff0000ff0000ull; + + /* Red and Green */ + tl64 = tl; + tl64 = ((tl64 << 16) & 0x000000ff00000000ull) | (tl64 & 0x0000ff00ull); + + tr64 = tr; + tr64 = ((tr64 << 16) & 0x000000ff00000000ull) | (tr64 & 0x0000ff00ull); + + bl64 = bl; + bl64 = ((bl64 << 16) & 0x000000ff00000000ull) | (bl64 & 0x0000ff00ull); + + br64 = br; + br64 = ((br64 << 16) & 0x000000ff00000000ull) | (br64 & 0x0000ff00ull); + + f = tl64 * distixiy + tr64 * distxiy + bl64 * distixy + br64 * distxy; + r |= ((f >> 16) & 0x000000ff00000000ull) | (f & 0xff000000ull); + + return (uint32_t)(r >> 16); +} + +static inline int _update_w_repeat (gdExtendMode repeat, int *c, int size) +{ + if (repeat == GD_EXTEND_NONE) { + if (*c < 0 || *c >= size) + return 0; + } + else if (repeat == GD_EXTEND_REPEAT) + { + while (*c >= size) + *c -= size; + while (*c < 0) + *c += size; + } + else if (repeat == GD_EXTEND_PAD) + { + *c = CLIP (*c, 0, size - 1); + } + else /* REFLECT */ + { + *c = MOD (*c, size * 2); + if (*c >= size) + *c = size * 2 - *c - 1; + } + return 1; +} + +static inline uint32_t +_surface_fetch_pixel_bilinear(gdSurfacePtr image, gd_fixed_t x, gd_fixed_t y, gdExtendMode repeat_mode) +{ + int width = image->width; + int height = image->height; + int x1, y1, x2, y2; + uint32_t tl, tr, bl, br; + int32_t distx, disty; + + x1 = x - gd_fixed_1 / 2; + y1 = y - gd_fixed_1 / 2; + + distx = _fixed_to_bilinear_weight(x1); + disty = _fixed_to_bilinear_weight(y1); + x1 = gd_fixed_to_int(x1); + y1 = gd_fixed_to_int(y1); + x2 = x1 + 1; + y2 = y1 + 1; + if (repeat_mode != GD_EXTEND_NONE) { + _update_w_repeat(repeat_mode, &x1, width); + _update_w_repeat(repeat_mode, &y1, height); + _update_w_repeat(repeat_mode, &x2, width); + _update_w_repeat(repeat_mode, &y2, height); + + tl = fetch_pixel_general(image, x1, y1, 0); + bl = fetch_pixel_general(image, x1, y2, 0); + tr = fetch_pixel_general(image, x2, y1, 0); + br = fetch_pixel_general(image, x2, y2, 0); + } else { + tl = fetch_pixel_general(image, x1, y1, 1); + tr = fetch_pixel_general(image, x2, y1, 1); + bl = fetch_pixel_general(image, x1, y2, 1); + br = fetch_pixel_general(image, x2, y2, 1); + } + return bilinear_interpolation(tl, tr, bl, br, distx, disty); +} + +#define ALPHA(c) ((c) >> 24) +static void operator_argb_color_source(uint32_t* dest, int length, uint32_t color, uint32_t alpha) +{ + gdPremulPixelF src = gdCompositePixelFromArgb32(color); + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_SOURCE, src, gdCompositePixelFromArgb32(dest[i]), alpha / 255.0f)); +} + +static void operator_argb_color_source_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + gdPremulPixelF src = gdCompositePixelFromArgb32(color); + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_OVER, src, gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void operator_argb_color_destination_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + gdPremulPixelF src = gdCompositePixelFromArgb32(color); + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_DEST_IN, src, gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void operator_argb_color_destination_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) +{ + gdPremulPixelF src = gdCompositePixelFromArgb32(color); + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_DEST_OUT, src, gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void operator_argb_color(gdCompositeOperator op, uint32_t *dest, + int length, uint32_t color, uint32_t alpha) +{ + gdPremulPixelF src = gdCompositePixelFromArgb32(color); + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + op, src, gdCompositePixelFromArgb32(dest[i]), alpha / 255.0f)); +} + +#define spanBlendLoop(func) \ + while(count--) \ + { \ + uint32_t* target = (uint32_t*)(surface->data + currentSpan->y * surface->stride) + currentSpan->x; \ + func(target, currentSpan->len, color, currentSpan->coverage); \ + ++currentSpan; \ + }; + +static void argb32_blend_color(gdSurfacePtr surface, gdImageOp op, const gdSpanRlePtr rle, uint32_t color) +{ + int count = rle->spans.size; + gdSpanPtr currentSpan = rle->spans.data; + switch (op) { + case gdImageOpsSrc: + spanBlendLoop(operator_argb_color_source); + break; + case gdImageOpsSrcOver: + spanBlendLoop(operator_argb_color_source_over); + break; + case gdImageOpsDstIn: + spanBlendLoop(operator_argb_color_destination_in); + break; + case gdImageOpsDstOut: + spanBlendLoop(operator_argb_color_destination_out); + break; + default: + while (count--) { + uint32_t *target = (uint32_t *)(surface->data + currentSpan->y * surface->stride) + currentSpan->x; + operator_argb_color(op, target, currentSpan->len, color, currentSpan->coverage); + ++currentSpan; + } + break; + } +} + +static inline uint32_t premultiply_color(const gdColorPtr color, double opacity) +{ + const double a = color->a * opacity; + const uint32_t alpha = (uint8_t)floor(a * 255.0 + 0.5); + const uint32_t pr = (uint8_t)floor(color->r * a * 255.0 + 0.5); + const uint32_t pg = (uint8_t)floor(color->g * a * 255.0 + 0.5); + const uint32_t pb = (uint8_t)floor(color->b * a * 255.0 + 0.5); + + return (alpha << 24) | (pr << 16) | (pg << 8) | (pb); +} + +#define _getVarName(var) #var +void gdBlendColor(gdContextPtr context, const gdSpanRlePtr rle, const gdColorPtr color) +{ + if(color==NULL) + return; + + gdStatePtr state = context->state; + // replace once we have more than + switch (context->surface->type) { + case GD_SURFACE_ARGB32: { + uint32_t pm_color = premultiply_color(color, state->opacity); + argb32_blend_color(context->surface, state->op, rle, pm_color); + break; + } + case GD_SURFACE_XRGB32: + case GD_SURFACE_A8: + gd_error("gdDraw does not implement %s yet.\n", _getVarName(GD_SURFACE_XRGB32)); + break; + default: + gd_error("gdDraw: provided surface has an unknown type.\n"); + return; + } +} + +typedef struct { + gdPathMatrix matrix; + gdExtendMode extend; + uint8_t* data; + gdSurfacePtr surface; + int width; + int height; + int stride; + int alpha; +} _spans_pattern; + +// TODO: Once the rest is a tat bit faster, use func ptr for all but *_compose_source +// Macros are not an option, unreadable and painful to debug. +static void argb32_compose_source(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_SOURCE, gdCompositePixelFromArgb32(src[i]), + gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void argb32_compose_source_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_OVER, gdCompositePixelFromArgb32(src[i]), + gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void argb32_compose_dst_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_DEST_OUT, gdCompositePixelFromArgb32(src[i]), + gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void argb32_compose_dst_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) +{ + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + GD_OP_DEST_IN, gdCompositePixelFromArgb32(src[i]), + gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +static void argb32_compose(gdCompositeOperator op, uint32_t *dest, int length, + const uint32_t *src, uint32_t const_alpha) +{ + for (int i = 0; i < length; i++) + dest[i] = gdCompositePixelToArgb32(gdCompositePixel( + op, gdCompositePixelFromArgb32(src[i]), + gdCompositePixelFromArgb32(dest[i]), const_alpha / 255.0f)); +} + +#define BUFFER_SIZE 1024 +static void render_spans_compose_source(const gdSurface *surface, const _spans_pattern *pattern, uint32_t *buffer, + const gdExtendMode extend, int fdx, int fdy, int count, gdSpanPtr spans) { + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + int x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + int y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + const int coverage = (spans->coverage * pattern->alpha + 127) / 255; + int length = spans->len; + while(length) { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + *b = _surface_fetch_pixel_bilinear(pattern->surface, x, y, extend); + x += fdx; + y += fdy; + ++b; + } + argb32_compose_source(target, l, buffer, coverage); + target += l; + length -= l; + } + ++spans; + } +} + +static void render_spans_compose_source_over(const gdSurface *surface, const _spans_pattern *pattern, uint32_t *buffer, + const gdExtendMode extend, int fdx, int fdy, int count, gdSpanPtr spans) { + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + int x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + int y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + const int coverage = (spans->coverage * pattern->alpha + 127) / 255; + int length = spans->len; + while(length) { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + *b = _surface_fetch_pixel_bilinear(pattern->surface, x, y, extend); + x += fdx; + y += fdy; + ++b; + } + argb32_compose_source_over(target, l, buffer, coverage); + target += l; + length -= l; + } + ++spans; + } +} + +static void render_spans_compose_dst_in(const gdSurface *surface, const _spans_pattern *pattern, uint32_t *buffer, + const gdExtendMode extend, int fdx, int fdy, int count, gdSpanPtr spans) { + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + int x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + int y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + const int coverage = (spans->coverage * pattern->alpha + 127) / 255; + int length = spans->len; + while(length) { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + *b = _surface_fetch_pixel_bilinear(pattern->surface, x, y, extend); + x += fdx; + y += fdy; + ++b; + } + argb32_compose_dst_in(target, l, buffer, coverage); + target += l; + length -= l; + } + ++spans; + } +} + +static void render_spans_compose_dst_out(const gdSurface *surface, const _spans_pattern *pattern, uint32_t *buffer, + const gdExtendMode extend, int fdx, int fdy, int count, gdSpanPtr spans) { + while(count--) { + uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + int x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + int y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + const int coverage = (spans->coverage * pattern->alpha + 127) / 255; + int length = spans->len; + while(length) { + int l = MIN(length, BUFFER_SIZE); + const uint32_t* end = buffer + l; + uint32_t* b = buffer; + while(b < end) { + *b = _surface_fetch_pixel_bilinear(pattern->surface, x, y, extend); + x += fdx; + y += fdy; + ++b; + } + argb32_compose_dst_out(target, l, buffer, coverage); + target += l; + length -= l; + } + ++spans; + } +} + +static void render_spans_compose(const gdSurface *surface, gdCompositeOperator op, + const _spans_pattern *pattern, uint32_t *buffer, + gdExtendMode extend, int fdx, int fdy, + int count, gdSpanPtr spans) +{ + while (count--) { + uint32_t *target = (uint32_t *)(surface->data + spans->y * surface->stride) + spans->x; + const double cx = spans->x + 0.5; + const double cy = spans->y + 0.5; + int x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + int y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + int coverage = (spans->coverage * pattern->alpha + 127) / 255; + int length = spans->len; + while (length) { + int l = MIN(length, BUFFER_SIZE); + for (int i = 0; i < l; i++) { + buffer[i] = _surface_fetch_pixel_bilinear(pattern->surface, x, y, extend); + x += fdx; + y += fdy; + } + argb32_compose(op, target, l, buffer, coverage); + target += l; + length -= l; + } + ++spans; + } +} + +static void argb32_pattern_tiled_blend_transformed(gdSurfacePtr surface, gdImageOp op, const gdSpanRlePtr rle, const _spans_pattern * pattern) +{ + uint32_t buffer[BUFFER_SIZE]; + const gdExtendMode extend = pattern->extend; + int fdx = gd_double_to_fixed(pattern->matrix.m00); + int fdy = gd_double_to_fixed(pattern->matrix.m10); + int count = rle->spans.size; + gdSpanPtr spans = rle->spans.data; + switch (op) { + case gdImageOpsSrc: + render_spans_compose_source(surface, pattern, buffer, extend, fdx, fdy, count, spans); + break; + case gdImageOpsSrcOver: + render_spans_compose_source_over(surface, pattern, buffer, extend, fdx, fdy, count, spans); + break; + case gdImageOpsDstIn: + render_spans_compose_dst_in(surface, pattern, buffer, extend, fdx, fdy, count, spans); + break; + case gdImageOpsDstOut: + render_spans_compose_dst_out(surface, pattern, buffer, extend, fdx, fdy, count, spans); + break; + default: + render_spans_compose(surface, op, pattern, buffer, extend, + fdx, fdy, count, spans); + break; + } +} + +#define spans_untransformed_blend_loop(composition) \ +while(count--) { \ + int x = spans->x; \ + int length = spans->len; \ + int sx = xoff + x; \ + int sy = yoff + spans->y; \ + if(sy >= 0 && sy < image_height && sx < image_width) { \ + if(sx < 0) { \ + x -= sx; \ + length += sx; \ + sx = 0; \ + } \ + if(sx + length > image_width) length = image_width - sx; \ + if(length > 0) { \ + const int coverage = (spans->coverage * pattern->alpha + 127) / 255; \ + const uint32_t* src = (const uint32_t*)(pattern->data + sy * pattern->stride) + sx; \ + uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; \ + composition(dest, length, src, coverage); \ + } \ + } \ + ++spans; \ +} + +static void argb32_pattern_blend_untransformed(gdSurfacePtr surface, gdImageOp op, const gdSpanRlePtr rle, const _spans_pattern * pattern) +{ + const int image_width = pattern->width; + const int image_height = pattern->height; + int xoff = (int)(pattern->matrix.m02); + int yoff = (int)(pattern->matrix.m12); + + int count = rle->spans.size; + gdSpanPtr spans = rle->spans.data; + + switch (op) { + case gdImageOpsSrc: + spans_untransformed_blend_loop(argb32_compose_source); + break; + case gdImageOpsSrcOver: + spans_untransformed_blend_loop(argb32_compose_source_over); + break; + case gdImageOpsDstIn: + spans_untransformed_blend_loop(argb32_compose_dst_in); + break; + case gdImageOpsDstOut: + spans_untransformed_blend_loop(argb32_compose_dst_out); + break; + default: + while (count--) { + int x = spans->x; + int length = spans->len; + int sx = xoff + x; + int sy = yoff + spans->y; + if (sy >= 0 && sy < image_height && sx < image_width) { + if (sx < 0) { x -= sx; length += sx; sx = 0; } + if (sx + length > image_width) length = image_width - sx; + if (length > 0) { + int coverage = (spans->coverage * pattern->alpha + 127) / 255; + const uint32_t *src = (const uint32_t *)(pattern->data + sy * pattern->stride) + sx; + uint32_t *dest = (uint32_t *)(surface->data + spans->y * surface->stride) + x; + argb32_compose(op, dest, length, src, coverage); + } + } + ++spans; + } + } +} + +void gdDrawBlendPattern(gdContextPtr context, const gdSpanRlePtr rle, const gdPathPatternPtr pattern) +{ + if(pattern == NULL) + return; + + gdStatePtr state = context->state; + _spans_pattern pattern_impl; + pattern_impl.extend = pattern->extend; + pattern_impl.data = pattern->surface->data; + pattern_impl.surface = pattern->surface; + pattern_impl.width = pattern->surface->width; + pattern_impl.height = pattern->surface->height; + pattern_impl.stride = pattern->surface->stride; + pattern_impl.alpha = (int)(state->opacity * pattern->opacity * 255.0 + 0.5); + + pattern_impl.matrix = pattern->matrix; + gdPathMatrixMultiply(&pattern_impl.matrix, &pattern_impl.matrix, &state->matrix); + gdPathMatrixInvert(&pattern_impl.matrix); + + const gdPathMatrixPtr matrix = &pattern_impl.matrix; + int translating = (matrix->m00==1.0 && matrix->m10==0.0 && matrix->m01==0.0 && matrix->m11==1.0); + if(translating) { + if(pattern->extend == GD_EXTEND_NONE) + argb32_pattern_blend_untransformed(context->surface, state->op, rle, &pattern_impl); + else + argb32_pattern_tiled_blend_transformed(context->surface, state->op, rle, &pattern_impl); + } else { + if(pattern->extend == GD_EXTEND_NONE) + //argb32_pattern_blend_untransformed(context->surface, state->op, rle, &pattern_impl); + argb32_pattern_tiled_blend_transformed(context->surface, state->op, rle, &pattern_impl); + else + argb32_pattern_tiled_blend_transformed(context->surface, state->op, rle, &pattern_impl); + } +} + +static unsigned int rle_coverage_at(const gdSpanRlePtr rle, int x, int y, + int *cursor) +{ + if (!rle) + return 255; + while (*cursor < rle->spans.size) { + const gdSpanPtr s = &rle->spans.data[*cursor]; + if (s->y < y || (s->y == y && s->x + s->len <= x)) { + (*cursor)++; + continue; + } + if (s->y == y && x >= s->x && x < s->x + s->len) + return s->coverage; + break; + } + return 0; +} + +static gdPremulPixelF pattern_pixel_at(const _spans_pattern *pattern, int px, int py) +{ + double cx = px + 0.5; + double cy = py + 0.5; + gd_fixed_t x = gd_double_to_fixed(pattern->matrix.m01 * cy + pattern->matrix.m00 * cx + pattern->matrix.m02); + gd_fixed_t y = gd_double_to_fixed(pattern->matrix.m11 * cy + pattern->matrix.m10 * cx + pattern->matrix.m12); + return gdCompositePixelFromArgb32( + _surface_fetch_pixel_bilinear(pattern->surface, x, y, pattern->extend)); +} + +typedef struct { + const gdGradient *gradient; + gdPathMatrix device_to_pattern; + int valid; +} gdGradientSampler; + +static void gradient_sampler_init(gdGradientSampler *s, const gdGradient *g, + const gdStatePtr state) +{ + s->gradient = g; + s->device_to_pattern = g->matrix; + gdPathMatrixMultiply(&s->device_to_pattern, &s->device_to_pattern, + &state->matrix); + s->valid = gdPathMatrixInvert(&s->device_to_pattern); +} + +static gdPremulPixelF gradient_pixel_at(const gdGradientSampler *s, int x, int y) +{ + gdPremulPixelF z = {0, 0, 0, 0}; + return s->valid ? gdGradientSample(s->gradient, &s->device_to_pattern, + x + 0.5, y + 0.5) : z; +} + +static void gdDrawBlendGradient(gdContextPtr context, const gdSpanRlePtr rle, + const gdGradient *gradient) +{ + gdGradientSampler sampler; + gdStatePtr state = context->state; + int count = rle->spans.size; + gdSpanPtr span = rle->spans.data; + gradient_sampler_init(&sampler, gradient, state); + while (count--) { + uint32_t *dst = (uint32_t *)(context->surface->data + + span->y * context->surface->stride) + span->x; + float coverage = (float)(span->coverage / 255.0 * state->opacity); + for (int i = 0; i < span->len; i++) { + gdPremulPixelF src = gradient_pixel_at(&sampler, span->x + i, span->y); + gdPremulPixelF old = gdCompositePixelFromArgb32(dst[i]); + dst[i] = gdCompositePixelToArgb32(gdCompositePixel(state->op, src, old, coverage)); + } + span++; + } +} + +static gdPremulPixelF lerp_clip(gdPremulPixelF dst, gdPremulPixelF result, + float coverage) +{ + gdPremulPixelF p; + p.r = dst.r + (result.r - dst.r) * coverage; + p.g = dst.g + (result.g - dst.g) * coverage; + p.b = dst.b + (result.b - dst.b) * coverage; + p.a = dst.a + (result.a - dst.a) * coverage; + return p; +} + +static void blend_unbounded(gdContextPtr context, const gdSpanRlePtr shape) +{ + gdStatePtr state = context->state; + gdPaintPtr source = state->source; + gdSpanRlePtr clip_path = state->clippath; + gdPremulPixelF solid = {0, 0, 0, 0}; + _spans_pattern pattern; + gdGradientSampler gradient; + float paint_opacity = (float)state->opacity; + int shape_cursor = 0, clip_cursor = 0; + int x0 = MAX(0, (int)floor(context->clip.x)); + int y0 = MAX(0, (int)floor(context->clip.y)); + int x1 = MIN(context->surface->width, (int)ceil(context->clip.x + context->clip.w)); + int y1 = MIN(context->surface->height, (int)ceil(context->clip.y + context->clip.h)); + + if (source->type == gdPaintTypeColor) { + solid = gdCompositePixelFromArgb32(premultiply_color(source->color, 1.0)); + } else if (source->type == gdPaintTypePattern) { + gdPathPatternPtr p = source->pattern; + pattern.extend = p->extend; + pattern.surface = p->surface; + pattern.matrix = p->matrix; + gdPathMatrixMultiply(&pattern.matrix, &pattern.matrix, &state->matrix); + gdPathMatrixInvert(&pattern.matrix); + paint_opacity *= (float)p->opacity; + } else if (source->type == gdPaintTypeGradient) { + gradient_sampler_init(&gradient, source->gradient, state); + } else { + gd_error("Paint method not implemented or does not exist."); + return; + } + + for (int y = y0; y < y1; y++) { + uint32_t *row = (uint32_t *)(context->surface->data + y * context->surface->stride); + for (int x = x0; x < x1; x++) { + float mask = rle_coverage_at(shape, x, y, &shape_cursor) / 255.0f; + float clip = clip_path ? rle_coverage_at(clip_path, x, y, &clip_cursor) / 255.0f : 1.0f; + gdPremulPixelF src = source->type == gdPaintTypeColor ? solid : + (source->type == gdPaintTypePattern ? pattern_pixel_at(&pattern, x, y) : + gradient_pixel_at(&gradient, x, y)); + gdPremulPixelF dst = gdCompositePixelFromArgb32(row[x]); + gdPremulPixelF result; + if (clip == 0.0f) + continue; + result = gdCompositePixel(state->op, src, dst, mask * paint_opacity); + if (clip < 1.0f) + result = lerp_clip(dst, result, clip); + row[x] = gdCompositePixelToArgb32(result); + } + } +} + +void gdPathBlend(gdContextPtr context, const gdSpanRlePtr rle) +{ + if (rle == NULL) + return; + + if (gdCompositeOperatorIsUnbounded(context->state->op) && + rle == context->rle) { + blend_unbounded(context, rle); + return; + } + if (rle->spans.size == 0 || context->state->opacity == 0.0) + return; + + gdPaintPtr source = context->state->source; + switch (source->type) { + case gdPaintTypeColor: + gdBlendColor(context, rle, source->color); + break; + case gdPaintTypePattern: + gdDrawBlendPattern(context, rle, source->pattern); + break; + case gdPaintTypeGradient: + gdDrawBlendGradient(context, rle, source->gradient); + break; + case gdPaintTypeSurface: + default: + gd_error("Paint method not implemented or does not exist."); + break; + } +} diff --git a/ext/gd/libgd/gd_draw_blend.h b/ext/gd/libgd/gd_draw_blend.h new file mode 100644 index 000000000000..720bcb054e28 --- /dev/null +++ b/ext/gd/libgd/gd_draw_blend.h @@ -0,0 +1,7 @@ +#ifndef GD_DRAW_BLEND_H +#define GD_DRAW_BLEND_H + +void gdBlendColor(gdContextPtr context, const gdSpanRlePtr rle, const gdColorPtr color); +void gdPathBlend(gdContextPtr context, const gdSpanRlePtr rle); + +#endif // GD_DRAW_BLEND_H diff --git a/ext/gd/libgd/gd_filename.c b/ext/gd/libgd/gd_filename.c new file mode 100644 index 000000000000..938eb6857a3a --- /dev/null +++ b/ext/gd/libgd/gd_filename.c @@ -0,0 +1,270 @@ +/* Convenience functions to read or write images from or to disk, + * determining file type from the filename extension. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "gd.h" +#include "gd_intern.h" + +typedef gdImagePtr(BGD_STDCALL *ReadFn)(FILE *in); +typedef void(BGD_STDCALL *WriteFn)(gdImagePtr im, FILE *out); +typedef gdImagePtr(BGD_STDCALL *LoadFn)(char *filename); + +#ifdef HAVE_LIBZ +static void BGD_STDCALL writegd2(gdImagePtr im, FILE *out) { + gdImageGd2(im, out, 0, GD2_FMT_COMPRESSED); +} /* writegd*/ +#endif + +#ifdef HAVE_LIBJPEG +static void BGD_STDCALL writejpeg(gdImagePtr im, FILE *out) { + gdImageJpeg(im, out, -1); +} /* writejpeg*/ +#endif + +static void BGD_STDCALL writewbmp(gdImagePtr im, FILE *out) { + int fg = gdImageColorClosest(im, 0, 0, 0); + + gdImageWBMP(im, fg, out); +} /* writejpeg*/ + +static void BGD_STDCALL writebmp(gdImagePtr im, FILE *out) { + gdImageBmp(im, out, GD_TRUE); +} /* writejpeg*/ + +static const struct FileType { + const char *ext; + ReadFn reader; + WriteFn writer; + LoadFn loader; +} Types[] = {{".gif", gdImageCreateFromGif, gdImageGif, NULL}, + {".gd", gdImageCreateFromGd, gdImageGd, NULL}, + {".wbmp", gdImageCreateFromWBMP, writewbmp, NULL}, + {".bmp", gdImageCreateFromBmp, writebmp, NULL}, + + {".xbm", gdImageCreateFromXbm, NULL, NULL}, + {".tga", gdImageCreateFromTga, NULL, NULL}, + +#ifdef HAVE_LIBAVIF + {".avif", gdImageCreateFromAvif, gdImageAvif, NULL}, +#endif + +#ifdef HAVE_LIBPNG + {".png", gdImageCreateFromPng, gdImagePng, NULL}, +#endif + + {".qoi", gdImageCreateFromQoi, gdImageQoi, NULL}, + +#ifdef HAVE_LIBJPEG + {".jpg", gdImageCreateFromJpeg, writejpeg, NULL}, + {".jpeg", gdImageCreateFromJpeg, writejpeg, NULL}, +#endif + +#ifdef HAVE_LIBHEIF + {".heic", gdImageCreateFromHeif, gdImageHeif, NULL}, + {".heix", gdImageCreateFromHeif, NULL, NULL}, +#endif + +#ifdef HAVE_LIBTIFF + {".tiff", gdImageCreateFromTiff, gdImageTiff, NULL}, + {".tif", gdImageCreateFromTiff, gdImageTiff, NULL}, +#endif + +#ifdef HAVE_LIBZ + {".gd2", gdImageCreateFromGd2, writegd2, NULL}, +#endif + +#ifdef HAVE_LIBWEBP + {".webp", gdImageCreateFromWebp, gdImageWebp, NULL}, +#endif + +#ifdef HAVE_LIBJXL + {".jxl", gdImageCreateFromJxl, gdImageJxl, NULL}, +#endif + +#ifdef HAVE_LIBXPM + {".xpm", NULL, NULL, gdImageCreateFromXpm}, +#endif + + {NULL, NULL, NULL, NULL}}; + +static const struct FileType *ftype(const char *filename) { + int n; + const char *ext; + + /* Find the file extension (i.e. the last period in the string. */ + ext = strrchr(filename, '.'); + if (!ext) + return NULL; + + for (n = 0; Types[n].ext; n++) { + if (gd_strcasecmp(ext, Types[n].ext) == 0) { + return &Types[n]; + } /* if */ + } /* for */ + + return NULL; +} /* ftype*/ + +/* + Function: gdSupportsFileType + + Tests if a given file type is supported by GD. + + Given the name of an image file (which does not have to exist), + returns 1 (i.e. TRUE) if can read a file + of that type. This is useful if you do not know which image types + were enabled at compile time. + + If _writing_ is true, the result will be true only if + can write a file of this type. + + Note that filename parsing is done exactly the same as is done by + and and is subject to the + same limitations. + + Assuming LibGD is compiled with support for these image types, the + following extensions are supported: + + - .gif + - .gd, .gd2 + - .wbmp + - .bmp + - .xbm + - .tga + - .png + - .qoi + - .jpg, .jpeg + - .heif, .heix + - .avif + - .tiff, .tif + - .webp + - .jxl + - .xpm + + Names are parsed case-insenstively. + + Parameters: + + filename - Filename with tested extension. + writing - Flag: true tests if writing works + + Returns: + + GD_TRUE (1) if the file type is supported, GD_FALSE (0) if not. + +*/ +BGD_DECLARE(int) +gdSupportsFileType(const char *filename, int writing) { + const struct FileType *entry = ftype(filename); + return !!entry && (!writing || !!entry->writer); +} /* gdSupportsFileType*/ + +/* + Function: gdImageCreateFromFile + + Read an image file of any supported. + + Given the path to a file, will open the + file, read its contents with the appropriate _gdImageCreateFrom*_ + function and return it. + + File type is determined by the filename extension, so having an + incorrect extension will probably not work. For example, renaming + PNG image "foo.png" to "foo.gif" and then attempting to load it + will fail even if GD supports both formats. See + for more details. + + NULL is returned on error. + + Parameters: + + filename - the input file name + + Returns: + + A pointer to the new image or NULL if an error occurred. + +*/ + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromFile(const char *filename) { + const struct FileType *entry = ftype(filename); + FILE *fh; + gdImagePtr result; + + if (!entry) + return NULL; + if (entry->loader) + return entry->loader((char *)filename); + if (!entry->reader) + return NULL; + + fh = fopen(filename, "rb"); + if (!fh) + return NULL; + + result = entry->reader(fh); + + fclose(fh); + + return result; +} /* gdImageCreateFromFile*/ + +/* + Function: gdImageFile + + Writes an image to a file in the format indicated by the filename. + + File type is determined by the extension of the file name. See + for an overview of the parsing. + + For file types that require extra arguments, + attempts to use sane defaults: + + - chunk size = 0, compression is enabled. + - quality = -1 (i.e. the reasonable default) + - foreground is the darkest available color + + Everything else is called with the two-argument function and so + will use the default values. + + has some rudimentary error detection and will return + GD_FALSE (0) if a detectable error occurred. However, the image + loaders do not normally return their error status so a result of + GD_TRUE (1) does **not** mean the file was saved successfully. + + Parameters: + + im - The image to save. + filename - The path to the file to which the image is saved. + + Returns: + + GD_FALSE (0) if an error was detected, GD_TRUE (1) if not. + +*/ + +BGD_DECLARE(int) +gdImageFile(gdImagePtr im, const char *filename) { + const struct FileType *entry = ftype(filename); + FILE *fh; + + if (!entry || !entry->writer) + return GD_FALSE; + + fh = fopen(filename, "wb"); + if (!fh) + return GD_FALSE; + + entry->writer(im, fh); + + fclose(fh); + + return GD_TRUE; +} /* gdImageFile*/ diff --git a/ext/gd/libgd/gd_filter.c b/ext/gd/libgd/gd_filter.c index 97dea5c552cd..ba00b86ab2be 100644 --- a/ext/gd/libgd/gd_filter.c +++ b/ext/gd/libgd/gd_filter.c @@ -1,6 +1,11 @@ +/** + * File: Image Filters + */ + #include "gd.h" #include "gd_intern.h" +#include "gdhelpers.h" #ifdef _WIN32 # include @@ -11,18 +16,19 @@ #include #include -/* Filters function added on 2003/12 - * by Pierre-Alain Joye (pierre@php.net) - * - * Scatter filter added in libgd 2.1.0 - * by Kalle Sommer Nielsen (kalle@php.net) - **/ +#undef NDEBUG +/* Comment out this line to enable asserts. + * TODO: This logic really belongs in cmake and configure. + */ +#define NDEBUG 1 +#include -/* Begin filters function */ -#define GET_PIXEL_FUNCTION(src)(src->trueColor?gdImageGetTrueColorPixel:gdImageGetPixel) +typedef int(BGD_STDCALL *FuncPtr)(gdImagePtr, int, int); -static int gdClampFloatToByte(float value) -{ +#define GET_PIXEL_FUNCTION(src) \ + (src->trueColor ? gdImageGetTrueColorPixel : gdImageGetPixel) + +static int gdClampFloatToByte(float value) { if (!isfinite(value)) { return value > 0.0f ? 255 : 0; } @@ -41,8 +47,10 @@ static int gdClampFloatToByte(float value) # define GD_SCATTER_SEED() (unsigned int)(time(0) * getpid()) #endif -int gdImageScatter(gdImagePtr im, int sub, int plus) -{ +/* + Function: gdImageScatter + */ +BGD_DECLARE(int) gdImageScatter(gdImagePtr im, int sub, int plus) { gdScatter s; s.sub = sub; @@ -52,8 +60,12 @@ int gdImageScatter(gdImagePtr im, int sub, int plus) return gdImageScatterEx(im, &s); } -int gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], unsigned int num_colors) -{ +/* + Function: gdImageScatterColor + */ +BGD_DECLARE(int) +gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], + unsigned int num_colors) { gdScatter s; s.sub = sub; @@ -64,8 +76,10 @@ int gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], unsigned return gdImageScatterEx(im, &s); } -int gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter) -{ +/* + Function: gdImageScatterEx + */ +BGD_DECLARE(int) gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter) { register int x, y; int dest_x, dest_y; int pxl, new_pxl; @@ -74,8 +88,7 @@ int gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter) if (plus == 0 && sub == 0) { return 1; - } - else if (sub >= plus) { + } else if (sub >= plus) { return 0; } @@ -102,8 +115,7 @@ int gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter) } } } - } - else { + } else { for (y = 0; y < im->sy; y++) { for (x = 0; x < im->sx; x++) { dest_x = (int)(x + ((rand() % (plus - sub)) + sub)); @@ -125,8 +137,11 @@ int gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter) return 1; } -int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode) -{ +/* + Function: gdImagePixelate + */ +BGD_DECLARE(int) +gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode) { int x, y; if (block_size <= 0) { @@ -140,7 +155,8 @@ int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode) for (x = 0; x < im->sx; x += block_size) { if (gdImageBoundsSafe(im, x, y)) { int c = gdImageGetPixel(im, x, y); - gdImageFilledRectangle(im, x, y, x + block_size - 1, y + block_size - 1, c); + gdImageFilledRectangle(im, x, y, x + block_size - 1, + y + block_size - 1, c); } } } @@ -169,8 +185,10 @@ int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode) } /* drawing */ if (total > 0) { - c = gdImageColorResolveAlpha(im, r / total, g / total, b / total, a / total); - gdImageFilledRectangle(im, x, y, x + block_size - 1, y + block_size - 1, c); + c = gdImageColorResolveAlpha(im, r / total, g / total, + b / total, a / total); + gdImageFilledRectangle(im, x, y, x + block_size - 1, + y + block_size - 1, c); } } } @@ -181,13 +199,21 @@ int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode) return 1; } -/* invert src image */ -int gdImageNegate(gdImagePtr src) -{ +/** + * Function: gdImageNegate + * + * Invert an image + * + * Parameters: + * src - The image. + * + * Returns: + * Non-zero on success, zero on failure. + */ +BGD_DECLARE(int) gdImageNegate(gdImagePtr src) { int x, y; int r,g,b,a; int new_pxl, pxl; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src==NULL) { @@ -204,9 +230,11 @@ int gdImageNegate(gdImagePtr src) b = gdImageBlue(src, pxl); a = gdImageAlpha(src, pxl); - new_pxl = gdImageColorAllocateAlpha(src, 255-r, 255-g, 255-b, a); + new_pxl = + gdImageColorAllocateAlpha(src, 255 - r, 255 - g, 255 - b, a); if (new_pxl == -1) { - new_pxl = gdImageColorClosestAlpha(src, 255-r, 255-g, 255-b, a); + new_pxl = + gdImageColorClosestAlpha(src, 255 - r, 255 - g, 255 - b, a); } gdImageSetPixel (src, x, y, new_pxl); } @@ -214,18 +242,30 @@ int gdImageNegate(gdImagePtr src) return 1; } -/* Convert the image src to a grayscale image */ -int gdImageGrayScale(gdImagePtr src) -{ +/** + * Function: gdImageGrayScale + * + * Convert an image to grayscale + * + * The red, green and blue components of each pixel are replaced by their + * weighted sum using the same coefficients as the REC.601 luma (Y') + * calculation. The alpha components are retained. + * + * For palette images the result may differ due to palette limitations. + * + * Parameters: + * src - The image. + * + * Returns: + * Non-zero on success, zero on failure. + */ +BGD_DECLARE(int) gdImageGrayScale(gdImagePtr src) { int x, y; int r,g,b,a; int new_pxl, pxl; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; int alpha_blending; - f = GET_PIXEL_FUNCTION(src); - if (src==NULL) { return 0; } @@ -233,6 +273,8 @@ int gdImageGrayScale(gdImagePtr src) alpha_blending = src->alphaBlendingFlag; gdImageAlphaBlending(src, gdEffectReplace); + f = GET_PIXEL_FUNCTION(src); + for (y=0; ysy; ++y) { for (x=0; xsx; ++x) { pxl = f (src, x, y); @@ -254,15 +296,27 @@ int gdImageGrayScale(gdImagePtr src) return 1; } -/* Set the brightness level for the image src */ -int gdImageBrightness(gdImagePtr src, int brightness) -{ +/** + * Function: gdImageBrightness + * + * Change the brightness of an image + * + * Parameters: + * src - The image. + * brightness - The value to add to the color channels of all pixels. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) gdImageBrightness(gdImagePtr src, int brightness) { int x, y; int r,g,b,a; int new_pxl, pxl; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; - f = GET_PIXEL_FUNCTION(src); if (src==NULL || (brightness < -255 || brightness>255)) { return 0; @@ -272,6 +326,8 @@ int gdImageBrightness(gdImagePtr src, int brightness) return 1; } + f = GET_PIXEL_FUNCTION(src); + for (y=0; ysy; ++y) { for (x=0; xsx; ++x) { pxl = f (src, x, y); @@ -291,7 +347,8 @@ int gdImageBrightness(gdImagePtr src, int brightness) new_pxl = gdImageColorAllocateAlpha(src, (int)r, (int)g, (int)b, a); if (new_pxl == -1) { - new_pxl = gdImageColorClosestAlpha(src, (int)r, (int)g, (int)b, a); + new_pxl = + gdImageColorClosestAlpha(src, (int)r, (int)g, (int)b, a); } gdImageSetPixel (src, x, y, new_pxl); } @@ -299,22 +356,37 @@ int gdImageBrightness(gdImagePtr src, int brightness) return 1; } - -int gdImageContrast(gdImagePtr src, double contrast) -{ +/** + * Function: gdImageContrast + * + * Change the contrast of an image + * + * Parameters: + * src - The image. + * contrast - The contrast adjustment value. Negative values increase, postive + * values decrease the contrast. The larger the absolute value, the + * stronger the effect. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageContrast(gdImagePtr src, double contrast) { int x, y; int r,g,b,a; double rf,gf,bf; int new_pxl, pxl; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; - f = GET_PIXEL_FUNCTION(src); if (src==NULL) { return 0; } + f = GET_PIXEL_FUNCTION(src); + contrast = (double)(100.0-contrast)/100.0; contrast = contrast*contrast; @@ -349,9 +421,11 @@ int gdImageContrast(gdImagePtr src, double contrast) gf = (gf > 255.0)? 255.0 : ((gf < 0.0)? 0.0:gf); bf = (bf > 255.0)? 255.0 : ((bf < 0.0)? 0.0:bf); - new_pxl = gdImageColorAllocateAlpha(src, (int)rf, (int)gf, (int)bf, a); + new_pxl = + gdImageColorAllocateAlpha(src, (int)rf, (int)gf, (int)bf, a); if (new_pxl == -1) { - new_pxl = gdImageColorClosestAlpha(src, (int)rf, (int)gf, (int)bf, a); + new_pxl = + gdImageColorClosestAlpha(src, (int)rf, (int)gf, (int)bf, a); } gdImageSetPixel (src, x, y, new_pxl); } @@ -359,12 +433,29 @@ int gdImageContrast(gdImagePtr src, double contrast) return 1; } - -int gdImageColor(gdImagePtr src, const int red, const int green, const int blue, const int alpha) -{ +/** + * Function: gdImageColor + * + * Change channel values of an image + * + * Parameters: + * src - The image. + * red - The value to add to the red channel of all pixels. + * green - The value to add to the green channel of all pixels. + * blue - The value to add to the blue channel of all pixels. + * alpha - The value to add to the alpha channel of all pixels. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + */ +BGD_DECLARE(int) +gdImageColor(gdImagePtr src, const int red, const int green, const int blue, + const int alpha) { int x, y; int new_pxl, pxl; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src == NULL) { @@ -403,13 +494,37 @@ int gdImageColor(gdImagePtr src, const int red, const int green, const int blue, return 1; } -int gdImageConvolution(gdImagePtr src, float filter[3][3], float filter_div, float offset) -{ +/** + * Function: gdImageConvolution + * + * Apply a convolution matrix to an image + * + * Depending on the matrix a wide range of effects can be accomplished, e.g. + * blurring, sharpening, embossing and edge detection. + * + * Parameters: + * src - The image. + * filter - The 3x3 convolution matrix. + * filter_div - The value to divide the convoluted channel values by. + * offset - The value to add to the convoluted channel values. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + * - + * - + * - + * - + */ +BGD_DECLARE(int) +gdImageConvolution(gdImagePtr src, float filter[3][3], float filter_div, + float offset) { int x, y, i, j, new_a; float new_r, new_g, new_b; int new_pxl, pxl=0; gdImagePtr srcback; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src==NULL) { @@ -455,9 +570,11 @@ int gdImageConvolution(gdImagePtr src, float filter[3][3], float filter_div, flo new_gi = gdClampFloatToByte(new_g); new_bi = gdClampFloatToByte(new_b); - new_pxl = gdImageColorAllocateAlpha(src, new_ri, new_gi, new_bi, new_a); + new_pxl = + gdImageColorAllocateAlpha(src, new_ri, new_gi, new_bi, new_a); if (new_pxl == -1) { - new_pxl = gdImageColorClosestAlpha(src, new_ri, new_gi, new_bi, new_a); + new_pxl = gdImageColorClosestAlpha(src, new_ri, new_gi, new_bi, + new_a); } gdImageSetPixel (src, x, y, new_pxl); } @@ -466,8 +583,10 @@ int gdImageConvolution(gdImagePtr src, float filter[3][3], float filter_div, flo return 1; } -int gdImageSelectiveBlur( gdImagePtr src) -{ +/* + Function: gdImageSelectiveBlur + */ +BGD_DECLARE(int) gdImageSelectiveBlur(gdImagePtr src) { int x, y, i, j; float new_r, new_g, new_b; int new_pxl, cpxl, pxl, new_a=0; @@ -477,7 +596,6 @@ int gdImageSelectiveBlur( gdImagePtr src) float flt_r_sum, flt_g_sum, flt_b_sum; gdImagePtr srcback; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src==NULL) { @@ -506,7 +624,8 @@ int gdImageSelectiveBlur( gdImagePtr src) pxl = f(src, x-(3>>1)+i, y-(3>>1)+j); new_a = gdImageAlpha(srcback, pxl); - new_r = ((float)gdImageRed(srcback, cpxl)) - ((float)gdImageRed (srcback, pxl)); + new_r = ((float)gdImageRed(srcback, cpxl)) - + ((float)gdImageRed(srcback, pxl)); if (new_r < 0.0f) { new_r = -new_r; @@ -517,7 +636,8 @@ int gdImageSelectiveBlur( gdImagePtr src) flt_r[j][i] = 1.0f; } - new_g = ((float)gdImageGreen(srcback, cpxl)) - ((float)gdImageGreen(srcback, pxl)); + new_g = ((float)gdImageGreen(srcback, cpxl)) - + ((float)gdImageGreen(srcback, pxl)); if (new_g < 0.0f) { new_g = -new_g; @@ -528,7 +648,8 @@ int gdImageSelectiveBlur( gdImagePtr src) flt_g[j][i] = 1.0f; } - new_b = ((float)gdImageBlue(srcback, cpxl)) - ((float)gdImageBlue(srcback, pxl)); + new_b = ((float)gdImageBlue(srcback, cpxl)) - + ((float)gdImageBlue(srcback, pxl)); if (new_b < 0.0f) { new_b = -new_b; @@ -574,9 +695,11 @@ int gdImageSelectiveBlur( gdImagePtr src) new_r = (new_r > 255.0f)? 255.0f : ((new_r < 0.0f)? 0.0f:new_r); new_g = (new_g > 255.0f)? 255.0f : ((new_g < 0.0f)? 0.0f:new_g); new_b = (new_b > 255.0f)? 255.0f : ((new_b < 0.0f)? 0.0f:new_b); - new_pxl = gdImageColorAllocateAlpha(src, (int)new_r, (int)new_g, (int)new_b, new_a); + new_pxl = gdImageColorAllocateAlpha(src, (int)new_r, (int)new_g, + (int)new_b, new_a); if (new_pxl == -1) { - new_pxl = gdImageColorClosestAlpha(src, (int)new_r, (int)new_g, (int)new_b, new_a); + new_pxl = gdImageColorClosestAlpha(src, (int)new_r, (int)new_g, + (int)new_b, new_a); } gdImageSetPixel (src, x, y, new_pxl); } @@ -585,55 +708,348 @@ int gdImageSelectiveBlur( gdImagePtr src) return 1; } -int gdImageEdgeDetectQuick(gdImagePtr src) -{ - float filter[3][3] = {{-1.0,0.0,-1.0}, - {0.0,4.0,0.0}, - {-1.0,0.0,-1.0}}; +/** + * Function: gdImageEdgeDetectQuick + * + * Edge detection of an image + * + * (see edge_detect_quick.jpg) + * + * Parameters: + * src - The image. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) gdImageEdgeDetectQuick(gdImagePtr src) { + float filter[3][3] = { + {-1.0, 0.0, -1.0}, {0.0, 4.0, 0.0}, {-1.0, 0.0, -1.0}}; return gdImageConvolution(src, filter, 1, 127); } -int gdImageGaussianBlur(gdImagePtr im) -{ - float filter[3][3] = {{1.0,2.0,1.0}, - {2.0,4.0,2.0}, - {1.0,2.0,1.0}}; +/* + Function: gdImageGaussianBlur + + performs a Gaussian blur of radius 1 on the + image. The image is modified in place. + + *NOTE:* You will almost certain want to use + instead, as it allows you to change + your kernel size and sigma value. Future versions of this + function may fall back to calling it instead of + , causing subtle changes so be warned. + + Parameters: + im - The image to blur + + Returns: + GD_TRUE (1) on success, GD_FALSE (0) on failure. + +*/ + +BGD_DECLARE(int) gdImageGaussianBlur(gdImagePtr im) { + float filter[3][3] = {{1.0, 2.0, 1.0}, {2.0, 4.0, 2.0}, {1.0, 2.0, 1.0}}; return gdImageConvolution(im, filter, 16, 0); } -int gdImageEmboss(gdImagePtr im) -{ +/** + * Function: gdImageEmboss + * + * Emboss an image + * + * (see emboss.jpg) + * + * Parameters: + * im - The image. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageEmboss(gdImagePtr im) { /* float filter[3][3] = {{1.0,1.0,1.0}, {0.0,0.0,0.0}, {-1.0,-1.0,-1.0}}; */ - float filter[3][3] = {{ 1.5, 0.0, 0.0}, - { 0.0, 0.0, 0.0}, - { 0.0, 0.0,-1.5}}; + float filter[3][3] = {{1.5, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, -1.5}}; return gdImageConvolution(im, filter, 1, 127); } -int gdImageMeanRemoval(gdImagePtr im) -{ - float filter[3][3] = {{-1.0,-1.0,-1.0}, - {-1.0,9.0,-1.0}, - {-1.0,-1.0,-1.0}}; +/** + * Function: gdImageMeanRemoval + * + * Mean removal of an image + * + * (see mean_removal.jpg) + * + * Parameters: + * im - The image. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) gdImageMeanRemoval(gdImagePtr im) { + float filter[3][3] = { + {-1.0, -1.0, -1.0}, {-1.0, 9.0, -1.0}, {-1.0, -1.0, -1.0}}; return gdImageConvolution(im, filter, 1, 0); } -int gdImageSmooth(gdImagePtr im, float weight) -{ - float filter[3][3] = {{1.0,1.0,1.0}, - {1.0,0.0,1.0}, - {1.0,1.0,1.0}}; +/** + * Function: gdImageSmooth + * + * Smooth an image + * + * (see smooth.jpg) + * + * Parameters: + * im - The image. + * weight - The strength of the smoothing. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + */ +BGD_DECLARE(int) gdImageSmooth(gdImagePtr im, float weight) { + float filter[3][3] = {{1.0, 1.0, 1.0}, {1.0, 0.0, 1.0}, {1.0, 1.0, 1.0}}; filter[1][1] = weight; return gdImageConvolution(im, filter, weight+8, 0); } + +/* Return an array of coefficients for 'radius' and 'sigma' (sigma >= + * 0 means compute it). Result length is 2*radius+1. */ +static double *gaussian_coeffs(int radius, double sigmaArg) { + const double sigma = (sigmaArg <= 0.0) ? (2.0 / 3.0) * radius : sigmaArg; + const double s = 2.0 * sigma * sigma; + double *result; + double sum = 0; + int x, n, count; + + count = 2 * radius + 1; + + result = gdMalloc(sizeof(double) * count); + if (!result) { + return NULL; + } /* if */ + + for (x = -radius; x <= radius; x++) { + double coeff = exp(-(x * x) / s); + + sum += coeff; + result[x + radius] = coeff; + } /* for */ + + for (n = 0; n < count; n++) { + result[n] /= sum; + } /* for */ + + return result; +} /* gaussian_coeffs*/ + +static inline int reflect(int max, int x) { + assert(x > -max && x < 2 * max); + + if (x < 0) + return -x; + if (x >= max) + return max - (x - max) - 1; + return x; +} /* reflect*/ + +static inline void applyCoeffsLine(gdImagePtr src, gdImagePtr dst, int line, + int linelen, double *coeffs, int radius, + gdAxis axis) { + int ndx; + + for (ndx = 0; ndx < linelen; ndx++) { + double r = 0, g = 0, b = 0, a = 0; + int cndx; + int *dest = (axis == HORIZONTAL) ? &dst->tpixels[line][ndx] + : &dst->tpixels[ndx][line]; + + for (cndx = -radius; cndx <= radius; cndx++) { + const double coeff = coeffs[cndx + radius]; + const int rndx = reflect(linelen, ndx + cndx); + + const int srcpx = (axis == HORIZONTAL) ? src->tpixels[line][rndx] + : src->tpixels[rndx][line]; + + r += coeff * (double)gdTrueColorGetRed(srcpx); + g += coeff * (double)gdTrueColorGetGreen(srcpx); + b += coeff * (double)gdTrueColorGetBlue(srcpx); + a += coeff * (double)gdTrueColorGetAlpha(srcpx); + } /* for */ + + *dest = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), + uchar_clamp(b, 0xFF), uchar_clamp(a, 0x7F)); + } /* for */ +} /* applyCoeffsLine*/ + +static void applyCoeffs(gdImagePtr src, gdImagePtr dst, double *coeffs, + int radius, gdAxis axis) { + int line, numlines, linelen; + + if (axis == HORIZONTAL) { + numlines = src->sy; + linelen = src->sx; + } else { + numlines = src->sx; + linelen = src->sy; + } /* if .. else*/ + + for (line = 0; line < numlines; line++) { + applyCoeffsLine(src, dst, line, linelen, coeffs, radius, axis); + } /* for */ +} /* applyCoeffs*/ + +/* + Function: gdImageCopyGaussianBlurred + + Return a copy of the source image _src_ blurred according to the + parameters using the Gaussian Blur algorithm. + + _radius_ is a radius, not a diameter so a radius of 2 (for + example) will blur across a region 5 pixels across (2 to the + center, 1 for the center itself and another 2 to the other edge). + + _sigma_ represents the "fatness" of the curve (lower == fatter). + If _sigma_ is less than or equal to 0, + ignores it and instead computes an + "optimal" value. Be warned that future versions of this function + may compute sigma differently. + + The resulting image is always truecolor. + + More Details: + + A Gaussian Blur is generated by replacing each pixel's color + values with the average of the surrounding pixels' colors. This + region is a circle whose radius is given by argument _radius_. + Thus, a larger radius will yield a blurrier image. + + This average is not a simple mean of the values. Instead, values + are weighted using the Gaussian function (roughly a bell curve + centered around the destination pixel) giving it much more + influence on the result than its neighbours. Thus, a fatter curve + will give the center pixel more weight and make the image less + blurry; lower _sigma_ values will yield flatter curves. + + Currently, computes the default sigma + as + + (2/3)*radius + + Note, however that we reserve the right to change this if we find + a better ratio. If you absolutely need the current sigma value, + you should set it yourself. + + Parameters: + + src - the source image + radius - the blur radius (*not* diameter--range is 2*radius + 1) + sigma - the sigma value or a value <= 0.0 to use the computed default + + Returns: + + The new image or NULL if an error occurred. The result is always + truecolor. + + Example: + (start code) + + FILE *in; + gdImagePtr result, src; + + in = fopen("foo.png", "rb"); + src = gdImageCreateFromPng(in); + + result = gdImageCopyGaussianBlurred(im, src->sx / 10, -1.0); + + (end code) +*/ + +/* TODO: Look into turning this into a generic seperable filter + * function with Gaussian Blur being one special case. (At the + * moment, I can't find any other useful separable filter so for not, + * it's just blur.) */ +BGD_DECLARE(gdImagePtr) +gdImageCopyGaussianBlurred(gdImagePtr src, int radius, double sigma) { + gdImagePtr tmp = NULL, result = NULL; + double *coeffs; + int freeSrc = 0; + + if (radius < 1) { + return NULL; + } /* if */ + + /* Compute the coefficients. */ + coeffs = gaussian_coeffs(radius, sigma); + if (!coeffs) { + return NULL; + } /* if */ + + /* If the image is not truecolor, we first make a truecolor + * scratch copy. */ + if (!src->trueColor) { + int tcstat; + + src = gdImageClone(src); + if (!src) { + gdFree(coeffs); + return NULL; + } + + tcstat = gdImagePaletteToTrueColor(src); + if (!tcstat) { + gdImageDestroy(src); + gdFree(coeffs); + return NULL; + } /* if */ + + freeSrc = 1; + } /* if */ + + /* Apply the filter horizontally. */ + tmp = gdImageCreateTrueColor(src->sx, src->sy); + if (!tmp) { + if (freeSrc) { + gdImageDestroy(src); + } + gdFree(coeffs); + return NULL; + } + applyCoeffs(src, tmp, coeffs, radius, HORIZONTAL); + + /* Apply the filter vertically. */ + result = gdImageCreateTrueColor(src->sx, src->sy); + if (result) { + applyCoeffs(tmp, result, coeffs, radius, VERTICAL); + } /* if */ + + gdImageDestroy(tmp); + gdFree(coeffs); + + if (freeSrc) + gdImageDestroy(src); + + return result; +} /* gdImageCopyGaussianBlurred*/ /* End filters function */ diff --git a/ext/gd/libgd/gd_fixed.h b/ext/gd/libgd/gd_fixed.h new file mode 100644 index 000000000000..81942d51bd9b --- /dev/null +++ b/ext/gd/libgd/gd_fixed.h @@ -0,0 +1,32 @@ +// +// Created by pierr on 7/21/2021. +// + +#ifndef GD_GD_FIXED_H +#define GD_GD_FIXED_H +/* Add *BSD and co for inttypes.h */ +#if defined (_MSC_VER) && _MSC_VER < 1600 +#include "msinttypes/stdint.h" +#else +#include +#endif +/* only used here, let do a generic fixed point integers later if required by other + part of GD */ +typedef uint32_t gdFixed; +typedef int32_t gd_fixed_16_16; +typedef gd_fixed_16_16 gd_fixed_t; +#define gd_fixed_e ((gd_fixed_t) 1) +#define gd_fixed_1 (gd_int_to_fixed(1)) +#define gd_fixed_1_minus_e (gd_fixed_1 - gd_fixed_e) +#define gd_fixed_minus_1 (pixman_int_to_fixed(-1)) +#define gd_fixed_to_int(f) ((int) ((f) >> 16)) +#define gd_int_to_fixed(i) ((gd_fixed_t) (i) * (gd_fixed_t) 65536) +#define gd_fixed_to_double(f) (double) ((f) / (double) gd_fixed_1) +#define gd_double_to_fixed(d) ((gd_fixed_t) ((d) * 65536.0)) +#define gd_fixed_frac(f) ((f) & gd_fixed_1_minus_e) +#define gd_fixed_floor(f) ((f) & ~gd_fixed_1_minus_e) +#define gd_fixed_ceil(f) gd_fixed_floor ((f) + gd_fixed_1_minus_e) +#define gd_fixed_fraction(f) ((f) & gd_fixed_1_minus_e) +#define gd_fixed_mod_2(f) ((f) & (gd_fixed1 | gd_fixed_1_minus_e)) + +#endif //GD_GD_FIXED_H diff --git a/ext/gd/libgd/gd_gd.c b/ext/gd/libgd/gd_gd.c index d5fe16a9af16..ebf2b592bcad 100644 --- a/ext/gd/libgd/gd_gd.c +++ b/ext/gd/libgd/gd_gd.c @@ -1,8 +1,60 @@ -#include +/** + * File: GD IO + * + * Read and write GD images. + * + * The GD image format is a proprietary image format of libgd. *It has to be* + * *regarded as being obsolete, and should only be used for development and* + * *testing purposes.* + * + * Structure of a GD image file: + * - file header + * - color header (either truecolor or palette) + * - image data + * + * All numbers are stored in big-endian format. Note that all GD output is done + * in the GD 2.x format (not to be confused with the GD2 format), but input may + * also be in the GD 1.x format. + * + * GD 1.x file header structure: + * width - 1 word + * height - 1 word + * + * GD 1.x color header (palette only): + * count - 1 byte (the number of used palette colors) + * transparent - 1 word (257 signals no transparency) + * palette - 256×3 bytes (RGB triplets) + * + * GD 2.x file header structure: + * signature - 1 word ("\xFF\xFE" for truecolor, "\xFF\xFF" for palette) + * width - 1 word + * height - 1 word + * + * GD 2.x truecolor image color header: + * truecolor - 1 byte (always "\001") + * transparent - 1 dword (ARGB color); "\377\377\377\377" means that no + * transparent color is set + * + * GD 2.x palette image color header: + * truecolor - 1 byte (always "\0") + * count - 1 word (the number of used palette colors) + * transparent - 1 dword (palette index); "\377\377\377\377" means that no + * transparent color is set + * palette - 256 dwords (RGBA colors) + * + * Image data: + * Sequential pixel data; row-major from top to bottom, left to right: + * - 1 byte per pixel for palette images + * - 1 dword (ARGB) per pixel for truecolor images + */ + + +#include "gd.h" +#include "gd_errors.h" #include -#include +#include #include -#include "gd.h" +#include #define TRUE 1 #define FALSE 0 @@ -16,11 +68,10 @@ extern void gdImageGd (gdImagePtr im, FILE * out); /*#define GD2_DBG(s) (s) */ #define GD2_DBG(s) -/* */ -/* Shared code to read color tables from gd file. */ -/* */ -int _gdGetColors (gdIOCtx * in, gdImagePtr im, int gd2xFlag) -{ +/* + * Shared code to read color tables from gd file. + */ +int _gdGetColors(gdIOCtx *in, gdImagePtr im, int gd2xFlag) { int i; if (gd2xFlag) { int trueColorFlag; @@ -28,9 +79,8 @@ int _gdGetColors (gdIOCtx * in, gdImagePtr im, int gd2xFlag) goto fail1; } /* 2.0.12: detect bad truecolor .gd files created by pre-2.0.12. - * Beginning in 2.0.12 truecolor is indicated by the initial 2-byte - * signature. - */ + Beginning in 2.0.12 truecolor is indicated by the initial 2-byte + signature. */ if (trueColorFlag != im->trueColor) { goto fail1; } @@ -54,18 +104,16 @@ int _gdGetColors (gdIOCtx * in, gdImagePtr im, int gd2xFlag) if (!gdGetWord(&im->transparent, in)) { goto fail1; } - if (im->transparent == 257) { - im->transparent = (-1); - } } - + /* Make sure transparent index is within bounds of the palette. */ + if (!(im->trueColor) && (im->transparent >= im->colorsTotal || im->transparent < 0)) { + im->transparent = (-1); + } GD2_DBG(printf("Palette had %d colours (T=%d)\n", im->colorsTotal, im->transparent)); - if (im->trueColor) { return TRUE; } - - for (i = 0; i < gdMaxColors; i++) { + for (i = 0; (i < gdMaxColors); i++) { if (!gdGetByte(&im->red[i], in)) { goto fail1; } @@ -82,7 +130,7 @@ int _gdGetColors (gdIOCtx * in, gdImagePtr im, int gd2xFlag) } } - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < im->colorsTotal); i++) { im->open[i] = 0; } @@ -94,8 +142,7 @@ int _gdGetColors (gdIOCtx * in, gdImagePtr im, int gd2xFlag) /* */ /* Use the common basic header info to make the image object. */ /* */ -static gdImagePtr _gdCreateFromFile (gdIOCtx * in, int *sx, int *sy) -{ +static gdImagePtr _gdCreateFromFile(gdIOCtx *in, int *sx, int *sy) { gdImagePtr im; int gd2xFlag = 0; int trueColorFlag = 0; @@ -103,7 +150,7 @@ static gdImagePtr _gdCreateFromFile (gdIOCtx * in, int *sx, int *sy) if (!gdGetWord(sx, in)) { goto fail1; } - if (*sx == 65535 || *sx == 65534) { + if ((*sx == 65535) || (*sx == 65534)) { /* This is a gd 2.0 .gd file */ gd2xFlag = 1; /* 2.0.12: 65534 signals a truecolor .gd file. There is a slight redundancy here but we can live with it. */ @@ -139,12 +186,56 @@ static gdImagePtr _gdCreateFromFile (gdIOCtx * in, int *sx, int *sy) return 0; } -gdImagePtr gdImageCreateFromGd (FILE * inFile) -{ +/* + Function: gdImageCreateFromGd + + is called to load images from gd format + files. Invoke with an already opened pointer + to a file containing the desired image in the gd file format, + which is specific to gd and intended for very fast loading. (It is + not intended for compression; for compression, use PNG or JPEG.) + + returns a to the new image, or + NULL if unable to load the image (most often because the file is + corrupt or does not contain a gd format + image). does not close the file. You can + inspect the sx and sy members of the image to determine its + size. The image must eventually be destroyed using + . + + Variants: + + creates an image from GD data (i.e. the + contents of a GD file) already in memory. + + reads in an image using the functions in + a struct. + + Parameters: + + infile - The input FILE pointer + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + + > gdImagePtr im; + > FILE *in; + > in = fopen("mygd.gd", "rb"); + > im = gdImageCreateFromGd(in); + > fclose(in); + > // ... Use the image ... + > gdImageDestroy(im); +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd(FILE *inFile) { gdImagePtr im; gdIOCtx *in; in = gdNewFileCtx(inFile); + if (in == NULL) + return NULL; im = gdImageCreateFromGdCtx(in); in->gd_free(in); @@ -152,18 +243,34 @@ gdImagePtr gdImageCreateFromGd (FILE * inFile) return im; } -gdImagePtr gdImageCreateFromGdPtr (int size, void *data) -{ +/* + Function: gdImageCreateFromGdPtr + + Parameters: + + size - size of GD data in bytes. + data - GD data (i.e. contents of a GIF file). + + Reads in GD data from memory. See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGdPtr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) + return 0; im = gdImageCreateFromGdCtx(in); in->gd_free(in); return im; } -gdImagePtr gdImageCreateFromGdCtx (gdIOCtxPtr in) -{ +/* + Function: gdImageCreateFromGdCtx + + Reads in a GD image via a struct. See + . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGdCtx(gdIOCtxPtr in) { int sx, sy; int x, y; gdImagePtr im; @@ -178,8 +285,8 @@ gdImagePtr gdImageCreateFromGdCtx (gdIOCtxPtr in) /* Then the data... */ /* 2.0.12: support truecolor properly in .gd as well as in .gd2. Problem reported by Andreas Pfaller. */ if (im->trueColor) { - for (y = 0; y < sy; y++) { - for (x = 0; x < sx; x++) { + for (y = 0; (y < sy); y++) { + for (x = 0; (x < sx); x++) { int pix; if (!gdGetInt(&pix, in)) { goto fail2; @@ -188,8 +295,8 @@ gdImagePtr gdImageCreateFromGdCtx (gdIOCtxPtr in) } } } else { - for (y = 0; y < sy; y++) { - for (x = 0; x < sx; x++) { + for (y = 0; (y < sy); y++) { + for (x = 0; (x < sx); x++) { int ch; ch = gdGetC(in); if (ch == EOF) { @@ -209,8 +316,7 @@ gdImagePtr gdImageCreateFromGdCtx (gdIOCtxPtr in) return 0; } -void _gdPutColors (gdImagePtr im, gdIOCtx * out) -{ +void _gdPutColors(gdImagePtr im, gdIOCtx *out) { int i; gdPutC(im->trueColor, out); @@ -219,7 +325,7 @@ void _gdPutColors (gdImagePtr im, gdIOCtx * out) } gdPutInt(im->transparent, out); if (!im->trueColor) { - for (i = 0; i < gdMaxColors; i++) { + for (i = 0; (i < gdMaxColors); i++) { gdPutC((unsigned char) im->red[i], out); gdPutC((unsigned char) im->green[i], out); gdPutC((unsigned char) im->blue[i], out); @@ -228,8 +334,7 @@ void _gdPutColors (gdImagePtr im, gdIOCtx * out) } } -static void _gdPutHeader (gdImagePtr im, gdIOCtx * out) -{ +static void _gdPutHeader(gdImagePtr im, gdIOCtx *out) { /* 65535 indicates this is a gd 2.x .gd file. * 2.0.12: 65534 indicates truecolor. */ @@ -244,14 +349,13 @@ static void _gdPutHeader (gdImagePtr im, gdIOCtx * out) _gdPutColors(im, out); } -static void _gdImageGd (gdImagePtr im, gdIOCtx * out) -{ +static void _gdImageGd(gdImagePtr im, gdIOCtx *out) { int x, y; _gdPutHeader(im, out); - for (y = 0; y < im->sy; y++) { - for (x = 0; x < im->sx; x++) { + for (y = 0; (y < im->sy); y++) { + for (x = 0; (x < im->sx); x++) { /* ROW-MAJOR IN GD 1.3 */ if (im->trueColor) { gdPutInt(im->tpixels[y][x], out); @@ -262,17 +366,25 @@ static void _gdImageGd (gdImagePtr im, gdIOCtx * out) } } -void gdImageGd (gdImagePtr im, FILE * outFile) -{ +/* + Function: gdImageGd + */ +BGD_DECLARE(void) gdImageGd(gdImagePtr im, FILE *outFile) { gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; _gdImageGd(im, out); out->gd_free(out); } -void *gdImageGdPtr (gdImagePtr im, int *size) -{ +/* + Function: gdImageGdPtr + */ +BGD_DECLARE(void *) gdImageGdPtr(gdImagePtr im, int *size) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; _gdImageGd(im, out); rv = gdDPExtractData(out, size); out->gd_free(out); diff --git a/ext/gd/libgd/gd_gd2.c b/ext/gd/libgd/gd_gd2.c index 8e0307e5b120..ac40b430e1ba 100644 --- a/ext/gd/libgd/gd_gd2.c +++ b/ext/gd/libgd/gd_gd2.c @@ -10,14 +10,71 @@ * */ -#include -#include -#include -#include -#include +/** + * File: GD2 IO + * + * Read and write GD2 images. + * + * The GD2 image format is a proprietary image format of libgd. *It has to be* + * *regarded as being obsolete, and should only be used for development and* + * *testing purposes.* + * + * Structure of a GD2 image file: + * - file header + * - chunk headers (only for compressed data) + * - color header (either truecolor or palette) + * - chunks of image data (chunk-row-major, top to bottom, left to right) + * + * All numbers are stored in big-endian format. + * + * File header structure: + * signature - 4 bytes (always "gd2\0") + * version - 1 word (e.g. "\0\002") + * width - 1 word + * height - 1 word + * chunk_size - 1 word + * format - 1 word + * x_chunk_count - 1 word + * y_chunk_count - 1 word + * + * Recognized formats: + * 1 - raw palette image data + * 2 - compressed palette image data + * 3 - raw truecolor image data + * 4 - compressed truecolor image data + * + * Chunk header: + * offset - 1 dword + * size - 1 dword + * + * There are x_chunk_count * y_chunk_count chunk headers. + * + * Truecolor image color header: + * truecolor - 1 byte (always "\001") + * transparent - 1 dword (ARGB color); "\377\377\377\377" means that no + * transparent color is set + * + * Palette image color header: + * truecolor - 1 byte (always "\0") + * count - 1 word (the number of used palette colors) + * transparent - 1 dword (palette index); "\377\377\377\377" means that no + * transparent color is set + * palette - 256 dwords (RGBA colors) + * + * Chunk structure: + * Sequential pixel data of a rectangular area (chunk_size x chunk_size), + * row-major from top to bottom, left to right: + * - 1 byte per pixel for palette images + * - 1 dword (ARGB) per pixel for truecolor images + * + * Depending on format, the chunk may be ZLIB compressed. + */ #include "gd.h" #include "gd_errors.h" #include "gdhelpers.h" +#include +#include +#include #include @@ -40,8 +97,7 @@ /* #define GD2_DBG(s) (s) */ #define GD2_DBG(s) -typedef struct -{ +typedef struct { int offset; int size; } t_chunk_info; @@ -52,8 +108,7 @@ extern void _gdPutColors(gdImagePtr im, gdIOCtx * out); /* */ /* Read the extra info in the gd2 header. */ /* */ -static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, int *fmt, int *ncx, int *ncy, t_chunk_info ** chunkIdx) -{ +static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, int *fmt, int *ncx, int *ncy, t_chunk_info ** chunkIdx) { int i; int ch; char id[5]; @@ -67,9 +122,9 @@ static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, in ch = gdGetC(in); if (ch == EOF) { goto fail1; - } + }; id[i] = ch; - } + }; id[4] = 0; GD2_DBG(gd_error("Got file code: %s", id)); @@ -78,18 +133,18 @@ static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, in if (strcmp(id, GD2_ID) != 0) { GD2_DBG(gd_error("Not a valid gd2 file")); goto fail1; - } + }; /* Version */ if (gdGetWord(vers, in) != 1) { goto fail1; - } + }; GD2_DBG(gd_error("Version: %d", *vers)); if ((*vers != 1) && (*vers != 2)) { GD2_DBG(gd_error("Bad version: %d", *vers)); goto fail1; - } + }; /* Image Size */ if (!gdGetWord(sx, in)) { @@ -105,35 +160,35 @@ static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, in /* Chunk Size (pixels, not bytes!) */ if (gdGetWord(cs, in) != 1) { goto fail1; - } + }; GD2_DBG(gd_error("ChunkSize: %d", *cs)); if ((*cs < GD2_CHUNKSIZE_MIN) || (*cs > GD2_CHUNKSIZE_MAX)) { GD2_DBG(gd_error("Bad chunk size: %d", *cs)); goto fail1; - } + }; /* Data Format */ if (gdGetWord(fmt, in) != 1) { goto fail1; - } + }; GD2_DBG(gd_error("Format: %d", *fmt)); if ((*fmt != GD2_FMT_RAW) && (*fmt != GD2_FMT_COMPRESSED) && (*fmt != GD2_FMT_TRUECOLOR_RAW) && (*fmt != GD2_FMT_TRUECOLOR_COMPRESSED)) { GD2_DBG(gd_error("Bad data format: %d", *fmt)); goto fail1; - } + }; /* # of chunks wide */ if (gdGetWord(ncx, in) != 1) { goto fail1; - } + }; GD2_DBG(gd_error("%d Chunks Wide", *ncx)); /* # of chunks high */ if (gdGetWord(ncy, in) != 1) { goto fail1; - } + }; GD2_DBG(gd_error("%d Chunks vertically", *ncy)); if (gd2_compressed(*fmt)) { @@ -150,6 +205,7 @@ static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, in if (sidx <= 0) { goto fail1; } + cidx = gdCalloc(sidx, 1); if (cidx == NULL) { goto fail1; @@ -157,25 +213,23 @@ static int _gd2GetHeader(gdIOCtxPtr in, int *sx, int *sy, int *cs, int *vers, in for (i = 0; i < nc; i++) { if (gdGetInt(&cidx[i].offset, in) != 1) { - gdFree(cidx); - goto fail1; - } + goto fail2; + }; if (gdGetInt(&cidx[i].size, in) != 1) { - gdFree(cidx); - goto fail1; - } - if (cidx[i].offset < 0 || cidx[i].size < 0) { - gdFree(cidx); - goto fail1; - } - } + goto fail2; + }; + if (cidx[i].offset < 0 || cidx[i].size < 0 || + cidx[i].size == INT_MAX) + goto fail2; + }; *chunkIdx = cidx; - } + }; GD2_DBG(gd_error("gd2 header complete")); return 1; - +fail2: + gdFree(cidx); fail1: return 0; } @@ -241,12 +295,57 @@ static int _gd2ReadChunk (int offset, char *compBuf, int compSize, char *chunkBu return TRUE; } +/* + Function: gdImageCreateFromGd2 -gdImagePtr gdImageCreateFromGd2 (FILE * inFile) -{ + is called to load images from gd2 format + files. Invoke with an already opened + pointer to a file containing the desired image in the gd2 file + format, which is specific to gd2 and intended for fast loading of + parts of large images. (It is a compressed format, but generally + not as good as maximum compression of the entire image would be.) + + returns a to the new image, or + NULL if unable to load the image (most often because the file is + corrupt or does not contain a gd format + image). does not close the file. You can + inspect the sx and sy members of the image to determine its + size. The image must eventually be destroyed using + . + + + Variants: + + creates an image from GD data (i.e. the + contents of a GD2 file) already in memory. + + reads in an image using the functions in + a struct. + + Parameters: + + infile - The input FILE pointer + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + + > gdImagePtr im; + > FILE *in; + > in = fopen("mygd.gd2", "rb"); + > im = gdImageCreateFromGd2(in); + > fclose(in); + > // ... Use the image ... + > gdImageDestroy(im); +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2(FILE *inFile) { gdIOCtx *in = gdNewFileCtx(inFile); gdImagePtr im; + if (in == NULL) + return NULL; im = gdImageCreateFromGd2Ctx(in); in->gd_free(in); @@ -254,18 +353,34 @@ gdImagePtr gdImageCreateFromGd2 (FILE * inFile) return im; } -gdImagePtr gdImageCreateFromGd2Ptr (int size, void *data) -{ +/* + Function: gdImageCreateFromGd2Ptr + + Parameters: + + size - size of GD2 data in bytes. + data - GD2 data (i.e. contents of a GIF file). + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ptr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) + return 0; im = gdImageCreateFromGd2Ctx(in); in->gd_free(in); return im; } -gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) -{ +/* + Function: gdImageCreateFromGd2Ctx + + Reads in a GD2 image via a struct. See + . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Ctx(gdIOCtxPtr in) { int sx, sy; int i; int ncx, ncy, nc, cs, cx, cy; @@ -289,8 +404,14 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) } bytesPerPixel = im->trueColor ? 4 : 1; + if (overflow2(ncx, ncy)) + goto fail; nc = ncx * ncy; + if (overflow2(ncy, cs) || overflow2(ncx, cs) || + overflow2(bytesPerPixel, cs)) + goto fail; + if (gd2_compressed(fmt)) { /* Find the maximum compressed chunk size. */ compMax = 0; @@ -303,15 +424,19 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) /* Allocate buffers */ chunkMax = cs * bytesPerPixel * cs; - if (chunkMax <= 0) { - return 0; - } chunkBuf = gdCalloc(chunkMax, 1); + if (!chunkBuf) { + goto fail; + } compBuf = gdCalloc(compMax, 1); - - GD2_DBG(gd_error("Largest compressed chunk is %d bytes", compMax)); + if (!compBuf) { + goto fail; } + GD2_DBG(printf("Largest compressed chunk is %d bytes\n", compMax)); + }; + + /* Read the data... */ for (cy = 0; (cy < ncy); cy++) { for (cx = 0; (cx < ncx); cx++) { @@ -328,7 +453,7 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) if (!_gd2ReadChunk(chunkIdx[chunkNum].offset, compBuf, chunkIdx[chunkNum].size, (char *) chunkBuf, &chunkLen, in)) { GD2_DBG(gd_error("Error reading comproessed chunk")); - goto fail2; + goto fail; } chunkPos = 0; @@ -346,15 +471,13 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) if (im->trueColor) { if (!gdGetInt(&im->tpixels[y][x], in)) { gd_error("gd2: EOF while reading\n"); - gdImageDestroy(im); - return NULL; + goto fail; } } else { int ch; if (!gdGetByte(&ch, in)) { gd_error("gd2: EOF while reading\n"); - gdImageDestroy(im); - return NULL; + goto fail; } im->pixels[y][x] = ch; } @@ -394,7 +517,7 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) return im; -fail2: +fail: gdImageDestroy(im); if (chunkBuf) { gdFree(chunkBuf); @@ -409,30 +532,84 @@ gdImagePtr gdImageCreateFromGd2Ctx (gdIOCtxPtr in) return 0; } -gdImagePtr gdImageCreateFromGd2PartPtr (int size, void *data, int srcx, int srcy, int w, int h) -{ +/* + Function: gdImageCreateFromGd2Part + + is called to load parts of images from + gd2 format files. Invoked in the same way as , + but with extra parameters indicating the source (x, y) and + width/height of the desired image. + returns a to the new image, or NULL if unable to load + the image. The image must eventually be destroyed using + . + + Variants: + + creates an image from GD2 data + (i.e. the contents of a GD2 file) already in memory. + + reads in an image using the functions in + a struct. + + Parameters: + + infile - The input FILE pointer + srcx, srcy - The source X and Y coordinates + w, h - The resulting image's width and height + + Returns: + + A pointer to the new image or NULL if an error occurred. + +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2Part(FILE *inFile, int srcx, int srcy, int w, int h) { gdImagePtr im; - gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + gdIOCtx *in = gdNewFileCtx(inFile); + if (in == NULL) + return NULL; im = gdImageCreateFromGd2PartCtx(in, srcx, srcy, w, h); + in->gd_free(in); return im; } -gdImagePtr gdImageCreateFromGd2Part (FILE * inFile, int srcx, int srcy, int w, int h) -{ - gdImagePtr im; - gdIOCtx *in = gdNewFileCtx(inFile); +/* + Function: gdImageCreateFromGd2PartPtr - im = gdImageCreateFromGd2PartCtx(in, srcx, srcy, w, h); + Parameters: - in->gd_free(in); + size - size of GD data in bytes. + data - GD data (i.e. contents of a GIF file). + srcx, srcy - The source X and Y coordinates + w, h - The resulting image's width and height + Reads in part of a GD2 image file stored from memory. See + . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2PartPtr(int size, void *data, int srcx, int srcy, int w, int h) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) + return 0; + im = gdImageCreateFromGd2PartCtx(in, srcx, srcy, w, h); + in->gd_free(in); return im; } -gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, int h) -{ +/* + Function: gdImageCreateFromGd2PartCtx + + Parameters: + + in - The data source. + srcx, srcy - The source X and Y coordinates + w, h - The resulting image's width and height + + Reads in part of a GD2 data image file via a struct. See + . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGd2PartCtx(gdIOCtx *in, int srcx, int srcy, int w, int h) { int scx, scy, ecx, ecy, fsx, fsy; int nc, ncx, ncy, cs, cx, cy; int x, y, ylo, yhi, xlo, xhi; @@ -451,7 +628,6 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, char *compBuf = NULL; gdImagePtr im; - if (w<1 || h <1) { return 0; } @@ -504,7 +680,13 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, } chunkBuf = gdCalloc(chunkMax, 1); + if (!chunkBuf) { + goto fail2; + } compBuf = gdCalloc(compMax, 1); + if (!compBuf) { + goto fail2; + } } /* Work out start/end chunks */ @@ -538,7 +720,7 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, yhi = fsy; } - for (cx = scx; cx <= ecx; cx++) { + for (cx = scx; (cx <= ecx); cx++) { xlo = cx * cs; xhi = xlo + cs; @@ -575,7 +757,6 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, } GD2_DBG(gd_error(" into (%d, %d) - (%d, %d)", xlo, ylo, xhi, yhi)); - for (y = ylo; (y < yhi); y++) { for (x = xlo; x < xhi; x++) { if (!gd2_compressed(fmt)) { @@ -622,7 +803,6 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, if (chunkIdx) { gdFree(chunkIdx); } - return im; fail2: @@ -637,7 +817,6 @@ gdImagePtr gdImageCreateFromGd2PartCtx (gdIOCtx * in, int srcx, int srcy, int w, if (chunkIdx) { gdFree(chunkIdx); } - return 0; } @@ -661,8 +840,9 @@ static void _gd2PutHeader (gdImagePtr im, gdIOCtx * out, int cs, int fmt, int cx gdPutWord(cy, out); } -static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) -{ +/* returns 0 on success, 1 on failure */ +static int _gdImageGd2(gdImagePtr im, gdIOCtx *out, int cs, int fmt) { + int ret = 0; int ncx, ncy, cx, cy; int x, y, ylo, yhi, xlo, xhi; int chunkLen; @@ -707,20 +887,21 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) if (gd2_compressed(fmt)) { /* Work out size of buffer for compressed data, If CHUNKSIZE is large, * then these will be large! - */ - - /* The zlib notes say output buffer size should be (input size) * 1.01 * 12 + * The zlib notes say output buffer size should be (input size) * 1.01 * 12 * - we'll use 1.02 to be paranoid. */ compMax = (int)(cs * bytesPerPixel * cs * 1.02f) + 12; - /* Allocate the buffers. */ - chunkData = safe_emalloc(cs * bytesPerPixel, cs, 0); - memset(chunkData, 0, cs * bytesPerPixel * cs); - if (compMax <= 0) { + chunkData = gdCalloc(cs * bytesPerPixel * cs, 1); + if (!chunkData) { + ret = 1; goto fail; } compData = gdCalloc(compMax, 1); + if (!compData) { + ret = 1; + goto fail; + } /* Save the file position of chunk index, and allocate enough space for * each chunk_info block . @@ -730,8 +911,11 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) GD2_DBG(gd_error("Index size is %d", idxSize)); gdSeek(out, idxPos + idxSize); - chunkIdx = safe_emalloc(idxSize, sizeof(t_chunk_info), 0); - memset(chunkIdx, 0, idxSize * sizeof(t_chunk_info)); + chunkIdx = gdCalloc(idxSize * sizeof(t_chunk_info), 1); + if (!chunkIdx) { + ret = 1; + goto fail; + } } _gdPutColors (im, out); @@ -750,7 +934,6 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) GD2_DBG(gd_error("Processing Chunk (%dx%d), y from %d to %d", cx, cy, ylo, yhi)); chunkLen = 0; for (y = ylo; (y < yhi); y++) { - GD2_DBG(gd_error("y=%d: ",y)); xlo = cx * cs; xhi = xlo + cs; if (xhi > im->sx) { @@ -759,7 +942,6 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) if (gd2_compressed(fmt)) { for (x = xlo; x < xhi; x++) { - GD2_DBG(gd_error("%d...",x)); if (im->trueColor) { int p = im->tpixels[y][x]; chunkData[chunkLen++] = gdTrueColorGetAlpha(p); @@ -772,8 +954,6 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) } } else { for (x = xlo; x < xhi; x++) { - GD2_DBG(gd_error("%d, ",x)); - if (im->trueColor) { gdPutInt(im->tpixels[y][x], out); } else { @@ -781,9 +961,7 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) } } } - GD2_DBG(gd_error("y=%d done.",y)); } - if (gd2_compressed(fmt)) { compLen = compMax; if (compress((unsigned char *) &compData[0], &compLen, (unsigned char *) &chunkData[0], chunkLen) != Z_OK) { @@ -801,7 +979,6 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) } } } - if (gd2_compressed(fmt)) { /* Save the position, write the index, restore position (paranoia). */ GD2_DBG(gd_error("Seeking %d to write index", idxPos)); @@ -827,25 +1004,36 @@ static void _gdImageGd2 (gdImagePtr im, gdIOCtx * out, int cs, int fmt) gdFree(chunkIdx); } GD2_DBG(gd_error("Done")); + + return ret; } -void gdImageGd2 (gdImagePtr im, FILE * outFile, int cs, int fmt) -{ +/* + Function: gdImageGd2 +*/ +BGD_DECLARE(void) gdImageGd2(gdImagePtr im, FILE *outFile, int cs, int fmt) { gdIOCtx *out = gdNewFileCtx(outFile); - + if (out == NULL) + return; _gdImageGd2(im, out, cs, fmt); - out->gd_free(out); } -void *gdImageGd2Ptr (gdImagePtr im, int cs, int fmt, int *size) -{ +/* + Function: gdImageGd2Ptr +*/ +BGD_DECLARE(void *) gdImageGd2Ptr(gdImagePtr im, int cs, int fmt, int *size) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; - _gdImageGd2(im, out, cs, fmt); - rv = gdDPExtractData(out, size); - out->gd_free(out); + if (_gdImageGd2(im, out, cs, fmt)) { + rv = NULL; + } else { + rv = gdDPExtractData(out, size); + } + out->gd_free(out); return rv; } diff --git a/ext/gd/libgd/gd_gif_in.c b/ext/gd/libgd/gd_gif_in.c index 0204c4158db4..88adf33ae008 100644 --- a/ext/gd/libgd/gd_gif_in.c +++ b/ext/gd/libgd/gd_gif_in.c @@ -1,8 +1,15 @@ -#include +/** + * File: GIF Input + * + * Read GIF images. + */ +#include "gd.h" +#include "gdhelpers.h" #include +#include +#include #include #include -#include "gd.h" #include "gd_errors.h" #include "php.h" @@ -16,8 +23,7 @@ static int verbose_set = 0; static int verbose; #define VERBOSE (verbose_set?verbose:set_verbose()) -static int set_verbose(void) -{ +static int set_verbose(void) { verbose = !!getenv("GIF_VERBOSE"); verbose_set = 1; return(verbose); @@ -29,7 +35,6 @@ static int set_verbose(void) #endif - #define MAXCOLORMAPSIZE 256 #define TRUE 1 @@ -43,9 +48,10 @@ static int set_verbose(void) #define INTERLACE 0x40 #define LOCALCOLORMAP 0x80 + #define BitSet(byte, bit) (((byte) & (bit)) == (bit)) -#define ReadOK(file,buffer,len) (gdGetBuf(buffer, len, file) > 0) +#define ReadOK(file, buffer, len) (gdGetBuf(buffer, len, file) == len) #define LM_to_uint(a,b) (((b)<<8)|(a)) @@ -77,7 +83,10 @@ static struct { typedef struct { unsigned char buf[CSD_BUF_SIZE]; - int curbit, lastbit, done, last_byte; + int curbit; + int lastbit; + int done; + int last_byte; } CODE_STATIC_DATA; typedef struct { @@ -92,41 +101,836 @@ typedef struct { } LZW_STATIC_DATA; static int ReadColorMap (gdIOCtx *fd, int number, unsigned char (*buffer)[256]); -static int DoExtension (gdIOCtx *fd, int label, int *Transparent, int *ZeroDataBlockP); +static int DoExtension(gdIOCtx *fd, int label, int *Transparent, + int *ZeroDataBlockP); static int GetDataBlock (gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP); -static int GetCode (gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP); -static int LWZReadByte (gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP); +static int GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, + int *ZeroDataBlockP); +static int LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, + int input_code_size, int *ZeroDataBlockP); -static void ReadImage (gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap)[256], int interlace, int *ZeroDataBlockP); /*1.4//, int ignore); */ +static int ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, + unsigned char (*cmap)[256], int colorCount, int interlace, + int *ZeroDataBlockP); /*1.4//, int ignore); */ -gdImagePtr gdImageCreateFromGifSource(gdSourcePtr inSource) /* {{{ */ -{ - gdIOCtx *in = gdNewSSCtx(inSource, NULL); - gdImagePtr im; +typedef struct { + int transparent; + int delay; + int disposal; +} GifGraphicControl; + +typedef struct gdGifReadStruct { + gdIOCtxPtr in; + int ownsCtx; + int done; + int error; + int pendingSeparator; + int frameIndex; + int screenWidth; + int screenHeight; + int backgroundIndex; + int haveGlobalColormap; + int globalColorCount; + int loopCount; + unsigned char globalColorMap[3][MAXCOLORMAPSIZE]; + unsigned char localColorMap[3][MAXCOLORMAPSIZE]; + GifGraphicControl gce; + gdImagePtr rawFrame; + gdImagePtr canvas; + gdImagePtr previousCanvas; + gdGifFrameInfo lastInfo; +} gdGifRead; + +static void GifResetGraphicControl(GifGraphicControl *gce); +static void GifTrimColorTable(gdImagePtr im); +static int GifReadHeader(gdGifRead *gif); +static int GifPrimeFirstImage(gdGifRead *gif); +static int GifSkipSubBlocks(gdIOCtxPtr in, int *ZeroDataBlockP); +static int GifReadApplicationExtension(gdGifRead *gif, int *ZeroDataBlockP); +static int GifReadExtension(gdGifRead *gif, int label, int *ZeroDataBlockP); +static void GifFillFrameInfo(gdGifRead *gif, gdGifFrameInfo *info); +static int GifFrameToColor(gdImagePtr frame, int color); +static int GifBackgroundColor(gdGifRead *gif, int transparentIndex); +static int GifEnsureCanvas(gdGifRead *gif, int transparentIndex); +static gdImagePtr GifCloneImage(gdImagePtr src); +static void GifApplyPreviousDisposal(gdGifRead *gif); +static int GifCompositeFrame(gdGifRead *gif); +static int GifProbeIsAnimated(gdIOCtxPtr in); + +static void GifResetGraphicControl(GifGraphicControl *gce) { + gce->transparent = -1; + gce->delay = 0; + gce->disposal = gdDisposalUnknown; +} - im = gdImageCreateFromGifCtx(in); +static void GifTrimColorTable(gdImagePtr im) { + int i; + for (i = im->colorsTotal - 1; i >= 0; i--) { + if (im->open[i]) { + im->colorsTotal--; + } else { + break; + } + } +} + +static int GifReadHeader(gdGifRead *gif) { + unsigned char buf[16]; + int bitPixel; + + memset(gif->globalColorMap, 0, 3 * MAXCOLORMAPSIZE); + memset(gif->localColorMap, 0, 3 * MAXCOLORMAPSIZE); + GifResetGraphicControl(&gif->gce); + gif->loopCount = 1; + + if (!ReadOK(gif->in, buf, 6)) { + return 0; + } + if (strncmp((char *)buf, "GIF", 3) != 0) { + return 0; + } + if (memcmp((char *)buf + 3, "87a", 3) != 0 && + memcmp((char *)buf + 3, "89a", 3) != 0) { + return 0; + } + if (!ReadOK(gif->in, buf, 7)) { + return 0; + } + + gif->screenWidth = LM_to_uint(buf[0], buf[1]); + gif->screenHeight = LM_to_uint(buf[2], buf[3]); + if (gif->screenWidth <= 0 || gif->screenHeight <= 0) { + return 0; + } + + gif->backgroundIndex = buf[5]; + bitPixel = 2 << (buf[4] & 0x07); + gif->globalColorCount = bitPixel; + gif->haveGlobalColormap = BitSet(buf[4], LOCALCOLORMAP); + if (gif->haveGlobalColormap) { + if (ReadColorMap(gif->in, bitPixel, gif->globalColorMap)) { + return 0; + } + } + + return 1; +} + +static int GifSkipSubBlocks(gdIOCtxPtr in, int *ZeroDataBlockP) { + unsigned char buf[256]; + int count; + + do { + count = GetDataBlock(in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + } while (count > 0); + + return 1; +} + +static int GifReadApplicationExtension(gdGifRead *gif, int *ZeroDataBlockP) { + unsigned char buf[256]; + int count; + + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + + if (count == 11 && memcmp(buf, "NETSCAPE2.0", 11) == 0) { + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + if (count >= 3 && buf[0] == 1) { + gif->loopCount = LM_to_uint(buf[1], buf[2]); + } + while (count > 0) { + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + } + return 1; + } + + while (count > 0) { + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + } + return 1; +} + +static int GifReadExtension(gdGifRead *gif, int label, int *ZeroDataBlockP) { + unsigned char buf[256]; + int count; + + if (label == 0xf9) { + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + if (count >= 4) { + gif->gce.disposal = (buf[0] >> 2) & 0x7; + if (gif->gce.disposal == 4) { + gif->gce.disposal = gdDisposalRestorePrevious; + } + gif->gce.delay = LM_to_uint(buf[1], buf[2]); + gif->gce.transparent = (buf[0] & 0x1) ? buf[3] : -1; + } + while (count > 0) { + count = GetDataBlock(gif->in, buf, ZeroDataBlockP); + if (count < 0) { + return 0; + } + } + return 1; + } + + if (label == 0xff) { + return GifReadApplicationExtension(gif, ZeroDataBlockP); + } + + return GifSkipSubBlocks(gif->in, ZeroDataBlockP); +} + +static int GifPrimeFirstImage(gdGifRead *gif) { + unsigned char c; + int ZeroDataBlock = FALSE; + + for (;;) { + if (!ReadOK(gif->in, &c, 1)) { + return 0; + } + if (c == ';') { + gif->done = 1; + return 1; + } + if (c == ',') { + gif->pendingSeparator = 1; + return 1; + } + if (c == '!') { + if (!ReadOK(gif->in, &c, 1) || + !GifReadExtension(gif, c, &ZeroDataBlock)) { + return 0; + } + continue; + } + } +} + +static void GifFillFrameInfo(gdGifRead *gif, gdGifFrameInfo *info) { + if (info != NULL) { + *info = gif->lastInfo; + } +} + +static int GifFrameToColor(gdImagePtr frame, int color) { + return gdTrueColorAlpha(frame->red[color], frame->green[color], + frame->blue[color], frame->alpha[color]); +} + +static int GifBackgroundColor(gdGifRead *gif, int transparentIndex) { + int bg = gif->backgroundIndex; + + if (bg == transparentIndex) { + return gdTrueColorAlpha(0, 0, 0, gdAlphaTransparent); + } + if (gif->haveGlobalColormap && bg >= 0 && bg < MAXCOLORMAPSIZE) { + return gdTrueColorAlpha( + gif->globalColorMap[CM_RED][bg], gif->globalColorMap[CM_GREEN][bg], + gif->globalColorMap[CM_BLUE][bg], gdAlphaOpaque); + } + return gdTrueColorAlpha(0, 0, 0, gdAlphaTransparent); +} + +static int GifEnsureCanvas(gdGifRead *gif, int transparentIndex) { + int x, y, bg; + + if (gif->canvas != NULL) { + return 1; + } + + gif->canvas = gdImageCreateTrueColor(gif->screenWidth, gif->screenHeight); + if (gif->canvas == NULL) { + return 0; + } + gdImageAlphaBlending(gif->canvas, 0); + gdImageSaveAlpha(gif->canvas, 1); + + bg = GifBackgroundColor(gif, transparentIndex); + for (y = 0; y < gif->screenHeight; y++) { + for (x = 0; x < gif->screenWidth; x++) { + gdImageSetPixel(gif->canvas, x, y, bg); + } + } + + return 1; +} + +static gdImagePtr GifCloneImage(gdImagePtr src) { + gdImagePtr dst; + int x, y; + + if (src == NULL) { + return NULL; + } + + dst = src->trueColor ? gdImageCreateTrueColor(src->sx, src->sy) + : gdImageCreate(src->sx, src->sy); + if (dst == NULL) { + return NULL; + } + + if (src->trueColor) { + gdImageAlphaBlending(dst, 0); + gdImageSaveAlpha(dst, src->saveAlphaFlag); + for (y = 0; y < src->sy; y++) { + for (x = 0; x < src->sx; x++) { + gdImageSetPixel(dst, x, y, gdImageGetPixel(src, x, y)); + } + } + } else { + for (x = 0; x < gdMaxColors; x++) { + dst->red[x] = src->red[x]; + dst->green[x] = src->green[x]; + dst->blue[x] = src->blue[x]; + dst->alpha[x] = src->alpha[x]; + dst->open[x] = src->open[x]; + } + dst->colorsTotal = src->colorsTotal; + dst->transparent = src->transparent; + for (y = 0; y < src->sy; y++) { + for (x = 0; x < src->sx; x++) { + gdImageSetPixel(dst, x, y, gdImageGetPixel(src, x, y)); + } + } + } + + return dst; +} + +static void GifApplyPreviousDisposal(gdGifRead *gif) { + gdGifFrameInfo *info = &gif->lastInfo; + int x, y, bg; + + if (gif->canvas == NULL || gif->frameIndex <= 0) { + return; + } + + if (info->disposal == gdDisposalRestoreBackground) { + bg = GifBackgroundColor(gif, info->transparentIndex); + for (y = info->y; y < info->y + info->height; y++) { + for (x = info->x; x < info->x + info->width; x++) { + gdImageSetPixel(gif->canvas, x, y, bg); + } + } + } else if (info->disposal == gdDisposalRestorePrevious && + gif->previousCanvas != NULL) { + for (y = info->y; y < info->y + info->height; y++) { + for (x = info->x; x < info->x + info->width; x++) { + gdImageSetPixel(gif->canvas, x, y, + gdImageGetPixel(gif->previousCanvas, x, y)); + } + } + } + + if (gif->previousCanvas != NULL) { + gdImageDestroy(gif->previousCanvas); + gif->previousCanvas = NULL; + } +} + +static int GifCompositeFrame(gdGifRead *gif) { + gdGifFrameInfo *info = &gif->lastInfo; + int x, y, c; + + if (!GifEnsureCanvas(gif, info->transparentIndex)) { + return 0; + } + + if (info->disposal == gdDisposalRestorePrevious) { + gif->previousCanvas = GifCloneImage(gif->canvas); + if (gif->previousCanvas == NULL) { + return 0; + } + } + + for (y = 0; y < info->height; y++) { + for (x = 0; x < info->width; x++) { + c = gdImageGetPixel(gif->rawFrame, x, y); + if (c == info->transparentIndex) { + continue; + } + gdImageSetPixel(gif->canvas, info->x + x, info->y + y, + GifFrameToColor(gif->rawFrame, c)); + } + } + + return 1; +} + +static int GifProbeIsAnimated(gdIOCtxPtr in) { + unsigned char buf[16], c; + int bitPixel, frameCount = 0, zero = 0; + + if (in == NULL || !ReadOK(in, buf, 6)) { + return -1; + } + if (strncmp((char *)buf, "GIF", 3) != 0 || + (memcmp((char *)buf + 3, "87a", 3) != 0 && + memcmp((char *)buf + 3, "89a", 3) != 0)) { + return -1; + } + if (!ReadOK(in, buf, 7)) { + return -1; + } + if (LM_to_uint(buf[0], buf[1]) <= 0 || LM_to_uint(buf[2], buf[3]) <= 0) { + return -1; + } + bitPixel = 2 << (buf[4] & 0x07); + if (BitSet(buf[4], LOCALCOLORMAP)) { + while (bitPixel-- > 0) { + if (!ReadOK(in, buf, 3)) { + return -1; + } + } + } + + for (;;) { + if (!ReadOK(in, &c, 1)) { + return -1; + } + if (c == ';') { + return frameCount > 1 ? 1 : 0; + } + if (c == '!') { + if (!ReadOK(in, &c, 1) || !GifSkipSubBlocks(in, &zero)) { + return -1; + } + continue; + } + if (c == ',') { + int localColorCount; + if (!ReadOK(in, buf, 9)) { + return -1; + } + localColorCount = + BitSet(buf[8], LOCALCOLORMAP) ? (2 << (buf[8] & 0x07)) : 0; + while (localColorCount-- > 0) { + if (!ReadOK(in, buf, 3)) { + return -1; + } + } + if (!ReadOK(in, &c, 1) || !GifSkipSubBlocks(in, &zero)) { + return -1; + } + frameCount++; + if (frameCount > 1) { + return 1; + } + continue; + } + return -1; + } +} + +BGD_DECLARE(int) gdGifIsAnimated(FILE *fdFile) { + gdIOCtx *fd; + int result, pos; + + if (fdFile == NULL) { + return -1; + } + fd = gdNewFileCtx(fdFile); + if (fd == NULL) { + return -1; + } + pos = (int)gdTell(fd); + if (pos < 0) { + fd->gd_free(fd); + return -1; + } + result = GifProbeIsAnimated(fd); + if (!gdSeek(fd, pos)) { + result = -1; + } + fd->gd_free(fd); + return result; +} + +BGD_DECLARE(int) gdGifIsAnimatedCtx(gdIOCtxPtr in) { + int result, pos; + + if (in == NULL || in->tell == NULL || in->seek == NULL) { + return -1; + } + pos = (int)gdTell(in); + if (pos < 0) { + return -1; + } + result = GifProbeIsAnimated(in); + if (!gdSeek(in, pos)) { + return -1; + } + return result; +} + +BGD_DECLARE(int) gdGifIsAnimatedPtr(int size, void *data) { + gdIOCtx *in; + int result; + + if (size <= 0 || data == NULL) { + return -1; + } + in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) { + return -1; + } + result = GifProbeIsAnimated(in); in->gd_free(in); + return result; +} - return im; +BGD_DECLARE(gdGifReadPtr) gdGifReadOpen(FILE *fdFile) { + gdIOCtx *fd; + gdGifReadPtr gif; + + if (fdFile == NULL) { + return NULL; +} + fd = gdNewFileCtx(fdFile); + if (fd == NULL) { + return NULL; + } + gif = gdGifReadOpenCtx(fd); + if (gif == NULL) { + fd->gd_free(fd); + return NULL; + } + gif->ownsCtx = 1; + return gif; } -/* }}} */ -gdImagePtr gdImageCreateFromGif(FILE *fdFile) /* {{{ */ -{ +BGD_DECLARE(gdGifReadPtr) gdGifReadOpenPtr(int size, void *data) { + gdIOCtx *in; + gdGifReadPtr gif; + + if (size <= 0 || data == NULL) { + return NULL; + } + in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) { + return NULL; + } + gif = gdGifReadOpenCtx(in); + if (gif == NULL) { + in->gd_free(in); + return NULL; + } + gif->ownsCtx = 1; + return gif; +} + +BGD_DECLARE(gdGifReadPtr) gdGifReadOpenCtx(gdIOCtxPtr in) { + gdGifReadPtr gif; + + if (in == NULL) { + return NULL; + } + + gif = (gdGifReadPtr)gdCalloc(1, sizeof(gdGifRead)); + if (gif == NULL) { + return NULL; + } + gif->in = in; + gif->ownsCtx = 0; + GifResetGraphicControl(&gif->gce); + if (!GifReadHeader(gif) || !GifPrimeFirstImage(gif)) { + gdFree(gif); + return NULL; + } + + return gif; +} + +BGD_DECLARE(void) gdGifReadClose(gdGifReadPtr gif) { + if (gif == NULL) { + return; + } + if (gif->rawFrame != NULL) { + gdImageDestroy(gif->rawFrame); + } + if (gif->canvas != NULL) { + gdImageDestroy(gif->canvas); + } + if (gif->previousCanvas != NULL) { + gdImageDestroy(gif->previousCanvas); + } + if (gif->ownsCtx && gif->in != NULL) { + gif->in->gd_free(gif->in); + } + gdFree(gif); +} + +BGD_DECLARE(int) gdGifReadGetInfo(gdGifReadPtr gif, gdGifInfo *info) { + if (gif == NULL || info == NULL) { + return 0; + } + info->width = gif->screenWidth; + info->height = gif->screenHeight; + info->backgroundIndex = gif->backgroundIndex; + info->globalColorTable = gif->haveGlobalColormap; + info->loopCount = gif->loopCount; + return 1; +} + +BGD_DECLARE(int) +gdGifReadNextFrame(gdGifReadPtr gif, gdGifFrameInfo *info, gdImagePtr *frame) { + unsigned char buf[16], c; + int ZeroDataBlock = FALSE; + + if (frame != NULL) { + *frame = NULL; + } + if (gif == NULL || gif->error) { + return -1; + } + if (gif->done) { + return 0; + } + + for (;;) { + int top, left, width, height; + int useGlobalColormap, bitPixel, interlace, hasLocal; + + if (gif->pendingSeparator) { + c = ','; + gif->pendingSeparator = 0; + } else if (!ReadOK(gif->in, &c, 1)) { + gif->error = 1; + return -1; + } + if (c == ';') { + gif->done = 1; + return 0; + } + if (c == '!') { + if (!ReadOK(gif->in, &c, 1) || + !GifReadExtension(gif, c, &ZeroDataBlock)) { + gif->error = 1; + return -1; + } + continue; + } + if (c != ',') { + continue; + } + + if (!ReadOK(gif->in, buf, 9)) { + gif->error = 1; + return -1; + } + + hasLocal = BitSet(buf[8], LOCALCOLORMAP); + useGlobalColormap = !hasLocal; + bitPixel = 1 << ((buf[8] & 0x07) + 1); + left = LM_to_uint(buf[0], buf[1]); + top = LM_to_uint(buf[2], buf[3]); + width = LM_to_uint(buf[4], buf[5]); + height = LM_to_uint(buf[6], buf[7]); + interlace = BitSet(buf[8], INTERLACE); + + if (width <= 0 || height <= 0 || ((left + width) > gif->screenWidth) || + ((top + height) > gif->screenHeight)) { + gif->error = 1; + return -1; + } + if (useGlobalColormap && !gif->haveGlobalColormap) { + gif->globalColorMap[CM_RED][1] = 0xff; + gif->globalColorMap[CM_GREEN][1] = 0xff; + gif->globalColorMap[CM_BLUE][1] = 0xff; + } + + if (gif->rawFrame != NULL) { + gdImageDestroy(gif->rawFrame); + gif->rawFrame = NULL; + } + gif->rawFrame = gdImageCreate(width, height); + if (gif->rawFrame == NULL) { + gif->error = 1; + return -1; + } + gif->rawFrame->interlace = interlace; + + if (hasLocal) { + if (ReadColorMap(gif->in, bitPixel, gif->localColorMap) || + !ReadImage(gif->rawFrame, gif->in, width, height, + gif->localColorMap, bitPixel, interlace, + &ZeroDataBlock)) { + gif->error = 1; + return -1; + } + } else { + if (!ReadImage(gif->rawFrame, gif->in, width, height, + gif->globalColorMap, gif->globalColorCount, + interlace, &ZeroDataBlock)) { + gif->error = 1; + return -1; + } + } + + if (gif->gce.transparent != -1) { + gdImageColorTransparent(gif->rawFrame, gif->gce.transparent); + } + GifTrimColorTable(gif->rawFrame); + if (!gif->rawFrame->colorsTotal) { + gif->error = 1; + return -1; + } + + gif->lastInfo.frameIndex = gif->frameIndex; + gif->lastInfo.x = left; + gif->lastInfo.y = top; + gif->lastInfo.width = width; + gif->lastInfo.height = height; + gif->lastInfo.delay = gif->gce.delay; + gif->lastInfo.disposal = gif->gce.disposal; + gif->lastInfo.transparentIndex = gif->gce.transparent; + gif->lastInfo.localColorTable = hasLocal; + gif->lastInfo.interlace = interlace; + gif->frameIndex++; + GifFillFrameInfo(gif, info); + if (frame != NULL) { + *frame = gif->rawFrame; + } + GifResetGraphicControl(&gif->gce); + return 1; + } +} + +BGD_DECLARE(int) +gdGifReadNextImage(gdGifReadPtr gif, gdGifFrameInfo *info, gdImagePtr *image) { + int result; + + if (image != NULL) { + *image = NULL; + } + if (gif == NULL) { + return -1; + } + + GifApplyPreviousDisposal(gif); + result = gdGifReadNextFrame(gif, info, NULL); + if (result <= 0) { + return result; + } + if (!GifCompositeFrame(gif)) { + gif->error = 1; + return -1; + } + if (image != NULL) { + *image = gif->canvas; + } + return 1; +} + +BGD_DECLARE(gdImagePtr) gdGifReadCloneImage(gdGifReadPtr gif) { + if (gif == NULL) { + return NULL; + } + return GifCloneImage(gif->canvas); +} + +/* + Function: gdImageCreateFromGif + + is called to load images from GIF format + files. Invoke with an already opened + pointer to a file containing the desired + image. + + returns a to the new image, or + NULL if unable to load the image (most often because the file is + corrupt or does not contain a GIF image). + does not close the file. You can inspect the sx and sy members of + the image to determine its size. The image must eventually be + destroyed using . + + Variants: + + creates an image from GIF data (i.e. the + contents of a GIF file) already in memory. + + reads in an image using the functions in + a struct. + + Parameters: + + infile - The input FILE pointer + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + + > gdImagePtr im; + > ... inside a function ... + > FILE *in; + > in = fopen("mygif.gif", "rb"); + > im = gdImageCreateFromGif(in); + > fclose(in); + > // ... Use the image ... + > gdImageDestroy(im); + +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGif(FILE *fdFile) { gdIOCtx *fd = gdNewFileCtx(fdFile); - gdImagePtr im = 0; + gdImagePtr im; + if (fd == NULL) + return NULL; im = gdImageCreateFromGifCtx(fd); fd->gd_free(fd); return im; } -/* }}} */ -gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ -{ +/* + Function: gdImageCreateFromGifPtr + + Parameters: + + size - size of GIF data in bytes. + data - GIF data (i.e. contents of a GIF file). + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGifPtr(int size, void *data) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return 0; + } + im = gdImageCreateFromGifCtx(in); + in->gd_free(in); + return im; +} + +/* + Function: gdImageCreateFromGifCtx + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromGifCtx(gdIOCtxPtr fd) { int BitPixel; #if 0 int ColorResolution; @@ -140,21 +944,21 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ unsigned char localColorMap[3][MAXCOLORMAPSIZE]; int imw, imh, screen_width, screen_height; int useGlobalColormap; - int bitPixel; - int i; + int bitPixel, i; /*1.4//int imageCount = 0; */ - + /* 2.0.28: threadsafe storage */ int ZeroDataBlock = FALSE; int haveGlobalColormap; + gdImagePtr im = 0; memset(ColorMap, 0, 3 * MAXCOLORMAPSIZE); memset(localColorMap, 0, 3 * MAXCOLORMAPSIZE); - /*1.4//imageNumber = 1; */ if (! ReadOK(fd,buf,6)) { return 0; } + if (strncmp((char *)buf,"GIF",3) != 0) { return 0; } @@ -194,6 +998,7 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ if (! ReadOK(fd,&c,1)) { return 0; } + if (c == ';') { /* GIF terminator */ goto terminated; } @@ -202,6 +1007,7 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ if (! ReadOK(fd,&c,1)) { return 0; } + DoExtension(fd, c, &Transparent, &ZeroDataBlock); continue; } @@ -224,7 +1030,8 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ width = LM_to_uint(buf[4], buf[5]); height = LM_to_uint(buf[6], buf[7]); - if (left + width > screen_width || top + height > screen_height) { + if (((left + width) > screen_width) || + ((top + height) > screen_height)) { if (VERBOSE) { printf("Frame is not confined to screen dimension.\n"); } @@ -234,14 +1041,24 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ if (!(im = gdImageCreate(width, height))) { return 0; } + im->interlace = BitSet(buf[8], INTERLACE); if (!useGlobalColormap) { if (ReadColorMap(fd, bitPixel, localColorMap)) { gdImageDestroy(im); return 0; } - ReadImage(im, fd, width, height, localColorMap, - BitSet(buf[8], INTERLACE), &ZeroDataBlock); + + /* Legacy gdImageCreateFromGif* is intentionally tolerant of + * out-of-palette LZW results and maps them to color 0. The newer + * iterator API passes the actual color table size and rejects those + * malformed frames instead. + */ + if (!ReadImage(im, fd, width, height, localColorMap, 0, + BitSet(buf[8], INTERLACE), &ZeroDataBlock)) { + gdImageDestroy(im); + return 0; + } } else { if (!haveGlobalColormap) { // Still a valid gif, apply simple default palette as per spec @@ -249,13 +1066,21 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ ColorMap[CM_GREEN][1] = 0xff; ColorMap[CM_BLUE][1] = 0xff; } - ReadImage(im, fd, width, height, - ColorMap, - BitSet(buf[8], INTERLACE), &ZeroDataBlock); + + /* Keep legacy tolerance here as above; strict validation is used by + * the animated GIF iterator. + */ + if (!ReadImage(im, fd, width, height, ColorMap, 0, + BitSet(buf[8], INTERLACE), &ZeroDataBlock)) { + gdImageDestroy(im); + return 0; } + } + if (Transparent != (-1)) { gdImageColorTransparent(im, Transparent); } + goto terminated; } @@ -264,76 +1089,77 @@ gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */ if (!im) { return 0; } + /* Check for open colors at the end, so - we can reduce colorsTotal and ultimately - BitsPerPixel */ - for (i=((im->colorsTotal-1)); (i>=0); i--) { + * we can reduce colorsTotal and ultimately + * BitsPerPixel */ + for (i = im->colorsTotal - 1; i >= 0; i--) { if (im->open[i]) { im->colorsTotal--; } else { break; } } + if (!im->colorsTotal) { gdImageDestroy(im); return 0; } + return im; } -/* }}} */ -static int ReadColorMap(gdIOCtx *fd, int number, unsigned char (*buffer)[256]) /* {{{ */ -{ +static int ReadColorMap(gdIOCtx *fd, int number, unsigned char (*buffer)[256]) { int i; unsigned char rgb[3]; - for (i = 0; i < number; ++i) { if (! ReadOK(fd, rgb, sizeof(rgb))) { return TRUE; } + buffer[CM_RED][i] = rgb[0] ; buffer[CM_GREEN][i] = rgb[1] ; buffer[CM_BLUE][i] = rgb[2] ; } - return FALSE; } -/* }}} */ -static int -DoExtension(gdIOCtx *fd, int label, int *Transparent, int *ZeroDataBlockP) -{ +static int DoExtension(gdIOCtx *fd, int label, int *Transparent, + int *ZeroDataBlockP) { unsigned char buf[256]; switch (label) { case 0xf9: /* Graphic Control Extension */ - memset(buf, 0, 4); /* initialize a few bytes in the case the next function fails */ + memset( + buf, 0, + 4); /* initialize a few bytes in the case the next function fails */ (void) GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP); #if 0 Gif89.disposal = (buf[0] >> 2) & 0x7; Gif89.inputFlag = (buf[0] >> 1) & 0x1; Gif89.delayTime = LM_to_uint(buf[1],buf[2]); #endif - if ((buf[0] & 0x1) != 0) + if ((buf[0] & 0x1) != 0) { *Transparent = buf[3]; + } + + while (GetDataBlock(fd, (unsigned char *)buf, ZeroDataBlockP) > 0) + ; + return FALSE; - while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0); - return FALSE; default: break; } - while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0) + + while (GetDataBlock(fd, (unsigned char *)buf, ZeroDataBlockP) > 0) ; return FALSE; } -/* }}} */ -static int -GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP) -{ +static int GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP) { unsigned char count; if (! ReadOK(fd,&count,1)) { @@ -348,35 +1174,28 @@ GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP) return count; } -/* }}} */ -static int -GetDataBlock(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP) -{ - int rv; - int i; +static int GetDataBlock(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP) { + int rv, i; rv = GetDataBlock_(fd,buf, ZeroDataBlockP); + if (VERBOSE) { - char *tmp = NULL; + printf("[GetDataBlock returning %d", rv); if (rv > 0) { - tmp = safe_emalloc(3 * rv, sizeof(char), 1); + printf(":"); for (i=0;ibuf[0] = scd->buf[scd->last_byte-2]; scd->buf[1] = scd->buf[scd->last_byte-1]; + scd->last_byte = 2; + scd->curbit = (scd->curbit - scd->lastbit) + 16; + scd->lastbit = 16; + + do { + if (scd->last_byte > (CSD_BUF_SIZE - 255)) { + return -1; + } - if ((count = GetDataBlock(fd, &scd->buf[2], ZeroDataBlockP)) <= 0) + if ((count = GetDataBlock(fd, &scd->buf[scd->last_byte], + ZeroDataBlockP)) <= 0) { scd->done = TRUE; + break; + } - scd->last_byte = 2 + count; - scd->curbit = (scd->curbit - scd->lastbit) + 16; - scd->lastbit = (2+count)*8 ; + scd->last_byte += count; + scd->lastbit += count * 8; + } while ((scd->curbit + code_size) > scd->lastbit); + } + + if ((scd->curbit + code_size) > scd->lastbit) { + return -1; } if ((scd->curbit + code_size - 1) >= (CSD_BUF_SIZE * 8)) { @@ -416,23 +1251,25 @@ GetCode_(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroD } scd->curbit += code_size; + return ret; } -static int -GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP) -{ +static int GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, + int *ZeroDataBlockP) { int rv; rv = GetCode_(fd, scd, code_size,flag, ZeroDataBlockP); - if (VERBOSE) printf("[GetCode(,%d,%d) returning %d]\n",code_size,flag,rv); - return(rv); + + if (VERBOSE) { + printf("[GetCode(,%d,%d) returning %d]\n", code_size, flag, rv); } -/* }}} */ -static int -LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP) -{ + return rv; +} + +static int LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, + int input_code_size, int *ZeroDataBlockP) { int code, incode, i; if (flag) { @@ -451,51 +1288,64 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i sd->table[0][i] = 0; sd->table[1][i] = i; } - for (; i < (1<table[0][i] = sd->table[1][0] = 0; + } sd->sp = sd->stack; return 0; + } else if (sd->fresh) { sd->fresh = FALSE; + do { sd->firstcode = sd->oldcode = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP); } while (sd->firstcode == sd->clear_code); + return sd->firstcode; } - if (sd->sp > sd->stack) + if (sd->sp > sd->stack) { return *--sd->sp; + } - while ((code = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP)) >= 0) { + while ((code = GetCode(fd, &sd->scd, sd->code_size, FALSE, + ZeroDataBlockP)) >= 0) { if (code == sd->clear_code) { for (i = 0; i < sd->clear_code; ++i) { sd->table[0][i] = 0; sd->table[1][i] = i; } - for (; i < (1<table[0][i] = sd->table[1][i] = 0; + } + sd->code_size = sd->set_code_size+1; sd->max_code_size = 2*sd->clear_code; sd->max_code = sd->clear_code+2; sd->sp = sd->stack; sd->firstcode = sd->oldcode = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP); + return sd->firstcode; } else if (code == sd->end_code) { int count; unsigned char buf[260]; - if (*ZeroDataBlockP) + if (*ZeroDataBlockP) { return -2; + } while ((count = GetDataBlock(fd, buf, ZeroDataBlockP)) > 0) ; - if (count != 0) + if (count != 0) { return -2; + } } incode = code; @@ -515,12 +1365,23 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i /* Bad compressed data stream */ return -1; } + if (code >= (1 << MAX_LWZ_BITS)) { + /* Corrupted code */ + return -1; + } + *sd->sp++ = sd->table[1][code]; + if (code == sd->table[0][code]) { /* Oh well */ } + code = sd->table[0][code]; } + if (code >= (1 << MAX_LWZ_BITS)) { + /* Corrupted code */ + return -1; + } *sd->sp++ = sd->firstcode = sd->table[1][code]; @@ -528,6 +1389,7 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i sd->table[0][code] = sd->oldcode; sd->table[1][code] = sd->firstcode; ++sd->max_code; + if ((sd->max_code >= sd->max_code_size) && (sd->max_code_size < (1<max_code_size *= 2; @@ -537,43 +1399,44 @@ LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, i sd->oldcode = incode; - if (sd->sp > sd->stack) + if (sd->sp > sd->stack) { return *--sd->sp; } + } + return code; } -/* }}} */ -static int -LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP) -{ +static int LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, + int input_code_size, int *ZeroDataBlockP) { int rv; rv = LWZReadByte_(fd, sd, flag, input_code_size, ZeroDataBlockP); - if (VERBOSE) printf("[LWZReadByte(,%d,%d) returning %d]\n",flag,input_code_size,rv); - return(rv); + + if (VERBOSE) { + printf("[LWZReadByte(,%d,%d) returning %d]\n", flag, input_code_size, + rv); +} + + return rv; } -/* }}} */ -static void -ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap)[256], int interlace, int *ZeroDataBlockP) /*1.4//, int ignore) */ +static int ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, + unsigned char (*cmap)[256], int colorCount, int interlace, + int *ZeroDataBlockP) /*1.4//, int ignore) */ { unsigned char c; - int v; int xpos = 0, ypos = 0, pass = 0; - int i; + int v, i; LZW_STATIC_DATA sd; - - /* - ** Initialize the Compression routines - */ + /* Initialize the Compression routines */ if (! ReadOK(fd,&c,1)) { - return; + return 0; } if (c > MAX_LWZ_BITS) { - return; + return 0; } /* Stash the color map into the image */ @@ -583,10 +1446,11 @@ ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap) im->blue[i] = cmap[CM_BLUE][i]; im->open[i] = 1; } + /* Many (perhaps most) of these colors will remain marked open. */ im->colorsTotal = gdMaxColors; if (LWZReadByte(fd, &sd, TRUE, c, ZeroDataBlockP) < 0) { - return; + return 0; } /* @@ -600,14 +1464,20 @@ ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap) /*} */ while ((v = LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP)) >= 0) { + if (colorCount > 0 && v >= colorCount) { + return 0; + } if (v >= gdMaxColors) { v = 0; } + /* This how we recognize which colors are actually used. */ if (im->open[v]) { im->open[v] = 0; } + gdImageSetPixel(im, xpos, ypos, v); + ++xpos; if (xpos == len) { xpos = 0; @@ -615,22 +1485,28 @@ ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap) switch (pass) { case 0: case 1: - ypos += 8; break; + ypos += 8; + break; case 2: - ypos += 4; break; + ypos += 4; + break; case 3: - ypos += 2; break; + ypos += 2; + break; } if (ypos >= height) { ++pass; switch (pass) { case 1: - ypos = 4; break; + ypos = 4; + break; case 2: - ypos = 2; break; + ypos = 2; + break; case 3: - ypos = 1; break; + ypos = 1; + break; default: goto fini; } @@ -639,13 +1515,16 @@ ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap) ++ypos; } } - if (ypos >= height) + + if (ypos >= height) { break; + } } fini: if (LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP) >=0) { /* Ignore extra */ } + return 1; } /* }}} */ diff --git a/ext/gd/libgd/gd_gif_out.c b/ext/gd/libgd/gd_gif_out.c index e721992b48cb..f9a48a81bb54 100644 --- a/ext/gd/libgd/gd_gif_out.c +++ b/ext/gd/libgd/gd_gif_out.c @@ -1,8 +1,15 @@ -#include +/** + * File: GIF Output + * + * Write GIF images. + */ + +#include "gd.h" +#include "gdhelpers.h" #include -#include +#include #include -#include "gd.h" +#include /* Code drawn from ppmtogif.c, from the pbmplus package ** @@ -25,9 +32,7 @@ ** CompuServe Incorporated. */ -/* - * a code_int must be able to hold 2**GIFBITS values of type int, and also -1 - */ +/* a code_int must be able to hold 2**GIFBITS values of type int, and also -1 */ typedef int code_int; #ifdef SIGNED_COMPARE_SLOW @@ -45,8 +50,7 @@ typedef long int count_int; #define maxmaxcode ((code_int)1 << GIFBITS) #define HSIZE 5003 /* 80% occupancy */ -#define hsize HSIZE /* Apparently invariant, left over from - compress */ +#define hsize HSIZE /* Apparently invariant, left over from compress */ typedef struct { int Width, Height; @@ -54,34 +58,26 @@ typedef struct { long CountDown; int Pass; int Interlace; - int n_bits; /* number of bits/code */ - code_int maxcode; /* maximum code, given n_bits */ + int n_bits; + code_int maxcode; count_int htab [HSIZE]; unsigned short codetab [HSIZE]; - code_int free_ent; /* first unused entry */ - /* - * block compression parameters -- after all codes are used up, - * and compression rate changes, start over. - */ + /* first unused entry */ + code_int free_ent; + /* block compression parameters -- after all codes are used up, + * and compression rate changes, start over. */ int clear_flg; int offset; - long int in_count; /* length of input */ - long int out_count; /* # of codes output (for debugging) */ - + long int in_count; + /* # of codes output (for debugging) */ + long int out_count; int g_init_bits; gdIOCtx * g_outfile; - int ClearCode; int EOFCode; unsigned long cur_accum; int cur_bits; - /* - * Number of characters so far in this 'packet' - */ int a_count; - /* - * Define the storage for the packet accumulator - */ char accum[ 256 ]; } GifCtx; @@ -89,8 +85,15 @@ static int gifPutWord(int w, gdIOCtx *out); static int colorstobpp(int colors); static void BumpPixel (GifCtx *ctx); static int GIFNextPixel (gdImagePtr im, GifCtx *ctx); -static void GIFEncode (gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background, int Transparent, int BitsPerPixel, int *Red, int *Green, int *Blue, gdImagePtr im); -static void compress (int init_bits, gdIOCtx *outfile, gdImagePtr im, GifCtx *ctx); +static void GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, + int Background, int Transparent, int BitsPerPixel, + int *Red, int *Green, int *Blue, gdImagePtr im); +static void GIFAnimEncode(gdIOCtxPtr fp, int IWidth, int IHeight, int LeftOfs, + int TopOfs, int GInterlace, int Transparent, + int Delay, int Disposal, int BitsPerPixel, int *Red, + int *Green, int *Blue, gdImagePtr im); +static void compress(int init_bits, gdIOCtx *outfile, gdImagePtr im, + GifCtx *ctx); static void output (code_int code, GifCtx *ctx); static void cl_block (GifCtx *ctx); static void cl_hash (register count_int chsize, GifCtx *ctx); @@ -99,11 +102,39 @@ static void char_out (int c, GifCtx *ctx); static void flush_char (GifCtx *ctx); static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out); +static int _gdImageGifAnimAddCtx(gdImagePtr im, gdIOCtxPtr out, int LocalCM, + int LeftOfs, int TopOfs, int Delay, + int Disposal, gdImagePtr previm); -void * gdImageGifPtr (gdImagePtr im, int *size) -{ +/* + Function: gdImageGifPtr + + Identical to except that it returns a pointer to a + memory area with the GIF data. This memory must be freed by the + caller when it is no longer needed. + + The caller *must* invoke , not _free()_. This is because + it is not guaranteed that libgd will use the same implementation + of malloc, free, etc. as your proggram. + + The 'size' parameter receives the total size of the block of + memory. + + Parameters: + + im - The image to write + size - Output: the size of the resulting image. + + Returns: + + A pointer to the GIF data or NULL if an error occurred. + +*/ +BGD_DECLARE(void *) gdImageGifPtr(gdImagePtr im, int *size) { void *rv; gdIOCtx *out = gdNewDynamicCtx (2048, NULL); + if (out == NULL) + return NULL; if (!_gdImageGifCtx(im, out)) { rv = gdDPExtractData(out, size); } else { @@ -113,24 +144,98 @@ void * gdImageGifPtr (gdImagePtr im, int *size) return rv; } -void gdImageGif (gdImagePtr im, FILE * outFile) -{ +/* + Function: gdImageGif + + outputs the specified image to the specified file in + GIF format. The file must be open for binary writing. (Under MSDOS + and all versions of Windows, it is important to use "wb" as + opposed to simply "w" as the mode when opening the file; under + Unix there is no penalty for doing so). does not close + the file; your code must do so. + + GIF does not support true color; GIF images can contain a maximum + of 256 colors. If the image to be written is a truecolor image, + such as those created with gdImageCreateTrueColor or loaded from a + JPEG or a truecolor PNG image file, a palette-based temporary + image will automatically be created internally using the + function. The original image + pixels are not modified. This conversion produces high quality + palettes but does require some CPU time. If you are regularly + converting truecolor to palette in this way, you should consider + creating your image as a palette-based image in the first place. + + Variants: + + outputs the image via a struct. + + stores the image in a large array of bytes. + + Parameters: + + im - The image to write + outFile - The FILE pointer to write the image to. + + Returns: + + Nothing + + Example: + + > gdImagePtr im; + > int black, white; + > FILE *out; + > // Create the image + > im = gdImageCreate(100, 100); + > // Allocate background + > white = gdImageColorAllocate(im, 255, 255, 255); + > // Allocate drawing color + > black = gdImageColorAllocate(im, 0, 0, 0); + > // Draw rectangle + > gdImageRectangle(im, 0, 0, 99, 99, black); + > // Open output file in binary mode + > out = fopen("rect.gif", "wb"); + > // Write GIF + > gdImageGif(im, out); + > // Close file + > fclose(out); + > // Destroy image + > gdImageDestroy(im); + +*/ +BGD_DECLARE(void) gdImageGif(gdImagePtr im, FILE *outFile) { gdIOCtx *out = gdNewFileCtx (outFile); + if (out == NULL) + return; gdImageGifCtx (im, out); out->gd_free (out); } -void gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) -{ +/* + Function: gdImageGifCtx + + Writes a GIF image via a . See . + + Parameters: + + im - The image to write + out - The struct used to do the writing. + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) { _gdImageGifCtx(im, out); } /* returns 0 on success, 1 on failure */ -static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) -{ +static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) { gdImagePtr pim = 0, tim = im; int interlace, BitsPerPixel; interlace = im->interlace; + if (im->trueColor) { /* Expensive, but the only way that produces an acceptable result: mix down to a palette @@ -141,11 +246,13 @@ static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) } tim = pim; } + BitsPerPixel = colorstobpp(tim->colorsTotal); + /* All set, let's do it. */ - GIFEncode( - out, tim->sx, tim->sy, interlace, 0, tim->transparent, BitsPerPixel, - tim->red, tim->green, tim->blue, tim); + GIFEncode(out, tim->sx, tim->sy, interlace, 0, tim->transparent, + BitsPerPixel, tim->red, tim->green, tim->blue, tim); + if (pim) { /* Destroy palette based temporary image. */ gdImageDestroy( pim); @@ -154,9 +261,701 @@ static int _gdImageGifCtx(gdImagePtr im, gdIOCtxPtr out) return 0; } -static int -colorstobpp(int colors) +/* + Function: gdImageGifAnimBeginPtr + + Like except that it outputs to a memory + buffer. See . + + The returned memory must be freed by the caller when it is no + longer needed. **The caller must invoke (), not free()**, + unless the caller is absolutely certain that the same + implementations of malloc, free, etc. are used both at library + build time and at application build time (but don't; it could + always change). + + The 'size' parameter receives the total size of the block of + memory. + + Parameters: + + im - The reference image + size - Output: the size in bytes of the result. + GlobalCM - Global colormap flag: 1 -> yes, 0 -> no, -1 -> do default + Loops - Loop count; 0 -> infinite, -1 means no loop + + Returns: + + A pointer to the resulting data (the contents of the start of the + GIF) or NULL if an error occurred. + +*/ + +BGD_DECLARE(void *) +gdImageGifAnimBeginPtr(gdImagePtr im, int *size, int GlobalCM, int Loops) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + gdImageGifAnimBeginCtx(im, out, GlobalCM, Loops); + rv = gdDPExtractData(out, size); + out->gd_free(out); + return rv; +} + +/* + Function: gdImageGifAnimBegin + + This function must be called as the first function when creating a + GIF animation. It writes the correct GIF file headers to selected + file output, and prepares for frames to be added for the + animation. The image argument is not used to produce an image + frame to the file, it is only used to establish the GIF animation + frame size, interlacing options and the color + palette. is used to add the first and + subsequent frames to the animation, and the animation must be + terminated by writing a semicolon character (;) to it or by using + gdImageGifAnimEnd to do that. + + The GlobalCM flag indicates if a global color map (or palette) is + used in the GIF89A header. A nonzero value specifies that a global + color map should be used to reduce the size of the animation. Of + course, if the color maps of individual frames differ greatly, a + global color map may not be a good idea. GlobalCM=1 means write + global color map, GlobalCM=0 means do not, and GlobalCM=-1 means + to do the default, which currently is to use a global color map. + + If Loops is 0 or greater, the Netscape 2.0 extension for animation + loop count is written. 0 means infinite loop count. -1 means that + the extension is not added which results in no looping. -1 is the + default. + + Variants: + + outputs the image via a struct. + + stores the image in a large array of bytes. + + Parameters: + + im - The reference image + outfile - The output FILE*. + GlobalCM - Global colormap flag: 1 -> yes, 0 -> no, -1 -> do default + Loops - Loop count; 0 -> infinite, -1 means no loop + + Returns: + + Nothing. + + Example: + + See . + +*/ + +BGD_DECLARE(void) +gdImageGifAnimBegin(gdImagePtr im, FILE *outFile, int GlobalCM, int Loops) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; + gdImageGifAnimBeginCtx(im, out, GlobalCM, Loops); + out->gd_free(out); +} + +/* + Function: gdImageGifAnimBeginCtx + + Like except that it outputs to . + See . + + Parameters: + + im - The reference image + out - Pointer to the output . + GlobalCM - Global colormap flag: 1 -> yes, 0 -> no, -1 -> do default + Loops - Loop count; 0 -> infinite, -1 means no loop + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) +gdImageGifAnimBeginCtx(gdImagePtr im, gdIOCtxPtr out, int GlobalCM, int Loops) { + int B; + int RWidth, RHeight; + int Resolution; + int ColorMapSize; + int BitsPerPixel; + int Background = 0; + int i; + + /* Default is to use global color map */ + if (GlobalCM < 0) { + GlobalCM = 1; + } + + BitsPerPixel = colorstobpp(im->colorsTotal); + ColorMapSize = 1 << BitsPerPixel; + + RWidth = im->sx; + RHeight = im->sy; + + Resolution = BitsPerPixel; + + /* Write the Magic header */ + gdPutBuf("GIF89a", 6, out); + + /* Write out the screen width and height */ + gifPutWord(RWidth, out); + gifPutWord(RHeight, out); + + /* Indicate that there is a global colour map */ + B = GlobalCM ? 0x80 : 0; + + /* OR in the resolution */ + B |= (Resolution - 1) << 4; + + /* OR in the Bits per Pixel */ + B |= (BitsPerPixel - 1); + + /* Write it out */ + gdPutC(B, out); + + /* Write out the Background colour */ + gdPutC(Background, out); + + /* Byte of 0's (future expansion) */ + gdPutC(0, out); + + /* Write out the Global Colour Map */ + if (GlobalCM) { + for (i = 0; i < ColorMapSize; ++i) { + gdPutC(im->red[i], out); + gdPutC(im->green[i], out); + gdPutC(im->blue[i], out); + } + } + + if (Loops >= 0) { + gdPutBuf("!\377\13NETSCAPE2.0\3\1", 16, out); + gifPutWord(Loops, out); + gdPutC(0, out); + } +} + +/* + Function: gdImageGifAnimAddPtr + + Like (which contains more information) except + that it stores the data to write into memory and returns a pointer + to it. + + This memory must be freed by the caller when it is no longer + needed. **The caller must invoke (), not free(),** unless + the caller is absolutely certain that the same implementations of + malloc, free, etc. are used both at library build time and at + application build time (but don't; it could always change). + + The 'size' parameter receives the total size of the block of + memory. + + Parameters: + + im - The image to add. + size - Output: the size of the resulting buffer. + LocalCM - Flag. If 1, use a local color map for this frame. + LeftOfs - Left offset of image in frame. + TopOfs - Top offset of image in frame. + Delay - Delay before next frame (in 1/100 seconds) + Disposal - MODE: How to treat this frame when the next one loads. + previm - NULL or a pointer to the previous image written. + + Returns: + + Pointer to the resulting data or NULL if an error occurred. + +*/ +BGD_DECLARE(void *) +gdImageGifAnimAddPtr(gdImagePtr im, int *size, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + if (!_gdImageGifAnimAddCtx(im, out, LocalCM, LeftOfs, TopOfs, Delay, + Disposal, previm)) { + rv = gdDPExtractData(out, size); + } else { + rv = NULL; + } + out->gd_free(out); + return rv; +} + +/* + Function: gdImageGifAnimAdd + + This function writes GIF animation frames to GIF animation, which + was initialized with . With _LeftOfs_ and + _TopOfs_ you can place this frame in different offset than (0,0) + inside the image screen as defined in . Delay + between the previous frame and this frame is in 1/100s + units. _Disposal_ is usually , meaning that the + pixels changed by this frame should remain on the display when the + next frame begins to render, but can also be + (not recommended), (restores the + first allocated color of the global palette), or + (restores the appearance of the + affected area before the frame was rendered). Only + is a sensible choice for the first frame. If + _previm_ is passed, the built-in GIF optimizer will always use + regardless of the Disposal parameter. + + Setting the _LocalCM_ flag to 1 adds a local palette for this + image to the animation. Otherwise the global palette is assumed + and the user must make sure the palettes match. Use + to do that. + + Automatic optimization is activated by giving the previous image + as a parameter. This function then compares the images and only + writes the changed pixels to the new frame in animation. The + _Disposal_ parameter for optimized animations must be set to 1, + also for the first frame. _LeftOfs_ and _TopOfs_ parameters are + ignored for optimized frames. To achieve good optimization, it is + usually best to use a single global color map. To allow + to compress unchanged pixels via the use of a + transparent color, the image must include a transparent color. + + + Variants: + + outputs its data via a struct. + + outputs its data to a memory buffer which + it returns. + + Parameters: + + im - The image to add. + outfile - The output FILE* being written. + LocalCM - Flag. If 1, use a local color map for this frame. + LeftOfs - Left offset of image in frame. + TopOfs - Top offset of image in frame. + Delay - Delay before next frame (in 1/100 seconds) + Disposal - MODE: How to treat this frame when the next one loads. + previm - NULL or a pointer to the previous image written. + + Returns: + + Nothing. + + Example: + (start code) + { + gdImagePtr im, im2, im3; + int black, white, trans; + FILE *out; + + im = gdImageCreate(100, 100); // Create the image + white = gdImageColorAllocate(im, 255, 255, 255); // Allocate background + black = gdImageColorAllocate(im, 0, 0, 0); // Allocate drawing color + trans = gdImageColorAllocate(im, 1, 1, 1); // trans clr for compression + gdImageRectangle(im, 0, 0, 10, 10, black); // Draw rectangle + + out = fopen("anim.gif", "wb");// Open output file in binary mode + gdImageGifAnimBegin(im, out, 1, 3);// Write GIF hdr, global clr map,loops + // Write the first frame. No local color map. Delay = 1s + gdImageGifAnimAdd(im, out, 0, 0, 0, 100, 1, NULL); + + // construct the second frame + im2 = gdImageCreate(100, 100); + (void)gdImageColorAllocate(im2, 255, 255, 255); // White background + gdImagePaletteCopy (im2, im); // Make sure the palette is identical + gdImageRectangle(im2, 0, 0, 15, 15, black); // Draw something + // Allow animation compression with transparent pixels + gdImageColorTransparent (im2, trans); + gdImageGifAnimAdd(im2, out, 0, 0, 0, 100, 1, im); // Add second frame + + // construct the third frame + im3 = gdImageCreate(100, 100); + (void)gdImageColorAllocate(im3, 255, 255, 255); // white background + gdImagePaletteCopy (im3, im); // Make sure the palette is identical + gdImageRectangle(im3, 0, 0, 15, 20, black); // Draw something + // Allow animation compression with transparent pixels + gdImageColorTransparent (im3, trans); + // Add the third frame, compressing against the second one + gdImageGifAnimAdd(im3, out, 0, 0, 0, 100, 1, im2); + gdImageGifAnimEnd(out); // End marker, same as putc(';', out); + fclose(out); // Close file + + // Destroy images + gdImageDestroy(im); + gdImageDestroy(im2); + gdImageDestroy(im3); + } + + (end code) +*/ + +BGD_DECLARE(void) +gdImageGifAnimAdd(gdImagePtr im, FILE *outFile, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; + gdImageGifAnimAddCtx(im, out, LocalCM, LeftOfs, TopOfs, Delay, Disposal, + previm); + out->gd_free(out); +} + +static int comparewithmap(gdImagePtr im1, gdImagePtr im2, int c1, int c2, + int *colorMap) { + if (!colorMap) { + return c1 == c2; + } + + if (-2 != colorMap[c1]) { + return colorMap[c1] == c2; + } + + return (colorMap[c1] = + gdImageColorExactAlpha(im2, im1->red[c1], im1->green[c1], + im1->blue[c1], im1->alpha[c1])) == c2; +} + +/* + Function: gdImageGifAnimAddCtx + + Adds an animation frame via a . See gdImageGifAnimAdd>. + + Parameters: + + im - The image to add. + out - The output . + LocalCM - Flag. If 1, use a local color map for this frame. + LeftOfs - Left offset of image in frame. + TopOfs - Top offset of image in frame. + Delay - Delay before next frame (in 1/100 seconds) + Disposal - MODE: How to treat this frame when the next one loads. + previm - NULL or a pointer to the previous image written. + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) +gdImageGifAnimAddCtx(gdImagePtr im, gdIOCtxPtr out, int LocalCM, int LeftOfs, + int TopOfs, int Delay, int Disposal, gdImagePtr previm) { + _gdImageGifAnimAddCtx(im, out, LocalCM, LeftOfs, TopOfs, Delay, Disposal, + previm); +} + +/* returns 0 on success, 1 on failure */ +static int _gdImageGifAnimAddCtx(gdImagePtr im, gdIOCtxPtr out, int LocalCM, + int LeftOfs, int TopOfs, int Delay, + int Disposal, gdImagePtr previm) { + gdImagePtr pim = NULL, tim = im; + int interlace, transparent, BitsPerPixel; + interlace = im->interlace; + transparent = im->transparent; + + /* Default is no local color map */ + if (LocalCM < 0) { + LocalCM = 0; + } + + if (im->trueColor) { + /* Expensive, but the only way that produces an + acceptable result: mix down to a palette + based temporary image. */ + pim = gdImageCreatePaletteFromTrueColor(im, 1, 256); + if (!pim) { + return 1; + } + tim = pim; + } + + if (previm) { + /* create optimized animation. Compare this image to + the previous image and crop the temporary copy of + current image to include only changed rectangular + area. Also replace unchanged pixels inside this + area with transparent color. Transparent color + needs to be already allocated! + Preconditions: + TopOfs, LeftOfs are assumed 0 + + Images should be of same size. If not, a temporary + copy is made with the same size as previous image. + + */ + gdImagePtr prev_pim = 0, prev_tim = previm; + int x, y; + int no_changes = 0; + int min_x = 0; + int min_y = tim->sy; + int max_x = 0; + int max_y = 0; + int colorMap[256]; + + if (previm->trueColor) { + prev_pim = gdImageCreatePaletteFromTrueColor(previm, 1, 256); + if (!prev_pim) { + goto fail_end; + } + prev_tim = prev_pim; + } + + for (x = 0; x < 256; ++x) { + colorMap[x] = -2; + } + + /* First find bounding box of changed areas. */ + /* first find the top changed row */ + for (y = 0; y < tim->sy; ++y) { + for (x = 0; x < tim->sx; ++x) { + if (!comparewithmap(prev_tim, tim, prev_tim->pixels[y][x], + tim->pixels[y][x], colorMap)) { + min_y = max_y = y; + min_x = max_x = x; + goto break_top; + } + } + } + + break_top: + if (tim->sy == min_y) { + /* No changes in this frame. Encode a 1x1 transparent placeholder. + */ + no_changes = 1; + transparent = 0; + min_x = min_y = 0; + max_x = max_y = 0; + } else { + /* Then the bottom row */ + for (y = tim->sy - 1; y > min_y; --y) { + for (x = 0; x < tim->sx; ++x) { + if (!gdImageBoundsSafe(prev_tim, x, y)) + continue; + if (!comparewithmap(prev_tim, tim, prev_tim->pixels[y][x], + tim->pixels[y][x], colorMap)) { + max_y = y; + if (x < min_x) { + min_x = x; + } + if (x > max_x) { + max_x = x; + } + goto break_bot; + } + } + } + + break_bot: + /* left side */ + for (x = 0; x < min_x; ++x) { + for (y = min_y; y <= max_y; ++y) { + if (!gdImageBoundsSafe(prev_tim, x, y)) + continue; + if (!comparewithmap(prev_tim, tim, prev_tim->pixels[y][x], + tim->pixels[y][x], colorMap)) { + min_x = x; + goto break_left; + } + } + } + + break_left: + /* right side */ + for (x = tim->sx - 1; x > max_x; --x) { + for (y = min_y; y <= max_y; ++y) { + if (!gdImageBoundsSafe(prev_tim, x, y)) + continue; + if (!comparewithmap(prev_tim, tim, prev_tim->pixels[y][x], + tim->pixels[y][x], colorMap)) { + max_x = x; + goto break_right; + } + } + } + + break_right:; + } + + LeftOfs = min_x; + TopOfs = min_y; + Disposal = 1; + + /* Make a copy of the image with the new offsets. + But only if necessary. */ + if (min_x != 0 || max_x != tim->sx - 1 || min_y != 0 || + max_y != tim->sy - 1 || transparent >= 0) { + + gdImagePtr pim2 = + gdImageCreate(max_x - min_x + 1, max_y - min_y + 1); + + if (!pim2) { + if (prev_pim) { + gdImageDestroy(prev_pim); + } + goto fail_end; + } + + gdImagePaletteCopy(pim2, LocalCM ? tim : prev_tim); + gdImageCopy(pim2, tim, 0, 0, min_x, min_y, max_x - min_x + 1, + max_y - min_y + 1); + + if (pim) { + gdImageDestroy(pim); + } + + tim = pim = pim2; + } + + /* now let's compare pixels for transparent + optimization. But only if transparent is set. */ + if (transparent >= 0) { + for (y = 0; y < tim->sy; ++y) { + for (x = 0; x < tim->sx; ++x) { + if (no_changes || + comparewithmap(prev_tim, tim, + prev_tim->pixels[min_y + y][min_x + x], + tim->pixels[y][x], 0)) { + gdImageSetPixel(tim, x, y, transparent); + break; + } + } + } + } + + if (prev_pim) { + gdImageDestroy(prev_pim); + } + } + + BitsPerPixel = colorstobpp(tim->colorsTotal); + + /* All set, let's do it. */ + GIFAnimEncode(out, tim->sx, tim->sy, LeftOfs, TopOfs, interlace, + transparent, Delay, Disposal, BitsPerPixel, + LocalCM ? tim->red : 0, tim->green, tim->blue, tim); + if (pim) { + /* Destroy palette based temporary image. */ + gdImageDestroy(pim); + } + return 0; + +fail_end: + if (pim) { + /* Destroy palette based temporary image. */ + gdImageDestroy(pim); + } + return 1; +} + +/* + Function: gdImageGifAnimEnd + + Terminates the GIF file properly. + + (Previous versions of this function's documentation suggested just + manually writing a semicolon (';') instead since that is all this + function does. While that has no longer changed, we now suggest + that you do not do this and instead always call + (or equivalent) since later versions could + possibly do more or different things.) + + Variants: + + outputs its data via a struct. + + outputs its data to a memory buffer which + it returns. + + Parameters: + + outfile - the destination FILE*. + + Returns: + + Nothing. + +*/ + +BGD_DECLARE(void) gdImageGifAnimEnd(FILE *outFile) { +#if 1 + putc(';', outFile); +#else + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; + gdImageGifAnimEndCtx(out); + out->gd_free(out); +#endif +} + +/* + Function: gdImageGifAnimEndPtr + + Like (which contains more information) except + that it stores the data to write into memory and returns a pointer + to it. + + This memory must be freed by the caller when it is no longer + needed. **The caller must invoke (), not free(),** unless + the caller is absolutely certain that the same implementations of + malloc, free, etc. are used both at library build time and at + application build time (but don't; it could always change). + + The 'size' parameter receives the total size of the block of + memory. + + Parameters: + + size - Output: the size of the resulting buffer. + + Returns: + + Pointer to the resulting data or NULL if an error occurred. + +*/ + +BGD_DECLARE(void *) gdImageGifAnimEndPtr(int *size) { + char *rv = (char *)gdMalloc(1); + if (!rv) { + return 0; + } + *rv = ';'; + *size = 1; + return (void *)rv; +} + +/* + Function: gdImageGifAnimEndCtx + + Like , but writes its data via a . + + Parameters: + + out - the destination . + + Returns: + + Nothing. + +*/ + +BGD_DECLARE(void) gdImageGifAnimEndCtx(gdIOCtx *out) { + /* + * Write the GIF file terminator + */ + gdPutC(';', out); +} + +static int colorstobpp(int colors) { int bpp = 0; if ( colors <= 2 ) @@ -175,6 +974,7 @@ colorstobpp(int colors) bpp = 7; else if ( colors <= 256 ) bpp = 8; + return bpp; } @@ -189,28 +989,21 @@ colorstobpp(int colors) #define TRUE 1 #define FALSE 0 -/* - * Bump the 'curx' and 'cury' to point to the next pixel - */ -static void -BumpPixel(GifCtx *ctx) -{ - /* - * Bump the current X position - */ + +/* Bump the 'curx' and 'cury' to point to the next pixel */ +static void BumpPixel(GifCtx *ctx) { + /* Bump the current X position */ ++(ctx->curx); - /* - * If we are at the end of a scan line, set curx back to the beginning + /* If we are at the end of a scan line, set curx back to the beginning * If we are interlaced, bump the cury to the appropriate spot, - * otherwise, just increment it. - */ + * otherwise, just increment it. */ if( ctx->curx == ctx->Width ) { ctx->curx = 0; - if( !ctx->Interlace ) + if (!ctx->Interlace) { ++(ctx->cury); - else { + } else { switch( ctx->Pass ) { case 0: @@ -245,16 +1038,13 @@ BumpPixel(GifCtx *ctx) } } -/* - * Return the next pixel from the image - */ -static int -GIFNextPixel(gdImagePtr im, GifCtx *ctx) -{ +/* Return the next pixel from the image */ +static int GIFNextPixel(gdImagePtr im, GifCtx *ctx) { int r; - if( ctx->CountDown == 0 ) + if (ctx->CountDown == 0) { return EOF; + } --(ctx->CountDown); @@ -267,9 +1057,9 @@ GIFNextPixel(gdImagePtr im, GifCtx *ctx) /* public */ -static void -GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background, int Transparent, int BitsPerPixel, int *Red, int *Green, int *Blue, gdImagePtr im) -{ +static void GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, + int Background, int Transparent, int BitsPerPixel, + int *Red, int *Green, int *Blue, gdImagePtr im) { int B; int RWidth, RHeight; int LeftOfs, TopOfs; @@ -280,6 +1070,7 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background GifCtx ctx; memset(&ctx, 0, sizeof(ctx)); + ctx.Interlace = GInterlace; ctx.in_count = 1; @@ -291,82 +1082,56 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background Resolution = BitsPerPixel; - /* - * Calculate number of bits we are expecting - */ + /* Calculate number of bits we are expecting */ ctx.CountDown = (long)ctx.Width * (long)ctx.Height; - /* - * Indicate which pass we are on (if interlace) - */ + /* Indicate which pass we are on (if interlace) */ ctx.Pass = 0; - /* - * The initial code size - */ - if( BitsPerPixel <= 1 ) + /* The initial code size */ + if (BitsPerPixel <= 1) { InitCodeSize = 2; - else + } else { InitCodeSize = BitsPerPixel; + } - /* - * Set up the current x and y position - */ + /* Set up the current x and y position */ ctx.curx = ctx.cury = 0; - /* - * Write the Magic header - */ + /* Write the Magic header */ gdPutBuf(Transparent < 0 ? "GIF87a" : "GIF89a", 6, fp ); - /* - * Write out the screen width and height - */ + /* Write out the screen width and height */ gifPutWord( RWidth, fp ); gifPutWord( RHeight, fp ); - /* - * Indicate that there is a global colour map - */ - B = 0x80; /* Yes, there is a color map */ + /* Indicate that there is a global colour map */ + /* Yes, there is a color map */ + B = 0x80; - /* - * OR in the resolution - */ + /* OR in the resolution */ B |= (Resolution - 1) << 4; - /* - * OR in the Bits per Pixel - */ + /* OR in the Bits per Pixel */ B |= (BitsPerPixel - 1); - /* - * Write it out - */ + /* Write it out */ gdPutC( B, fp ); - /* - * Write out the Background colour - */ + /* Write out the Background colour */ gdPutC( Background, fp ); - /* - * Byte of 0's (future expansion) - */ + /* Byte of 0's (future expansion) */ gdPutC( 0, fp ); - /* - * Write out the Global Colour Map - */ + /* Write out the Global Colour Map */ for( i=0; i= 0 ) { gdPutC( '!', fp ); gdPutC( 0xf9, fp ); @@ -378,49 +1143,134 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background gdPutC( 0, fp ); } - /* - * Write an Image separator - */ + /* Write an Image separator */ gdPutC( ',', fp ); - /* - * Write the Image header - */ - + /* Write the Image header */ gifPutWord( LeftOfs, fp ); gifPutWord( TopOfs, fp ); gifPutWord( ctx.Width, fp ); gifPutWord( ctx.Height, fp ); - /* - * Write out whether or not the image is interlaced - */ - if( ctx.Interlace ) + /* Write out whether or not the image is interlaced */ + if (ctx.Interlace) { gdPutC( 0x40, fp ); - else + } else { gdPutC( 0x00, fp ); + } - /* - * Write out the initial code size - */ + /* Write out the initial code size */ gdPutC( InitCodeSize, fp ); - /* - * Go and actually compress the data - */ + /* Go and actually compress the data */ compress( InitCodeSize+1, fp, im, &ctx ); - /* - * Write out a Zero-length packet (to end the series) - */ + /* Write out a Zero-length packet (to end the series) */ gdPutC( 0, fp ); - /* - * Write the GIF file terminator - */ + /* Write the GIF file terminator */ gdPutC( ';', fp ); } +static void GIFAnimEncode(gdIOCtxPtr fp, int IWidth, int IHeight, int LeftOfs, + int TopOfs, int GInterlace, int Transparent, + int Delay, int Disposal, int BitsPerPixel, int *Red, + int *Green, int *Blue, gdImagePtr im) { + int B; + int ColorMapSize; + int InitCodeSize; + int i; + GifCtx ctx; + + memset(&ctx, 0, sizeof(ctx)); + + ctx.Interlace = GInterlace; + ctx.in_count = 1; + + ColorMapSize = 1 << BitsPerPixel; + + if (LeftOfs < 0) { + LeftOfs = 0; + } + if (TopOfs < 0) { + TopOfs = 0; + } + if (Delay < 0) { + Delay = 100; + } + if (Disposal < 0) { + Disposal = 1; + } + + ctx.Width = IWidth; + ctx.Height = IHeight; + + /* Calculate number of bits we are expecting */ + ctx.CountDown = (long)ctx.Width * (long)ctx.Height; + + /* Indicate which pass we are on (if interlace) */ + ctx.Pass = 0; + + /* The initial code size */ + if (BitsPerPixel <= 1) { + InitCodeSize = 2; + } else { + InitCodeSize = BitsPerPixel; + } + + /* Set up the current x and y position */ + ctx.curx = ctx.cury = 0; + + /* Write out extension for image animation and looping */ + gdPutC('!', fp); + gdPutC(0xf9, fp); + gdPutC(4, fp); + gdPutC((Transparent >= 0 ? 1 : 0) | (Disposal << 2), fp); + gdPutC((unsigned char)(Delay & 255), fp); + gdPutC((unsigned char)((Delay >> 8) & 255), fp); + gdPutC((unsigned char)Transparent, fp); + gdPutC(0, fp); + + /* Write an Image separator */ + gdPutC(',', fp); + + /* Write out the Image header */ + gifPutWord(LeftOfs, fp); + gifPutWord(TopOfs, fp); + gifPutWord(ctx.Width, fp); + gifPutWord(ctx.Height, fp); + + /* Indicate that there is a local colour map */ + B = (Red && Green && Blue) ? 0x80 : 0; + + /* OR in the interlacing */ + B |= ctx.Interlace ? 0x40 : 0; + + /* OR in the Bits per Pixel */ + B |= (Red && Green && Blue) ? (BitsPerPixel - 1) : 0; + + /* Write it out */ + gdPutC(B, fp); + + /* Write out the Local Colour Map */ + if (Red && Green && Blue) { + for (i = 0; i < ColorMapSize; ++i) { + gdPutC(Red[i], fp); + gdPutC(Green[i], fp); + gdPutC(Blue[i], fp); + } + } + + /* Write out the initial code size */ + gdPutC(InitCodeSize, fp); + + /* Go and actually compress the data */ + compress(InitCodeSize + 1, fp, im, &ctx); + + /* Write out a Zero-length packet (to end the series) */ + gdPutC(0, fp); +} + /*************************************************************************** * * GIFCOMPR.C - GIF Image compression routines @@ -430,9 +1280,7 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background * ***************************************************************************/ -/* - * General DEFINEs - */ +/* General DEFINEs */ #define GIFBITS 12 @@ -469,7 +1317,6 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background #define HashTabOf(i) ctx->htab[i] #define CodeTabOf(i) ctx->codetab[i] - /* * To save much memory, we overlay the table used by compress() with those * used by decompress(). The tab_prefix table is the same size and type @@ -499,30 +1346,25 @@ GIFEncode(gdIOCtxPtr fp, int GWidth, int GHeight, int GInterlace, int Background * questions about this implementation to ames!jaw. */ -static void -output(code_int code, GifCtx *ctx); +static void output(code_int code, GifCtx *ctx); -static void -compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) -{ +static void compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, + GifCtx *ctx) { register long fcode; - register code_int i /* = 0 */; + register code_int i; register int c; register code_int ent; register code_int disp; register code_int hsize_reg; register int hshift; - /* - * Set up the globals: g_init_bits - initial number of bits - * g_outfile - pointer to output file - */ + /* Set up the globals: + * g_init_bits - initial number of bits + * g_outfile - pointer to output file */ ctx->g_init_bits = init_bits; ctx->g_outfile = outfile; - /* - * Set up the necessary values - */ + /* Set up the necessary values */ ctx->offset = 0; ctx->out_count = 0; ctx->clear_flg = 0; @@ -538,8 +1380,9 @@ compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) ent = GIFNextPixel( im, ctx ); hshift = 0; - for ( fcode = (long) hsize; fcode < 65536L; fcode *= 2L ) + for (fcode = (long)hsize; fcode < 65536L; fcode *= 2L) { ++hshift; + } hshift = 8 - hshift; /* set hash code range bound */ hsize_reg = hsize; @@ -550,7 +1393,7 @@ compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) #ifdef SIGNED_COMPARE_SLOW while ( (c = GIFNextPixel( im, ctx )) != (unsigned) EOF ) { #else /*SIGNED_COMPARE_SLOW*/ - while ( (c = GIFNextPixel( im, ctx )) != EOF ) { /* } */ + while ((c = GIFNextPixel(im, ctx)) != EOF) { #endif /*SIGNED_COMPARE_SLOW*/ ++(ctx->in_count); @@ -561,21 +1404,30 @@ compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) if ( HashTabOf (i) == fcode ) { ent = CodeTabOf (i); continue; - } else if ( (long)HashTabOf (i) < 0 ) /* empty slot */ + } else if ((long)HashTabOf(i) < 0) { /* empty slot */ goto nomatch; + } + disp = hsize_reg - i; /* secondary hash (after G. Knott) */ - if ( i == 0 ) + + if (i == 0) { disp = 1; + } + probe: - if ( (i -= disp) < 0 ) + if ((i -= disp) < 0) { i += hsize_reg; + } if ( HashTabOf (i) == fcode ) { ent = CodeTabOf (i); continue; } - if ( (long)HashTabOf (i) > 0 ) + + if ((long)HashTabOf(i) > 0) { goto probe; + } + nomatch: output ( (code_int) ent, ctx ); ++(ctx->out_count); @@ -587,12 +1439,12 @@ compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) #endif /*SIGNED_COMPARE_SLOW*/ CodeTabOf (i) = ctx->free_ent++; /* code -> hashtable */ HashTabOf (i) = fcode; - } else + } else { cl_block(ctx); } - /* - * Put out the final code. - */ + } + + /* Put out the final code. */ output( (code_int)ent, ctx ); ++(ctx->out_count); output( (code_int) ctx->EOFCode, ctx ); @@ -615,11 +1467,9 @@ compress(int init_bits, gdIOCtxPtr outfile, gdImagePtr im, GifCtx *ctx) * code in turn. When the buffer fills up empty it and start over. */ -static const unsigned long masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, - 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, - 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; - +static const unsigned long masks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; /* Arbitrary value to mark output is done. When we see EOFCode, then we don't * expect to see any more data. If we do (e.g. corrupt image inputs), cur_bits @@ -627,20 +1477,16 @@ static const unsigned long masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, */ #define CUR_BITS_FINISHED -1000 - -static void -output(code_int code, GifCtx *ctx) -{ - if (ctx->cur_bits == CUR_BITS_FINISHED) { +static void output(code_int code, GifCtx *ctx) { + if (ctx->cur_bits == CUR_BITS_FINISHED) return; - } - ctx->cur_accum &= masks[ ctx->cur_bits ]; - if( ctx->cur_bits > 0 ) + if (ctx->cur_bits > 0) { ctx->cur_accum |= ((long)code << ctx->cur_bits); - else + } else { ctx->cur_accum = code; + } ctx->cur_bits += ctx->n_bits; @@ -655,32 +1501,26 @@ output(code_int code, GifCtx *ctx) * then increase it, if possible. */ if ( ctx->free_ent > ctx->maxcode || ctx->clear_flg ) { - if( ctx->clear_flg ) { - ctx->maxcode = MAXCODE (ctx->n_bits = ctx->g_init_bits); ctx->clear_flg = 0; - } else { - ++(ctx->n_bits); - if ( ctx->n_bits == maxbits ) + if (ctx->n_bits == maxbits) { ctx->maxcode = maxmaxcode; - else + } else { ctx->maxcode = MAXCODE(ctx->n_bits); + } } } if( code == ctx->EOFCode ) { - /* - * At EOF, write the rest of the buffer. - */ + /* At EOF, write the rest of the buffer. */ while( ctx->cur_bits > 0 ) { char_out( (unsigned int)(ctx->cur_accum & 0xff), ctx); ctx->cur_accum >>= 8; ctx->cur_bits -= 8; } - /* Flag that it's done to prevent re-entry. */ ctx->cur_bits = CUR_BITS_FINISHED; @@ -691,10 +1531,8 @@ output(code_int code, GifCtx *ctx) /* * Clear out the hash table */ -static void -cl_block (GifCtx *ctx) /* table clear for block compress */ +static void cl_block(GifCtx *ctx) /* table clear for block compress */ { - cl_hash ( (count_int) hsize, ctx ); ctx->free_ent = ctx->ClearCode + 2; ctx->clear_flg = 1; @@ -702,13 +1540,10 @@ cl_block (GifCtx *ctx) /* table clear for block compress */ output( (code_int)ctx->ClearCode, ctx); } -static void -cl_hash(register count_int chsize, GifCtx *ctx) /* reset code table */ - +static void cl_hash(register count_int chsize, + GifCtx *ctx) /* reset code table */ { - register count_int *htab_p = ctx->htab+chsize; - register long i; register long m1 = -1; @@ -733,8 +1568,9 @@ cl_hash(register count_int chsize, GifCtx *ctx) /* reset code table */ htab_p -= 16; } while ((i -= 16) >= 0); - for ( i += 16; i > 0; --i ) + for (i += 16; i > 0; --i) { *--htab_p = m1; + } } /****************************************************************************** @@ -746,30 +1582,23 @@ cl_hash(register count_int chsize, GifCtx *ctx) /* reset code table */ /* * Set up the 'byte output' routine */ -static void -char_init(GifCtx *ctx) -{ - ctx->a_count = 0; -} +static void char_init(GifCtx *ctx) { ctx->a_count = 0; } /* * Add a character to the end of the current packet, and if it is 254 * characters, flush the packet to disk. */ -static void -char_out(int c, GifCtx *ctx) -{ +static void char_out(int c, GifCtx *ctx) { ctx->accum[ ctx->a_count++ ] = c; - if( ctx->a_count >= 254 ) + if (ctx->a_count >= 254) { flush_char(ctx); + } } /* * Flush the packet to disk, and reset the accumulator */ -static void -flush_char(GifCtx *ctx) -{ +static void flush_char(GifCtx *ctx) { if( ctx->a_count > 0 ) { gdPutC( ctx->a_count, ctx->g_outfile ); gdPutBuf( ctx->accum, ctx->a_count, ctx->g_outfile ); @@ -777,8 +1606,7 @@ flush_char(GifCtx *ctx) } } -static int gifPutWord(int w, gdIOCtx *out) -{ +static int gifPutWord(int w, gdIOCtx *out) { /* Byte order is little-endian */ gdPutC(w & 0xFF, out); gdPutC((w >> 8) & 0xFF, out); diff --git a/ext/gd/libgd/gd_gradient.c b/ext/gd/libgd/gd_gradient.c new file mode 100644 index 000000000000..1855bbbfcddb --- /dev/null +++ b/ext/gd/libgd/gd_gradient.c @@ -0,0 +1,185 @@ +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gdhelpers.h" +#include "gd_path_matrix.h" +#include "gd_gradient.h" + +static int finite_matrix(const gdPathMatrix *m) +{ + return m && isfinite(m->m00) && isfinite(m->m10) && + isfinite(m->m01) && isfinite(m->m11) && + isfinite(m->m02) && isfinite(m->m12); +} + +static int valid_extend(gdExtendMode e) +{ + return e >= GD_EXTEND_NONE && e <= GD_EXTEND_PAD; +} + +static gdPaintPtr create_gradient(gdGradientKind kind) +{ + gdPaintPtr paint = gdMalloc(sizeof(*paint)); + gdGradient *g; + if (!paint) return NULL; + g = gdCalloc(1, sizeof(*g)); + if (!g) { gdFree(paint); return NULL; } + g->kind = kind; + g->extend = GD_EXTEND_PAD; + gdPathMatrixInitIdentity(&g->matrix); + gdPathMatrixInitIdentity(&g->inverse); + paint->ref = 1; + paint->type = gdPaintTypeGradient; + paint->gradient = g; + return paint; +} + +BGD_DECLARE(gdPaintPtr) gdPaintCreateLinear(double x0, double y0, + double x1, double y1) +{ + gdPaintPtr p; + gdGradient *g; + double dx, dy, ls, s; + if (!isfinite(x0) || !isfinite(y0) || !isfinite(x1) || !isfinite(y1)) return NULL; + p = create_gradient(GD_GRADIENT_LINEAR); + if (!p) return NULL; + g = p->gradient; dx = x1-x0; dy = y1-y0; ls = dx*dx+dy*dy; + s = fmax(fmax(fabs(x0), fabs(y0)), fmax(fabs(x1), fabs(y1))); + g->geometry.linear.x0=x0; g->geometry.linear.y0=y0; + g->geometry.linear.dx=dx; g->geometry.linear.dy=dy; + g->geometry.linear.scale=s; + g->geometry.linear.inverse_length_squared = + (isfinite(ls) && ls > 64.0*DBL_EPSILON*fmax(1.0,s*s)) ? 1.0/ls : 0.0; + return p; +} + +BGD_DECLARE(gdPaintPtr) gdPaintCreateRadial(double x0, double y0, double r0, + double x1, double y1, double r1) +{ + gdPaintPtr p; gdGradient *g; double s; + if (!isfinite(x0)||!isfinite(y0)||!isfinite(r0)||!isfinite(x1)|| + !isfinite(y1)||!isfinite(r1)||r0<0||r1<0) return NULL; + p=create_gradient(GD_GRADIENT_RADIAL); if(!p) return NULL; g=p->gradient; + g->geometry.radial.x0=x0; g->geometry.radial.y0=y0; g->geometry.radial.r0=r0; + g->geometry.radial.x1=x1; g->geometry.radial.y1=y1; g->geometry.radial.r1=r1; + g->geometry.radial.cdx=x1-x0; g->geometry.radial.cdy=y1-y0; g->geometry.radial.dr=r1-r0; + g->geometry.radial.quadratic_a=g->geometry.radial.cdx*g->geometry.radial.cdx+ + g->geometry.radial.cdy*g->geometry.radial.cdy-g->geometry.radial.dr*g->geometry.radial.dr; + s=fmax(fmax(fabs(x0),fabs(y0)),fmax(fabs(x1),fabs(y1))); + s=fmax(s,fmax(r0,r1)); g->geometry.radial.scale=s; + return p; +} + +void gdGradientDestroy(gdGradient *g) +{ if(g){ gdFree(g->stops); gdFree(g); } } + +static gdPremulPixelF ramp_at(const gdColorStop *s, size_t n, double t) +{ + size_t i, last; + gdPremulPixelF z={0,0,0,0}, a, b, out; + double u; + if(!n) return z; + if(n==1) return s[0].color; + if(t=s[n-1].offset) return s[n-1].color; + for(i=0;itype!=gdPaintTypeGradient||!isfinite(off)||!isfinite(r)||!isfinite(g)|| + !isfinite(b)||!isfinite(a)||off<0||off>1||r<0||r>1||g<0||g>1||b<0||b>1||a<0||a>1) return 0; + gradient=p->gradient; n=gradient->stop_count+1; candidate=gdMalloc(n*sizeof(*candidate)); if(!candidate)return 0; + if(gradient->stop_count) memcpy(candidate,gradient->stops,gradient->stop_count*sizeof(*candidate)); + candidate[n-1].offset=off; candidate[n-1].sequence=gradient->next_sequence; + candidate[n-1].color.r=(float)(r*a); candidate[n-1].color.g=(float)(g*a); + candidate[n-1].color.b=(float)(b*a); candidate[n-1].color.a=(float)a; + for(i=1;iv.offset || + (candidate[j-1].offset==v.offset&&candidate[j-1].sequence>v.sequence))){candidate[j]=candidate[j-1];j--;} + candidate[j]=v; + } + for(i=0;istops); gradient->stops=candidate; gradient->stop_count=n; gradient->stop_capacity=n; + gradient->next_sequence++; memcpy(gradient->lut,lut,sizeof(lut)); return 1; +} + +BGD_DECLARE(int) gdPaintAddColorStopRgb(gdPaintPtr p,double o,double r,double g,double b) +{ return gdPaintAddColorStopRgba(p,o,r,g,b,1.0); } + +BGD_DECLARE(int) gdPaintSetExtend(gdPaintPtr p, gdExtendMode e) +{ + if(!p||!valid_extend(e)) return 0; + if(p->type==gdPaintTypeGradient){p->gradient->extend=e;return 1;} + if(p->type==gdPaintTypePattern){p->pattern->extend=e;return 1;} + return 0; +} + +BGD_DECLARE(int) gdPaintSetMatrix(gdPaintPtr p, const gdPathMatrixPtr m) +{ + gdPathMatrix inv; + if(!p||!finite_matrix(m)) return 0; + inv=*m; + if(!gdPathMatrixInvert(&inv))return 0; + if(p->type==gdPaintTypeGradient){p->gradient->matrix=*m;p->gradient->inverse=inv;return 1;} + if(p->type==gdPaintTypePattern){p->pattern->matrix=*m;return 1;} + return 0; +} + +int gdExtendFold(double raw, gdExtendMode mode, double *f) +{ + double m; if(!f||!isfinite(raw)||!valid_extend(mode))return 0; + if(raw==0.0)raw=0.0; + switch(mode){ + case GD_EXTEND_NONE: if(raw<0||raw>1)return 0;*f=raw;return 1; + case GD_EXTEND_PAD:*f=raw<0?0:(raw>1?1:raw);return 1; + case GD_EXTEND_REPEAT:*f=raw-floor(raw);return 1; + case GD_EXTEND_REFLECT:m=fmod(raw,2.0);if(m<0)m+=2;*f=m<=1?m:2-m;return 1; + default:return 0; + } +} + +static int radial_t(const gdGradient *g,double x,double y,double *out) +{ + const double cx=g->geometry.radial.cdx,cy=g->geometry.radial.cdy,dr=g->geometry.radial.dr; + double px=x-g->geometry.radial.x0,py=y-g->geometry.radial.y0,r0=g->geometry.radial.r0; + double A=g->geometry.radial.quadratic_a,B=px*cx+py*cy+r0*dr,C=px*px+py*py-r0*r0; + double scale=fmax(1.0,g->geometry.radial.scale+fabs(px)+fabs(py)); + double coord_tol=64*DBL_EPSILON*scale; + double tol=coord_tol*scale, roots[2],disc; int n=0,valid=0; + if(fabs(cx)<=coord_tol&&fabs(cy)<=coord_tol&&fabs(dr)<=coord_tol)return 0; + if(fabs(A)<=tol){if(fabs(B)<=tol)return 0;roots[n++]=C/(2*B);} + else {disc=B*B-A*C;if(disc<0){if(disc>=-tol*scale*scale)disc=0;else return 0;} + disc=sqrt(disc);roots[n++]=(B+disc)/A;roots[n++]=(B-disc)/A;} + for(int i=0;i=-coord_tol&& + (g->extend!=GD_EXTEND_NONE||(roots[i]>=0&&roots[i]<=1))){if(!valid||roots[i]>*out)*out=roots[i];valid=1;} + return valid; +} + +gdPremulPixelF gdGradientSample(const gdGradient *g,const gdPathMatrix *m,double x,double y) +{ + gdPremulPixelF z={0,0,0,0}; double px,py,t,f; long i; + if(!g||!m||!g->stop_count)return z; + px=m->m01*y+m->m00*x+m->m02; py=m->m11*y+m->m10*x+m->m12; + if(g->kind==GD_GRADIENT_LINEAR){if(g->geometry.linear.inverse_length_squared==0)return z; + t=((px-g->geometry.linear.x0)*g->geometry.linear.dx+(py-g->geometry.linear.y0)*g->geometry.linear.dy)*g->geometry.linear.inverse_length_squared;} + else if(!radial_t(g,px,py,&t))return z; + if(!gdExtendFold(t,g->extend,&f))return z; + i=(long)floor(f*(GD_GRADIENT_LUT_SIZE-1)+0.5);if(i<0)i=0;if(i>=GD_GRADIENT_LUT_SIZE)i=GD_GRADIENT_LUT_SIZE-1; + return g->lut[i]; +} diff --git a/ext/gd/libgd/gd_gradient.h b/ext/gd/libgd/gd_gradient.h new file mode 100644 index 000000000000..ec48b1fbf10d --- /dev/null +++ b/ext/gd/libgd/gd_gradient.h @@ -0,0 +1,40 @@ +#ifndef GD_GRADIENT_H +#define GD_GRADIENT_H + +#include +#include +#include "gd_vector2d_private.h" +#include "gd_compositor.h" + +#define GD_GRADIENT_LUT_SIZE 1024 + +typedef enum { GD_GRADIENT_LINEAR, GD_GRADIENT_RADIAL } gdGradientKind; + +typedef struct { + double offset; + gdPremulPixelF color; + uint64_t sequence; +} gdColorStop; + +struct gdGradientStruct { + gdGradientKind kind; + gdExtendMode extend; + gdPathMatrix matrix; + gdPathMatrix inverse; + gdColorStop *stops; + size_t stop_count, stop_capacity; + uint64_t next_sequence; + gdPremulPixelF lut[GD_GRADIENT_LUT_SIZE]; + union { + struct { double x0, y0, dx, dy, inverse_length_squared, scale; } linear; + struct { double x0, y0, r0, x1, y1, r1, cdx, cdy, dr, quadratic_a, scale; } radial; + } geometry; +}; + +void gdGradientDestroy(gdGradient *gradient); +int gdExtendFold(double raw, gdExtendMode mode, double *folded); +gdPremulPixelF gdGradientSample(const gdGradient *gradient, + const gdPathMatrix *device_to_pattern, + double device_x, double device_y); + +#endif diff --git a/ext/gd/libgd/gd_heif.c b/ext/gd/libgd/gd_heif.c new file mode 100644 index 000000000000..d473d0e2f985 --- /dev/null +++ b/ext/gd/libgd/gd_heif.c @@ -0,0 +1,624 @@ +/** + * File: HEIF IO + * + * Read and write HEIF images. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "gd.h" +#include "gd_errors.h" +#include "gdhelpers.h" +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBHEIF +#include + +#define GD_HEIF_ALLOC_STEP (4 * 1024) +#define GD_HEIF_HEADER 12 + +typedef enum gd_heif_brand { + GD_HEIF_BRAND_AVIF = 1, + GD_HEIF_BRAND_MIF1 = 2, + GD_HEIF_BRAND_HEIC = 4, + GD_HEIF_BRAND_HEIX = 8, +} gd_heif_brand; + +/* + Function: gdImageCreateFromHeif + + is called to load truecolor images from + HEIF format files. Invoke with an + already opened pointer to a file containing the desired + image. returns a to the new + truecolor image, or NULL if unable to load the image (most often + because the file is corrupt or does not contain a HEIF + image). does not close the file. + + You can inspect the sx and sy members of the image to determine + its size. The image must eventually be destroyed using + . + + *The returned image is always a truecolor image.* + + Parameters: + + infile - The input FILE pointer. + + Returns: + + A pointer to the new *truecolor* image. This will need to be + destroyed with once it is no longer needed. + + On error, returns NULL. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile) { + gdImagePtr im; + gdIOCtx *in = gdNewFileCtx(inFile); + + if (!in) + return NULL; + im = gdImageCreateFromHeifCtx(in); + in->gd_free(in); + + return im; +} + +/* + Function: gdImageCreateFromHeifPtr + + See . + + Parameters: + + size - size of HEIF data in bytes. + data - pointer to HEIF data. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + + if (!in) + return NULL; + im = gdImageCreateFromHeifCtx(in); + in->gd_free(in); + + return im; +} + +static int _gdHeifCheckBrand(unsigned char *magic, + gd_heif_brand expected_brand) { + if (memcmp(magic + 4, "ftyp", 4) != 0) + return GD_FALSE; + if (memcmp(magic + 8, "avif", 4) == 0 && + expected_brand & GD_HEIF_BRAND_AVIF) + return GD_TRUE; + if (memcmp(magic + 8, "heic", 4) == 0 && + expected_brand & GD_HEIF_BRAND_HEIC) + return GD_TRUE; + if (memcmp(magic + 8, "heix", 4) == 0 && + expected_brand & GD_HEIF_BRAND_HEIX) + return GD_TRUE; + if (memcmp(magic + 8, "mif1", 4) == 0 && + expected_brand & GD_HEIF_BRAND_MIF1) + return GD_TRUE; + + return GD_FALSE; +} + +static gdImagePtr _gdImageCreateFromHeifCtx(gdIOCtx *infile, + gd_heif_brand expected_brand) { + struct heif_context *heif_ctx; + struct heif_decoding_options *heif_dec_opts; + struct heif_image_handle *heif_imhandle; + struct heif_image *heif_im; + struct heif_error err; + int width, height; + uint8_t *filedata = NULL; + uint8_t *rgba = NULL; + unsigned char *read, *temp, magic[GD_HEIF_HEADER]; + int magic_len; + size_t size = 0, n = GD_HEIF_ALLOC_STEP; + gdImagePtr im; + int x, y; + uint8_t *p, *row_start; + int stride; + + magic_len = gdGetBuf(magic, GD_HEIF_HEADER, infile); + if (magic_len != GD_HEIF_HEADER || + !_gdHeifCheckBrand(magic, expected_brand)) { + gd_error("gd-heif incorrect type of file\n"); + return NULL; + } + gdSeek(infile, 0); + + while (n == GD_HEIF_ALLOC_STEP) { + temp = gdRealloc(filedata, size + GD_HEIF_ALLOC_STEP); + if (temp) { + filedata = temp; + read = temp + size; + } else { + gdFree(filedata); + gd_error("gd-heif decode realloc failed\n"); + return NULL; + } + + n = gdGetBuf(read, GD_HEIF_ALLOC_STEP, infile); + if (n > 0) { + size += n; + } + } + + heif_ctx = heif_context_alloc(); + if (heif_ctx == NULL) { + gd_error("gd-heif could not allocate context\n"); + gdFree(filedata); + return NULL; + } + err = heif_context_read_from_memory_without_copy(heif_ctx, filedata, size, + NULL); + if (err.code != heif_error_Ok) { + gd_error("gd-heif context creation failed\n"); + gdFree(filedata); + heif_context_free(heif_ctx); + return NULL; + } + + heif_imhandle = NULL; + err = heif_context_get_primary_image_handle(heif_ctx, &heif_imhandle); + if (err.code != heif_error_Ok) { + gd_error("gd-heif cannot retreive handle\n"); + gdFree(filedata); + heif_context_free(heif_ctx); + return NULL; + } + + heif_im = NULL; + heif_dec_opts = heif_decoding_options_alloc(); + if (heif_dec_opts == NULL) { + gd_error("gd-heif could not allocate decode options\n"); + gdFree(filedata); + heif_image_handle_release(heif_imhandle); + heif_context_free(heif_ctx); + return NULL; + } + + heif_dec_opts->convert_hdr_to_8bit = GD_TRUE; + heif_dec_opts->ignore_transformations = GD_TRUE; + err = heif_decode_image(heif_imhandle, &heif_im, heif_colorspace_RGB, + heif_chroma_interleaved_RGBA, heif_dec_opts); + heif_decoding_options_free(heif_dec_opts); + if (err.code != heif_error_Ok) { + gd_error("gd-heif decoding failed\n"); + gdFree(filedata); + heif_image_handle_release(heif_imhandle); + heif_context_free(heif_ctx); + return NULL; + } + + width = heif_image_get_width(heif_im, heif_channel_interleaved); + height = heif_image_get_height(heif_im, heif_channel_interleaved); + + im = gdImageCreateTrueColor(width, height); + if (!im) { + gdFree(filedata); + heif_image_release(heif_im); + heif_image_handle_release(heif_imhandle); + heif_context_free(heif_ctx); + return NULL; + } + rgba = (uint8_t *)heif_image_get_plane_readonly( + heif_im, heif_channel_interleaved, &stride); + if (!rgba) { + gd_error("gd-heif cannot get image plane\n"); + gdFree(filedata); + heif_image_release(heif_im); + heif_image_handle_release(heif_imhandle); + heif_context_free(heif_ctx); + gdImageDestroy(im); + return NULL; + } + row_start = rgba; + for (y = 0, p = rgba; y < height; y++) { + p = row_start; + for (x = 0; x < width; x++) { + uint8_t r = *(p++); + uint8_t g = *(p++); + uint8_t b = *(p++); + uint8_t a = gdAlphaMax - (*(p++) >> 1); + im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a); + } + row_start += stride; + } + gdFree(filedata); + heif_image_release(heif_im); + heif_image_handle_release(heif_imhandle); + heif_context_free(heif_ctx); + + return im; +} + +/* + Function: gdImageCreateFromHeifCtx + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtx *infile) { + return _gdImageCreateFromHeifCtx( + infile, GD_HEIF_BRAND_AVIF | GD_HEIF_BRAND_MIF1 | GD_HEIF_BRAND_HEIC | + GD_HEIF_BRAND_HEIX); +} + +static struct heif_error _gdImageWriteHeif(struct heif_context *heif_ctx, + const void *data, size_t size, + void *userdata) { + ARG_NOT_USED(heif_ctx); + gdIOCtx *outfile; + struct heif_error err; + int bytes_written; + + outfile = (gdIOCtx *)userdata; + if (outfile == NULL || data == NULL || size > INT_MAX) { + err.code = heif_error_Encoding_error; + err.subcode = heif_suberror_Cannot_write_output_data; + err.message = "gd-heif write callback received invalid arguments"; + return err; + } + + bytes_written = gdPutBuf(data, (int)size, outfile); + if (bytes_written != (int)size) { + err.code = heif_error_Encoding_error; + err.subcode = heif_suberror_Cannot_write_output_data; + err.message = "gd-heif failed to write output data"; + return err; + } + + err.code = heif_error_Ok; + err.subcode = heif_suberror_Unspecified; + err.message = ""; + + return err; +} + +/* returns GD_TRUE on success, GD_FALSE on failure */ +static int _gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, + gdHeifCodec codec, gdHeifChroma chroma) { + struct heif_context *heif_ctx; + struct heif_encoder *heif_enc; + struct heif_image *heif_im; + struct heif_writer heif_wr; + struct heif_error err; + uint8_t *rgba; + int x, y; + uint8_t *p; + uint8_t *row_start; + int stride; + if (im == NULL) { + return GD_FALSE; + } + + if (codec != GD_HEIF_CODEC_HEVC && codec != GD_HEIF_CODEC_AV1) { + gd_error("Unsupported format by heif"); + return GD_FALSE; + } + + if (!gdImageTrueColor(im)) { + gd_error("Palette image not supported by heif\n"); + return GD_FALSE; + } + + if (overflow2(gdImageSX(im), 4)) { + return GD_FALSE; + } + + if (overflow2(gdImageSX(im) * 4, gdImageSY(im))) { + return GD_FALSE; + } + + heif_ctx = heif_context_alloc(); + if (heif_ctx == NULL) { + gd_error("gd-heif could not allocate context\n"); + return GD_FALSE; + } + err = heif_context_get_encoder_for_format( + heif_ctx, (enum heif_compression_format)codec, &heif_enc); + if (err.code != heif_error_Ok) { + gd_error("gd-heif encoder acquisition failed (missing codec support?: " + "code: %d, subcode: %d, message: %s)\n", + err.code, err.subcode, err.message); + heif_context_free(heif_ctx); + return GD_FALSE; + } + + if (quality == 200) { + err = heif_encoder_set_lossless(heif_enc, GD_TRUE); + } else if (quality == -1) { + err = heif_encoder_set_lossy_quality(heif_enc, 80); + } else { + err = heif_encoder_set_lossy_quality(heif_enc, quality); + } + if (err.code != heif_error_Ok) { + gd_error("gd-heif invalid quality number\n"); + heif_encoder_release(heif_enc); + heif_context_free(heif_ctx); + return GD_FALSE; + } + + if (heif_get_version_number_major() >= 1 && + heif_get_version_number_minor() >= 9) { + err = heif_encoder_set_parameter_string(heif_enc, "chroma", chroma); + if (err.code != heif_error_Ok) { + gd_error("gd-heif invalid chroma subsampling parameter\n"); + heif_encoder_release(heif_enc); + heif_context_free(heif_ctx); + return GD_FALSE; + } + } + + err = heif_image_create(gdImageSX(im), gdImageSY(im), heif_colorspace_RGB, + heif_chroma_interleaved_RGBA, &heif_im); + if (err.code != heif_error_Ok) { + gd_error("gd-heif image creation failed"); + heif_encoder_release(heif_enc); + heif_context_free(heif_ctx); + return GD_FALSE; + } + + err = heif_image_add_plane(heif_im, heif_channel_interleaved, gdImageSX(im), + gdImageSY(im), 32); + if (err.code != heif_error_Ok) { + gd_error("gd-heif cannot add image plane\n"); + heif_image_release(heif_im); + heif_encoder_release(heif_enc); + heif_context_free(heif_ctx); + return GD_FALSE; + } + + rgba = (uint8_t *)heif_image_get_plane_readonly( + heif_im, heif_channel_interleaved, &stride); + if (!rgba) { + gd_error("gd-heif cannot get image plane\n"); + heif_image_release(heif_im); + heif_encoder_release(heif_enc); + heif_context_free(heif_ctx); + return GD_FALSE; + } + row_start = rgba; + for (y = 0; y < gdImageSY(im); y++) { + p = row_start; + for (x = 0; x < gdImageSX(im); x++) { + int c; + char a; + c = im->tpixels[y][x]; + a = gdTrueColorGetAlpha(c); + if (a == 127) { + a = 0; + } else { + a = 255 - ((a << 1) + (a >> 6)); + } + *(p++) = gdTrueColorGetRed(c); + *(p++) = gdTrueColorGetGreen(c); + *(p++) = gdTrueColorGetBlue(c); + *(p++) = a; + } + row_start += stride; + } + err = heif_context_encode_image(heif_ctx, heif_im, heif_enc, NULL, NULL); + heif_encoder_release(heif_enc); + if (err.code != heif_error_Ok) { + gd_error("gd-heif encoding failed\n"); + heif_image_release(heif_im); + heif_context_free(heif_ctx); + return GD_FALSE; + } + heif_wr.write = _gdImageWriteHeif; + heif_wr.writer_api_version = 1; + err = heif_context_write(heif_ctx, &heif_wr, (void *)outfile); + + heif_image_release(heif_im); + heif_context_free(heif_ctx); + if (err.code != heif_error_Ok) { + gd_error("gd-heif write failed (code: %d, subcode: %d, message: %s)\n", + err.code, err.subcode, err.message); + return GD_FALSE; + } + + return GD_TRUE; +} + +/* + Function: gdImageHeifCtx + + Write the image as HEIF data via a . See + for more details. + + Parameters: + + im - The image to write. + outfile - The output sink. + quality - Image quality. + codec - The output coding format. + chroma - The output chroma subsampling format. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) +gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + _gdImageHeifCtx(im, outfile, quality, codec, chroma); +} + +/* + Function: gdImageHeifEx + + outputs the specified image to the specified file in + HEIF format. The file must be open for writing. Under MSDOS and + all versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + If _quality_ is -1, a reasonable quality value (which should yield a + good general quality / size tradeoff for most situations) is used. Otherwise + _quality_ should be a value in the range 0-100, higher quality values + usually implying both higher quality and larger image sizes or 200, for + lossless codec. + + For _codec_, the default and most widely supported option is + GD_HEIF_CODEC_HEVC. GD_HEIF_CODEC_AV1 is a newer codec that may not be + supported by all decoders but can offer better compression efficiency. + They must be installed on the system and enabled at compile time to be used. + + Variants: + + stores the image using a struct. + + stores the image to RAM. + + Parameters: + + im - The image to save. + outFile - The FILE pointer to write to. + quality - Codec quality (0-100). + codec - The output coding format. + chroma - The output chroma subsampling format. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) +gdImageHeifEx(gdImagePtr im, FILE *outFile, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return; + } + _gdImageHeifCtx(im, out, quality, codec, chroma); + out->gd_free(out); +} + +/* + Function: gdImageHeif + + Variant of which uses the default quality (-1), the + default codec (GD_HEIF_Codec_HEVC) and the default chroma + subsampling (GD_HEIF_CHROMA_444). + + Parameters: + + im - The image to save + outFile - The FILE pointer to write to. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImageHeif(gdImagePtr im, FILE *outFile) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return; + } + _gdImageHeifCtx(im, out, -1, GD_HEIF_CODEC_HEVC, GD_HEIF_CHROMA_444); + out->gd_free(out); +} + +/* + Function: gdImageHeifPtr + + See . +*/ +BGD_DECLARE(void *) gdImageHeifPtr(gdImagePtr im, int *size) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + if (_gdImageHeifCtx(im, out, -1, GD_HEIF_CODEC_HEVC, GD_HEIF_CHROMA_444)) { + rv = gdDPExtractData(out, size); + } else { + rv = NULL; + } + out->gd_free(out); + + return rv; +} + +/* + Function: gdImageHeifPtrEx + + See . +*/ +BGD_DECLARE(void *) +gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + if (_gdImageHeifCtx(im, out, quality, codec, chroma)) { + rv = gdDPExtractData(out, size); + } else { + rv = NULL; + } + out->gd_free(out); + return rv; +} + +#else /* HAVE_LIBHEIF */ + +static void _noHeifError(void) { + gd_error("HEIF image support has been disabled\n"); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeif(FILE *inFile) { + _noHeifError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifPtr(int size, void *data) { + _noHeifError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromHeifCtx(gdIOCtx *infile) { + _noHeifError(); + return NULL; +} + +BGD_DECLARE(void) +gdImageHeifCtx(gdImagePtr im, gdIOCtx *outfile, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + _noHeifError(); +} + +BGD_DECLARE(void) +gdImageHeifEx(gdImagePtr im, FILE *outFile, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + _noHeifError(); +} + +BGD_DECLARE(void) gdImageHeif(gdImagePtr im, FILE *outFile) { _noHeifError(); } + +BGD_DECLARE(void *) gdImageHeifPtr(gdImagePtr im, int *size) { + _noHeifError(); + return NULL; +} + +BGD_DECLARE(void *) +gdImageHeifPtrEx(gdImagePtr im, int *size, int quality, gdHeifCodec codec, + gdHeifChroma chroma) { + _noHeifError(); + return NULL; +} + +#endif /* HAVE_LIBHEIF */ diff --git a/ext/gd/libgd/gd_intern.h b/ext/gd/libgd/gd_intern.h index c7098d21b837..b8050e6c737a 100644 --- a/ext/gd/libgd/gd_intern.h +++ b/ext/gd/libgd/gd_intern.h @@ -1,5 +1,23 @@ #ifndef GD_INTERN_H #define GD_INTERN_H +#include +#include "gd.h" +#ifdef _MSC_VER +#ifndef SSIZE_MAX +#ifdef _WIN64 +#define SSIZE_MAX _I64_MAX +#else +#define SSIZE_MAX INT_MAX +#endif +#endif +#endif +#if defined(_MSC_VER) +#define UNUSED_PARAM(x) x +#elif defined(__GNUC__) || defined(__clang__) +#define UNUSED_PARAM(x) x __attribute__((unused)) +#else +#define UNUSED_PARAM(x) x +#endif #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) #endif @@ -8,13 +26,167 @@ #define MAX(a,b) ((a)<(b)?(b):(a)) #endif #define MAX3(a,b,c) ((a)<(b)?(MAX(b,c)):(MAX(a,c))) -#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#define CLAMP(x, low, high) \ + (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) + +#ifdef _MSC_VER +#define gd_strcasecmp _stricmp +#else +#define gd_strcasecmp strcasecmp +#endif + +typedef enum { + HORIZONTAL, + VERTICAL, +} gdAxis; + +/* Convert a double to an unsigned char, rounding to the nearest + * integer and clamping the result between 0 and max. The absolute + * value of clr must be less than the maximum value of an unsigned + * short. */ +static inline unsigned char uchar_clamp(double clr, unsigned char max) { + unsigned short result; + + // assert(fabs(clr) <= SHRT_MAX); + + /* Casting a negative float to an unsigned short is undefined. + * However, casting a float to a signed truncates toward zero and + * casting a negative signed value to an unsigned of the same size + * results in a bit-identical value (assuming twos-complement + * arithmetic). This is what we want: all legal negative values + * for clr will be greater than 255. */ + + /* Convert and clamp. */ + result = (unsigned short)(short)(clr + 0.5); + if (result > max) { + result = (clr < 0) ? 0 : max; + } /* if */ + + return result; +} /* uchar_clamp*/ /* Internal prototypes: */ +/* gd_jpeg.c */ +void *gdImageJpegPtrWithMetadataNoSubsampling(gdImagePtr im, int *size, + int quality, + const gdImageMetadata *metadata); + /* gd_rotate.c */ gdImagePtr gdImageRotate90(gdImagePtr src, int ignoretransparent); gdImagePtr gdImageRotate180(gdImagePtr src, int ignoretransparent); gdImagePtr gdImageRotate270(gdImagePtr src, int ignoretransparent); -#endif +/** + * Clip a copy operation to the destination image bounds. + * Adjusts srcX/srcY to match any clipping applied to dstX/dstY. + * Returns 0 if the operation is entirely outside dst (nothing to do), + * 1 if there is work to do. + */ +static inline int gdImageClipCopy(gdImagePtr dst, int *dstX, int *dstY, + int *srcX, int *srcY, int *w, int *h) { + int x1, y1, x2, y2; + + /* overflow-safe dst rect: [dstX, dstY] to [dstX+w, dstY+h] */ + x1 = *dstX; + y1 = *dstY; + + /* check w/h are positive */ + if (*w <= 0 || *h <= 0) { + return 0; + } + + /* overflow check for dstX+w and dstY+h */ + if (*dstX > 0 && *w > INT_MAX - *dstX) { + x2 = INT_MAX; + } else { + x2 = *dstX + *w; + } + if (*dstY > 0 && *h > INT_MAX - *dstY) { + y2 = INT_MAX; + } else { + y2 = *dstY + *h; + } + + /* entirely outside dst? */ + if (x1 >= gdImageSX(dst) || y1 >= gdImageSY(dst) || x2 <= 0 || y2 <= 0) { + return 0; + } + + /* clip left */ + if (x1 < 0) { + *srcX -= x1; /* advance srcX by the same amount */ + *w += x1; /* reduce width */ + *dstX = 0; + } + + /* clip top */ + if (y1 < 0) { + *srcY -= y1; + *h += y1; + *dstY = 0; + } + + /* clip right */ + if (*dstX + *w > gdImageSX(dst)) { + *w = gdImageSX(dst) - *dstX; + } + + /* clip bottom */ + if (*dstY + *h > gdImageSY(dst)) { + *h = gdImageSY(dst) - *dstY; + } + + /* sanity: clipping may have reduced w/h to zero */ + if (*w <= 0 || *h <= 0) { + return 0; + } + + return 1; +} +static inline int gdImageClipCopyResized(gdImagePtr dst, int *dstX, int *dstY, + int *dstW, int *dstH, int *srcX, + int *srcY, int *srcW, int *srcH) { + int orig_dstW = *dstW; + int orig_dstH = *dstH; + + if (*dstW <= 0 || *dstH <= 0 || *srcW <= 0 || *srcH <= 0) { + return 0; + } + if (*dstX >= gdImageSX(dst) || *dstY >= gdImageSY(dst) || + *dstX + *dstW <= 0 || *dstY + *dstH <= 0) { + return 0; + } + + /* clip left — adjust srcX proportionally */ + if (*dstX < 0) { + *srcX += (-*dstX) * *srcW / orig_dstW; + *srcW -= (-*dstX) * *srcW / orig_dstW; + *dstW += *dstX; + *dstX = 0; + } + /* clip top */ + if (*dstY < 0) { + *srcY += (-*dstY) * *srcH / orig_dstH; + *srcH -= (-*dstY) * *srcH / orig_dstH; + *dstH += *dstY; + *dstY = 0; + } + /* clip right */ + if (*dstX + *dstW > gdImageSX(dst)) { + int clip = *dstX + *dstW - gdImageSX(dst); + *srcW -= clip * *srcW / orig_dstW; + *dstW = gdImageSX(dst) - *dstX; + } + /* clip bottom */ + if (*dstY + *dstH > gdImageSY(dst)) { + int clip = *dstY + *dstH - gdImageSY(dst); + *srcH -= clip * *srcH / orig_dstH; + *dstH = gdImageSY(dst) - *dstY; + } + + if (*dstW <= 0 || *dstH <= 0) + return 0; + return 1; +} +#endif /* GD_INTERN_H */ diff --git a/ext/gd/libgd/gd_interpolation.c b/ext/gd/libgd/gd_interpolation.c index 7731d47a0c7c..cdaf787ac5db 100644 --- a/ext/gd/libgd/gd_interpolation.c +++ b/ext/gd/libgd/gd_interpolation.c @@ -33,7 +33,6 @@ rounded to the nearest pixel color value instead of being casted to ILubyte (usually an int or char). Otherwise, artifacting occurs. - */ /* @@ -50,18 +49,30 @@ /* TODO: - Optimize pixel accesses and loops once we have continuous buffer - - Add scale support for a portion only of an image (equivalent of copyresized/resampled) + - Add scale support for a portion only of an image (equivalent of +copyresized/resampled) */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include #include #include #include -#include -#include + +#undef NDEBUG +/* Comment out this line to enable asserts. + * TODO: This logic really belongs in cmake and configure. + */ +/* #define NDEBUG 1 */ +#include #include "gd.h" -#include "gdhelpers.h" #include "gd_intern.h" +#include "gdhelpers.h" static gdImagePtr gdImageScaleBilinear(gdImagePtr im, const unsigned int new_width, @@ -70,24 +81,19 @@ static gdImagePtr gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width, const unsigned int height); static gdImagePtr gdImageScaleNearestNeighbour(gdImagePtr im, - const unsigned int width, const unsigned int height); -static gdImagePtr gdImageScaleTwoPass(const gdImagePtr pOrigImage, - const unsigned int uOrigWidth, - const unsigned int uOrigHeight, - const unsigned int uNewWidth, - const unsigned int uNewHeight); + const unsigned int width, + const unsigned int height); static gdImagePtr gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, const int bgColor); -static gdImagePtr gdImageRotateGeneric(gdImagePtr src, - const float degrees, +static gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees, const int bgColor); -/* only used here, let do a generic fixed point integers later if required by other - part of GD */ +#ifdef FUNCTION_NOT_USED_YET +/* only used by the inactive edge helper below */ typedef long gdFixed; /* Integer to fixed point */ -#define gd_itofx(x) (long)((unsigned long)(x) << 8) +#define gd_itofx(x) ((x) << 8) /* Float to fixed point */ #define gd_ftofx(x) (long)((x) * 256) @@ -108,85 +114,31 @@ typedef long gdFixed; #define gd_mulfx(x,y) (((x) * (y)) >> 8) /* Divide a fixed by a fixed */ -#define gd_divfx(x,y) ((long)((unsigned long)(x) << 8) / (y)) +#define gd_divfx(x, y) (((x) << 8) / (y)) +#endif + +typedef struct _FilterInfo { + double (*function)(const double, const double), support; +} FilterInfo; -typedef struct -{ +typedef struct { double *Weights; /* Normalized weights of neighboring pixels */ int Left,Right; /* Bounds of source pixels window */ } ContributionType; /* Contirbution information for a single pixel */ -typedef struct -{ +typedef struct { ContributionType *ContribRow; /* Row (or column) of contribution weights */ - unsigned int WindowSize, /* Filter window size (of affecting source pixels) */ + unsigned int + WindowSize, /* Filter window size (of affecting source pixels) */ LineLength; /* Length of line (no. or rows / cols) */ } LineContribType; -/* Each core filter has its own radius */ -#define DEFAULT_FILTER_LINEAR 1.0f -#define DEFAULT_FILTER_BICUBIC 3.0f -#define DEFAULT_FILTER_BOX 0.5f -#define DEFAULT_FILTER_GENERALIZED_CUBIC 0.5f -#define DEFAULT_FILTER_RADIUS 1.0f -#define DEFAULT_LANCZOS8_RADIUS 8.0f -#define DEFAULT_LANCZOS3_RADIUS 3.0f -#define DEFAULT_HERMITE_RADIUS 1.0f -#define DEFAULT_BOX_RADIUS 0.5f -#define DEFAULT_TRIANGLE_RADIUS 1.0f -#define DEFAULT_BELL_RADIUS 1.5f -#define DEFAULT_CUBICSPLINE_RADIUS 2.0f -#define DEFAULT_MITCHELL_RADIUS 2.0f -#define DEFAULT_COSINE_RADIUS 1.0f -#define DEFAULT_CATMULLROM_RADIUS 2.0f -#define DEFAULT_QUADRATIC_RADIUS 1.5f -#define DEFAULT_QUADRATICBSPLINE_RADIUS 1.5f -#define DEFAULT_CUBICCONVOLUTION_RADIUS 3.0f -#define DEFAULT_GAUSSIAN_RADIUS 1.0f -#define DEFAULT_HANNING_RADIUS 1.0f -#define DEFAULT_HAMMING_RADIUS 1.0f -#define DEFAULT_SINC_RADIUS 1.0f -#define DEFAULT_WELSH_RADIUS 1.0f - -enum GD_RESIZE_FILTER_TYPE{ - FILTER_DEFAULT = 0, - FILTER_BELL, - FILTER_BESSEL, - FILTER_BLACKMAN, - FILTER_BOX, - FILTER_BSPLINE, - FILTER_CATMULLROM, - FILTER_COSINE, - FILTER_CUBICCONVOLUTION, - FILTER_CUBICSPLINE, - FILTER_HERMITE, - FILTER_LANCZOS3, - FILTER_LANCZOS8, - FILTER_MITCHELL, - FILTER_QUADRATIC, - FILTER_QUADRATICBSPLINE, - FILTER_TRIANGLE, - FILTER_GAUSSIAN, - FILTER_HANNING, - FILTER_HAMMING, - FILTER_SINC, - FILTER_WELSH, - - FILTER_CALLBACK = 999 -}; - -typedef enum GD_RESIZE_FILTER_TYPE gdResizeFilterType; - -static double KernelBessel_J1(const double x) -{ +static double KernelBessel_J1(const double x) { double p, q; register long i; - static const double - Pone[] = - { - 0.581199354001606143928050809e+21, + static const double Pone[] = {0.581199354001606143928050809e+21, -0.6672106568924916298020941484e+20, 0.2316433580634002297931815435e+19, -0.3588817569910106050743641413e+17, @@ -194,11 +146,8 @@ static double KernelBessel_J1(const double x) -0.1322983480332126453125473247e+13, 0.3413234182301700539091292655e+10, -0.4695753530642995859767162166e+7, - 0.270112271089232341485679099e+4 - }, - Qone[] = - { - 0.11623987080032122878585294e+22, + 0.270112271089232341485679099e+4}, + Qone[] = {0.11623987080032122878585294e+22, 0.1185770712190320999837113348e+20, 0.6092061398917521746105196863e+17, 0.2081661221307607351240184229e+15, @@ -206,93 +155,72 @@ static double KernelBessel_J1(const double x) 0.1013863514358673989967045588e+10, 0.1501793594998585505921097578e+7, 0.1606931573481487801970916749e+4, - 0.1e+1 - }; + 0.1e+1}; p = Pone[8]; q = Qone[8]; - for (i=7; i >= 0; i--) - { + for (i = 7; i >= 0; i--) { p = p*x*x+Pone[i]; q = q*x*x+Qone[i]; } return (double)(p/q); } -static double KernelBessel_P1(const double x) -{ +static double KernelBessel_P1(const double x) { double p, q; register long i; - static const double - Pone[] = - { - 0.352246649133679798341724373e+5, + static const double Pone[] = {0.352246649133679798341724373e+5, 0.62758845247161281269005675e+5, 0.313539631109159574238669888e+5, 0.49854832060594338434500455e+4, 0.2111529182853962382105718e+3, - 0.12571716929145341558495e+1 - }, - Qone[] = - { - 0.352246649133679798068390431e+5, + 0.12571716929145341558495e+1}, + Qone[] = {0.352246649133679798068390431e+5, 0.626943469593560511888833731e+5, 0.312404063819041039923015703e+5, 0.4930396490181088979386097e+4, 0.2030775189134759322293574e+3, - 0.1e+1 - }; + 0.1e+1}; p = Pone[5]; q = Qone[5]; - for (i=4; i >= 0; i--) - { + for (i = 4; i >= 0; i--) { p = p*(8.0/x)*(8.0/x)+Pone[i]; q = q*(8.0/x)*(8.0/x)+Qone[i]; } return (double)(p/q); } -static double KernelBessel_Q1(const double x) -{ +static double KernelBessel_Q1(const double x) { double p, q; register long i; - static const double - Pone[] = - { - 0.3511751914303552822533318e+3, + static const double Pone[] = {0.3511751914303552822533318e+3, 0.7210391804904475039280863e+3, 0.4259873011654442389886993e+3, 0.831898957673850827325226e+2, 0.45681716295512267064405e+1, - 0.3532840052740123642735e-1 - }, - Qone[] = - { - 0.74917374171809127714519505e+4, + 0.3532840052740123642735e-1}, + Qone[] = {0.74917374171809127714519505e+4, 0.154141773392650970499848051e+5, 0.91522317015169922705904727e+4, 0.18111867005523513506724158e+4, 0.1038187585462133728776636e+3, - 0.1e+1 - }; + 0.1e+1}; p = Pone[5]; q = Qone[5]; - for (i=4; i >= 0; i--) - { + for (i = 4; i >= 0; i--) { p = p*(8.0/x)*(8.0/x)+Pone[i]; q = q*(8.0/x)*(8.0/x)+Qone[i]; } return (double)(p/q); } -static double KernelBessel_Order1(double x) -{ +static double KernelBessel_Order1(double x) { double p, q; if (x == 0.0) @@ -302,27 +230,38 @@ static double KernelBessel_Order1(double x) x=(-x); if (x < 8.0) return (p*KernelBessel_J1(x)); - q = (double)sqrt(2.0f/(M_PI*x))*(double)(KernelBessel_P1(x)*(1.0f/sqrt(2.0f)*(sin(x)-cos(x)))-8.0f/x*KernelBessel_Q1(x)* + q = (double)sqrt(2.0f / (M_PI * x)) * + (double)(KernelBessel_P1(x) * (1.0f / sqrt(2.0f) * (sin(x) - cos(x))) - + 8.0f / x * KernelBessel_Q1(x) * (-1.0f/sqrt(2.0f)*(sin(x)+cos(x)))); if (p < 0.0f) q = (-q); return (q); } -static double filter_bessel(const double x) -{ +static double filter_sinc(const double x, const double support) { + ARG_NOT_USED(support); + /* X-scaled Sinc(x) function. */ + if (x == 0.0) + return (1.0); + return (sin(M_PI * (double)x) / (M_PI * (double)x)); +} + +static double filter_bessel(const double x, const double support) { + ARG_NOT_USED(support); if (x == 0.0f) return (double)(M_PI/4.0f); return (KernelBessel_Order1((double)M_PI*x)/(2.0f*x)); } - -static double filter_blackman(const double x) -{ - return (0.42f+0.5f*(double)cos(M_PI*x)+0.08f*(double)cos(2.0f*M_PI*x)); +static double filter_blackman(const double x, const double support) { + ARG_NOT_USED(support); + return (0.42f + 0.5f * (double)cos(M_PI * x) + + 0.08f * (double)cos(2.0f * M_PI * x)); } -double filter_linear(const double x) { +static double filter_linear(const double x, const double support) { + ARG_NOT_USED(support); double ax = fabs(x); if (ax < 1.0f) { return (1.0f - ax); @@ -330,24 +269,14 @@ double filter_linear(const double x) { return 0.0f; } -/** - * Bicubic interpolation kernel (a=-1): - \verbatim - / - | 1-2|t|**2+|t|**3 , if |t| < 1 - h(t) = | 4-8|t|+5|t|**2-|t|**3 , if 1<=|t|<2 - | 0 , otherwise - \ - \endverbatim - * ***bd*** 2.2004 - */ -static double filter_bicubic(const double t) -{ - const double abs_t = (double)fabs(t); - const double abs_t_sq = abs_t * abs_t; - if (abs_t<1) return 1-2*abs_t_sq+abs_t_sq*abs_t; - if (abs_t<2) return 4 - 8*abs_t +5*abs_t_sq - abs_t_sq*abs_t; - return 0; +static double filter_blackman_bessel(const double x, const double support) { + ARG_NOT_USED(support); + return (filter_blackman(x / support, support) * filter_bessel(x, support)); +} + +static double filter_blackman_sinc(const double x, const double support) { + ARG_NOT_USED(support); + return (filter_blackman(x / support, support) * filter_sinc(x, support)); } /** @@ -361,20 +290,20 @@ static double filter_bicubic(const double t) \endverbatim * Often used values for a are -1 and -1/2. */ -static double filter_generalized_cubic(const double t) -{ - const double a = -DEFAULT_FILTER_GENERALIZED_CUBIC; +static double filter_generalized_cubic(const double t, const double support) { + const double a = -support; double abs_t = (double)fabs(t); double abs_t_sq = abs_t * abs_t; - if (abs_t < 1) return (a + 2) * abs_t_sq * abs_t - (a + 3) * abs_t_sq + 1; - if (abs_t < 2) return a * abs_t_sq * abs_t - 5 * a * abs_t_sq + 8 * a * abs_t - 4 * a; + if (abs_t < 1) + return (a + 2) * abs_t_sq * abs_t - (a + 3) * abs_t_sq + 1; + if (abs_t < 2) + return a * abs_t_sq * abs_t - 5 * a * abs_t_sq + 8 * a * abs_t - 4 * a; return 0; } -#ifdef FUNCTION_NOT_USED_YET /* CubicSpline filter, default radius 2 */ -static double filter_cubic_spline(const double x1) -{ +static double filter_cubic_spline(const double x1, const double support) { + ARG_NOT_USED(support); const double x = x1 < 0.0 ? -x1 : x1; if (x < 1.0 ) { @@ -387,33 +316,34 @@ static double filter_cubic_spline(const double x1) } return 0; } -#endif #ifdef FUNCTION_NOT_USED_YET /* CubicConvolution filter, default radius 3 */ -static double filter_cubic_convolution(const double x1) -{ +static double filter_cubic_convolution(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; const double x2 = x1 * x1; const double x2_x = x2 * x; - - if (x <= 1.0) return ((4.0 / 3.0)* x2_x - (7.0 / 3.0) * x2 + 1.0); - if (x <= 2.0) return (- (7.0 / 12.0) * x2_x + 3 * x2 - (59.0 / 12.0) * x + 2.5); - if (x <= 3.0) return ( (1.0/12.0) * x2_x - (2.0 / 3.0) * x2 + 1.75 * x - 1.5); + ARG_NOT_USED(support); + if (x <= 1.0) + return ((4.0 / 3.0) * x2_x - (7.0 / 3.0) * x2 + 1.0); + if (x <= 2.0) + return (-(7.0 / 12.0) * x2_x + 3 * x2 - (59.0 / 12.0) * x + 2.5); + if (x <= 3.0) + return ((1.0 / 12.0) * x2_x - (2.0 / 3.0) * x2 + 1.75 * x - 1.5); return 0; } #endif -static double filter_box(double x) { - if (x < - DEFAULT_FILTER_BOX) +static double filter_box(double x, const double support) { + if (x < -support) return 0.0f; - if (x < DEFAULT_FILTER_BOX) + if (x < support) return 1.0f; return 0.0f; } -static double filter_catmullrom(const double x) -{ +static double filter_catmullrom(const double x, const double support) { + ARG_NOT_USED(support); if (x < -2.0) return(0.0f); if (x < -1.0) @@ -427,82 +357,66 @@ static double filter_catmullrom(const double x) return(0.0f); } -#ifdef FUNCTION_NOT_USED_YET -static double filter_filter(double t) -{ - /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */ - if(t < 0.0) t = -t; - if(t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0); - return(0.0); -} -#endif - -#ifdef FUNCTION_NOT_USED_YET /* Lanczos8 filter, default radius 8 */ -static double filter_lanczos8(const double x1) -{ +static double filter_lanczos8(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; -#define R DEFAULT_LANCZOS8_RADIUS - if ( x == 0.0) return 1; + if (x == 0.0) + return 1; - if ( x < R) { - return R * sin(x*M_PI) * sin(x * M_PI/ R) / (x * M_PI * x * M_PI); + if (x < support) { + return support * sin(x * M_PI) * sin(x * M_PI / support) / + (x * M_PI * x * M_PI); } return 0.0; -#undef R } -#endif -#ifdef FUNCTION_NOT_USED_YET -/* Lanczos3 filter, default radius 3 */ -static double filter_lanczos3(const double x1) -{ - const double x = x1 < 0.0 ? -x1 : x1; -#define R DEFAULT_LANCZOS3_RADIUS - - if ( x == 0.0) return 1; - - if ( x < R) - { - return R * sin(x*M_PI) * sin(x * M_PI / R) / (x * M_PI * x * M_PI); +static double filter_lanczos3(const double x1, const double support) { + if (x1 < -3.0) + return (0.0); + if (x1 < 0.0) + return (filter_sinc(-x1, support) * filter_sinc(-x1 / 3.0, support)); + if (x1 < 3.0) + return (filter_sinc(x1, support) * filter_sinc(x1 / 3.0, support)); + return (0.0); } - return 0.0; -#undef R -} -#endif /* Hermite filter, default radius 1 */ -static double filter_hermite(const double x1) -{ +static double filter_hermite(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; + ARG_NOT_USED(support); - if (x < 1.0) return ((2.0 * x - 3) * x * x + 1.0 ); + if (x < 1.0) + return ((2.0 * x - 3) * x * x + 1.0); return 0.0; } /* Trangle filter, default radius 1 */ -static double filter_triangle(const double x1) -{ +static double filter_triangle(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; - if (x < 1.0) return (1.0 - x); + ARG_NOT_USED(support); + + if (x < 1.0) + return (1.0 - x); return 0.0; } /* Bell filter, default radius 1.5 */ -static double filter_bell(const double x1) -{ +static double filter_bell(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; + ARG_NOT_USED(support); - if (x < 0.5) return (0.75 - x*x); - if (x < 1.5) return (0.5 * pow(x - 1.5, 2.0)); + if (x < 0.5) + return (0.75 - x * x); + if (x < 1.5) + return (0.5 * pow(x - 1.5, 2.0)); return 0.0; } /* Mitchell filter, default radius 2.0 */ -static double filter_mitchell(const double x) -{ +static double filter_mitchell(const double x, const double support) { + ARG_NOT_USED(support); #define KM_B (1.0f/3.0f) #define KM_C (1.0f/3.0f) #define KM_P0 (( 6.0f - 2.0f * KM_B ) / 6.0f) @@ -523,33 +437,32 @@ static double filter_mitchell(const double x) return(KM_P0+x*x*(KM_P2+x*KM_P3)); if (x < 2.0f) return(KM_Q0+x*(KM_Q1+x*(KM_Q2+x*KM_Q3))); + return(0.0f); } - - -#ifdef FUNCTION_NOT_USED_YET /* Cosine filter, default radius 1 */ -static double filter_cosine(const double x) -{ - if ((x >= -1.0) && (x <= 1.0)) return ((cos(x * M_PI) + 1.0)/2.0); +static double filter_cosine(const double x, const double support) { + ARG_NOT_USED(support); + if ((x >= -1.0) && (x <= 1.0)) + return ((cos(x * M_PI) + 1.0) / 2.0); return 0; } -#endif /* Quadratic filter, default radius 1.5 */ -static double filter_quadratic(const double x1) -{ +static double filter_quadratic(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; - - if (x <= 0.5) return (- 2.0 * x * x + 1); - if (x <= 1.5) return (x * x - 2.5* x + 1.5); + ARG_NOT_USED(support); + if (x <= 0.5) + return (-2.0 * x * x + 1); + if (x <= 1.5) + return (x * x - 2.5 * x + 1.5); return 0.0; } -static double filter_bspline(const double x) -{ +static double filter_bspline(const double x, const double support) { + ARG_NOT_USED(support); if (x>2.0f) { return 0.0f; } else { @@ -559,41 +472,53 @@ static double filter_bspline(const double x) const double xp1 = x + 1.0f; const double xp2 = x + 2.0f; - if ((xp2) <= 0.0f) a = 0.0f; else a = xp2*xp2*xp2; - if ((xp1) <= 0.0f) b = 0.0f; else b = xp1*xp1*xp1; - if (x <= 0) c = 0.0f; else c = x*x*x; - if ((xm1) <= 0.0f) d = 0.0f; else d = xm1*xm1*xm1; - - return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d))); + if ((xp2) <= 0.0f) + a = 0.0f; + else + a = xp2 * xp2 * xp2; + if ((xp1) <= 0.0f) + b = 0.0f; + else + b = xp1 * xp1 * xp1; + if (x <= 0) + c = 0.0f; + else + c = x * x * x; + if ((xm1) <= 0.0f) + d = 0.0f; + else + d = xm1 * xm1 * xm1; + + return (0.16666666666666666667f * + (a - (4.0f * b) + (6.0f * c) - (4.0f * d))); } } -#ifdef FUNCTION_NOT_USED_YET /* QuadraticBSpline filter, default radius 1.5 */ -static double filter_quadratic_bspline(const double x1) -{ +static double filter_quadratic_bspline(const double x1, const double support) { const double x = x1 < 0.0 ? -x1 : x1; - - if (x <= 0.5) return (- x * x + 0.75); - if (x <= 1.5) return (0.5 * x * x - 1.5 * x + 1.125); + ARG_NOT_USED(support); + if (x <= 0.5) + return (-x * x + 0.75); + if (x <= 1.5) + return (0.5 * x * x - 1.5 * x + 1.125); return 0.0; } -#endif -static double filter_gaussian(const double x) -{ +static double filter_gaussian(const double x, const double support) { + ARG_NOT_USED(support); /* return(exp((double) (-2.0 * x * x)) * sqrt(2.0 / M_PI)); */ return (double)(exp(-2.0f * x * x) * 0.79788456080287f); } -static double filter_hanning(const double x) -{ +static double filter_hanning(const double x, const double support) { + ARG_NOT_USED(support); /* A Cosine windowing function */ return(0.5 + 0.5 * cos(M_PI * x)); } -static double filter_hamming(const double x) -{ +static double filter_hamming(const double x, const double support) { + ARG_NOT_USED(support); /* should be (0.54+0.46*cos(M_PI*(double) x)); but this approximation is sufficient */ @@ -606,36 +531,31 @@ static double filter_hamming(const double x) return 0.0f; } -static double filter_power(const double x) -{ +static double filter_power(const double x, const double support) { + ARG_NOT_USED(support); const double a = 2.0f; - if (fabs(x)>1) return 0.0f; + if (fabs(x) > 1) + return 0.0f; return (1.0f - (double)fabs(pow(x,a))); } -static double filter_sinc(const double x) -{ - /* X-scaled Sinc(x) function. */ - if (x == 0.0) return(1.0); - return (sin(M_PI * (double) x) / (M_PI * (double) x)); -} - -#ifdef FUNCTION_NOT_USED_YET -static double filter_welsh(const double x) -{ +static double filter_welsh(const double x, const double support) { + ARG_NOT_USED(support); /* Welsh parabolic windowing filter */ if (x < 1.0) return(1 - x*x); return(0.0); } -#endif +#if defined(_MSC_VER) && !defined(inline) +#define inline __inline +#endif -/* keep it for future usage for affine copy over an existing image, targetting fix for 2.2.2 */ -#if 0 +/* keep it for future usage for affine copy over an existing image, targetting + * fix for 2.2.2 */ +#ifdef FUNCTION_NOT_USED_YET /* Copied from upstream's libgd */ -static inline int _color_blend (const int dst, const int src) -{ +static inline int _color_blend(const int dst, const int src) { const int src_alpha = gdTrueColorGetAlpha(src); if( src_alpha == gdAlphaOpaque ) { @@ -643,47 +563,77 @@ static inline int _color_blend (const int dst, const int src) } else { const int dst_alpha = gdTrueColorGetAlpha(dst); - if( src_alpha == gdAlphaTransparent ) return dst; + if (src_alpha == gdAlphaTransparent) + return dst; if( dst_alpha == gdAlphaTransparent ) { return src; } else { register int alpha, red, green, blue; const int src_weight = gdAlphaTransparent - src_alpha; - const int dst_weight = (gdAlphaTransparent - dst_alpha) * src_alpha / gdAlphaMax; + const int dst_weight = + (gdAlphaTransparent - dst_alpha) * src_alpha / gdAlphaMax; const int tot_weight = src_weight + dst_weight; alpha = src_alpha * dst_alpha / gdAlphaMax; - red = (gdTrueColorGetRed(src) * src_weight - + gdTrueColorGetRed(dst) * dst_weight) / tot_weight; - green = (gdTrueColorGetGreen(src) * src_weight - + gdTrueColorGetGreen(dst) * dst_weight) / tot_weight; - blue = (gdTrueColorGetBlue(src) * src_weight - + gdTrueColorGetBlue(dst) * dst_weight) / tot_weight; + red = (gdTrueColorGetRed(src) * src_weight + + gdTrueColorGetRed(dst) * dst_weight) / + tot_weight; + green = (gdTrueColorGetGreen(src) * src_weight + + gdTrueColorGetGreen(dst) * dst_weight) / + tot_weight; + blue = (gdTrueColorGetBlue(src) * src_weight + + gdTrueColorGetBlue(dst) * dst_weight) / + tot_weight; return ((alpha << 24) + (red << 16) + (green << 8) + blue); } } } + +static inline int _setEdgePixel(const gdImagePtr src, unsigned int x, + unsigned int y, gdFixed coverage, + const int bgColor) { + const gdFixed f_127 = gd_itofx(127); + register int c = src->tpixels[y][x]; + c = c | (((int)(gd_fxtof(gd_mulfx(coverage, f_127)) + 50.5f)) << 24); + return _color_blend(bgColor, c); +} #endif -static inline int getPixelOverflowTC(gdImagePtr im, const int x, const int y, const int bgColor) -{ +static inline int getPixelOverflowTC(gdImagePtr im, const int x, const int y, + const int bgColor /* 31bit ARGB TC */) { if (gdImageBoundsSafe(im, x, y)) { const int c = im->tpixels[y][x]; if (c == im->transparent) { return bgColor == -1 ? gdTrueColorAlpha(0, 0, 0, 127) : bgColor; } - return c; + return c; /* 31bit ARGB TC */ } else { + return bgColor; /* 31bit ARGB TC */ + } +} + +static inline int getPixelOverflowTCClipped(gdImagePtr im, const int x, + const int y, const int bgColor, + const gdRectPtr clip) { + if (clip != NULL && + (x < clip->x || y < clip->y || x >= clip->x + clip->width || + y >= clip->y + clip->height)) { return bgColor; } + return getPixelOverflowTC(im, x, y, bgColor); } -#define colorIndex2RGBA(c) gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], im->alpha[(c)]) -#define colorIndex2RGBcustomA(c, a) gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], im->alpha[(a)]) -static inline int getPixelOverflowPalette(gdImagePtr im, const int x, const int y, const int bgColor) -{ +#define colorIndex2RGBA(c) \ + gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], \ + im->alpha[(c)]) +#define colorIndex2RGBcustomA(c, a) \ + gdTrueColorAlpha(im->red[(c)], im->green[(c)], im->blue[(c)], \ + im->alpha[(a)]) +static inline int +getPixelOverflowPalette(gdImagePtr im, const int x, const int y, + const int bgColor /* 31bit ARGB TC */) { if (gdImageBoundsSafe(im, x, y)) { const int c = im->pixels[y][x]; if (c == im->transparent) { @@ -691,12 +641,45 @@ static inline int getPixelOverflowPalette(gdImagePtr im, const int x, const int } return colorIndex2RGBA(c); } else { + return bgColor; /* 31bit ARGB TC */ + } +} + +static inline int getPixelOverflowPaletteClipped(gdImagePtr im, const int x, + const int y, const int bgColor, + const gdRectPtr clip) { + if (clip != NULL && + (x < clip->x || y < clip->y || x >= clip->x + clip->width || + y >= clip->y + clip->height)) { return bgColor; } + return getPixelOverflowPalette(im, x, y, bgColor); +} + +static inline int gdClampInt(const int value, const int min, const int max) { + return value < min ? min : (value > max ? max : value); } -static int getPixelInterpolateWeight(gdImagePtr im, const double x, const double y, const int bgColor) -{ +static inline interpolation_method +gdImageGetEffectiveInterpolation(const gdImagePtr im) { + switch (im->interpolation_id) { + case GD_DEFAULT: + case GD_BILINEAR_FIXED: + case GD_LINEAR: + return filter_linear; + + case GD_BICUBIC: + case GD_BICUBIC_FIXED: + return filter_cubic_spline; + + default: + return im->interpolation; + } +} + +static int getPixelInterpolateWeightClipped(gdImagePtr im, const double x, + const double y, const int bgColor, + const gdRectPtr clip) { /* Closest pixel <= (xf,yf) */ int sx = (int)(x); int sy = (int)(y); @@ -710,27 +693,38 @@ static int getPixelInterpolateWeight(gdImagePtr im, const double x, const double const double m4 = nxf * nyf; /* get color values of neighbouring pixels */ - const int c1 = im->trueColor == 1 ? getPixelOverflowTC(im, sx, sy, bgColor) : getPixelOverflowPalette(im, sx, sy, bgColor); - const int c2 = im->trueColor == 1 ? getPixelOverflowTC(im, sx - 1, sy, bgColor) : getPixelOverflowPalette(im, sx - 1, sy, bgColor); - const int c3 = im->trueColor == 1 ? getPixelOverflowTC(im, sx, sy - 1, bgColor) : getPixelOverflowPalette(im, sx, sy - 1, bgColor); - const int c4 = im->trueColor == 1 ? getPixelOverflowTC(im, sx - 1, sy - 1, bgColor) : getPixelOverflowPalette(im, sx, sy - 1, bgColor); + const int c1 = + im->trueColor == 1 + ? getPixelOverflowTCClipped(im, sx, sy, bgColor, clip) + : getPixelOverflowPaletteClipped(im, sx, sy, bgColor, clip); + const int c2 = + im->trueColor == 1 + ? getPixelOverflowTCClipped(im, sx - 1, sy, bgColor, clip) + : getPixelOverflowPaletteClipped(im, sx - 1, sy, bgColor, clip); + const int c3 = + im->trueColor == 1 + ? getPixelOverflowTCClipped(im, sx, sy - 1, bgColor, clip) + : getPixelOverflowPaletteClipped(im, sx, sy - 1, bgColor, clip); + const int c4 = + im->trueColor == 1 + ? getPixelOverflowTCClipped(im, sx - 1, sy - 1, bgColor, clip) + : getPixelOverflowPaletteClipped(im, sx, sy - 1, bgColor, clip); int r, g, b, a; - if (x < 0) sx--; - if (y < 0) sy--; + if (x < 0) + sx--; + if (y < 0) + sy--; /* component-wise summing-up of color values */ - if (im->trueColor) { - r = (int)(m1*gdTrueColorGetRed(c1) + m2*gdTrueColorGetRed(c2) + m3*gdTrueColorGetRed(c3) + m4*gdTrueColorGetRed(c4)); - g = (int)(m1*gdTrueColorGetGreen(c1) + m2*gdTrueColorGetGreen(c2) + m3*gdTrueColorGetGreen(c3) + m4*gdTrueColorGetGreen(c4)); - b = (int)(m1*gdTrueColorGetBlue(c1) + m2*gdTrueColorGetBlue(c2) + m3*gdTrueColorGetBlue(c3) + m4*gdTrueColorGetBlue(c4)); - a = (int)(m1*gdTrueColorGetAlpha(c1) + m2*gdTrueColorGetAlpha(c2) + m3*gdTrueColorGetAlpha(c3) + m4*gdTrueColorGetAlpha(c4)); - } else { - r = (int)(m1*im->red[(c1)] + m2*im->red[(c2)] + m3*im->red[(c3)] + m4*im->red[(c4)]); - g = (int)(m1*im->green[(c1)] + m2*im->green[(c2)] + m3*im->green[(c3)] + m4*im->green[(c4)]); - b = (int)(m1*im->blue[(c1)] + m2*im->blue[(c2)] + m3*im->blue[(c3)] + m4*im->blue[(c4)]); - a = (int)(m1*im->alpha[(c1)] + m2*im->alpha[(c2)] + m3*im->alpha[(c3)] + m4*im->alpha[(c4)]); - } + r = (int)(m1 * gdTrueColorGetRed(c1) + m2 * gdTrueColorGetRed(c2) + + m3 * gdTrueColorGetRed(c3) + m4 * gdTrueColorGetRed(c4)); + g = (int)(m1 * gdTrueColorGetGreen(c1) + m2 * gdTrueColorGetGreen(c2) + + m3 * gdTrueColorGetGreen(c3) + m4 * gdTrueColorGetGreen(c4)); + b = (int)(m1 * gdTrueColorGetBlue(c1) + m2 * gdTrueColorGetBlue(c2) + + m3 * gdTrueColorGetBlue(c3) + m4 * gdTrueColorGetBlue(c4)); + a = (int)(m1 * gdTrueColorGetAlpha(c1) + m2 * gdTrueColorGetAlpha(c2) + + m3 * gdTrueColorGetAlpha(c3) + m4 * gdTrueColorGetAlpha(c4)); r = CLAMP(r, 0, 255); g = CLAMP(g, 0, 255); @@ -740,7 +734,7 @@ static int getPixelInterpolateWeight(gdImagePtr im, const double x, const double } /** - * Function: getPixelInterpolated + * InternalFunction: getPixelInterpolated * Returns the interpolated color value using the default interpolation * method. The returned color is always in the ARGB format (truecolor). * @@ -751,13 +745,14 @@ static int getPixelInterpolateWeight(gdImagePtr im, const double x, const double * method - Interpolation method * * Returns: - * GD_TRUE if the affine is rectilinear or GD_FALSE + * the interpolated color or -1 on error * * See also: * */ -int getPixelInterpolated(gdImagePtr im, const double x, const double y, const int bgColor) -{ +static int getPixelInterpolatedClipped(gdImagePtr im, const double x, + const double y, const int bgColor, + const gdRectPtr clip) { const int xi=(int)(x); const int yi=(int)(y); int yii; @@ -765,27 +760,27 @@ int getPixelInterpolated(gdImagePtr im, const double x, const double y, const in double kernel, kernel_cache_y; double kernel_x[12], kernel_y[4]; double new_r = 0.0f, new_g = 0.0f, new_b = 0.0f, new_a = 0.0f; + const interpolation_method interpolation = + gdImageGetEffectiveInterpolation(im); /* These methods use special implementations */ if (im->interpolation_id == GD_NEAREST_NEIGHBOUR) { - return -1; + const int sx = gdClampInt((int)floor(x + 0.5), 0, gdImageSX(im) - 1); + const int sy = gdClampInt((int)floor(y + 0.5), 0, gdImageSY(im) - 1); + + return im->trueColor == 1 + ? getPixelOverflowTCClipped(im, sx, sy, bgColor, clip) + : getPixelOverflowPaletteClipped(im, sx, sy, bgColor, clip); } if (im->interpolation_id == GD_WEIGHTED4) { - return getPixelInterpolateWeight(im, x, y, bgColor); + return getPixelInterpolateWeightClipped(im, x, y, bgColor, clip); } - if (im->interpolation_id == GD_NEAREST_NEIGHBOUR) { - if (im->trueColor == 1) { - return getPixelOverflowTC(im, xi, yi, bgColor); - } else { - return getPixelOverflowPalette(im, xi, yi, bgColor); - } - } - if (im->interpolation) { + if (interpolation) { for (i=0; i<4; i++) { - kernel_x[i] = (double) im->interpolation((double)(xi+i-1-x)); - kernel_y[i] = (double) im->interpolation((double)(yi+i-1-y)); + kernel_x[i] = (double)interpolation((double)(xi + i - 1 - x), 1.0); + kernel_y[i] = (double)interpolation((double)(yi + i - 1 - y), 1.0); } } else { return -1; @@ -800,7 +795,8 @@ int getPixelInterpolated(gdImagePtr im, const double x, const double y, const in kernel_cache_y = kernel_y[yii-(yi-1)]; if (im->trueColor) { for (xii=xi-1; xiiContribRow = (ContributionType *) gdMalloc(line_length * sizeof(ContributionType)); + res->ContribRow = + (ContributionType *)gdMalloc(line_length * sizeof(ContributionType)); if (res->ContribRow == NULL) { gdFree(res); return NULL; @@ -859,6 +863,7 @@ static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, res->ContribRow[u].Weights = (double *) gdMalloc(weights_size); if (res->ContribRow[u].Weights == NULL) { unsigned int i; + for (i=0;iContribRow[i].Weights); } @@ -870,8 +875,7 @@ static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, return res; } -static inline void _gdContributionsFree(LineContribType * p) -{ +static inline void _gdContributionsFree(LineContribType *p) { unsigned int u; for (u = 0; u < p->LineLength; u++) { gdFree(p->ContribRow[u].Weights); @@ -880,11 +884,13 @@ static inline void _gdContributionsFree(LineContribType * p) gdFree(p); } -static inline LineContribType *_gdContributionsCalc(unsigned int line_size, unsigned int src_size, double scale_d, const interpolation_method pFilter) -{ +static inline LineContribType * +_gdContributionsCalc(unsigned int line_size, unsigned int src_size, + double scale_d, const double support, + const interpolation_method pFilter) { double width_d; double scale_f_d = 1.0; - const double filter_width_d = DEFAULT_BOX_RADIUS; + const double filter_width_d = support; int windows_size; unsigned int u; LineContribType *res; @@ -922,7 +928,10 @@ static inline LineContribType *_gdContributionsCalc(unsigned int line_size, unsi res->ContribRow[u].Right = iRight; for (iSrc = iLeft; iSrc <= iRight; iSrc++) { - dTotalWeight += (res->ContribRow[u].Weights[iSrc-iLeft] = scale_f_d * (*pFilter)(scale_f_d * (dCenter - (double)iSrc))); + dTotalWeight += + (res->ContribRow[u].Weights[iSrc - iLeft] = + scale_f_d * + (*pFilter)(scale_f_d * (dCenter - (double)iSrc), support)); } if (dTotalWeight < 0.0) { @@ -939,187 +948,193 @@ static inline LineContribType *_gdContributionsCalc(unsigned int line_size, unsi return res; } -/* Convert a double to an unsigned char, rounding to the nearest - * integer and clamping the result between 0 and max. The absolute - * value of clr must be less than the maximum value of an unsigned - * short. */ -static inline unsigned char -uchar_clamp(double clr, unsigned char max) { - unsigned short result; - - //assert(fabs(clr) <= SHRT_MAX); - - /* Casting a negative float to an unsigned short is undefined. - * However, casting a float to a signed truncates toward zero and - * casting a negative signed value to an unsigned of the same size - * results in a bit-identical value (assuming twos-complement - * arithmetic). This is what we want: all legal negative values - * for clr will be greater than 255. */ - - /* Convert and clamp. */ - result = (unsigned short)(short)(clr + 0.5); - if (result > max) { - result = (clr < 0) ? 0 : max; - }/* if */ - - return result; -}/* uchar_clamp*/ - -static inline void _gdScaleRow(gdImagePtr pSrc, unsigned int src_width, gdImagePtr dst, unsigned int dst_width, unsigned int row, LineContribType *contrib) -{ - int *p_src_row = pSrc->tpixels[row]; - int *p_dst_row = dst->tpixels[row]; - unsigned int x; - - for (x = 0; x < dst_width; x++) { - double r = 0, g = 0, b = 0, a = 0; - const int left = contrib->ContribRow[x].Left; - const int right = contrib->ContribRow[x].Right; +static inline void _gdScaleOneAxis(gdImagePtr pSrc, gdImagePtr dst, + unsigned int dst_len, unsigned int row, + LineContribType *contrib, gdAxis axis) { + unsigned int ndx; + + for (ndx = 0; ndx < dst_len; ndx++) { + double r = 0, g = 0, b = 0, a = 0; + const int left = contrib->ContribRow[ndx].Left; + const int right = contrib->ContribRow[ndx].Right; + int *dest = (axis == HORIZONTAL) ? &dst->tpixels[row][ndx] + : &dst->tpixels[ndx][row]; + int i; /* Accumulate each channel */ for (i = left; i <= right; i++) { const int left_channel = i - left; - r += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetRed(p_src_row[i])); - g += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetGreen(p_src_row[i])); - b += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetBlue(p_src_row[i])); - a += contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetAlpha(p_src_row[i])); - } - p_dst_row[x] = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), + const int srcpx = (axis == HORIZONTAL) ? pSrc->tpixels[row][i] + : pSrc->tpixels[i][row]; + + r += contrib->ContribRow[ndx].Weights[left_channel] * + (double)(gdTrueColorGetRed(srcpx)); + g += contrib->ContribRow[ndx].Weights[left_channel] * + (double)(gdTrueColorGetGreen(srcpx)); + b += contrib->ContribRow[ndx].Weights[left_channel] * + (double)(gdTrueColorGetBlue(srcpx)); + a += contrib->ContribRow[ndx].Weights[left_channel] * + (double)(gdTrueColorGetAlpha(srcpx)); + } /* for */ + + *dest = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), uchar_clamp(b, 0xFF), uchar_clamp(a, 0x7F)); /* alpha is 0..127 */ - } -} - -static inline int _gdScaleHoriz(gdImagePtr pSrc, unsigned int src_width, unsigned int src_height, gdImagePtr pDst, unsigned int dst_width, unsigned int dst_height) -{ - unsigned int u; + } /* for */ +} /* _gdScaleOneAxis*/ + +static inline int _gdScalePass(const gdImagePtr pSrc, + const unsigned int src_len, + const gdImagePtr pDst, + const unsigned int dst_len, + const unsigned int num_lines, const gdAxis axis, + const FilterInfo *filter) { + unsigned int line_ndx; LineContribType * contrib; - /* same width, just copy it */ - if (dst_width == src_width) { - unsigned int y; - for (y = 0; y < src_height - 1; ++y) { - memcpy(pDst->tpixels[y], pSrc->tpixels[y], src_width); - } - } + /* Same dim, just copy it. */ + assert(dst_len != src_len); // TODO: caller should handle this. - contrib = _gdContributionsCalc(dst_width, src_width, (double)dst_width / (double)src_width, pSrc->interpolation); + contrib = _gdContributionsCalc(dst_len, src_len, + (double)dst_len / (double)src_len, + filter->support, filter->function); if (contrib == NULL) { return 0; } - /* Scale each row */ - for (u = 0; u < dst_height; u++) { - _gdScaleRow(pSrc, src_width, pDst, dst_width, u, contrib); + + /* Scale each line */ + for (line_ndx = 0; line_ndx < num_lines; line_ndx++) { + _gdScaleOneAxis(pSrc, pDst, dst_len, line_ndx, contrib, axis); } _gdContributionsFree (contrib); return 1; -} - -static inline void _gdScaleCol (gdImagePtr pSrc, unsigned int src_width, gdImagePtr pRes, unsigned int dst_width, unsigned int dst_height, unsigned int uCol, LineContribType *contrib) -{ - unsigned int y; - for (y = 0; y < dst_height; y++) { - double r = 0, g = 0, b = 0, a = 0; - const int iLeft = contrib->ContribRow[y].Left; - const int iRight = contrib->ContribRow[y].Right; - int i; - - /* Accumulate each channel */ - for (i = iLeft; i <= iRight; i++) { - const int pCurSrc = pSrc->tpixels[i][uCol]; - const int i_iLeft = i - iLeft; - r += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetRed(pCurSrc)); - g += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetGreen(pCurSrc)); - b += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetBlue(pCurSrc)); - a += contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetAlpha(pCurSrc)); - } - pRes->tpixels[y][uCol] = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF), - uchar_clamp(b, 0xFF), - uchar_clamp(a, 0x7F)); /* alpha is 0..127 */ - } -} +} /* _gdScalePass*/ + +static const FilterInfo filters[GD_METHOD_COUNT + 1] = { + {filter_box, 0.0}, + {filter_bell, 1.5}, + {filter_bessel, 0.0}, + {NULL, 0.0}, /* NA bilenear/bilinear fixed */ + {NULL, 0.0}, /* NA bicubic */ + {NULL, 0.0}, /* NA bicubic fixed */ + {filter_blackman, 1.0}, + {filter_box, 0.5}, + {filter_bspline, 1.5}, + {filter_catmullrom, 2.0}, + {filter_gaussian, 1.25}, + {filter_generalized_cubic, 0.5}, + {filter_hermite, 1.0}, + {filter_hamming, 1.0}, + {filter_hanning, 1.0}, + {filter_mitchell, 2.0}, + {NULL, 0.0}, /* NA Nearest */ + {filter_power, 0.0}, + {filter_quadratic, 1.5}, + {filter_sinc, 1.0}, + {filter_triangle, 1.0}, + {NULL, 1.0}, /* NA weighted4 */ + {filter_linear, 1.0}, + {filter_lanczos3, 3.0}, + {filter_lanczos8, 8.0}, + {filter_blackman_bessel, 3.2383}, + {filter_blackman_sinc, 4.0}, + {filter_quadratic_bspline, 1.5}, + {filter_cubic_spline, 0.0}, + {filter_cosine, 0.0}, + {filter_welsh, 0.0}, +}; -static inline int _gdScaleVert (const gdImagePtr pSrc, const unsigned int src_width, const unsigned int src_height, const gdImagePtr pDst, const unsigned int dst_width, const unsigned int dst_height) -{ - unsigned int u; - LineContribType * contrib; +static const FilterInfo *_get_filterinfo_for_id(gdInterpolationMethod id) { - /* same height, copy it */ - if (src_height == dst_height) { - unsigned int y; - for (y = 0; y < src_height - 1; ++y) { - memcpy(pDst->tpixels[y], pSrc->tpixels[y], src_width); + if (id >= GD_METHOD_COUNT) { + id = GD_DEFAULT; } + return &filters[id]; } - contrib = _gdContributionsCalc(dst_height, src_height, (double)(dst_height) / (double)(src_height), pSrc->interpolation); - if (contrib == NULL) { - return 0; - } - /* scale each column */ - for (u = 0; u < dst_width; u++) { - _gdScaleCol(pSrc, src_width, pDst, dst_width, dst_height, u, contrib); - } - _gdContributionsFree(contrib); - return 1; -} - -static gdImagePtr -gdImageScaleTwoPass(const gdImagePtr src, const unsigned int src_width, const unsigned int src_height, const unsigned int new_width, const unsigned int new_height) -{ - gdImagePtr tmp_im; - gdImagePtr dst; +static gdImagePtr gdImageScaleTwoPass(const gdImagePtr src, + const unsigned int new_width, + const unsigned int new_height) { + const unsigned int src_width = src->sx; + const unsigned int src_height = src->sy; + gdImagePtr tmp_im = NULL; + gdImagePtr dst = NULL; int scale_pass_res; + const FilterInfo *filter = _get_filterinfo_for_id(src->interpolation_id); + + /* First, handle the trivial case. */ + if (src_width == new_width && src_height == new_height) { + return gdImageClone(src); + } /* Convert to truecolor if it isn't; this code requires it. */ if (!src->trueColor) { gdImagePaletteToTrueColor(src); } - tmp_im = gdImageCreateTrueColor(new_width, src_height); - if (tmp_im == NULL) { - return NULL; + /* Scale horizontally unless sizes are the same. */ + if (src_width == new_width) { + tmp_im = src; + } else { + tmp_im = gdImageCreateTrueColor(new_width, src_height); + if (tmp_im == NULL) { + return NULL; + } + gdImageSetInterpolationMethod(tmp_im, src->interpolation_id); + + scale_pass_res = _gdScalePass(src, src_width, tmp_im, new_width, + src_height, HORIZONTAL, filter); + if (scale_pass_res != 1) { + gdImageDestroy(tmp_im); + return NULL; + } } - gdImageSetInterpolationMethod(tmp_im, src->interpolation_id); - scale_pass_res = _gdScaleHoriz(src, src_width, src_height, tmp_im, new_width, src_height); - if (scale_pass_res != 1) { - gdImageDestroy(tmp_im); - return NULL; + + /* If vertical sizes match, we're done. */ + if (src_height == new_height) { + assert(tmp_im != src); + return tmp_im; } + /* Otherwise, we need to scale vertically. */ dst = gdImageCreateTrueColor(new_width, new_height); - if (dst == NULL) { - gdImageDestroy(tmp_im); - return NULL; - } - gdImageSetInterpolationMethod(dst, src->interpolation_id); - scale_pass_res = _gdScaleVert(tmp_im, new_width, src_height, dst, new_width, new_height); - if (scale_pass_res != 1) { - gdImageDestroy(dst); - gdImageDestroy(tmp_im); - return NULL; + if (dst != NULL) { + gdImageSetInterpolationMethod(dst, src->interpolation_id); + scale_pass_res = _gdScalePass(tmp_im, src_height, dst, new_height, + new_width, VERTICAL, filter); + if (scale_pass_res != 1) { + gdImageDestroy(dst); + if (src != tmp_im) { + gdImageDestroy(tmp_im); + } + return NULL; + } } + + if (src != tmp_im) { gdImageDestroy(tmp_im); + } return dst; -} +} /* gdImageScaleTwoPass*/ /* - BilinearFixed, BicubicFixed and nearest implementations are rewamped versions of the implementation in CBitmapEx + Bilinear, bicubic and nearest implementations are + rewamped versions of the implementation in CBitmapEx + http://www.codeproject.com/Articles/29121/CBitmapEx-Free-C-Bitmap-Manipulation-Class - Integer only implementation, good to have for common usages like pre scale very large - images before using another interpolation methods for the last step. + + The GD_BILINEAR_FIXED and GD_BICUBIC_FIXED public names are kept for + compatibility, but these implementations use floating point arithmetic. */ -static gdImagePtr -gdImageScaleNearestNeighbour(gdImagePtr im, const unsigned int width, const unsigned int height) -{ +static gdImagePtr gdImageScaleNearestNeighbour(gdImagePtr im, + const unsigned int width, + const unsigned int height) { const unsigned long new_width = MAX(1, width); const unsigned long new_height = MAX(1, height); - const float dx = (float)im->sx / (float)new_width; - const float dy = (float)im->sy / (float)new_height; - const gdFixed f_dx = gd_ftofx(dx); - const gdFixed f_dy = gd_ftofx(dy); + const double dx = (double)im->sx / (double)new_width; + const double dy = (double)im->sy / (double)new_height; gdImagePtr dst_img; unsigned long dst_offset_x; @@ -1137,25 +1152,23 @@ gdImageScaleNearestNeighbour(gdImagePtr im, const unsigned int width, const unsi dst_offset_x = 0; if (im->trueColor) { for (j=0; jtpixels[dst_offset_y][dst_offset_x++] = im->tpixels[m][n]; + const long m = + gdClampInt((int)((double)i * dy), 0, gdImageSY(im) - 1); + const long n = + gdClampInt((int)((double)j * dx), 0, gdImageSX(im) - 1); + + dst_img->tpixels[dst_offset_y][dst_offset_x++] = + im->tpixels[m][n]; } } else { for (j=0; jtpixels[dst_offset_y][dst_offset_x++] = colorIndex2RGBA(im->pixels[m][n]); + const long m = + gdClampInt((int)((double)i * dy), 0, gdImageSY(im) - 1); + const long n = + gdClampInt((int)((double)j * dx), 0, gdImageSX(im) - 1); + + dst_img->tpixels[dst_offset_y][dst_offset_x++] = + colorIndex2RGBA(im->pixels[m][n]); } } dst_offset_y++; @@ -1163,15 +1176,13 @@ gdImageScaleNearestNeighbour(gdImagePtr im, const unsigned int width, const unsi return dst_img; } -static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, const unsigned int new_width, const unsigned int new_height) -{ +static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, + const unsigned int new_width, + const unsigned int new_height) { long _width = MAX(1, new_width); long _height = MAX(1, new_height); - float dx = (float)gdImageSX(im) / (float)_width; - float dy = (float)gdImageSY(im) / (float)_height; - gdFixed f_dx = gd_ftofx(dx); - gdFixed f_dy = gd_ftofx(dy); - gdFixed f_1 = gd_itofx(1); + double dx = (double)gdImageSX(im) / (double)_width; + double dy = (double)gdImageSY(im) / (double)_height; int dst_offset_h; int dst_offset_v = 0; @@ -1188,38 +1199,33 @@ static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, const unsigned int /* uninitialized */ new_img->transparent = -1; } else { - new_img->transparent = gdTrueColorAlpha(im->red[transparent], im->green[transparent], im->blue[transparent], im->alpha[transparent]); + new_img->transparent = + gdTrueColorAlpha(im->red[transparent], im->green[transparent], + im->blue[transparent], im->alpha[transparent]); } for (i=0; i < _height; i++) { long j; - const gdFixed f_i = gd_itofx(i); - const gdFixed f_a = gd_mulfx(f_i, f_dy); - register long m = gd_fxtoi(f_a); + const double src_y = (double)i * dy; + register long m = gdClampInt((int)src_y, 0, gdImageSY(im) - 1); dst_offset_h = 0; for (j=0; j < _width; j++) { /* Update bitmap */ - gdFixed f_j = gd_itofx(j); - gdFixed f_b = gd_mulfx(f_j, f_dx); - - const long n = gd_fxtoi(f_b); - gdFixed f_f = f_a - gd_itofx(m); - gdFixed f_g = f_b - gd_itofx(n); - - const gdFixed f_w1 = gd_mulfx(f_1-f_f, f_1-f_g); - const gdFixed f_w2 = gd_mulfx(f_1-f_f, f_g); - const gdFixed f_w3 = gd_mulfx(f_f, f_1-f_g); - const gdFixed f_w4 = gd_mulfx(f_f, f_g); + const double src_x = (double)j * dx; + const long n = gdClampInt((int)src_x, 0, gdImageSX(im) - 1); + const double f_f = src_y - (double)m; + const double f_g = src_x - (double)n; + + const double f_w1 = (1.0 - f_f) * (1.0 - f_g); + const double f_w2 = (1.0 - f_f) * f_g; + const double f_w3 = f_f * (1.0 - f_g); + const double f_w4 = f_f * f_g; unsigned int pixel1; unsigned int pixel2; unsigned int pixel3; unsigned int pixel4; - register gdFixed f_r1, f_r2, f_r3, f_r4, - f_g1, f_g2, f_g3, f_g4, - f_b1, f_b2, f_b3, f_b4, - f_a1, f_a2, f_a3, f_a4; /* 0 for bgColor; (n,m) is supposed to be valid anyway */ pixel1 = getPixelOverflowPalette(im, n, m, 0); @@ -1227,30 +1233,34 @@ static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, const unsigned int pixel3 = getPixelOverflowPalette(im, n, m + 1, pixel1); pixel4 = getPixelOverflowPalette(im, n + 1, m + 1, pixel1); - f_r1 = gd_itofx(gdTrueColorGetRed(pixel1)); - f_r2 = gd_itofx(gdTrueColorGetRed(pixel2)); - f_r3 = gd_itofx(gdTrueColorGetRed(pixel3)); - f_r4 = gd_itofx(gdTrueColorGetRed(pixel4)); - f_g1 = gd_itofx(gdTrueColorGetGreen(pixel1)); - f_g2 = gd_itofx(gdTrueColorGetGreen(pixel2)); - f_g3 = gd_itofx(gdTrueColorGetGreen(pixel3)); - f_g4 = gd_itofx(gdTrueColorGetGreen(pixel4)); - f_b1 = gd_itofx(gdTrueColorGetBlue(pixel1)); - f_b2 = gd_itofx(gdTrueColorGetBlue(pixel2)); - f_b3 = gd_itofx(gdTrueColorGetBlue(pixel3)); - f_b4 = gd_itofx(gdTrueColorGetBlue(pixel4)); - f_a1 = gd_itofx(gdTrueColorGetAlpha(pixel1)); - f_a2 = gd_itofx(gdTrueColorGetAlpha(pixel2)); - f_a3 = gd_itofx(gdTrueColorGetAlpha(pixel3)); - f_a4 = gd_itofx(gdTrueColorGetAlpha(pixel4)); - { - const unsigned char red = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_r1) + gd_mulfx(f_w2, f_r2) + gd_mulfx(f_w3, f_r3) + gd_mulfx(f_w4, f_r4)); - const unsigned char green = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_g1) + gd_mulfx(f_w2, f_g2) + gd_mulfx(f_w3, f_g3) + gd_mulfx(f_w4, f_g4)); - const unsigned char blue = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_b1) + gd_mulfx(f_w2, f_b2) + gd_mulfx(f_w3, f_b3) + gd_mulfx(f_w4, f_b4)); - const unsigned char alpha = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_a1) + gd_mulfx(f_w2, f_a2) + gd_mulfx(f_w3, f_a3) + gd_mulfx(f_w4, f_a4)); - - new_img->tpixels[dst_offset_v][dst_offset_h] = gdTrueColorAlpha(red, green, blue, alpha); + const unsigned char red = + uchar_clamp(f_w1 * gdTrueColorGetRed(pixel1) + + f_w2 * gdTrueColorGetRed(pixel2) + + f_w3 * gdTrueColorGetRed(pixel3) + + f_w4 * gdTrueColorGetRed(pixel4), + 0xFF); + const unsigned char green = + uchar_clamp(f_w1 * gdTrueColorGetGreen(pixel1) + + f_w2 * gdTrueColorGetGreen(pixel2) + + f_w3 * gdTrueColorGetGreen(pixel3) + + f_w4 * gdTrueColorGetGreen(pixel4), + 0xFF); + const unsigned char blue = + uchar_clamp(f_w1 * gdTrueColorGetBlue(pixel1) + + f_w2 * gdTrueColorGetBlue(pixel2) + + f_w3 * gdTrueColorGetBlue(pixel3) + + f_w4 * gdTrueColorGetBlue(pixel4), + 0xFF); + const unsigned char alpha = + uchar_clamp(f_w1 * gdTrueColorGetAlpha(pixel1) + + f_w2 * gdTrueColorGetAlpha(pixel2) + + f_w3 * gdTrueColorGetAlpha(pixel3) + + f_w4 * gdTrueColorGetAlpha(pixel4), + 0x7F); + + new_img->tpixels[dst_offset_v][dst_offset_h] = + gdTrueColorAlpha(red, green, blue, alpha); } dst_offset_h++; @@ -1261,15 +1271,13 @@ static gdImagePtr gdImageScaleBilinearPalette(gdImagePtr im, const unsigned int return new_img; } -static gdImagePtr gdImageScaleBilinearTC(gdImagePtr im, const unsigned int new_width, const unsigned int new_height) -{ +static gdImagePtr gdImageScaleBilinearTC(gdImagePtr im, + const unsigned int new_width, + const unsigned int new_height) { long dst_w = MAX(1, new_width); long dst_h = MAX(1, new_height); - float dx = (float)gdImageSX(im) / (float)dst_w; - float dy = (float)gdImageSY(im) / (float)dst_h; - gdFixed f_dx = gd_ftofx(dx); - gdFixed f_dy = gd_ftofx(dy); - gdFixed f_1 = gd_itofx(1); + double dx = (double)gdImageSX(im) / (double)dst_w; + double dy = (double)gdImageSY(im) / (double)dst_h; int dst_offset_h; int dst_offset_v = 0; @@ -1286,56 +1294,55 @@ static gdImagePtr gdImageScaleBilinearTC(gdImagePtr im, const unsigned int new_w dst_offset_h = 0; for (j=0; j < dst_w; j++) { /* Update bitmap */ - gdFixed f_i = gd_itofx(i); - gdFixed f_j = gd_itofx(j); - gdFixed f_a = gd_mulfx(f_i, f_dy); - gdFixed f_b = gd_mulfx(f_j, f_dx); - const long m = gd_fxtoi(f_a); - const long n = gd_fxtoi(f_b); - gdFixed f_f = f_a - gd_itofx(m); - gdFixed f_g = f_b - gd_itofx(n); - - const gdFixed f_w1 = gd_mulfx(f_1-f_f, f_1-f_g); - const gdFixed f_w2 = gd_mulfx(f_1-f_f, f_g); - const gdFixed f_w3 = gd_mulfx(f_f, f_1-f_g); - const gdFixed f_w4 = gd_mulfx(f_f, f_g); + const double src_y = (double)i * dy; + const double src_x = (double)j * dx; + const long m = gdClampInt((int)src_y, 0, gdImageSY(im) - 1); + const long n = gdClampInt((int)src_x, 0, gdImageSX(im) - 1); + const double f_f = src_y - (double)m; + const double f_g = src_x - (double)n; + + const double f_w1 = (1.0 - f_f) * (1.0 - f_g); + const double f_w2 = (1.0 - f_f) * f_g; + const double f_w3 = f_f * (1.0 - f_g); + const double f_w4 = f_f * f_g; unsigned int pixel1; unsigned int pixel2; unsigned int pixel3; unsigned int pixel4; - register gdFixed f_r1, f_r2, f_r3, f_r4, - f_g1, f_g2, f_g3, f_g4, - f_b1, f_b2, f_b3, f_b4, - f_a1, f_a2, f_a3, f_a4; /* 0 for bgColor; (n,m) is supposed to be valid anyway */ pixel1 = getPixelOverflowTC(im, n, m, 0); pixel2 = getPixelOverflowTC(im, n + 1, m, pixel1); pixel3 = getPixelOverflowTC(im, n, m + 1, pixel1); pixel4 = getPixelOverflowTC(im, n + 1, m + 1, pixel1); - f_r1 = gd_itofx(gdTrueColorGetRed(pixel1)); - f_r2 = gd_itofx(gdTrueColorGetRed(pixel2)); - f_r3 = gd_itofx(gdTrueColorGetRed(pixel3)); - f_r4 = gd_itofx(gdTrueColorGetRed(pixel4)); - f_g1 = gd_itofx(gdTrueColorGetGreen(pixel1)); - f_g2 = gd_itofx(gdTrueColorGetGreen(pixel2)); - f_g3 = gd_itofx(gdTrueColorGetGreen(pixel3)); - f_g4 = gd_itofx(gdTrueColorGetGreen(pixel4)); - f_b1 = gd_itofx(gdTrueColorGetBlue(pixel1)); - f_b2 = gd_itofx(gdTrueColorGetBlue(pixel2)); - f_b3 = gd_itofx(gdTrueColorGetBlue(pixel3)); - f_b4 = gd_itofx(gdTrueColorGetBlue(pixel4)); - f_a1 = gd_itofx(gdTrueColorGetAlpha(pixel1)); - f_a2 = gd_itofx(gdTrueColorGetAlpha(pixel2)); - f_a3 = gd_itofx(gdTrueColorGetAlpha(pixel3)); - f_a4 = gd_itofx(gdTrueColorGetAlpha(pixel4)); { - const unsigned char red = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_r1) + gd_mulfx(f_w2, f_r2) + gd_mulfx(f_w3, f_r3) + gd_mulfx(f_w4, f_r4)); - const unsigned char green = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_g1) + gd_mulfx(f_w2, f_g2) + gd_mulfx(f_w3, f_g3) + gd_mulfx(f_w4, f_g4)); - const unsigned char blue = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_b1) + gd_mulfx(f_w2, f_b2) + gd_mulfx(f_w3, f_b3) + gd_mulfx(f_w4, f_b4)); - const unsigned char alpha = (unsigned char) gd_fxtoi(gd_mulfx(f_w1, f_a1) + gd_mulfx(f_w2, f_a2) + gd_mulfx(f_w3, f_a3) + gd_mulfx(f_w4, f_a4)); - - new_img->tpixels[dst_offset_v][dst_offset_h] = gdTrueColorAlpha(red, green, blue, alpha); + const unsigned char red = + uchar_clamp(f_w1 * gdTrueColorGetRed(pixel1) + + f_w2 * gdTrueColorGetRed(pixel2) + + f_w3 * gdTrueColorGetRed(pixel3) + + f_w4 * gdTrueColorGetRed(pixel4), + 0xFF); + const unsigned char green = + uchar_clamp(f_w1 * gdTrueColorGetGreen(pixel1) + + f_w2 * gdTrueColorGetGreen(pixel2) + + f_w3 * gdTrueColorGetGreen(pixel3) + + f_w4 * gdTrueColorGetGreen(pixel4), + 0xFF); + const unsigned char blue = + uchar_clamp(f_w1 * gdTrueColorGetBlue(pixel1) + + f_w2 * gdTrueColorGetBlue(pixel2) + + f_w3 * gdTrueColorGetBlue(pixel3) + + f_w4 * gdTrueColorGetBlue(pixel4), + 0xFF); + const unsigned char alpha = + uchar_clamp(f_w1 * gdTrueColorGetAlpha(pixel1) + + f_w2 * gdTrueColorGetAlpha(pixel2) + + f_w3 * gdTrueColorGetAlpha(pixel3) + + f_w4 * gdTrueColorGetAlpha(pixel4), + 0x7F); + + new_img->tpixels[dst_offset_v][dst_offset_h] = + gdTrueColorAlpha(red, green, blue, alpha); } dst_offset_h++; @@ -1346,9 +1353,9 @@ static gdImagePtr gdImageScaleBilinearTC(gdImagePtr im, const unsigned int new_w return new_img; } -static gdImagePtr -gdImageScaleBilinear(gdImagePtr im, const unsigned int new_width, const unsigned int new_height) -{ +static gdImagePtr gdImageScaleBilinear(gdImagePtr im, + const unsigned int new_width, + const unsigned int new_height) { if (im->trueColor) { return gdImageScaleBilinearTC(im, new_width, new_height); } else { @@ -1356,20 +1363,16 @@ gdImageScaleBilinear(gdImagePtr im, const unsigned int new_width, const unsigned } } -static gdImagePtr -gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width, const unsigned int height) -{ +static gdImagePtr gdImageScaleBicubicFixed(gdImagePtr src, + const unsigned int width, + const unsigned int height) { const long new_width = MAX(1, width); const long new_height = MAX(1, height); const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const gdFixed f_dx = gd_ftofx((float)src_w / (float)new_width); - const gdFixed f_dy = gd_ftofx((float)src_h / (float)new_height); - const gdFixed f_1 = gd_itofx(1); - const gdFixed f_2 = gd_itofx(2); - const gdFixed f_4 = gd_itofx(4); - const gdFixed f_6 = gd_itofx(6); - const gdFixed f_gamma = gd_ftofx(1.04f); + const double dx = (double)src_w / (double)new_width; + const double dy = (double)src_h / (double)new_height; + const double gamma = 1.04; gdImagePtr dst; unsigned int dst_offset_x; @@ -1392,184 +1395,49 @@ gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width, const unsigne for (i=0; i < new_height; i++) { long j; + const double src_y = (double)i * dy; + const long m = gdClampInt((int)src_y, 0, src_h - 1); + const double f_f = src_y - (double)m; dst_offset_x = 0; for (j=0; j < new_width; j++) { - const gdFixed f_a = gd_mulfx(gd_itofx(i), f_dy); - const gdFixed f_b = gd_mulfx(gd_itofx(j), f_dx); - const long m = gd_fxtoi(f_a); - const long n = gd_fxtoi(f_b); - const gdFixed f_f = f_a - gd_itofx(m); - const gdFixed f_g = f_b - gd_itofx(n); - unsigned int src_offset_x[16], src_offset_y[16]; + const double src_x = (double)j * dx; + const long n = gdClampInt((int)src_x, 0, src_w - 1); + const double f_g = src_x - (double)n; long k; - register gdFixed f_red = 0, f_green = 0, f_blue = 0, f_alpha = 0; + double red_acc = 0.0, green_acc = 0.0, blue_acc = 0.0, + alpha_acc = 0.0; unsigned char red, green, blue, alpha = 0; int *dst_row = dst->tpixels[dst_offset_y]; - if ((m < 1) || (n < 1)) { - src_offset_x[0] = n; - src_offset_y[0] = m; - } else { - src_offset_x[0] = n - 1; - src_offset_y[0] = m; - } - - src_offset_x[1] = n; - src_offset_y[1] = m; - - if ((m < 1) || (n >= src_w - 1)) { - src_offset_x[2] = n; - src_offset_y[2] = m; - } else { - src_offset_x[2] = n + 1; - src_offset_y[2] = m; - } - - if ((m < 1) || (n >= src_w - 2)) { - src_offset_x[3] = n; - src_offset_y[3] = m; - } else { - src_offset_x[3] = n + 1 + 1; - src_offset_y[3] = m; - } - - if (n < 1) { - src_offset_x[4] = n; - src_offset_y[4] = m; - } else { - src_offset_x[4] = n - 1; - src_offset_y[4] = m; - } - - src_offset_x[5] = n; - src_offset_y[5] = m; - if (n >= src_w-1) { - src_offset_x[6] = n; - src_offset_y[6] = m; - } else { - src_offset_x[6] = n + 1; - src_offset_y[6] = m; - } - - if (n >= src_w - 2) { - src_offset_x[7] = n; - src_offset_y[7] = m; - } else { - src_offset_x[7] = n + 1 + 1; - src_offset_y[7] = m; - } - - if ((m >= src_h - 1) || (n < 1)) { - src_offset_x[8] = n; - src_offset_y[8] = m; - } else { - src_offset_x[8] = n - 1; - src_offset_y[8] = m; - } - - src_offset_x[9] = n; - src_offset_y[9] = m; - - if ((m >= src_h-1) || (n >= src_w-1)) { - src_offset_x[10] = n; - src_offset_y[10] = m; - } else { - src_offset_x[10] = n + 1; - src_offset_y[10] = m; - } - - if ((m >= src_h - 1) || (n >= src_w - 2)) { - src_offset_x[11] = n; - src_offset_y[11] = m; - } else { - src_offset_x[11] = n + 1 + 1; - src_offset_y[11] = m; - } - - if ((m >= src_h - 2) || (n < 1)) { - src_offset_x[12] = n; - src_offset_y[12] = m; - } else { - src_offset_x[12] = n - 1; - src_offset_y[12] = m; - } - - src_offset_x[13] = n; - src_offset_y[13] = m; - - if ((m >= src_h - 2) || (n >= src_w - 1)) { - src_offset_x[14] = n; - src_offset_y[14] = m; - } else { - src_offset_x[14] = n + 1; - src_offset_y[14] = m; - } - - if ((m >= src_h - 2) || (n >= src_w - 2)) { - src_offset_x[15] = n; - src_offset_y[15] = m; - } else { - src_offset_x[15] = n + 1 + 1; - src_offset_y[15] = m; - } - for (k = -1; k < 3; k++) { - const gdFixed f = gd_itofx(k)-f_f; - const gdFixed f_fm1 = f - f_1; - const gdFixed f_fp1 = f + f_1; - const gdFixed f_fp2 = f + f_2; - register gdFixed f_a = 0, f_b = 0, f_d = 0, f_c = 0; - register gdFixed f_RY; int l; - - if (f_fp2 > 0) f_a = gd_mulfx(f_fp2, gd_mulfx(f_fp2,f_fp2)); - if (f_fp1 > 0) f_b = gd_mulfx(f_fp1, gd_mulfx(f_fp1,f_fp1)); - if (f > 0) f_c = gd_mulfx(f, gd_mulfx(f,f)); - if (f_fm1 > 0) f_d = gd_mulfx(f_fm1, gd_mulfx(f_fm1,f_fm1)); - - f_RY = gd_divfx((f_a - gd_mulfx(f_4,f_b) + gd_mulfx(f_6,f_c) - gd_mulfx(f_4,f_d)),f_6); + const int src_y_sample = + gdClampInt((int)m + (int)k, 0, src_h - 1); + const double weight_y = + filter_cubic_spline((double)k - f_f, 0.0); for (l = -1; l < 3; l++) { - const gdFixed f = gd_itofx(l) - f_g; - const gdFixed f_fm1 = f - f_1; - const gdFixed f_fp1 = f + f_1; - const gdFixed f_fp2 = f + f_2; - register gdFixed f_a = 0, f_b = 0, f_c = 0, f_d = 0; - register gdFixed f_RX, f_R, f_rs, f_gs, f_bs, f_ba; - register int c; - const int _k = ((k+1)*4) + (l+1); - - if (f_fp2 > 0) f_a = gd_mulfx(f_fp2,gd_mulfx(f_fp2,f_fp2)); - - if (f_fp1 > 0) f_b = gd_mulfx(f_fp1,gd_mulfx(f_fp1,f_fp1)); - - if (f > 0) f_c = gd_mulfx(f,gd_mulfx(f,f)); - - if (f_fm1 > 0) f_d = gd_mulfx(f_fm1,gd_mulfx(f_fm1,f_fm1)); - - f_RX = gd_divfx((f_a-gd_mulfx(f_4,f_b)+gd_mulfx(f_6,f_c)-gd_mulfx(f_4,f_d)),f_6); - f_R = gd_mulfx(f_RY,f_RX); - - c = src->tpixels[*(src_offset_y + _k)][*(src_offset_x + _k)]; - f_rs = gd_itofx(gdTrueColorGetRed(c)); - f_gs = gd_itofx(gdTrueColorGetGreen(c)); - f_bs = gd_itofx(gdTrueColorGetBlue(c)); - f_ba = gd_itofx(gdTrueColorGetAlpha(c)); - - f_red += gd_mulfx(f_rs,f_R); - f_green += gd_mulfx(f_gs,f_R); - f_blue += gd_mulfx(f_bs,f_R); - f_alpha += gd_mulfx(f_ba,f_R); + const int src_x_sample = + gdClampInt((int)n + (int)l, 0, src_w - 1); + const double weight = + weight_y * filter_cubic_spline((double)l - f_g, 0.0); + const int c = src->tpixels[src_y_sample][src_x_sample]; + + red_acc += weight * (double)gdTrueColorGetRed(c); + green_acc += weight * (double)gdTrueColorGetGreen(c); + blue_acc += weight * (double)gdTrueColorGetBlue(c); + alpha_acc += weight * (double)gdTrueColorGetAlpha(c); } } - red = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_red, f_gamma)), 0, 255); - green = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_green, f_gamma)), 0, 255); - blue = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_blue, f_gamma)), 0, 255); - alpha = (unsigned char) CLAMP(gd_fxtoi(gd_mulfx(f_alpha, f_gamma)), 0, 127); + red = uchar_clamp(red_acc * gamma, 0xFF); + green = uchar_clamp(green_acc * gamma, 0xFF); + blue = uchar_clamp(blue_acc * gamma, 0xFF); + alpha = uchar_clamp(alpha_acc * gamma, 0x7F); - *(dst_row + dst_offset_x) = gdTrueColorAlpha(red, green, blue, alpha); + *(dst_row + dst_offset_x) = + gdTrueColorAlpha(red, green, blue, alpha); dst_offset_x++; } @@ -1578,19 +1446,41 @@ gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width, const unsigne return dst; } -gdImagePtr gdImageScale(const gdImagePtr src, const unsigned int new_width, const unsigned int new_height) -{ +/** + * Function: gdImageScale + * + * Scale an image + * + * Creates a new image, scaled to the requested size using the current + * . + * + * Note that GD_WEIGHTED4 is not yet supported by this function. + * + * Parameters: + * src - The source image. + * new_width - The new width. + * new_height - The new height. + * + * Returns: + * The scaled image on success, NULL on failure. + * + * See also: + * - + * - + */ +BGD_DECLARE(gdImagePtr) +gdImageScale(const gdImagePtr src, const unsigned int new_width, + const unsigned int new_height) { gdImagePtr im_scaled = NULL; - if (src == NULL || src->interpolation_id < 0 || src->interpolation_id > GD_METHOD_COUNT) { + if (src == NULL || (uintmax_t)src->interpolation_id >= GD_METHOD_COUNT) { return NULL; } if (new_width == 0 || new_height == 0) { return NULL; } - - if (new_width == gdImageSX(src) && new_height == gdImageSY(src)) { + if ((int)new_width == gdImageSX(src) && (int)new_height == gdImageSY(src)) { return gdImageClone(src); } switch (src->interpolation_id) { @@ -1614,14 +1504,15 @@ gdImagePtr gdImageScale(const gdImagePtr src, const unsigned int new_width, cons if (src->interpolation == NULL) { return NULL; } - im_scaled = gdImageScaleTwoPass(src, src->sx, src->sy, new_width, new_height); + im_scaled = gdImageScaleTwoPass(src, new_width, new_height); break; } + return im_scaled; } -static int gdRotatedImageSize(gdImagePtr src, const float angle, gdRectPtr bbox) -{ +static int gdRotatedImageSize(gdImagePtr src, const float angle, + gdRectPtr bbox) { gdRect src_area; double m[6]; @@ -1637,24 +1528,23 @@ static int gdRotatedImageSize(gdImagePtr src, const float angle, gdRectPtr bbox) return GD_TRUE; } -static gdImagePtr -gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, const int bgColor) -{ - float _angle = ((float) (-degrees / 180.0f) * (float)M_PI); +static gdImagePtr gdImageRotateNearestNeighbour(gdImagePtr src, + const float degrees, + const int bgColor) { + double _angle = ((double)(-degrees) / 180.0) * M_PI; const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const gdFixed f_0_5 = gd_ftofx(0.5f); - const gdFixed f_H = gd_itofx(src_h/2); - const gdFixed f_W = gd_itofx(src_w/2); - const gdFixed f_cos = gd_ftofx(cos(-_angle)); - const gdFixed f_sin = gd_ftofx(sin(-_angle)); + const double H = (double)(src_h / 2); + const double W = (double)(src_w / 2); + const double cos_angle = cos(-_angle); + const double sin_angle = sin(-_angle); unsigned int dst_offset_x; unsigned int dst_offset_y = 0; unsigned int i; gdImagePtr dst; gdRect bbox; - int new_height, new_width; + unsigned int new_height, new_width; gdRotatedImageSize(src, degrees, &bbox); new_width = bbox.width; @@ -1669,16 +1559,17 @@ gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, const int bgC unsigned int j; dst_offset_x = 0; for (j = 0; j < new_width; j++) { - gdFixed f_i = gd_itofx((int)i - (int)new_height/2); - gdFixed f_j = gd_itofx((int)j - (int)new_width/2); - gdFixed f_m = gd_mulfx(f_j,f_sin) + gd_mulfx(f_i,f_cos) + f_0_5 + f_H; - gdFixed f_n = gd_mulfx(f_j,f_cos) - gd_mulfx(f_i,f_sin) + f_0_5 + f_W; - long m = gd_fxtoi(f_m); - long n = gd_fxtoi(f_n); + const double dst_y = (double)((int)i - (int)new_height / 2); + const double dst_x = (double)((int)j - (int)new_width / 2); + const double src_y = dst_x * sin_angle + dst_y * cos_angle + H; + const double src_x = dst_x * cos_angle - dst_y * sin_angle + W; + long m = (long)floor(src_y + 0.5); + long n = (long)floor(src_x + 0.5); if ((m > 0) && (m < src_h-1) && (n > 0) && (n < src_w-1)) { if (dst_offset_y < new_height) { - dst->tpixels[dst_offset_y][dst_offset_x++] = src->tpixels[m][n]; + dst->tpixels[dst_offset_y][dst_offset_x++] = + src->tpixels[m][n]; } } else { if (dst_offset_y < new_height) { @@ -1691,33 +1582,27 @@ gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, const int bgC return dst; } -static gdImagePtr -gdImageRotateGeneric(gdImagePtr src, const float degrees, const int bgColor) -{ - float _angle = ((float) (-degrees / 180.0f) * (float)M_PI); +static gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees, + const int bgColor) { + double _angle = ((double)(-degrees) / 180.0) * M_PI; const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const gdFixed f_0_5 = gd_ftofx(0.5f); - const gdFixed f_H = gd_itofx(src_h/2); - const gdFixed f_W = gd_itofx(src_w/2); - const gdFixed f_cos = gd_ftofx(cos(-_angle)); - const gdFixed f_sin = gd_ftofx(sin(-_angle)); + const double H = (double)(src_h / 2); + const double W = (double)(src_w / 2); + const double cos_angle = cos(-_angle); + const double sin_angle = sin(-_angle); unsigned int dst_offset_x; unsigned int dst_offset_y = 0; unsigned int i; gdImagePtr dst; - int new_width, new_height; + unsigned int new_width, new_height; gdRect bbox; if (bgColor < 0) { return NULL; } - if (src->interpolation == NULL) { - gdImageSetInterpolationMethod(src, GD_DEFAULT); - } - gdRotatedImageSize(src, degrees, &bbox); new_width = bbox.width; new_height = bbox.height; @@ -1732,17 +1617,18 @@ gdImageRotateGeneric(gdImagePtr src, const float degrees, const int bgColor) unsigned int j; dst_offset_x = 0; for (j = 0; j < new_width; j++) { - gdFixed f_i = gd_itofx((int)i - (int)new_height/ 2); - gdFixed f_j = gd_itofx((int)j - (int)new_width / 2); - gdFixed f_m = gd_mulfx(f_j,f_sin) + gd_mulfx(f_i,f_cos) + f_0_5 + f_H; - gdFixed f_n = gd_mulfx(f_j,f_cos) - gd_mulfx(f_i,f_sin) + f_0_5 + f_W; - long m = gd_fxtoi(f_m); - long n = gd_fxtoi(f_n); + const double dst_y = (double)((int)i - (int)new_height / 2); + const double dst_x = (double)((int)j - (int)new_width / 2); + const double src_y = dst_x * sin_angle + dst_y * cos_angle + H; + const double src_x = dst_x * cos_angle - dst_y * sin_angle + W; + long m = (long)floor(src_y); + long n = (long)floor(src_x); if (m < -1 || n < -1 || m >= src_h || n >= src_w ) { dst->tpixels[dst_offset_y][dst_offset_x++] = bgColor; } else { - dst->tpixels[dst_offset_y][dst_offset_x++] = getPixelInterpolated(src, gd_fxtod(f_n), gd_fxtod(f_m), bgColor); + dst->tpixels[dst_offset_y][dst_offset_x++] = + getPixelInterpolated(src, src_x, src_y, bgColor); } } dst_offset_y++; @@ -1750,79 +1636,105 @@ gdImageRotateGeneric(gdImagePtr src, const float degrees, const int bgColor) return dst; } -gdImagePtr gdImageRotateInterpolated(const gdImagePtr src, const float angle, int bgcolor) -{ - /* round to two decimals and keep the 100x multiplication to use it in the common square angles - case later. Keep the two decimal precisions so smaller rotation steps can be done, useful for - slow animations. */ +/** + * Function: gdImageRotateInterpolated + * + * Rotate an image + * + * Creates a new image, counter-clockwise rotated by the requested angle + * using the current . Non-square angles will add a + * border with bgcolor. + * + * Parameters: + * src - The source image. + * angle - The angle in degrees. + * bgcolor - The color to fill the added background with. + * + * Returns: + * The rotated image on success, NULL on failure. + * + * See also: + * - + */ +BGD_DECLARE(gdImagePtr) +gdImageRotateInterpolated(const gdImagePtr src, const float angle, + int bgcolor) { + /* round to two decimals and keep the 100x multiplication to use it in the + common square angles case later. Keep the two decimal precisions so + smaller rotation steps can be done, useful for slow animations, f.e. */ const int angle_rounded = fmod((int) floorf(angle * 100), 360 * 100); - - if (bgcolor < 0) { + gdImagePtr src_tc = src; + int src_cloned = 0; + if (src == NULL || bgcolor < 0) { return NULL; } - /* impact perf a bit, but not that much. Implementation for palette - images can be done at a later point. - */ - if (src->trueColor == 0) { + if (!gdImageTrueColor(src)) { if (bgcolor < gdMaxColors) { - bgcolor = gdTrueColorAlpha(src->red[bgcolor], src->green[bgcolor], src->blue[bgcolor], src->alpha[bgcolor]); + bgcolor = gdTrueColorAlpha(src->red[bgcolor], src->green[bgcolor], + src->blue[bgcolor], src->alpha[bgcolor]); } - gdImagePaletteToTrueColor(src); + src_tc = gdImageClone(src); + gdImagePaletteToTrueColor(src_tc); + src_cloned = 1; } - /* no interpolation needed here */ + /* 0 && 90 degrees multiple rotation, 0 rotation simply clones the return + image and convert it to truecolor, as we must return truecolor image. */ switch (angle_rounded) { - case 0: { - gdImagePtr dst = gdImageCreateTrueColor(src->sx, src->sy); - if (dst == NULL) { - return NULL; - } - dst->transparent = src->transparent; - dst->saveAlphaFlag = 1; - dst->alphaBlendingFlag = gdEffectReplace; + case 0: + return src_cloned ? src_tc : gdImageClone(src); - gdImageCopy(dst, src, 0,0,0,0,src->sx,src->sy); - return dst; - } case -27000: case 9000: - return gdImageRotate90(src, 0); + if (src_cloned) + gdImageDestroy(src_tc); + return gdImageRotate90(src, 0); + case -18000: case 18000: - return gdImageRotate180(src, 0); + if (src_cloned) + gdImageDestroy(src_tc); + return gdImageRotate180(src, 0); + case -9000: case 27000: - return gdImageRotate270(src, 0); + if (src_cloned) + gdImageDestroy(src_tc); + return gdImageRotate270(src, 0); } - if (src == NULL || src->interpolation_id < 1 || src->interpolation_id > GD_METHOD_COUNT) { + if (src->interpolation_id < 1 || src->interpolation_id > GD_METHOD_COUNT) { + if (src_cloned) + gdImageDestroy(src_tc); return NULL; } switch (src->interpolation_id) { - case GD_NEAREST_NEIGHBOUR: - return gdImageRotateNearestNeighbour(src, angle, bgcolor); - break; + case GD_NEAREST_NEIGHBOUR: { + gdImagePtr res = gdImageRotateNearestNeighbour(src_tc, angle, bgcolor); + if (src_cloned) + gdImageDestroy(src_tc); + return res; + } case GD_BILINEAR_FIXED: case GD_BICUBIC_FIXED: - default: - return gdImageRotateGeneric(src, angle, bgcolor); + default: { + gdImagePtr res = gdImageRotateGeneric(src_tc, angle, bgcolor); + if (src_cloned) + gdImageDestroy(src_tc); + return res; + } } return NULL; } /** - * Title: Affine transformation - **/ - -/** - * Group: Transform + * Group: Affine Transformation **/ - static void gdImageClipRectangle(gdImagePtr im, gdRectPtr r) -{ +static void gdImageClipRectangle(gdImagePtr im, gdRectPtr r) { int c1x, c1y, c2x, c2y; int x1,y1; @@ -1835,11 +1747,6 @@ gdImagePtr gdImageRotateInterpolated(const gdImagePtr src, const float angle, in r->height = CLAMP(y1, c1y, c2y) - r->y + 1; } -void gdDumpRect(const char *msg, gdRectPtr r) -{ - printf("%s (%i, %i) (%i, %i)\n", msg, r->x, r->y, r->width, r->height); -} - /** * Function: gdTransformAffineGetImage * Applies an affine transformation to a region and return an image @@ -1856,11 +1763,9 @@ void gdDumpRect(const char *msg, gdRectPtr r) * Returns: * GD_TRUE if the affine is rectilinear or GD_FALSE */ -int gdTransformAffineGetImage(gdImagePtr *dst, - const gdImagePtr src, - gdRectPtr src_area, - const double affine[6]) -{ +BGD_DECLARE(int) +gdTransformAffineGetImage(gdImagePtr *dst, const gdImagePtr src, + gdRectPtr src_area, const double affine[6]) { int res; double m[6]; gdRect bbox; @@ -1884,6 +1789,11 @@ int gdTransformAffineGetImage(gdImagePtr *dst, return GD_FALSE; } (*dst)->saveAlphaFlag = 1; + gdImageAlphaBlending(*dst, 0); + /* The API has no background-color parameter, so uncovered output starts + * transparent. */ + gdImageFilledRectangle(*dst, 0, 0, bbox.width - 1, bbox.height - 1, + gdTrueColorAlpha(0, 0, 0, gdAlphaTransparent)); if (!src->trueColor) { gdImagePaletteToTrueColor(src); @@ -1893,23 +1803,96 @@ int gdTransformAffineGetImage(gdImagePtr *dst, gdAffineTranslate(m, -bbox.x, -bbox.y); gdAffineConcat(m, affine, m); - gdImageAlphaBlending(*dst, 0); - - res = gdTransformAffineCopy(*dst, - 0,0, - src, - src_area, - m); + res = gdTransformAffineCopy(*dst, 0, 0, src, src_area, m); if (res != GD_TRUE) { gdImageDestroy(*dst); - dst = NULL; + *dst = NULL; return GD_FALSE; } else { return GD_TRUE; } } +/** Function: getPixelRgbInterpolated + * get the index of the image's colors + * + * Parameters: + * im - Image to draw the transformed image + * tcolor - TrueColor + * + * Return: + * index of colors + */ +static int getPixelRgbInterpolated(gdImagePtr im, const int tcolor) { + unsigned char r, g, b, a; + int ct; + int i; + + b = (unsigned char)(tcolor); + g = (unsigned char)(tcolor >> 8); + r = (unsigned char)(tcolor >> 16); + a = (unsigned char)(tcolor >> 24); + + for (i = 0; i < im->colorsTotal; i++) { + if (im->red[i] == r && im->green[i] == g && im->blue[i] == b && + im->alpha[i] == a) { + return i; + } + } + + ct = im->colorsTotal; + if (ct == gdMaxColors) { + return -1; + } + + im->colorsTotal++; + im->red[ct] = r; + im->green[ct] = g; + im->blue[ct] = b; + im->alpha[ct] = a; + im->open[ct] = 0; + + return ct; +} + +#define GD_AFFINE_KERNEL_SUPPORT 2.0 + +typedef struct { + gdImagePtr src; + gdRectPtr clip; + int bgColor; +} gdAffineSampleContext; + +static inline int gdAffineSampleIntersectsRegion(const double x, const double y, + const gdRectPtr region) { + const double left = (double)region->x - GD_AFFINE_KERNEL_SUPPORT; + const double top = (double)region->y - GD_AFFINE_KERNEL_SUPPORT; + const double right = + (double)(region->x + region->width - 1) + GD_AFFINE_KERNEL_SUPPORT; + const double bottom = + (double)(region->y + region->height - 1) + GD_AFFINE_KERNEL_SUPPORT; + + return x >= left && x <= right && y >= top && y <= bottom; +} + +static inline int gdAffineSample(const gdAffineSampleContext *ctx, + const double x, const double y, int *color) { + int c; + + if (!gdAffineSampleIntersectsRegion(x, y, ctx->clip)) { + return GD_FALSE; + } + + c = getPixelInterpolatedClipped(ctx->src, x, y, ctx->bgColor, ctx->clip); + if (gdTrueColorGetAlpha(c) == gdAlphaTransparent) { + return GD_FALSE; + } + + *color = c; + return GD_TRUE; +} + /** * Function: gdTransformAffineCopy * Applies an affine transformation to a region and copy the result @@ -1925,13 +1908,10 @@ int gdTransformAffineGetImage(gdImagePtr *dst, * Returns: * GD_TRUE on success or GD_FALSE on failure */ -int gdTransformAffineCopy(gdImagePtr dst, - int dst_x, int dst_y, - const gdImagePtr src, - gdRectPtr src_region, - const double affine[6]) -{ - int c1x,c1y,c2x,c2y; +BGD_DECLARE(int) +gdTransformAffineCopy(gdImagePtr dst, int dst_x, int dst_y, + const gdImagePtr src, gdRectPtr src_region, + const double affine[6]) { int backclip = 0; int backup_clipx1, backup_clipy1, backup_clipx2, backup_clipy2; register int x, y, src_offset_x, src_offset_y; @@ -1939,25 +1919,18 @@ int gdTransformAffineCopy(gdImagePtr dst, gdPointF pt, src_pt; gdRect bbox; int end_x, end_y; - gdInterpolationMethod interpolation_id_bak = src->interpolation_id; - - /* These methods use special implementations */ - if (src->interpolation_id == GD_BILINEAR_FIXED || src->interpolation_id == GD_BICUBIC_FIXED || src->interpolation_id == GD_NEAREST_NEIGHBOUR) { - interpolation_id_bak = src->interpolation_id; - - gdImageSetInterpolationMethod(src, GD_BICUBIC); - } - + const int transparent = gdTrueColorAlpha(0, 0, 0, gdAlphaTransparent); + gdAffineSampleContext sample_ctx; gdImageClipRectangle(src, src_region); - if (src_region->x > 0 || src_region->y > 0 - || src_region->width < gdImageSX(src) - || src_region->height < gdImageSY(src)) { + if (src_region->x > 0 || src_region->y > 0 || + src_region->width < gdImageSX(src) || + src_region->height < gdImageSY(src)) { backclip = 1; - gdImageGetClip(src, &backup_clipx1, &backup_clipy1, - &backup_clipx2, &backup_clipy2); + gdImageGetClip(src, &backup_clipx1, &backup_clipy1, &backup_clipx2, + &backup_clipy2); gdImageSetClip(src, src_region->x, src_region->y, src_region->x + src_region->width - 1, @@ -1966,62 +1939,83 @@ int gdTransformAffineCopy(gdImagePtr dst, if (!gdTransformAffineBoundingBox(src_region, affine, &bbox)) { if (backclip) { - gdImageSetClip(src, backup_clipx1, backup_clipy1, - backup_clipx2, backup_clipy2); + gdImageSetClip(src, backup_clipx1, backup_clipy1, backup_clipx2, + backup_clipy2); } - gdImageSetInterpolationMethod(src, interpolation_id_bak); return GD_FALSE; } - gdImageGetClip(dst, &c1x, &c1y, &c2x, &c2y); - end_x = bbox.width + abs(bbox.x); end_y = bbox.height + abs(bbox.y); /* Get inverse affine to let us work with destination -> source */ if (gdAffineInvert(inv, affine) == GD_FALSE) { - gdImageSetInterpolationMethod(src, interpolation_id_bak); + if (backclip) { + gdImageSetClip(src, backup_clipx1, backup_clipy1, backup_clipx2, + backup_clipy2); + } return GD_FALSE; } src_offset_x = src_region->x; src_offset_y = src_region->y; + sample_ctx.src = src; + sample_ctx.clip = src_region; + sample_ctx.bgColor = transparent; if (dst->alphaBlendingFlag) { for (y = bbox.y; y <= end_y; y++) { pt.y = y + 0.5; - for (x = 0; x <= end_x; x++) { + for (x = bbox.x; x <= end_x; x++) { pt.x = x + 0.5; + int c; gdAffineApplyToPointF(&src_pt, &pt, inv); - gdImageSetPixel(dst, dst_x + x, dst_y + y, getPixelInterpolated(src, src_offset_x + src_pt.x, src_offset_y + src_pt.y, 0)); + const double sample_x = src_offset_x + src_pt.x; + const double sample_y = src_offset_y + src_pt.y; + if (!gdAffineSample(&sample_ctx, sample_x, sample_y, &c)) { + continue; + } + gdImageSetPixel(dst, dst_x + x, dst_y + y, c); } } } else { - for (y = 0; y <= end_y; y++) { + for (y = bbox.y; y <= end_y; y++) { unsigned char *dst_p = NULL; int *tdst_p = NULL; - pt.y = y + 0.5 + bbox.y; + pt.y = y + 0.5; if ((dst_y + y) < 0 || ((dst_y + y) > gdImageSY(dst) -1)) { continue; } if (dst->trueColor) { - tdst_p = dst->tpixels[dst_y + y] + dst_x; + tdst_p = dst->tpixels[dst_y + y]; } else { - dst_p = dst->pixels[dst_y + y] + dst_x; + dst_p = dst->pixels[dst_y + y]; } - for (x = 0; x <= end_x; x++) { - pt.x = x + 0.5 + bbox.x; + for (x = bbox.x; x <= end_x; x++) { + pt.x = x + 0.5; gdAffineApplyToPointF(&src_pt, &pt, inv); if ((dst_x + x) < 0 || (dst_x + x) > (gdImageSX(dst) - 1)) { break; } + { + const double sample_x = src_offset_x + src_pt.x; + const double sample_y = src_offset_y + src_pt.y; + int c; + + if (!gdAffineSample(&sample_ctx, sample_x, sample_y, &c)) { + continue; + } if (dst->trueColor) { - *(tdst_p++) = getPixelInterpolated(src, src_offset_x + src_pt.x, src_offset_y + src_pt.y, -1); + *(tdst_p + dst_x + x) = c; } else { - *(dst_p++) = getPixelInterpolated(src, src_offset_x + src_pt.x, src_offset_y + src_pt.y, -1); + const int pc = getPixelRgbInterpolated(dst, c); + if (pc >= 0) { + *(dst_p + dst_x + x) = pc; + } + } } } } @@ -2029,11 +2023,10 @@ int gdTransformAffineCopy(gdImagePtr dst, /* Restore clip if required */ if (backclip) { - gdImageSetClip(src, backup_clipx1, backup_clipy1, - backup_clipx2, backup_clipy2); + gdImageSetClip(src, backup_clipx1, backup_clipy1, backup_clipx2, + backup_clipy2); } - gdImageSetInterpolationMethod(src, interpolation_id_bak); return GD_TRUE; } @@ -2050,10 +2043,11 @@ int gdTransformAffineCopy(gdImagePtr dst, * Returns: * GD_TRUE if the affine is rectilinear or GD_FALSE */ -int gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], gdRectPtr bbox) -{ +BGD_DECLARE(int) +gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], + gdRectPtr bbox) { gdPointF extent[4], min, max, point; - double width, height; + double bbox_x_d, bbox_y_d, bbox_width_d, bbox_height_d; int bbox_x, bbox_y, bbox_width, bbox_height; int i; @@ -2085,35 +2079,58 @@ int gdTransformAffineBoundingBox(gdRectPtr src, const double affine[6], gdRectPt if (max.y < extent[i].y) max.y=extent[i].y; } - width = floor(max.x - min.x); - height = floor(max.y - min.y); - if (!isfinite(min.x) || !isfinite(min.y) || !isfinite(width) || !isfinite(height) - || min.x <= INT_MIN || min.x > INT_MAX - || min.y <= INT_MIN || min.y > INT_MAX - || width < 1.0 || width > INT_MAX - || height < 0.0 || height > INT_MAX) { + bbox_x_d = floor(min.x); + bbox_y_d = floor(min.y); + bbox_width_d = ceil(max.x) - bbox_x_d; + bbox_height_d = ceil(max.y) - bbox_y_d; + if (!isfinite(bbox_x_d) || !isfinite(bbox_y_d) || !isfinite(bbox_width_d) || + !isfinite(bbox_height_d) || bbox_x_d < INT_MIN || bbox_x_d > INT_MAX || + bbox_y_d < INT_MIN || bbox_y_d > INT_MAX || bbox_width_d < 0.0 || + bbox_width_d > INT_MAX || bbox_height_d < 0.0 || + bbox_height_d > INT_MAX) { return GD_FALSE; } - bbox_x = (int) min.x; - bbox_y = (int) min.y; - bbox_width = (int) width - 1; - bbox_height = (int) height; - if ((bbox_x < 0 && bbox_width > INT_MAX + bbox_x) - || (bbox_x > 0 && bbox_width > INT_MAX - bbox_x) - || (bbox_y < 0 && bbox_height > INT_MAX + bbox_y) - || (bbox_y > 0 && bbox_height > INT_MAX - bbox_y)) { + bbox_x = (int)bbox_x_d; + bbox_y = (int)bbox_y_d; + bbox_width = (int)bbox_width_d; + bbox_height = (int)bbox_height_d; + if ((bbox_x < 0 && bbox_width > INT_MAX + bbox_x) || + (bbox_x > 0 && bbox_width > INT_MAX - bbox_x) || + (bbox_y < 0 && bbox_height > INT_MAX + bbox_y) || + (bbox_y > 0 && bbox_height > INT_MAX - bbox_y)) { return GD_FALSE; } bbox->x = bbox_x; bbox->y = bbox_y; bbox->width = bbox_width; bbox->height = bbox_height; + return GD_TRUE; } -int gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id) -{ - if (im == NULL || id < 0 || id > GD_METHOD_COUNT) { +/** + * Group: Interpolation Method + */ + +/** + * Function: gdImageSetInterpolationMethod + * + * Set the interpolation method for subsequent operations + * + * Parameters: + * im - The image. + * id - The interpolation method. + * + * Returns: + * Non-zero on success, zero on failure. + * + * See also: + * - + * - + */ +BGD_DECLARE(int) +gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id) { + if (im == NULL || (uintmax_t)id > GD_METHOD_COUNT) { return 0; } @@ -2137,7 +2154,8 @@ int gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id) break; case GD_BICUBIC_FIXED: case GD_BICUBIC: - im->interpolation = filter_bicubic; + /* no interpolation as gdImageScale calls a dedicated function */ + im->interpolation = NULL; break; case GD_BLACKMAN: im->interpolation = filter_blackman; @@ -2181,11 +2199,34 @@ int gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id) case GD_TRIANGLE: im->interpolation = filter_triangle; break; + case GD_LANCZOS3: + im->interpolation = filter_lanczos3; + break; + case GD_LANCZOS8: + im->interpolation = filter_lanczos8; + break; + case GD_BLACKMAN_BESSEL: + im->interpolation = filter_blackman_bessel; + break; + case GD_BLACKMAN_SINC: + im->interpolation = filter_blackman_sinc; + break; + case GD_QUADRATIC_BSPLINE: + im->interpolation = filter_quadratic_bspline; + break; + case GD_CUBIC_SPLINE: + im->interpolation = filter_cubic_spline; + break; + case GD_COSINE: + im->interpolation = filter_cosine; + break; + case GD_WELSH: + im->interpolation = filter_welsh; + break; case GD_DEFAULT: id = GD_LINEAR; im->interpolation = filter_linear; break; - default: return 0; } @@ -2211,10 +2252,8 @@ int gdImageSetInterpolationMethod(gdImagePtr im, gdInterpolationMethod id) * - * - */ -gdInterpolationMethod gdImageGetInterpolationMethod(gdImagePtr im) -{ - return im->interpolation_id; -} +BGD_DECLARE(gdInterpolationMethod) +gdImageGetInterpolationMethod(gdImagePtr im) { return im->interpolation_id; } #ifdef _MSC_VER # pragma optimize("", on) diff --git a/ext/gd/libgd/gd_io.c b/ext/gd/libgd/gd_io.c index 1ca822fc9b63..7ce626153a8f 100644 --- a/ext/gd/libgd/gd_io.c +++ b/ext/gd/libgd/gd_io.c @@ -9,94 +9,79 @@ * so they were moved here. They also make IOCtx calls look better... * * Written (or, at least, moved) 1999, Philip Warner. - * - */ + */ + +#include "gd.h" #include -#include +#include #include -#include "gd.h" +#include /* Use this for commenting out debug-print statements. */ /* Just use the first '#define' to allow all the prints... */ /*#define IO_DBG(s) (s) */ #define IO_DBG(s) +#define GD_IO_EOF_CHK(r) \ + if (r == EOF) { \ + return 0; \ + } -#define GD_IO_EOF_CHK(r) \ - if (r == EOF) { \ - return 0; \ - } \ - -/* - * Write out a word to the I/O context pointer - */ -void Putword (int w, gdIOCtx * ctx) -{ - unsigned char buf[2]; - - buf[0] = w & 0xff; - buf[1] = (w / 256) & 0xff; - (ctx->putBuf) (ctx, (char *) buf, 2); -} - -void Putchar (int c, gdIOCtx * ctx) -{ - (ctx->putC) (ctx, c & 0xff); -} +void gdPutC(const unsigned char c, gdIOCtx *ctx) { (ctx->putC)(ctx, c); } -void gdPutC (const unsigned char c, gdIOCtx * ctx) -{ - (ctx->putC) (ctx, c); -} - -void gdPutWord (int w, gdIOCtx * ctx) -{ - IO_DBG (gd_error("Putting word...")); +void gdPutWord(int w, gdIOCtx *ctx) { + IO_DBG(printf("Putting word...\n")); (ctx->putC) (ctx, (unsigned char) (w >> 8)); (ctx->putC) (ctx, (unsigned char) (w & 0xFF)); - IO_DBG (gd_error("put.")); + IO_DBG(printf("put.\n")); } -void gdPutInt (int w, gdIOCtx * ctx) -{ - IO_DBG (gd_error("Putting int...")); +void gdPutInt(int w, gdIOCtx *ctx) { + IO_DBG(printf("Putting int...\n")); (ctx->putC) (ctx, (unsigned char) (w >> 24)); (ctx->putC) (ctx, (unsigned char) ((w >> 16) & 0xFF)); (ctx->putC) (ctx, (unsigned char) ((w >> 8) & 0xFF)); (ctx->putC) (ctx, (unsigned char) (w & 0xFF)); - IO_DBG (gd_error("put.")); + IO_DBG(printf("put.\n")); } -int gdGetC (gdIOCtx * ctx) -{ - return ((ctx->getC) (ctx)); -} +int gdGetC(gdIOCtx *ctx) { return ((ctx->getC)(ctx)); } -int gdGetByte (int *result, gdIOCtx * ctx) -{ +int gdGetByte(int *result, gdIOCtx *ctx) { int r; + r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); + if (r == EOF) { + return 0; + } + *result = r; + return 1; } -int gdGetWord (int *result, gdIOCtx * ctx) -{ +int gdGetWord(int *result, gdIOCtx *ctx) { int r; + r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); + if (r == EOF) { + return 0; + } + *result = r << 8; + r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); + if (r == EOF) { + return 0; + } + *result += r; + return 1; } - -int gdGetWordLSB(signed short int *result, gdIOCtx *ctx) -{ +int gdGetWordLSB(signed short int *result, gdIOCtx *ctx) { int high = 0, low = 0; low = (ctx->getC) (ctx); if (low == EOF) { @@ -115,91 +100,95 @@ int gdGetWordLSB(signed short int *result, gdIOCtx *ctx) return 1; } -int gdGetInt (int *result, gdIOCtx * ctx) -{ - unsigned int r; +int gdGetInt(int *result, gdIOCtx *ctx) { + int r; + uint32_t value; + r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); - *result = r << 24; + if (r == EOF) { + return 0; + } + + value = (uint32_t)r << 24; r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); - *result += r << 16; + if (r == EOF) { + return 0; + } + + value |= (uint32_t)r << 16; r = (ctx->getC) (ctx); if (r == EOF) { return 0; } - *result += r << 8; + + value |= (uint32_t)r << 8; r = (ctx->getC) (ctx); - GD_IO_EOF_CHK(r); - *result += r; + if (r == EOF) { + return 0; + } + + value |= (uint32_t)r; + *result = (int32_t)value; return 1; } -int gdGetIntLSB(signed int *result, gdIOCtx *ctx) -{ - unsigned int c; - unsigned int r = 0; +int gdGetIntLSB(signed int *result, gdIOCtx *ctx) { + int c; + uint32_t r; c = (ctx->getC) (ctx); if (c == EOF) { return 0; } - r |= (c << 24); - r >>= 8; + r = (uint32_t)c; c = (ctx->getC) (ctx); if (c == EOF) { return 0; } - r |= (c << 24); - r >>= 8; + r |= (uint32_t)c << 8; c = (ctx->getC) (ctx); if (c == EOF) { return 0; } - r |= (c << 24); - r >>= 8; + r |= (uint32_t)c << 16; c = (ctx->getC) (ctx); if (c == EOF) { return 0; } - r |= (c << 24); + r |= (uint32_t)c << 24; if (result) { - *result = (signed int)r; + *result = (int32_t)r; } return 1; } -int gdPutBuf (const void *buf, int size, gdIOCtx * ctx) -{ - IO_DBG (gd_error("Putting buf...")); +int gdPutBuf(const void *buf, int size, gdIOCtx *ctx) { + IO_DBG(printf("Putting buf...\n")); return (ctx->putBuf) (ctx, buf, size); - IO_DBG (gd_error("put.")); + IO_DBG(printf("put.\n")); } -int gdGetBuf (void *buf, int size, gdIOCtx * ctx) -{ +int gdGetBuf(void *buf, int size, gdIOCtx *ctx) { return (ctx->getBuf) (ctx, buf, size); } -int gdSeek (gdIOCtx * ctx, const int pos) -{ - IO_DBG (gd_error("Seeking...")); +int gdSeek(gdIOCtx *ctx, const int pos) { + IO_DBG(printf("Seeking...\n")); return ((ctx->seek) (ctx, pos)); - IO_DBG (gd_error("Done.")); + IO_DBG(printf("Done.\n")); } -long gdTell (gdIOCtx * ctx) -{ - IO_DBG (gd_error("Telling...")); +long gdTell(gdIOCtx *ctx) { + IO_DBG(printf("Telling...\n")); return ((ctx->tell) (ctx)); - IO_DBG (gd_error ("told.")); + IO_DBG(printf("told.\n")); } diff --git a/ext/gd/libgd/gd_io_dp.c b/ext/gd/libgd/gd_io_dp.c index 81b988157fb2..930b02e2d85b 100644 --- a/ext/gd/libgd/gd_io_dp.c +++ b/ext/gd/libgd/gd_io_dp.c @@ -6,28 +6,26 @@ * Based on GD.pm code by Lincoln Stein for interfacing to libgd. * Added support for reading as well as support for 'tell' and 'seek'. * - * As will all I/O modules, most functions are for local use only (called + * As with all I/O modules, most functions are for local use only (called * via function pointers in the I/O context). * * gdDPExtractData is the exception to this: it will return the pointer to * the internal data, and reset the internal storage. * * Written/Modified 1999, Philip Warner. - * - */ + */ -#include -#include -#include #include "gd.h" #include "gdhelpers.h" +#include +#include +#include #define TRUE 1 #define FALSE 0 /* this is used for creating images in main memory */ -typedef struct dpStruct -{ +typedef struct dpStruct { void *data; int logicalSize; int realSize; @@ -36,8 +34,7 @@ typedef struct dpStruct int freeOK; } dynamicPtr; -typedef struct dpIOCtx -{ +typedef struct dpIOCtx { gdIOCtx ctx; dynamicPtr *dp; } dpIOCtx; @@ -49,32 +46,46 @@ static int allocDynamic (dynamicPtr * dp, int initialSize, void *data); static int appendDynamic (dynamicPtr * dp, const void *src, int size); static int gdReallocDynamic (dynamicPtr * dp, int required); static int trimDynamic (dynamicPtr * dp); -static void gdFreeDynamicCtx (struct gdIOCtx *ctx); +static void gdFreeDynamicCtx(gdIOCtxPtr ctx); static dynamicPtr *newDynamic (int initialSize, void *data, int freeOKFlag); -static int dynamicPutbuf (struct gdIOCtx *, const void *, int); -static void dynamicPutchar (struct gdIOCtx *, int a); +static int dynamicPutbuf(gdIOCtxPtr, const void *, int); +static void dynamicPutchar(gdIOCtxPtr, int a); static int dynamicGetbuf (gdIOCtxPtr ctx, void *buf, int len); static int dynamicGetchar (gdIOCtxPtr ctx); -static int dynamicSeek (struct gdIOCtx *, const int); -static long dynamicTell (struct gdIOCtx *); +static int dynamicSeek(gdIOCtxPtr, const int); +static long dynamicTell(gdIOCtxPtr); -/* return data as a dynamic pointer */ -gdIOCtx * gdNewDynamicCtx (int initialSize, void *data) -{ +/* + Function: gdNewDynamicCtx + + Return data as a dynamic pointer. +*/ +BGD_DECLARE(gdIOCtx *) gdNewDynamicCtx(int initialSize, void *data) { + /* 2.0.23: Phil Moore: 'return' keyword was missing! */ return gdNewDynamicCtxEx(initialSize, data, 1); } -gdIOCtx * gdNewDynamicCtxEx (int initialSize, void *data, int freeOKFlag) -{ +/* + Function: gdNewDynamicCtxEx +*/ +BGD_DECLARE(gdIOCtx *) +gdNewDynamicCtxEx(int initialSize, void *data, int freeOKFlag) { dpIOCtx *ctx; dynamicPtr *dp; ctx = (dpIOCtx *) gdMalloc (sizeof (dpIOCtx)); + if (ctx == NULL) { + return NULL; + } dp = newDynamic(initialSize, data, freeOKFlag); + if (!dp) { + gdFree(ctx); + return NULL; + }; ctx->dp = dp; @@ -92,8 +103,10 @@ gdIOCtx * gdNewDynamicCtxEx (int initialSize, void *data, int freeOKFlag) return (gdIOCtx *) ctx; } -void * gdDPExtractData (struct gdIOCtx *ctx, int *size) -{ +/* + Function: gdDPExtractData +*/ +BGD_DECLARE(void *) gdDPExtractData(gdIOCtxPtr ctx, int *size) { dynamicPtr *dp; dpIOCtx *dctx; void *data; @@ -109,7 +122,8 @@ void * gdDPExtractData (struct gdIOCtx *ctx, int *size) } else { *size = 0; data = NULL; - if (dp->data != NULL && dp->freeOK) { + /* 2.0.21: never free memory we don't own */ + if ((dp->data != NULL) && (dp->freeOK)) { gdFree(dp->data); } } @@ -121,8 +135,7 @@ void * gdDPExtractData (struct gdIOCtx *ctx, int *size) return data; } -static void gdFreeDynamicCtx (struct gdIOCtx *ctx) -{ +static void gdFreeDynamicCtx(gdIOCtxPtr ctx) { dynamicPtr *dp; dpIOCtx *dctx; @@ -131,23 +144,27 @@ static void gdFreeDynamicCtx (struct gdIOCtx *ctx) gdFree(ctx); + /* clean up the data block and return it */ + /* 2.0.21: never free memory we don't own */ + if ((dp->data != NULL) && (dp->freeOK)) { + gdFree(dp->data); + dp->data = NULL; + } + dp->realSize = 0; dp->logicalSize = 0; gdFree(dp); } -static long dynamicTell (struct gdIOCtx *ctx) -{ +static long dynamicTell(gdIOCtxPtr ctx) { dpIOCtx *dctx; dctx = (dpIOCtx *) ctx; - return (dctx->dp->pos); } -static int dynamicSeek (struct gdIOCtx *ctx, const int pos) -{ +static int dynamicSeek(gdIOCtxPtr ctx, const int pos) { int bytesNeeded; dynamicPtr *dp; dpIOCtx *dctx; @@ -168,15 +185,24 @@ static int dynamicSeek (struct gdIOCtx *ctx, const int pos) if (!dp->freeOK) { return FALSE; } - gdReallocDynamic (dp, dp->realSize * 2); + + if (overflow2(dp->realSize, 2)) { + return FALSE; + } + + if (!gdReallocDynamic(dp, dp->realSize * 2)) { + dp->dataGood = FALSE; + return FALSE; + } } - /* if we get here, we can be sure that we have enough bytes to copy safely */ + /* if we get here, we can be sure that we have enough bytes + * to copy safely */ /* Extend the logical size if we seek beyond EOF. */ if (pos > dp->logicalSize) { dp->logicalSize = pos; - } + }; dp->pos = pos; @@ -184,12 +210,18 @@ static int dynamicSeek (struct gdIOCtx *ctx, const int pos) } /* return data as a dynamic pointer */ -static dynamicPtr * newDynamic (int initialSize, void *data, int freeOKFlag) -{ +static dynamicPtr *newDynamic(int initialSize, void *data, int freeOKFlag) { dynamicPtr *dp; + dp = (dynamicPtr *) gdMalloc (sizeof (dynamicPtr)); + if (dp == NULL) { + return NULL; + } - allocDynamic (dp, initialSize, data); + if (!allocDynamic(dp, initialSize, data)) { + gdFree(dp); + return NULL; + } dp->pos = 0; dp->freeOK = freeOKFlag; @@ -197,27 +229,20 @@ static dynamicPtr * newDynamic (int initialSize, void *data, int freeOKFlag) return dp; } -static int -dynamicPutbuf (struct gdIOCtx *ctx, const void *buf, int size) -{ +static int dynamicPutbuf(gdIOCtxPtr ctx, const void *buf, int size) { dpIOCtx *dctx; dctx = (dpIOCtx *) ctx; appendDynamic (dctx->dp, buf, size); - if (dctx->dp->dataGood) - { + if (dctx->dp->dataGood) { return size; - } - else - { + } else { return -1; }; - } -static void dynamicPutchar (struct gdIOCtx *ctx, int a) -{ +static void dynamicPutchar(gdIOCtxPtr ctx, int a) { unsigned char b; dpIOCtxPtr dctx; @@ -227,8 +252,8 @@ static void dynamicPutchar (struct gdIOCtx *ctx, int a) appendDynamic(dctx->dp, &b, 1); } -static int dynamicGetbuf (gdIOCtxPtr ctx, void *buf, int len) -{ +/* returns the number of bytes actually read; 0 on EOF and error */ +static int dynamicGetbuf(gdIOCtxPtr ctx, void *buf, int len) { int rlen, remain; dpIOCtxPtr dctx; dynamicPtr *dp; @@ -236,28 +261,41 @@ static int dynamicGetbuf (gdIOCtxPtr ctx, void *buf, int len) dctx = (dpIOCtxPtr) ctx; dp = dctx->dp; + if (dp->pos < 0 || dp->pos >= dp->realSize) { + return 0; + } + remain = dp->logicalSize - dp->pos; if (remain >= len) { rlen = len; } else { if (remain <= 0) { - return EOF; + return 0; } + rlen = remain; } + if (dp->pos + rlen > dp->realSize) { + rlen = dp->realSize - dp->pos; + } + + if (rlen < 0) { + return 0; + } + memcpy(buf, (void *) ((char *) dp->data + dp->pos), rlen); dp->pos += rlen; return rlen; } -static int dynamicGetchar (gdIOCtxPtr ctx) -{ +static int dynamicGetchar(gdIOCtxPtr ctx) { unsigned char b; int rv; rv = dynamicGetbuf (ctx, &b, 1); + if (rv != 1) { return EOF; } else { @@ -266,15 +304,9 @@ static int dynamicGetchar (gdIOCtxPtr ctx) } /* ********************************************************************* - * InitDynamic - Return a dynamically resizable void* - * - * ********************************************************************* - */ -static int -allocDynamic (dynamicPtr * dp, int initialSize, void *data) -{ - + **********************************************************************/ +static int allocDynamic(dynamicPtr *dp, int initialSize, void *data) { if (data == NULL) { dp->logicalSize = 0; dp->dataGood = FALSE; @@ -285,16 +317,19 @@ allocDynamic (dynamicPtr * dp, int initialSize, void *data) dp->data = data; } + if (dp->data != NULL) { dp->realSize = initialSize; dp->dataGood = TRUE; dp->pos = 0; - return TRUE; + } else { + dp->realSize = 0; + return FALSE; + } } /* append bytes to the end of a dynamic pointer */ -static int appendDynamic (dynamicPtr * dp, const void *src, int size) -{ +static int appendDynamic(dynamicPtr *dp, const void *src, int size) { int bytesNeeded; char *tmp; @@ -310,10 +345,20 @@ static int appendDynamic (dynamicPtr * dp, const void *src, int size) if (!dp->freeOK) { return FALSE; } - gdReallocDynamic(dp, bytesNeeded * 2); + + if (overflow2(dp->realSize, 2)) { + return FALSE; } - /* if we get here, we can be sure that we have enough bytes to copy safely */ + if (!gdReallocDynamic(dp, bytesNeeded * 2)) { + dp->dataGood = FALSE; + return FALSE; + } + } + + /* if we get here, we can be sure that we have enough bytes + * to copy safely */ + /*printf("Mem OK Size: %d, Pos: %d\n", dp->realSize, dp->pos); */ tmp = (char *) dp->data; @@ -322,17 +367,17 @@ static int appendDynamic (dynamicPtr * dp, const void *src, int size) if (dp->pos > dp->logicalSize) { dp->logicalSize = dp->pos; - } + }; return TRUE; } /* grow (or shrink) dynamic pointer */ -static int gdReallocDynamic (dynamicPtr * dp, int required) -{ +static int gdReallocDynamic(dynamicPtr *dp, int required) { void *newPtr; - /* First try gdRealloc(). If that doesn't work, make a new memory block and copy. */ + /* First try gdRealloc(). If that doesn't work, make a new + * memory block and copy. */ if ((newPtr = gdRealloc(dp->data, required))) { dp->realSize = required; dp->data = newPtr; @@ -341,6 +386,10 @@ static int gdReallocDynamic (dynamicPtr * dp, int required) /* create a new pointer */ newPtr = gdMalloc(required); + if (!newPtr) { + dp->dataGood = FALSE; + return FALSE; + } /* copy the old data into it */ memcpy(newPtr, dp->data, dp->logicalSize); @@ -348,16 +397,15 @@ static int gdReallocDynamic (dynamicPtr * dp, int required) dp->data = newPtr; dp->realSize = required; - return TRUE; } /* trim pointer so that its real and logical sizes match */ -static int trimDynamic (dynamicPtr * dp) -{ +static int trimDynamic(dynamicPtr *dp) { /* 2.0.21: we don't reallocate memory we don't own */ if (!dp->freeOK) { - return FALSE; + return TRUE; } + return gdReallocDynamic(dp, dp->logicalSize); } diff --git a/ext/gd/libgd/gd_io_file.c b/ext/gd/libgd/gd_io_file.c index 8318a45969a2..79ebb6750ce2 100644 --- a/ext/gd/libgd/gd_io_file.c +++ b/ext/gd/libgd/gd_io_file.c @@ -1,4 +1,3 @@ - /* * io_file.c * @@ -14,43 +13,50 @@ */ /* For platforms with incomplete ANSI defines. Fortunately, - SEEK_SET is defined to be zero by the standard. */ + * SEEK_SET is defined to be zero by the standard. */ #ifndef SEEK_SET #define SEEK_SET 0 #endif /* SEEK_SET */ -#include -#include -#include #include "gd.h" #include "gdhelpers.h" +#include +#include +#include /* this is used for creating images in main memory */ -typedef struct fileIOCtx -{ +typedef struct fileIOCtx { gdIOCtx ctx; FILE *f; } fileIOCtx; -gdIOCtx *newFileCtx (FILE * f); +gdIOCtxPtr newFileCtx(FILE *f); -static int fileGetbuf (gdIOCtx *, void *, int); -static int filePutbuf (gdIOCtx *, const void *, int); -static void filePutchar (gdIOCtx *, int); -static int fileGetchar (gdIOCtx * ctx); +static int fileGetbuf(gdIOCtxPtr, void *, int); +static int filePutbuf(gdIOCtxPtr, const void *, int); +static void filePutchar(gdIOCtxPtr, int); +static int fileGetchar(gdIOCtxPtr ctx); -static int fileSeek (struct gdIOCtx *, const int); -static long fileTell (struct gdIOCtx *); -static void gdFreeFileCtx (gdIOCtx * ctx); +static int fileSeek(gdIOCtxPtr, const int); +static long fileTell(gdIOCtxPtr); +static void gdFreeFileCtx(gdIOCtxPtr ctx); -/* return data as a dynamic pointer */ -gdIOCtx * gdNewFileCtx (FILE * f) -{ +/* + Function: gdNewFileCtx + + Return data as a dynamic pointer. +*/ +BGD_DECLARE(gdIOCtxPtr) gdNewFileCtx(FILE *f) { fileIOCtx *ctx; + if (f == NULL) + return NULL; ctx = (fileIOCtx *) gdMalloc(sizeof (fileIOCtx)); + if (ctx == NULL) { + return NULL; + } ctx->f = f; @@ -65,34 +71,26 @@ gdIOCtx * gdNewFileCtx (FILE * f) ctx->ctx.gd_free = gdFreeFileCtx; - return (gdIOCtx *) ctx; -} - -static void gdFreeFileCtx (gdIOCtx * ctx) -{ - gdFree(ctx); + return (gdIOCtxPtr)ctx; } +static void gdFreeFileCtx(gdIOCtxPtr ctx) { gdFree(ctx); } -static int filePutbuf (gdIOCtx * ctx, const void *buf, int size) -{ +static int filePutbuf(gdIOCtxPtr ctx, const void *buf, int size) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return fwrite(buf, 1, size, fctx->f); - } -static int fileGetbuf (gdIOCtx * ctx, void *buf, int size) -{ +static int fileGetbuf(gdIOCtxPtr ctx, void *buf, int size) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; - return fread(buf, 1, size, fctx->f); + return (fread(buf, 1, size, fctx->f)); } -static void filePutchar (gdIOCtx * ctx, int a) -{ +static void filePutchar(gdIOCtxPtr ctx, int a) { unsigned char b; fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; @@ -102,25 +100,20 @@ static void filePutchar (gdIOCtx * ctx, int a) putc (b, fctx->f); } -static int fileGetchar (gdIOCtx * ctx) -{ +static int fileGetchar(gdIOCtxPtr ctx) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return getc (fctx->f); } - -static int fileSeek (struct gdIOCtx *ctx, const int pos) -{ +static int fileSeek(gdIOCtxPtr ctx, const int pos) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; - return (fseek (fctx->f, pos, SEEK_SET) == 0); } -static long fileTell (struct gdIOCtx *ctx) -{ +static long fileTell(gdIOCtxPtr ctx) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; diff --git a/ext/gd/libgd/gd_io_ss.c b/ext/gd/libgd/gd_io_ss.c index 0275cdc40330..6a05f4f96b0d 100644 --- a/ext/gd/libgd/gd_io_ss.c +++ b/ext/gd/libgd/gd_io_ss.c @@ -21,16 +21,15 @@ * */ -#include -#include -#include #include "gd.h" #include "gdhelpers.h" +#include +#include +#include /* this is used for creating images in main memory */ -typedef struct ssIOCtx -{ +typedef struct ssIOCtx { gdIOCtx ctx; gdSourcePtr src; gdSinkPtr snk; @@ -38,20 +37,24 @@ typedef struct ssIOCtx typedef struct ssIOCtx *ssIOCtxPtr; -gdIOCtx *gdNewSSCtx (gdSourcePtr src, gdSinkPtr snk); - static int sourceGetbuf (gdIOCtx *, void *, int); static int sourceGetchar (gdIOCtx * ctx); static int sinkPutbuf (gdIOCtx * ctx, const void *buf, int size); static void sinkPutchar (gdIOCtx * ctx, int a); static void gdFreeSsCtx (gdIOCtx * ctx); -/* return data as a dynamic pointer */ -gdIOCtx * gdNewSSCtx (gdSourcePtr src, gdSinkPtr snk) -{ +/* + Function: gdNewSSCtx + + Return data as a dynamic pointer. +*/ +BGD_DECLARE(gdIOCtx *) gdNewSSCtx(gdSourcePtr src, gdSinkPtr snk) { ssIOCtxPtr ctx; ctx = (ssIOCtxPtr) gdMalloc (sizeof (ssIOCtx)); + if (ctx == NULL) { + return NULL; + } ctx->src = src; ctx->snk = snk; @@ -70,14 +73,9 @@ gdIOCtx * gdNewSSCtx (gdSourcePtr src, gdSinkPtr snk) return (gdIOCtx *) ctx; } -static void gdFreeSsCtx (gdIOCtx * ctx) -{ - gdFree(ctx); -} - +static void gdFreeSsCtx(gdIOCtx *ctx) { gdFree(ctx); } -static int sourceGetbuf (gdIOCtx * ctx, void *buf, int size) -{ +static int sourceGetbuf(gdIOCtx *ctx, void *buf, int size) { ssIOCtx *lctx; int res; @@ -91,7 +89,7 @@ static int sourceGetbuf (gdIOCtx * ctx, void *buf, int size) */ if (res == 0) { - return EOF; + return 0; } else if (res < 0) { return 0; } else { @@ -99,8 +97,7 @@ static int sourceGetbuf (gdIOCtx * ctx, void *buf, int size) } } -static int sourceGetchar (gdIOCtx * ctx) -{ +static int sourceGetchar(gdIOCtx *ctx) { int res; unsigned char buf; @@ -113,8 +110,7 @@ static int sourceGetchar (gdIOCtx * ctx) } } -static int sinkPutbuf (gdIOCtx * ctx, const void *buf, int size) -{ +static int sinkPutbuf(gdIOCtx *ctx, const void *buf, int size) { ssIOCtxPtr lctx; int res; @@ -129,10 +125,10 @@ static int sinkPutbuf (gdIOCtx * ctx, const void *buf, int size) } } -static void sinkPutchar (gdIOCtx * ctx, int a) -{ +static void sinkPutchar(gdIOCtx *ctx, int a) { unsigned char b; b = a; + sinkPutbuf (ctx, &b, 1); } diff --git a/ext/gd/libgd/gd_jpeg.c b/ext/gd/libgd/gd_jpeg.c index c15a97c7b592..370284b86809 100644 --- a/ext/gd/libgd/gd_jpeg.c +++ b/ext/gd/libgd/gd_jpeg.c @@ -1,6 +1,6 @@ /* * gd_jpeg.c: Read and write JPEG (JFIF) format image files using the - * gd graphics library (http://www.boutell.com/gd/). + * gd graphics library (https://libgd.github.io). * * This software is based in part on the work of the Independent JPEG * Group. For more information on the IJG JPEG software (and JPEG @@ -21,52 +21,64 @@ * Christian Aberger */ -#include -#include -#include +/** + * File: JPEG IO + * + * Read and write JPEG images. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include +#include +#include #include #include "gd.h" #include "gd_errors.h" +#include "gd_intern.h" /* TBB: move this up so include files are not brought in */ /* JCE: arrange HAVE_LIBJPEG so that it can be set in gd.h */ #ifdef HAVE_LIBJPEG #include "gdhelpers.h" -#undef HAVE_STDLIB_H + +#if defined(_WIN32) && defined(__MINGW32__) +#define HAVE_BOOLEAN +#endif /* 1.8.1: remove dependency on jinclude.h */ -#include "jpeglib.h" #include "jerror.h" +#include "jpeglib.h" static const char *const GD_JPEG_VERSION = "1.0"; -typedef struct _jmpbuf_wrapper -{ +typedef struct _jmpbuf_wrapper { jmp_buf jmpbuf; int ignore_warning; } jmpbuf_wrapper; -static void php_jpeg_emit_message(j_common_ptr jpeg_info, int level) -{ +static void jpeg_emit_message(j_common_ptr jpeg_info, int level) { char message[JMSG_LENGTH_MAX]; jmpbuf_wrapper *jmpbufw; int ignore_warning = 0; - jmpbufw = (jmpbuf_wrapper *) jpeg_info->client_data; + jmpbufw = (jmpbuf_wrapper *)jpeg_info->client_data; if (jmpbufw != 0) { ignore_warning = jmpbufw->ignore_warning; } - (jpeg_info->err->format_message)(jpeg_info,message); + (jpeg_info->err->format_message)(jpeg_info, message); /* It is a warning message */ if (level < 0) { /* display only the 1st warning, as would do a default libjpeg * unless strace_level >= 3 */ - if ((jpeg_info->err->num_warnings == 0) || (jpeg_info->err->trace_level >= 3)) { + if ((jpeg_info->err->num_warnings == 0) || + (jpeg_info->err->trace_level >= 3)) { if (!ignore_warning) { gd_error("gd-jpeg, libjpeg: recoverable error: %s\n", message); } @@ -83,30 +95,32 @@ static void php_jpeg_emit_message(j_common_ptr jpeg_info, int level) } } - /* Called by the IJG JPEG library upon encountering a fatal error */ -static void fatal_jpeg_error (j_common_ptr cinfo) -{ +static void fatal_jpeg_error(j_common_ptr cinfo) { jmpbuf_wrapper *jmpbufw; char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); - gd_error_ex(GD_WARNING, "gd-jpeg: JPEG library reports unrecoverable error: %s", buffer); + gd_error_ex(GD_WARNING, + "gd-jpeg: JPEG library reports unrecoverable error: %s", + buffer); - jmpbufw = (jmpbuf_wrapper *) cinfo->client_data; - jpeg_destroy (cinfo); + jmpbufw = (jmpbuf_wrapper *)cinfo->client_data; + jpeg_destroy(cinfo); if (jmpbufw != 0) { - longjmp (jmpbufw->jmpbuf, 1); - gd_error_ex(GD_ERROR, "gd-jpeg: EXTREMELY fatal error: longjmp returned control; terminating"); + longjmp(jmpbufw->jmpbuf, 1); + gd_error_ex(GD_ERROR, "gd-jpeg: EXTREMELY fatal error: longjmp " + "returned control; terminating\n"); } else { - gd_error_ex(GD_ERROR, "gd-jpeg: EXTREMELY fatal error: jmpbuf unrecoverable; terminating"); + gd_error_ex(GD_ERROR, "gd-jpeg: EXTREMELY fatal error: jmpbuf " + "unrecoverable; terminating\n"); } - exit (99); + exit(99); } -const char * gdJpegGetVersionString() +BGD_DECLARE(const char *) gdJpegGetVersionString() { switch(JPEG_LIB_VERSION) { case 62: @@ -134,7 +148,9 @@ const char * gdJpegGetVersionString() } } -static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality); +static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality, + const gdImageMetadata *metadata, + int force_no_subsampling); /* * Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality @@ -145,37 +161,267 @@ static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality); * library documentation for more details. */ -void gdImageJpeg (gdImagePtr im, FILE * outFile, int quality) -{ - gdIOCtx *out = gdNewFileCtx (outFile); - gdImageJpegCtx (im, out, quality); - out->gd_free (out); +/* + Function: gdImageJpeg + + outputs the specified image to the specified file in + JPEG format. The file must be open for writing. Under MSDOS and + all versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + If _quality_ is negative, the default IJG JPEG quality value (which + should yield a good general quality / size tradeoff for most + situations) is used. Otherwise, for practical purposes, _quality_ + should be a value in the range 0-95, higher quality values usually + implying both higher quality and larger image sizes. + + If you have set image interlacing using , this + function will interpret that to mean you wish to output a + progressive JPEG. Some programs (e.g., Web browsers) can display + progressive JPEGs incrementally; this can be useful when browsing + over a relatively slow communications link, for + example. Progressive JPEGs can also be slightly smaller than + sequential (non-progressive) JPEGs. + + Variants: + + stores the image using a struct. + + stores the image to RAM. + + Parameters: + + im - The image to save + outFile - The FILE pointer to write to. + quality - Compression quality (0-95, 0 means use the default). + + Returns: + + Nothing. + + Example: + (start code) + + gdImagePtr im; + int black, white; + FILE *out; + // Create the image + im = gdImageCreate(100, 100); + // Allocate background + white = gdImageColorAllocate(im, 255, 255, 255); + // Allocate drawing color + black = gdImageColorAllocate(im, 0, 0, 0); + // Draw rectangle + gdImageRectangle(im, 0, 0, 99, 99, black); + // Open output file in binary mode + out = fopen("rect.jpg", "wb"); + // Write JPEG using default quality + gdImageJpeg(im, out, -1); + // Close file + fclose(out); + // Destroy image + gdImageDestroy(im); + + (end code) +*/ + +BGD_DECLARE(void) gdImageJpeg(gdImagePtr im, FILE *outFile, int quality) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; + gdImageJpegCtx(im, out, quality); + out->gd_free(out); } -void *gdImageJpegPtr (gdImagePtr im, int *size, int quality) -{ +/* + Function: gdImageJpegPtr + + Identical to except that it returns a pointer to a + memory area with the JPEG data. This memory must be freed by the + caller when it is no longer needed. + + The caller *must* invoke , not free(). This is because it + is not guaranteed that libgd will use the same implementation of + malloc, free, etc. as your proggram. + + The 'size' parameter receives the total size of the block of + memory. + + Parameters: + + im - The image to write + size - Output: the size of the resulting image. + quality - Compression quality. + + Returns: + + A pointer to the JPEG data or NULL if an error occurred. + +*/ +BGD_DECLARE(void *) gdImageJpegPtr(gdImagePtr im, int *size, int quality) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + if (!_gdImageJpegCtx(im, out, quality, NULL, 0)) { + rv = gdDPExtractData(out, size); + } else { + rv = NULL; + } + out->gd_free(out); + return rv; +} + +BGD_DECLARE(void *) +gdImageJpegPtrWithMetadata(gdImagePtr im, int *size, int quality, + const gdImageMetadata *metadata) { void *rv; - gdIOCtx *out = gdNewDynamicCtx (2048, NULL); - if (!_gdImageJpegCtx(im, out, quality)) { + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + if (!_gdImageJpegCtx(im, out, quality, metadata, 0)) { rv = gdDPExtractData(out, size); } else { rv = NULL; } - out->gd_free (out); + out->gd_free(out); + return rv; +} +void *gdImageJpegPtrWithMetadataNoSubsampling(gdImagePtr im, int *size, + int quality, + const gdImageMetadata *metadata) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + if (!_gdImageJpegCtx(im, out, quality, metadata, 1)) { + rv = gdDPExtractData(out, size); + } else { + rv = NULL; + } + out->gd_free(out); return rv; } -void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile); +static void jpeg_gdIOCtx_dest(j_compress_ptr cinfo, gdIOCtx *outfile); -void gdImageJpegCtx (gdImagePtr im, gdIOCtx * outfile, int quality) -{ - _gdImageJpegCtx(im, outfile, quality); +/* + Function: gdImageJpegCtx + + Write the image as JPEG data via a . See + for more details. + + Parameters: + + im - The image to write. + outfile - The output sink. + quality - Image quality. + +*/ +BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) { + _gdImageJpegCtx(im, outfile, quality, NULL, 0); +} + +BGD_DECLARE(void) +gdImageJpegCtxWithMetadata(gdImagePtr im, gdIOCtx *outfile, int quality, + const gdImageMetadata *metadata) { + _gdImageJpegCtx(im, outfile, quality, metadata, 0); } /* returns 0 on success, 1 on failure */ -static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) -{ +static int gdJpegWriteAppMarker(j_compress_ptr cinfo, int marker, + const unsigned char *data, size_t size) { + if (data == NULL && size != 0) { + return 1; + } + if (size > 65533) { + return 1; + } + jpeg_write_marker(cinfo, marker, data, (unsigned int)size); + return 0; +} + +static int gdJpegWriteIccProfile(j_compress_ptr cinfo, + const unsigned char *data, size_t size) { + static const unsigned char icc_signature[] = "ICC_PROFILE"; + unsigned char *segment; + size_t offset = 0; + size_t max_payload = 65533 - 14; + int segment_count; + int segment_index; + + if (data == NULL && size != 0) { + return 1; + } + if (size == 0) { + return 0; + } + if (size > max_payload * 255) { + return 1; + } + + segment_count = (int)((size + max_payload - 1) / max_payload); + segment = (unsigned char *)gdMalloc(65533); + if (segment == NULL) { + return 1; + } + memcpy(segment, icc_signature, 12); + + for (segment_index = 1; segment_index <= segment_count; segment_index++) { + size_t chunk_size = size - offset; + if (chunk_size > max_payload) { + chunk_size = max_payload; + } + segment[12] = (unsigned char)segment_index; + segment[13] = (unsigned char)segment_count; + memcpy(segment + 14, data + offset, chunk_size); + jpeg_write_marker(cinfo, JPEG_APP0 + 2, segment, + (unsigned int)(chunk_size + 14)); + offset += chunk_size; + } + + gdFree(segment); + return 0; +} + +static int gdJpegWriteMetadata(j_compress_ptr cinfo, + const gdImageMetadata *metadata) { + const unsigned char *data; + size_t size; + + if (metadata == NULL) { + return 0; + } + + data = gdImageMetadataGetProfile(metadata, "exif", &size); + if (data != NULL && + gdJpegWriteAppMarker(cinfo, JPEG_APP0 + 1, data, size)) { + return 1; + } + data = gdImageMetadataGetProfile(metadata, "xmp", &size); + if (data != NULL && + gdJpegWriteAppMarker(cinfo, JPEG_APP0 + 1, data, size)) { + return 1; + } + data = gdImageMetadataGetProfile(metadata, "icc", &size); + if (data != NULL && gdJpegWriteIccProfile(cinfo, data, size)) { + return 1; + } + data = gdImageMetadataGetProfile(metadata, "iptc", &size); + if (data != NULL && + gdJpegWriteAppMarker(cinfo, JPEG_APP0 + 13, data, size)) { + return 1; + } + + return 0; +} + +static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality, + const gdImageMetadata *metadata, + int force_no_subsampling) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; int i, j, jidx; @@ -186,75 +432,129 @@ static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) JDIMENSION nlines; char comment[255]; - memset (&cinfo, 0, sizeof (cinfo)); - memset (&jerr, 0, sizeof (jerr)); +#ifdef JPEG_DEBUG + gd_error_ex(GD_DEBUG, "gd-jpeg: gd JPEG version %s\n", GD_JPEG_VERSION); + gd_error_ex(GD_DEBUG, + "gd-jpeg: JPEG library version %d, %d-bit sample values\n", + JPEG_LIB_VERSION, BITS_IN_JSAMPLE); + if (!im->trueColor) { + for (i = 0; i < im->colorsTotal; i++) { + if (!im->open[i]) { + gd_error_ex(GD_DEBUG, + "gd-jpeg: gd colormap index %d: (%d, %d, %d)\n", i, + im->red[i], im->green[i], im->blue[i]); + } + } + } +#endif /* JPEG_DEBUG */ + + memset(&cinfo, 0, sizeof(cinfo)); + memset(&jerr, 0, sizeof(jerr)); - cinfo.err = jpeg_std_error (&jerr); + cinfo.err = jpeg_std_error(&jerr); cinfo.client_data = &jmpbufw; - if (setjmp (jmpbufw.jmpbuf) != 0) { + + if (setjmp(jmpbufw.jmpbuf) != 0) { /* we're here courtesy of longjmp */ if (row) { - gdFree (row); + gdFree(row); } return 1; } + cinfo.err->emit_message = jpeg_emit_message; cinfo.err->error_exit = fatal_jpeg_error; - jpeg_create_compress (&cinfo); + jpeg_create_compress(&cinfo); cinfo.image_width = im->sx; cinfo.image_height = im->sy; - cinfo.input_components = 3; /* # of color components per pixel */ - cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ - jpeg_set_defaults (&cinfo); + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); cinfo.density_unit = 1; cinfo.X_density = im->res_x; cinfo.Y_density = im->res_y; if (quality >= 0) { - jpeg_set_quality (&cinfo, quality, TRUE); + jpeg_set_quality(&cinfo, quality, TRUE); + } + if (force_no_subsampling || quality >= 90) { + for (i = 0; i < cinfo.num_components; i++) { + cinfo.comp_info[i].h_samp_factor = 1; + cinfo.comp_info[i].v_samp_factor = 1; + } } /* If user requests interlace, translate that to progressive JPEG */ - if (gdImageGetInterlaced (im)) { - jpeg_simple_progression (&cinfo); + if (gdImageGetInterlaced(im)) { +#ifdef JPEG_DEBUG + gd_error_ex( + GD_DEBUG, + "gd-jpeg: interlace set, outputting progressive JPEG image\n"); +#endif + jpeg_simple_progression(&cinfo); } - jpeg_gdIOCtx_dest (&cinfo, outfile); + jpeg_gdIOCtx_dest(&cinfo, outfile); + + row = (JSAMPROW)gdCalloc(1, cinfo.image_width * cinfo.input_components * + sizeof(JSAMPLE)); + if (row == 0) { + gd_error("gd-jpeg: error: unable to allocate JPEG row structure: " + "gdCalloc returns NULL\n"); + jpeg_destroy_compress(&cinfo); + return 1; + } - row = (JSAMPROW) safe_emalloc(cinfo.image_width * cinfo.input_components, sizeof(JSAMPLE), 0); - memset(row, 0, cinfo.image_width * cinfo.input_components * sizeof(JSAMPLE)); rowptr[0] = row; - jpeg_start_compress (&cinfo, TRUE); + jpeg_start_compress(&cinfo, TRUE); + + if (gdJpegWriteMetadata(&cinfo, metadata)) { + gd_error("gd-jpeg: error: unable to write metadata\n"); + goto error; + } + + sprintf(comment, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d),", + GD_JPEG_VERSION, JPEG_LIB_VERSION); if (quality >= 0) { - snprintf(comment, sizeof(comment)-1, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d), quality = %d\n", GD_JPEG_VERSION, JPEG_LIB_VERSION, quality); + sprintf(comment + strlen(comment), " quality = %d\n", quality); } else { - snprintf(comment, sizeof(comment)-1, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d), default quality\n", GD_JPEG_VERSION, JPEG_LIB_VERSION); + strcat(comment + strlen(comment), " default quality\n"); } - jpeg_write_marker (&cinfo, JPEG_COM, (unsigned char *) comment, (unsigned int) strlen (comment)); - if (im->trueColor) { + jpeg_write_marker(&cinfo, JPEG_COM, (unsigned char *)comment, + (unsigned int)strlen(comment)); + + if (im->trueColor) { #if BITS_IN_JSAMPLE == 12 - gd_error("gd-jpeg: error: jpeg library was compiled for 12-bit precision. This is mostly useless, because JPEGs on the web are 8-bit and such versions of the jpeg library won't read or write them. GD doesn't support these unusual images. Edit your jmorecfg.h file to specify the correct precision and completely 'make clean' and 'make install' libjpeg again. Sorry"); + gd_error( + "gd-jpeg: error: jpeg library was compiled for 12-bit\n" + "precision. This is mostly useless, because JPEGs on the web are\n" + "8-bit and such versions of the jpeg library won't read or write\n" + "them. GD doesn't support these unusual images. Edit your\n" + "jmorecfg.h file to specify the correct precision and completely\n" + "'make clean' and 'make install' libjpeg again. Sorry.\n"); goto error; #endif /* BITS_IN_JSAMPLE == 12 */ - for (i = 0; i < im->sy; i++) { for (jidx = 0, j = 0; j < im->sx; j++) { int val = im->tpixels[i][j]; - - row[jidx++] = gdTrueColorGetRed (val); - row[jidx++] = gdTrueColorGetGreen (val); - row[jidx++] = gdTrueColorGetBlue (val); + row[jidx++] = gdTrueColorGetRed(val); + row[jidx++] = gdTrueColorGetGreen(val); + row[jidx++] = gdTrueColorGetBlue(val); } - nlines = jpeg_write_scanlines (&cinfo, rowptr, 1); + nlines = jpeg_write_scanlines(&cinfo, rowptr, 1); + if (nlines != 1) { - gd_error_ex(GD_WARNING, "gd_jpeg: warning: jpeg_write_scanlines returns %u -- expected 1", nlines); + gd_error("gd_jpeg: warning: jpeg_write_scanlines returns %u -- " + "expected 1\n", + nlines); } } } else { @@ -262,7 +562,8 @@ static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) for (jidx = 0, j = 0; j < im->sx; j++) { int idx = im->pixels[i][j]; - /* NB: Although gd RGB values are ints, their max value is + /* + * NB: Although gd RGB values are ints, their max value is * 255 (see the documentation for gdImageColorAllocate()) * -- perfect for 8-bit JPEG encoding (which is the norm) */ @@ -279,65 +580,319 @@ static int _gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) #endif } - nlines = jpeg_write_scanlines (&cinfo, rowptr, 1); + nlines = jpeg_write_scanlines(&cinfo, rowptr, 1); if (nlines != 1) { - gd_error_ex(GD_WARNING, "gd_jpeg: warning: jpeg_write_scanlines returns %u -- expected 1", nlines); + gd_error("gd_jpeg: warning: jpeg_write_scanlines" + " returns %u -- expected 1\n", + nlines); } } } - jpeg_finish_compress (&cinfo); - jpeg_destroy_compress (&cinfo); - gdFree (row); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + gdFree(row); return 0; + +error: + jpeg_destroy_compress(&cinfo); + if (row) { + gdFree(row); + } + return 1; } -gdImagePtr gdImageCreateFromJpeg (FILE * inFile) -{ +/* + Function: gdImageCreateFromJpeg + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpeg(FILE *inFile) { return gdImageCreateFromJpegEx(inFile, 1); } -gdImagePtr gdImageCreateFromJpegEx (FILE * inFile, int ignore_warning) -{ +/* + Function: gdImageCreateFromJpegEx + + is called to load truecolor images from + JPEG format files. Invoke with an + already opened pointer to a file containing the desired + image. returns a to the new + truecolor image, or NULL if unable to load the image (most often + because the file is corrupt or does not contain a JPEG + image). does not close the file. + + You can inspect the sx and sy members of the image to determine + its size. The image must eventually be destroyed using + . + + *The returned image is always a truecolor image.* + + Variants: + + creates an image from JPEG data + already in memory. + + reads its data via the function + pointers in a structure. + + , and + are equivalent to calling their + _Ex_-named counterparts with an ignore_warning set to 1 + (i.e. TRUE). + + Parameters: + + infile - The input FILE pointer. + ignore_warning - Flag. If true, ignores recoverable warnings. + + Returns: + + A pointer to the new *truecolor* image. This will need to be + destroyed with once it is no longer needed. + + On error, returns NULL. + + Example: + (start code) + + gdImagePtr im; + FILE *in; + in = fopen("myjpeg.jpg", "rb"); + im = gdImageCreateFromJpegEx(in, GD_TRUE); + fclose(in); + // ... Use the image ... + gdImageDestroy(im); + + (end code) +*/ +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegEx(FILE *inFile, int ignore_warning) { gdImagePtr im; gdIOCtx *in = gdNewFileCtx(inFile); + if (in == NULL) + return NULL; im = gdImageCreateFromJpegCtxEx(in, ignore_warning); - in->gd_free (in); - + in->gd_free(in); return im; } -gdImagePtr gdImageCreateFromJpegPtr (int size, void *data) -{ +/* + Function: gdImageCreateFromJpegPtr + + Parameters: + + size - size of JPEG data in bytes. + data - pointer to JPEG data. + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegPtr(int size, void *data) { return gdImageCreateFromJpegPtrEx(size, data, 1); } -gdImagePtr gdImageCreateFromJpegPtrEx (int size, void *data, int ignore_warning) -{ +/* + Function: gdImageCreateFromJpegPtrEx + + Parameters: + + size - size of JPEG data in bytes. + data - pointer to JPEG data. + ignore_warning - if true, ignore recoverable warnings + + See . +*/ +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrEx(int size, void *data, int ignore_warning) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return 0; + } im = gdImageCreateFromJpegCtxEx(in, ignore_warning); in->gd_free(in); + return im; +} +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrWithMetadata(int size, void *data, + gdImageMetadata *metadata) { + return gdImageCreateFromJpegPtrExWithMetadata(size, data, 1, metadata); +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrExWithMetadata(int size, void *data, int ignore_warning, + gdImageMetadata *metadata) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return 0; + } + im = gdImageCreateFromJpegCtxExWithMetadata(in, ignore_warning, metadata); + in->gd_free(in); return im; } -void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx * infile); +static void jpeg_gdIOCtx_src(j_decompress_ptr cinfo, gdIOCtx *infile); static int CMYKToRGB(int c, int m, int y, int k, int inverted); +static int gdJpegMarkerStartsWith(jpeg_saved_marker_ptr marker, + const unsigned char *prefix, + size_t prefix_size) { + return marker->data_length >= prefix_size && + memcmp(marker->data, prefix, prefix_size) == 0; +} + +static int gdJpegCollectIccProfile(j_decompress_ptr cinfo, + gdImageMetadata *metadata) { + static const unsigned char icc_signature[] = "ICC_PROFILE"; + jpeg_saved_marker_ptr marker; + jpeg_saved_marker_ptr segments[256]; + unsigned int segment_sizes[256]; + unsigned int segment_count = 0; + unsigned int i; + size_t total_size = 0; + size_t offset = 0; + unsigned char *icc; + int status; + + memset(segments, 0, sizeof(segments)); + memset(segment_sizes, 0, sizeof(segment_sizes)); + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + unsigned int sequence; + unsigned int count; + + if (marker->marker != JPEG_APP0 + 2 || + !gdJpegMarkerStartsWith(marker, icc_signature, 12) || + marker->data_length < 14) { + continue; + } + + sequence = marker->data[12]; + count = marker->data[13]; + if (sequence == 0 || count == 0 || sequence > count) { + return GD_META_ERR_PARSE; + } + if (segment_count == 0) { + segment_count = count; + } else if (segment_count != count) { + return GD_META_ERR_PARSE; + } + if (segments[sequence] != NULL) { + return GD_META_ERR_PARSE; + } + segments[sequence] = marker; + segment_sizes[sequence] = marker->data_length - 14; + if ((size_t)-1 - total_size < segment_sizes[sequence]) { + return GD_META_ERR_LIMIT; + } + total_size += segment_sizes[sequence]; + } + + if (segment_count == 0) { + return GD_META_OK; + } + + for (i = 1; i <= segment_count; i++) { + if (segments[i] == NULL) { + return GD_META_ERR_PARSE; + } + } + + icc = (unsigned char *)gdMalloc(total_size); + if (icc == NULL && total_size != 0) { + return GD_META_ERR_NOMEM; + } + + for (i = 1; i <= segment_count; i++) { + if (segment_sizes[i] != 0) { + // codechecker_false_positive [all] suppress all checker results + memcpy(icc + offset, segments[i]->data + 14, segment_sizes[i]); + } + offset += segment_sizes[i]; + } + + status = gdImageMetadataSetProfile(metadata, "icc", icc, total_size); + if (icc != NULL) { + gdFree(icc); + } + return status; +} + +static int gdJpegCollectMetadata(j_decompress_ptr cinfo, + gdImageMetadata *metadata) { + static const unsigned char exif_signature[] = {'E', 'x', 'i', + 'f', '\0', '\0'}; + static const unsigned char xmp_signature[] = "http://ns.adobe.com/xap/1.0/"; + static const unsigned char iptc_signature[] = "Photoshop 3.0"; + jpeg_saved_marker_ptr marker; + int status; + + if (metadata == NULL) { + return GD_META_OK; + } + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (marker->marker == JPEG_APP0 + 1 && + gdJpegMarkerStartsWith(marker, exif_signature, + sizeof(exif_signature))) { + status = gdImageMetadataSetProfile(metadata, "exif", marker->data, + marker->data_length); + if (status != GD_META_OK) { + return status; + } + } else if (marker->marker == JPEG_APP0 + 1 && + gdJpegMarkerStartsWith(marker, xmp_signature, + sizeof(xmp_signature))) { + status = gdImageMetadataSetProfile(metadata, "xmp", marker->data, + marker->data_length); + if (status != GD_META_OK) { + return status; + } + } else if (marker->marker == JPEG_APP0 + 13 && + gdJpegMarkerStartsWith(marker, iptc_signature, + sizeof(iptc_signature))) { + status = gdImageMetadataSetProfile(metadata, "iptc", marker->data, + marker->data_length); + if (status != GD_META_OK) { + return status; + } + } + } + + return gdJpegCollectIccProfile(cinfo, metadata); +} /* - * Create a gd-format image from the JPEG-format INFILE. Returns the - * image, or NULL upon error. - */ -gdImagePtr gdImageCreateFromJpegCtx (gdIOCtx * infile) -{ + Function: gdImageCreateFromJpegCtx + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegCtx(gdIOCtx *infile) { return gdImageCreateFromJpegCtxEx(infile, 1); } -gdImagePtr gdImageCreateFromJpegCtxEx (gdIOCtx * infile, int ignore_warning) -{ +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxWithMetadata(gdIOCtx *infile, + gdImageMetadata *metadata) { + return gdImageCreateFromJpegCtxExWithMetadata(infile, 1, metadata); +} + +/* + Function: gdImageCreateFromJpegCtxEx + + See . +*/ +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxEx(gdIOCtx *infile, int ignore_warning) { + return gdImageCreateFromJpegCtxExWithMetadata(infile, ignore_warning, NULL); +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxExWithMetadata(gdIOCtx *infile, int ignore_warning, + gdImageMetadata *metadata) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; jmpbuf_wrapper jmpbufw; @@ -345,57 +900,89 @@ gdImagePtr gdImageCreateFromJpegCtxEx (gdIOCtx * infile, int ignore_warning) volatile JSAMPROW row = 0; volatile gdImagePtr im = 0; JSAMPROW rowptr[1]; - unsigned int i, j; + JDIMENSION i, j; int retval; JDIMENSION nrows; int channels = 3; int inverted = 0; - memset (&cinfo, 0, sizeof (cinfo)); - memset (&jerr, 0, sizeof (jerr)); +#ifdef JPEG_DEBUG + gd_error_ex(GD_DEBUG, "gd-jpeg: gd JPEG version %s\n", GD_JPEG_VERSION); + gd_error_ex(GD_DEBUG, + "gd-jpeg: JPEG library version %d, %d-bit sample values\n", + JPEG_LIB_VERSION, BITS_IN_JSAMPLE); + gd_error_ex(GD_DEBUG, "sizeof: %d\n", + sizeof(struct jpeg_decompress_struct)); +#endif + + memset(&cinfo, 0, sizeof(cinfo)); + memset(&jerr, 0, sizeof(jerr)); jmpbufw.ignore_warning = ignore_warning; - cinfo.err = jpeg_std_error (&jerr); + cinfo.err = jpeg_std_error(&jerr); cinfo.client_data = &jmpbufw; - cinfo.err->emit_message = php_jpeg_emit_message; - if (setjmp (jmpbufw.jmpbuf) != 0) { + cinfo.err->emit_message = jpeg_emit_message; + + if (setjmp(jmpbufw.jmpbuf) != 0) { /* we're here courtesy of longjmp */ if (row) { - gdFree (row); + gdFree(row); } if (im) { - gdImageDestroy (im); + gdImageDestroy(im); } return 0; } cinfo.err->error_exit = fatal_jpeg_error; - jpeg_create_decompress (&cinfo); + jpeg_create_decompress(&cinfo); - jpeg_gdIOCtx_src (&cinfo, infile); + jpeg_gdIOCtx_src(&cinfo, infile); - /* 2.0.22: save the APP14 marker to check for Adobe Photoshop CMYK files with inverted components. */ + /* 2.0.22: save the APP14 marker to check for Adobe Photoshop CMYK + * files with inverted components. + */ jpeg_save_markers(&cinfo, JPEG_APP0 + 14, 256); + if (metadata != NULL) { + jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF); + jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF); + jpeg_save_markers(&cinfo, JPEG_APP0 + 13, 0xFFFF); + } - retval = jpeg_read_header (&cinfo, TRUE); + retval = jpeg_read_header(&cinfo, TRUE); if (retval != JPEG_HEADER_OK) { - gd_error_ex(GD_WARNING, "gd-jpeg: warning: jpeg_read_header returned %d, expected %d", retval, JPEG_HEADER_OK); + gd_error("gd-jpeg: warning: jpeg_read_header returns" + " %d, expected %d\n", + retval, JPEG_HEADER_OK); + } + + retval = gdJpegCollectMetadata(&cinfo, metadata); + if (retval != GD_META_OK) { + gd_error("gd-jpeg: error: unable to read metadata\n"); + goto error; } if (cinfo.image_height > INT_MAX) { - gd_error_ex(GD_WARNING, "gd-jpeg: warning: JPEG image height (%u) is greater than INT_MAX (%d) (and thus greater than gd can handle)", cinfo.image_height, INT_MAX); + gd_error("gd-jpeg: warning: JPEG image height (%u) is" + " greater than INT_MAX (%d) (and thus greater than" + " gd can handle)", + cinfo.image_height, INT_MAX); } if (cinfo.image_width > INT_MAX) { - gd_error_ex(GD_WARNING, "gd-jpeg: warning: JPEG image width (%u) is greater than INT_MAX (%d) (and thus greater than gd can handle)", cinfo.image_width, INT_MAX); + gd_error("gd-jpeg: warning: JPEG image width (%u) is" + " greater than INT_MAX (%d) (and thus greater than" + " gd can handle)\n", + cinfo.image_width, INT_MAX); } - im = gdImageCreateTrueColor ((int) cinfo.image_width, (int) cinfo.image_height); + im = + gdImageCreateTrueColor((int)cinfo.image_width, (int)cinfo.image_height); if (im == 0) { - gd_error("gd-jpeg error: cannot allocate gdImage struct"); + gd_error("gd-jpeg error: cannot allocate gdImage struct\n"); goto error; } @@ -413,17 +1000,71 @@ gdImagePtr gdImageCreateFromJpegCtxEx (gdIOCtx * infile, int ignore_warning) /* 2.0.22: very basic support for reading CMYK colorspace files. Nice for * thumbnails but there's no support for fussy adjustment of the - * assumed properties of inks and paper. */ - if ((cinfo.jpeg_color_space == JCS_CMYK) || (cinfo.jpeg_color_space == JCS_YCCK)) { + * assumed properties of inks and paper. + */ + if ((cinfo.jpeg_color_space == JCS_CMYK) || + (cinfo.jpeg_color_space == JCS_YCCK)) { cinfo.out_color_space = JCS_CMYK; } else { cinfo.out_color_space = JCS_RGB; } - if (jpeg_start_decompress (&cinfo) != TRUE) { - gd_error("gd-jpeg: warning: jpeg_start_decompress reports suspended data source"); + if (jpeg_start_decompress(&cinfo) != TRUE) { + gd_error("gd-jpeg: warning: jpeg_start_decompress" + " reports suspended data source\n"); + } + +#ifdef JPEG_DEBUG + gd_error_ex(GD_DEBUG, "gd-jpeg: JPEG image information:"); + if (cinfo.saw_JFIF_marker) { + gd_error_ex(GD_DEBUG, " JFIF version %d.%.2d", + (int)cinfo.JFIF_major_version, + (int)cinfo.JFIF_minor_version); + } else if (cinfo.saw_Adobe_marker) { + gd_error_ex(GD_DEBUG, " Adobe format"); + } else { + gd_error_ex(GD_DEBUG, " UNKNOWN format"); } + gd_error_ex(GD_DEBUG, " %ux%u (raw) / %ux%u (scaled) %d-bit", + cinfo.image_width, cinfo.image_height, cinfo.output_width, + cinfo.output_height, cinfo.data_precision); + gd_error_ex(GD_DEBUG, " %s", + (cinfo.progressive_mode ? "progressive" : "baseline")); + gd_error_ex(GD_DEBUG, " image, %d quantized colors, ", + cinfo.actual_number_of_colors); + + switch (cinfo.jpeg_color_space) { + case JCS_GRAYSCALE: + gd_error_ex(GD_DEBUG, "grayscale"); + break; + + case JCS_RGB: + gd_error_ex(GD_DEBUG, "RGB"); + break; + + case JCS_YCbCr: + gd_error_ex(GD_DEBUG, "YCbCr (a.k.a. YUV)"); + break; + + case JCS_CMYK: + gd_error_ex(GD_DEBUG, "CMYK"); + break; + + case JCS_YCCK: + gd_error_ex(GD_DEBUG, "YCbCrK"); + break; + + default: + gd_error_ex(GD_DEBUG, "UNKNOWN (value: %d)", + (int)cinfo.jpeg_color_space); + break; + } + + gd_error_ex(GD_DEBUG, " colorspace\n"); + fflush(stdout); +#endif /* JPEG_DEBUG */ + /* REMOVED by TBB 2/12/01. This field of the structure is * documented as private, and sure enough it's gone in the * latest libjpeg, replaced by something else. Unfortunately @@ -431,141 +1072,196 @@ gdImagePtr gdImageCreateFromJpegCtxEx (gdIOCtx * infile, int ignore_warning) * progressive or not; just declare your intent before you * write one by calling gdImageInterlace(im, 1) yourself. * After all, we're not really supposed to rework JPEGs and - * write them out again anyway. Lossy compression, remember? - */ + * write them out again anyway. Lossy compression, remember? */ #if 0 - gdImageInterlace (im, cinfo.progressive_mode != 0); + gdImageInterlace (im, cinfo.progressive_mode != 0); #endif - if (cinfo.out_color_space == JCS_RGB) { if (cinfo.output_components != 3) { - gd_error_ex(GD_WARNING, "gd-jpeg: error: JPEG color quantization request resulted in output_components == %d (expected 3 for RGB)", cinfo.output_components); + gd_error("gd-jpeg: error: JPEG color quantization" + " request resulted in output_components == %d" + " (expected 3 for RGB)\n", + cinfo.output_components); goto error; } channels = 3; } else if (cinfo.out_color_space == JCS_CMYK) { jpeg_saved_marker_ptr marker; - if (cinfo.output_components != 4) { - gd_error_ex(GD_WARNING, "gd-jpeg: error: JPEG color quantization request resulted in output_components == %d (expected 4 for CMYK)", cinfo.output_components); + if (cinfo.output_components != 4) { + gd_error("gd-jpeg: error: JPEG color quantization" + " request resulted in output_components == %d" + " (expected 4 for CMYK)\n", + cinfo.output_components); goto error; } channels = 4; + marker = cinfo.marker_list; while (marker) { - if ((marker->marker == (JPEG_APP0 + 14)) && (marker->data_length >= 12) && (!strncmp((const char *) marker->data, "Adobe", 5))) { + if ((marker->marker == (JPEG_APP0 + 14)) && + (marker->data_length >= 12) && + (!strncmp((const char *)marker->data, "Adobe", 5))) { inverted = 1; break; } marker = marker->next; } } else { - gd_error_ex(GD_WARNING, "gd-jpeg: error: unexpected colorspace."); + gd_error("gd-jpeg: error: unexpected colorspace\n"); goto error; } - #if BITS_IN_JSAMPLE == 12 - gd_error("gd-jpeg: error: jpeg library was compiled for 12-bit precision. This is mostly useless, because JPEGs on the web are 8-bit and such versions of the jpeg library won't read or write them. GD doesn't support these unusual images. Edit your jmorecfg.h file to specify the correct precision and completely 'make clean' and 'make install' libjpeg again. Sorry."); + gd_error_ex( + GD_ERROR, + "gd-jpeg: error: jpeg library was compiled for 12-bit\n" + "precision. This is mostly useless, because JPEGs on the web are\n" + "8-bit and such versions of the jpeg library won't read or write\n" + "them. GD doesn't support these unusual images. Edit your\n" + "jmorecfg.h file to specify the correct precision and completely\n" + "'make clean' and 'make install' libjpeg again. Sorry.\n"); goto error; #endif /* BITS_IN_JSAMPLE == 12 */ - row = safe_emalloc(cinfo.output_width * channels, sizeof(JSAMPLE), 0); - memset(row, 0, cinfo.output_width * channels * sizeof(JSAMPLE)); + row = gdCalloc(cinfo.output_width * channels, sizeof(JSAMPLE)); + if (row == 0) { + gd_error("gd-jpeg: error: unable to allocate row for" + " JPEG scanline: gdCalloc returns NULL\n"); + goto error; + } rowptr[0] = row; - if (cinfo.out_color_space == JCS_CMYK) { for (i = 0; i < cinfo.output_height; i++) { register JSAMPROW currow = row; register int *tpix = im->tpixels[i]; - nrows = jpeg_read_scanlines (&cinfo, rowptr, 1); + nrows = jpeg_read_scanlines(&cinfo, rowptr, 1); if (nrows != 1) { - gd_error_ex(GD_WARNING, "gd-jpeg: error: jpeg_read_scanlines returns %u, expected 1", nrows); + gd_error("gd-jpeg: error: jpeg_read_scanlines" + " returns %u, expected 1\n", + nrows); goto error; } for (j = 0; j < cinfo.output_width; j++, currow += 4, tpix++) { - *tpix = CMYKToRGB (currow[0], currow[1], currow[2], currow[3], inverted); + *tpix = CMYKToRGB(currow[0], currow[1], currow[2], currow[3], + inverted); } } } else { for (i = 0; i < cinfo.output_height; i++) { register JSAMPROW currow = row; register int *tpix = im->tpixels[i]; - nrows = jpeg_read_scanlines (&cinfo, rowptr, 1); + nrows = jpeg_read_scanlines(&cinfo, rowptr, 1); if (nrows != 1) { - gd_error_ex(GD_WARNING, "gd-jpeg: error: jpeg_read_scanlines returns %u, expected 1", nrows); + gd_error("gd-jpeg: error: jpeg_read_scanlines" + " returns %u, expected 1\n", + nrows); goto error; } for (j = 0; j < cinfo.output_width; j++, currow += 3, tpix++) { - *tpix = gdTrueColor (currow[0], currow[1], currow[2]); + *tpix = gdTrueColor(currow[0], currow[1], currow[2]); } } } - if (jpeg_finish_decompress (&cinfo) != TRUE) { - gd_error("gd-jpeg: warning: jpeg_finish_decompress reports suspended data source"); - } - if (!ignore_warning) { - if (cinfo.err->num_warnings > 0) { - goto error; - } + if (jpeg_finish_decompress(&cinfo) != TRUE) { + gd_error("gd-jpeg: warning: jpeg_finish_decompress" + " reports suspended data source\n"); } + /* TBB 2.0.29: we should do our best to read whatever we can read, and a + * warning is a warning. A fatal error on warnings doesn't make sense. */ +#if 0 + /* This was originally added by Truxton Fulton */ + if (cinfo.err->num_warnings > 0) + goto error; +#endif - jpeg_destroy_decompress (&cinfo); - gdFree (row); - + jpeg_destroy_decompress(&cinfo); + gdFree(row); return im; error: - jpeg_destroy_decompress (&cinfo); + jpeg_destroy_decompress(&cinfo); + if (row) { - gdFree (row); + gdFree(row); } if (im) { - gdImageDestroy (im); + gdImageDestroy(im); } + return 0; } /* A very basic conversion approach, TBB */ -static int CMYKToRGB(int c, int m, int y, int k, int inverted) -{ + +static int CMYKToRGB(int c, int m, int y, int k, int inverted) { + if (inverted) { + c = 255 - c; + m = 255 - m; + y = 255 - y; + k = 255 - k; + } + + return gdTrueColor((255 - c) * (255 - k) / 255, (255 - m) * (255 - k) / 255, + (255 - y) * (255 - k) / 255); +#if 0 if (inverted) { c = 255 - c; m = 255 - m; y = 255 - y; k = 255 - k; } - return gdTrueColor((255 - c) * (255 - k) / 255, (255 - m) * (255 - k) / 255, (255 - y) * (255 - k) / 255); + c = c * (255 - k) / 255 + k; + if (c > 255) { + c = 255; + } + if (c < 0) { + c = 0; + } + m = m * (255 - k) / 255 + k; + if (m > 255) { + m = 255; + } + if (m < 0) { + m = 0; + } + y = y * (255 - k) / 255 + k; + if (y > 255) { + y = 255; + } + if (y < 0) { + y = 0; + } + c = 255 - c; + m = 255 - m; + y = 255 - y; + return gdTrueColor (c, m, y); +#endif } /* * gdIOCtx JPEG data sources and sinks, T. Boutell * almost a simple global replace from T. Lane's stdio versions. - * */ /* Expanded data source object for gdIOCtx input */ - -typedef struct -{ - struct jpeg_source_mgr pub; /* public fields */ - - gdIOCtx *infile; /* source stream */ - unsigned char *buffer; /* start of buffer */ - boolean start_of_file; /* have we gotten any data yet? */ +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + gdIOCtx *infile; /* source stream */ + unsigned char *buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ } my_source_mgr; typedef my_source_mgr *my_src_ptr; -#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ /* * Initialize source --- called by jpeg_read_header * before any data is actually read. */ -void init_source (j_decompress_ptr cinfo) -{ - my_src_ptr src = (my_src_ptr) cinfo->src; +static void init_source(j_decompress_ptr cinfo) { + my_src_ptr src = (my_src_ptr)cinfo->src; /* We reset the empty-input-file flag for each image, * but we don't clear the input buffer. @@ -574,7 +1270,6 @@ void init_source (j_decompress_ptr cinfo) src->start_of_file = TRUE; } - /* * Fill the input buffer --- called whenever buffer is emptied. * @@ -610,21 +1305,19 @@ void init_source (j_decompress_ptr cinfo) #define END_JPEG_SEQUENCE "\r\n[*]--:END JPEG:--[*]\r\n" -boolean fill_input_buffer (j_decompress_ptr cinfo) -{ - my_src_ptr src = (my_src_ptr) cinfo->src; +static boolean fill_input_buffer(j_decompress_ptr cinfo) { + my_src_ptr src = (my_src_ptr)cinfo->src; /* 2.0.12: signed size. Thanks to Geert Jansen */ ssize_t nbytes = 0; - - /* ssize_t got; */ - /* char *s; */ memset(src->buffer, 0, INPUT_BUF_SIZE); while (nbytes < INPUT_BUF_SIZE) { - int got = gdGetBuf(src->buffer + nbytes, INPUT_BUF_SIZE - nbytes, src->infile); + int got = gdGetBuf(src->buffer + nbytes, INPUT_BUF_SIZE - nbytes, + src->infile); - if (got == EOF || got == 0) { - /* EOF or error. If we got any data, don't worry about it. If we didn't, then this is unexpected. */ + if ((got == EOF) || (got == 0)) { + /* EOF or error. If we got any data, don't worry about it. + * If we didn't, then this is unexpected. */ if (!nbytes) { nbytes = -1; } @@ -634,13 +1327,14 @@ boolean fill_input_buffer (j_decompress_ptr cinfo) } if (nbytes <= 0) { - if (src->start_of_file) { /* Treat empty input file as fatal error */ - ERREXIT (cinfo, JERR_INPUT_EMPTY); + if (src->start_of_file) { + /* Treat empty input file as fatal error */ + ERREXIT(cinfo, JERR_INPUT_EMPTY); } - WARNMS (cinfo, JWRN_JPEG_EOF); + WARNMS(cinfo, JWRN_JPEG_EOF); /* Insert a fake EOI marker */ - src->buffer[0] = (unsigned char) 0xFF; - src->buffer[1] = (unsigned char) JPEG_EOI; + src->buffer[0] = (unsigned char)0xFF; + src->buffer[1] = (unsigned char)JPEG_EOI; nbytes = 2; } @@ -651,7 +1345,6 @@ boolean fill_input_buffer (j_decompress_ptr cinfo) return TRUE; } - /* * Skip data --- used to skip over a potentially large amount of * uninteresting data (such as an APPn marker). @@ -664,27 +1357,25 @@ boolean fill_input_buffer (j_decompress_ptr cinfo) * buffer is the application writer's problem. */ -void skip_input_data (j_decompress_ptr cinfo, long num_bytes) -{ - my_src_ptr src = (my_src_ptr) cinfo->src; +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + my_src_ptr src = (my_src_ptr)cinfo->src; /* Just a dumb implementation for now. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ if (num_bytes > 0) { - while (num_bytes > (long) src->pub.bytes_in_buffer) { - num_bytes -= (long) src->pub.bytes_in_buffer; - (void) fill_input_buffer (cinfo); + while (num_bytes > (long)src->pub.bytes_in_buffer) { + num_bytes -= (long)src->pub.bytes_in_buffer; + (void)fill_input_buffer(cinfo); /* note we assume that fill_input_buffer will never return FALSE, * so suspension need not be handled. */ } - src->pub.next_input_byte += (size_t) num_bytes; - src->pub.bytes_in_buffer -= (size_t) num_bytes; + src->pub.next_input_byte += (size_t)num_bytes; + src->pub.bytes_in_buffer -= (size_t)num_bytes; } } - /* * An additional method that can be provided by data source modules is the * resync_to_restart method for error recovery in the presence of RST markers. @@ -693,7 +1384,6 @@ void skip_input_data (j_decompress_ptr cinfo, long num_bytes) * is possible. */ - /* * Terminate source --- called by jpeg_finish_decompress * after all data has been read. Often a no-op. @@ -702,15 +1392,7 @@ void skip_input_data (j_decompress_ptr cinfo, long num_bytes) * application must deal with any cleanup that should happen even * for error exit. */ - -void term_source (j_decompress_ptr cinfo) -{ -#if 0 - * never used */ - my_src_ptr src = (my_src_ptr) cinfo->src; -#endif -} - +static void term_source(j_decompress_ptr cinfo) { (void)cinfo; } /* * Prepare for input from a gdIOCtx stream. @@ -718,8 +1400,7 @@ void term_source (j_decompress_ptr cinfo) * for closing it after finishing decompression. */ -void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx * infile) -{ +static void jpeg_gdIOCtx_src(j_decompress_ptr cinfo, gdIOCtx *infile) { my_src_ptr src; /* The source object and input buffer are made permanent so that a series @@ -729,55 +1410,57 @@ void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx * infile) * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ - if (cinfo->src == NULL) { /* first time for this JPEG object? */ - cinfo->src = (struct jpeg_source_mgr *) - (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (my_source_mgr)); - src = (my_src_ptr) cinfo->src; - src->buffer = (unsigned char *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof (unsigned char)); - + if (cinfo->src == NULL) { + /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *)(*cinfo->mem->alloc_small)( + (j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(my_source_mgr)); + src = (my_src_ptr)cinfo->src; + src->buffer = (unsigned char *)(*cinfo->mem->alloc_small)( + (j_common_ptr)cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * sizeof(unsigned char)); } - src = (my_src_ptr) cinfo->src; + src = (my_src_ptr)cinfo->src; src->pub.init_source = init_source; src->pub.fill_input_buffer = fill_input_buffer; src->pub.skip_input_data = skip_input_data; - src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.resync_to_restart = + jpeg_resync_to_restart; /* use default method */ src->pub.term_source = term_source; src->infile = infile; - src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ - src->pub.next_input_byte = NULL; /* until buffer loaded */ + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ } /* Expanded data destination object for stdio output */ - -typedef struct -{ +typedef struct { struct jpeg_destination_mgr pub; /* public fields */ - gdIOCtx *outfile; /* target stream */ - unsigned char *buffer; /* start of buffer */ + gdIOCtx *outfile; /* target stream */ + unsigned char *buffer; /* start of buffer */ } my_destination_mgr; typedef my_destination_mgr *my_dest_ptr; -#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ /* * Initialize destination --- called by jpeg_start_compress * before any data is actually written. */ -void init_destination (j_compress_ptr cinfo) -{ - my_dest_ptr dest = (my_dest_ptr) cinfo->dest; +static void init_destination(j_compress_ptr cinfo) { + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; - /* Allocate the output buffer --- it will be released when done with image */ - dest->buffer = (unsigned char *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof (unsigned char)); + /* Allocate the output buffer --- it will be released when done with image + */ + dest->buffer = (unsigned char *)(*cinfo->mem->alloc_small)( + (j_common_ptr)cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * sizeof(unsigned char)); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; } - /* * Empty the output buffer --- called whenever buffer fills up. * @@ -801,12 +1484,12 @@ void init_destination (j_compress_ptr cinfo) * write it out when emptying the buffer externally. */ -boolean empty_output_buffer (j_compress_ptr cinfo) -{ - my_dest_ptr dest = (my_dest_ptr) cinfo->dest; +static boolean empty_output_buffer(j_compress_ptr cinfo) { + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; - if (gdPutBuf (dest->buffer, OUTPUT_BUF_SIZE, dest->outfile) != (size_t) OUTPUT_BUF_SIZE) { - ERREXIT (cinfo, JERR_FILE_WRITE); + if (gdPutBuf(dest->buffer, OUTPUT_BUF_SIZE, dest->outfile) != + (size_t)OUTPUT_BUF_SIZE) { + ERREXIT(cinfo, JERR_FILE_WRITE); } dest->pub.next_output_byte = dest->buffer; @@ -815,7 +1498,6 @@ boolean empty_output_buffer (j_compress_ptr cinfo) return TRUE; } - /* * Terminate destination --- called by jpeg_finish_compress * after all data has been written. Usually needs to flush buffer. @@ -825,26 +1507,25 @@ boolean empty_output_buffer (j_compress_ptr cinfo) * for error exit. */ -void term_destination (j_compress_ptr cinfo) -{ - my_dest_ptr dest = (my_dest_ptr) cinfo->dest; +static void term_destination(j_compress_ptr cinfo) { + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; /* Write any data remaining in the buffer */ - if (datacount > 0 && ((size_t)gdPutBuf (dest->buffer, datacount, dest->outfile) != datacount)) { - ERREXIT (cinfo, JERR_FILE_WRITE); + if (datacount > 0) { + if ((size_t)gdPutBuf(dest->buffer, datacount, dest->outfile) != datacount) { + ERREXIT(cinfo, JERR_FILE_WRITE); + } } } - /* * Prepare for output to a stdio stream. * The caller must have already opened the stream, and is responsible * for closing it after finishing compression. */ -void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile) -{ +static void jpeg_gdIOCtx_dest(j_compress_ptr cinfo, gdIOCtx *outfile) { my_dest_ptr dest; /* The destination object is made permanent so that multiple JPEG images @@ -853,15 +1534,161 @@ void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile) * manager serially with the same JPEG object, because their private object * sizes may be different. Caveat programmer. */ - if (cinfo->dest == NULL) { /* first time for this JPEG object? */ - cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (my_destination_mgr)); + if (cinfo->dest == NULL) { + /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)( + (j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); } - dest = (my_dest_ptr) cinfo->dest; + dest = (my_dest_ptr)cinfo->dest; dest->pub.init_destination = init_destination; dest->pub.empty_output_buffer = empty_output_buffer; dest->pub.term_destination = term_destination; dest->outfile = outfile; } +#else /* !HAVE_LIBJPEG */ + +static void _noJpegError(void) { + gd_error("JPEG image support has been disabled\n"); +} + +BGD_DECLARE(void) gdImageJpeg(gdImagePtr im, FILE *outFile, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + ARG_NOT_USED(quality); + _noJpegError(); +} + +BGD_DECLARE(void *) gdImageJpegPtr(gdImagePtr im, int *size, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(quality); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(void *) +gdImageJpegPtrWithMetadata(gdImagePtr im, int *size, int quality, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(quality); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + +void *gdImageJpegPtrWithMetadataNoSubsampling(gdImagePtr im, int *size, + int quality, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(quality); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(void) gdImageJpegCtx(gdImagePtr im, gdIOCtx *outfile, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(quality); + _noJpegError(); +} + +BGD_DECLARE(void) +gdImageJpegCtxWithMetadata(gdImagePtr im, gdIOCtx *outfile, int quality, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(quality); + ARG_NOT_USED(metadata); + _noJpegError(); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpeg(FILE *inFile) { + ARG_NOT_USED(inFile); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegEx(FILE *inFile, int ignore_warning) { + ARG_NOT_USED(inFile); + ARG_NOT_USED(ignore_warning); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrEx(int size, void *data, int ignore_warning) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + ARG_NOT_USED(ignore_warning); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrWithMetadata(int size, void *data, + gdImageMetadata *metadata) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegPtrExWithMetadata(int size, void *data, int ignore_warning, + gdImageMetadata *metadata) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + ARG_NOT_USED(ignore_warning); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJpegCtx(gdIOCtx *infile) { + ARG_NOT_USED(infile); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxEx(gdIOCtx *infile, int ignore_warning) { + ARG_NOT_USED(infile); + ARG_NOT_USED(ignore_warning); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxWithMetadata(gdIOCtx *infile, + gdImageMetadata *metadata) { + ARG_NOT_USED(infile); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromJpegCtxExWithMetadata(gdIOCtx *infile, int ignore_warning, + gdImageMetadata *metadata) { + ARG_NOT_USED(infile); + ARG_NOT_USED(ignore_warning); + ARG_NOT_USED(metadata); + _noJpegError(); + return NULL; +} + #endif /* HAVE_LIBJPEG */ diff --git a/ext/gd/libgd/gd_jxl.c b/ext/gd/libgd/gd_jxl.c new file mode 100644 index 000000000000..1bfa821c674e --- /dev/null +++ b/ext/gd/libgd/gd_jxl.c @@ -0,0 +1,1484 @@ +/** + * File: JPEG XL IO + * + * Read and write JPEG XL images. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "gd.h" +#include "gd_errors.h" +#include "gd_intern.h" +#include "gdhelpers.h" +#include +#include +#include +#include + +#ifdef HAVE_LIBJXL +#include +#include +#include +#include + +#define GD_JXL_ALLOC_STEP (4 * 1024) + +/* ---- Internal helpers ---- */ + +/* Slurp gdIOCtx into dynamic buffer (pattern from gd_webp.c) */ +static uint8_t *JxlReadCtxData(gdIOCtx *infile, size_t *size) { + uint8_t *filedata = NULL, *temp, *read; + ssize_t n; + + *size = 0; + do { + temp = gdRealloc(filedata, *size + GD_JXL_ALLOC_STEP); + if (temp == NULL) { + gdFree(filedata); + gd_error("JXL decode: realloc failed"); + return NULL; + } + filedata = temp; + read = temp + *size; + n = gdGetBuf(read, GD_JXL_ALLOC_STEP, infile); + if (n > 0 && n != EOF) { + *size += n; + } + } while (n > 0 && n != EOF); + + if (*size == 0) { + gdFree(filedata); + return NULL; + } + + return filedata; +} + +/* JXL alpha (0-255) -> gd alpha (0-127, 0=opaque) */ +static int JxlAlphaJxlToGd(uint8_t jxl_alpha) { + if (jxl_alpha == 0) { + return gdAlphaTransparent; + } + return gdAlphaMax - (jxl_alpha >> 1); +} + +/* gd alpha (0-127) -> JXL alpha (0-255) */ +static uint8_t JxlAlphaGdToJxl(int gd_alpha) { + if (gd_alpha == gdAlphaTransparent) { + return 0; + } + return (uint8_t)((gdAlphaMax - gd_alpha) << 1); +} + +static int JxlImageHasAlpha(gdImagePtr im) { + int x, y; + + if (im == NULL) { + return 0; + } + + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + if (gdTrueColorGetAlpha(im->tpixels[y][x]) != gdAlphaOpaque) { + return 1; + } + } + } + + return 0; +} + +static int JxlDurationToMs(uint32_t duration, + const JxlAnimationHeader *animation) { + if (animation == NULL || animation->tps_numerator == 0) { + return 0; + } + + return (int)((uint64_t)duration * 1000 * animation->tps_denominator / + animation->tps_numerator); +} + +/* Build gdImagePtr from RGBA u8 buffer */ +static gdImagePtr JxlImageFromRGBA(const uint8_t *rgba, int width, int height, + int has_alpha) { + gdImagePtr im; + const uint8_t *p; + int x, y; + + if (rgba == NULL || width <= 0 || height <= 0) { + return NULL; + } + im = gdImageCreateTrueColor(width, height); + if (im == NULL) { + return NULL; + } + gdImageAlphaBlending(im, 0); + gdImageSaveAlpha(im, has_alpha); + for (y = 0, p = rgba; y < height; y++) { + for (x = 0; x < width; x++) { + uint8_t r = *(p++); + uint8_t g = *(p++); + uint8_t b = *(p++); + uint8_t a = *(p++); + im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, JxlAlphaJxlToGd(a)); + } + } + return im; +} + +/* Extract RGBA u8 buffer from gdImagePtr */ +static int JxlImageToRGBA(gdImagePtr im, uint8_t **rgba, int *has_alpha) { + uint8_t *p; + int x, y; + int w, h; + + *rgba = NULL; + *has_alpha = 0; + + if (im == NULL) { + return 0; + } + + /* Convert palette to truecolor if needed */ + if (!gdImageTrueColor(im)) { + gdImagePaletteToTrueColor(im); + } + + w = gdImageSX(im); + h = gdImageSY(im); + + /* Check for alpha presence */ + if (gdImageGetTransparent(im) != -1 || JxlImageHasAlpha(im)) { + *has_alpha = 1; + } + + /* Overflow checks */ + if (overflow2(w, 4)) { + return 0; + } + if (overflow2(w * 4, h)) { + return 0; + } + + *rgba = (uint8_t *)gdMalloc((size_t)w * 4 * (size_t)h); + if (*rgba == NULL) { + return 0; + } + + p = *rgba; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + int c = im->tpixels[y][x]; + *(p++) = gdTrueColorGetRed(c); + *(p++) = gdTrueColorGetGreen(c); + *(p++) = gdTrueColorGetBlue(c); + *(p++) = JxlAlphaGdToJxl(gdTrueColorGetAlpha(c)); + } + } + return 1; +} + +static uint8_t *JxlRGBFromRGBA(const uint8_t *rgba, int width, int height) { + uint8_t *rgb, *dst; + const uint8_t *src; + int x, y; + + if (overflow2(width, 3) || overflow2(width * 3, height)) { + return NULL; + } + + rgb = (uint8_t *)gdMalloc((size_t)width * 3 * (size_t)height); + if (rgb == NULL) { + return NULL; + } + + src = rgba; + dst = rgb; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + *(dst++) = *(src++); + *(dst++) = *(src++); + *(dst++) = *(src++); + src++; + } + } + + return rgb; +} + +/* ---- Still-image decode ---- */ + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxl(FILE *inFile) { + gdImagePtr im; + gdIOCtx *in = gdNewFileCtx(inFile); + if (!in) { + return 0; + } + im = gdImageCreateFromJxlCtx(in); + in->gd_free(in); + return im; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlPtr(int size, void *data) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return 0; + } + im = gdImageCreateFromJxlCtx(in); + in->gd_free(in); + return im; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlCtx(gdIOCtxPtr inCtx) { + size_t buf_len = 0; + uint8_t *buf = NULL; + JxlDecoder *dec = NULL; + JxlBasicInfo info = {0}; + uint8_t *pixels = NULL; + gdImagePtr im = NULL; + int has_alpha = 0; + int have_basic_info = 0; + + if (inCtx == NULL) { + return NULL; + } + + buf = JxlReadCtxData(inCtx, &buf_len); + if (buf == NULL) { + return NULL; + } + + dec = JxlDecoderCreate(NULL); + if (dec == NULL) { + gd_error("gdImageCreateFromJxl: JxlDecoderCreate failed"); + gdFree(buf); + return NULL; + } + + JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | + JXL_DEC_FULL_IMAGE); + + /* Attach CMS for color space conversion to sRGB */ + JxlDecoderSetCms(dec, *JxlGetDefaultCms()); + + /* Set desired output color profile (sRGB) */ + JxlColorEncoding srgb_enc; + JxlColorEncodingSetToSRGB(&srgb_enc, JXL_FALSE); + JxlDecoderSetPreferredColorProfile(dec, &srgb_enc); + + JxlDecoderSetInput(dec, buf, buf_len); + JxlDecoderCloseInput(dec); + + for (;;) { + JxlDecoderStatus status = JxlDecoderProcessInput(dec); + + if (status == JXL_DEC_BASIC_INFO) { + status = JxlDecoderGetBasicInfo(dec, &info); + if (status != JXL_DEC_SUCCESS) { + gd_error("gdImageCreateFromJxl: failed to get basic info"); + goto decode_fail; + } + have_basic_info = 1; + has_alpha = (info.alpha_bits > 0); + } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { + JxlPixelFormat fmt = {.num_channels = 4, /* RGBA */ + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + .align = 0}; + size_t pixels_size; + + if (!have_basic_info) { + gd_error("gdImageCreateFromJxl: missing basic info"); + goto decode_fail; + } + if (overflow2((int)info.xsize, (int)info.ysize) || + overflow2((int)info.xsize * (int)info.ysize, 4)) { + gd_error("gdImageCreateFromJxl: image dimensions overflow"); + goto decode_fail; + } + + pixels_size = (size_t)info.xsize * (size_t)info.ysize * 4; + pixels = (uint8_t *)gdMalloc(pixels_size); + if (pixels == NULL) { + gd_error( + "gdImageCreateFromJxl: pixel buffer allocation failed"); + goto decode_fail; + } + + if (JxlDecoderSetImageOutBuffer(dec, &fmt, pixels, + pixels_size) != JXL_DEC_SUCCESS) { + gd_error("gdImageCreateFromJxl: failed to set output buffer"); + goto decode_fail; + } + } else if (status == JXL_DEC_FULL_IMAGE) { + if (!have_basic_info || pixels == NULL) { + gd_error("gdImageCreateFromJxl: incomplete decoded image"); + goto decode_fail; + } + break; /* pixels buffer is populated */ + } else if (status == JXL_DEC_SUCCESS || status == JXL_DEC_ERROR) { + gd_error("gdImageCreateFromJxl: decoder error"); + goto decode_fail; + } + } + + /* Build gdImagePtr from RGBA buffer */ + im = JxlImageFromRGBA(pixels, (int)info.xsize, (int)info.ysize, has_alpha); + if (!im) { + gd_error("gdImageCreateFromJxl: JxlImageFromRGBA failed"); + goto decode_fail; + } + + /* Success path - pixels consumed by im */ + gdFree(pixels); + pixels = NULL; + gdFree(buf); + buf = NULL; + JxlDecoderDestroy(dec); + return im; + +decode_fail: + if (pixels) + gdFree(pixels); + if (buf) + gdFree(buf); + if (dec) + JxlDecoderDestroy(dec); + return NULL; +} + +/* ---- Still-image encode ---- */ + +static int _gdImageJxlCtxEx(gdImagePtr im, gdIOCtx *outfile, int lossless, + float distance, int effort) { + JxlEncoder *enc = NULL; + JxlEncoderFrameSettings *frame_opts = NULL; + uint8_t *pixels = NULL; + int has_alpha = 0; + uint8_t outbuf[65536]; + int w, h; + int ret = 1; + + if (im == NULL) { + return 1; + } + + w = gdImageSX(im); + h = gdImageSY(im); + + /* Extract RGBA pixels */ + if (!JxlImageToRGBA(im, &pixels, &has_alpha)) { + gd_error("gdImageJxl: pixel extraction failed"); + return 1; + } + + enc = JxlEncoderCreate(NULL); + if (enc == NULL) { + gd_error("gdImageJxl: JxlEncoderCreate failed"); + gdFree(pixels); + return 1; + } + + /* Set basic info */ + JxlBasicInfo info; + JxlEncoderInitBasicInfo(&info); + info.xsize = w; + info.ysize = h; + info.bits_per_sample = 8; + info.exponent_bits_per_sample = 0; + info.num_color_channels = 3; + info.num_extra_channels = has_alpha ? 1 : 0; + info.alpha_bits = has_alpha ? 8 : 0; + info.alpha_exponent_bits = 0; + info.alpha_premultiplied = JXL_FALSE; + info.uses_original_profile = lossless ? JXL_TRUE : JXL_FALSE; + + if (JxlEncoderSetBasicInfo(enc, &info) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderSetBasicInfo failed"); + goto encode_fail; + } + + /* Set color encoding (sRGB) */ + JxlColorEncoding srgb_enc; + JxlColorEncodingSetToSRGB(&srgb_enc, JXL_FALSE); + if (JxlEncoderSetColorEncoding(enc, &srgb_enc) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderSetColorEncoding failed"); + goto encode_fail; + } + + /* Configure frame options */ + frame_opts = JxlEncoderFrameSettingsCreate(enc, NULL); + if (frame_opts == NULL) { + gd_error("gdImageJxl: JxlEncoderFrameSettingsCreate failed"); + goto encode_fail; + } + + if (lossless) { + if (JxlEncoderSetFrameLossless(frame_opts, JXL_TRUE) != + JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderSetFrameLossless failed"); + goto encode_fail; + } + } else { + if (JxlEncoderSetFrameDistance(frame_opts, distance) != + JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderSetFrameDistance failed"); + goto encode_fail; + } + } + + if (JxlEncoderFrameSettingsSetOption(frame_opts, + JXL_ENC_FRAME_SETTING_EFFORT, + effort) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderFrameSettingsSetOption effort failed"); + goto encode_fail; + } + + /* Add image frame */ + uint8_t *frame_pixels = pixels; + size_t frame_pixels_size = (size_t)w * (size_t)h * 4; + JxlPixelFormat fmt = {.num_channels = has_alpha ? 4u : 3u, + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + .align = 0}; + + if (!has_alpha) { + frame_pixels = JxlRGBFromRGBA(pixels, w, h); + if (frame_pixels == NULL) { + gd_error("gdImageJxl: RGB buffer allocation failed"); + goto encode_fail; + } + frame_pixels_size = (size_t)w * (size_t)h * 3; + } + + if (JxlEncoderAddImageFrame(frame_opts, &fmt, frame_pixels, + frame_pixels_size) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxl: JxlEncoderAddImageFrame failed"); + if (frame_pixels != pixels) + gdFree(frame_pixels); + goto encode_fail; + } + if (frame_pixels != pixels) + gdFree(frame_pixels); + + JxlEncoderCloseInput(enc); + for (;;) { + uint8_t *next_out = outbuf; + size_t avail = sizeof(outbuf); + JxlEncoderStatus st = JxlEncoderProcessOutput(enc, &next_out, &avail); + + size_t written = sizeof(outbuf) - avail; + if (written > 0) { + if (gdPutBuf(outbuf, (int)written, outfile) != (int)written) { + gd_error("gdImageJxl: write error"); + goto encode_fail; + } + } + + if (st == JXL_ENC_SUCCESS) { + ret = 0; + break; + } + if (st != JXL_ENC_NEED_MORE_OUTPUT) { + gd_error("gdImageJxl: encoder error"); + goto encode_fail; + } + } + +encode_fail: + // frame_opts is owned by enc, so no separate destroy needed + if (enc) + JxlEncoderDestroy(enc); + if (pixels) + gdFree(pixels); + return ret; +} + +BGD_DECLARE(void) gdImageJxl(gdImagePtr im, FILE *outFile) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return; + } + _gdImageJxlCtxEx(im, out, 0, 1.0f, 7); + out->gd_free(out); +} + +BGD_DECLARE(void) +gdImageJxlEx(gdImagePtr im, FILE *outFile, int lossless, float distance, + int effort) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return; + } + _gdImageJxlCtxEx(im, out, lossless, distance, effort); + out->gd_free(out); +} + +BGD_DECLARE(void *) gdImageJxlPtr(gdImagePtr im, int *size) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + if (_gdImageJxlCtxEx(im, out, 0, 1.0f, 7)) { + rv = NULL; + } else { + rv = gdDPExtractData(out, size); + } + out->gd_free(out); + return rv; +} + +BGD_DECLARE(void *) +gdImageJxlPtrEx(gdImagePtr im, int *size, int lossless, float distance, + int effort) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + if (_gdImageJxlCtxEx(im, out, lossless, distance, effort)) { + rv = NULL; + } else { + rv = gdDPExtractData(out, size); + } + out->gd_free(out); + return rv; +} + +BGD_DECLARE(void) gdImageJxlCtx(gdImagePtr im, gdIOCtxPtr outfile) { + _gdImageJxlCtxEx(im, outfile, 0, 1.0f, 7); +} + +BGD_DECLARE(void) +gdImageJxlCtxEx(gdImagePtr im, gdIOCtxPtr outfile, int lossless, float distance, + int effort) { + _gdImageJxlCtxEx(im, outfile, lossless, distance, effort); +} + +/* ---- Animation structures ---- */ + +typedef struct gdJxlAnim { + JxlEncoder *enc; + JxlEncoderFrameSettings *frame_opts; + gdIOCtxPtr ctx; + uint32_t width; + uint32_t height; + int has_alpha; + int ownsCtx; + int memoryWriter; + int finalized; + int timestamp; +} gdJxlAnim; + +typedef struct gdJxlAnimReader { + JxlDecoder *dec; + uint8_t *buf; + size_t buf_len; + JxlBasicInfo info; + int coalesced; + int done; + int last_frame_seen; +} gdJxlAnimReader; + +/* ---- Animation write ---- */ + +static int JxlAnimDrainEncoder(gdJxlAnim *anim) { + uint8_t outbuf[65536]; + for (;;) { + uint8_t *next_out = outbuf; + size_t avail = sizeof(outbuf); + JxlEncoderStatus st = + JxlEncoderProcessOutput(anim->enc, &next_out, &avail); + + size_t written = sizeof(outbuf) - avail; + if (written > 0) { + if (gdPutBuf(outbuf, (int)written, anim->ctx) != (int)written) { + gd_error("gdImageJxlAnim: write error"); + return 0; + } + } + + if (st == JXL_ENC_SUCCESS) { + return 1; + } + if (st != JXL_ENC_NEED_MORE_OUTPUT) { + gd_error("gdImageJxlAnim: encoder error"); + return 0; + } + } +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBegin(FILE *outFile, int width, int height, int lossless, + float distance, int effort) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return NULL; + } + gdJxlAnimPtr anim = + gdImageJxlAnimBeginCtx(out, width, height, lossless, distance, effort); + if (anim) { + anim->ownsCtx = 1; + } else { + out->gd_free(out); + } + return anim; +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBeginCtx(gdIOCtxPtr outCtx, int width, int height, int lossless, + float distance, int effort) { + gdJxlAnimPtr anim; + JxlBasicInfo info; + + if (outCtx == NULL || width <= 0 || height <= 0) { + return NULL; + } + + anim = (gdJxlAnimPtr)gdCalloc(1, sizeof(struct gdJxlAnim)); + if (anim == NULL) { + return NULL; + } + + anim->enc = JxlEncoderCreate(NULL); + if (anim->enc == NULL) { + gd_error("gdImageJxlAnimBegin: JxlEncoderCreate failed"); + gdFree(anim); + return NULL; + } + + anim->ctx = outCtx; + anim->width = width; + anim->height = height; + anim->ownsCtx = 0; + anim->memoryWriter = 0; + anim->finalized = 0; + anim->timestamp = 0; + + /* Set basic info for animation */ + JxlEncoderInitBasicInfo(&info); + info.xsize = width; + info.ysize = height; + info.bits_per_sample = 8; + info.exponent_bits_per_sample = 0; + info.num_color_channels = 3; + info.num_extra_channels = 1; + info.alpha_bits = 8; + info.alpha_exponent_bits = 0; + info.alpha_premultiplied = JXL_FALSE; + info.uses_original_profile = lossless ? JXL_TRUE : JXL_FALSE; + info.have_animation = JXL_TRUE; + info.animation.tps_numerator = 1000; /* ms == ticks */ + info.animation.tps_denominator = 1; + info.animation.num_loops = 0; /* loop forever */ + + if (JxlEncoderSetBasicInfo(anim->enc, &info) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimBegin: JxlEncoderSetBasicInfo failed"); + JxlEncoderDestroy(anim->enc); + gdFree(anim); + return NULL; + } + + /* Set color encoding (sRGB) */ + JxlColorEncoding srgb_enc; + JxlColorEncodingSetToSRGB(&srgb_enc, JXL_FALSE); + if (JxlEncoderSetColorEncoding(anim->enc, &srgb_enc) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimBegin: JxlEncoderSetColorEncoding failed"); + JxlEncoderDestroy(anim->enc); + gdFree(anim); + return NULL; + } + + /* Create frame settings once, reuse for all frames */ + anim->frame_opts = JxlEncoderFrameSettingsCreate(anim->enc, NULL); + if (anim->frame_opts == NULL) { + gd_error("gdImageJxlAnimBegin: JxlEncoderFrameSettingsCreate failed"); + JxlEncoderDestroy(anim->enc); + gdFree(anim); + return NULL; + } + + if (lossless) { + if (JxlEncoderSetFrameLossless(anim->frame_opts, JXL_TRUE) != + JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimBegin: JxlEncoderSetFrameLossless failed"); + goto anim_begin_fail; + } + } else { + if (JxlEncoderSetFrameDistance(anim->frame_opts, distance) != + JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimBegin: JxlEncoderSetFrameDistance failed"); + goto anim_begin_fail; + } + } + + if (JxlEncoderFrameSettingsSetOption(anim->frame_opts, + JXL_ENC_FRAME_SETTING_EFFORT, + effort) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimBegin: JxlEncoderFrameSettingsSetOption effort " + "failed"); + goto anim_begin_fail; + } + + return anim; + +anim_begin_fail: + // anim->frame_opts is owned by anim->enc, so no separate destroy needed + if (anim->enc) + JxlEncoderDestroy(anim->enc); + gdFree(anim); + return NULL; +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBeginPtr(int width, int height, int lossless, float distance, + int effort) { + gdIOCtx *out; + gdJxlAnimPtr anim; + + out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + + anim = + gdImageJxlAnimBeginCtx(out, width, height, lossless, distance, effort); + if (anim == NULL) { + out->gd_free(out); + return NULL; + } + + anim->ownsCtx = 1; + anim->memoryWriter = 1; + return anim; +} + +BGD_DECLARE(int) +gdImageJxlAnimAddFrame(gdJxlAnimPtr anim, gdImagePtr im, int delay_ms) { + JxlFrameHeader fhdr; + JxlPixelFormat fmt; + uint8_t *pixels = NULL; + int has_alpha = 0; + int w, h; + + if (anim == NULL || im == NULL || delay_ms < 0 || anim->finalized) { + return 0; + } + + if (!gdImageTrueColor(im)) { + gd_error("Palette image not supported by JXL animation"); + return 0; + } + + w = gdImageSX(im); + h = gdImageSY(im); + + if (w != (int)anim->width || h != (int)anim->height) { + gd_error("gdImageJxlAnimAddFrame: frame size must match canvas size"); + return 0; + } + + if (!JxlImageToRGBA(im, &pixels, &has_alpha)) { + gd_error("gdImageJxlAnimAddFrame: pixel extraction failed"); + return 0; + } + + /* Update alpha info for this frame if needed */ + if (has_alpha && anim->has_alpha == 0) { + anim->has_alpha = 1; + } + + /* Set frame header - duration only, REPLACE blend, full canvas */ + JxlEncoderInitFrameHeader(&fhdr); + fhdr.duration = (uint32_t)delay_ms; /* ticks == ms */ + + if (JxlEncoderSetFrameHeader(anim->frame_opts, &fhdr) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimAddFrame: JxlEncoderSetFrameHeader failed"); + gdFree(pixels); + return 0; + } + + fmt.num_channels = 4; + fmt.data_type = JXL_TYPE_UINT8; + fmt.endianness = JXL_NATIVE_ENDIAN; + fmt.align = 0; + + if (JxlEncoderAddImageFrame(anim->frame_opts, &fmt, pixels, + (size_t)w * (size_t)h * 4) != JXL_ENC_SUCCESS) { + gd_error("gdImageJxlAnimAddFrame: JxlEncoderAddImageFrame failed"); + gdFree(pixels); + return 0; + } + + gdFree(pixels); + pixels = NULL; + + anim->timestamp += delay_ms; + return 1; +} + +BGD_DECLARE(int) gdImageJxlAnimEnd(gdJxlAnimPtr anim) { + int ret = 0; + + if (anim == NULL) { + return 0; + } + + if (!anim->finalized) { + JxlEncoderCloseInput(anim->enc); + + if (!JxlAnimDrainEncoder(anim)) { + goto anim_end_cleanup; + } + + anim->finalized = 1; + } + + ret = 1; + +anim_end_cleanup: + // anim->frame_opts is owned by anim->enc, so no separate destroy needed + if (anim->enc) + JxlEncoderDestroy(anim->enc); + if (anim->ownsCtx && anim->ctx) + anim->ctx->gd_free(anim->ctx); + gdFree(anim); + return ret; +} + +BGD_DECLARE(void *) gdImageJxlAnimEndPtr(gdJxlAnimPtr anim, int *size) { + void *rv = NULL; + + if (size != NULL) { + *size = 0; + } + if (anim == NULL || !anim->memoryWriter) { + if (anim != NULL) { + gdImageJxlAnimEnd(anim); + } + return NULL; + } + + if (!anim->finalized) { + JxlEncoderCloseInput(anim->enc); + + if (!JxlAnimDrainEncoder(anim)) { + goto anim_end_ptr_cleanup; + } + + anim->finalized = 1; + } + + rv = gdDPExtractData(anim->ctx, size); + +anim_end_ptr_cleanup: + if (anim->enc) + JxlEncoderDestroy(anim->enc); + if (anim->ctx) + anim->ctx->gd_free(anim->ctx); + gdFree(anim); + return rv; +} + +/* ---- Animation read (coalesced & raw) ---- */ + +static gdJxlAnimReaderPtr _gdImageJxlAnimReaderCreateCtx(gdIOCtxPtr inCtx, + int coalesced) { + size_t buf_len = 0; + uint8_t *buf = NULL; + JxlDecoder *dec = NULL; + gdJxlAnimReaderPtr reader = NULL; + + if (inCtx == NULL) { + return NULL; + } + + buf = JxlReadCtxData(inCtx, &buf_len); + if (buf == NULL) { + return NULL; + } + + dec = JxlDecoderCreate(NULL); + if (dec == NULL) { + gd_error("gdImageJxlAnimReaderCreate: JxlDecoderCreate failed"); + gdFree(buf); + return NULL; + } + + reader = (gdJxlAnimReaderPtr)gdCalloc(1, sizeof(struct gdJxlAnimReader)); + if (reader == NULL) { + JxlDecoderDestroy(dec); + gdFree(buf); + return NULL; + } + + if (coalesced) { + JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | + JXL_DEC_COLOR_ENCODING | + JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE); + JxlDecoderSetCoalescing(dec, JXL_TRUE); + } else { + JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | + JXL_DEC_COLOR_ENCODING | + JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE); + JxlDecoderSetCoalescing(dec, JXL_FALSE); + } + + /* Attach CMS for color space conversion to sRGB */ + JxlDecoderSetCms(dec, *JxlGetDefaultCms()); + + /* Set desired output color profile (sRGB) */ + JxlColorEncoding srgb_enc; + JxlColorEncodingSetToSRGB(&srgb_enc, JXL_FALSE); + JxlDecoderSetPreferredColorProfile(dec, &srgb_enc); + + JxlDecoderSetInput(dec, buf, buf_len); + JxlDecoderCloseInput(dec); + + /* Process until BASIC_INFO to get dimensions */ + for (;;) { + JxlDecoderStatus status = JxlDecoderProcessInput(dec); + if (status == JXL_DEC_BASIC_INFO) { + if (JxlDecoderGetBasicInfo(dec, &reader->info) != + JXL_DEC_SUCCESS) { + gd_error("gdImageJxlAnimReaderCreate: failed to get basic info"); + JxlDecoderDestroy(dec); + gdFree(buf); + gdFree(reader); + return NULL; + } + break; + } + if (status == JXL_DEC_ERROR || status == JXL_DEC_SUCCESS) { + gd_error("gdImageJxlAnimReaderCreate: failed to get basic info"); + JxlDecoderDestroy(dec); + gdFree(buf); + gdFree(reader); + return NULL; + } + } + + reader->dec = dec; + reader->buf = buf; + reader->buf_len = buf_len; + reader->coalesced = coalesced; + reader->done = 0; + reader->last_frame_seen = 0; + + return reader; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreate(FILE *inFile) { + gdIOCtx *in = gdNewFileCtx(inFile); + if (!in) { + return NULL; + } + gdJxlAnimReaderPtr reader = _gdImageJxlAnimReaderCreateCtx(in, JXL_TRUE); + in->gd_free(in); + return reader; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreatePtr(int size, void *data) { + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return NULL; + } + gdJxlAnimReaderPtr reader = _gdImageJxlAnimReaderCreateCtx(in, JXL_TRUE); + in->gd_free(in); + return reader; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateCtx(gdIOCtxPtr inCtx) { + return _gdImageJxlAnimReaderCreateCtx(inCtx, JXL_TRUE); +} + +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateRaw(FILE *inFile) { + gdIOCtx *in = gdNewFileCtx(inFile); + if (!in) { + return NULL; + } + gdJxlAnimReaderPtr reader = _gdImageJxlAnimReaderCreateCtx(in, JXL_FALSE); + in->gd_free(in); + return reader; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateRawPtr(int size, void *data) { + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return NULL; + } + gdJxlAnimReaderPtr reader = _gdImageJxlAnimReaderCreateCtx(in, JXL_FALSE); + in->gd_free(in); + return reader; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateRawCtx(gdIOCtxPtr inCtx) { + return _gdImageJxlAnimReaderCreateCtx(inCtx, JXL_FALSE); +} + +/* Helper: build gdImagePtr from current decoder state */ +static gdImagePtr JxlAnimReaderGetCurrentFrame(gdJxlAnimReaderPtr reader, + int *delay_ms) { + JxlDecoder *dec = reader->dec; + JxlBasicInfo info = reader->info; + uint8_t *pixels = NULL; + gdImagePtr im = NULL; + int have_frame_header = 0; + + if (reader->done) { + return NULL; + } + + for (;;) { + JxlDecoderStatus status = JxlDecoderProcessInput(dec); + + if (status == JXL_DEC_FRAME) { + JxlFrameHeader fhdr = {0}; + status = JxlDecoderGetFrameHeader(dec, &fhdr); + if (status != JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to get frame header"); + if (pixels) + gdFree(pixels); + return NULL; + } + have_frame_header = 1; + reader->last_frame_seen = fhdr.is_last == JXL_TRUE; + if (delay_ms) { + *delay_ms = JxlDurationToMs(fhdr.duration, &info.animation); + } + } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { + JxlPixelFormat fmt = {.num_channels = 4, + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + .align = 0}; + size_t pixels_size; + int w, h; + + if (reader->coalesced) { + w = (int)info.xsize; + h = (int)info.ysize; + } else { + /* For non-coalesced, we need to query the frame size */ + size_t buf_size; + if (!have_frame_header) { + gd_error("gdJxlReadNextFrame: missing frame header"); + return NULL; + } + if (JxlDecoderImageOutBufferSize(dec, &fmt, &buf_size) != + JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to get buffer size"); + return NULL; + } + w = (int)sqrt( + buf_size / + 4); /* approximation - we'll get actual size from fmt */ + h = (int)(buf_size / 4 / w); + /* Actually better: we know the frame size from layer_info but + * for simplicity with coalesced=false we allocate based on + * the buffer size reported */ + } + + if (overflow2(w, h) || overflow2(w * h, 4)) { + gd_error("gdJxlReadNextFrame: frame dimensions overflow"); + return NULL; + } + + pixels_size = (size_t)w * (size_t)h * 4; + pixels = (uint8_t *)gdMalloc(pixels_size); + if (pixels == NULL) { + gd_error("gdJxlReadNextFrame: pixel buffer allocation failed"); + return NULL; + } + + if (JxlDecoderSetImageOutBuffer(dec, &fmt, pixels, + pixels_size) != JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to set output buffer"); + gdFree(pixels); + return NULL; + } + } else if (status == JXL_DEC_FULL_IMAGE) { + int has_alpha = (info.alpha_bits > 0); + int w, h; + + if (reader->coalesced) { + w = (int)info.xsize; + h = (int)info.ysize; + } else { + /* For non-coalesced, we don't know exact dimensions here. + * We'll reconstruct from the buffer. This is a simplification. + */ + w = (int)info.xsize; + h = (int)info.ysize; + } + + im = JxlImageFromRGBA(pixels, w, h, has_alpha); + if (pixels) + gdFree(pixels); + if (reader->last_frame_seen) + reader->done = 1; + return im; + } else if (status == JXL_DEC_SUCCESS) { + reader->done = 1; + if (pixels) + gdFree(pixels); + return NULL; + } else if (status == JXL_DEC_ERROR) { + gd_error("gdJxlReadNextFrame: decoder error"); + if (pixels) + gdFree(pixels); + return NULL; + } + } +} + +BGD_DECLARE(gdImagePtr) +gdJxlReadNextImage(gdJxlAnimReaderPtr reader, int *delay_ms) { + if (reader == NULL || reader->coalesced == 0) { + return NULL; + } + return JxlAnimReaderGetCurrentFrame(reader, delay_ms); +} + +/* For non-coalesced reader, we need to extract frame info */ +static void JxlFrameHeaderToInfo(const JxlFrameHeader *fhdr, + const JxlBasicInfo *info, + gdJxlFrameInfo *out) { + out->delay_ms = JxlDurationToMs(fhdr->duration, &info->animation); + out->x_offset = (int)fhdr->layer_info.crop_x0; + out->y_offset = (int)fhdr->layer_info.crop_y0; + out->width = (int)fhdr->layer_info.xsize; + out->height = (int)fhdr->layer_info.ysize; + out->blend_mode = (int)fhdr->layer_info.blend_info.blendmode; + out->is_last = (int)fhdr->is_last; +} + +static gdImagePtr JxlAnimReaderGetCurrentRawFrame(gdJxlAnimReaderPtr reader, + gdJxlFrameInfo *info) { + JxlDecoder *dec = reader->dec; + JxlBasicInfo basic = reader->info; + JxlFrameHeader fhdr = {0}; + uint8_t *pixels = NULL; + gdImagePtr im = NULL; + int has_alpha = 0; + int have_frame_header = 0; + int w = 0, h = 0; + + if (reader->done) { + return NULL; + } + + for (;;) { + JxlDecoderStatus status = JxlDecoderProcessInput(dec); + + if (status == JXL_DEC_FRAME) { + status = JxlDecoderGetFrameHeader(dec, &fhdr); + if (status != JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to get frame header"); + return NULL; + } + have_frame_header = 1; + reader->last_frame_seen = fhdr.is_last == JXL_TRUE; + JxlFrameHeaderToInfo(&fhdr, &basic, info); + has_alpha = (basic.alpha_bits > 0); + } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { + JxlPixelFormat fmt = {.num_channels = 4, + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + .align = 0}; + size_t pixels_size; + + /* Query the exact buffer size for this frame */ + if (!have_frame_header) { + gd_error("gdJxlReadNextFrame: missing frame header"); + return NULL; + } + if (JxlDecoderImageOutBufferSize(dec, &fmt, &pixels_size) != + JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to get buffer size"); + return NULL; + } + w = (int)fhdr.layer_info.xsize; + h = (int)fhdr.layer_info.ysize; + + if (overflow2(w, h) || overflow2(w * h, 4)) { + gd_error("gdJxlReadNextFrame: frame dimensions overflow"); + return NULL; + } + + /* Verify pixels_size matches expected */ + if (pixels_size != (size_t)w * (size_t)h * 4) { + gd_error("gdJxlReadNextFrame: unexpected buffer size"); + return NULL; + } + + pixels = (uint8_t *)gdMalloc(pixels_size); + if (pixels == NULL) { + gd_error("gdJxlReadNextFrame: pixel buffer allocation failed"); + return NULL; + } + + if (JxlDecoderSetImageOutBuffer(dec, &fmt, pixels, + pixels_size) != JXL_DEC_SUCCESS) { + gd_error("gdJxlReadNextFrame: failed to set output buffer"); + gdFree(pixels); + return NULL; + } + } else if (status == JXL_DEC_FULL_IMAGE) { + if (pixels == NULL) { + gd_error("gdJxlReadNextFrame: incomplete decoded frame"); + return NULL; + } + w = info->width; + h = info->height; + im = JxlImageFromRGBA(pixels, w, h, has_alpha); + if (pixels) + gdFree(pixels); + if (reader->last_frame_seen) + reader->done = 1; + return im; + } else if (status == JXL_DEC_SUCCESS) { + reader->done = 1; + if (pixels) + gdFree(pixels); + return NULL; + } else if (status == JXL_DEC_ERROR) { + gd_error("gdJxlReadNextFrame: decoder error"); + if (pixels) + gdFree(pixels); + return NULL; + } + } +} + +BGD_DECLARE(gdImagePtr) +gdJxlReadNextFrame(gdJxlAnimReaderPtr reader, gdJxlFrameInfo *info) { + if (reader == NULL || reader->coalesced != 0 || info == NULL) { + return NULL; + } + return JxlAnimReaderGetCurrentRawFrame(reader, info); +} + +BGD_DECLARE(void) gdImageJxlAnimReaderDestroy(gdJxlAnimReaderPtr reader) { + if (reader == NULL) { + return; + } + if (reader->dec) + JxlDecoderDestroy(reader->dec); + if (reader->buf) + gdFree(reader->buf); + gdFree(reader); +} + +#else /* !HAVE_LIBJXL */ + +static void _noJxlError(void) { + gd_error("JXL image support has been disabled\n"); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxl(FILE *inFile) { + ARG_NOT_USED(inFile); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromJxlCtx(gdIOCtx *infile) { + ARG_NOT_USED(infile); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(void) gdImageJxl(gdImagePtr im, FILE *outFile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + _noJxlError(); +} + +BGD_DECLARE(void) +gdImageJxlEx(gdImagePtr im, FILE *outFile, int lossless, float distance, + int effort) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); +} + +BGD_DECLARE(void *) gdImageJxlPtr(gdImagePtr im, int *size) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(void *) +gdImageJxlPtrEx(gdImagePtr im, int *size, int lossless, float distance, + int effort) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(void) gdImageJxlCtx(gdImagePtr im, gdIOCtxPtr outfile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + _noJxlError(); +} + +BGD_DECLARE(void) +gdImageJxlCtxEx(gdImagePtr im, gdIOCtxPtr outfile, int lossless, float distance, + int effort) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); +} + +/* Animation stubs */ +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreate(FILE *inFile) { + ARG_NOT_USED(inFile); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreatePtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateCtx(gdIOCtxPtr inCtx) { + ARG_NOT_USED(inCtx); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdJxlReadNextImage(gdJxlAnimReaderPtr reader, int *delay_ms) { + ARG_NOT_USED(reader); + ARG_NOT_USED(delay_ms); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) gdImageJxlAnimReaderCreateRaw(FILE *inFile) { + ARG_NOT_USED(inFile); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateRawPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimReaderPtr) +gdImageJxlAnimReaderCreateRawCtx(gdIOCtxPtr inCtx) { + ARG_NOT_USED(inCtx); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdJxlReadNextFrame(gdJxlAnimReaderPtr reader, gdJxlFrameInfo *info) { + ARG_NOT_USED(reader); + ARG_NOT_USED(info); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(void) gdImageJxlAnimReaderDestroy(gdJxlAnimReaderPtr reader) { + ARG_NOT_USED(reader); + _noJxlError(); +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBegin(FILE *outFile, int width, int height, int lossless, + float distance, int effort) { + ARG_NOT_USED(outFile); + ARG_NOT_USED(width); + ARG_NOT_USED(height); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBeginCtx(gdIOCtxPtr outCtx, int width, int height, int lossless, + float distance, int effort) { + ARG_NOT_USED(outCtx); + ARG_NOT_USED(width); + ARG_NOT_USED(height); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(gdJxlAnimPtr) +gdImageJxlAnimBeginPtr(int width, int height, int lossless, float distance, + int effort) { + ARG_NOT_USED(width); + ARG_NOT_USED(height); + ARG_NOT_USED(lossless); + ARG_NOT_USED(distance); + ARG_NOT_USED(effort); + _noJxlError(); + return NULL; +} + +BGD_DECLARE(int) +gdImageJxlAnimAddFrame(gdJxlAnimPtr anim, gdImagePtr im, int delay_ms) { + ARG_NOT_USED(anim); + ARG_NOT_USED(im); + ARG_NOT_USED(delay_ms); + _noJxlError(); + return 0; +} + +BGD_DECLARE(int) gdImageJxlAnimEnd(gdJxlAnimPtr anim) { + ARG_NOT_USED(anim); + _noJxlError(); + return 0; +} + +BGD_DECLARE(void *) gdImageJxlAnimEndPtr(gdJxlAnimPtr anim, int *size) { + ARG_NOT_USED(anim); + if (size != NULL) { + *size = 0; + } + _noJxlError(); + return NULL; +} + +#endif /* HAVE_LIBJXL */ diff --git a/ext/gd/libgd/gd_matrix.c b/ext/gd/libgd/gd_matrix.c index ec4067483358..6209b5d74cc3 100644 --- a/ext/gd/libgd/gd_matrix.c +++ b/ext/gd/libgd/gd_matrix.c @@ -8,6 +8,27 @@ /** * Title: Matrix * Group: Affine Matrix + * + * Matrix functions to initialize, transform and various other operations + * on these matrices. + * They can be used with gdTransformAffineCopy and are also used in various + * transformations functions in GD. + * + * matrix are create using a 6 elements double array: + * (start code) + * matrix[0] == xx + * matrix[1] == yx + * matrix[2] == xy + * matrix[3] == xy + * matrix[4] == x0 + * matrix[5] == y0 + * (end code) + * where the transformation of a given point (x,y) is given by: + * + * (start code) + * x_new = xx * x + xy * y + x0; + * y_new = yx * x + yy * y + y0; + * (end code) */ /** @@ -24,9 +45,9 @@ * Returns: * GD_TRUE if the affine is rectilinear or GD_FALSE */ -int gdAffineApplyToPointF (gdPointFPtr dst, const gdPointFPtr src, - const double affine[6]) -{ +BGD_DECLARE(int) +gdAffineApplyToPointF(gdPointFPtr dst, const gdPointFPtr src, + const double affine[6]) { double x = src->x; double y = src->y; dst->x = x * affine[0] + y * affine[2] + affine[4]; @@ -55,11 +76,13 @@ int gdAffineApplyToPointF (gdPointFPtr dst, const gdPointFPtr src, * Returns: * GD_TRUE on success or GD_FALSE on failure */ -int gdAffineInvert (double dst[6], const double src[6]) -{ +BGD_DECLARE(int) gdAffineInvert(double dst[6], const double src[6]) { double r_det = (src[0] * src[3] - src[1] * src[2]); - if (r_det <= 0.0) { + if (!isfinite(r_det)) { + return GD_FALSE; + } + if (r_det == 0) { return GD_FALSE; } @@ -88,10 +111,11 @@ int gdAffineInvert (double dst[6], const double src[6]) * flip_v - Whether or not to flip vertically * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineFlip (double dst[6], const double src[6], const int flip_h, const int flip_v) -{ +BGD_DECLARE(int) +gdAffineFlip(double dst[6], const double src[6], const int flip_h, + const int flip_v) { dst[0] = flip_h ? - src[0] : src[0]; dst[1] = flip_h ? - src[1] : src[1]; dst[2] = flip_v ? - src[2] : src[2]; @@ -116,10 +140,10 @@ int gdAffineFlip (double dst[6], const double src[6], const int flip_h, const in * m2 - Second affine matrix * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineConcat (double dst[6], const double m1[6], const double m2[6]) -{ +BGD_DECLARE(int) +gdAffineConcat(double dst[6], const double m1[6], const double m2[6]) { double dst0, dst1, dst2, dst3, dst4, dst5; dst0 = m1[0] * m2[0] + m1[1] * m2[2]; @@ -145,10 +169,9 @@ int gdAffineConcat (double dst[6], const double m1[6], const double m2[6]) * dst - Where to store the resulting affine transform * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineIdentity (double dst[6]) -{ +BGD_DECLARE(int) gdAffineIdentity(double dst[6]) { dst[0] = 1; dst[1] = 0; dst[2] = 0; @@ -167,10 +190,10 @@ int gdAffineIdentity (double dst[6]) * scale_y - Y scale factor * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineScale (double dst[6], const double scale_x, const double scale_y) -{ +BGD_DECLARE(int) +gdAffineScale(double dst[6], const double scale_x, const double scale_y) { dst[0] = scale_x; dst[1] = 0; dst[2] = 0; @@ -192,10 +215,9 @@ int gdAffineScale (double dst[6], const double scale_x, const double scale_y) * angle - Rotation angle in degrees * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineRotate (double dst[6], const double angle) -{ +BGD_DECLARE(int) gdAffineRotate(double dst[6], const double angle) { const double sin_t = sin (angle * M_PI / 180.0); const double cos_t = cos (angle * M_PI / 180.0); @@ -217,10 +239,9 @@ int gdAffineRotate (double dst[6], const double angle) * angle - Shear angle in degrees * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineShearHorizontal(double dst[6], const double angle) -{ +BGD_DECLARE(int) gdAffineShearHorizontal(double dst[6], const double angle) { dst[0] = 1; dst[1] = 0; dst[2] = tan(angle * M_PI / 180.0); @@ -239,10 +260,9 @@ int gdAffineShearHorizontal(double dst[6], const double angle) * angle - Shear angle in degrees * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineShearVertical(double dst[6], const double angle) -{ +BGD_DECLARE(int) gdAffineShearVertical(double dst[6], const double angle) { dst[0] = 1; dst[1] = tan(angle * M_PI / 180.0); dst[2] = 0; @@ -262,10 +282,10 @@ int gdAffineShearVertical(double dst[6], const double angle) * offset_y - Vertical translation amount * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineTranslate (double dst[6], const double offset_x, const double offset_y) -{ +BGD_DECLARE(int) +gdAffineTranslate(double dst[6], const double offset_x, const double offset_y) { dst[0] = 1; dst[1] = 0; dst[2] = 0; @@ -284,10 +304,9 @@ int gdAffineTranslate (double dst[6], const double offset_x, const double offset * composed of scaling, rotation, shearing, and translation, returns * the amount of scaling. * - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE **/ -double gdAffineExpansion (const double src[6]) -{ +BGD_DECLARE(double) gdAffineExpansion(const double src[6]) { return sqrt (fabs (src[0] * src[3] - src[1] * src[2])); } @@ -302,8 +321,7 @@ double gdAffineExpansion (const double src[6]) * Returns: * GD_TRUE if the affine is rectilinear or GD_FALSE */ -int gdAffineRectilinear (const double m[6]) -{ +BGD_DECLARE(int) gdAffineRectilinear(const double m[6]) { return ((fabs (m[1]) < GD_EPSILON && fabs (m[2]) < GD_EPSILON) || (fabs (m[0]) < GD_EPSILON && fabs (m[3]) < GD_EPSILON)); } @@ -318,14 +336,11 @@ int gdAffineRectilinear (const double m[6]) * m2 - The first affine transformation * * Returns: - * GD_SUCCESS on success or GD_FAILURE + * GD_TRUE on success or GD_FALSE */ -int gdAffineEqual (const double m1[6], const double m2[6]) -{ - return (fabs (m1[0] - m2[0]) < GD_EPSILON && - fabs (m1[1] - m2[1]) < GD_EPSILON && - fabs (m1[2] - m2[2]) < GD_EPSILON && - fabs (m1[3] - m2[3]) < GD_EPSILON && - fabs (m1[4] - m2[4]) < GD_EPSILON && - fabs (m1[5] - m2[5]) < GD_EPSILON); +BGD_DECLARE(int) gdAffineEqual(const double m1[6], const double m2[6]) { + return ( + fabs(m1[0] - m2[0]) < GD_EPSILON && fabs(m1[1] - m2[1]) < GD_EPSILON && + fabs(m1[2] - m2[2]) < GD_EPSILON && fabs(m1[3] - m2[3]) < GD_EPSILON && + fabs(m1[4] - m2[4]) < GD_EPSILON && fabs(m1[5] - m2[5]) < GD_EPSILON); } diff --git a/ext/gd/libgd/gd_metadata.c b/ext/gd/libgd/gd_metadata.c new file mode 100644 index 000000000000..dac108637122 --- /dev/null +++ b/ext/gd/libgd/gd_metadata.c @@ -0,0 +1,318 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "gd.h" +#include "gdhelpers.h" + +typedef struct gdImageMetadataProfile { + char *key; + unsigned char *data; + size_t size; + struct gdImageMetadataProfile *next; +} gdImageMetadataProfile; + +struct gdImageMetadata { + gdImageMetadataProfile *profiles; + size_t profile_count; + size_t total_size; + size_t max_profile_size; + size_t max_total_size; +}; + +static int gdMetadataKeyIsValid(const char *key) { + return key != NULL && key[0] != '\0'; +} + +static char *gdMetadataStrdup(const char *src) { + size_t len; + char *dst; + + len = strlen(src); + dst = (char *)gdMalloc(len + 1); + if (dst == NULL) { + return NULL; + } + memcpy(dst, src, len + 1); + return dst; +} + +static gdImageMetadataProfile * +gdMetadataFindProfile(const gdImageMetadata *metadata, const char *key) { + gdImageMetadataProfile *profile; + + if (metadata == NULL || !gdMetadataKeyIsValid(key)) { + return NULL; + } + + profile = metadata->profiles; + while (profile != NULL) { + if (strcmp(profile->key, key) == 0) { + return profile; + } + profile = profile->next; + } + + return NULL; +} + +static void gdMetadataFreeProfile(gdImageMetadataProfile *profile) { + if (profile == NULL) { + return; + } + if (profile->key != NULL) { + gdFree(profile->key); + } + if (profile->data != NULL) { + gdFree(profile->data); + } + gdFree(profile); +} + +BGD_DECLARE(gdImageMetadata *) gdImageMetadataCreate(void) { + gdImageMetadata *metadata; + + metadata = (gdImageMetadata *)gdCalloc(1, sizeof(gdImageMetadata)); + if (metadata == NULL) { + return NULL; + } + + metadata->max_profile_size = GD_METADATA_DEFAULT_MAX_PROFILE_SIZE; + metadata->max_total_size = GD_METADATA_DEFAULT_MAX_TOTAL_SIZE; + + return metadata; +} + +BGD_DECLARE(void) gdImageMetadataFree(gdImageMetadata *metadata) { + if (metadata == NULL) { + return; + } + + gdImageMetadataReset(metadata); + gdFree(metadata); +} + +BGD_DECLARE(void) gdImageMetadataReset(gdImageMetadata *metadata) { + gdImageMetadataProfile *profile; + + if (metadata == NULL) { + return; + } + + profile = metadata->profiles; + while (profile != NULL) { + gdImageMetadataProfile *next = profile->next; + gdMetadataFreeProfile(profile); + profile = next; + } + + metadata->profiles = NULL; + metadata->profile_count = 0; + metadata->total_size = 0; +} + +BGD_DECLARE(int) +gdImageMetadataSetLimits(gdImageMetadata *metadata, size_t max_profile_size, + size_t max_total_size) { + if (metadata == NULL) { + return GD_META_ERR_INVALID; + } + + if (max_profile_size != 0 || max_total_size != 0) { + gdImageMetadataProfile *profile; + + if (max_total_size != 0 && metadata->total_size > max_total_size) { + return GD_META_ERR_LIMIT; + } + + profile = metadata->profiles; + while (profile != NULL) { + if (max_profile_size != 0 && profile->size > max_profile_size) { + return GD_META_ERR_LIMIT; + } + profile = profile->next; + } + } + + metadata->max_profile_size = max_profile_size; + metadata->max_total_size = max_total_size; + return GD_META_OK; +} + +BGD_DECLARE(void) +gdImageMetadataGetLimits(const gdImageMetadata *metadata, + size_t *max_profile_size, size_t *max_total_size) { + if (max_profile_size != NULL) { + *max_profile_size = metadata != NULL ? metadata->max_profile_size : 0; + } + if (max_total_size != NULL) { + *max_total_size = metadata != NULL ? metadata->max_total_size : 0; + } +} + +BGD_DECLARE(int) +gdImageMetadataSetProfile(gdImageMetadata *metadata, const char *key, + const unsigned char *data, size_t size) { + gdImageMetadataProfile *profile; + unsigned char *new_data = NULL; + size_t old_size = 0; + size_t new_total; + + if (metadata == NULL || !gdMetadataKeyIsValid(key) || + (data == NULL && size != 0)) { + return GD_META_ERR_INVALID; + } + + if (metadata->max_profile_size != 0 && size > metadata->max_profile_size) { + return GD_META_ERR_LIMIT; + } + + profile = gdMetadataFindProfile(metadata, key); + if (profile != NULL) { + old_size = profile->size; + } + + if ((size_t)-1 - (metadata->total_size - old_size) < size) { + return GD_META_ERR_LIMIT; + } + new_total = metadata->total_size - old_size + size; + if (metadata->max_total_size != 0 && new_total > metadata->max_total_size) { + return GD_META_ERR_LIMIT; + } + + if (size != 0) { + new_data = (unsigned char *)gdMalloc(size); + if (new_data == NULL) { + return GD_META_ERR_NOMEM; + } + memcpy(new_data, data, size); + } + + if (profile == NULL) { + profile = (gdImageMetadataProfile *)gdCalloc( + 1, sizeof(gdImageMetadataProfile)); + if (profile == NULL) { + if (new_data != NULL) { + gdFree(new_data); + } + return GD_META_ERR_NOMEM; + } + profile->key = gdMetadataStrdup(key); + if (profile->key == NULL) { + if (new_data != NULL) { + gdFree(new_data); + } + gdFree(profile); + return GD_META_ERR_NOMEM; + } + profile->next = metadata->profiles; + metadata->profiles = profile; + metadata->profile_count++; + } else if (profile->data != NULL) { + gdFree(profile->data); + } + + profile->data = new_data; + profile->size = size; + metadata->total_size = new_total; + + return GD_META_OK; +} + +BGD_DECLARE(const unsigned char *) +gdImageMetadataGetProfile(const gdImageMetadata *metadata, const char *key, + size_t *size) { + gdImageMetadataProfile *profile; + + if (size != NULL) { + *size = 0; + } + + profile = gdMetadataFindProfile(metadata, key); + if (profile == NULL) { + return NULL; + } + + if (size != NULL) { + *size = profile->size; + } + return profile->data; +} + +BGD_DECLARE(int) +gdImageMetadataRemoveProfile(gdImageMetadata *metadata, const char *key) { + gdImageMetadataProfile *profile; + gdImageMetadataProfile *previous = NULL; + + if (metadata == NULL || !gdMetadataKeyIsValid(key)) { + return GD_META_ERR_INVALID; + } + + profile = metadata->profiles; + while (profile != NULL) { + if (strcmp(profile->key, key) == 0) { + if (previous == NULL) { + metadata->profiles = profile->next; + } else { + previous->next = profile->next; + } + metadata->profile_count--; + metadata->total_size -= profile->size; + gdMetadataFreeProfile(profile); + return GD_META_OK; + } + previous = profile; + profile = profile->next; + } + + return GD_META_OK; +} + +BGD_DECLARE(size_t) +gdImageMetadataGetProfileCount(const gdImageMetadata *metadata) { + return metadata != NULL ? metadata->profile_count : 0; +} + +BGD_DECLARE(int) +gdImageMetadataGetProfileAt(const gdImageMetadata *metadata, size_t index, + const char **key, const unsigned char **data, + size_t *size) { + gdImageMetadataProfile *profile; + size_t i = 0; + + if (key != NULL) { + *key = NULL; + } + if (data != NULL) { + *data = NULL; + } + if (size != NULL) { + *size = 0; + } + + if (metadata == NULL) { + return GD_META_ERR_INVALID; + } + + profile = metadata->profiles; + while (profile != NULL) { + if (i == index) { + if (key != NULL) { + *key = profile->key; + } + if (data != NULL) { + *data = profile->data; + } + if (size != NULL) { + *size = profile->size; + } + return GD_META_OK; + } + i++; + profile = profile->next; + } + + return GD_META_ERR_INVALID; +} diff --git a/ext/gd/libgd/gd_nnquant.c b/ext/gd/libgd/gd_nnquant.c new file mode 100644 index 000000000000..fd1756e3ee8f --- /dev/null +++ b/ext/gd/libgd/gd_nnquant.c @@ -0,0 +1,635 @@ +/* NeuQuant Neural-Net Quantization Algorithm + * ------------------------------------------ + * + * Copyright (c) 1994 Anthony Dekker + * + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. + * See "Kohonen neural networks for optimal colour quantization" + * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. + * for a discussion of the algorithm. + * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML + * + * Any party obtaining a copy of these files from the author, directly or + * indirectly, is granted, free of charge, a full and unrestricted irrevocable, + * world-wide, paid up, royalty-free, nonexclusive right and license to deal + * in this software and documentation files (the "Software"), including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons who + * receive copies from any such party to do so, with the only requirement being + * that this copyright notice remain intact. + * + * + * Modified to process 32bit RGBA images. + * Stuart Coyle 2004-2007 + * From: http://pngnq.sourceforge.net/ + * + * Ported to libgd by Pierre A. Joye + * (and make it thread safety by droping static and global variables) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "gd.h" +#include "gd_errors.h" +#include "gdhelpers.h" +#include +#include +#include + +#include "gd_nnquant.h" + +/* Network Definitions + ------------------- */ + +#define maxnetpos (MAXNETSIZE - 1) +#define netbiasshift 4 /* bias for colour values */ +#define ncycles 100 /* no. of learning cycles */ + +/* defs for freq and bias */ +#define intbiasshift 16 /* bias for fractions */ +#define intbias (((int)1) << intbiasshift) +#define gammashift 10 /* gamma = 1024 */ +#define gamma (((int)1) << gammashift) +#define betashift 10 +#define beta (intbias >> betashift) /* beta = 1/1024 */ +#define betagamma (intbias << (gammashift - betashift)) + +/* defs for decreasing radius factor */ +#define initrad (MAXNETSIZE >> 3) /* for 256 cols, radius starts */ +#define radiusbiasshift 6 /* at 32.0 biased by 6 bits */ +#define radiusbias (((int)1) << radiusbiasshift) +#define initradius (initrad * radiusbias) /* and decreases by a */ +#define radiusdec 30 /* factor of 1/30 each cycle */ + +/* defs for decreasing alpha factor */ +#define alphabiasshift 10 /* alpha starts at 1.0 */ +#define initalpha (((int)1) << alphabiasshift) + +/* radbias and alpharadbias used for radpower calculation */ +#define radbiasshift 8 +#define radbias (((int)1) << radbiasshift) +#define alpharadbshift (alphabiasshift + radbiasshift) +#define alpharadbias (((int)1) << alpharadbshift) + +#define ALPHA 0 +#define RED 1 +#define BLUE 2 +#define GREEN 3 + +typedef int nq_pixel[5]; + +typedef struct { + /* biased by 10 bits */ + int alphadec; + + /* lengthcount = H*W*3 */ + int lengthcount; + + /* sampling factor 1..30 */ + int samplefac; + + /* Number of colours to use. Made a global instead of #define */ + int netsize; + + /* for network lookup - really 256 */ + int netindex[256]; + + /* ABGRc */ + /* the network itself */ + nq_pixel network[MAXNETSIZE]; + + /* bias and freq arrays for learning */ + int bias[MAXNETSIZE]; + int freq[MAXNETSIZE]; + + /* radpower for precomputation */ + int radpower[initrad]; + + /* the input image itself */ + unsigned char *thepicture; +} nn_quant; + +/* Initialise network in range (0,0,0,0) to (255,255,255,255) and set parameters + ----------------------------------------------------------------------- */ +static void initnet(nn_quant *nnq, unsigned char *thepic, int len, int sample, + int colours) { + register int i; + register int *p; + + /* Clear out network from previous runs */ + /* thanks to Chen Bin for this fix */ + memset((void *)nnq->network, 0, sizeof(nq_pixel) * MAXNETSIZE); + + nnq->thepicture = thepic; + nnq->lengthcount = len; + nnq->samplefac = sample; + nnq->netsize = colours; + + for (i = 0; i < nnq->netsize; i++) { + p = nnq->network[i]; + p[0] = p[1] = p[2] = p[3] = (i << (netbiasshift + 8)) / nnq->netsize; + nnq->freq[i] = intbias / nnq->netsize; /* 1/netsize */ + nnq->bias[i] = 0; + } +} + +/* -------------------------- */ + +/* Unbias network to give byte values 0..255 and record + * position i to prepare for sort + */ +/* -------------------------- */ + +static void unbiasnet(nn_quant *nnq) { + int i, j, temp; + + for (i = 0; i < nnq->netsize; i++) { + for (j = 0; j < 4; j++) { + /* OLD CODE: network[i][j] >>= netbiasshift; */ + /* Fix based on bug report by Juergen Weigert jw@suse.de */ + temp = (nnq->network[i][j] + (1 << (netbiasshift - 1))) >> + netbiasshift; + if (temp > 255) + temp = 255; + nnq->network[i][j] = temp; + } + nnq->network[i][4] = i; /* record colour no */ + } +} + +/* Output colormap to unsigned char ptr in RGBA format */ +static void getcolormap(nn_quant *nnq, unsigned char *map) { + int i, j; + for (j = 0; j < nnq->netsize; j++) { + for (i = 3; i >= 0; i--) { + *map = nnq->network[j][i]; + map++; + } + } +} + +/* Insertion sort of network and building of netindex[0..255] (to do after + unbias) + ------------------------------------------------------------------------------- + */ +static void inxbuild(nn_quant *nnq) { + register int i, j, smallpos, smallval; + register int *p, *q; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + for (i = 0; i < nnq->netsize; i++) { + p = nnq->network[i]; + smallpos = i; + smallval = p[2]; /* index on g */ + /* find smallest in i..netsize-1 */ + for (j = i + 1; j < nnq->netsize; j++) { + q = nnq->network[j]; + if (q[2] < smallval) { /* index on g */ + smallpos = j; + smallval = q[2]; /* index on g */ + } + } + q = nnq->network[smallpos]; + /* swap p (i) and q (smallpos) entries */ + if (i != smallpos) { + j = q[0]; + q[0] = p[0]; + p[0] = j; + j = q[1]; + q[1] = p[1]; + p[1] = j; + j = q[2]; + q[2] = p[2]; + p[2] = j; + j = q[3]; + q[3] = p[3]; + p[3] = j; + j = q[4]; + q[4] = p[4]; + p[4] = j; + } + /* smallval entry is now in position i */ + if (smallval != previouscol) { + nnq->netindex[previouscol] = (startpos + i) >> 1; + for (j = previouscol + 1; j < smallval; j++) + nnq->netindex[j] = i; + previouscol = smallval; + startpos = i; + } + } + nnq->netindex[previouscol] = (startpos + maxnetpos) >> 1; + for (j = previouscol + 1; j < 256; j++) + nnq->netindex[j] = maxnetpos; /* really 256 */ +} + +/* Search for ABGR values 0..255 (after net is unbiased) and return colour index + ---------------------------------------------------------------------------- + */ +static unsigned int inxsearch(nn_quant *nnq, int al, int b, int g, int r) { + register int i, j, dist, a, bestd; + register int *p; + unsigned int best; + + bestd = 1000; /* biggest possible dist is 256*3 */ + best = 0; + i = nnq->netindex[g]; /* index on g */ + j = i - 1; /* start at netindex[g] and work outwards */ + + while ((i < nnq->netsize) || (j >= 0)) { + if (i < nnq->netsize) { + p = nnq->network[i]; + dist = p[2] - g; /* inx key */ + if (dist >= bestd) + i = nnq->netsize; /* stop iter */ + else { + i++; + if (dist < 0) + dist = -dist; + a = p[1] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[3] - r; + if (a < 0) + a = -a; + dist += a; + } + if (dist < bestd) { + a = p[0] - al; + if (a < 0) + a = -a; + dist += a; + } + if (dist < bestd) { + bestd = dist; + best = p[4]; + } + } + } + + if (j >= 0) { + p = nnq->network[j]; + dist = g - p[2]; /* inx key - reverse dif */ + if (dist >= bestd) + j = -1; /* stop iter */ + else { + j--; + if (dist < 0) + dist = -dist; + a = p[1] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[3] - r; + if (a < 0) + a = -a; + dist += a; + } + if (dist < bestd) { + a = p[0] - al; + if (a < 0) + a = -a; + dist += a; + } + if (dist < bestd) { + bestd = dist; + best = p[4]; + } + } + } + } + + return (best); +} + +/* Search for biased ABGR values + ---------------------------- */ +static int contest(nn_quant *nnq, int al, int b, int g, int r) { + /* finds closest neuron (min dist) and updates freq */ + /* finds best neuron (min dist-bias) and returns position */ + /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ + /* bias[i] = gamma*((1/netsize)-freq[i]) */ + + register int i, dist, a, biasdist, betafreq; + unsigned int bestpos, bestbiaspos; + double bestd, bestbiasd; + register int *p, *f, *n; + + bestd = INT_MAX; + bestbiasd = bestd; + bestpos = 0; + bestbiaspos = bestpos; + p = nnq->bias; + f = nnq->freq; + + for (i = 0; i < nnq->netsize; i++) { + n = nnq->network[i]; + dist = n[0] - al; + if (dist < 0) + dist = -dist; + a = n[1] - b; + if (a < 0) + a = -a; + dist += a; + a = n[2] - g; + if (a < 0) + a = -a; + dist += a; + a = n[3] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + biasdist = dist - ((*p) >> (intbiasshift - netbiasshift)); + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + betafreq = (*f >> betashift); + *f++ -= betafreq; + *p++ += (betafreq << gammashift); + } + nnq->freq[bestpos] += beta; + nnq->bias[bestpos] -= betagamma; + return (bestbiaspos); +} + +/* Move neuron i towards biased (a,b,g,r) by factor alpha + ---------------------------------------------------- */ + +static void altersingle(nn_quant *nnq, int alpha, int i, int al, int b, int g, + int r) { + register int *n; + + n = nnq->network[i]; /* alter hit neuron */ + *n -= (alpha * (*n - al)) / initalpha; + n++; + *n -= (alpha * (*n - b)) / initalpha; + n++; + *n -= (alpha * (*n - g)) / initalpha; + n++; + *n -= (alpha * (*n - r)) / initalpha; +} + +/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in + radpower[|i-j|] + --------------------------------------------------------------------------------- + */ + +static void alterneigh(nn_quant *nnq, int rad, int i, int al, int b, int g, + int r) { + register int j, k, lo, hi, a; + register int *p, *q; + + lo = i - rad; + if (lo < -1) + lo = -1; + hi = i + rad; + if (hi > nnq->netsize) + hi = nnq->netsize; + + j = i + 1; + k = i - 1; + q = nnq->radpower; + while ((j < hi) || (k > lo)) { + a = (*(++q)); + if (j < hi) { + p = nnq->network[j]; + *p -= (a * (*p - al)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - r)) / alpharadbias; + j++; + } + if (k > lo) { + p = nnq->network[k]; + *p -= (a * (*p - al)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - r)) / alpharadbias; + k--; + } + } +} + +/* Main Learning Loop + ------------------ */ + +static void learn(nn_quant *nnq, + int verbose) /* Stu: N.B. added parameter so that main() could + control verbosity. */ +{ + register int i, j, al, b, g, r; + int radius, rad, alpha, step, delta, samplepixels; + register unsigned char *p; + unsigned char *lim; + + nnq->alphadec = 30 + ((nnq->samplefac - 1) / 3); + p = nnq->thepicture; + lim = nnq->thepicture + nnq->lengthcount; + samplepixels = nnq->lengthcount / (4 * nnq->samplefac); + /* here's a problem with small images: samplepixels < ncycles => delta = 0 + */ + delta = samplepixels / ncycles; + /* kludge to fix */ + if (delta == 0) + delta = 1; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + + for (i = 0; i < rad; i++) + nnq->radpower[i] = + alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); + + if (verbose) + gd_error_ex(GD_NOTICE, "beginning 1D learning: initial radius=%d\n", + rad); + + if ((nnq->lengthcount % prime1) != 0) + step = 4 * prime1; + else { + if ((nnq->lengthcount % prime2) != 0) + step = 4 * prime2; + else { + if ((nnq->lengthcount % prime3) != 0) + step = 4 * prime3; + else + step = 4 * prime4; + } + } + + i = 0; + while (i < samplepixels) { + al = p[ALPHA] << netbiasshift; + b = p[BLUE] << netbiasshift; + g = p[GREEN] << netbiasshift; + r = p[RED] << netbiasshift; + j = contest(nnq, al, b, g, r); + + altersingle(nnq, alpha, j, al, b, g, r); + if (rad) + alterneigh(nnq, rad, j, al, b, g, r); /* alter neighbours */ + + p += step; + while (p >= lim) + p -= nnq->lengthcount; + + i++; + if (i % delta == 0) { /* FPE here if delta=0*/ + alpha -= alpha / nnq->alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if (rad <= 1) + rad = 0; + for (j = 0; j < rad; j++) + nnq->radpower[j] = + alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); + } + } + if (verbose) + gd_error_ex(GD_NOTICE, "finished 1D learning: final alpha=%f !\n", + ((float)alpha) / initalpha); +} + +/** + * Function: gdImageNeuQuant + * + * Creates a new palette image from a truecolor image + * + * This is the same as calling with the + * quantization method . + * + * Parameters: + * im - The image. + * max_color - The number of desired palette entries. + * sample_factor - The quantization precision between 1 (highest quality) and + * 10 (fastest). + * + * Returns: + * A newly create palette image; NULL on failure. + */ +BGD_DECLARE(gdImagePtr) +gdImageNeuQuant(gdImagePtr im, const int max_color, int sample_factor) { + const int newcolors = max_color; + const int verbose = 1; + + int i, x; + + unsigned char map[MAXNETSIZE][4] = {{0}}; + unsigned char *d; + + nn_quant *nnq = NULL; + + int row; + unsigned char *rgba = NULL; + gdImagePtr dst = NULL; + + if (newcolors <= 0 || newcolors > MAXNETSIZE) { + gd_error("neuquant: max_color must be between 1 and %d\n", MAXNETSIZE); + goto done; + } + + /* Default it to 3 */ + if (sample_factor < 1) { + sample_factor = 3; + } + /* Start neuquant */ + /* Pierre: + * This implementation works with aligned contiguous buffer only + * Upcoming new buffers are contiguous and will be much faster. + * let don't bloat this code to support our good "old" 31bit format. + * It also lets us convert palette image, if one likes to reduce + * a palette + */ + if (overflow2(gdImageSX(im), gdImageSY(im)) || + overflow2(gdImageSX(im) * gdImageSY(im), 4)) { + goto done; + } + rgba = (unsigned char *)gdMalloc(gdImageSX(im) * gdImageSY(im) * 4); + if (!rgba) { + goto done; + } + + d = rgba; + for (row = 0; row < gdImageSY(im); row++) { + int *p = im->tpixels[row]; + register int c; + + for (i = 0; i < gdImageSX(im); i++) { + c = *p; + *d++ = gdImageAlpha(im, c); + *d++ = gdImageRed(im, c); + *d++ = gdImageBlue(im, c); + *d++ = gdImageGreen(im, c); + p++; + } + } + + nnq = (nn_quant *)gdMalloc(sizeof(nn_quant)); + if (!nnq) { + goto done; + } + + initnet(nnq, rgba, gdImageSY(im) * gdImageSX(im) * 4, sample_factor, + newcolors); + + learn(nnq, verbose); + unbiasnet(nnq); + getcolormap(nnq, (unsigned char *)map); + inxbuild(nnq); + + dst = gdImageCreate(gdImageSX(im), gdImageSY(im)); + if (!dst) { + goto done; + } + + for (x = 0; x < newcolors; ++x) { + dst->red[x] = map[x][0]; + dst->green[x] = map[x][1]; + dst->blue[x] = map[x][2]; + dst->alpha[x] = map[x][3]; + dst->open[x] = 0; + dst->colorsTotal++; + } + + /* Do each image row */ + for (row = 0; row < gdImageSY(im); ++row) { + int offset; + unsigned char *p = dst->pixels[row]; + + /* Assign the new colors */ + offset = row * gdImageSX(im) * 4; + for (i = 0; i < gdImageSX(im); i++) { + p[i] = inxsearch( + nnq, rgba[i * 4 + offset + ALPHA], rgba[i * 4 + offset + BLUE], + rgba[i * 4 + offset + GREEN], rgba[i * 4 + offset + RED]); + } + } + +done: + if (rgba) { + gdFree(rgba); + } + + if (nnq) { + gdFree(nnq); + } + return dst; +} diff --git a/ext/gd/libgd/gd_nnquant.h b/ext/gd/libgd/gd_nnquant.h new file mode 100644 index 000000000000..0b7a17889609 --- /dev/null +++ b/ext/gd/libgd/gd_nnquant.h @@ -0,0 +1,15 @@ +/* maximum number of colours that can be used. + actual number is now passed to initcolors */ +#define MAXNETSIZE 256 + +/* For 256 colours, fixed arrays need 8kb, plus space for the image + ---------------------------------------------------------------- */ + +/* four primes near 500 - assume no image has a length so large */ +/* that it is divisible by all four primes */ +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 + +#define minpicturebytes (4 * prime4) /* minimum size for input image */ diff --git a/ext/gd/libgd/gd_path.c b/ext/gd/libgd/gd_path.c new file mode 100644 index 000000000000..b49959d40e04 --- /dev/null +++ b/ext/gd/libgd/gd_path.c @@ -0,0 +1,704 @@ + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gd_intern.h" +#include "gdhelpers.h" + +#include "gd_path_matrix.h" +#include "gd_span_rle.h" +#include "gd_path.h" +#include "gd_path_arc.h" +#include "gd_path_dash.h" +#include "gd_gradient.h" +#include "gd_compositor.h" + +static gdSurfacePtr gdSurfaceSnapshotImage(gdImagePtr image) +{ + gdSurfacePtr surface; + int x, y; + + if (!image) + return NULL; + surface = gdSurfaceCreate(image->sx, image->sy, GD_SURFACE_ARGB32); + if (!surface) + return NULL; + for (y = 0; y < image->sy; y++) { + uint32_t *row = (uint32_t *)(surface->data + y * surface->stride); + for (x = 0; x < image->sx; x++) { + int pixel = gdImageGetTrueColorPixel(image, x, y); + row[x] = gdCompositePixelToArgb32(gdCompositePixelFromGd(pixel)); + } + } + return surface; +} + +gdPaintPtr gdPaintAddRef(gdPaintPtr paint) +{ + if (paint == NULL) + return NULL; + + ++paint->ref; + return paint; +} + +BGD_DECLARE(void) gdPaintDestroy(gdPaintPtr paint) +{ + if (paint == NULL) + return; + paint->ref--; + + if (paint->ref == 0) + { + switch (paint->type) + { + case gdPaintTypeColor: + gdFree(paint->color); + break; + case gdPaintTypeGradient: + gdGradientDestroy(paint->gradient); + break; + case gdPaintTypeSurface: + // Not implemented + break; + case gdPaintTypePattern: + gdPathPatternDestroy(paint->pattern); + break; + default: + { + } + } + gdFree(paint); + } +} + +void gdColorInitRgba(gdColorPtr color, double r, double g, double b, double a) +{ + color->r = CLAMP(r, 0.0, 1.0); + color->g = CLAMP(g, 0.0, 1.0); + color->b = CLAMP(b, 0.0, 1.0); + color->a = CLAMP(a, 0.0, 1.0); +} + +gdPathPatternPtr gdPaintGetPattern(const gdPaintPtr paint) +{ + return paint->type == gdPaintTypePattern ? paint->pattern : NULL; +} + +GD_VECTOR2D_INTERNAL gdPathPatternPtr gdPathPatternCreate(gdSurfacePtr surface) +{ + if (!surface) + return NULL; + gdPathPatternPtr pattern = gdMalloc(sizeof(gdPathPattern)); + if (!pattern) + return NULL; + pattern->ref = 1; + pattern->extend = GD_EXTEND_NONE; + pattern->surface = surface; + gdSurfaceAddRef(surface); + pattern->opacity = 1.0; + gdPathMatrixInitIdentity(&pattern->matrix); + return pattern; +} + +BGD_DECLARE(gdPathPatternPtr) gdPathPatternCreateForImage(gdImagePtr image) +{ + gdSurfacePtr snapshot = gdSurfaceSnapshotImage(image); + gdPathPatternPtr pattern; + + if (!snapshot) + return NULL; + pattern = gdPathPatternCreate(snapshot); + gdSurfaceDestroy(snapshot); + return pattern; +} + +BGD_DECLARE(void) gdPathPatternDestroy(gdPathPatternPtr pattern) +{ + if(pattern == NULL) + return; + + if(--pattern->ref==0) + { + gdSurfaceDestroy(pattern->surface); + gdFree(pattern); + } +} + +void gdPathPatternAddRef(gdPathPatternPtr pattern) +{ + if(pattern==NULL) return; + pattern->ref++; +} + +BGD_DECLARE(gdPaintPtr) gdPaintCreateFromPattern(gdPathPatternPtr pattern) +{ + gdPaintPtr paint = gdMalloc(sizeof(gdPaint)); + if (!paint) return NULL; + paint->ref = 1; + paint->type = gdPaintTypePattern; + paint->pattern = pattern; + gdPathPatternAddRef(pattern); + return paint; +} + +gdPaintPtr gdPaintCreateForSurface(gdSurfacePtr surface) { + gdPathPatternPtr pattern = gdPathPatternCreate(surface); + gdPaintPtr paint = gdPaintCreateFromPattern(pattern); + gdPathPatternDestroy(pattern); + return paint; +} + +BGD_DECLARE(void) gdPathPatternSetExtend(gdPathPatternPtr pattern, gdExtendMode extend) +{ + pattern->extend = extend; +} + +BGD_DECLARE(void) gdPathPatternSetMatrix(gdPathPatternPtr pattern, gdPathMatrixPtr matrix) +{ + memcpy(&pattern->matrix, matrix, sizeof(gdPathMatrix)); +} + +BGD_DECLARE(void) gdPathPatternSetOpacity(gdPathPatternPtr pattern, double opacity) +{ + if (!pattern || !isfinite(opacity)) + return; + pattern->opacity = CLAMP(opacity, 0.0, 1.0); +} + +void gdPaintSetSourceSurface(gdContextPtr context, gdSurfacePtr surface, double x, double y) +{ + gdPathMatrix matrix; + gdPaintPtr paint; + gdPathPatternPtr pattern = gdPathPatternCreate(surface); + gdPathPatternSetExtend(pattern, GD_EXTEND_NONE); + gdPathMatrixMultiply(&matrix, &matrix, &context->state->matrix); + gdPathMatrixInitTranslate(&matrix, x, y); + gdPathPatternSetMatrix (pattern, &matrix); + paint = gdPaintCreateFromPattern(pattern); + gdContextSetSource(context, paint); +} + +gdPaintPtr gdPaintCreateRgba(double r, double g, double b, double a) +{ + gdPaintPtr paint = gdMalloc(sizeof(gdPaint)); + if (!paint) + { + return NULL; + } + paint->color = gdMalloc(sizeof(gdColor)); + if (!paint->color) + { + gdFree(paint); + return NULL; + } + + paint->ref = 1; + paint->type = gdPaintTypeColor; + gdColorInitRgba(paint->color, r, g, b, a); + return paint; +} + +gdPaintPtr gdPaintCreateRgb(double r, double g, double b) +{ + return gdPaintCreateRgba(r, g, b, 1.0); +} + +BGD_DECLARE(void) gdContextSetSource(gdContextPtr context, gdPaintPtr source) +{ + source = gdPaintAddRef(source); + gdPaintDestroy(context->state->source); + context->state->source = source; +} + +void gdContextSetSourceColorRgb(gdContextPtr context, double r, double g, double b) +{ + gdContextSetSourceRgba(context, r, g, b, 1.0); +} + +gdStatePtr gdStateCreate() +{ + gdStatePtr state = gdMalloc(sizeof(gdState)); + if (!state) + { + return NULL; + } + //state->font = NULL; + state->source = gdPaintCreateRgba(0, 0, 0, 1.0); + if (!state->source) + { + gdFree(state); + return NULL; + } + gdPathMatrixInitIdentity(&state->matrix); + state->winding = gdFillRuleNonZero; + state->stroke.width = 1.0; + state->stroke.miterlimit = 4.0; + state->stroke.cap = gdLineCapButt; + state->stroke.join = gdLineJoinMiter; + state->stroke.dash = NULL; + state->op = GD_OP_OVER; + //state->fontsize = 12.0; + state->opacity = 1.0; + state->clippath = NULL; + state->next = NULL; + return state; +} + +void gdStateDestroy(gdStatePtr state) +{ + //state->font + gdSpanRleDestroy(state->clippath); + gdPaintDestroy(state->source); + gdPathDashDestroy(state->stroke.dash); + gdFree(state); +} + +BGD_DECLARE(gdPathPtr) gdPathCreate() +{ + gdPathPtr path = gdMalloc(sizeof(gdPath)); + if (!path) + return NULL; + path->ref = 1; + path->contours = 0; + path->start.x = 0.0; + path->start.y = 0.0; + gdArrayInit(&path->elements, sizeof(gdPathOps)); + gdArrayInit(&path->points, sizeof(gdPointF)); + return path; +} + +gdPathPtr gdPathDuplicate(const gdPathPtr path) +{ + gdPathPtr result = gdPathCreate(); + if (!result) + return NULL; + gdArrayInit(&result->elements, sizeof(gdPathOps)); + gdArrayInit(&result->points, sizeof(gdPointF)); + gdArrayAppendMultiple(&result->elements, + gdArrayGetData(&path->elements), + gdArrayNumElements(&path->elements)); + gdArrayAppendMultiple(&result->points, + gdArrayGetData(&path->points), + gdArrayNumElements(&path->points)); + + result->contours = path->contours; + result->start = path->start; + + return result; +} + + +BGD_DECLARE(void) gdPathAppendPath(gdPathPtr path, const gdPathPtr source) +{ + gdArrayAppendMultiple(&path->elements, + gdArrayGetData(&source->elements), + gdArrayNumElements(&source->elements)); + gdArrayAppendMultiple(&path->points, + gdArrayGetData(&source->points), + gdArrayNumElements(&source->points)); + + path->contours += source->contours; + path->start = source->start; +} + +BGD_DECLARE(void) gdPathTransform(gdPathPtr path, const gdPathMatrixPtr matrix) +{ + gdPointFPtr p; + unsigned int numElements = gdArrayNumElements(&path->points); + unsigned int i; + + //memset(p, 0, sizeof(gdPointF) * 3); + for (i = 0; i < numElements; i++) + { + p = (gdPointFPtr)gdArrayIndex(&path->points, i); + gdPathMatrixMapPoint(matrix, p, p); + } +} + +static inline void _path_get_current_point(const gdPathPtr path, double *x, double *y) +{ + unsigned int numElems = gdArrayNumElements(&path->points); + if (x) + *x = 0.0; + if (y) + *y = 0.0; + if (numElems < 1) + return; + gdPointFPtr point = gdArrayIndex(&path->points, numElems - 1); + if (x) + { + *x = point->x; + } + if (y) + { + *y = point->y; + } +} + +typedef struct +{ + double x1; + double y1; + double x2; + double y2; + double x3; + double y3; + double x4; + double y4; +} cubic_points; + +static inline void split(const cubic_points* b, cubic_points* first, cubic_points* second) +{ + double c = (b->x2 + b->x3) * 0.5; + first->x2 = (b->x1 + b->x2) * 0.5; + second->x3 = (b->x3 + b->x4) * 0.5; + first->x1 = b->x1; + second->x4 = b->x4; + first->x3 = (first->x2 + c) * 0.5; + second->x2 = (second->x3 + c) * 0.5; + first->x4 = second->x1 = (first->x3 + second->x2) * 0.5; + + c = (b->y2 + b->y3) * 0.5; + first->y2 = (b->y1 + b->y2) * 0.5; + second->y3 = (b->y3 + b->y4) * 0.5; + first->y1 = b->y1; + second->y4 = b->y4; + first->y3 = (first->y2 + c) * 0.5; + second->y2 = (second->y3 + c) * 0.5; + first->y4 = second->y1 = (first->y3 + second->y2) * 0.5; +} + +/* See http://agg.sourceforge.net/antigrain.com/research/adaptive_bezier/index.html */ +static void _cubic_flatten(gdPathPtr path, + const gdPointFPtr p0, + const gdPointFPtr p1, + const gdPointFPtr p2, + const gdPointFPtr p3) +{ + cubic_points beziers[32]; + beziers[0].x1 = p0->x; + beziers[0].y1 = p0->y; + beziers[0].x2 = p1->x; + beziers[0].y2 = p1->y; + beziers[0].x3 = p2->x; + beziers[0].y3 = p2->y; + beziers[0].x4 = p3->x; + beziers[0].y4 = p3->y; + + /* tolerance for the distance t to the line + 0.1 is a common accepted value + */ + const double tolerance = 0.1; + + cubic_points *b = beziers; + while (b >= beziers) + { + double y4y1 = b->y4 - b->y1; + double x4x1 = b->x4 - b->x1; + double l = fabs(x4x1) + fabs(y4y1); + double d; + if (l > 1.0) + { + d = fabs((x4x1) * (b->y1 - b->y2) - (y4y1) * (b->x1 - b->x2)) + fabs((x4x1) * (b->y1 - b->y3) - (y4y1) * (b->x1 - b->x3)); + } + else + { + d = fabs(b->x1 - b->x2) + fabs(b->y1 - b->y2) + fabs(b->x1 - b->x3) + fabs(b->y1 - b->y3); + l = 1.0; + } + + if (d < tolerance * l || b == beziers + 31) + { + gdPathLineTo(path, b->x4, b->y4); + --b; + } + else + { + split(b, b + 1, b); + ++b; + } + } +} + +gdPathPtr gdPathDuplicateFlattened(const gdPathPtr path) +{ + gdPathPtr result = gdPathCreate(); + + gdArrayReallocBy(&result->elements, gdArrayNumElements(&path->elements)); + gdArrayReallocBy(&result->points, gdArrayNumElements(&path->points)); + + gdPointFPtr points = gdArrayGetData(&path->points); + for (unsigned int i = 0; i < gdArrayNumElements(&path->elements); i++) + { + const gdPathOpsPtr cur_elem = gdArrayIndex(&path->elements, i); + switch (*cur_elem) + { + case gdPathOpsMoveTo: + gdPathMoveTo(result, points[0].x, points[0].y); + points += 1; + break; + case gdPathOpsLineTo: + case gdPathOpsClose: + gdPathLineTo(result, points[0].x, points[0].y); + points += 1; + break; + case gdPathOpsCubicTo: + { + gdPointF p0; + _path_get_current_point(result, &p0.x, &p0.y); + _cubic_flatten(result, &p0, points, points + 1, points + 2); + points += 3; + break; + } + default: + // Only to silent compiler + break; + } + } + return result; +} + +gdPathPtr gdPathAddRef(gdPathPtr path) +{ + if (path == NULL) + return NULL; + path->ref++; + return path; +} + +BGD_DECLARE(void) gdPathDestroy(gdPathPtr path) +{ + if (path == NULL) + return; + path->ref--; + if (path->ref == 0) + { + gdArrayDestroy(&path->elements); + gdArrayDestroy(&path->points); + gdFree(path); + } +} + +void gdPathClear(gdPathPtr path) +{ + gdArrayTruncate(&path->elements, 0); + gdArrayTruncate(&path->points, 0); + path->contours = 0; + path->start.x = 0.0; + path->start.y = 0.0; +} + +/* dump a path, could be nicer but good enough for now +Not exported, only for debugging purposes here */ +void gdPathDumpPathTransform(const gdPathPtr path, const gdPathMatrixPtr matrix) +{ + //GD_FT_Outline* outline = gd_ft_outline_create(path->points.size, path->contours); + gdPointF p[3]; + unsigned int numElements = gdArrayNumElements(&path->elements); + unsigned int pointsIndex = 0; + unsigned int i; + + memset(p, 0, sizeof(gdPointF) * 3); + printf("NEWOUTLINE CONVERT\n"); + for (i = 0; i < numElements; i++) + { + gdPathOpsPtr element = (gdPathOpsPtr)gdArrayIndex(&path->elements, i); + gdPointFPtr point = gdArrayIndex(&path->points, pointsIndex); + printf("-------\n"); + switch (*element) + { + case gdPathOpsMoveTo: + if (matrix) gdPathMatrixMapPoint(matrix, point, &p[0]); + printf("MoveTo(%f, %f)", point->x, point->y); + printf("(%f, %f)", p[0].x, p[0].y); + pointsIndex += 1; + break; + case gdPathOpsLineTo: + if (matrix) gdPathMatrixMapPoint(matrix, point, &p[0]); + printf("LineTo(%f, %f)", point->x, point->y); + printf("(%f, %f)", p[0].x, p[0].y); + pointsIndex += 1; + break; + case gdPathOpsCubicTo: + printf("CubicTo(%f, %f)", point->x, point->y); + if (matrix) gdPathMatrixMapPoint(matrix, point, &p[0]); + printf("(%f, %f)", p[0].x, p[0].y); + + point = gdArrayIndex(&path->points, pointsIndex + 1); + if (matrix) gdPathMatrixMapPoint(matrix, point, &p[1]); + printf("(%f, %f)", p[1].x, p[1].y); + + point = gdArrayIndex(&path->points, pointsIndex + 2); + if (matrix) gdPathMatrixMapPoint(matrix, point, &p[2]); + printf("(%f, %f)", p[2].x, p[2].y); + pointsIndex += 3; + break; + case gdPathOpsClose: + printf("Outline close"); + pointsIndex += 1; + break; + default: + break; + } + printf("\n-------\n"); + } +} + +static inline void _relativeTo(const gdPathPtr path, double *x, double *y) +{ + double _x = -1, _y = -1; + _path_get_current_point(path, &_x, &_y); + *x += _x; + *y += _y; +} + +BGD_DECLARE(void) gdPathMoveTo(gdPathPtr path, double x, double y) +{ + const gdPathOps op = gdPathOpsMoveTo; + gdPointF point; + + gdArrayAppend(&path->elements, &op); + path->contours += 1; + + point.x = x; + point.y = y; + gdArrayAppend(&path->points, &point); + path->start.x = x; + path->start.y = y; +} + +BGD_DECLARE(void) gdPathLineTo(gdPathPtr path, double x, double y) +{ + const gdPathOps op = gdPathOpsLineTo; + gdPointF point; + + gdArrayAppend(&path->elements, &op); + + point.x = x; + point.y = y; + gdArrayAppend(&path->points, &point); +} +BGD_DECLARE(void) gdPathRelLineTo(gdPathPtr path, double dx, double dy) +{ + _relativeTo(path, &dx, &dy); + gdPathLineTo(path, dx, dy); +} + +BGD_DECLARE(void) gdPathCurveTo(gdPathPtr path, double x1, double y1, double x2, double y2, double x3, double y3) +{ + const gdPathOps op = gdPathOpsCubicTo; + gdPointF points[3]; + + gdArrayAppend(&path->elements, &op); + points[0].x = x1; + points[0].y = y1; + points[1].x = x2; + points[1].y = y2; + points[2].x = x3; + points[2].y = y3; + gdArrayAppend(&path->points, &points[0]); + gdArrayAppend(&path->points, &points[1]); + gdArrayAppend(&path->points, &points[2]); +} + +BGD_DECLARE(void) gdPathQuadTo(gdPathPtr path, double x1, double y1, double x2, double y2) +{ + const gdPathOps op = gdPathOpsQuadTo; + gdPointF points[2] = {{x1, y1}, {x2, y2}}; + + gdArrayAppend(&path->elements, &op); + gdArrayAppend(&path->points, &points[0]); + gdArrayAppend(&path->points, &points[1]); +} + +/* +Based on http://www.whizkidtech.redprince.net/bezier/circle/kappa/ +*/ +void gdPathAddArc(gdPathPtr path, double cx, double cy, double radius, double angle1, double angle2, int ccw) +{ + if (ccw) + _gd_arc_path_negative(path, cx, cy, radius, angle1, angle2); + else + _gd_arc_path(path, cx, cy, radius, angle1, angle2); +} + +void gdPathArcTo(gdPathPtr path, double x1, double y1, double x2, double y2, double radius) +{ + double x0, y0; + _path_get_current_point(path, &x0, &y0); + if ((x0 == x1 && y0 == y1) || (x1 == x2 && y1 == y2) || radius == 0.0) + { + gdPathLineTo(path, x1, y2); + return; + } + + double dir = (x2 - x1) * (y0 - y1) + (y2 - y1) * (x1 - x0); + if (dir == 0.0) + { + gdPathLineTo(path, x1, y2); + return; + } + + double a2 = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1); + double b2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); + double c2 = (x0 - x2) * (x0 - x2) + (y0 - y2) * (y0 - y2); + + double cosx = (a2 + b2 - c2) / (2 * sqrt(a2 * b2)); + double sinx = sqrt(1 - cosx * cosx); + double d = radius / ((1 - cosx) / sinx); + + double anx = (x1 - x0) / sqrt(a2); + double any = (y1 - y0) / sqrt(a2); + double bnx = (x1 - x2) / sqrt(b2); + double bny = (y1 - y2) / sqrt(b2); + + double x3 = x1 - anx * d; + double y3 = y1 - any * d; + double x4 = x1 - bnx * d; + double y4 = y1 - bny * d; + + int ccw = dir < 0.0; + double cx = x3 + any * radius * (ccw ? 1 : -1); + double cy = y3 - anx * radius * (ccw ? 1 : -1); + double a0 = atan2(y3 - cy, x3 - cx); + double a1 = atan2(y4 - cy, x4 - cx); + + gdPathLineTo(path, x3, y3); + gdPathAddArc(path, cx, cy, radius, a0, a1, ccw); +} + +void gdPathAddRectangle(gdPathPtr path, double x, double y, double w, double h) +{ + gdPathMoveTo(path, x, y); + gdPathLineTo(path, x + w, y); + gdPathLineTo(path, x + w, y + h); + gdPathLineTo(path, x, y + h); + gdPathLineTo(path, x, y); + gdPathClose(path); +} + +BGD_DECLARE(void) gdPathClose(gdPathPtr path) +{ + const int numElements = gdArrayNumElements(&path->elements); + const gdPathOps OpClose = gdPathOpsClose; + if (numElements == 0) + return; + const gdPathOpsPtr lastOpPtr = gdArrayIndex(&path->elements, + (unsigned int)numElements - 1); + if (*lastOpPtr == gdPathOpsClose) + return; + + gdPointF point; + point.x = path->start.x; + point.y = path->start.y; + gdArrayAppend(&path->elements, &OpClose); + gdArrayAppend(&path->points, &point); +} + +void gdPathBlend(gdContextPtr context, const gdSpanRlePtr rle); diff --git a/ext/gd/libgd/gd_path.h b/ext/gd/libgd/gd_path.h new file mode 100644 index 000000000000..be80d7d46738 --- /dev/null +++ b/ext/gd/libgd/gd_path.h @@ -0,0 +1,63 @@ +#ifndef GD_PATH_H +#define GD_PATH_H + +#include + +BGD_DECLARE(gdPathPtr) gdPathCreate(void); +gdPathPtr gdPathAddRef(gdPathPtr path); +BGD_DECLARE(void) gdPathDestroy(gdPathPtr path); + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#ifndef M_PI2 +#define M_PI2 M_PI * 2 +#endif + +#define EPSILON_DOUBLE 0.000000000001f +#define MAX_FULL_CIRCLES 65536 +#define PATH_KAPPA 0.5522847498 +#define DEFAULT_TOLERANCE 0.1 +static inline int _doubleEqualsEpsilon(double p1, double p2) +{ + return (fabs(p1 - p2) < EPSILON_DOUBLE); +} + +static inline int _doubleIsZero(double f) +{ + return (fabs(f) <= EPSILON_DOUBLE); +} + +#define ARRAY_LENGTH(__array) ((int) (sizeof (__array) / sizeof (__array[0]))) + +gdPaintPtr gdPaintCreateRgba(double r, double g, double b, double a); +BGD_DECLARE(void) gdPaintDestroy(gdPaintPtr paint); +BGD_DECLARE(void) gdContextSetSource(gdContextPtr context, gdPaintPtr source); + +gdStatePtr gdStateCreate(); +void gdStateDestroy(gdStatePtr state); + +BGD_DECLARE(gdPathPtr) gdPathCreate(void); +gdPathPtr gdPathDuplicate(const gdPathPtr path); +gdPathPtr gdPathDuplicateFlattened(const gdPathPtr path); +void gdPathClear(gdPathPtr path); +BGD_DECLARE(void) gdPathDestroy(gdPathPtr path); + +gdPaintPtr gdPaintCreateForSurface(gdSurfacePtr surface); +gdPathPatternPtr gdPaintGetPattern(const gdPaintPtr paint); +BGD_DECLARE(void) gdPathPatternSetMatrix(gdPathPatternPtr pattern, gdPathMatrixPtr matrix); +void gdPaintSetSourceSurface(gdContextPtr context, gdSurfacePtr surface, double x, double y); +BGD_DECLARE(void) gdPathPatternDestroy(gdPathPatternPtr pattern); + +BGD_DECLARE(void) gdPathCurveTo(gdPathPtr path, double x1, double y1, double x2, double y2, double x3, double y3); +BGD_DECLARE(void) gdPathQuadTo(gdPathPtr path, double x1, double y1, double x2, double y2); +BGD_DECLARE(void) gdPathLineTo(gdPathPtr path, double x, double y); +BGD_DECLARE(void) gdPathRelLineTo(gdPathPtr path, double dx, double dy); +BGD_DECLARE(void) gdPathMoveTo(gdPathPtr path, double x, double y); +void gdPathArcTo(gdPathPtr path, double x1, double y1, double x2, double y2, double radius); +void gdPathAddRectangle(gdPathPtr path, double x, double y, double w, double h); +BGD_DECLARE(void) gdPathClose(gdPathPtr path); + +void gdPathAddArc(gdPathPtr path, double cx, double cy, double radius, double angle1, double angle2, int ccw); +void gdPathDumpPathTransform(const gdPathPtr path, const gdPathMatrixPtr matrix); +#endif // GD_PATH_H diff --git a/ext/gd/libgd/gd_path_arc.c b/ext/gd/libgd/gd_path_arc.c new file mode 100644 index 000000000000..46accde527ff --- /dev/null +++ b/ext/gd/libgd/gd_path_arc.c @@ -0,0 +1,311 @@ + +/* This is a port of Carl's amazing work on this. The license chosen + * here is the MPL 1.1, applying to this file only. + * + * Copyright © 2004 Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * The Initial Developer of the Original Code is University of Southern + * California. + * + * Contributor(s): + * Kristian Høgsberg + * Carl Worth + */ + +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gd_intern.h" +#include "gdhelpers.h" +#include "gd_errors.h" + +#include "gd_surface.h" +#include "gd_array.h" +#include "gd_path_matrix.h" +#include "gd_span_rle.h" +#include "gd_path.h" +#include "gd_path_arc.h" + +/* Spline deviation from the circle in radius would be given by: + + error = sqrt (x**2 + y**2) - 1 + + A simpler error function to work with is: + + e = x**2 + y**2 - 1 + + From "Good approximation of circles by curvature-continuous Bezier + curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric + Design 8 (1990) 22-41, we learn: + + abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4) + + and + abs (error) =~ 1/2 * e + + Of course, this error value applies only for the particular spline + approximation that is used in _gd_arc_segment. + + Detailed explanation https://itc.ktu.lt/index.php/ITC/article/view/11812 +*/ +static double +_arc_error_normalized(double angle) +{ + return 2.0 / 27.0 * pow(sin(angle / 4), 6) / pow(cos(angle / 4), 2); +} + +static double +_arc_max_angle_for_tolerance_normalized(double tolerance) +{ + double angle, error; + int i; + + /* Use table lookup to reduce search time in most cases. */ + struct + { + double angle; + double error; + } table[] = { + {M_PI / 1.0, 0.0185185185185185036127}, + {M_PI / 2.0, 0.000272567143730179811158}, + {M_PI / 3.0, 2.38647043651461047433e-05}, + {M_PI / 4.0, 4.2455377443222443279e-06}, + {M_PI / 5.0, 1.11281001494389081528e-06}, + {M_PI / 6.0, 3.72662000942734705475e-07}, + {M_PI / 7.0, 1.47783685574284411325e-07}, + {M_PI / 8.0, 6.63240432022601149057e-08}, + {M_PI / 9.0, 3.2715520137536980553e-08}, + {M_PI / 10.0, 1.73863223499021216974e-08}, + {M_PI / 11.0, 9.81410988043554039085e-09}, + }; + int table_size = ARRAY_LENGTH(table); + + for (i = 0; i < table_size; i++) + if (table[i].error < tolerance) + return table[i].angle; + + ++i; + do + { + angle = M_PI / i++; + error = _arc_error_normalized(angle); + } while (error > tolerance); + + return angle; +} + +static int +_arc_segments_needed(double angle, + double radius, + double tolerance) +{ + double major_axis, max_angle; + + major_axis = radius; + max_angle = _arc_max_angle_for_tolerance_normalized(tolerance / major_axis); + + return ceil(fabs(angle) / max_angle); +} + +/* We want to draw a single spline approximating a circular arc radius + R from angle A to angle B. Since we want a symmetric spline that + matches the endpoints of the arc in position and slope, we know + that the spline control points must be: + + (R * cos(A), R * sin(A)) + (R * cos(A) - h * sin(A), R * sin(A) + h * cos (A)) + (R * cos(B) + h * sin(B), R * sin(B) - h * cos (B)) + (R * cos(B), R * sin(B)) + + for some value of h. + + "Approximation of circular arcs by cubic polynomials", Michael + Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides + various values of h along with error analysis for each. + + From that paper, a very practical value of h is: + + h = 4/3 * R * tan(angle/4) + + This value does not give the spline with minimal error, but it does + provide a very good approximation, (6th-order convergence), and the + error expression is quite simple, (see the comment for + _arc_error_normalized). +*/ +static void +_gd_arc_segment(gdPathPtr path, + double xc, + double yc, + double radius, + double angle_A, + double angle_B) +{ + double r_sin_A, r_cos_A; + double r_sin_B, r_cos_B; + double h; + + r_sin_A = radius * sin(angle_A); + r_cos_A = radius * cos(angle_A); + r_sin_B = radius * sin(angle_B); + r_cos_B = radius * cos(angle_B); + + h = 4.0 / 3.0 * tan((angle_B - angle_A) / 4.0); + + gdPathCurveTo(path, + xc + r_cos_A - h * r_sin_A, + yc + r_sin_A + h * r_cos_A, + xc + r_cos_B + h * r_sin_B, + yc + r_sin_B - h * r_cos_B, + xc + r_cos_B, + yc + r_sin_B); +} + +static void +_gd_arc_in_direction(gdPathPtr path, + double xc, + double yc, + double radius, + double angle_min, + double angle_max, + arcDirectionType dir) +{ + if (!path) + return; + + if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) + { + angle_max = fmod(angle_max - angle_min, 2 * M_PI); + angle_min = fmod(angle_min, 2 * M_PI); + angle_max += angle_min + 2 * M_PI * MAX_FULL_CIRCLES; + } + + /* Recurse if drawing arc larger than pi */ + if (angle_max - angle_min > M_PI) + { + double angle_mid = angle_min + (angle_max - angle_min) / 2.0; + if (dir == ARC_CW) + { + _gd_arc_in_direction(path, xc, yc, radius, + angle_min, angle_mid, + dir); + + _gd_arc_in_direction(path, xc, yc, radius, + angle_mid, angle_max, + dir); + } + else + { + _gd_arc_in_direction(path, xc, yc, radius, + angle_mid, angle_max, + dir); + + _gd_arc_in_direction(path, xc, yc, radius, + angle_min, angle_mid, + dir); + } + } + else if (angle_max != angle_min) + { + int i, segments; + double step; + segments = _arc_segments_needed(angle_max - angle_min, + radius, DEFAULT_TOLERANCE); + step = (angle_max - angle_min) / segments; + segments -= 1; + + if (dir == ARC_CCW) + { + double t; + + t = angle_min; + angle_min = angle_max; + angle_max = t; + + step = -step; + } + gdPathMoveTo(path, xc + radius * cos(angle_min), yc + radius * sin(angle_min)); + + for (i = 0; i < segments; i++, angle_min += step) + { + _gd_arc_segment(path, xc, yc, radius, angle_min, angle_min + step); + } + + _gd_arc_segment(path, xc, yc, radius, angle_min, angle_max); + } + else + { + gdPathLineTo(path, xc + radius * cos(angle_min), yc + radius * sin(angle_min)); + } +} + +void _gd_arc_path(gdPathPtr path, + double xc, + double yc, + double radius, + double angle_min, + double angle_max) +{ + if (angle_min > M_PI2) { + angle_min = fmod(angle_min,M_PI2); + } + + if (angle_max > M_PI2) { + printf(" fmod max -"); + angle_max = fmod(angle_max, M_PI2); + } + + if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) { + angle_max = fmod (angle_max - angle_min, 2 * M_PI); + angle_min = fmod (angle_min, 2 * M_PI); + angle_max += angle_min + 2 * M_PI * MAX_FULL_CIRCLES; + } + _gd_arc_in_direction(path, xc, yc, + radius, + angle_min, angle_max, + ARC_CW); +} + +void _gd_arc_path_negative(gdPathPtr path, + double xc, + double yc, + double radius, + double angle1, + double angle2) +{ + _gd_arc_in_direction(path, xc, yc, + radius, + angle2, angle1, + ARC_CCW); +} diff --git a/ext/gd/libgd/gd_path_arc.h b/ext/gd/libgd/gd_path_arc.h new file mode 100644 index 000000000000..111ec8141829 --- /dev/null +++ b/ext/gd/libgd/gd_path_arc.h @@ -0,0 +1,24 @@ +#ifndef GD_PATH_ARC_H +#define GD_PATH_ARC_H +void +_gd_arc_path (gdPathPtr path, + double xc, + double yc, + double radius, + double angle1, + double angle2); + +void +_gd_arc_path_negative(gdPathPtr path, + double xc, + double yc, + double radius, + double angle1, + double angle2); + + +typedef enum arcDirectionTypeStruct { + ARC_CW, + ARC_CCW +} arcDirectionType; +#endif // GD_PATH_ARC_H diff --git a/ext/gd/libgd/gd_path_dash.c b/ext/gd/libgd/gd_path_dash.c new file mode 100644 index 000000000000..91266bd437e9 --- /dev/null +++ b/ext/gd/libgd/gd_path_dash.c @@ -0,0 +1,121 @@ +#include +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gd_intern.h" +#include "gdhelpers.h" +#include "gd_errors.h" + +#include "gd_array.h" +#include "gd_path.h" +#include "gd_path_dash.h" +#include "gd_path_matrix.h" + +gdPathDashPtr gdPathDashCreate(const double *data, int size, double offset) +{ + if (data == NULL || size == 0) + return NULL; + + gdPathDashPtr dash = gdMalloc(sizeof(gdPathDash)); + if (!dash) + { + return NULL; + } + dash->offset = offset; + dash->data = gdMalloc((size_t)size * sizeof(double)); + dash->size = size; + memcpy(dash->data, data, (size_t)size * sizeof(double)); + return dash; +} + +void gdPathDashDestroy(gdPathDashPtr dash) +{ + if (dash == NULL) + return; + gdFree(dash->data); + gdFree(dash); +} + +gdPathDashPtr gdPathDashClone(const gdPathDashPtr dash) +{ + if (dash == NULL) + return NULL; + + return gdPathDashCreate(dash->data, dash->size, dash->offset); +} + +gdPathPtr gdPathApplyDash(const gdPathDashPtr dash, const gdPathPtr path) +{ + gdPathPtr flat = gdPathDuplicateFlattened(path); + gdPathPtr result = gdPathCreate(); + + gdArrayReallocBy(&result->elements, gdArrayNumElements(&flat->elements)); + gdArrayReallocBy(&result->points, gdArrayNumElements(&flat->points)); + + int toggle = 1; + int offset = 0; + double phase = dash->offset; + while(phase >= dash->data[offset]) + { + toggle = !toggle; + phase -= dash->data[offset]; + if(++offset == dash->size) offset = 0; + } + + gdPathOpsPtr elements = (gdPathOpsPtr)gdArrayGetData(&flat->elements); + gdPathOpsPtr end = elements + gdArrayNumElements(&flat->elements); + gdPointFPtr points = (gdPointFPtr)gdArrayGetData(&flat->points); + + while(elements < end) + { + int itoggle = toggle; + int ioffset = offset; + double iphase = phase; + + double x0 = points->x; + double y0 = points->y; + + if(itoggle) gdPathMoveTo(result, x0, y0); + ++elements; + ++points; + while(elements < end + && *elements==gdPathOpsLineTo) + { + double dx = points->x - x0; + double dy = points->y - y0; + double dist0 = sqrt(dx*dx + dy*dy); + double dist1 = 0; + while(dist0 - dist1 > dash->data[ioffset] - iphase) + { + dist1 += dash->data[ioffset] - iphase; + double a = dist1 / dist0; + double x = x0 + a * dx; + double y = y0 + a * dy; + + if(itoggle) gdPathLineTo(result, x, y); + else { + gdPathMoveTo(result, x, y); + } + + itoggle = !itoggle; + iphase = 0; + if(++ioffset==dash->size) ioffset = 0; + } + + iphase += dist0 - dist1; + + x0 = points->x; + y0 = points->y; + + if(itoggle) gdPathLineTo(result, x0, y0); + + ++elements; + ++points; + } + } + + gdPathDestroy(flat); + return result; +} diff --git a/ext/gd/libgd/gd_path_dash.h b/ext/gd/libgd/gd_path_dash.h new file mode 100644 index 000000000000..bd142ce0b11c --- /dev/null +++ b/ext/gd/libgd/gd_path_dash.h @@ -0,0 +1,26 @@ +#ifndef GD_PATH_DASH_H +#define GD_PATH_DASH_H + + +#define _dash_init(dash) \ + do { \ + dash.data = NULL; \ + dash.size = 0; \ + dash.capacity = 0; \ + } while(0) + +#define _dash_allocate(dash, count) \ + do { \ + if(dash.size + count > dash.capacity) { \ + int capacity = dash.size + count; \ + int newcapacity = dash.capacity == 0 ? 8 : dash.capacity; \ + while(newcapacity < capacity) { newcapacity *= 2; } \ + dash.data = gdRealloc(dash.data, (size_t)newcapacity * sizeof(dash.data[0])); \ + dash.capacity = newcapacity; \ + } \ + } while(0) + +gdPathDashPtr gdPathDashCreate(const double *data, int size, double offset); +void gdPathDashDestroy(gdPathDashPtr dash); +gdPathPtr gdPathApplyDash(const gdPathDashPtr dash, const gdPathPtr path); +#endif // GD_PATH_DASH_H diff --git a/ext/gd/libgd/gd_path_matrix.c b/ext/gd/libgd/gd_path_matrix.c new file mode 100644 index 000000000000..2797be80c915 --- /dev/null +++ b/ext/gd/libgd/gd_path_matrix.c @@ -0,0 +1,245 @@ +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd_vector2d_private.h" +#include "gd_path_matrix.h" + +void gdPathMatrixInit(gdPathMatrixPtr matrix, + double m00, double m10, + double m01, double m11, + double m02, double m12) +{ + matrix->m00 = m00; matrix->m10 = m10; + matrix->m01 = m01; matrix->m11 = m11; + matrix->m02 = m02; matrix->m12 = m12; +} + +BGD_DECLARE(void) gdPathMatrixInitIdentity(gdPathMatrixPtr matrix) +{ + matrix->m00 = 1.0; matrix->m10 = 0.0; + matrix->m01 = 0.0; matrix->m11 = 1.0; + matrix->m02 = 0.0; matrix->m12 = 0.0; +} + +BGD_DECLARE(void) gdPathMatrixInitTranslate(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrixInit(matrix, 1.0, 0.0, 0.0, 1.0, x, y); +} + +BGD_DECLARE(void) gdPathMatrixInitScale(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrixInit(matrix, x, 0.0, 0.0, y, 0.0, 0.0); +} + +void gdPathMatrixInitShear(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrixInit(matrix, 1.0, tan(y), tan(x), 1.0, 0.0, 0.0); +} + +void gdPathMatrixInitRotate(gdPathMatrixPtr matrix, double radians) +{ + double c = cos(radians); + double s = sin(radians); + + gdPathMatrixInit(matrix, c, s, -s, c, 0.0, 0.0); +} + +void gdPathMatrixInitRotateTranslate(gdPathMatrixPtr matrix, double radians, double x, double y) +{ + double c = cos(radians); + double s = sin(radians); + + double cx = x * (1 - c) + y * s; + double cy = y * (1 - c) - x * s; + + gdPathMatrixInit(matrix, c, s, -s, c, cx, cy); +} + +void gdPathMatrixTranslate(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrix m; + gdPathMatrixInitTranslate(&m, x, y); + gdPathMatrixMultiply(matrix, &m, matrix); +} + +BGD_DECLARE(void) gdPathMatrixScale(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrix m; + gdPathMatrixInitScale(&m, x, y); + gdPathMatrixMultiply(matrix, &m, matrix); +} + +void gdPathMatrixShear(gdPathMatrixPtr matrix, double x, double y) +{ + gdPathMatrix m; + gdPathMatrixInitShear(&m, x, y); + gdPathMatrixMultiply(matrix, &m, matrix); +} + +BGD_DECLARE(void) gdPathMatrixRotate(gdPathMatrixPtr matrix, double radians) +{ + gdPathMatrix m; + gdPathMatrixInitRotate(&m, radians); + gdPathMatrixMultiply(matrix, &m, matrix); +} + +void gdPathMatrixRotateTranslate(gdPathMatrixPtr matrix, double radians, double x, double y) +{ + gdPathMatrix m; + gdPathMatrixInitRotateTranslate(&m, radians, x, y); + gdPathMatrixMultiply(matrix, &m, matrix); +} + +void gdPathMatrixMultiply(gdPathMatrixPtr matrix, const gdPathMatrixPtr a, const gdPathMatrixPtr b) +{ + double m00 = a->m00 * b->m00 + a->m10 * b->m01; + double m10 = a->m00 * b->m10 + a->m10 * b->m11; + double m01 = a->m01 * b->m00 + a->m11 * b->m01; + double m11 = a->m01 * b->m10 + a->m11 * b->m11; + double m02 = a->m02 * b->m00 + a->m12 * b->m01 + b->m02; + double m12 = a->m02 * b->m10 + a->m12 * b->m11 + b->m12; + + gdPathMatrixInit(matrix, m00, m10, m01, m11, m02, m12); +} + +int gdPathMatrixInvert(gdPathMatrixPtr matrix) +{ + double det = (matrix->m00 * matrix->m11 - matrix->m10 * matrix->m01); + if(det == 0.0) + return 0; + + double inv_det = 1.0 / det; + double m00 = matrix->m00 * inv_det; + double m10 = matrix->m10 * inv_det; + double m01 = matrix->m01 * inv_det; + double m11 = matrix->m11 * inv_det; + double m02 = (matrix->m01 * matrix->m12 - matrix->m11 * matrix->m02) * inv_det; + double m12 = (matrix->m10 * matrix->m02 - matrix->m00 * matrix->m12) * inv_det; + + gdPathMatrixInit(matrix, m11, -m10, -m01, m00, m02, m12); + return 1; +} + +void gdPathMatrixMap(const gdPathMatrixPtr matrix, double x, double y, double* _x, double* _y) +{ + *_x = x * matrix->m00 + y * matrix->m01 + matrix->m02; + *_y = x * matrix->m10 + y * matrix->m11 + matrix->m12; +} + +void gdPathMatrixMapPoint(const gdPathMatrixPtr matrix, const gdPointFPtr src, gdPointFPtr dst) +{ + gdPathMatrixMap(matrix, src->x, src->y, &dst->x, &dst->y); +} + +void gdPathMatrixMapRect(const gdPathMatrixPtr matrix, const gdRectFPtr src, gdRectFPtr dst) +{ + gdPointF p[4]; + p[0].x = src->x; + p[0].y = src->y; + p[1].x = src->x + src->w; + p[1].y = src->y; + p[2].x = src->x + src->w; + p[2].y = src->y + src->h; + p[3].x = src->x; + p[3].y = src->y + src->h; + + gdPathMatrixMapPoint(matrix, &p[0], &p[0]); + gdPathMatrixMapPoint(matrix, &p[1], &p[1]); + gdPathMatrixMapPoint(matrix, &p[2], &p[2]); + gdPathMatrixMapPoint(matrix, &p[3], &p[3]); + + double l = p[0].x; + double t = p[0].y; + double r = p[0].x; + double b = p[0].y; + + for(int i = 1;i < 4;i++) + { + if(p[i].x < l) l = p[i].x; + if(p[i].x > r) r = p[i].x; + if(p[i].y < t) t = p[i].y; + if(p[i].y > b) b = p[i].y; + } + + dst->x = l; + dst->y = t; + dst->w = r - l; + dst->h = b - t; +} + +#define SCALING_EPSILON (double)(1.0 / 256.0) +double _matrix_compute_determinant (const gdPathMatrixPtr matrix) +{ + double a, b, c, d; + + a = matrix->m00; b = matrix->m10; + c = matrix->m01; d = matrix->m11; + + return a*d - b*c; +} + +void +_matrix_get_affine (const gdPathMatrixPtr matrix, + double *m00, double *m10, + double *m01, double *m11, + double *m02, double *m12) +{ + *m00 = matrix->m00; + *m10 = matrix->m10; + + *m01 = matrix->m01; + *m11 = matrix->m11; + + if (m02) + *m02 = matrix->m02; + if (m12) + *m12 = matrix->m12; +} + +int _matrix_has_unity_scale (const gdPathMatrixPtr matrix) +{ + /* check that the determinant is near +/-1 */ + double det = _matrix_compute_determinant (matrix); + if (fabs (det * det - 1.0) < SCALING_EPSILON) { + /* check that one axis is close to zero */ + if (fabs (matrix->m01) < SCALING_EPSILON && + fabs (matrix->m10) < SCALING_EPSILON) + return 1; + if (fabs (matrix->m00) < SCALING_EPSILON && + fabs (matrix->m11) < SCALING_EPSILON) + return 1; + /* If rotations are allowed then it must instead test for + * orthogonality. This is m00*m01+m10*m11 ~= 0. + */ + } + return 0; +} + +double _gd_matrix_transformed_circle_major_axis (const gdPathMatrixPtr matrix, double radius) +{ + double a, b, c, d, f, g, h, i, j; + + if (_matrix_has_unity_scale (matrix)) return radius; + + _matrix_get_affine (matrix, + &a, &b, + &c, &d, + NULL, NULL); + + i = a*a + b*b; + j = c*c + d*d; + + f = 0.5 * (i + j); + g = 0.5 * (i - j); + h = a*c + b*d; + + return radius * sqrt (f + hypot (g, h)); + + /* + * we don't need the minor axis length, which is + * double min = radius * sqrt (f - sqrt (g*g+h*h)); + */ +} diff --git a/ext/gd/libgd/gd_path_matrix.h b/ext/gd/libgd/gd_path_matrix.h new file mode 100644 index 000000000000..7a6a2734fad7 --- /dev/null +++ b/ext/gd/libgd/gd_path_matrix.h @@ -0,0 +1,25 @@ +#ifndef GD_PATH_MATRIX_H +#define GD_PATH_MATRIX_H + +void gdPathMatrixInit(gdPathMatrixPtr matrix, double m00, double m10, double m01, double m11, double m02, double m12); +BGD_DECLARE(void) gdPathMatrixInitIdentity(gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdPathMatrixInitTranslate(gdPathMatrixPtr matrix, double x, double y); +BGD_DECLARE(void) gdPathMatrixInitScale(gdPathMatrixPtr matrix, double x, double y); +void gdPathMatrixInitShear(gdPathMatrixPtr matrix, double x, double y); +void gdPathMatrixInitRotate(gdPathMatrixPtr matrix, double radians); +void gdPathMatrixInitRotateTranslate(gdPathMatrixPtr matrix, double radians, double x, double y); +void gdPathMatrixTranslate(gdPathMatrixPtr matrix, double x, double y); +BGD_DECLARE(void) gdPathMatrixScale(gdPathMatrixPtr matrix, double x, double y); +void gdPathMatrixShear(gdPathMatrixPtr matrix, double x, double y); +BGD_DECLARE(void) gdPathMatrixRotate(gdPathMatrixPtr matrix, double radians); +void gdPathMatrixRotateTranslate(gdPathMatrixPtr matrix, double radians, double x, double y); +void gdPathMatrixMultiply(gdPathMatrixPtr matrix, const gdPathMatrixPtr a, const gdPathMatrixPtr b); +int gdPathMatrixInvert(gdPathMatrixPtr matrix); +void gdPathMatrixMap(const gdPathMatrixPtr matrix, double x, double y, double* _x, double* _y); +void gdPathMatrixMapPoint(const gdPathMatrixPtr matrix, const gdPointFPtr src, gdPointFPtr dst); +void gdPathMatrixMapRect(const gdPathMatrixPtr matrix, const gdRectFPtr src, gdRectFPtr dst); + +double _gd_matrix_transformed_circle_major_axis (const gdPathMatrixPtr matrix, double radius); +int _matrix_has_unity_scale (const gdPathMatrixPtr matrix); + +#endif // GD_PATH_MATRIX_H diff --git a/ext/gd/libgd/gd_path_outline.h b/ext/gd/libgd/gd_path_outline.h new file mode 100644 index 000000000000..9ae0a4cf139e --- /dev/null +++ b/ext/gd/libgd/gd_path_outline.h @@ -0,0 +1,21 @@ +#ifndef GD_PATH_OUTLINE_H +#define GD_PATH_OUTLINE_H + +#include "gd_vector2d_private.h" +#include "ftraster/gd_ft_raster.h" + +/* Internal gdPath <-> FreeType outline bridge. */ +GD_FT_Outline *gd_ft_outline_create(int points, int contours); +void gd_ft_outline_close(GD_FT_Outline *outline); +void gd_ft_outline_end(GD_FT_Outline *outline); +void gd_ft_outline_move_to(GD_FT_Outline *outline, double x, double y); +void gd_ft_outline_line_to(GD_FT_Outline *outline, double x, double y); +void gd_ft_outline_cubic_to(GD_FT_Outline *outline, double x1, double y1, + double x2, double y2, double x3, double y3); +void gd_ft_outline_conic_to(GD_FT_Outline *outline, double x1, double y1, + double x2, double y2); +GD_FT_Outline *gd_ft_outline_convert(const gdPathPtr path, + const gdPathMatrixPtr matrix); +void gd_ft_outline_destroy(GD_FT_Outline *outline); + +#endif diff --git a/ext/gd/libgd/gd_path_stroke.c b/ext/gd/libgd/gd_path_stroke.c new file mode 100644 index 000000000000..e0300b431314 --- /dev/null +++ b/ext/gd/libgd/gd_path_stroke.c @@ -0,0 +1,191 @@ +#include "gd_vector2d_private.h" +#include "gdhelpers.h" +#include "gd_path.h" +#include "gd_path_matrix.h" +#include "gd_path_outline.h" +#include "ftraster/gd_ft_stroker.h" +#include "ftraster/gd_ft_raster.h" +#include + +static gdPathPtr ft_outline_to_gdpath(const GD_FT_Outline* outline) +{ + if (!outline || outline->n_points == 0) + return NULL; + + gdPathPtr path = gdPathCreate(); + if (!path) + return NULL; + + int first = 0; + for (int contour = 0; contour < outline->n_contours; contour++) { + int last = outline->contours[contour]; + int point = first; + GD_FT_Vector start; + + if (last < first) + goto invalid_outline; + + char first_tag = GD_FT_CURVE_TAG(outline->tags[first]); + char last_tag = GD_FT_CURVE_TAG(outline->tags[last]); + + if (first_tag == GD_FT_CURVE_TAG_ON) { + start = outline->points[point++]; + } else if (first_tag == GD_FT_CURVE_TAG_CONIC) { + if (last_tag == GD_FT_CURVE_TAG_ON) { + start = outline->points[last--]; + } else if (last_tag == GD_FT_CURVE_TAG_CONIC) { + start.x = (outline->points[first].x + outline->points[last].x) / 2; + start.y = (outline->points[first].y + outline->points[last].y) / 2; + } else { + goto invalid_outline; + } + } else { + goto invalid_outline; + } + + gdPathMoveTo(path, start.x / 64.0, start.y / 64.0); + + while (point <= last) { + char tag = GD_FT_CURVE_TAG(outline->tags[point]); + GD_FT_Vector current = outline->points[point++]; + + if (tag == GD_FT_CURVE_TAG_ON) { + gdPathLineTo(path, current.x / 64.0, current.y / 64.0); + continue; + } + + if (tag == GD_FT_CURVE_TAG_CONIC) { + GD_FT_Vector control = current; + for (;;) { + if (point > last) { + gdPathQuadTo(path, control.x / 64.0, control.y / 64.0, + start.x / 64.0, start.y / 64.0); + break; + } + + tag = GD_FT_CURVE_TAG(outline->tags[point]); + current = outline->points[point++]; + if (tag == GD_FT_CURVE_TAG_ON) { + gdPathQuadTo(path, control.x / 64.0, control.y / 64.0, + current.x / 64.0, current.y / 64.0); + break; + } + if (tag != GD_FT_CURVE_TAG_CONIC) + goto invalid_outline; + + GD_FT_Vector middle = { + (control.x + current.x) / 2, + (control.y + current.y) / 2 + }; + gdPathQuadTo(path, control.x / 64.0, control.y / 64.0, + middle.x / 64.0, middle.y / 64.0); + control = current; + } + continue; + } + + if (tag != GD_FT_CURVE_TAG_CUBIC || point > last || + GD_FT_CURVE_TAG(outline->tags[point]) != GD_FT_CURVE_TAG_CUBIC) + goto invalid_outline; + + GD_FT_Vector control1 = current; + GD_FT_Vector control2 = outline->points[point++]; + GD_FT_Vector end = start; + if (point <= last) { + if (GD_FT_CURVE_TAG(outline->tags[point]) != GD_FT_CURVE_TAG_ON) + goto invalid_outline; + end = outline->points[point++]; + } + gdPathCurveTo(path, control1.x / 64.0, control1.y / 64.0, + control2.x / 64.0, control2.y / 64.0, + end.x / 64.0, end.y / 64.0); + } + + gdPathClose(path); + first = outline->contours[contour] + 1; + } + + return path; + +invalid_outline: + gdPathDestroy(path); + return NULL; +} + +GD_VECTOR2D_INTERNAL gdPathPtr gdPathStrokeToPath(const gdPathPtr path, const gdStrokePtr stroke, const gdPathMatrixPtr matrix) +{ + if (!path || !stroke || stroke->width <= 0) + return NULL; + + GD_FT_Outline *outline = gd_ft_outline_convert(path, matrix); + if (!outline) + return NULL; + + GD_FT_Stroker stroker; + GD_FT_Stroker_New(&stroker); + + double radius = stroke->width / 2.0; + GD_FT_Fixed ftWidth = (GD_FT_Fixed)(radius * 64); + GD_FT_Fixed ftMiterLimit = (GD_FT_Fixed)(stroke->miterlimit * 65536); + + GD_FT_Stroker_LineCap ftCap; + switch (stroke->cap) + { + case gdLineCapSquare: + ftCap = GD_FT_STROKER_LINECAP_SQUARE; + break; + case gdLineCapRound: + ftCap = GD_FT_STROKER_LINECAP_ROUND; + break; + case gdLineCapButt: + default: + ftCap = GD_FT_STROKER_LINECAP_BUTT; + break; + } + + GD_FT_Stroker_LineJoin ftJoin; + switch (stroke->join) + { + case gdLineJoinBevel: + ftJoin = GD_FT_STROKER_LINEJOIN_BEVEL; + break; + case gdLineJoinRound: + ftJoin = GD_FT_STROKER_LINEJOIN_ROUND; + break; + case gdLineJoinMiter: + default: + ftJoin = GD_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } + + GD_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit); + + GD_FT_Stroker_ParseOutline(stroker, outline); + + GD_FT_UInt points, contours; + GD_FT_Stroker_GetCounts(stroker, &points, &contours); + + GD_FT_Outline *strokeOutline = gd_ft_outline_create((int)points, (int)contours); + if (!strokeOutline) + { + GD_FT_Stroker_Done(stroker); + gd_ft_outline_destroy(outline); + return NULL; + } + + // Use combined export (both borders + caps) - works for both open and closed paths + GD_FT_Stroker_Export(stroker, strokeOutline); + GD_FT_Stroker_Done(stroker); + + gdPathPtr strokePath = ft_outline_to_gdpath(strokeOutline); + + gd_ft_outline_destroy(outline); + gd_ft_outline_destroy(strokeOutline); + + if (!strokePath) + { + return NULL; + } + + return strokePath; +} diff --git a/ext/gd/libgd/gd_png.c b/ext/gd/libgd/gd_png.c index 655eee77b906..81460a953f6e 100644 --- a/ext/gd/libgd/gd_png.c +++ b/ext/gd/libgd/gd_png.c @@ -1,55 +1,286 @@ -#include -#include -#include -#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include "gd.h" #include "gd_errors.h" +#include "gd_intern.h" +#include +#include +#include +#include +#include /* JCE: Arrange HAVE_LIBPNG so that it can be set in gd.h */ #ifdef HAVE_LIBPNG -#include "png.h" /* includes zlib.h and setjmp.h */ #include "gdhelpers.h" +#include "png.h" /* includes zlib.h and setjmp.h */ +#include "zlib.h" #define TRUE 1 #define FALSE 0 +static const unsigned char gdPngSignature[8] = {137, 80, 78, 71, + 13, 10, 26, 10}; + + +BGD_DECLARE(const char *) gdPngGetVersionString(void) +{ + return PNG_LIBPNG_VER_STRING; +} + +static unsigned int gdPngGetUint32(const unsigned char *data) { + return ((unsigned int)data[0] << 24) | ((unsigned int)data[1] << 16) | + ((unsigned int)data[2] << 8) | (unsigned int)data[3]; +} + +static void gdPngPutUint32(unsigned char *data, size_t value) { + data[0] = (unsigned char)((value >> 24) & 0xff); + data[1] = (unsigned char)((value >> 16) & 0xff); + data[2] = (unsigned char)((value >> 8) & 0xff); + data[3] = (unsigned char)(value & 0xff); +} + +static int gdPngChunkIs(const unsigned char *type, const char *name) { + return memcmp(type, name, 4) == 0; +} + +static int gdPngIsXmpItxt(const unsigned char *data, size_t size) { + static const char keyword[] = "XML:com.adobe.xmp"; + size_t keyword_size = sizeof(keyword) - 1; + + return size > keyword_size && memcmp(data, keyword, keyword_size) == 0 && + data[keyword_size] == 0; +} + +static int gdPngIsRawProfile(const unsigned char *data, size_t size, + const char *profile_type) { + static const char prefix[] = "Raw profile type "; + size_t prefix_size = sizeof(prefix) - 1; + size_t profile_type_size; + + if (data == NULL) { + return FALSE; + } + + profile_type_size = strlen(profile_type); + return size > prefix_size + profile_type_size && + memcmp(data, prefix, prefix_size) == 0 && + memcmp(data + prefix_size, profile_type, profile_type_size) == 0 && + data[prefix_size + profile_type_size] == 0; +} + +static int gdPngSetTextProfile(gdImageMetadata *metadata, + const unsigned char *data, size_t size) { + const unsigned char *nul; + size_t keyword_size; + char *key; + int status; + + nul = (const unsigned char *)memchr(data, 0, size); + if (nul == NULL || nul == data) { + return GD_META_OK; + } + + keyword_size = (size_t)(nul - data); + key = (char *)gdMalloc(sizeof("png:text:") + keyword_size); + if (key == NULL) { + return GD_META_ERR_NOMEM; + } + memcpy(key, "png:text:", sizeof("png:text:") - 1); + memcpy(key + sizeof("png:text:") - 1, data, keyword_size); + key[sizeof("png:text:") - 1 + keyword_size] = '\0'; + + status = gdImageMetadataSetProfile(metadata, key, data, size); + gdFree(key); + return status; +} + +static int gdPngReadMetadataFromMemory(const unsigned char *png, + size_t png_size, + gdImageMetadata *metadata) { + size_t pos = 8; + + if (metadata == NULL) { + return GD_META_OK; + } + if (png == NULL || png_size < 8 || memcmp(png, gdPngSignature, 8) != 0) { + return GD_META_ERR_FORMAT; + } + + while (pos + 12 <= png_size) { + unsigned int chunk_size = gdPngGetUint32(png + pos); + const unsigned char *type = png + pos + 4; + const unsigned char *chunk_data = png + pos + 8; + int status = GD_META_OK; + + if ((size_t)chunk_size > png_size - pos - 12) { + return GD_META_ERR_PARSE; + } + + if (gdPngChunkIs(type, "eXIf")) { + status = gdImageMetadataSetProfile(metadata, "exif", chunk_data, + chunk_size); + } else if (gdPngChunkIs(type, "zTXt") && + gdPngIsRawProfile(chunk_data, chunk_size, "exif")) { + status = gdImageMetadataSetProfile(metadata, "exif", chunk_data, + chunk_size); + } else if (gdPngChunkIs(type, "iCCP")) { + status = gdImageMetadataSetProfile(metadata, "icc", chunk_data, + chunk_size); + } else if (gdPngChunkIs(type, "iTXt") && + gdPngIsXmpItxt(chunk_data, chunk_size)) { + status = gdImageMetadataSetProfile(metadata, "xmp", chunk_data, + chunk_size); + } else if (gdPngChunkIs(type, "zTXt") && + gdPngIsRawProfile(chunk_data, chunk_size, "xmp")) { + status = gdImageMetadataSetProfile(metadata, "xmp", chunk_data, + chunk_size); + } else if (gdPngChunkIs(type, "tEXt")) { + status = gdPngSetTextProfile(metadata, chunk_data, chunk_size); + } + if (status != GD_META_OK) { + return status; + } + + pos += (size_t)chunk_size + 12; + if (gdPngChunkIs(type, "IEND")) { + return GD_META_OK; + } + } + + return GD_META_ERR_PARSE; +} + +static int gdPngAppendChunk(unsigned char *out, size_t *out_pos, + const char *type, const unsigned char *data, + size_t size) { + uLong crc; + + if (data == NULL || size > UINT32_MAX) { + return GD_META_ERR_INVALID; + } + + gdPngPutUint32(out + *out_pos, size); + memcpy(out + *out_pos + 4, type, 4); + if (size != 0) { + memcpy(out + *out_pos + 8, data, size); + } + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, out + *out_pos + 4, (uInt)(size + 4)); + gdPngPutUint32(out + *out_pos + 8 + size, crc); + *out_pos += size + 12; + return GD_META_OK; +} + +static int gdPngMetadataChunkSize(size_t *total, const unsigned char *data, + size_t size) { + if (data == NULL) { + return GD_META_OK; + } + if (size > UINT32_MAX || (size_t)-1 - *total < size + 12) { + return GD_META_ERR_LIMIT; + } + *total += size + 12; + return GD_META_OK; +} + +static int gdPngShouldSkipChunk(const unsigned char *type, + const unsigned char *data, size_t size, + const gdImageMetadata *metadata) { + return (gdImageMetadataGetProfile(metadata, "exif", NULL) != NULL && + (gdPngChunkIs(type, "eXIf") || + (gdPngChunkIs(type, "zTXt") && + gdPngIsRawProfile(data, size, "exif")))) || + (gdImageMetadataGetProfile(metadata, "icc", NULL) != NULL && + gdPngChunkIs(type, "iCCP")) || + (gdImageMetadataGetProfile(metadata, "xmp", NULL) != NULL && + ((gdPngChunkIs(type, "iTXt") && gdPngIsXmpItxt(data, size)) || + (gdPngChunkIs(type, "zTXt") && + gdPngIsRawProfile(data, size, "xmp")))); +} + +static void *gdPngReadCtxToMemory(gdIOCtx *infile, int *size) { + enum { GD_PNG_ALLOC_STEP = 8192 }; + unsigned char *data = NULL; + int logical_size = 0; + int real_size = 0; + + if (size != NULL) { + *size = 0; + } + if (infile == NULL || size == NULL) { + return NULL; + } + + for (;;) { + int n; + + if (real_size - logical_size < GD_PNG_ALLOC_STEP) { + unsigned char *temp; + int new_size; + + if (real_size > INT_MAX - GD_PNG_ALLOC_STEP) { + gdFree(data); + return NULL; + } + new_size = real_size + GD_PNG_ALLOC_STEP; + temp = (unsigned char *)gdRealloc(data, new_size); + if (temp == NULL) { + gdFree(data); + return NULL; + } + data = temp; + real_size = new_size; + } + + n = gdGetBuf(data + logical_size, GD_PNG_ALLOC_STEP, infile); + if (n <= 0) { + break; + } + logical_size += n; + } + + *size = logical_size; + return data; +} + /*--------------------------------------------------------------------------- - gd_png.c Copyright 1999 Greg Roelofs and Thomas Boutell + gd_png.c Copyright 1999 Greg Roelofs and Thomas Boutell - The routines in this file, gdImagePng*() and gdImageCreateFromPng*(), - are drop-in replacements for gdImageGif*() and gdImageCreateFromGif*(), - except that these functions are noisier in the case of errors (comment - out all fprintf() statements to disable that). + The routines in this file, gdImagePng*() and gdImageCreateFromPng*(), + are drop-in replacements for gdImageGif*() and gdImageCreateFromGif*(), + except that these functions are noisier in the case of errors (comment + out all fprintf() statements to disable that). - GD 2.0 supports RGBA truecolor and will read and write truecolor PNGs. - GD 2.0 supports 8 bits of color resolution per channel and - 7 bits of alpha channel resolution. Images with more than 8 bits - per channel are reduced to 8 bits. Images with an alpha channel are - only able to resolve down to '1/128th opaque' instead of '1/256th', - and this conversion is also automatic. I very much doubt you can see it. - Both tRNS and true alpha are supported. + GD 2.0 supports RGBA truecolor and will read and write truecolor PNGs. + GD 2.0 supports 8 bits of color resolution per channel and + 7 bits of alpha channel resolution. Images with more than 8 bits + per channel are reduced to 8 bits. Images with an alpha channel are + only able to resolve down to '1/128th opaque' instead of '1/256th', + and this conversion is also automatic. I very much doubt you can see it. + Both tRNS and true alpha are supported. - Gamma is ignored, and there is no support for text annotations. + Gamma is ignored, and there is no support for text annotations. - Last updated: 9 February 2001 + Last updated: 9 February 2001 ---------------------------------------------------------------------------*/ -const char * gdPngGetVersionString() -{ - return PNG_LIBPNG_VER_STRING; -} +/** + * File: PNG IO + * + * Read and write PNG images. + */ #ifdef PNG_SETJMP_SUPPORTED -typedef struct _jmpbuf_wrapper -{ +typedef struct _jmpbuf_wrapper { jmp_buf jmpbuf; } jmpbuf_wrapper; -static void gdPngErrorHandler (png_structp png_ptr, png_const_charp msg) -{ +static void gdPngErrorHandler(png_structp png_ptr, png_const_charp msg) { jmpbuf_wrapper *jmpbuf_ptr; /* This function, aside from the extra step of retrieving the "error @@ -59,67 +290,176 @@ static void gdPngErrorHandler (png_structp png_ptr, png_const_charp msg) * setjmp() and longjmp() are called from the same code, they are * guaranteed to have compatible notions of how big a jmp_buf is, * regardless of whether _BSD_SOURCE or anything else has (or has not) - * been defined. - */ + * been defined. */ - gd_error_ex(GD_WARNING, "gd-png: fatal libpng error: %s", msg); + gd_error_ex(GD_WARNING, "gd-png: fatal libpng error: %s\n", msg); - jmpbuf_ptr = png_get_error_ptr (png_ptr); + jmpbuf_ptr = png_get_error_ptr(png_ptr); if (jmpbuf_ptr == NULL) { /* we are completely hosed now */ - gd_error_ex(GD_ERROR, "gd-png: EXTREMELY fatal error: jmpbuf unrecoverable; terminating."); + gd_error_ex(GD_ERROR, "gd-png: EXTREMELY fatal error: jmpbuf " + "unrecoverable; terminating.\n"); + exit(99); } - longjmp (jmpbuf_ptr->jmpbuf, 1); + longjmp(jmpbuf_ptr->jmpbuf, 1); } -static void gdPngWarningHandler (png_structp png_ptr, png_const_charp msg) -{ +static void gdPngWarningHandler(UNUSED_PARAM(png_structp png_ptr), + png_const_charp msg) { gd_error_ex(GD_WARNING, "gd-png: libpng warning: %s", msg); } #endif -static void gdPngReadData (png_structp png_ptr, png_bytep data, png_size_t length) -{ +static void gdPngReadData(png_structp png_ptr, png_bytep data, + png_size_t length) { int check; - check = gdGetBuf(data, length, (gdIOCtx *) png_get_io_ptr(png_ptr)); - if (check != length) { + check = gdGetBuf(data, length, (gdIOCtx *)png_get_io_ptr(png_ptr)); + if (check != (int)length) { png_error(png_ptr, "Read Error: truncated data"); } } -static void gdPngWriteData (png_structp png_ptr, png_bytep data, png_size_t length) -{ - gdPutBuf (data, length, (gdIOCtx *) png_get_io_ptr(png_ptr)); +static void gdPngWriteData(png_structp png_ptr, png_bytep data, + png_size_t length) { + gdPutBuf(data, length, (gdIOCtx *)png_get_io_ptr(png_ptr)); } -static void gdPngFlushData (png_structp png_ptr) -{ -} +static void gdPngFlushData(png_structp png_ptr) { (void)png_ptr; } -gdImagePtr gdImageCreateFromPng (FILE * inFile) -{ +/* + Function: gdImageCreateFromPng + + is called to load images from PNG format + files. Invoke with an already opened + pointer to a FILE containing the desired + image. returns a to the new + image, or NULL if unable to load the image (most often because the + file is corrupt or does not contain a PNG + image). does not close the file. You can + inspect the sx and sy members of the image to determine its + size. The image must eventually be destroyed using + gdImageDestroy(). + + If the PNG image being loaded is a truecolor image, the resulting + gdImagePtr will refer to a truecolor image. If the PNG image being + loaded is a palette or grayscale image, the resulting gdImagePtr + will refer to a palette image. gd retains only 8 bits of + resolution for each of the red, green and blue channels, and only + 7 bits of resolution for the alpha channel. The former restriction + affects only a handful of very rare 48-bit color and 16-bit + grayscale PNG images. The second restriction affects all + semitransparent PNG images, but the difference is essentially + invisible to the eye. 7 bits of alpha channel resolution is, in + practice, quite a lot. + + Variants: + + creates an image from PNG data (i.e. the + contents of a PNG file) already in memory. + + reads in an image using the functions in + a struct. + + is similar to + but uses the old interface. + It is *obsolete*. + + Parameters: + + infile - The input FILE pointer. + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + ... inside a function ... + FILE *in; + in = fopen("mypng.png", "rb"); + im = gdImageCreateFromPng(in); + fclose(in); + // ... Use the image ... + gdImageDestroy(im); + + (end code) + */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPng(FILE *inFile) { gdImagePtr im; gdIOCtx *in = gdNewFileCtx(inFile); + if (in == NULL) + return NULL; im = gdImageCreateFromPngCtx(in); in->gd_free(in); - return im; } -gdImagePtr gdImageCreateFromPngPtr (int size, void *data) -{ +/* + Function: gdImageCreateFromPngPtr + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngPtr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) + return 0; + im = gdImageCreateFromPngCtx(in); + in->gd_free(in); + return im; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngPtrWithMetadata(int size, void *data, + gdImageMetadata *metadata) { + gdImagePtr im; + gdIOCtx *in; + + if (metadata != NULL) { + int status = gdPngReadMetadataFromMemory((const unsigned char *)data, + size, metadata); + if (status != GD_META_OK) { + return NULL; + } + } + + in = gdNewDynamicCtxEx(size, data, 0); + if (!in) + return 0; im = gdImageCreateFromPngCtx(in); in->gd_free(in); return im; } -/* This routine is based in part on the Chapter 13 demo code in "PNG: The - * Definitive Guide" (http://www.cdrom.com/pub/png/pngbook.html). +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngCtxWithMetadata(gdIOCtx *infile, + gdImageMetadata *metadata) { + void *data; + int size; + gdImagePtr im; + + data = gdPngReadCtxToMemory(infile, &size); + if (data == NULL) { + return NULL; + } + + im = gdImageCreateFromPngPtrWithMetadata(size, data, metadata); + gdFree(data); + return im; +} + +/* This routine is based in part on the Chapter 13 demo code in + * "PNG: The Definitive Guide" (http://www.libpng.org/pub/png/book/). */ -gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) -{ + +/* + Function: gdImageCreateFromPngCtx + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngCtx(gdIOCtx *infile) { png_byte sig[8]; #ifdef PNG_SETJMP_SUPPORTED jmpbuf_wrapper jbw; @@ -128,48 +468,47 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) png_infop info_ptr; png_uint_32 width, height, rowbytes, w, h, res_x, res_y; int bit_depth, color_type, interlace_type, unit_type; - int num_palette, num_trans; + int num_palette = 0, num_trans; png_colorp palette; png_color_16p trans_gray_rgb; png_color_16p trans_color_rgb; png_bytep trans; - volatile png_bytep image_data = NULL; - volatile png_bytepp row_pointers = NULL; + png_bytep image_data = NULL; + png_bytepp row_pointers = NULL; gdImagePtr im = NULL; int i, j, *open = NULL; volatile int transparent = -1; volatile int palette_allocated = FALSE; - /* Make sure the signature can't match by dumb luck -- TBB */ /* GRR: isn't sizeof(infile) equal to the size of the pointer? */ - memset (sig, 0, sizeof(sig)); + memset(sig, 0, sizeof(sig)); - /* first do a quick check that the file really is a PNG image; could - * have used slightly more general png_sig_cmp() function instead - */ + /* first do a quick check that the file really is a PNG image; could + * have used slightly more general png_sig_cmp() function instead */ if (gdGetBuf(sig, 8, infile) < 8) { return NULL; } if (png_sig_cmp(sig, 0, 8) != 0) { /* bad signature */ - return NULL; + return NULL; /* bad signature */ } #ifdef PNG_SETJMP_SUPPORTED - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &jbw, gdPngErrorHandler, gdPngWarningHandler); + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &jbw, + gdPngErrorHandler, gdPngWarningHandler); #else png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); #endif if (png_ptr == NULL) { - gd_error("gd-png error: cannot allocate libpng main struct"); + gd_error("gd-png error: cannot allocate libpng main struct\n"); return NULL; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { - gd_error("gd-png error: cannot allocate libpng info struct"); - png_destroy_read_struct (&png_ptr, NULL, NULL); + gd_error("gd-png error: cannot allocate libpng info struct\n"); + png_destroy_read_struct(&png_ptr, NULL, NULL); return NULL; } @@ -180,55 +519,52 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) */ /* setjmp() must be called in every non-callback function that calls a - * PNG-reading libpng function + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. */ #ifdef PNG_SETJMP_SUPPORTED if (setjmp(jbw.jmpbuf)) { - gd_error("gd-png error: setjmp returns error condition"); + gd_error("gd-png error: setjmp returns error condition 1\n"); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return NULL; } #endif - png_set_sig_bytes(png_ptr, 8); /* we already read the 8 signature bytes */ + png_set_sig_bytes(png_ptr, 8); /* we already read the 8 signature bytes */ - png_set_read_fn(png_ptr, (void *) infile, gdPngReadData); - png_read_info(png_ptr, info_ptr); /* read all PNG info up to image data */ + png_set_read_fn(png_ptr, (void *)infile, gdPngReadData); + png_set_user_limits(png_ptr, 0x7fffffffL, 0x7fffffffL); + png_read_info(png_ptr, info_ptr); /* read all PNG info up to image data */ - png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); - if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA) - || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { - im = gdImageCreateTrueColor((int) width, (int) height); + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_type, NULL, NULL); + if ((color_type == PNG_COLOR_TYPE_RGB) || + (color_type == PNG_COLOR_TYPE_RGB_ALPHA) || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + im = gdImageCreateTrueColor((int)width, (int)height); } else { - im = gdImageCreate((int) width, (int) height); + im = gdImageCreate((int)width, (int)height); } if (im == NULL) { - gd_error("gd-png error: cannot allocate gdImage struct"); - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - - return NULL; + gd_error("gd-png error: cannot allocate gdImage struct\n"); + goto error; } if (bit_depth == 16) { png_set_strip_16(png_ptr); } else if (bit_depth < 8) { - png_set_packing (png_ptr); /* expand to 1 byte per pixel */ + png_set_packing(png_ptr); /* expand to 1 byte per pixel */ } /* setjmp() must be called in every non-callback function that calls a - * PNG-reading libpng function + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. */ #ifdef PNG_SETJMP_SUPPORTED if (setjmp(jbw.jmpbuf)) { - gd_error("gd-png error: setjmp returns error condition"); - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - gdFree(image_data); - gdFree(row_pointers); - if (im) { - gdImageDestroy(im); - } - return NULL; + gd_error("gd-png error: setjmp returns error condition 2\n"); + goto error; } #endif @@ -247,93 +583,96 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) #endif switch (color_type) { - case PNG_COLOR_TYPE_PALETTE: - png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + case PNG_COLOR_TYPE_PALETTE: + png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); #ifdef DEBUG - gd_error("gd-png color_type is palette, colors: %d", num_palette); + gd_error("gd-png color_type is palette, colors: %d\n", num_palette); #endif /* DEBUG */ - if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { - /* gd 2.0: we support this rather thoroughly now. Grab the - * first fully transparent entry, if any, as the value of - * the simple-transparency index, mostly for backwards - * binary compatibility. The alpha channel is where it's - * really at these days. - */ - int firstZero = 1; - png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL); - for (i = 0; i < num_trans; ++i) { - im->alpha[i] = gdAlphaMax - (trans[i] >> 1); - if ((trans[i] == 0) && (firstZero)) { - transparent = i; - firstZero = 0; - } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + /* gd 2.0: we support this rather thoroughly now. Grab the + * first fully transparent entry, if any, as the value of + * the simple-transparency index, mostly for backwards + * binary compatibility. The alpha channel is where it's + * really at these days. + */ + int firstZero = 1; + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL); + for (i = 0; i < num_trans; ++i) { + im->alpha[i] = gdAlphaMax - (trans[i] >> 1); + if ((trans[i] == 0) && (firstZero)) { + /* 2.0.5: long-forgotten patch from Wez Furlong */ + transparent = i; + firstZero = 0; } } - break; - case PNG_COLOR_TYPE_GRAY: - /* create a fake palette and check for single-shade transparency */ - if ((palette = (png_colorp) gdMalloc (256 * sizeof (png_color))) == NULL) { - gd_error("gd-png error: cannot allocate gray palette"); - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + } + break; - return NULL; + case PNG_COLOR_TYPE_GRAY: + /* create a fake palette and check for single-shade transparency */ + if ((palette = (png_colorp)gdMalloc(256 * sizeof(png_color))) == NULL) { + gd_error("gd-png error: cannot allocate gray palette\n"); + goto error; + } + palette_allocated = TRUE; + if (bit_depth < 8) { + num_palette = 1 << bit_depth; + for (i = 0; i < 256; ++i) { + j = (255 * i) / (num_palette - 1); + palette[i].red = palette[i].green = palette[i].blue = j; } - palette_allocated = TRUE; - if (bit_depth < 8) { - num_palette = 1 << bit_depth; - for (i = 0; i < 256; ++i) { - j = (255 * i) / (num_palette - 1); - palette[i].red = palette[i].green = palette[i].blue = j; - } - } else { - num_palette = 256; - for (i = 0; i < 256; ++i) { - palette[i].red = palette[i].green = palette[i].blue = i; - } + } else { + num_palette = 256; + for (i = 0; i < 256; ++i) { + palette[i].red = palette[i].green = palette[i].blue = i; } - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_gray_rgb); - if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ - transparent = trans_gray_rgb->gray >> 8; - } else { - transparent = trans_gray_rgb->gray; - } - /* Note slight error in 16-bit case: up to 256 16-bit shades - * may get mapped to a single 8-bit shade, and only one of them - * is supposed to be transparent. IOW, both opaque pixels and - * transparent pixels will be mapped into the transparent entry. - * There is no particularly good way around this in the case - * that all 256 8-bit shades are used, but one could write some - * custom 16-bit code to handle the case where there are gdFree - * palette entries. This error will be extremely rare in - * general, though. (Quite possibly there is only one such - * image in existence.) - */ + } + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_gray_rgb); + if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ + transparent = trans_gray_rgb->gray >> 8; + } else { + transparent = trans_gray_rgb->gray; } - break; - - case PNG_COLOR_TYPE_GRAY_ALPHA: - png_set_gray_to_rgb(png_ptr); - ZEND_FALLTHROUGH; - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_RGB_ALPHA: - /* gd 2.0: we now support truecolor. See the comment above - * for a rare situation in which the transparent pixel may not - * work properly with 16-bit channels. - */ - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_color_rgb); - if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ - transparent = gdTrueColor(trans_color_rgb->red >> 8, - trans_color_rgb->green >> 8, - trans_color_rgb->blue >> 8); - } else { - transparent = gdTrueColor(trans_color_rgb->red, - trans_color_rgb->green, + /* Note slight error in 16-bit case: up to 256 16-bit shades + * may get mapped to a single 8-bit shade, and only one of them + * is supposed to be transparent. IOW, both opaque pixels and + * transparent pixels will be mapped into the transparent entry. + * There is no particularly good way around this in the case + * that all 256 8-bit shades are used, but one could write some + * custom 16-bit code to handle the case where there are gdFree + * palette entries. This error will be extremely rare in + * general, though. (Quite possibly there is only one such + * image in existence.) */ + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + png_set_gray_to_rgb(png_ptr); + // fall through + // Keep above comment, gcc recognizes it and silent its warning about + // fall through case here + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + /* gd 2.0: we now support truecolor. See the comment above + for a rare situation in which the transparent pixel may not + work properly with 16-bit channels. */ + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_get_tRNS(png_ptr, info_ptr, NULL, NULL, &trans_color_rgb); + if (bit_depth == 16) { /* png_set_strip_16() not yet in effect */ + transparent = gdTrueColor(trans_color_rgb->red >> 8, + trans_color_rgb->green >> 8, + trans_color_rgb->blue >> 8); + } else { + transparent = + gdTrueColor(trans_color_rgb->red, trans_color_rgb->green, trans_color_rgb->blue); - } } - break; + } + break; + default: + gd_error("gd-png color_type is unknown: %d\n", color_type); + goto error; } /* enable the interlace transform if supported */ @@ -345,17 +684,40 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) /* allocate space for the PNG image data */ rowbytes = png_get_rowbytes(png_ptr, info_ptr); - image_data = (png_bytep) safe_emalloc(rowbytes, height, 0); + if (overflow2(rowbytes, height)) + goto error; + image_data = (png_bytep)gdMalloc(rowbytes * height); + if (!image_data) { + gd_error("gd-png error: cannot allocate image data\n"); + goto error; + } + if (overflow2(height, sizeof(png_bytep))) + goto error; + + row_pointers = (png_bytepp)gdMalloc(height * sizeof(png_bytep)); + if (!row_pointers) { + gd_error("gd-png error: cannot allocate row pointers\n"); + goto error; + } - row_pointers = (png_bytepp) safe_emalloc(height, sizeof(png_bytep), 0); + /* setjmp() must be called in every non-callback function that calls a + * PNG-reading libpng function. We must reset it everytime we get a + * new allocation that we save in a stack variable. + */ +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(jbw.jmpbuf)) { + gd_error("gd-png error: setjmp returns error condition 3\n"); + goto error; + } +#endif /* set the individual row_pointers to point at the correct offsets */ for (h = 0; h < height; ++h) { row_pointers[h] = image_data + h * rowbytes; } - png_read_image(png_ptr, row_pointers); /* read whole image... */ - png_read_end(png_ptr, NULL); /* ...done! */ + png_read_image(png_ptr, row_pointers); /* read whole image... */ + png_read_end(png_ptr, NULL); /* ...done! */ if (!im->trueColor) { im->colorsTotal = num_palette; @@ -371,49 +733,48 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) open[i] = 1; } } - /* 2.0.12: Slaven Rezic: palette images are not the only images - * with a simple transparent color setting. - */ + with a simple transparent color setting */ im->transparent = transparent; im->interlace = (interlace_type == PNG_INTERLACE_ADAM7); /* can't nuke structs until done with palette */ png_destroy_read_struct(&png_ptr, &info_ptr, NULL); switch (color_type) { - case PNG_COLOR_TYPE_RGB: - for (h = 0; h < height; h++) { - int boffset = 0; - for (w = 0; w < width; w++) { - register png_byte r = row_pointers[h][boffset++]; - register png_byte g = row_pointers[h][boffset++]; - register png_byte b = row_pointers[h][boffset++]; - im->tpixels[h][w] = gdTrueColor (r, g, b); - } + case PNG_COLOR_TYPE_RGB: + for (h = 0; h < height; h++) { + int boffset = 0; + for (w = 0; w < width; w++) { + register png_byte r = row_pointers[h][boffset++]; + register png_byte g = row_pointers[h][boffset++]; + register png_byte b = row_pointers[h][boffset++]; + im->tpixels[h][w] = gdTrueColor(r, g, b); } - break; + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_RGB_ALPHA: + for (h = 0; h < height; h++) { + int boffset = 0; + for (w = 0; w < width; w++) { + register png_byte r = row_pointers[h][boffset++]; + register png_byte g = row_pointers[h][boffset++]; + register png_byte b = row_pointers[h][boffset++]; + + /* gd has only 7 bits of alpha channel resolution, and + * 127 is transparent, 0 opaque. A moment of convenience, + * a lifetime of compatibility. + */ - case PNG_COLOR_TYPE_GRAY_ALPHA: - case PNG_COLOR_TYPE_RGB_ALPHA: - for (h = 0; h < height; h++) { - int boffset = 0; - for (w = 0; w < width; w++) { - register png_byte r = row_pointers[h][boffset++]; - register png_byte g = row_pointers[h][boffset++]; - register png_byte b = row_pointers[h][boffset++]; - - /* gd has only 7 bits of alpha channel resolution, and - * 127 is transparent, 0 opaque. A moment of convenience, - * a lifetime of compatibility. - */ - - register png_byte a = gdAlphaMax - (row_pointers[h][boffset++] >> 1); - im->tpixels[h][w] = gdTrueColorAlpha(r, g, b, a); - } + register png_byte a = + gdAlphaMax - (row_pointers[h][boffset++] >> 1); + im->tpixels[h][w] = gdTrueColorAlpha(r, g, b, a); } - break; - - default: + } + break; + default: + if (!im->trueColor) { /* Palette image, or something coerced to be one */ for (h = 0; h < height; ++h) { for (w = 0; w < width; ++w) { @@ -422,157 +783,655 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) open[idx] = 0; } } + } } #ifdef DEBUG if (!im->trueColor) { for (i = num_palette; i < gdMaxColors; ++i) { if (!open[i]) { - gd_error("gd-png warning: image data references out-of-range color index (%d)", i); + fprintf(stderr, + "gd-png warning: image data references out-of-range" + " color index (%d)\n", + i); } } } #endif +done: if (palette_allocated) { gdFree(palette); } - gdFree(image_data); - gdFree(row_pointers); + if (image_data) + gdFree(image_data); + if (row_pointers) + gdFree(row_pointers); return im; + +error: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + if (im) { + gdImageDestroy(im); + im = NULL; + } + goto done; } -void gdImagePngEx (gdImagePtr im, FILE * outFile, int level, int basefilter) -{ +/* + Function: gdImagePngEx + + outputs the specified image to the specified file in + PNG format. The file must be open for writing. Under MSDOS and all + versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + In addition, allows the level of compression to be + specified. A compression level of 0 means "no compression." A + compression level of 1 means "compressed, but as quickly as + possible." A compression level of 9 means "compressed as much as + possible to produce the smallest possible file." A compression level + of -1 will use the default compression level at the time zlib was + compiled on your system. + + Variants: + + is equivalent to calling with + compression of -1. + + and write via a + instead of a file handle. + + and store the image file to + memory. + + Parameters: + + im - the image to write + outFile - the output FILE* object. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + Nothing. + + Example: + (start code) + + gdImagePtr im; + int black, white; + FILE *out; + + im = gdImageCreate(100, 100); // Create the image + white = gdImageColorAllocate(im, 255, 255, 255); // Alloc background + black = gdImageColorAllocate(im, 0, 0, 0); // Allocate drawing color + gdImageRectangle(im, 0, 0, 99, 99, black); // Draw rectangle + out = fopen("rect.png", "wb"); // Open output file (binary) + gdImagePngEx(im, out, 9); // Write PNG, max compression + fclose(out); // Close file + gdImageDestroy(im); // Destroy image + + (end code) +*/ +BGD_DECLARE(void) gdImagePngEx(gdImagePtr im, FILE *outFile, int level) { gdIOCtx *out = gdNewFileCtx(outFile); - gdImagePngCtxEx(im, out, level, basefilter); + if (out == NULL) + return; + gdImagePngCtxEx(im, out, level); out->gd_free(out); } -void gdImagePng (gdImagePtr im, FILE * outFile) -{ - gdIOCtx *out = gdNewFileCtx(outFile); - gdImagePngCtxEx(im, out, -1, -1); +/* + Function: gdImagePng + + Equivalent to calling with compression of -1. + + Parameters: + + im - the image to save. + outFile - the output FILE*. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImagePng(gdImagePtr im, FILE *outFile) { + gdImagePngEx(im, outFile, -1); +} + +static int _gdImagePngCtxWithOptions(gdImagePtr im, gdIOCtx *outfile, + const gdPngWriteOptions *options); + +BGD_DECLARE(void) gdPngWriteOptionsInit(gdPngWriteOptions *options) { + if (options == NULL) + return; + memset(options, 0, sizeof(*options)); + options->struct_size = sizeof(*options); + options->compression_level = -1; + options->filters = GD_PNG_FILTER_AUTO; + options->compression_strategy = GD_PNG_COMPRESSION_STRATEGY_DEFAULT; +} + +static int gdPngWriteOptionsValid(const gdPngWriteOptions *options) { + if (options->struct_size < sizeof(*options)) { + gd_error("gd-png error: invalid options structure size\n"); + return FALSE; + } + if (options->compression_level < -1 || options->compression_level > 9) { + gd_error("gd-png error: compression level must be -1 through 9\n"); + return FALSE; + } + if ((options->filters & ~GD_PNG_FILTER_ALL) != 0) { + gd_error("gd-png error: invalid filter mask\n"); + return FALSE; + } + if (options->compression_strategy < GD_PNG_COMPRESSION_STRATEGY_DEFAULT || + options->compression_strategy > GD_PNG_COMPRESSION_STRATEGY_FIXED) { + gd_error("gd-png error: invalid compression strategy\n"); + return FALSE; + } + return TRUE; +} + +BGD_DECLARE(int) +gdImagePngWithOptions(gdImagePtr im, FILE *outFile, + const gdPngWriteOptions *options) { + gdIOCtx *out; + int status; + if (im == NULL || outFile == NULL) + return 1; + out = gdNewFileCtx(outFile); + if (out == NULL) + return 1; + status = gdImagePngCtxWithOptions(im, out, options); out->gd_free(out); + return status; } -void * gdImagePngPtr (gdImagePtr im, int *size) -{ +/* + Function: gdImagePngPtr + + Equivalent to calling with compression of -1. + + See for more information. + + Parameters: + + im - the image to save. + size - Output: size in bytes of the result. + + Returns: + + A pointer to memory containing the image data or NULL on error. + +*/ +BGD_DECLARE(void *) gdImagePngPtr(gdImagePtr im, int *size) { + return gdImagePngPtrEx(im, size, -1); +} + +/* + Function: gdImagePngPtrEx + + Identical to except that it returns a pointer to a + memory area with the PNG data. This memory must be freed by the + caller when it is no longer needed. **The caller must invoke + gdFree(), not free()** + + The 'size' parameter receives the total size of the block of + memory. + + See for more information. + + Parameters: + + im - the image to save. + size - Output: size in bytes of the result. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + A pointer to memory containing the image data or NULL on error. + +*/ +BGD_DECLARE(void *) gdImagePngPtrEx(gdImagePtr im, int *size, int level) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); - gdImagePngCtxEx(im, out, -1, -1); - rv = gdDPExtractData(out, size); + gdPngWriteOptions options; + if (out == NULL) + return NULL; + gdPngWriteOptionsInit(&options); + options.compression_level = level; + if (!_gdImagePngCtxWithOptions(im, out, &options)) + rv = gdDPExtractData(out, size); + else + rv = NULL; out->gd_free(out); - return rv; } -void * gdImagePngPtrEx (gdImagePtr im, int *size, int level, int basefilter) -{ - void *rv; - gdIOCtx *out = gdNewDynamicCtx(2048, NULL); - gdImagePngCtxEx(im, out, level, basefilter); - rv = gdDPExtractData(out, size); +BGD_DECLARE(void *) +gdImagePngPtrWithOptions(gdImagePtr im, int *size, + const gdPngWriteOptions *options) { + gdPngWriteOptions defaults; + gdIOCtx *out; + void *rv = NULL; + if (size != NULL) + *size = 0; + if (im == NULL || size == NULL) + return NULL; + if (options == NULL) { + gdPngWriteOptionsInit(&defaults); + options = &defaults; + } + if (!gdPngWriteOptionsValid(options)) + return NULL; + out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + if (!_gdImagePngCtxWithOptions(im, out, options)) + rv = gdDPExtractData(out, size); out->gd_free(out); + if (rv != NULL && options->metadata != NULL && + gdImageMetadataInjectPng(&rv, size, options->metadata) != GD_META_OK) { + gdFree(rv); + *size = 0; + return NULL; + } return rv; } -void gdImagePngCtx (gdImagePtr im, gdIOCtx * outfile) -{ - gdImagePngCtxEx(im, outfile, -1, -1); +BGD_DECLARE(void *) +gdImagePngPtrWithMetadata(gdImagePtr im, int *size, + const gdImageMetadata *metadata) { + return gdImagePngPtrExWithMetadata(im, size, -1, metadata); +} + +BGD_DECLARE(void *) +gdImagePngPtrExWithMetadata(gdImagePtr im, int *size, int level, + const gdImageMetadata *metadata) { + void *rv = gdImagePngPtrEx(im, size, level); + if (rv == NULL) + return NULL; + if (gdImageMetadataInjectPng(&rv, size, metadata) != GD_META_OK) { + gdFree(rv); + if (size != NULL) + *size = 0; + return NULL; + } + return rv; +} + +BGD_DECLARE(int) +gdImageMetadataInjectPng(void **data, int *size, + const gdImageMetadata *metadata) { + const unsigned char *png; + unsigned char *out; + const unsigned char *profile; + size_t profile_size; + size_t png_size; + size_t pos; + size_t out_pos; + size_t out_size; + size_t extra_size = 0; + size_t i; + int wrote_after_ihdr = FALSE; + + if (data == NULL || size == NULL || *data == NULL || *size < 0) { + return GD_META_ERR_INVALID; + } + if (metadata == NULL) { + return GD_META_OK; + } + + png = (const unsigned char *)*data; + png_size = (size_t)*size; + if (png_size < 8 || memcmp(png, gdPngSignature, 8) != 0) { + return GD_META_ERR_FORMAT; + } + + profile = gdImageMetadataGetProfile(metadata, "exif", &profile_size); + if (gdPngMetadataChunkSize(&extra_size, profile, profile_size) != + GD_META_OK) { + return GD_META_ERR_LIMIT; + } + profile = gdImageMetadataGetProfile(metadata, "icc", &profile_size); + if (gdPngMetadataChunkSize(&extra_size, profile, profile_size) != + GD_META_OK) { + return GD_META_ERR_LIMIT; + } + profile = gdImageMetadataGetProfile(metadata, "xmp", &profile_size); + if (gdPngMetadataChunkSize(&extra_size, profile, profile_size) != + GD_META_OK) { + return GD_META_ERR_LIMIT; + } + for (i = 0; i < gdImageMetadataGetProfileCount(metadata); i++) { + const char *key = NULL; + const unsigned char *text_data = NULL; + size_t text_size = 0; + if (gdImageMetadataGetProfileAt(metadata, i, &key, &text_data, + &text_size) == GD_META_OK && + key != NULL && strncmp(key, "png:text:", 9) == 0) { + if (gdPngMetadataChunkSize(&extra_size, text_data, text_size) != + GD_META_OK) { + return GD_META_ERR_LIMIT; + } + } + } + + if ((size_t)-1 - png_size < extra_size || png_size + extra_size > INT_MAX) { + return GD_META_ERR_LIMIT; + } + out_size = png_size + extra_size; + out = (unsigned char *)gdMalloc(out_size); + if (out == NULL) { + return GD_META_ERR_NOMEM; + } + + memcpy(out, png, 8); + pos = 8; + out_pos = 8; + while (pos + 12 <= png_size) { + unsigned int chunk_size = gdPngGetUint32(png + pos); + const unsigned char *type = png + pos + 4; + const unsigned char *chunk_data = png + pos + 8; + size_t chunk_total; + + if ((size_t)chunk_size > png_size - pos - 12) { + gdFree(out); + return GD_META_ERR_PARSE; + } + chunk_total = (size_t)chunk_size + 12; + + if (!gdPngShouldSkipChunk(type, chunk_data, chunk_size, metadata)) { + memcpy(out + out_pos, png + pos, chunk_total); + out_pos += chunk_total; + } + pos += chunk_total; + + if (gdPngChunkIs(type, "IHDR") && !wrote_after_ihdr) { + int status; + profile = + gdImageMetadataGetProfile(metadata, "exif", &profile_size); + if (profile != NULL && + (status = gdPngAppendChunk( + out, &out_pos, + gdPngIsRawProfile(profile, profile_size, "exif") ? "zTXt" + : "eXIf", + profile, profile_size)) != GD_META_OK) { + gdFree(out); + return status; + } + profile = gdImageMetadataGetProfile(metadata, "icc", &profile_size); + if (profile != NULL && + (status = gdPngAppendChunk(out, &out_pos, "iCCP", profile, + profile_size)) != GD_META_OK) { + gdFree(out); + return status; + } + profile = gdImageMetadataGetProfile(metadata, "xmp", &profile_size); + if (profile != NULL && + (status = gdPngAppendChunk( + out, &out_pos, + gdPngIsRawProfile(profile, profile_size, "xmp") ? "zTXt" + : "iTXt", + profile, profile_size)) != GD_META_OK) { + gdFree(out); + return status; + } + for (i = 0; i < gdImageMetadataGetProfileCount(metadata); i++) { + const char *key = NULL; + const unsigned char *text_data = NULL; + size_t text_size = 0; + if (gdImageMetadataGetProfileAt(metadata, i, &key, &text_data, + &text_size) == GD_META_OK && + key != NULL && strncmp(key, "png:text:", 9) == 0) { + status = gdPngAppendChunk(out, &out_pos, "tEXt", text_data, + text_size); + if (status != GD_META_OK) { + gdFree(out); + return status; + } + } + } + wrote_after_ihdr = TRUE; + } + + if (gdPngChunkIs(type, "IEND")) { + gdFree(*data); + *data = out; + *size = (int)out_pos; + return GD_META_OK; + } + } + + gdFree(out); + return GD_META_ERR_PARSE; +} + +/* + Function: gdImagePngCtx + + Equivalent to calling with compression of -1. + See for more information. + + Parameters: + + im - the image to save. + outfile - the to write to. + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) gdImagePngCtx(gdImagePtr im, gdIOCtx *outfile) { + /* 2.0.13: 'return' here was an error, thanks to Kevin Smith */ + gdImagePngCtxEx(im, outfile, -1); +} + +BGD_DECLARE(void) +gdImagePngCtxWithMetadata(gdImagePtr im, gdIOCtx *outfile, + const gdImageMetadata *metadata) { + gdImagePngCtxExWithMetadata(im, outfile, -1, metadata); +} + +/* + Function: gdImagePngCtxEx + + Outputs the given image as PNG data, but using a instead + of a file. See . + + Parameters: + + im - the image to save. + outfile - the to write to. + level - compression level: 0 -> none, 1-9 -> level, -1 -> default + + Returns: + + Nothing. + +*/ +BGD_DECLARE(void) gdImagePngCtxEx(gdImagePtr im, gdIOCtx *outfile, int level) { + gdPngWriteOptions options; + gdPngWriteOptionsInit(&options); + options.compression_level = level; + (void)_gdImagePngCtxWithOptions(im, outfile, &options); +} + +BGD_DECLARE(void) +gdImagePngCtxExWithMetadata(gdImagePtr im, gdIOCtx *outfile, int level, + const gdImageMetadata *metadata) { + void *data; + int size = 0; + data = gdImagePngPtrExWithMetadata(im, &size, level, metadata); + if (data == NULL) + return; + gdPutBuf(data, size, outfile); + gdFree(data); +} + +BGD_DECLARE(int) +gdImagePngCtxWithOptions(gdImagePtr im, gdIOCtx *outfile, + const gdPngWriteOptions *options) { + gdPngWriteOptions defaults; + void *data; + int size = 0; + + if (im == NULL || outfile == NULL) + return 1; + if (options == NULL) { + gdPngWriteOptionsInit(&defaults); + options = &defaults; + } + if (!gdPngWriteOptionsValid(options)) + return 1; + if (options->metadata == NULL) + return _gdImagePngCtxWithOptions(im, outfile, options); + data = gdImagePngPtrWithOptions(im, &size, options); + if (data == NULL) + return 1; + if (gdPutBuf(data, size, outfile) != size) { + gdFree(data); + return 1; + } + gdFree(data); + return 0; } /* This routine is based in part on code from Dale Lutz (Safe Software Inc.) * and in part on demo code from Chapter 15 of "PNG: The Definitive Guide" - * (http://www.cdrom.com/pub/png/pngbook.html). + * (http://www.libpng.org/pub/png/book/). */ -void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilter) -{ +/* returns 0 on success, 1 on failure */ +static int _gdImagePngCtxWithOptions(gdImagePtr im, gdIOCtx *outfile, + const gdPngWriteOptions *options) { int i, j, bit_depth = 0, interlace_type; int width = im->sx; int height = im->sy; int colors = im->colorsTotal; int *open = im->open; - int mapping[gdMaxColors]; /* mapping[gd_index] == png_index */ + int mapping[gdMaxColors]; /* mapping[gd_index] == png_index */ png_byte trans_values[256]; png_color_16 trans_rgb_value; png_color palette[gdMaxColors]; png_structp png_ptr; png_infop info_ptr; + png_bytep *row_pointers = NULL; volatile int transparent = im->transparent; volatile int remap = FALSE; #ifdef PNG_SETJMP_SUPPORTED jmpbuf_wrapper jbw; +#endif + int ret = 0; - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &jbw, gdPngErrorHandler, gdPngWarningHandler); + /* width or height of value 0 is invalid in IHDR; + see http://www.w3.org/TR/PNG-Chunks.html */ + if (width == 0 || height == 0) + return 1; + +#ifdef PNG_SETJMP_SUPPORTED + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &jbw, + gdPngErrorHandler, gdPngWarningHandler); #else png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); #endif if (png_ptr == NULL) { - gd_error("gd-png error: cannot allocate libpng main struct"); - return; + gd_error("gd-png error: cannot allocate libpng main struct\n"); + return 1; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { - gd_error("gd-png error: cannot allocate libpng info struct"); - png_destroy_write_struct (&png_ptr, (png_infopp) NULL); - - return; - } + gd_error("gd-png error: cannot allocate libpng info struct\n"); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return 1; + } #ifdef PNG_SETJMP_SUPPORTED if (setjmp(jbw.jmpbuf)) { - gd_error("gd-png error: setjmp returns error condition"); - png_destroy_write_struct (&png_ptr, &info_ptr); + gd_error("gd-png error: setjmp returns error condition\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); - return; + if (row_pointers) { + for (i = 0; i < height; ++i) + gdFree(row_pointers[i]); + gdFree(row_pointers); + } + + return 1; } #endif - png_set_write_fn(png_ptr, (void *) outfile, gdPngWriteData, gdPngFlushData); + png_set_write_fn(png_ptr, (void *)outfile, gdPngWriteData, gdPngFlushData); - /* This is best for palette images, and libpng defaults to it for - * palette images anyway, so we don't need to do it explicitly. - * What to ideally do for truecolor images depends, alas, on the image. - * gd is intentionally imperfect and doesn't spend a lot of time - * fussing with such things. - */ + png_set_user_limits(png_ptr, 0x7fffffffL, 0x7fffffffL); - /* png_set_filter(png_ptr, 0, PNG_FILTER_NONE); */ + /* This is best for palette images, and libpng defaults to it for + palette images anyway, so we don't need to do it explicitly. + What to ideally do for truecolor images depends, alas, on the image. + gd is intentionally imperfect and doesn't spend a lot of time + fussing with such things. */ /* 2.0.12: this is finally a parameter */ - if (level != -1 && (level < 0 || level > 9)) { - gd_error("gd-png error: compression level must be 0 through 9"); - return; + png_set_compression_level(png_ptr, options->compression_level); + if (options->filters != GD_PNG_FILTER_AUTO) { + int filters = 0; + if (options->filters & GD_PNG_FILTER_NONE) + filters |= PNG_FILTER_NONE; + if (options->filters & GD_PNG_FILTER_SUB) + filters |= PNG_FILTER_SUB; + if (options->filters & GD_PNG_FILTER_UP) + filters |= PNG_FILTER_UP; + if (options->filters & GD_PNG_FILTER_AVERAGE) + filters |= PNG_FILTER_AVG; + if (options->filters & GD_PNG_FILTER_PAETH) + filters |= PNG_FILTER_PAETH; + png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, filters); } - png_set_compression_level(png_ptr, level); - if (basefilter >= 0) { - png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, basefilter); + if (options->compression_strategy != GD_PNG_COMPRESSION_STRATEGY_DEFAULT) { + int strategy = Z_DEFAULT_STRATEGY; + switch (options->compression_strategy) { + case GD_PNG_COMPRESSION_STRATEGY_FILTERED: + strategy = Z_FILTERED; + break; + case GD_PNG_COMPRESSION_STRATEGY_HUFFMAN_ONLY: + strategy = Z_HUFFMAN_ONLY; + break; + case GD_PNG_COMPRESSION_STRATEGY_RLE: + strategy = Z_RLE; + break; + case GD_PNG_COMPRESSION_STRATEGY_FIXED: + strategy = Z_FIXED; + break; + } + png_set_compression_strategy(png_ptr, strategy); } #ifdef PNG_pHYs_SUPPORTED /* 2.1.0: specify the resolution */ png_set_pHYs(png_ptr, info_ptr, DPI2DPM(im->res_x), DPI2DPM(im->res_y), - PNG_RESOLUTION_METER); + PNG_RESOLUTION_METER); #endif /* can set this to a smaller value without compromising compression if all - * image data is 16K or less; will save some decoder memory [min == 8] - */ - + * image data is 16K or less; will save some decoder memory [min == 8] */ /* png_set_compression_window_bits(png_ptr, 15); */ if (!im->trueColor) { - if (transparent >= im->colorsTotal || (transparent >= 0 && open[transparent])) { + if (transparent >= im->colorsTotal || + (transparent >= 0 && open[transparent])) transparent = -1; - } - - for (i = 0; i < gdMaxColors; ++i) { + } + if (!im->trueColor) { + for (i = 0; i < gdMaxColors; ++i) mapping[i] = -1; - } - - /* count actual number of colors used (colorsTotal == high-water mark) */ + } + if (!im->trueColor) { + /* count actual number of colors used (colorsTotal == high-water mark) + */ colors = 0; for (i = 0; i < im->colorsTotal; ++i) { if (!open[i]) { @@ -581,57 +1440,56 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte } } if (colors == 0) { - gd_error("gd-png error: no colors in palette"); + gd_error("gd-png error: no colors in palette\n"); + ret = 1; goto bail; } if (colors < im->colorsTotal) { remap = TRUE; } - if (colors <= 2) { + if (colors <= 2) bit_depth = 1; - } else if (colors <= 4) { + else if (colors <= 4) bit_depth = 2; - } else if (colors <= 16) { + else if (colors <= 16) bit_depth = 4; - } else { + else bit_depth = 8; - } } - interlace_type = im->interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; if (im->trueColor) { if (im->saveAlphaFlag) { - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, interlace_type, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); } else { - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, interlace_type, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); } } else { - png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, PNG_COLOR_TYPE_PALETTE, interlace_type, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, + PNG_COLOR_TYPE_PALETTE, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); } - - if (im->trueColor && !im->saveAlphaFlag && (transparent >= 0)) { + if (im->trueColor && (!im->saveAlphaFlag) && (transparent >= 0)) { /* 2.0.9: fixed by Thomas Winzig */ - trans_rgb_value.red = gdTrueColorGetRed (im->transparent); - trans_rgb_value.green = gdTrueColorGetGreen (im->transparent); - trans_rgb_value.blue = gdTrueColorGetBlue (im->transparent); + trans_rgb_value.red = gdTrueColorGetRed(im->transparent); + trans_rgb_value.green = gdTrueColorGetGreen(im->transparent); + trans_rgb_value.blue = gdTrueColorGetBlue(im->transparent); png_set_tRNS(png_ptr, info_ptr, 0, 0, &trans_rgb_value); } - if (!im->trueColor) { - /* Oy veh. Remap the PNG palette to put the entries with interesting alpha channel - * values first. This minimizes the size of the tRNS chunk and thus the size - * of the PNG file as a whole. - */ - + /* Oy veh. Remap the PNG palette to put the + entries with interesting alpha channel + values first. This minimizes the size + of the tRNS chunk and thus the size + of the PNG file as a whole. */ int tc = 0; int i; int j; int k; - for (i = 0; (i < im->colorsTotal); i++) { if ((!im->open[i]) && (im->alpha[i] != gdAlphaOpaque)) { tc++; @@ -640,25 +1498,25 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte if (tc) { #if 0 for (i = 0; (i < im->colorsTotal); i++) { - trans_values[i] = 255 - ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); + trans_values[i] = 255 - + ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); } png_set_tRNS (png_ptr, info_ptr, trans_values, 256, NULL); #endif if (!remap) { remap = TRUE; } - - /* (Semi-)transparent indexes come up from the bottom of the list of real colors; opaque - * indexes come down from the top - */ + /* (Semi-)transparent indexes come up from the bottom + of the list of real colors; opaque + indexes come down from the top */ j = 0; k = colors - 1; - - for (i = 0; i < im->colorsTotal; i++) { + for (i = 0; (i < im->colorsTotal); i++) { if (!im->open[i]) { if (im->alpha[i] != gdAlphaOpaque) { /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ - trans_values[j] = 255 - ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); + trans_values[j] = + 255 - ((im->alpha[i] << 1) + (im->alpha[i] >> 6)); mapping[i] = j++; } else { mapping[i] = k--; @@ -671,23 +1529,20 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte /* convert palette to libpng layout */ if (!im->trueColor) { - if (remap) { + if (remap) for (i = 0; i < im->colorsTotal; ++i) { - if (mapping[i] < 0) { + if (mapping[i] < 0) continue; - } - palette[mapping[i]].red = im->red[i]; palette[mapping[i]].green = im->green[i]; palette[mapping[i]].blue = im->blue[i]; } - } else { + else for (i = 0; i < colors; ++i) { palette[i].red = im->red[i]; palette[i].green = im->green[i]; palette[i].blue = im->blue[i]; } - } png_set_PLTE(png_ptr, info_ptr, palette, colors); } @@ -702,26 +1557,43 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte * later, the im->pixels array is laid out identically to libpng's row * pointers and can be passed to png_write_image() function directly. * The remapping case could be accomplished with less memory for non- - * interlaced images, but interlacing causes some serious complications. - */ - + * interlaced images, but interlacing causes some serious complications. */ if (im->trueColor) { /* performance optimizations by Phong Tran */ int channels = im->saveAlphaFlag ? 4 : 3; /* Our little 7-bit alpha channel trick costs us a bit here. */ - png_bytep *row_pointers; - unsigned char* pOutputRow; + unsigned char *pOutputRow; int **ptpixels = im->tpixels; int *pThisRow; unsigned char a; int thisPixel; png_bytep *prow_pointers; int saveAlphaFlag = im->saveAlphaFlag; - - row_pointers = safe_emalloc(sizeof(png_bytep), height, 0); + if (overflow2(sizeof(png_bytep), height)) { + ret = 1; + goto bail; + } + /* Need to use calloc so we can clean it up sanely in the error handler. + */ + row_pointers = gdCalloc(height, sizeof(png_bytep)); + if (row_pointers == NULL) { + gd_error("gd-png error: unable to allocate row_pointers\n"); + ret = 1; + goto bail; + } prow_pointers = row_pointers; for (j = 0; j < height; ++j) { - *prow_pointers = (png_bytep) safe_emalloc(width, channels, 0); + if (overflow2(width, channels) || + ((*prow_pointers = (png_bytep)gdMalloc(width * channels)) == + NULL)) { + gd_error("gd-png error: unable to allocate rows\n"); + for (i = 0; i < j; ++i) + gdFree(row_pointers[i]); + /* 2.0.29: memory leak TBB */ + gdFree(row_pointers); + ret = 1; + goto bail; + } pOutputRow = *prow_pointers++; pThisRow = *ptpixels++; for (i = 0; i < width; ++i) { @@ -729,13 +1601,13 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte *pOutputRow++ = gdTrueColorGetRed(thisPixel); *pOutputRow++ = gdTrueColorGetGreen(thisPixel); *pOutputRow++ = gdTrueColorGetBlue(thisPixel); + if (saveAlphaFlag) { - /* convert the 7-bit alpha channel to an 8-bit alpha channel. - * We do a little bit-flipping magic, repeating the MSB - * as the LSB, to ensure that 0 maps to 0 and - * 127 maps to 255. We also have to invert to match - * PNG's convention in which 255 is opaque. - */ + /* convert the 7-bit alpha channel to an 8-bit alpha + channel. We do a little bit-flipping magic, repeating the + MSB as the LSB, to ensure that 0 maps to 0 and 127 maps + to 255. We also have to invert to match PNG's convention + in which 255 is opaque. */ a = gdTrueColorGetAlpha(thisPixel); /* Andrew Hull: >> 6, not >> 7! (gd 2.0.5) */ *pOutputRow++ = 255 - ((a << 1) + (a >> 6)); @@ -746,38 +1618,217 @@ void gdImagePngCtxEx (gdImagePtr im, gdIOCtx * outfile, int level, int basefilte png_write_image(png_ptr, row_pointers); png_write_end(png_ptr, info_ptr); - for (j = 0; j < height; ++j) { + for (j = 0; j < height; ++j) gdFree(row_pointers[j]); - } - gdFree(row_pointers); } else { if (remap) { png_bytep *row_pointers; - row_pointers = safe_emalloc(height, sizeof(png_bytep), 0); + if (overflow2(sizeof(png_bytep), height)) { + ret = 1; + goto bail; + } + row_pointers = gdMalloc(sizeof(png_bytep) * height); + if (row_pointers == NULL) { + gd_error("gd-png error: unable to allocate row_pointers\n"); + ret = 1; + goto bail; + } for (j = 0; j < height; ++j) { - row_pointers[j] = (png_bytep) gdMalloc(width); - for (i = 0; i < width; ++i) { - row_pointers[j][i] = mapping[im->pixels[j][i]]; + if ((row_pointers[j] = (png_bytep)gdMalloc(width)) == NULL) { + gd_error("gd-png error: unable to allocate rows\n"); + for (i = 0; i < j; ++i) + gdFree(row_pointers[i]); + /* TBB: memory leak */ + gdFree(row_pointers); + ret = 1; + goto bail; } + for (i = 0; i < width; ++i) + row_pointers[j][i] = mapping[im->pixels[j][i]]; } png_write_image(png_ptr, row_pointers); - png_write_end(png_ptr, info_ptr); - for (j = 0; j < height; ++j) { + for (j = 0; j < height; ++j) gdFree(row_pointers[j]); - } - gdFree(row_pointers); + + png_write_end(png_ptr, info_ptr); } else { png_write_image(png_ptr, im->pixels); png_write_end(png_ptr, info_ptr); } } /* 1.6.3: maybe we should give that memory BACK! TBB */ - bail: +bail: png_destroy_write_struct(&png_ptr, &info_ptr); + return ret; +} + +#else /* !HAVE_LIBPNG */ + +static void _noPngError(void) { + gd_error("PNG image support has been disabled\n"); +} + +BGD_DECLARE(void) gdPngWriteOptionsInit(gdPngWriteOptions *options) { + if (options == NULL) + return; + memset(options, 0, sizeof(*options)); + options->struct_size = sizeof(*options); + options->compression_level = -1; +} + +BGD_DECLARE(int) +gdImagePngWithOptions(gdImagePtr im, FILE *outFile, + const gdPngWriteOptions *options) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + ARG_NOT_USED(options); + _noPngError(); + return 1; +} + +BGD_DECLARE(int) +gdImagePngCtxWithOptions(gdImagePtr im, gdIOCtx *outfile, + const gdPngWriteOptions *options) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(options); + _noPngError(); + return 1; +} + +BGD_DECLARE(void *) +gdImagePngPtrWithOptions(gdImagePtr im, int *size, + const gdPngWriteOptions *options) { + ARG_NOT_USED(im); + ARG_NOT_USED(options); + if (size != NULL) + *size = 0; + _noPngError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPng(FILE *inFile) { + ARG_NOT_USED(inFile); + _noPngError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngPtrWithMetadata(int size, void *data, + gdImageMetadata *metadata) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + ARG_NOT_USED(metadata); + return NULL; +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromPngCtxWithMetadata(gdIOCtx *infile, + gdImageMetadata *metadata) { + ARG_NOT_USED(infile); + ARG_NOT_USED(metadata); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngCtx(gdIOCtx *infile) { + ARG_NOT_USED(infile); + return NULL; +} + +BGD_DECLARE(void) gdImagePngEx(gdImagePtr im, FILE *outFile, int level) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + ARG_NOT_USED(level); + _noPngError(); +} + +BGD_DECLARE(void) gdImagePng(gdImagePtr im, FILE *outFile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + _noPngError(); +} + +BGD_DECLARE(void *) gdImagePngPtr(gdImagePtr im, int *size) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + return NULL; +} + +BGD_DECLARE(void *) gdImagePngPtrEx(gdImagePtr im, int *size, int level) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(level); + return NULL; +} + +BGD_DECLARE(void *) +gdImagePngPtrWithMetadata(gdImagePtr im, int *size, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(metadata); + return NULL; +} + +BGD_DECLARE(void *) +gdImagePngPtrExWithMetadata(gdImagePtr im, int *size, int level, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(level); + ARG_NOT_USED(metadata); + return NULL; +} + +BGD_DECLARE(int) +gdImageMetadataInjectPng(void **data, int *size, + const gdImageMetadata *metadata) { + ARG_NOT_USED(data); + ARG_NOT_USED(size); + ARG_NOT_USED(metadata); + return GD_META_ERR_UNSUPPORTED; +} + +BGD_DECLARE(void) gdImagePngCtx(gdImagePtr im, gdIOCtx *outfile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + _noPngError(); +} + +BGD_DECLARE(void) +gdImagePngCtxWithMetadata(gdImagePtr im, gdIOCtx *outfile, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(metadata); + _noPngError(); +} + +BGD_DECLARE(void) gdImagePngCtxEx(gdImagePtr im, gdIOCtx *outfile, int level) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(level); + _noPngError(); +} + +BGD_DECLARE(void) +gdImagePngCtxExWithMetadata(gdImagePtr im, gdIOCtx *outfile, int level, + const gdImageMetadata *metadata) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(level); + ARG_NOT_USED(metadata); + _noPngError(); } #endif /* HAVE_LIBPNG */ diff --git a/ext/gd/libgd/gd_qoi.c b/ext/gd/libgd/gd_qoi.c new file mode 100644 index 000000000000..5d956ac11e13 --- /dev/null +++ b/ext/gd/libgd/gd_qoi.c @@ -0,0 +1,358 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "gd.h" +#include "gd_errors.h" +#include "gdhelpers.h" + +#define QOI_IMPLEMENTATION +#include "gd_qoi.h" + +#define GD_QOI_ALLOC_STEP 8192 + +static unsigned char alpha7BitTo8Bit(int alpha7Bit) { + return (unsigned char)(alpha7Bit == gdAlphaTransparent + ? 0 + : 255 - ((alpha7Bit << 1) + (alpha7Bit >> 6))); +} + +static int alpha8BitTo7Bit(unsigned char alpha8Bit) { + return gdAlphaMax - (alpha8Bit >> 1); +} + +static void *gdQoiReadCtxToMemory(gdIOCtx *infile, int *size) { + unsigned char *data = NULL; + int logical_size = 0; + int real_size = 0; + + if (size != NULL) { + *size = 0; + } + if (infile == NULL || size == NULL) { + return NULL; + } + + for (;;) { + int n; + + if (real_size - logical_size < GD_QOI_ALLOC_STEP) { + unsigned char *temp; + int new_size; + + if (real_size > INT_MAX - GD_QOI_ALLOC_STEP) { + gdFree(data); + return NULL; + } + new_size = real_size + GD_QOI_ALLOC_STEP; + temp = (unsigned char *)gdRealloc(data, new_size); + if (temp == NULL) { + gdFree(data); + return NULL; + } + data = temp; + real_size = new_size; + } + + n = gdGetBuf(data + logical_size, GD_QOI_ALLOC_STEP, infile); + if (n <= 0) { + break; + } + logical_size += n; + } + + *size = logical_size; + return data; +} + +static gdImagePtr gdImageCreateFromQoiBytes(const void *data, int size) { + qoi_desc desc; + unsigned char *pixels; + gdImagePtr im = NULL; + unsigned int x, y; + unsigned char *p; + + if (data == NULL || size <= 0) { + return NULL; + } + + memset(&desc, 0, sizeof(desc)); + pixels = (unsigned char *)gdQoiDecode(data, size, &desc, 4); + if (pixels == NULL) { + return NULL; + } + + if (desc.width == 0 || desc.height == 0 || desc.width > INT_MAX || + desc.height > INT_MAX) { + gdFree(pixels); + return NULL; + } + + im = gdImageCreateTrueColor((int)desc.width, (int)desc.height); + if (im == NULL) { + gdFree(pixels); + return NULL; + } + im->saveAlphaFlag = 1; + im->alphaBlendingFlag = 0; + + p = pixels; + for (y = 0; y < desc.height; y++) { + for (x = 0; x < desc.width; x++) { + unsigned char r = *(p++); + unsigned char g = *(p++); + unsigned char b = *(p++); + unsigned char a = *(p++); + im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, alpha8BitTo7Bit(a)); + } + } + + gdFree(pixels); + return im; +} + +static unsigned char *gdImageToQoiPixels(gdImagePtr im) { + unsigned char *pixels, *p; + int x, y; + + if (im == NULL || gdImageSX(im) <= 0 || gdImageSY(im) <= 0) { + return NULL; + } + if (overflow2(gdImageSX(im), gdImageSY(im)) || + overflow2(gdImageSX(im) * gdImageSY(im), 4)) { + return NULL; + } + + pixels = + (unsigned char *)gdMalloc((size_t)gdImageSX(im) * gdImageSY(im) * 4); + if (pixels == NULL) { + return NULL; + } + + p = pixels; + if (im->trueColor) { + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + int c = im->tpixels[y][x]; + *(p++) = gdTrueColorGetRed(c); + *(p++) = gdTrueColorGetGreen(c); + *(p++) = gdTrueColorGetBlue(c); + *(p++) = alpha7BitTo8Bit(gdTrueColorGetAlpha(c)); + } + } + } else { + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + int c = im->pixels[y][x]; + *(p++) = (unsigned char)im->red[c]; + *(p++) = (unsigned char)im->green[c]; + *(p++) = (unsigned char)im->blue[c]; + *(p++) = alpha7BitTo8Bit(im->alpha[c]); + } + } + } + + return pixels; +} + +static int gdQoiNormalizeColorspace(int colorspace) { + return colorspace == GD_QOI_LINEAR ? QOI_LINEAR : QOI_SRGB; +} + +static int _gdImageQoiCtx(gdImagePtr im, gdIOCtx *outfile, int colorspace) { + qoi_desc desc; + unsigned char *pixels; + void *encoded; + int encoded_size = 0; + int result = 0; + + if (outfile == NULL) { + return 0; + } + + pixels = gdImageToQoiPixels(im); + if (pixels == NULL) { + return 0; + } + + desc.width = (unsigned int)gdImageSX(im); + desc.height = (unsigned int)gdImageSY(im); + desc.channels = 4; + desc.colorspace = (unsigned char)gdQoiNormalizeColorspace(colorspace); + + encoded = gdQoiEncode(pixels, &desc, &encoded_size); + gdFree(pixels); + if (encoded == NULL || encoded_size <= 0) { + gdFree(encoded); + return 0; + } + + if (gdPutBuf(encoded, encoded_size, outfile) == encoded_size) { + result = 1; + } else { + gd_error("gd-qoi write error\n"); + } + + gdFree(encoded); + return result; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoi(FILE *inFile) { + gdImagePtr im; + gdIOCtx *in = gdNewFileCtx(inFile); + if (in == NULL) { + return NULL; + } + im = gdImageCreateFromQoiCtx(in); + in->gd_free(in); + return im; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiPtr(int size, void *data) { + return gdImageCreateFromQoiPtrWithMetadata(size, data, NULL); +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromQoiPtrWithMetadata(int size, void *data, + gdImageMetadata *metadata) { + ARG_NOT_USED(metadata); + return gdImageCreateFromQoiBytes(data, size); +} + +BGD_DECLARE(gdImagePtr) +gdImageCreateFromQoiCtxWithMetadata(gdIOCtx *infile, + gdImageMetadata *metadata) { + void *data; + int size = 0; + gdImagePtr im; + + ARG_NOT_USED(metadata); + data = gdQoiReadCtxToMemory(infile, &size); + if (data == NULL) { + return NULL; + } + im = gdImageCreateFromQoiBytes(data, size); + gdFree(data); + return im; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiCtx(gdIOCtx *infile) { + return gdImageCreateFromQoiCtxWithMetadata(infile, NULL); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromQoiSource(gdSourcePtr inSource) { + gdImagePtr im; + gdIOCtx *in; + if (inSource == NULL) { + return NULL; + } + in = gdNewSSCtx(inSource, NULL); + if (in == NULL) { + return NULL; + } + im = gdImageCreateFromQoiCtx(in); + in->gd_free(in); + return im; +} + +BGD_DECLARE(void) gdImageQoiEx(gdImagePtr im, FILE *outFile, int colorspace) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) { + return; + } + gdImageQoiCtxEx(im, out, colorspace); + out->gd_free(out); +} + +BGD_DECLARE(void) gdImageQoi(gdImagePtr im, FILE *outFile) { + gdImageQoiEx(im, outFile, GD_QOI_SRGB); +} + +BGD_DECLARE(void *) gdImageQoiPtr(gdImagePtr im, int *size) { + return gdImageQoiPtrEx(im, size, GD_QOI_SRGB); +} + +BGD_DECLARE(void *) gdImageQoiPtrEx(gdImagePtr im, int *size, int colorspace) { + gdIOCtx *out; + void *rv = NULL; + + if (size != NULL) { + *size = 0; + } + if (size == NULL) { + return NULL; + } + + out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + if (_gdImageQoiCtx(im, out, colorspace)) { + rv = gdDPExtractData(out, size); + } + out->gd_free(out); + return rv; +} + +BGD_DECLARE(void *) +gdImageQoiPtrWithMetadata(gdImagePtr im, int *size, + const gdImageMetadata *metadata) { + return gdImageQoiPtrExWithMetadata(im, size, GD_QOI_SRGB, metadata); +} + +BGD_DECLARE(void *) +gdImageQoiPtrExWithMetadata(gdImagePtr im, int *size, int colorspace, + const gdImageMetadata *metadata) { + ARG_NOT_USED(metadata); + return gdImageQoiPtrEx(im, size, colorspace); +} + +BGD_DECLARE(int) +gdImageMetadataInjectQoi(void **data, int *size, + const gdImageMetadata *metadata) { + ARG_NOT_USED(data); + ARG_NOT_USED(size); + ARG_NOT_USED(metadata); + return GD_META_OK; +} + +BGD_DECLARE(void) gdImageQoiCtx(gdImagePtr im, gdIOCtx *outfile) { + gdImageQoiCtxEx(im, outfile, GD_QOI_SRGB); +} + +BGD_DECLARE(void) +gdImageQoiCtxWithMetadata(gdImagePtr im, gdIOCtx *outfile, + const gdImageMetadata *metadata) { + gdImageQoiCtxExWithMetadata(im, outfile, GD_QOI_SRGB, metadata); +} + +BGD_DECLARE(void) +gdImageQoiCtxEx(gdImagePtr im, gdIOCtx *outfile, int colorspace) { + _gdImageQoiCtx(im, outfile, colorspace); +} + +BGD_DECLARE(void) +gdImageQoiCtxExWithMetadata(gdImagePtr im, gdIOCtx *outfile, int colorspace, + const gdImageMetadata *metadata) { + ARG_NOT_USED(metadata); + _gdImageQoiCtx(im, outfile, colorspace); +} + +BGD_DECLARE(void) gdImageQoiToSink(gdImagePtr im, gdSinkPtr outSink) { + gdIOCtx *out; + if (outSink == NULL) { + return; + } + out = gdNewSSCtx(NULL, outSink); + if (out == NULL) { + return; + } + gdImageQoiCtx(im, out); + out->gd_free(out); +} diff --git a/ext/gd/libgd/gd_qoi.h b/ext/gd/libgd/gd_qoi.h new file mode 100644 index 000000000000..e553271598cb --- /dev/null +++ b/ext/gd/libgd/gd_qoi.h @@ -0,0 +1,630 @@ +/* + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +This copy is vendored for libgd's QOI codec. The upstream MIT license is +also included in docs/QOI-LICENSE. + + +QOI - The "Quite OK Image" format for fast, lossless image compression + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef GD_QOI_H +#define GD_QOI_H + +#define QOI_NO_STDIO +#define QOI_MALLOC(sz) gdMalloc(sz) +#define QOI_FREE(p) gdFree(p) +#define qoi_encode gdQoiEncode +#define qoi_decode gdQoiDecode + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + +#ifdef __cplusplus +} +#endif +#endif /* GD_QOI_H */ + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC +#define QOI_MALLOC(sz) malloc(sz) +#define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR +#define QOI_ZEROARR(a) memset((a), 0, sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) \ + (C.rgba.r * 3 + C.rgba.g * 5 + C.rgba.b * 7 + C.rgba.a * 11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { + unsigned char r, g, b, a; + } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0, 0, 0, 0, 0, 0, 0, 1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if (data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || + desc->height == 0 || desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || desc->height >= QOI_PIXELS_MAX / desc->width) { + return NULL; + } + + max_size = desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *)QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + + if (channels == 4) { + px.rgba.a = pixels[px_pos + 3]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) & (64 - 1); + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && + vb < 2) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | + (vg + 2) << 2 | (vb + 2); + } else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if (data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if (desc->width == 0 || desc->height == 0 || desc->channels < 3 || + desc->channels > 4 || desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *)QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += (b1 & 0x03) - 2; + } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) & (64 - 1)] = px; + } + + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + + if (channels == 4) { + pixels[px_pos + 3] = px.rgba.a; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size, err; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fflush(f); + err = ferror(f); + fclose(f); + + QOI_FREE(encoded); + return err ? 0 : size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return NULL; + } + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + pixels = (bytes_read != size) + ? NULL + : qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/ext/gd/libgd/gd_readimage.c b/ext/gd/libgd/gd_readimage.c new file mode 100644 index 000000000000..dde1549cc071 --- /dev/null +++ b/ext/gd/libgd/gd_readimage.c @@ -0,0 +1,304 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd.h" +#include "gd_errors.h" +#include +#include + +#define GD_READIMAGE_PROBE_SIZE 32 + +typedef gdImagePtr(BGD_STDCALL *ReadCtxFn)(gdIOCtxPtr); + +struct FormatEntry { + const char *name; + size_t offset; + const unsigned char *sig; + size_t sig_len; + ReadCtxFn reader; + int enabled; +}; + +#ifdef HAVE_LIBPNG +#define GD_READIMAGE_PNG_READER gdImageCreateFromPngCtx +#define GD_READIMAGE_PNG_ENABLED 1 +#else +#define GD_READIMAGE_PNG_READER NULL +#define GD_READIMAGE_PNG_ENABLED 0 +#endif + +#ifdef HAVE_LIBJXL +#define GD_READIMAGE_JXL_READER gdImageCreateFromJxlCtx +#define GD_READIMAGE_JXL_ENABLED 1 +#else +#define GD_READIMAGE_JXL_READER NULL +#define GD_READIMAGE_JXL_ENABLED 0 +#endif + +#ifdef HAVE_LIBJPEG +#define GD_READIMAGE_JPEG_READER gdImageCreateFromJpegCtx +#define GD_READIMAGE_JPEG_ENABLED 1 +#else +#define GD_READIMAGE_JPEG_READER NULL +#define GD_READIMAGE_JPEG_ENABLED 0 +#endif + +#ifdef HAVE_LIBTIFF +#define GD_READIMAGE_TIFF_READER gdImageCreateFromTiffCtx +#define GD_READIMAGE_TIFF_ENABLED 1 +#else +#define GD_READIMAGE_TIFF_READER NULL +#define GD_READIMAGE_TIFF_ENABLED 0 +#endif + +#ifdef HAVE_LIBWEBP +#define GD_READIMAGE_WEBP_READER gdImageCreateFromWebpCtx +#define GD_READIMAGE_WEBP_ENABLED 1 +#else +#define GD_READIMAGE_WEBP_READER NULL +#define GD_READIMAGE_WEBP_ENABLED 0 +#endif + +#ifdef HAVE_LIBAVIF +#define GD_READIMAGE_AVIF_READER gdImageCreateFromAvifCtx +#define GD_READIMAGE_AVIF_ENABLED 1 +#else +#define GD_READIMAGE_AVIF_READER NULL +#define GD_READIMAGE_AVIF_ENABLED 0 +#endif + +#ifdef HAVE_LIBHEIF +#define GD_READIMAGE_HEIF_READER gdImageCreateFromHeifCtx +#define GD_READIMAGE_HEIF_ENABLED 1 +#else +#define GD_READIMAGE_HEIF_READER NULL +#define GD_READIMAGE_HEIF_ENABLED 0 +#endif + +#if ENABLE_GD_FORMATS +#define GD_READIMAGE_GD_READER gdImageCreateFromGdCtx +#define GD_READIMAGE_GD_ENABLED 1 +#else +#define GD_READIMAGE_GD_READER NULL +#define GD_READIMAGE_GD_ENABLED 0 +#endif + +#if ENABLE_GD_FORMATS && defined(HAVE_LIBZ) +#define GD_READIMAGE_GD2_READER gdImageCreateFromGd2Ctx +#define GD_READIMAGE_GD2_ENABLED 1 +#else +#define GD_READIMAGE_GD2_READER NULL +#define GD_READIMAGE_GD2_ENABLED 0 +#endif + +#ifdef HAVE_LIBXPM +#define GD_READIMAGE_XPM_ENABLED 1 +#else +#define GD_READIMAGE_XPM_ENABLED 0 +#endif + +#define ENTRY(n, off, reader_fn, is_enabled, ...) \ + { (n), (off), \ + (const unsigned char[]){__VA_ARGS__}, \ + sizeof((const unsigned char[]){__VA_ARGS__}) / sizeof(unsigned char), \ + (reader_fn), (is_enabled) } + +static const struct FormatEntry format_table[] = { + ENTRY("PNG", 0, GD_READIMAGE_PNG_READER, GD_READIMAGE_PNG_ENABLED, + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A), + ENTRY("JXL", 0, GD_READIMAGE_JXL_READER, GD_READIMAGE_JXL_ENABLED, + 0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, + 0x0D, 0x0A, 0x87, 0x0A), + ENTRY("JXL", 0, GD_READIMAGE_JXL_READER, GD_READIMAGE_JXL_ENABLED, + 0xFF, 0x0A), + ENTRY("JPEG", 0, GD_READIMAGE_JPEG_READER, GD_READIMAGE_JPEG_ENABLED, + 0xFF, 0xD8, 0xFF), + ENTRY("GIF", 0, gdImageCreateFromGifCtx, 1, + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61), + ENTRY("GIF", 0, gdImageCreateFromGifCtx, 1, + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61), + ENTRY("BMP", 0, gdImageCreateFromBmpCtx, 1, + 0x42, 0x4D), + ENTRY("TIFF", 0, GD_READIMAGE_TIFF_READER, GD_READIMAGE_TIFF_ENABLED, + 0x4D, 0x4D, 0x00, 0x2A), + ENTRY("TIFF", 0, GD_READIMAGE_TIFF_READER, GD_READIMAGE_TIFF_ENABLED, + 0x49, 0x49, 0x2A, 0x00), + ENTRY("WEBP", 8, GD_READIMAGE_WEBP_READER, GD_READIMAGE_WEBP_ENABLED, + 0x57, 0x45, 0x42, 0x50), + ENTRY("AVIF", 4, GD_READIMAGE_AVIF_READER, GD_READIMAGE_AVIF_ENABLED, + 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66), + ENTRY("HEIC", 4, GD_READIMAGE_HEIF_READER, GD_READIMAGE_HEIF_ENABLED, + 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63), + ENTRY("GD2", 0, GD_READIMAGE_GD2_READER, GD_READIMAGE_GD2_ENABLED, + 0x67, 0x64, 0x32, 0x00), + ENTRY("GD", 0, GD_READIMAGE_GD_READER, GD_READIMAGE_GD_ENABLED, + 0xFF, 0xFE), + ENTRY("GD", 0, GD_READIMAGE_GD_READER, GD_READIMAGE_GD_ENABLED, + 0xFF, 0xFF), + ENTRY("QOI", 0, gdImageCreateFromQoiCtx, 1, + 0x71, 0x6F, 0x69, 0x66), + ENTRY("XPM", 0, NULL, GD_READIMAGE_XPM_ENABLED, + 0x2F, 0x2A, 0x20, 0x58, 0x50, 0x4D, 0x20, 0x2A), + ENTRY("XBM", 0, NULL, 1, + 0x23, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x65, 0x20), + {NULL, 0, NULL, 0, NULL, 0} +}; + +static int sig_match(const unsigned char *probe, size_t probe_len, + const struct FormatEntry *e) { + if (e->offset + e->sig_len > probe_len) + return 0; + return memcmp(probe + e->offset, e->sig, e->sig_len) == 0; +} + +static const struct FormatEntry *find_format(const unsigned char *probe, + size_t probe_len) { + size_t i; + + for (i = 0; format_table[i].name != NULL; i++) { + const struct FormatEntry *e = &format_table[i]; + if (sig_match(probe, probe_len, e)) + return e; + } + + return NULL; +} + +static void report_unreadable_format(const struct FormatEntry *e, + const char *source) { + if (e == NULL) { + gd_error("gd-readimage: unknown image format"); + return; + } + + if (!e->enabled) { + gd_error("gd-readimage: %s image support has been disabled", + e->name); + return; + } + + gd_error("gd-readimage: %s image does not support reading from %s", + e->name, source); +} + +/* + Function: gdImageReadCtx + + Read an image from a , auto-detecting the format by + magic-byte signatures. + + reads the first bytes from the ctx, determines + the image format by matching known magic-byte signatures, and + dispatches to the appropriate codec's Ctx reader. The ctx must + support seeking so it can be rewound after probing. + + Formats without a Ctx reader (XPM, XBM) are not supported by + this function. Use for those formats. + + NULL is returned on error or if the format is not recognized. + + Parameters: + + ctx - the input + + Returns: + + A pointer to the new image or NULL if an error occurred. + +*/ +BGD_DECLARE(gdImagePtr) gdImageReadCtx(gdIOCtxPtr ctx) { + unsigned char probe[GD_READIMAGE_PROBE_SIZE]; + size_t probe_len; + const struct FormatEntry *e; + + if (ctx == NULL) + return NULL; + + probe_len = (size_t)gdGetBuf(probe, GD_READIMAGE_PROBE_SIZE, ctx); + gdSeek(ctx, 0); + + e = find_format(probe, probe_len); + if (e != NULL && e->reader != NULL) + return e->reader(ctx); + + report_unreadable_format(e, "gdIOCtx"); + return NULL; +} + +/* + Function: gdImageReadFile + + Read an image file, auto-detecting the format by magic-byte + signatures rather than filename extension. + + opens the file, probes its header bytes to + determine the format, and calls the appropriate + _gdImageCreateFrom*Ctx_ function. If the format has no Ctx + reader (XPM, XBM), it falls back to the filename-based or + FILE*-based reader. + + NULL is returned on error or if the format is not recognized. + + Parameters: + + filename - the input file name + + Returns: + + A pointer to the new image or NULL if an error occurred. + +*/ +BGD_DECLARE(gdImagePtr) gdImageReadFile(const char *filename) { + FILE *fh; + gdIOCtxPtr ctx; + unsigned char probe[GD_READIMAGE_PROBE_SIZE]; + size_t probe_len; + gdImagePtr im; + const struct FormatEntry *e; + + if (filename == NULL) + return NULL; + + fh = fopen(filename, "rb"); + if (!fh) + return NULL; + + ctx = gdNewFileCtx(fh); + if (!ctx) { + fclose(fh); + return NULL; + } + + probe_len = (size_t)gdGetBuf(probe, GD_READIMAGE_PROBE_SIZE, ctx); + gdSeek(ctx, 0); + + e = find_format(probe, probe_len); + if (e != NULL && e->reader != NULL) { + im = e->reader(ctx); + ctx->gd_free(ctx); + fclose(fh); + return im; + } + + ctx->gd_free(ctx); + fclose(fh); + + if (e != NULL && e->enabled) { +#ifdef HAVE_LIBXPM + if (strcmp(e->name, "XPM") == 0) + return gdImageCreateFromXpm((char *)filename); +#endif + if (strcmp(e->name, "XBM") == 0) { + FILE *xbm_fh = fopen(filename, "rb"); + if (!xbm_fh) + return NULL; + im = gdImageCreateFromXbm(xbm_fh); + fclose(xbm_fh); + return im; + } + } + + report_unreadable_format(e, "file"); + return NULL; +} diff --git a/ext/gd/libgd/gd_rotate.c b/ext/gd/libgd/gd_rotate.c index 53c6c9470db7..031984ec8ef2 100644 --- a/ext/gd/libgd/gd_rotate.c +++ b/ext/gd/libgd/gd_rotate.c @@ -11,11 +11,10 @@ #ifdef ROTATE_PI #undef ROTATE_PI #endif /* ROTATE_PI */ - +typedef int(BGD_STDCALL *FuncPtr)(gdImagePtr, int, int); #define ROTATE_DEG2RAD 3.1415926535897932384626433832795/180 void gdImageSkewX (gdImagePtr dst, gdImagePtr src, int uRow, int iOffset, double dWeight, int clrBack, int ignoretransparent) { - typedef int (*FuncPtr)(gdImagePtr, int, int); int i, r, g, b, a, clrBackR, clrBackG, clrBackB, clrBackA; FuncPtr f; @@ -112,7 +111,6 @@ void gdImageSkewX (gdImagePtr dst, gdImagePtr src, int uRow, int iOffset, double void gdImageSkewY (gdImagePtr dst, gdImagePtr src, int uCol, int iOffset, double dWeight, int clrBack, int ignoretransparent) { - typedef int (*FuncPtr)(gdImagePtr, int, int); int i, iYPos=0, r, g, b, a; FuncPtr f; int pxlOldLeft, pxlLeft=0, pxlSrc; @@ -198,8 +196,7 @@ void gdImageSkewY (gdImagePtr dst, gdImagePtr src, int uCol, int iOffset, double } /* Rotates an image by 90 degrees (counter clockwise) */ -gdImagePtr gdImageRotate90 (gdImagePtr src, int ignoretransparent) -{ +gdImagePtr gdImageRotate90(gdImagePtr src, int ignoretransparent) { int uY, uX; int c,r,g,b,a; gdImagePtr dst; @@ -232,7 +229,8 @@ gdImagePtr gdImageRotate90 (gdImagePtr src, int ignoretransparent) c = gdTrueColorAlpha(r, g, b, a); } if (ignoretransparent && c == dst->transparent) { - gdImageSetPixel(dst, uY, (dst->sy - uX - 1), dst->transparent); + gdImageSetPixel(dst, uY, (dst->sy - uX - 1), + dst->transparent); } else { gdImageSetPixel(dst, uY, (dst->sy - uX - 1), c); } @@ -245,12 +243,10 @@ gdImagePtr gdImageRotate90 (gdImagePtr src, int ignoretransparent) } /* Rotates an image by 180 degrees (counter clockwise) */ -gdImagePtr gdImageRotate180 (gdImagePtr src, int ignoretransparent) -{ +gdImagePtr gdImageRotate180(gdImagePtr src, int ignoretransparent) { int uY, uX; int c,r,g,b,a; gdImagePtr dst; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src->trueColor) { @@ -280,9 +276,11 @@ gdImagePtr gdImageRotate180 (gdImagePtr src, int ignoretransparent) } if (ignoretransparent && c == dst->transparent) { - gdImageSetPixel(dst, (dst->sx - uX - 1), (dst->sy - uY - 1), dst->transparent); + gdImageSetPixel(dst, (dst->sx - uX - 1), (dst->sy - uY - 1), + dst->transparent); } else { - gdImageSetPixel(dst, (dst->sx - uX - 1), (dst->sy - uY - 1), c); + gdImageSetPixel(dst, (dst->sx - uX - 1), (dst->sy - uY - 1), + c); } } } @@ -293,12 +291,10 @@ gdImagePtr gdImageRotate180 (gdImagePtr src, int ignoretransparent) } /* Rotates an image by 270 degrees (counter clockwise) */ -gdImagePtr gdImageRotate270 (gdImagePtr src, int ignoretransparent) -{ +gdImagePtr gdImageRotate270(gdImagePtr src, int ignoretransparent) { int uY, uX; int c,r,g,b,a; gdImagePtr dst; - typedef int (*FuncPtr)(gdImagePtr, int, int); FuncPtr f; if (src->trueColor) { @@ -328,7 +324,8 @@ gdImagePtr gdImageRotate270 (gdImagePtr src, int ignoretransparent) } if (ignoretransparent && c == dst->transparent) { - gdImageSetPixel(dst, (dst->sx - uY - 1), uX, dst->transparent); + gdImageSetPixel(dst, (dst->sx - uY - 1), uX, + dst->transparent); } else { gdImageSetPixel(dst, (dst->sx - uY - 1), uX, c); } diff --git a/ext/gd/libgd/gd_security.c b/ext/gd/libgd/gd_security.c index cbc4872d2fce..884938b69713 100644 --- a/ext/gd/libgd/gd_security.c +++ b/ext/gd/libgd/gd_security.c @@ -12,39 +12,50 @@ #include "config.h" #endif +#include "gd.h" +#include "gd_errors.h" +#include #include #include #include -#include -#include "gd.h" -#include "gd_errors.h" -int overflow2(int a, int b) -{ +int overflow2(int a, int b) { if(a <= 0 || b <= 0) { - gd_error("One parameter to a memory allocation multiplication is negative or zero, failing operation gracefully\n"); + gd_error_ex(GD_WARNING, + "one parameter to a memory allocation multiplication is " + "negative or zero, failing operation gracefully\n"); return 1; } if(a > INT_MAX / b) { - gd_error("Product of memory allocation multiplication would exceed INT_MAX, failing operation gracefully\n"); + gd_error_ex(GD_WARNING, + "Product of memory allocation multiplication would exceed " + "INT_MAX, failing operation gracefully\n"); return 1; } return 0; } -int overflowMul3(int a, int b, int c) -{ +int overflowMul3(int a, int b, int c) { if (a < 0 || b < 0 || c < 0) { return 1; } if (a == 0 || b == 0 || c == 0) { return 0; } + /* check a*b fits in int first */ if (a > INT_MAX / b) { return 1; } +#ifdef HAVE_INT64_T + /* check a*b*c fits in int64 */ if ((int64_t)a * b > INT64_MAX / c) { return 1; } +#else + /* no 64-bit type available, check against INT_MAX */ + if (a > INT_MAX / b / c) { + return 1; + } +#endif return 0; } diff --git a/ext/gd/libgd/gd_span_rle.c b/ext/gd/libgd/gd_span_rle.c new file mode 100644 index 000000000000..015de52452b2 --- /dev/null +++ b/ext/gd/libgd/gd_span_rle.c @@ -0,0 +1,452 @@ +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gd_vector2d_private.h" +#include "gd_intern.h" +#include "gdhelpers.h" +#include "gd_errors.h" + +#include "gd_surface.h" +#include "gd_array.h" +#include "gd_span_rle.h" +#include "gd_path_matrix.h" +#include "gd_path.h" +#include "gd_path_outline.h" +#include "gd_path_dash.h" +#include "ftraster/gd_ft_raster.h" +#include "ftraster/gd_ft_stroker.h" +#include "ftraster/gd_ft_types.h" +#include "ftraster/gd_ft_math.h" + +#define SQRT2 1.41421356237309504880 + +gdSpanRlePtr gdSpanRleCreate() +{ + gdSpanRlePtr rle = gdMalloc(sizeof(gdSpanRle)); + if (!rle) + return NULL; + _rle_spans_init(rle->spans); + rle->x = 0; + rle->y = 0; + rle->w = 0; + rle->h = 0; + return rle; +} + +void gdSpanRleDestroy(gdSpanRlePtr rle) +{ + if (rle == NULL) + return; + gdFree(rle->spans.data); + gdFree(rle); +} + +void gdSpanRleClear(gdSpanRlePtr rle) +{ + rle->spans.size = 0; + rle->x = 0; + rle->y = 0; + rle->w = 0; + rle->h = 0; +} + +gdSpanRlePtr gdSpanRleClone(gdSpanRlePtr rle) +{ + if (rle == NULL) return NULL; + + gdSpanRlePtr clone = gdMalloc(sizeof(gdSpanRle)); + if (!clone) return NULL; + + _rle_spans_init(clone->spans); + _rle_spans_allocate(clone->spans, rle->spans.size); + memcpy(clone->spans.data, rle->spans.data, (size_t)rle->spans.size * sizeof(gdSpan)); + clone->spans.size = rle->spans.size; + clone->x = rle->x; + clone->y = rle->y; + clone->w = rle->w; + clone->h = rle->h; + return clone; +} + +#define DIV255(x) (((x) + ((x) >> 8) + 0x80) >> 8) +gdSpanRlePtr gdSpanHorizontalClip(const gdSpanRlePtr a, const gdSpanRlePtr b) +{ + gdSpanRlePtr result = gdMalloc(sizeof(gdSpanRle)); + if (!result) + { + return NULL; + } + + _rle_spans_init(result->spans); + _rle_spans_allocate(result->spans, MAX(a->spans.size, b->spans.size)); + + gdSpanPtr a_spans = a->spans.data; + gdSpanPtr a_end = a_spans + a->spans.size; + + gdSpanPtr b_spans = b->spans.data; + gdSpanPtr b_end = b_spans + b->spans.size; + + while (a_spans < a_end && b_spans < b_end) + { + if (b_spans->y > a_spans->y) + { + ++a_spans; + continue; + } + + if (a_spans->y != b_spans->y) + { + ++b_spans; + continue; + } + + int ax1 = a_spans->x; + int ax2 = ax1 + a_spans->len; + int bx1 = b_spans->x; + int bx2 = bx1 + b_spans->len; + + if (bx1 < ax1 && bx2 < ax1) + { + ++b_spans; + continue; + } + + if (ax1 < bx1 && ax2 < bx1) + { + ++a_spans; + continue; + } + + int x = MAX(ax1, bx1); + int len = MIN(ax2, bx2) - x; + if (len) + { + gdSpanPtr span = result->spans.data + result->spans.size; + span->x = (short)x; + span->len = (unsigned short)len; + span->y = a_spans->y; + span->coverage = DIV255(a_spans->coverage * b_spans->coverage); + result->spans.size += 1; + } + + if (ax2 < bx2) + { + ++a_spans; + } + else + { + ++b_spans; + } + } + + if (result->spans.size == 0) + { + result->x = 0; + result->y = 0; + result->w = 0; + result->h = 0; + return result; + } + + gdSpanPtr spans = (result->spans.data); + int x1 = INT_MAX; + int y1 = spans[0].y; + int x2 = 0; + int y2 = spans[result->spans.size - 1].y; + for (int i = 0; i < result->spans.size; i++) + { + if (spans[i].x < x1) + x1 = spans[i].x; + if (spans[i].x + spans[i].len > x2) + x2 = spans[i].x + spans[i].len; + } + + result->x = x1; + result->y = y1; + result->w = x2 - x1; + result->h = y2 - y1 + 1; + return result; +} + +void gdSpanRlePathClip(gdSpanRlePtr rle, const gdSpanRlePtr clip) +{ + if (rle == NULL || clip == NULL) + return; + + gdSpanRlePtr result = gdSpanHorizontalClip(rle, clip); + if (!result) { + return; + } + _rle_spans_allocate(rle->spans, result->spans.size); + if (result->spans.size > 0) { + memcpy(rle->spans.data, result->spans.data, + (size_t)result->spans.size * sizeof(gdSpan)); + } + rle->spans.size = result->spans.size; + rle->x = result->x; + rle->y = result->y; + rle->w = result->w; + rle->h = result->h; + gdSpanRleDestroy(result); +} + +GD_FT_Outline *gd_ft_outline_create(int points, int contours) +{ + GD_FT_Outline *ft = gdMalloc(sizeof(GD_FT_Outline)); + if (!ft) + return NULL; + ft->points = malloc((size_t)(points + contours) * sizeof(GD_FT_Vector)); + ft->tags = malloc((size_t)(points + contours) * sizeof(char)); + ft->contours = malloc((size_t)contours * sizeof(short)); + ft->contours_flag = malloc((size_t)contours * sizeof(char)); + ft->n_points = ft->n_contours = 0; + ft->flags = 0x0; + return ft; +} + +void gd_ft_outline_close(GD_FT_Outline *ft) +{ + ft->contours_flag[ft->n_contours] = 0; + int index = ft->n_contours ? ft->contours[ft->n_contours - 1] + 1 : 0; + if (index == ft->n_points) + return; + + ft->points[ft->n_points].x = ft->points[index].x; + ft->points[ft->n_points].y = ft->points[index].y; + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_ON; + ft->n_points++; +} + +void gd_ft_outline_end(GD_FT_Outline *ft) +{ + if (ft->n_points) + { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } +} + +#define FT_COORD(x) (GD_FT_Pos)((x)*64) +void gd_ft_outline_move_to(GD_FT_Outline *ft, double x, double y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_ON; + if (ft->n_points) + { + ft->contours[ft->n_contours] = ft->n_points - 1; + ft->n_contours++; + } + + ft->contours_flag[ft->n_contours] = 1; + ft->n_points++; +} + +void gd_ft_outline_line_to(GD_FT_Outline *ft, double x, double y) +{ + ft->points[ft->n_points].x = FT_COORD(x); + ft->points[ft->n_points].y = FT_COORD(y); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_ON; + ft->n_points++; +} + +void gd_ft_outline_cubic_to(GD_FT_Outline *ft, double x1, double y1, double x2, double y2, double x3, double y3) +{ + ft->points[ft->n_points].x = FT_COORD(x1); + ft->points[ft->n_points].y = FT_COORD(y1); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x2); + ft->points[ft->n_points].y = FT_COORD(y2); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_CUBIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x3); + ft->points[ft->n_points].y = FT_COORD(y3); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_ON; + ft->n_points++; +} + +void gd_ft_outline_conic_to(GD_FT_Outline *ft, double x1, double y1, double x2, double y2) +{ + ft->points[ft->n_points].x = FT_COORD(x1); + ft->points[ft->n_points].y = FT_COORD(y1); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_CONIC; + ft->n_points++; + + ft->points[ft->n_points].x = FT_COORD(x2); + ft->points[ft->n_points].y = FT_COORD(y2); + ft->tags[ft->n_points] = GD_FT_CURVE_TAG_ON; + ft->n_points++; +} + +GD_FT_Outline *gd_ft_outline_convert(const gdPathPtr path, const gdPathMatrixPtr matrix) +{ + /* A path may begin with LineTo/CurveTo. The outline bridge treats that + prefix as an implicit contour before the first explicit MoveTo. */ + int contour_capacity = path->contours + 1; + GD_FT_Outline *outline = gd_ft_outline_create( + gdArrayNumElements(&path->points), contour_capacity); + gdPointF p[3]; + unsigned int numElements = gdArrayNumElements(&path->elements); + unsigned int pointsIndex = 0; + unsigned int i = 0; + int contour_open = 0; + + memset(p, 0, sizeof(gdPointF) * 3); + if (!outline) + return NULL; + for (i = 0; i < numElements; i++) + { + gdPathOpsPtr element = (gdPathOpsPtr)gdArrayIndex(&path->elements, i); + gdPointFPtr point = gdArrayIndex(&path->points, pointsIndex); + switch (*element) + { + case gdPathOpsMoveTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + gd_ft_outline_move_to(outline, p[0].x, p[0].y); + contour_open = 1; + pointsIndex += 1; + break; + case gdPathOpsLineTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + if (contour_open) + gd_ft_outline_line_to(outline, p[0].x, p[0].y); + else { + gd_ft_outline_move_to(outline, p[0].x, p[0].y); + contour_open = 1; + } + pointsIndex += 1; + break; + case gdPathOpsCubicTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + point = gdArrayIndex(&path->points, pointsIndex + 2); + gdPathMatrixMapPoint(matrix, point, &p[2]); + if (contour_open) + gd_ft_outline_cubic_to(outline, p[0].x, p[0].y, + p[1].x, p[1].y, p[2].x, p[2].y); + else { + gd_ft_outline_move_to(outline, p[2].x, p[2].y); + contour_open = 1; + } + pointsIndex += 3; + break; + case gdPathOpsClose: + if (contour_open) { + gd_ft_outline_close(outline); + contour_open = 0; + } + pointsIndex += 1; + break; + case gdPathOpsQuadTo: + gdPathMatrixMapPoint(matrix, point, &p[0]); + point = gdArrayIndex(&path->points, pointsIndex + 1); + gdPathMatrixMapPoint(matrix, point, &p[1]); + if (contour_open) + gd_ft_outline_conic_to(outline, p[0].x, p[0].y, + p[1].x, p[1].y); + else { + gd_ft_outline_move_to(outline, p[1].x, p[1].y); + contour_open = 1; + } + pointsIndex += 2; + break; + } + } + + gd_ft_outline_end(outline); + return outline; +} + +void gd_ft_outline_destroy(GD_FT_Outline *ft) +{ + gdFree(ft->points); + gdFree(ft->tags); + gdFree(ft->contours); + gdFree(ft->contours_flag); + gdFree(ft); +} + +static void generation_callback(int count, const GD_FT_Span *spans, void *user) +{ + gdSpanRlePtr rle = user; + _rle_spans_allocate(rle->spans, count); + gdSpanPtr data = rle->spans.data + rle->spans.size; + memcpy(data, spans, (size_t)count * sizeof(gdSpan)); + rle->spans.size += count; +} + +static void bbox_callback(int x, int y, int w, int h, void *user) +{ + gdSpanRlePtr rle = user; + rle->x = x; + rle->y = y; + rle->w = w; + rle->h = h; +} + +static void _rasterize_fill(gdSpanRlePtr rle, const gdPathPtr path, const gdPathMatrixPtr matrix, const gdRectFPtr clip, gdFillRule winding) +{ + static gdPathMatrix identity_matrix = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; + GD_FT_Raster_Params params = {0}; + params.flags = GD_FT_RASTER_FLAG_DIRECT | GD_FT_RASTER_FLAG_AA; + params.gray_spans = generation_callback; + params.bbox_cb = bbox_callback; + params.user = rle; + + if (clip) + { + params.flags |= GD_FT_RASTER_FLAG_CLIP; + params.clip_box.xMin = (GD_FT_Pos)clip->x; + params.clip_box.yMin = (GD_FT_Pos)clip->y; + params.clip_box.xMax = (GD_FT_Pos)(clip->x + clip->w); + params.clip_box.yMax = (GD_FT_Pos)(clip->y + clip->h); + } + + gd_ft_raster_render_path(path, matrix ? matrix : &identity_matrix, ¶ms, + winding == gdFillRulEvenOdd ? GD_FT_OUTLINE_EVEN_ODD_FILL + : GD_FT_OUTLINE_NONE); +} + +void gdSpanRleRasterize(gdSpanRlePtr rle, const gdPathPtr path, const gdPathMatrixPtr matrix, const gdRectFPtr clip, const gdStrokePtr stroke, gdFillRule winding) +{ + if (stroke && stroke->width > 0) + { + gdPathPtr pathToStroke = (gdPathPtr)path; + + // Apply dash pattern to original path BEFORE stroke conversion + if (stroke->dash) + { + pathToStroke = gdPathApplyDash(stroke->dash, path); + if (!pathToStroke) + return; + } + + gdPathPtr strokePath = gdPathStrokeToPath(pathToStroke, stroke, matrix); + if (!strokePath) + { + if (pathToStroke != path) + gdPathDestroy(pathToStroke); + return; + } + + if (pathToStroke != path) + gdPathDestroy(pathToStroke); + + _rasterize_fill(rle, strokePath, NULL, clip, winding); + gdPathDestroy(strokePath); + } + else + { + _rasterize_fill(rle, path, matrix, clip, winding); + } +} diff --git a/ext/gd/libgd/gd_span_rle.h b/ext/gd/libgd/gd_span_rle.h new file mode 100644 index 000000000000..5e674990b75e --- /dev/null +++ b/ext/gd/libgd/gd_span_rle.h @@ -0,0 +1,28 @@ +#ifndef GD_SPAN_RLE_H +#define GD_SPAN_RLE_H + +#define _rle_spans_init(rle_s) \ + do { \ + rle_s.data = NULL; \ + rle_s.size = 0; \ + rle_s.capacity = 0; \ + } while(0) + +#define _rle_spans_allocate(rle_s, count) \ + do { \ + if(rle_s.size + count > rle_s.capacity) { \ + int capacity = rle_s.size + count; \ + int newcapacity = rle_s.capacity == 0 ? 8 : rle_s.capacity; \ + while(newcapacity < capacity) { newcapacity *= 2; } \ + rle_s.data = gdRealloc(rle_s.data, (size_t)newcapacity * sizeof(rle_s.data[0])); \ + rle_s.capacity = newcapacity; \ + } \ + } while(0) + +gdSpanRlePtr gdSpanRleCreate(); +void gdSpanRleDestroy(gdSpanRlePtr rle); +void gdSpanRleClear(gdSpanRlePtr rle); +void gdSpanRleRasterize(gdSpanRlePtr rle, const gdPathPtr path, const gdPathMatrixPtr matrix, const gdRectFPtr clip, const gdStrokePtr stroke, gdFillRule winding); +void gdSpanRlePathClip(gdSpanRlePtr rle, const gdSpanRlePtr clip); +gdSpanRlePtr gdSpanRleClone(gdSpanRlePtr rle); +#endif // GD_SPAN_RLE_H diff --git a/ext/gd/libgd/gd_ss.c b/ext/gd/libgd/gd_ss.c index 048ac0a70085..fdb1debd04f1 100644 --- a/ext/gd/libgd/gd_ss.c +++ b/ext/gd/libgd/gd_ss.c @@ -1,8 +1,9 @@ -#include +#include "gd.h" +#include "gd_errors.h" #include -#include +#include #include -#include "gd.h" +#include #define TRUE 1 #define FALSE 0 @@ -17,15 +18,22 @@ extern gdImagePtr gdImageCreateFromPngSource (gdSourcePtr inSource); #define GD_SS_DBG(s) #ifdef HAVE_LIBPNG -void gdImagePngToSink (gdImagePtr im, gdSinkPtr outSink) -{ +/* + Function: gdImagePngToSink +*/ +BGD_DECLARE(void) gdImagePngToSink(gdImagePtr im, gdSinkPtr outSink) { gdIOCtx *out = gdNewSSCtx(NULL, outSink); gdImagePngCtx(im, out); out->gd_free(out); } -gdImagePtr gdImageCreateFromPngSource (gdSourcePtr inSource) -{ +/* + Function: gdImageCreateFromPngSource + + See for documentation. This is obsolete; use + instead. + */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngSource(gdSourcePtr inSource) { gdIOCtx *in = gdNewSSCtx(inSource, NULL); gdImagePtr im; @@ -36,14 +44,15 @@ gdImagePtr gdImageCreateFromPngSource (gdSourcePtr inSource) return im; } #else /* no HAVE_LIBPNG */ -void gdImagePngToSink (gdImagePtr im, gdSinkPtr outSink) -{ - gd_error("PNG support is not available"); +BGD_DECLARE(void) gdImagePngToSink(gdImagePtr im, gdSinkPtr outSink) { + (void)im; + (void)outSink; + gd_error("PNG support is not available\n"); } -gdImagePtr gdImageCreateFromPngSource (gdSourcePtr inSource) -{ - gd_error("PNG support is not available"); + +BGD_DECLARE(gdImagePtr) gdImageCreateFromPngSource(gdSourcePtr inSource) { + (void)inSource; + gd_error("PNG support is not available\n"); return NULL; } #endif /* HAVE_LIBPNG */ - diff --git a/ext/gd/libgd/gd_surface.c b/ext/gd/libgd/gd_surface.c new file mode 100644 index 000000000000..6841e7644286 --- /dev/null +++ b/ext/gd/libgd/gd_surface.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd_intern.h" +#include "gd_vector2d_private.h" +#include "gdhelpers.h" +#include "gd_errors.h" + +#include "gd_surface.h" +#include "gd_array.h" + +static int checkOverflowAndType(int width, int height, unsigned type) { + int bytes_per_pixel; + if (type == GD_SURFACE_NONE || type >= GD_SURFACE_COUNT || width <= 0 || height <= 0) { + gd_error("gdSurface: invalid dimensions or surface type\n"); + return 1; + } + bytes_per_pixel = type == GD_SURFACE_A8 ? 1 : 4; + if (overflow2(width, bytes_per_pixel) || + overflow2(width * bytes_per_pixel, height)) { + return 1; + } + return 0; +} + +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceCreate(int width, int height, unsigned int type) +{ + gdSurfacePtr surface; + + if (type >= GD_SURFACE_COUNT) { + return NULL; + } + if (checkOverflowAndType(width, height, type)) { + return NULL; + } + surface = gdMalloc(sizeof(gdSurface)); + if (!surface) { + return NULL; + } + + const int bytes_per_pixel = type == GD_SURFACE_A8 ? 1 : 4; + const size_t size = (size_t)width * (size_t)height * (size_t)bytes_per_pixel; + surface->data = gdCalloc(1, size); + if (!surface->data) { + gdFree(surface); + return NULL; + } + surface->type = type; + surface->ref = 1; + surface->gdOwned = 1; + surface->width = width; + surface->height = height; + surface->stride = width * bytes_per_pixel; + return surface; +} + +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceCreateForData(unsigned char* data, int width, int height, int stride, unsigned int type) +{ + gdSurfacePtr surface; + + if (!data) { + return NULL; + } + if (checkOverflowAndType(width, height, type)) { + return NULL; + } + if (stride < width * (type == GD_SURFACE_A8 ? 1 : 4)) { + return NULL; + } + + surface = gdMalloc(sizeof(gdSurface)); + if (!surface) { + return NULL; + } + surface->ref = 1; + surface->gdOwned = 0; + surface->data = data; + surface->width = width; + surface->height = height; + surface->stride = stride; + surface->type = type; + return surface; +} + +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceAddRef(gdSurfacePtr surface) +{ + if(surface==NULL) { + return NULL; + } + surface->ref++; + return surface; +} + +GD_VECTOR2D_INTERNAL unsigned char *gdSurfaceGetData(const gdSurfacePtr surface) +{ + if(surface==NULL) { + return NULL; + } + return surface->data; +} + +GD_VECTOR2D_INTERNAL gdSurfaceType gdSurfaceGetType(const gdSurfacePtr surface) +{ + if(surface==NULL) { + return GD_SURFACE_NONE; + } + return surface->type; +} + +GD_VECTOR2D_INTERNAL int gdSurfaceGetWidth(const gdSurfacePtr surface) +{ + return surface->width; +} + +GD_VECTOR2D_INTERNAL int gdSurfaceGetHeight(const gdSurfacePtr surface) +{ + return surface->height; +} + +GD_VECTOR2D_INTERNAL int gdSurfaceGetStride(const gdSurfacePtr surface) +{ + return surface->stride; +} + +GD_VECTOR2D_INTERNAL void gdSurfaceDestroy (gdSurfacePtr surface) +{ + if (!surface) { + return; + } + if (--surface->ref == 0) { + if (surface->gdOwned) { + gdFree(surface->data); + } + gdFree(surface); + } + +} diff --git a/ext/gd/libgd/gd_surface.h b/ext/gd/libgd/gd_surface.h new file mode 100644 index 000000000000..85446d49dccc --- /dev/null +++ b/ext/gd/libgd/gd_surface.h @@ -0,0 +1,6 @@ +#ifndef GD_SURFACE_H +#define GD_SURFACE_H + +#include "gd_vector2d_private.h" + +#endif // GD_SURFACE_H diff --git a/ext/gd/libgd/gd_tga.c b/ext/gd/libgd/gd_tga.c index f888f9ea6fbe..d2ed2d967909 100644 --- a/ext/gd/libgd/gd_tga.c +++ b/ext/gd/libgd/gd_tga.c @@ -8,16 +8,29 @@ #include "config.h" #endif /* HAVE_CONFIG_H */ -#include #include +#include #include #include -#include "gd_tga.h" #include "gd.h" #include "gd_errors.h" +#include "gd_io.h" +#include "gd_tga.h" #include "gdhelpers.h" +static int tga_is_rle(uint8_t imagetype); +static int tga_pixel_size(uint8_t bits); +static int tga_read_color_map(gdIOCtx *ctx, oTga *tga); +static int tga_read_pixel(gdIOCtx *ctx, oTga *tga, int *pixel); +static void tga_apply_attribute_type(gdIOCtx *ctx, oTga *tga, int pixel_count); +static int tga_decode_color(const unsigned char *buf, int bits, int alpha_bits, + int *has_alpha); +static int tga_decode_16(unsigned int value, int alpha_bits, int *has_alpha); +static int tga_scale_5_to_8(int c); +static int tga_alpha_8_to_gd(int a); +static void tga_strip_alpha(oTga *tga, int pixel_count); + /* Function: gdImageCreateFromTga @@ -27,30 +40,29 @@ infile - Pointer to TGA binary file */ -gdImagePtr gdImageCreateFromTga(FILE *fp) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTga(FILE *fp) { gdImagePtr image; - gdIOCtx* in = gdNewFileCtx(fp); - if (in == NULL) return NULL; + gdIOCtx *in = gdNewFileCtx(fp); + if (in == NULL) + return NULL; image = gdImageCreateFromTgaCtx(in); - in->gd_free( in ); + in->gd_free(in); return image; } /* Function: gdImageCreateFromTgaPtr */ -gdImagePtr gdImageCreateFromTgaPtr(int size, void *data) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTgaPtr(int size, void *data) { gdImagePtr im; - gdIOCtx *in = gdNewDynamicCtxEx (size, data, 0); - if (in == NULL) return NULL; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) + return NULL; im = gdImageCreateFromTgaCtx(in); in->gd_free(in); return im; } - /* Function: gdImageCreateFromTgaCtx @@ -59,43 +71,41 @@ gdImagePtr gdImageCreateFromTgaPtr(int size, void *data) Parameters: ctx - Pointer to a gdIOCtx structure */ -gdImagePtr gdImageCreateFromTgaCtx(gdIOCtx* ctx) -{ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTgaCtx(gdIOCtx *ctx) { int bitmap_caret = 0; oTga *tga = NULL; - /* int pixel_block_size = 0; - int image_block_size = 0; */ volatile gdImagePtr image = NULL; int x = 0; int y = 0; - tga = (oTga *) gdMalloc(sizeof(oTga)); + if (ctx == NULL) { + return NULL; + } + + tga = (oTga *)gdMalloc(sizeof(oTga)); if (!tga) { return NULL; } tga->bitmap = NULL; + tga->colormap = NULL; tga->ident = NULL; + tga->has_alpha = 0; if (read_header_tga(ctx, tga) < 0) { free_tga(tga); return NULL; } - /*TODO: Will this be used? - pixel_block_size = tga->bits / 8; - image_block_size = (tga->width * tga->height) * pixel_block_size; - */ - if (read_image_tga(ctx, tga) < 0) { free_tga(tga); return NULL; } - image = gdImageCreateTrueColor((int)tga->width, (int)tga->height ); + image = gdImageCreateTrueColor((int)tga->width, (int)tga->height); if (image == 0) { - free_tga( tga ); + free_tga(tga); return NULL; } @@ -103,24 +113,15 @@ gdImagePtr gdImageCreateFromTgaCtx(gdIOCtx* ctx) * Copy the pixel data from our tga bitmap buffer into the GD image * Disable blending and save the alpha channel per default */ - if (tga->alphabits) { + if (tga->has_alpha) { gdImageAlphaBlending(image, 0); gdImageSaveAlpha(image, 1); } - /* TODO: use alphabits as soon as we support 24bit and other alpha bps (ie != 8bits) */ for (y = 0; y < tga->height; y++) { register int *tpix = image->tpixels[y]; - for ( x = 0; x < tga->width; x++, tpix++) { - if (tga->bits == TGA_BPP_24) { - *tpix = gdTrueColor(tga->bitmap[bitmap_caret + 2], tga->bitmap[bitmap_caret + 1], tga->bitmap[bitmap_caret]); - bitmap_caret += 3; - } else if (tga->bits == TGA_BPP_32 && tga->alphabits) { - register int a = tga->bitmap[bitmap_caret + 3]; - - *tpix = gdTrueColorAlpha(tga->bitmap[bitmap_caret + 2], tga->bitmap[bitmap_caret + 1], tga->bitmap[bitmap_caret], gdAlphaMax - (a >> 1)); - bitmap_caret += 4; - } + for (x = 0; x < tga->width; x++, tpix++) { + *tpix = tga->bitmap[bitmap_caret++]; } } @@ -138,18 +139,16 @@ gdImagePtr gdImageCreateFromTgaCtx(gdIOCtx* ctx) } /*! \brief Reads a TGA header. - * Reads the header block from a binary TGA file populating the referenced TGA structure. - * \param ctx Pointer to TGA binary file - * \param tga Pointer to TGA structure - * \return int 1 on success, -1 on failure + * Reads the header block from a binary TGA file populating the referenced TGA + *structure. \param ctx Pointer to TGA binary file \param tga Pointer to TGA + *structure \return int 1 on sucess, -1 on failure */ -int read_header_tga(gdIOCtx *ctx, oTga *tga) -{ +int read_header_tga(gdIOCtx *ctx, oTga *tga) { unsigned char header[18]; if (gdGetBuf(header, sizeof(header), ctx) < 18) { - gd_error("Fail to read header"); + gd_error("fail to read header"); return -1; } @@ -167,6 +166,7 @@ int read_header_tga(gdIOCtx *ctx, oTga *tga) tga->alphabits = header[17] & 0x0f; tga->fliph = (header[17] & 0x10) ? 1 : 0; tga->flipv = (header[17] & 0x20) ? 0 : 1; + tga->has_alpha = 0; #ifdef DEBUG printf("format bps: %i\n", tga->bits); @@ -175,19 +175,58 @@ int read_header_tga(gdIOCtx *ctx, oTga *tga) printf("wxh: %i %i\n", tga->width, tga->height); #endif - if (!((tga->bits == TGA_BPP_24 && tga->alphabits == 0) - || (tga->bits == TGA_BPP_32 && tga->alphabits == 8))) - { - gd_error_ex(GD_WARNING, "gd-tga: %u bits per pixel with %u alpha bits not supported\n", - tga->bits, tga->alphabits); + if (tga->width <= 0 || tga->height <= 0) { + gd_error("gd-tga: invalid image dimensions\n"); + return -1; + } + + if (tga->colormaptype > 1) { + gd_error_ex(GD_WARNING, "gd-tga: unsupported color map type %u\n", + tga->colormaptype); + return -1; + } + + switch (tga->imagetype) { + case TGA_TYPE_INDEXED: + case TGA_TYPE_INDEXED_RLE: + if (tga->colormaptype != 1 || tga->bits != TGA_BPP_8 || + !(tga->colormapbits == 15 || tga->colormapbits == 16 || + tga->colormapbits == 24 || tga->colormapbits == 32)) { + gd_error_ex(GD_WARNING, "gd-tga: unsupported color mapped image\n"); + return -1; + } + break; + + case TGA_TYPE_RGB: + case TGA_TYPE_RGB_RLE: + if (!(tga->bits == TGA_BPP_16 || tga->bits == TGA_BPP_24 || + (tga->bits == TGA_BPP_32 && tga->alphabits == 8))) { + gd_error_ex(GD_WARNING, "gd-tga: unsupported truecolor depth %u\n", + tga->bits); + return -1; + } + break; + + case TGA_TYPE_GREYSCALE: + case TGA_TYPE_GREYSCALE_RLE: + if (tga->bits != TGA_BPP_8) { + gd_error_ex(GD_WARNING, "gd-tga: unsupported grayscale depth %u\n", + tga->bits); + return -1; + } + break; + + default: + gd_error_ex(GD_WARNING, "gd-tga: unsupported image type %u\n", + tga->imagetype); return -1; } tga->ident = NULL; if (tga->identsize > 0) { - tga->ident = (char *) gdMalloc(tga->identsize * sizeof(char)); - if(tga->ident == NULL) { + tga->ident = (char *)gdMalloc(tga->identsize * sizeof(char)); + if (tga->ident == NULL) { return -1; } @@ -201,163 +240,318 @@ int read_header_tga(gdIOCtx *ctx, oTga *tga) } /*! \brief Reads a TGA image data into buffer. - * Reads the image data block from a binary TGA file populating the referenced TGA structure. - * \param ctx Pointer to TGA binary file - * \param tga Pointer to TGA structure - * \return int 0 on success, -1 on failure + * Reads the image data block from a binary TGA file populating the referenced + *TGA structure. \param ctx Pointer to TGA binary file \param tga Pointer to TGA + *structure \return int 0 on sucess, -1 on failure */ -int read_image_tga( gdIOCtx *ctx, oTga *tga ) -{ - int pixel_block_size = (tga->bits / 8); - int image_block_size; - int* decompression_buffer = NULL; - unsigned char* conversion_buffer = NULL; - int buffer_caret = 0; +int read_image_tga(gdIOCtx *ctx, oTga *tga) { + int pixel_count; int bitmap_caret = 0; - int i = 0; - int encoded_pixels; - int rle_size; - if(overflow2(tga->width, tga->height)) { + if (overflow2(tga->width, tga->height)) { return -1; } - if(overflow2(tga->width * tga->height, pixel_block_size)) { + pixel_count = tga->width * tga->height; + if (overflow2(pixel_count, sizeof(int))) { return -1; } - image_block_size = (tga->width * tga->height) * pixel_block_size; - if(overflow2(image_block_size, sizeof(int))) { + if (tga_read_color_map(ctx, tga) < 0) { return -1; } - /*! \todo Add more image type support. - */ - if (tga->imagetype != TGA_TYPE_RGB && tga->imagetype != TGA_TYPE_RGB_RLE) - return -1; - - /*! \brief Allocate memmory for image block + /*! \brief Allocate memory for image block * Allocate a chunk of memory for the image block to be passed into. */ - tga->bitmap = (int *) gdMalloc(image_block_size * sizeof(int)); + tga->bitmap = (int *)gdMalloc(pixel_count * sizeof(int)); if (tga->bitmap == NULL) return -1; - switch (tga->imagetype) { - case TGA_TYPE_RGB: - /*! \brief Read in uncompressed RGB TGA - * Chunk load the pixel data from an uncompressed RGB type TGA. - */ - conversion_buffer = (unsigned char *) gdMalloc(image_block_size * sizeof(unsigned char)); - if (conversion_buffer == NULL) { - return -1; - } + if (tga_is_rle(tga->imagetype)) { + while (bitmap_caret < pixel_count) { + int packet = gdGetC(ctx); + int encoded_pixels; + int pixel; + int i; - if (gdGetBuf(conversion_buffer, image_block_size, ctx) != image_block_size) { - gd_error("gd-tga: premature end of image data\n"); - gdFree(conversion_buffer); - return -1; - } + if (packet == EOF) { + gd_error("gd-tga: premature end of image data\n"); + return -1; + } - while (buffer_caret < image_block_size) { - tga->bitmap[buffer_caret] = (int) conversion_buffer[buffer_caret]; - buffer_caret++; + encoded_pixels = (packet & ~TGA_RLE_FLAG) + 1; + if (encoded_pixels > pixel_count - bitmap_caret) { + return -1; + } + + if ((packet & TGA_RLE_FLAG) == TGA_RLE_FLAG) { + if (tga_read_pixel(ctx, tga, &pixel) < 0) { + return -1; + } + for (i = 0; i < encoded_pixels; i++) { + tga->bitmap[bitmap_caret++] = pixel; + } + } else { + for (i = 0; i < encoded_pixels; i++) { + if (tga_read_pixel(ctx, tga, &pixel) < 0) { + return -1; + } + tga->bitmap[bitmap_caret++] = pixel; + } + } } + } else { + while (bitmap_caret < pixel_count) { + if (tga_read_pixel(ctx, tga, &tga->bitmap[bitmap_caret]) < 0) { + return -1; + } + bitmap_caret++; + } + } - gdFree(conversion_buffer); - break; + tga_apply_attribute_type(ctx, tga, pixel_count); - case TGA_TYPE_RGB_RLE: - /*! \brief Read in RLE compressed RGB TGA - * Chunk load the pixel data from an RLE compressed RGB type TGA. - */ - decompression_buffer = (int*) gdMalloc(image_block_size * sizeof(int)); - if (decompression_buffer == NULL) { + return 1; +} + +static int tga_is_rle(uint8_t imagetype) { + return imagetype == TGA_TYPE_INDEXED_RLE || imagetype == TGA_TYPE_RGB_RLE || + imagetype == TGA_TYPE_GREYSCALE_RLE; +} + +static int tga_pixel_size(uint8_t bits) { return (bits + 7) / 8; } + +static int tga_read_color_map(gdIOCtx *ctx, oTga *tga) { + int i; + int has_alpha = 0; + int entry_size; + + if (tga->colormaptype == 0) { + return 1; + } + + if (tga->colormaplength <= 0) { + return -1; + } + + if (overflow2(tga->colormaplength, sizeof(int))) { + return -1; + } + + entry_size = tga_pixel_size(tga->colormapbits); + tga->colormap = (int *)gdMalloc(tga->colormaplength * sizeof(int)); + if (tga->colormap == NULL) { + return -1; + } + + for (i = 0; i < tga->colormaplength; i++) { + unsigned char buf[4] = {0, 0, 0, 0}; + + if (gdGetBuf(buf, entry_size, ctx) != entry_size) { + gd_error("gd-tga: premature end of color map data\n"); return -1; } - conversion_buffer = (unsigned char *) gdMalloc(image_block_size * sizeof(unsigned char)); - if (conversion_buffer == NULL) { - gd_error("gd-tga: premature end of image data\n"); - gdFree( decompression_buffer ); + + tga->colormap[i] = + tga_decode_color(buf, tga->colormapbits, + tga->colormapbits == 32 ? 8 : 0, &has_alpha); + } + + if (has_alpha) { + tga->has_alpha = 1; + } + + return 1; +} + +static int tga_read_pixel(gdIOCtx *ctx, oTga *tga, int *pixel) { + unsigned char buf[4] = {0, 0, 0, 0}; + int size = tga_pixel_size(tga->bits); + int has_alpha = 0; + + if (gdGetBuf(buf, size, ctx) != size) { + gd_error("gd-tga: premature end of image data\n"); + return -1; + } + + switch (tga->imagetype) { + case TGA_TYPE_INDEXED: + case TGA_TYPE_INDEXED_RLE: { + int index = buf[0] - tga->colormapstart; + + if (index < 0 || index >= tga->colormaplength || + tga->colormap == NULL) { return -1; } + *pixel = tga->colormap[index]; + break; + } - rle_size = gdGetBuf(conversion_buffer, image_block_size, ctx); - if (rle_size <= 0) { - gdFree(conversion_buffer); - gdFree(decompression_buffer); - return -1; + case TGA_TYPE_RGB: + case TGA_TYPE_RGB_RLE: + *pixel = tga_decode_color(buf, tga->bits, tga->alphabits, &has_alpha); + if (has_alpha) { + tga->has_alpha = 1; } + break; + + case TGA_TYPE_GREYSCALE: + case TGA_TYPE_GREYSCALE_RLE: + *pixel = gdTrueColor(buf[0], buf[0], buf[0]); + break; - buffer_caret = 0; + default: + return -1; + } + + return 1; +} + +static void tga_apply_attribute_type(gdIOCtx *ctx, oTga *tga, int pixel_count) { + static const unsigned char signature[18] = {'T', 'R', 'U', 'E', 'V', 'I', + 'S', 'I', 'O', 'N', '-', 'X', + 'F', 'I', 'L', 'E', '.', '\0'}; + unsigned char footer_ring[26]; + unsigned char footer[26]; + size_t trailing_size = 0; + size_t i; + int c; + long start; + uint32_t extension_offset; + uint32_t attr_type_offset; + uint32_t end_offset; + int attr_type; + + if (!tga->has_alpha) { + return; + } + + if (ctx->tell == NULL || ctx->seek == NULL) { + return; + } - while( buffer_caret < rle_size) { - decompression_buffer[buffer_caret] = (int)conversion_buffer[buffer_caret]; - buffer_caret++; + start = gdTell(ctx); + if (start < 0 || start > INT_MAX) { + return; + } + + while ((c = gdGetC(ctx)) != EOF) { + footer_ring[trailing_size % sizeof(footer_ring)] = (unsigned char)c; + if (trailing_size == SIZE_MAX) { + return; } + trailing_size++; + } - buffer_caret = 0; + if (trailing_size < sizeof(footer)) { + return; + } - while( bitmap_caret < image_block_size ) { + for (i = 0; i < sizeof(footer); i++) { + footer[i] = footer_ring[(trailing_size + i) % sizeof(footer_ring)]; + } + if (memcmp(footer + 8, signature, sizeof(signature)) != 0) { + return; + } - if (buffer_caret + pixel_block_size > rle_size) { - gdFree( decompression_buffer ); - gdFree( conversion_buffer ); - return -1; - } + extension_offset = (uint32_t)footer[0] | ((uint32_t)footer[1] << 8) | + ((uint32_t)footer[2] << 16) | + ((uint32_t)footer[3] << 24); + if (extension_offset == 0 || extension_offset < (uint32_t)start || + extension_offset > (uint32_t)INT_MAX - 494) { + return; + } - if ((decompression_buffer[buffer_caret] & TGA_RLE_FLAG) == TGA_RLE_FLAG) { - encoded_pixels = ( ( decompression_buffer[ buffer_caret ] & ~TGA_RLE_FLAG ) + 1 ); - buffer_caret++; + attr_type_offset = extension_offset + 494; + if (trailing_size > (size_t)INT_MAX - (size_t)start || + attr_type_offset >= (uint32_t)start + (uint32_t)trailing_size - + sizeof(footer)) { + return; + } + end_offset = (uint32_t)start + (uint32_t)trailing_size; - if ((bitmap_caret + (encoded_pixels * pixel_block_size)) > image_block_size - || buffer_caret + pixel_block_size > rle_size) { - gdFree( decompression_buffer ); - gdFree( conversion_buffer ); - return -1; - } + if (!gdSeek(ctx, (int)attr_type_offset)) { + return; + } + attr_type = gdGetC(ctx); + gdSeek(ctx, (int)end_offset); + if (attr_type == EOF) { + return; + } + if (attr_type != 3 && attr_type != 4) { + tga_strip_alpha(tga, pixel_count); + } +} - for (i = 0; i < encoded_pixels; i++) { - memcpy(tga->bitmap + bitmap_caret, decompression_buffer + buffer_caret, pixel_block_size * sizeof(int)); - bitmap_caret += pixel_block_size; - } - buffer_caret += pixel_block_size; +static int tga_decode_color(const unsigned char *buf, int bits, int alpha_bits, + int *has_alpha) { + switch (bits) { + case 15: + case 16: + return tga_decode_16((unsigned int)buf[0] | ((unsigned int)buf[1] << 8), + alpha_bits, has_alpha); - } else { - encoded_pixels = decompression_buffer[ buffer_caret ] + 1; - buffer_caret++; + case 24: + return gdTrueColor(buf[2], buf[1], buf[0]); - if ((bitmap_caret + (encoded_pixels * pixel_block_size)) > image_block_size - || buffer_caret + (encoded_pixels * pixel_block_size) > rle_size) { - gdFree( decompression_buffer ); - gdFree( conversion_buffer ); - return -1; - } + case 32: + if (has_alpha) { + *has_alpha = 1; + } + return gdTrueColorAlpha(buf[2], buf[1], buf[0], + tga_alpha_8_to_gd(buf[3])); + } - memcpy(tga->bitmap + bitmap_caret, decompression_buffer + buffer_caret, encoded_pixels * pixel_block_size * sizeof(int)); - bitmap_caret += (encoded_pixels * pixel_block_size); - buffer_caret += (encoded_pixels * pixel_block_size); - } + return 0; +} + +static int tga_decode_16(unsigned int value, int alpha_bits, int *has_alpha) { + int b = tga_scale_5_to_8(value & 0x1f); + int g = tga_scale_5_to_8((value >> 5) & 0x1f); + int r = tga_scale_5_to_8((value >> 10) & 0x1f); + + if (alpha_bits > 0) { + int a = (value & 0x8000) ? gdAlphaOpaque : gdAlphaTransparent; + + if (has_alpha) { + *has_alpha = 1; } - gdFree( decompression_buffer ); - gdFree( conversion_buffer ); - break; + return gdTrueColorAlpha(r, g, b, a); } - return 1; + return gdTrueColor(r, g, b); +} + +static int tga_scale_5_to_8(int c) { return (c * 255) / 31; } + +static int tga_alpha_8_to_gd(int a) { return gdAlphaMax - (a >> 1); } + +static void tga_strip_alpha(oTga *tga, int pixel_count) { + int i; + + for (i = 0; i < pixel_count; i++) { + int pixel = tga->bitmap[i]; + + tga->bitmap[i] = + gdTrueColor(gdTrueColorGetRed(pixel), gdTrueColorGetGreen(pixel), + gdTrueColorGetBlue(pixel)); + } + tga->has_alpha = 0; } /*! \brief Cleans up a TGA structure. - * Dereferences the bitmap referenced in a TGA structure, then the structure itself - * \param tga Pointer to TGA structure + * Dereferences the bitmap referenced in a TGA structure, then the structure + *itself \param tga Pointer to TGA structure */ -void free_tga(oTga * tga) -{ +void free_tga(oTga *tga) { if (tga) { if (tga->ident) gdFree(tga->ident); if (tga->bitmap) gdFree(tga->bitmap); + if (tga->colormap) + gdFree(tga->colormap); gdFree(tga); } } diff --git a/ext/gd/libgd/gd_tga.h b/ext/gd/libgd/gd_tga.h index 297f3dc99d6d..30ccd62788fd 100644 --- a/ext/gd/libgd/gd_tga.h +++ b/ext/gd/libgd/gd_tga.h @@ -1,5 +1,5 @@ #ifndef __TGA_H -#define __TGA_H 1 +#define __TGA_H 1 #include "gd.h" #include "gdhelpers.h" @@ -7,46 +7,50 @@ #include "gd_intern.h" typedef struct oTga_ { - uint8_t identsize; // size of ID field that follows 18 uint8_t header (0 usually) - uint8_t colormaptype; // type of colour map 0=none, 1=has palette [IGNORED] Adrian requested no support - uint8_t imagetype; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed - - int colormapstart; // first colour map entry in palette [IGNORED] Adrian requested no support - int colormaplength; // number of colours in palette [IGNORED] Adrian requested no support - uint8_t colormapbits; // number of bits per palette entry 15,16,24,32 [IGNORED] Adrian requested no support - - int xstart; // image x origin - int ystart; // image y origin - int width; // image width in pixels - int height; // image height in pixels - uint8_t bits; // image bits per pixel 8,16,24,32 - uint8_t alphabits; // alpha bits (low 4bits of header 17) - uint8_t fliph; // horizontal or vertical - uint8_t flipv; // flip - char *ident; // identifcation tag string - int *bitmap; // bitmap data + uint8_t identsize; // size of ID field that follows 18 uint8_t header (0 + // usually) + uint8_t colormaptype; // type of colour map 0=none, 1=has palette + uint8_t + imagetype; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed + + int colormapstart; // first colour map entry in palette + int colormaplength; // number of colours in palette + uint8_t colormapbits; // number of bits per palette entry 15,16,24,32 + + int xstart; // image x origin + int ystart; // image y origin + int width; // image width in pixels + int height; // image height in pixels + uint8_t bits; // image bits per pixel 8,16,24,32 + uint8_t alphabits; // alpha bits (low 4bits of header 17) + uint8_t fliph; // horizontal or vertical + uint8_t flipv; // flip + uint8_t has_alpha; // decoded image contains alpha + char *ident; // identifcation tag string + int *bitmap; // bitmap data + int *colormap; // decoded color map } oTga; -#define TGA_TYPE_NO_IMAGE 0 -#define TGA_TYPE_INDEXED 1 -#define TGA_TYPE_RGB 2 -#define TGA_TYPE_GREYSCALE 3 -#define TGA_TYPE_INDEXED_RLE 9 -#define TGA_TYPE_RGB_RLE 10 -#define TGA_TYPE_GREYSCALE_RLE 11 -#define TGA_TYPE_INDEXED_HUFFMAN_DELTA_RLE 32 -#define TGA_TYPE_RGB_HUFFMAN_DELTA_QUADTREE_RLE 33 - -#define TGA_BPP_8 8 -#define TGA_BPP_16 16 -#define TGA_BPP_24 24 -#define TGA_BPP_32 32 - -#define TGA_RLE_FLAG 128 - -int read_header_tga(gdIOCtx *ctx, oTga *tga); -int read_image_tga(gdIOCtx *ctx, oTga *tga); +#define TGA_TYPE_NO_IMAGE 0 +#define TGA_TYPE_INDEXED 1 +#define TGA_TYPE_RGB 2 +#define TGA_TYPE_GREYSCALE 3 +#define TGA_TYPE_INDEXED_RLE 9 +#define TGA_TYPE_RGB_RLE 10 +#define TGA_TYPE_GREYSCALE_RLE 11 +#define TGA_TYPE_INDEXED_HUFFMAN_DELTA_RLE 32 +#define TGA_TYPE_RGB_HUFFMAN_DELTA_QUADTREE_RLE 33 + +#define TGA_BPP_8 8 +#define TGA_BPP_16 16 +#define TGA_BPP_24 24 +#define TGA_BPP_32 32 + +#define TGA_RLE_FLAG 128 + +int read_header_tga(gdIOCtxPtr ctx, oTga *tga); +int read_image_tga(gdIOCtxPtr ctx, oTga *tga); void free_tga(oTga *tga); #endif //__TGA_H diff --git a/ext/gd/libgd/gd_tiff.c b/ext/gd/libgd/gd_tiff.c new file mode 100644 index 000000000000..f1c0901efbb1 --- /dev/null +++ b/ext/gd/libgd/gd_tiff.c @@ -0,0 +1,2397 @@ +/* + TIFF - Tagged Image File Format Encapsulation for GD Library + + gd_tiff.c + Copyright (C) Pierre-A. Joye, M. Retallack + + --------------------------------------------------------------------------- + ** + ** Permission to use, copy, modify, and distribute this software and its + ** documentation for any purpose and without fee is hereby granted, provided + ** that the above copyright notice appear in all copies and that both that + ** copyright notice and this permission notice appear in supporting + ** documentation. This software is provided "as is" without express or + ** implied warranty. + ** + --------------------------------------------------------------------------- + Ctx code written by M. Retallack + + Todo: + + If we fail - cleanup + Writer: Use gd error function, overflow check may not be necessary as + we write our own data (check already done) + + Implement 2 color black/white saving using group4 fax compression + Implement function to specify encoding to use when writing tiff data + + ---------------------------------------------------------------------------- + */ + +/** + * File: TIFF IO + * + * Read and write TIFF images. + * + * Multi-page TIFF reading is supported via the gdTiffRead* API. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd.h" +#include "gd_errors.h" +#include "gd_intern.h" +#include "gdfonts.h" +#include +#include +#include +#include + +#include "gdhelpers.h" + +#ifdef HAVE_LIBTIFF + +#include "tiff.h" +#include "tiffio.h" + +#define GD_SUCCESS 1 +#define GD_FAILURE 0 + +#define TRUE 1 +#define FALSE 0 + +/* I define those here until the new formats + * are commited. We can then rely on the global + * def + */ +#define GD_PALETTE 1 +#define GD_TRUECOLOR 2 +#define GD_GRAY 3 +#define GD_INDEXED 4 +#define GD_RGB 5 + +typedef struct tiff_handle { + int size; + int pos; + gdIOCtx *ctx; + int written; +} tiff_handle; + +/* + Functions for reading, writing and seeking in gdIOCtx + This allows for non-file i/o operations with no + explicit use of libtiff fileio wrapper functions + + Note: because libtiff requires random access, but gdIOCtx + only supports streams, all writes are buffered + into memory and written out on close, also all + reads are done from a memory mapped version of the + tiff (assuming one already exists) +*/ + +static tiff_handle *new_tiff_handle(gdIOCtx *g, int initial_size) { + tiff_handle *t; + + if (!g) { + gd_error("Cannot create a new tiff handle, missing Ctx argument"); + return NULL; + } + + t = (tiff_handle *)gdMalloc(sizeof(tiff_handle)); + if (!t) { + gd_error("Failed to allocate a new tiff handle"); + return NULL; + } + + t->size = initial_size; + t->pos = 0; + t->ctx = g; + t->written = 0; + + return t; +} + +/* TIFFReadWriteProc tiff_readproc - Will use gdIOCtx procs to read required + (previously written) TIFF file content */ +static tsize_t tiff_readproc(thandle_t clientdata, tdata_t data, tsize_t size) { + tiff_handle *th = (tiff_handle *)clientdata; + gdIOCtx *ctx = th->ctx; + + size = (ctx->getBuf)(ctx, data, size); + if (size > 0) { + th->pos += size; + } + + return size; +} + +/* TIFFReadWriteProc tiff_writeproc - Will use gdIOCtx procs to write out + TIFF data */ +static tsize_t tiff_writeproc(thandle_t clientdata, tdata_t data, + tsize_t size) { + tiff_handle *th = (tiff_handle *)clientdata; + gdIOCtx *ctx = th->ctx; + + size = (ctx->putBuf)(ctx, data, size); + if (size > 0) { + th->pos += size; + } + if (th->pos > th->size) { + th->size = th->pos; + } + + return size; +} + +/* TIFFSeekProc tiff_seekproc + * used to move around the partially written TIFF */ +static toff_t tiff_seekproc(thandle_t clientdata, toff_t offset, int from) { + tiff_handle *th = (tiff_handle *)clientdata; + gdIOCtx *ctx = th->ctx; + int result; + + switch (from) { + default: + case SEEK_SET: + /* just use offset */ + break; + + case SEEK_END: + /* invert offset, so that it is from start, not end as supplied */ + offset = th->size + offset; + break; + + case SEEK_CUR: + /* add current position to translate it to 'from start', + * not from durrent as supplied + */ + offset += th->pos; + break; + } + + /* now, move pos in both io context and buf */ + if ((result = (ctx->seek)(ctx, offset))) { + th->pos = offset; + } + + return result ? offset : (toff_t)-1; +} + +/* TIFFCloseProc tiff_closeproc - used to finally close the TIFF file */ +static int tiff_closeproc(thandle_t clientdata) { + (void)clientdata; + /*tiff_handle *th = (tiff_handle *)clientdata; + gdIOCtx *ctx = th->ctx; + + (ctx->gd_free)(ctx);*/ + + return 0; +} + +/* TIFFSizeProc tiff_sizeproc */ +static toff_t tiff_sizeproc(thandle_t clientdata) { + tiff_handle *th = (tiff_handle *)clientdata; + return th->size; +} + +/* TIFFMapFileProc tiff_mapproc() */ +static int tiff_mapproc(thandle_t h, tdata_t *d, toff_t *o) { + (void)h; + (void)d; + (void)o; + return 0; +} + +/* TIFFUnmapFileProc tiff_unmapproc */ +static void tiff_unmapproc(thandle_t h, tdata_t d, toff_t o) { + (void)h; + (void)d; + (void)o; +} + +static int tiff_file_size(FILE *fp) { + long current_pos; + long end_pos; + + if (!fp) { + return 0; + } + + current_pos = ftell(fp); + if (current_pos < 0) { + return 0; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + (void)fseek(fp, current_pos, SEEK_SET); + return 0; + } + + end_pos = ftell(fp); + (void)fseek(fp, current_pos, SEEK_SET); + + if (end_pos < 0) { + return 0; + } + + if (end_pos > INT_MAX) { + return INT_MAX; + } + + return (int)end_pos; +} + +static int tiff_ctx_size(gdIOCtx *ctx) { + unsigned char buffer[4096]; + long current_pos; + long end_pos; + + if (ctx == NULL || ctx->tell == NULL || ctx->seek == NULL) + return 0; + + current_pos = ctx->tell(ctx); + if (current_pos < 0) + return 0; + + while (ctx->getBuf(ctx, buffer, sizeof(buffer)) > 0) + ; + end_pos = ctx->tell(ctx); + (void)ctx->seek(ctx, (int)current_pos); + + if (end_pos < 0) + return 0; + if (end_pos > INT_MAX) + return INT_MAX; + + return (int)end_pos; +} + +/* tiffWriter + * ---------- + * Write the gd image as a tiff file (called by gdImageTiffCtx) + * Parameters are: + * image: gd image structure; + * out: the stream where to write + * bitDepth: depth in bits of each pixel + */ +static void tiffWriter(gdImagePtr image, gdIOCtx *out, int bitDepth) { + int x, y; + int i; + int r, g, b, a; + TIFF *tiff; + int width, height; + int color; + char *scan; + int samplesPerPixel = 3; + int bitsPerSample; + int transparentColorR = -1; + int transparentColorG = -1; + int transparentColorB = -1; + uint16_t extraSamples[1]; + uint16_t *colorMapRed = NULL; + uint16_t *colorMapGreen = NULL; + uint16_t *colorMapBlue = NULL; + size_t colorMapSize; + + tiff_handle *th; + + th = new_tiff_handle(out, 0); + if (!th) { + return; + } + extraSamples[0] = EXTRASAMPLE_ASSOCALPHA; + + /* read in the width/height of gd image */ + width = gdImageSX(image); + height = gdImageSY(image); + + /* reset clip region to whole image */ + gdImageSetClip(image, 0, 0, width, height); + + /* handle old-style single-colour mapping to 100% transparency */ + if (image->transparent != -1) { + /* set our 100% transparent colour value */ + transparentColorR = gdImageRed(image, image->transparent); + transparentColorG = gdImageGreen(image, image->transparent); + transparentColorB = gdImageBlue(image, image->transparent); + } + + /* Open tiff file writing routines, but use special read/write/seek + * functions so that tiff lib writes correct bits of tiff content to + * correct areas of file opened and modifieable by the gdIOCtx functions + */ + tiff = TIFFClientOpen("", "w", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + + TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); + TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, + (bitDepth == 24) ? PHOTOMETRIC_RGB : PHOTOMETRIC_PALETTE); + + bitsPerSample = (bitDepth == 24 || bitDepth == 8) ? 8 : 1; + TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bitsPerSample); + + TIFFSetField(tiff, TIFFTAG_XRESOLUTION, (float)image->res_x); + TIFFSetField(tiff, TIFFTAG_YRESOLUTION, (float)image->res_y); + + /* build the color map for 8 bit images */ + if (bitDepth != 24) { + if (overflow2(1 << bitsPerSample, sizeof(uint16_t))) { + gdFree(th); + return; + } + colorMapSize = (size_t)(1 << bitsPerSample) * sizeof(uint16_t); + colorMapRed = (uint16_t *)gdMalloc(colorMapSize); + if (!colorMapRed) { + gdFree(th); + return; + } + colorMapGreen = (uint16_t *)gdMalloc(colorMapSize); + if (!colorMapGreen) { + gdFree(colorMapRed); + gdFree(th); + return; + } + colorMapBlue = (uint16_t *)gdMalloc(colorMapSize); + if (!colorMapBlue) { + gdFree(colorMapRed); + gdFree(colorMapGreen); + gdFree(th); + return; + } + + for (i = 0; i < image->colorsTotal; i++) { + colorMapRed[i] = + gdImageRed(image, i) + (gdImageRed(image, i) * 256); + colorMapGreen[i] = + gdImageGreen(image, i) + (gdImageGreen(image, i) * 256); + colorMapBlue[i] = + gdImageBlue(image, i) + (gdImageBlue(image, i) * 256); + } + + TIFFSetField(tiff, TIFFTAG_COLORMAP, colorMapRed, colorMapGreen, + colorMapBlue); + samplesPerPixel = 1; + } + + /* here, we check if the 'save alpha' flag is set on the source gd image */ + if ((bitDepth == 24) && + (image->saveAlphaFlag || image->transparent != -1)) { + /* so, we need to store the alpha values too! + * Also, tell TIFF what the extra sample means (associated alpha) */ + samplesPerPixel = 4; + TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel); + TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, extraSamples); + } else { + TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, samplesPerPixel); + } + + TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1); + + if (overflow2(width, samplesPerPixel)) { + if (colorMapRed) + gdFree(colorMapRed); + if (colorMapGreen) + gdFree(colorMapGreen); + if (colorMapBlue) + gdFree(colorMapBlue); + gdFree(th); + return; + } + + if (!(scan = (char *)gdMalloc(width * samplesPerPixel))) { + if (colorMapRed) + gdFree(colorMapRed); + if (colorMapGreen) + gdFree(colorMapGreen); + if (colorMapBlue) + gdFree(colorMapBlue); + gdFree(th); + return; + } + + /* loop through y-coords, and x-coords */ + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + /* generate scan line for writing to tiff */ + color = gdImageGetPixel(image, x, y); + + a = (127 - gdImageAlpha(image, color)) * 2; + a = (a == 0xfe) ? 0xff : a & 0xff; + b = gdImageBlue(image, color); + g = gdImageGreen(image, color); + r = gdImageRed(image, color); + + /* if this pixel has the same RGB as the transparent colour, + * then set alpha fully transparent */ + if (transparentColorR == r && transparentColorG == g && + transparentColorB == b) { + a = 0x00; + } + + if (bitDepth != 24) { + /* write out 1 or 8 bit value in 1 byte + * (currently treats 1bit as 8bit) */ + scan[(x * samplesPerPixel) + 0] = color; + } else { + /* write out 24 bit value in 3 (or 4 if transparent) bytes */ + if (image->saveAlphaFlag || image->transparent != -1) { + scan[(x * samplesPerPixel) + 3] = a; + } + + scan[(x * samplesPerPixel) + 2] = b; + scan[(x * samplesPerPixel) + 1] = g; + scan[(x * samplesPerPixel) + 0] = r; + } + } + + /* Write the scan line to the tiff */ + if (TIFFWriteEncodedStrip(tiff, y, scan, width * samplesPerPixel) == + -1) { + if (colorMapRed) + gdFree(colorMapRed); + if (colorMapGreen) + gdFree(colorMapGreen); + if (colorMapBlue) + gdFree(colorMapBlue); + gdFree(th); + /* error handler here */ + gd_error("Could not create TIFF\n"); + return; + } + } + + /* now cloase and free up resources */ + TIFFClose(tiff); + gdFree(scan); + gdFree(th); + + if (bitDepth != 24) { + gdFree(colorMapRed); + gdFree(colorMapGreen); + gdFree(colorMapBlue); + } +} + +/* + Function: gdImageTiffCtx + + Write the gd image as a tiff file. + + Parameters: + + image - gd image structure; + out - the stream where to write +*/ +BGD_DECLARE(void) gdImageTiffCtx(gdImagePtr image, gdIOCtx *out) { + int clipx1P, clipy1P, clipx2P, clipy2P; + int bitDepth = 24; + + /* First, switch off clipping, or we'll not get all the image! */ + gdImageGetClip(image, &clipx1P, &clipy1P, &clipx2P, &clipy2P); + + /* use the appropriate routine depending on the bit depth of the image */ + if (image->trueColor) { + bitDepth = 24; + } else if (image->colorsTotal == 2) { + bitDepth = 1; + } else { + bitDepth = 8; + } + + tiffWriter(image, out, bitDepth); + + /* reset clipping area to the gd image's original values */ + gdImageSetClip(image, clipx1P, clipy1P, clipx2P, clipy2P); +} + +/* Check if we are really in 8bit mode */ +static int checkColorMap(int n, uint16_t *r, uint16_t *g, uint16_t *b) { + while (n-- > 0) + if (*r++ >= 256 || *g++ >= 256 || *b++ >= 256) + return (16); + return (8); +} + +/* Read and convert a TIFF colormap */ +static int readTiffColorMap(gdImagePtr im, TIFF *tif, char is_bw, + int photometric) { + uint16_t *redcmap, *greencmap, *bluecmap; + uint16_t bps; + int i; + + if (is_bw) { + if (photometric == PHOTOMETRIC_MINISWHITE) { + gdImageColorAllocate(im, 255, 255, 255); + gdImageColorAllocate(im, 0, 0, 0); + } else { + gdImageColorAllocate(im, 0, 0, 0); + gdImageColorAllocate(im, 255, 255, 255); + } + } else { + uint16_t min_sample_val, max_sample_val; + + if (!TIFFGetField(tif, TIFFTAG_MINSAMPLEVALUE, &min_sample_val)) { + min_sample_val = 0; + } + if (!TIFFGetField(tif, TIFFTAG_MAXSAMPLEVALUE, &max_sample_val)) { + max_sample_val = 255; + } + + if (photometric == PHOTOMETRIC_MINISBLACK || + photometric == PHOTOMETRIC_MINISWHITE) { + /* TODO: use TIFFTAG_MINSAMPLEVALUE and TIFFTAG_MAXSAMPLEVALUE */ + /* Gray level palette */ + for (i = min_sample_val; i <= max_sample_val; i++) { + gdImageColorAllocate(im, i, i, i); + } + return GD_SUCCESS; + + } else if (!TIFFGetField(tif, TIFFTAG_COLORMAP, &redcmap, &greencmap, + &bluecmap)) { + gd_error("Cannot read the color map"); + return GD_FAILURE; + } + + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + +#define CVT(x) (((x) * 255) / ((1L << 16) - 1)) + if (checkColorMap(1 << bps, redcmap, greencmap, bluecmap) == 16) { + for (i = (1 << bps) - 1; i > 0; i--) { + redcmap[i] = CVT(redcmap[i]); + greencmap[i] = CVT(greencmap[i]); + bluecmap[i] = CVT(bluecmap[i]); + } + } + for (i = 0; i < 256; i++) { + gdImageColorAllocate(im, redcmap[i], greencmap[i], bluecmap[i]); + } +#undef CVT + } + return GD_SUCCESS; +} + +static void readTiffBw(const unsigned char *src, gdImagePtr im, + uint16_t photometric, int startx, int starty, int width, + int height, char has_alpha, int extra, int align) { + int x = startx, y = starty; + + (void)has_alpha; + (void)extra; + (void)align; + + for (y = starty; y < starty + height; y++) { + for (x = startx; x < startx + width;) { + register unsigned char curr = *src++; + register unsigned char mask; + + if (photometric == PHOTOMETRIC_MINISWHITE) { + curr = ~curr; + } + for (mask = 0x80; mask != 0 && x < startx + width; + x++, mask >>= 1) { + gdImageSetPixel(im, x, y, ((curr & mask) != 0) ? 0 : 1); + } + } + } +} + +static void readTiff8bit(const unsigned char *src, gdImagePtr im, + uint16_t photometric, int startx, int starty, + int width, int height, char has_alpha, int extra, + int align) { + int red, green, blue, alpha; + int x, y; + + (void)extra; + (void)align; + + switch (photometric) { + case PHOTOMETRIC_PALETTE: + /* Palette has no alpha (see TIFF specs for more details */ + for (y = starty; y < starty + height; y++) { + for (x = startx; x < startx + width; x++) { + gdImageSetPixel(im, x, y, *(src++)); + } + } + break; + + case PHOTOMETRIC_RGB: + if (has_alpha) { + gdImageAlphaBlending(im, 0); + gdImageSaveAlpha(im, 1); + + for (y = starty; y < starty + height; y++) { + for (x = startx; x < startx + width; x++) { + red = *src++; + green = *src++; + blue = *src++; + alpha = *src++; + red = MIN(red, alpha); + blue = MIN(blue, alpha); + green = MIN(green, alpha); + + if (alpha) { + gdImageSetPixel( + im, x, y, + gdTrueColorAlpha( + red * 255 / alpha, green * 255 / alpha, + blue * 255 / alpha, gdAlphaMax - (alpha >> 1))); + } else { + gdImageSetPixel( + im, x, y, + gdTrueColorAlpha(red, green, blue, + gdAlphaMax - (alpha >> 1))); + } + } + } + + } else { + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + register unsigned char r = *src++; + register unsigned char g = *src++; + register unsigned char b = *src++; + + gdImageSetPixel(im, x, y, gdTrueColor(r, g, b)); + } + } + } + break; + + case PHOTOMETRIC_MINISWHITE: + if (has_alpha) { + /* We don't process the extra yet */ + } else { + for (y = starty; y < starty + height; y++) { + for (x = startx; x < startx + width; x++) { + gdImageSetPixel(im, x, y, ~(*src++)); + } + } + } + break; + + case PHOTOMETRIC_MINISBLACK: + if (has_alpha) { + /* We don't process the extra yet */ + } else { + for (y = starty; y < height; y++) { + for (x = 0; x < width; x++) { + gdImageSetPixel(im, x, y, *src++); + } + } + } + break; + } +} + +static int createFromTiffTiles(TIFF *tif, gdImagePtr im, uint16_t bps, + uint16_t photometric, char has_alpha, char is_bw, + int extra) { + uint16_t planar; + int im_width, im_height; + int tile_width, tile_height; + int x, y, height, width; + unsigned char *buffer; + tmsize_t tile_size; + int success = GD_SUCCESS; + + if (!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar)) { + planar = PLANARCONFIG_CONTIG; + } + if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &im_width) == 0 || + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &im_height) == 0 || + TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width) == 0 || + TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height) == 0) { + return FALSE; + } + if (tile_width <= 0 || tile_height <= 0) { + return FALSE; + } + + tile_size = TIFFTileSize(tif); + if (tile_size <= 0) { + return FALSE; + } + buffer = (unsigned char *)gdMalloc((size_t)tile_size); + if (!buffer) { + return FALSE; + } + + for (y = 0; y < im_height; y += tile_height) { + for (x = 0; x < im_width; x += tile_width) { + if (TIFFReadTile(tif, buffer, x, y, 0, 0) < 0) { + success = GD_FAILURE; + goto end; + } + width = MIN(im_width - x, tile_width); + height = MIN(im_height - y, tile_height); + if (bps == 8) { + readTiff8bit(buffer, im, photometric, x, y, width, height, + has_alpha, extra, 0); + } else if (is_bw) { + readTiffBw(buffer, im, photometric, x, y, width, height, + has_alpha, extra, 0); + } else { + gd_error("TIFF error, unsupported tiled image format in direct " + "reader"); + success = GD_FAILURE; + goto end; + } + } + } +end: + gdFree(buffer); + return success; +} + +static int createFromTiffLines(TIFF *tif, gdImagePtr im, uint16_t bps, + uint16_t photometric, char has_alpha, char is_bw, + int extra) { + uint16_t planar; + uint32_t im_height, im_width, y; + + unsigned char *buffer; + int success = GD_SUCCESS; + + if (!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar)) { + planar = PLANARCONFIG_CONTIG; + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &im_height)) { + gd_error("Can't fetch TIFF height\n"); + return FALSE; + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &im_width)) { + gd_error("Can't fetch TIFF width \n"); + return FALSE; + } + + if (im_width > INT_MAX || overflow2((int)im_width, 4)) { + return GD_FAILURE; + } + buffer = (unsigned char *)gdMalloc((size_t)im_width * 4); + if (!buffer) { + return GD_FAILURE; + } + if (planar == PLANARCONFIG_CONTIG) { + switch (bps) { + case 8: + for (y = 0; y < im_height; y++) { + if (TIFFReadScanline(tif, buffer, y, 0) < 0) { + gd_error("Error while reading scanline %i", y); + success = GD_FAILURE; + break; + } + /* reading one line at a time */ + readTiff8bit(buffer, im, photometric, 0, y, im_width, 1, + has_alpha, extra, 0); + } + break; + + default: + if (is_bw) { + for (y = 0; y < im_height; y++) { + if (TIFFReadScanline(tif, buffer, y, 0) < 0) { + gd_error("Error while reading scanline %i", y); + success = GD_FAILURE; + break; + } + /* reading one line at a time */ + readTiffBw(buffer, im, photometric, 0, y, im_width, 1, + has_alpha, extra, 0); + } + } else { + gd_error("TIFF error, unsupported scanline image format in " + "direct reader"); + success = GD_FAILURE; + } + break; + } + } else { + gd_error( + "TIFF error, unsupported separate planar image in direct reader"); + success = GD_FAILURE; + } + + gdFree(buffer); + return success; +} + +static int createFromTiffRgba(TIFF *tif, gdImagePtr im) { + int a; + int x, y; + int alphaBlendingFlag = 0; + int color; + int width = im->sx; + int height = im->sy; + uint32_t *buffer; + uint32_t rgba; + int success; + + buffer = (uint32_t *)gdCalloc(sizeof(uint32_t), width * height); + if (!buffer) { + return GD_FAILURE; + } + + /* switch off colour merging on target gd image just while we write out + * content - we want to preserve the alpha data until the user chooses + * what to do with the image */ + alphaBlendingFlag = im->alphaBlendingFlag; + gdImageAlphaBlending(im, 0); + + success = TIFFReadRGBAImage(tif, width, height, buffer, 1); + + if (success) { + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + /* if it doesn't already exist, allocate a new colour, + * else use existing one */ + rgba = buffer[(y * width + x)]; + a = (0xff - TIFFGetA(rgba)) / 2; + color = gdTrueColorAlpha(TIFFGetR(rgba), TIFFGetG(rgba), + TIFFGetB(rgba), a); + + /* set pixel colour to this colour */ + gdImageSetPixel(im, x, height - y - 1, color); + } + } + } + + gdFree(buffer); + + /* now reset colour merge for alpha blending routines */ + gdImageAlphaBlending(im, alphaBlendingFlag); + return success; +} + +/* + Function: gdImageCreateFromTiffCtx + + Create a gdImage from a TIFF file input from an gdIOCtx. +*/ +static gdImagePtr TiffDecodeCurrentDirectory(TIFF *tif) { + uint16_t bps, spp, photometric; + uint16_t orientation; + int width, height; + uint16_t extra, *extra_types; + uint16_t planar; + char has_alpha, is_bw, is_gray; + char force_rgba = FALSE; + char save_transparent; + int image_type; + int ret; + float res_float; + + gdImagePtr im = NULL; + + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width)) { + gd_error("TIFF error, Cannot read image width"); + return NULL; + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) { + gd_error("TIFF error, Cannot read image height"); + return NULL; + } + + if (width <= 0 || height <= 0) { + gd_error("TIFF error, image dimensions must be greater than 0"); + return NULL; + } + + if (overflow2(width, height)) { + gd_error("TIFF error, image dimensions are too large"); + return NULL; + } + + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + + if (bps == 0 || bps > 32) { + gd_error("TIFF error, invalid bits per sample: %u", (unsigned)bps); + return NULL; + } + + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + + if (spp == 0 || spp > 4) { + gd_error("TIFF error, invalid samples per pixel: %u", (unsigned)spp); + return NULL; + } + + if (!TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types)) { + extra = 0; + } + + if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)) { + uint16_t compression; + if (TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression) && + (compression == COMPRESSION_CCITTFAX3 || + compression == COMPRESSION_CCITTFAX4 || + compression == COMPRESSION_CCITTRLE || + compression == COMPRESSION_CCITTRLEW)) { + gd_error("Could not get photometric. " + "Image is CCITT compressed, assuming min-is-white"); + photometric = PHOTOMETRIC_MINISWHITE; + } else { + gd_error("Could not get photometric. " + "Assuming min-is-black"); + + photometric = PHOTOMETRIC_MINISBLACK; + } + } + save_transparent = FALSE; + + /* test if the extrasample represents an associated alpha channel... */ + if (extra > 0 && (extra_types[0] == EXTRASAMPLE_ASSOCALPHA)) { + has_alpha = TRUE; + save_transparent = FALSE; + --extra; + } else if (extra > 0 && (extra_types[0] == EXTRASAMPLE_UNASSALPHA)) { + has_alpha = TRUE; + save_transparent = TRUE; + --extra; + } else if (extra > 0 && (extra_types[0] == EXTRASAMPLE_UNSPECIFIED)) { + /* assuming unassociated alpha if unspecified */ + gd_error("alpha channel type not defined, assuming alpha is not " + "premultiplied"); + has_alpha = TRUE; + save_transparent = TRUE; + --extra; + } else { + has_alpha = FALSE; + } + + if (photometric == PHOTOMETRIC_RGB && spp > 3 + extra) { + has_alpha = TRUE; + extra = spp - 4; + } else if (photometric != PHOTOMETRIC_RGB && spp > 1 + extra) { + has_alpha = TRUE; + extra = spp - 2; + } + + is_bw = FALSE; + is_gray = FALSE; + + switch (photometric) { + case PHOTOMETRIC_MINISBLACK: + case PHOTOMETRIC_MINISWHITE: + if (!has_alpha && bps == 1 && spp == 1) { + image_type = GD_INDEXED; + is_bw = TRUE; + } else { + image_type = GD_GRAY; + } + break; + + case PHOTOMETRIC_RGB: + image_type = GD_RGB; + break; + + case PHOTOMETRIC_PALETTE: + image_type = GD_INDEXED; + break; + + default: + force_rgba = TRUE; + break; + } + + if (!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar)) { + planar = PLANARCONFIG_CONTIG; + } + + /* The direct scanline/tile readers only implement contiguous 1-bit BW + * images. */ + if (!is_bw || bps != 1 || spp != 1 || has_alpha || + planar != PLANARCONFIG_CONTIG) { + force_rgba = TRUE; + } + + /* Force rgba if image planes are not contiguous or the format is otherwise + * unsupported. */ + if (force_rgba) { + image_type = GD_RGB; + } + + if (!force_rgba && (image_type == GD_PALETTE || image_type == GD_INDEXED || + image_type == GD_GRAY)) { + im = gdImageCreate(width, height); + if (!im) + return NULL; + readTiffColorMap(im, tif, is_bw, photometric); + } else { + im = gdImageCreateTrueColor(width, height); + if (!im) + return NULL; + } + +#ifdef DEBUG + printf("force rgba: %i\n", force_rgba); + printf("has_alpha: %i\n", has_alpha); + printf("save trans: %i\n", save_transparent); + printf("is_bw: %i\n", is_bw); + printf("is_gray: %i\n", is_gray); + printf("type: %i\n", image_type); +#else + (void)is_gray; + (void)save_transparent; +#endif + + if (force_rgba) { + ret = createFromTiffRgba(tif, im); + } else if (TIFFIsTiled(tif)) { + ret = createFromTiffTiles(tif, im, bps, photometric, has_alpha, is_bw, + extra); + } else { + ret = createFromTiffLines(tif, im, bps, photometric, has_alpha, is_bw, + extra); + } + + if (!ret) { + gdImageDestroy(im); + return NULL; + } + + if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &res_float)) { + im->res_x = (unsigned int)res_float; // truncate + } + if (TIFFGetField(tif, TIFFTAG_YRESOLUTION, &res_float)) { + im->res_y = (unsigned int)res_float; // truncate + } + + if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &orientation)) { + switch (orientation) { + case ORIENTATION_TOPLEFT: + case ORIENTATION_TOPRIGHT: + case ORIENTATION_BOTRIGHT: + case ORIENTATION_BOTLEFT: + break; + + default: + gd_error("Orientation %d not handled yet!", orientation); + break; + } + } + + return im; +} + +static gdImagePtr gdImageCreateFromTiffCtxEx(gdIOCtx *infile, + int initial_size) { + TIFF *tif; + tiff_handle *th; + gdImagePtr im = NULL; + + th = new_tiff_handle(infile, initial_size); + if (!th) { + return NULL; + } + + tif = TIFFClientOpen("", "rb", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + + if (!tif) { + gd_error("Cannot open TIFF image"); + gdFree(th); + return NULL; + } + + im = TiffDecodeCurrentDirectory(tif); + + TIFFClose(tif); + gdFree(th); + return im; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffCtx(gdIOCtx *infile) { + return gdImageCreateFromTiffCtxEx(infile, tiff_ctx_size(infile)); +} + +/* + Function: gdImageCreateFromTIFF +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiff(FILE *inFile) { + gdImagePtr im; + gdIOCtx *in = gdNewFileCtx(inFile); + int initial_size = tiff_file_size(inFile); + + if (in == NULL) + return NULL; + im = gdImageCreateFromTiffCtxEx(in, initial_size); + in->gd_free(in); + return im; +} + +/* + Function: gdImageCreateFromTiffPtr +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffPtr(int size, void *data) { + gdImagePtr im; + gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) + return NULL; + im = gdImageCreateFromTiffCtxEx(in, size); + in->gd_free(in); + return im; +} + +/* + Function: gdImageTiff +*/ +BGD_DECLARE(void) gdImageTiff(gdImagePtr im, FILE *outFile) { + gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; + gdImageTiffCtx(im, out); /* what's an fg again? */ + out->gd_free(out); +} + +/* + Function: gdImageTiffPtr +*/ +BGD_DECLARE(void *) gdImageTiffPtr(gdImagePtr im, int *size) { + void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + gdImageTiffCtx(im, out); /* what's an fg again? */ + rv = gdDPExtractData(out, size); + out->gd_free(out); + return rv; +} + +#define GD_TIFF_ALLOC_STEP (4 * 1024) + +typedef struct gdTiffReadStruct { + uint8_t *data; + size_t size; + TIFF *tif; + tiff_handle *th; + gdIOCtx *memCtx; + int pageCount; + int currentPage; + gdImagePtr image; +} gdTiffRead; + +static uint8_t *TiffReadCtxData(gdIOCtx *infile, size_t *size) { + uint8_t *filedata = NULL, *temp, *read; + ssize_t n; + + *size = 0; + do { + temp = gdRealloc(filedata, *size + GD_TIFF_ALLOC_STEP); + if (temp == NULL) { + gdFree(filedata); + gd_error("TIFF decode: realloc failed"); + return NULL; + } + filedata = temp; + read = temp + *size; + n = gdGetBuf(read, GD_TIFF_ALLOC_STEP, infile); + if (n > 0 && n != EOF) { + *size += n; + } + } while (n > 0 && n != EOF); + + if (*size == 0) { + gdFree(filedata); + return NULL; + } + + return filedata; +} + +static void TiffFillInfo(TIFF *tif, gdTiffInfo *info, int pageCount) { + uint16_t bps, spp, photometric, compression; + float res_float; + uint16_t resUnit; + + if (info == NULL || tif == NULL) { + return; + } + memset(info, 0, sizeof(*info)); + info->pageCount = pageCount; + + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, (uint32_t *)&info->width); + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, (uint32_t *)&info->height); + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + info->bitsPerSample = bps; + info->samplesPerPixel = spp; + + if (TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression)) { + info->compression = compression; + } + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)) { + info->photometric = photometric; + } + if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &res_float)) { + info->xResolution = res_float; + } + if (TIFFGetField(tif, TIFFTAG_YRESOLUTION, &res_float)) { + info->yResolution = res_float; + } + if (TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &resUnit)) { + info->resolutionUnit = resUnit; + } else { + info->resolutionUnit = 2; + } +} + +static void TiffFillPageInfo(TIFF *tif, gdTiffPageInfo *info, int pageIndex) { + uint16_t bps, spp, photometric, compression, planar; + uint16_t extra, *extra_types; + uint16_t resUnit; + float res_float; + + if (info == NULL || tif == NULL) { + return; + } + memset(info, 0, sizeof(*info)); + info->pageIndex = pageIndex; + + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, (uint32_t *)&info->width); + TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, (uint32_t *)&info->height); + TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + info->bitsPerSample = bps; + info->samplesPerPixel = spp; + info->isTiled = TIFFIsTiled(tif); + + if (TIFFGetField(tif, TIFFTAG_COMPRESSION, &compression)) { + info->compression = compression; + } + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)) { + info->photometric = photometric; + } + if (TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planar)) { + info->planar = planar; + } else { + info->planar = PLANARCONFIG_CONTIG; + } + + if (!TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types)) { + extra = 0; + } + info->hasAlpha = (extra > 0); + + if (TIFFGetField(tif, TIFFTAG_XRESOLUTION, &res_float)) { + info->xResolution = res_float; + } + if (TIFFGetField(tif, TIFFTAG_YRESOLUTION, &res_float)) { + info->yResolution = res_float; + } + if (TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &resUnit)) { + info->resolutionUnit = resUnit; + } else { + info->resolutionUnit = 2; + } +} + +static gdTiffReadPtr TiffReadOpenFromData(uint8_t *data, size_t size) { + gdTiffReadPtr tiff; + gdIOCtx *memCtx; + tiff_handle *th; + TIFF *tif; + + tiff = (gdTiffReadPtr)gdCalloc(1, sizeof(gdTiffRead)); + if (tiff == NULL) { + return NULL; + } + + tiff->data = data; + tiff->size = size; + + memCtx = gdNewDynamicCtxEx((int)size, data, 0); + if (memCtx == NULL) { + gdFree(tiff); + return NULL; + } + tiff->memCtx = memCtx; + + th = new_tiff_handle(memCtx, (int)size); + if (th == NULL) { + memCtx->gd_free(memCtx); + gdFree(tiff); + return NULL; + } + tiff->th = th; + + tif = TIFFClientOpen("", "rb", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + if (tif == NULL) { + gdFree(th); + memCtx->gd_free(memCtx); + gdFree(tiff); + return NULL; + } + tiff->tif = tif; + + tiff->pageCount = (int)TIFFNumberOfDirectories(tif); + if (tiff->pageCount <= 0) { + gd_error("TIFF error, invalid page count: %d", tiff->pageCount); + TIFFClose(tif); + tiff->tif = NULL; + gdFree(th); + tiff->th = NULL; + memCtx->gd_free(memCtx); + tiff->memCtx = NULL; + gdFree(tiff); + return NULL; + } + tiff->currentPage = -1; + tiff->image = NULL; + + return tiff; +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpen(FILE *fd) { + gdIOCtx *in; + gdTiffReadPtr tiff; + uint8_t *data; + size_t size; + + if (fd == NULL) { + return NULL; + } + in = gdNewFileCtx(fd); + if (in == NULL) { + return NULL; + } + data = TiffReadCtxData(in, &size); + in->gd_free(in); + if (data == NULL) { + return NULL; + } + tiff = TiffReadOpenFromData(data, size); + if (tiff == NULL) { + gdFree(data); + } + return tiff; +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenCtx(gdIOCtxPtr in) { + uint8_t *data; + size_t size; + + if (in == NULL) { + return NULL; + } + data = TiffReadCtxData(in, &size); + if (data == NULL) { + return NULL; + } + return TiffReadOpenFromData(data, size); +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenPtr(int size, void *data) { + uint8_t *buf; + + if (size <= 0 || data == NULL) { + return NULL; + } + buf = (uint8_t *)gdMalloc((size_t)size); + if (buf == NULL) { + return NULL; + } + memcpy(buf, data, (size_t)size); + return TiffReadOpenFromData(buf, (size_t)size); +} + +BGD_DECLARE(void) gdTiffReadClose(gdTiffReadPtr tiff) { + if (tiff == NULL) { + return; + } + if (tiff->image != NULL) { + gdImageDestroy(tiff->image); + } + if (tiff->tif != NULL) { + TIFFClose(tiff->tif); + } + if (tiff->th != NULL) { + gdFree(tiff->th); + } + if (tiff->memCtx != NULL) { + tiff->memCtx->gd_free(tiff->memCtx); + } + if (tiff->data != NULL) { + gdFree(tiff->data); + } + gdFree(tiff); +} + +BGD_DECLARE(int) gdTiffReadGetInfo(gdTiffReadPtr tiff, gdTiffInfo *info) { + tdir_t savedDir; + + if (tiff == NULL || info == NULL || tiff->tif == NULL) { + return 0; + } + savedDir = TIFFCurrentDirectory(tiff->tif); + TIFFSetDirectory(tiff->tif, 0); + TiffFillInfo(tiff->tif, info, tiff->pageCount); + TIFFSetDirectory(tiff->tif, savedDir); + return 1; +} + +BGD_DECLARE(int) +gdTiffReadNextImage(gdTiffReadPtr tiff, gdTiffPageInfo *info, + gdImagePtr *image) { + int ok; + + if (image != NULL) { + *image = NULL; + } + if (tiff == NULL || tiff->tif == NULL || tiff->pageCount <= 0) { + return -1; + } + + if (tiff->currentPage >= tiff->pageCount - 1 && tiff->currentPage >= 0) { + return 0; + } + + if (tiff->currentPage < 0) { + if (!TIFFSetDirectory(tiff->tif, 0)) { + return 0; + } + tiff->currentPage = 0; + } else { + if (!TIFFReadDirectory(tiff->tif)) { + return 0; + } + tiff->currentPage++; + } + + TiffFillPageInfo(tiff->tif, info, tiff->currentPage); + + if (tiff->image != NULL) { + gdImageDestroy(tiff->image); + tiff->image = NULL; + } + + tiff->image = TiffDecodeCurrentDirectory(tiff->tif); + if (tiff->image == NULL) { + ok = TIFFSetDirectory(tiff->tif, TIFFCurrentDirectory(tiff->tif)); + (void)ok; + return -1; + } + + if (image != NULL) { + *image = tiff->image; + } + return 1; +} + +BGD_DECLARE(gdImagePtr) gdTiffReadCloneImage(gdTiffReadPtr tiff) { + gdImagePtr dst; + int x, y; + + if (tiff == NULL || tiff->image == NULL) { + return NULL; + } + dst = + gdImageCreateTrueColor(gdImageSX(tiff->image), gdImageSY(tiff->image)); + if (dst == NULL) { + return NULL; + } + gdImageAlphaBlending(dst, 0); + gdImageSaveAlpha(dst, tiff->image->saveAlphaFlag); + for (y = 0; y < gdImageSY(tiff->image); y++) { + for (x = 0; x < gdImageSX(tiff->image); x++) { + dst->tpixels[y][x] = gdImageGetPixel(tiff->image, x, y); + } + } + return dst; +} + +BGD_DECLARE(int) gdTiffIsMultiPage(FILE *fd) { + gdIOCtx *in; + uint8_t *data; + size_t size; + gdIOCtx *memCtx; + tiff_handle *th; + TIFF *tif; + tdir_t dirCount; + int pos; + + if (fd == NULL) { + return -1; + } + in = gdNewFileCtx(fd); + if (in == NULL) { + return -1; + } + pos = (int)gdTell(in); + if (pos < 0) { + in->gd_free(in); + return -1; + } + data = TiffReadCtxData(in, &size); + if (data == NULL) { + in->gd_free(in); + return -1; + } + memCtx = gdNewDynamicCtxEx((int)size, data, 0); + if (memCtx == NULL) { + gdFree(data); + in->gd_free(in); + return -1; + } + th = new_tiff_handle(memCtx, (int)size); + if (th == NULL) { + memCtx->gd_free(memCtx); + gdFree(data); + in->gd_free(in); + return -1; + } + tif = TIFFClientOpen("", "rb", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + if (tif == NULL) { + gdFree(th); + memCtx->gd_free(memCtx); + gdFree(data); + in->gd_free(in); + return -1; + } + dirCount = TIFFNumberOfDirectories(tif); + TIFFClose(tif); + gdFree(th); + memCtx->gd_free(memCtx); + gdFree(data); + if (!gdSeek(in, pos)) { + in->gd_free(in); + return -1; + } + in->gd_free(in); + return dirCount > 1 ? 1 : 0; +} + +BGD_DECLARE(int) gdTiffIsMultiPageCtx(gdIOCtxPtr in) { + uint8_t *data; + size_t size; + gdIOCtx *memCtx; + tiff_handle *th; + TIFF *tif; + tdir_t dirCount; + int pos; + + if (in == NULL || in->tell == NULL || in->seek == NULL) { + return -1; + } + pos = (int)gdTell(in); + if (pos < 0) { + return -1; + } + data = TiffReadCtxData(in, &size); + if (data == NULL) { + return -1; + } + memCtx = gdNewDynamicCtxEx((int)size, data, 0); + if (memCtx == NULL) { + gdFree(data); + return -1; + } + th = new_tiff_handle(memCtx, (int)size); + if (th == NULL) { + memCtx->gd_free(memCtx); + gdFree(data); + return -1; + } + tif = TIFFClientOpen("", "rb", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + if (tif == NULL) { + gdFree(th); + memCtx->gd_free(memCtx); + gdFree(data); + return -1; + } + dirCount = TIFFNumberOfDirectories(tif); + TIFFClose(tif); + gdFree(th); + memCtx->gd_free(memCtx); + gdFree(data); + if (!gdSeek(in, pos)) { + return -1; + } + return dirCount > 1 ? 1 : 0; +} + +BGD_DECLARE(int) gdTiffIsMultiPagePtr(int size, void *data) { + gdIOCtx *in; + int result; + + if (size <= 0 || data == NULL) { + return -1; + } + in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) { + return -1; + } + result = gdTiffIsMultiPageCtx(in); + in->gd_free(in); + return result; +} + +/* ========== TIFF Write API ========== */ + +struct gdTiffWriteStruct { + gdIOCtx *out; + int ownsCtx; + int memoryWriter; + TIFF *tif; + tiff_handle *th; + gdTiffWriteOptions options; + int pageCount; + int finalized; +}; + +static int TiffWriteValidateOptions(const gdTiffWriteOptions *opts) { + if (opts == NULL) { + gd_error("gd-tiff write: options is NULL"); + return 0; + } + switch (opts->colorspace) { + case GD_TIFF_RGB: + case GD_TIFF_RGBA: + case GD_TIFF_GRAY: + case GD_TIFF_BILEVEL: + break; + default: + gd_error("gd-tiff write: invalid colorspace %d", opts->colorspace); + return 0; + } + switch (opts->bitDepth) { + case 1: + case 8: + case 16: + break; + default: + gd_error("gd-tiff write: invalid bit depth %d", opts->bitDepth); + return 0; + } + if (opts->bitDepth == 1 && opts->colorspace != GD_TIFF_BILEVEL) { + gd_error( + "gd-tiff write: 1-bit depth requires GD_TIFF_BILEVEL colorspace"); + return 0; + } + if (opts->colorspace == GD_TIFF_BILEVEL && opts->bitDepth != 1) { + gd_error("gd-tiff write: GD_TIFF_BILEVEL requires 1-bit depth"); + return 0; + } + switch (opts->compression) { + case COMPRESSION_NONE: + break; + case COMPRESSION_LZW: + case COMPRESSION_ADOBE_DEFLATE: + case COMPRESSION_DEFLATE: + case COMPRESSION_PACKBITS: + if (opts->bitDepth == 1 && opts->colorspace != GD_TIFF_BILEVEL) { + gd_error("gd-tiff write: LZW/Deflate/PackBits at 1-bit requires " + "BILEVEL"); + return 0; + } + break; + case COMPRESSION_JPEG: + if (opts->bitDepth != 8) { + gd_error("gd-tiff write: JPEG compression requires 8-bit depth"); + return 0; + } + break; + case COMPRESSION_CCITTFAX3: + case COMPRESSION_CCITTFAX4: + if (opts->bitDepth != 1) { + gd_error("gd-tiff write: CCITT compression requires 1-bit depth"); + return 0; + } + break; + default: + gd_error("gd-tiff write: unsupported compression %d", + opts->compression); + return 0; + } + if (opts->colorspace == GD_TIFF_RGB && opts->bitDepth == 1) { + gd_error("gd-tiff write: RGB at 1-bit is not valid"); + return 0; + } + if (opts->colorspace == GD_TIFF_RGBA && opts->bitDepth == 1) { + gd_error("gd-tiff write: RGBA at 1-bit is not valid"); + return 0; + } + return 1; +} + +static void TiffWriteSetDefaults(gdTiffWriteOptions *opts) { + if (opts->bitDepth == 0) + opts->bitDepth = 8; + if (opts->colorspace == 0) + opts->colorspace = GD_TIFF_RGBA; + if (opts->compression == 0) + opts->compression = COMPRESSION_ADOBE_DEFLATE; + if (opts->jpegQuality == 0 && opts->compression == COMPRESSION_JPEG) + opts->jpegQuality = 75; + if (opts->resolutionUnit == 0) + opts->resolutionUnit = GD_TIFF_RESUNIT_INCH; + if (opts->xResolution == 0) + opts->xResolution = 72.0f; + if (opts->yResolution == 0) + opts->yResolution = 72.0f; + if (opts->alphaType == 0) + opts->alphaType = GD_TIFF_ALPHA_UNASSOCIATED; +} + +static int TiffWriteSamplesPerPixel(const gdTiffWriteOptions *opts) { + switch (opts->colorspace) { + case GD_TIFF_RGBA: + return 4; + case GD_TIFF_RGB: + return 3; + case GD_TIFF_GRAY: + case GD_TIFF_BILEVEL: + return 1; + default: + return 3; + } +} + +static int TiffWritePhotometric(const gdTiffWriteOptions *opts) { + switch (opts->colorspace) { + case GD_TIFF_RGB: + case GD_TIFF_RGBA: + return PHOTOMETRIC_RGB; + case GD_TIFF_GRAY: + return opts->minIsWhite ? PHOTOMETRIC_MINISWHITE + : PHOTOMETRIC_MINISBLACK; + case GD_TIFF_BILEVEL: + return opts->minIsWhite ? PHOTOMETRIC_MINISWHITE + : PHOTOMETRIC_MINISBLACK; + default: + return PHOTOMETRIC_RGB; + } +} + +static int TiffWriteBitsPerSample(const gdTiffWriteOptions *opts) { + if (opts->colorspace == GD_TIFF_BILEVEL) + return 1; + return opts->bitDepth; +} + +static void TiffWriteConvertRowRGBA8(gdImagePtr im, int y, uint8_t *buf, + int width) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int a = gdImageAlpha(im, c); + a = (127 - a) * 2; + if (a > 255) + a = 255; + if (a == 254) + a = 255; + buf[x * 4 + 0] = (uint8_t)r; + buf[x * 4 + 1] = (uint8_t)g; + buf[x * 4 + 2] = (uint8_t)b; + buf[x * 4 + 3] = (uint8_t)a; + } +} + +static void TiffWriteConvertRowRGB8(gdImagePtr im, int y, uint8_t *buf, + int width) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int a = gdImageAlpha(im, c); + a = (127 - a) * 2; + if (a > 255) + a = 255; + if (a == 254) + a = 255; + if (a < 255) { + int af = a + 1; + r = (r * af + 127) / 255; + g = (g * af + 127) / 255; + b = (b * af + 127) / 255; + } + buf[x * 3 + 0] = (uint8_t)r; + buf[x * 3 + 1] = (uint8_t)g; + buf[x * 3 + 2] = (uint8_t)b; + } +} + +static void TiffWriteConvertRowGray8(gdImagePtr im, int y, uint8_t *buf, + int width, int minIsWhite) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int gray = (int)(0.2126f * r + 0.7152f * g + 0.0722f * b + 0.5f); + if (gray > 255) + gray = 255; + if (minIsWhite) + gray = 255 - gray; + buf[x] = (uint8_t)gray; + } +} + +static void TiffWriteConvertRowBilevel(gdImagePtr im, int y, uint8_t *buf, + int width, int minIsWhite) { + int x; + memset(buf, 0, (width + 7) / 8); + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int gray = (int)(0.2126f * r + 0.7152f * g + 0.0722f * b + 0.5f); + int bit; + if (minIsWhite) + bit = (gray < 128) ? 1 : 0; + else + bit = (gray >= 128) ? 1 : 0; + if (bit) + buf[x / 8] |= (uint8_t)(0x80 >> (x & 7)); + } +} + +static void TiffWriteConvertRowRGBA16(gdImagePtr im, int y, uint16_t *buf, + int width) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int a = gdImageAlpha(im, c); + a = (127 - a) * 2; + if (a > 255) + a = 255; + if (a == 254) + a = 255; + buf[x * 4 + 0] = (uint16_t)(r * 257); + buf[x * 4 + 1] = (uint16_t)(g * 257); + buf[x * 4 + 2] = (uint16_t)(b * 257); + buf[x * 4 + 3] = (uint16_t)(a * 257); + } +} + +static void TiffWriteConvertRowRGB16(gdImagePtr im, int y, uint16_t *buf, + int width) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int a = gdImageAlpha(im, c); + a = (127 - a) * 2; + if (a > 255) + a = 255; + if (a == 254) + a = 255; + if (a < 255) { + int af = a + 1; + r = (r * af + 127) / 255; + g = (g * af + 127) / 255; + b = (b * af + 127) / 255; + } + buf[x * 3 + 0] = (uint16_t)(r * 257); + buf[x * 3 + 1] = (uint16_t)(g * 257); + buf[x * 3 + 2] = (uint16_t)(b * 257); + } +} + +static void TiffWriteConvertRowGray16(gdImagePtr im, int y, uint16_t *buf, + int width, int minIsWhite) { + int x; + for (x = 0; x < width; x++) { + int c = im->tpixels[y][x]; + int r = gdImageRed(im, c); + int g = gdImageGreen(im, c); + int b = gdImageBlue(im, c); + int gray = (int)(0.2126f * r + 0.7152f * g + 0.0722f * b + 0.5f); + if (gray > 255) + gray = 255; + if (minIsWhite) + gray = 255 - gray; + buf[x] = (uint16_t)(gray * 257); + } +} + +static int TiffWriteWritePage(gdTiffWritePtr write, gdImagePtr im) { + TIFF *tif = write->tif; + gdTiffWriteOptions *opts = &write->options; + int width, height; + int nsamples, bps, photometric; + int y; + uint8_t *scanbuf = NULL; + + width = gdImageSX(im); + height = gdImageSY(im); + nsamples = TiffWriteSamplesPerPixel(opts); + bps = TiffWriteBitsPerSample(opts); + photometric = TiffWritePhotometric(opts); + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bps); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, nsamples); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric); + TIFFSetField(tif, TIFFTAG_COMPRESSION, opts->compression); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); + + if (opts->colorspace == GD_TIFF_RGBA) { + uint16_t extra = (opts->alphaType == GD_TIFF_ALPHA_ASSOCIATED) + ? EXTRASAMPLE_ASSOCALPHA + : EXTRASAMPLE_UNASSALPHA; + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, &extra); + } + + if (opts->compression == COMPRESSION_LZW || + opts->compression == COMPRESSION_ADOBE_DEFLATE || + opts->compression == COMPRESSION_DEFLATE) { + TIFFSetField(tif, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL); + } + + if (opts->compression == COMPRESSION_JPEG && opts->jpegQuality > 0) { + TIFFSetField(tif, TIFFTAG_JPEGQUALITY, opts->jpegQuality); + } + + if (opts->colorspace == GD_TIFF_BILEVEL && + (opts->compression == COMPRESSION_CCITTFAX3 || + opts->compression == COMPRESSION_CCITTFAX4)) { + uint32_t g3opts = 0; + if (opts->compression == COMPRESSION_CCITTFAX3) { + TIFFSetField(tif, TIFFTAG_GROUP3OPTIONS, g3opts); + } + if (opts->compression == COMPRESSION_CCITTFAX4) { + TIFFSetField(tif, TIFFTAG_GROUP4OPTIONS, 0); + } + TIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); + } + + { + uint16_t resUnit; + switch (opts->resolutionUnit) { + case GD_TIFF_RESUNIT_NONE: + resUnit = RESUNIT_NONE; + break; + case GD_TIFF_RESUNIT_INCH: + resUnit = RESUNIT_INCH; + break; + case GD_TIFF_RESUNIT_CENTIMETER: + resUnit = RESUNIT_CENTIMETER; + break; + default: + resUnit = RESUNIT_INCH; + break; + } + TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, resUnit); + TIFFSetField(tif, TIFFTAG_XRESOLUTION, opts->xResolution); + TIFFSetField(tif, TIFFTAG_YRESOLUTION, opts->yResolution); + } + + if (opts->colorspace == GD_TIFF_BILEVEL) { + size_t scanline_size = (size_t)((width + 7) / 8); + if (opts->compression == COMPRESSION_CCITTFAX3 || + opts->compression == COMPRESSION_CCITTFAX4) { + scanline_size = (size_t)TIFFScanlineSize(tif); + if (scanline_size == 0) + scanline_size = (size_t)((width + 7) / 8); + } + scanbuf = (uint8_t *)gdMalloc(scanline_size); + if (scanbuf == NULL) { + gd_error("gd-tiff write: could not allocate scanline buffer"); + return 0; + } + for (y = 0; y < height; y++) { + TiffWriteConvertRowBilevel(im, y, scanbuf, width, opts->minIsWhite); + if (TIFFWriteScanline(tif, scanbuf, y, 0) < 0) { + gdFree(scanbuf); + gd_error("gd-tiff write: could not write scanline"); + return 0; + } + } + } else if (opts->bitDepth == 16) { + size_t scanline_size = (size_t)TIFFScanlineSize(tif); + scanbuf = (uint8_t *)gdMalloc(scanline_size); + if (scanbuf == NULL) { + gd_error("gd-tiff write: could not allocate scanline buffer"); + return 0; + } + for (y = 0; y < height; y++) { + uint16_t *buf16 = (uint16_t *)scanbuf; + switch (opts->colorspace) { + case GD_TIFF_RGBA: + TiffWriteConvertRowRGBA16(im, y, buf16, width); + break; + case GD_TIFF_RGB: + TiffWriteConvertRowRGB16(im, y, buf16, width); + break; + case GD_TIFF_GRAY: + TiffWriteConvertRowGray16(im, y, buf16, width, + opts->minIsWhite); + break; + default: + break; + } + if (TIFFWriteScanline(tif, scanbuf, y, 0) < 0) { + gdFree(scanbuf); + gd_error("gd-tiff write: could not write scanline"); + return 0; + } + } + } else { + size_t scanline_size = (size_t)TIFFScanlineSize(tif); + scanbuf = (uint8_t *)gdMalloc(scanline_size); + if (scanbuf == NULL) { + gd_error("gd-tiff write: could not allocate scanline buffer"); + return 0; + } + for (y = 0; y < height; y++) { + switch (opts->colorspace) { + case GD_TIFF_RGBA: + TiffWriteConvertRowRGBA8(im, y, scanbuf, width); + break; + case GD_TIFF_RGB: + TiffWriteConvertRowRGB8(im, y, scanbuf, width); + break; + case GD_TIFF_GRAY: + TiffWriteConvertRowGray8(im, y, scanbuf, width, + opts->minIsWhite); + break; + default: + break; + } + if (TIFFWriteScanline(tif, scanbuf, y, 0) < 0) { + gdFree(scanbuf); + gd_error("gd-tiff write: could not write scanline"); + return 0; + } + } + } + + gdFree(scanbuf); + + if (!TIFFWriteDirectory(tif)) { + gd_error("gd-tiff write: could not write directory for page %d", + write->pageCount); + return 0; + } + + write->pageCount++; + return 1; +} + +static void TiffWriteFree(gdTiffWritePtr write) { + if (write == NULL) + return; + if (write->tif) { + TIFFClose(write->tif); + write->tif = NULL; + } + if (write->th) { + gdFree(write->th); + write->th = NULL; + } + if (write->ownsCtx && write->out) { + write->out->gd_free(write->out); + write->out = NULL; + } + gdFree(write); +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpen(FILE *outFile, const gdTiffWriteOptions *options) { + gdIOCtx *out; + gdTiffWritePtr write; + + if (outFile == NULL) + return NULL; + out = gdNewFileCtx(outFile); + if (out == NULL) + return NULL; + write = gdTiffWriteOpenCtx(out, options); + if (write == NULL) { + out->gd_free(out); + return NULL; + } + write->ownsCtx = 1; + return write; +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenCtx(gdIOCtxPtr out, const gdTiffWriteOptions *options) { + gdTiffWritePtr write; + tiff_handle *th; + TIFF *tif; + + if (out == NULL) + return NULL; + + write = (gdTiffWritePtr)gdCalloc(1, sizeof(struct gdTiffWriteStruct)); + if (write == NULL) + return NULL; + + write->out = out; + write->ownsCtx = 0; + + if (options != NULL) { + write->options = *options; + } else { + memset(&write->options, 0, sizeof(write->options)); + } + TiffWriteSetDefaults(&write->options); + + if (!TiffWriteValidateOptions(&write->options)) { + gdFree(write); + return NULL; + } + + th = new_tiff_handle(out, 0); + if (th == NULL) { + gdFree(write); + return NULL; + } + write->th = th; + + tif = TIFFClientOpen("", "w", th, tiff_readproc, tiff_writeproc, + tiff_seekproc, tiff_closeproc, tiff_sizeproc, + tiff_mapproc, tiff_unmapproc); + if (tif == NULL) { + gdFree(th); + gdFree(write); + gd_error("gd-tiff write: could not open TIFF for writing"); + return NULL; + } + write->tif = tif; + + return write; +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenPtr(const gdTiffWriteOptions *options) { + gdIOCtx *out; + gdTiffWritePtr write; + + out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; + write = gdTiffWriteOpenCtx(out, options); + if (write == NULL) { + out->gd_free(out); + return NULL; + } + write->ownsCtx = 1; + write->memoryWriter = 1; + return write; +} + +BGD_DECLARE(int) gdTiffWriteAddImage(gdTiffWritePtr write, gdImagePtr image) { + if (write == NULL || image == NULL || write->finalized) + return 0; + if (!image->trueColor) { + gd_error("gd-tiff write: only truecolor images are supported by the " + "new write API"); + return 0; + } + return TiffWriteWritePage(write, image); +} + +BGD_DECLARE(void) gdTiffWriteClose(gdTiffWritePtr write) { + if (write == NULL) + return; + write->finalized = 1; + TiffWriteFree(write); +} + +BGD_DECLARE(void *) gdTiffWritePtrFinish(gdTiffWritePtr write, int *size) { + void *rv = NULL; + + if (size != NULL) + *size = 0; + if (write == NULL || !write->memoryWriter) { + TiffWriteFree(write); + return NULL; + } + write->finalized = 1; + if (write->tif != NULL) { + TIFFClose(write->tif); + write->tif = NULL; + } + if (write->out != NULL) { + rv = gdDPExtractData(write->out, size); + } + TiffWriteFree(write); + return rv; +} + +#else + +static void _noTiffError(void) { + gd_error("TIFF image support has been disabled\n"); +} + +BGD_DECLARE(void) gdImageTiffCtx(gdImagePtr image, gdIOCtx *out) { + ARG_NOT_USED(image); + ARG_NOT_USED(out); + _noTiffError(); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffCtx(gdIOCtx *infile) { + ARG_NOT_USED(infile); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiff(FILE *inFile) { + ARG_NOT_USED(inFile); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromTiffPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(void) gdImageTiff(gdImagePtr im, FILE *outFile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + _noTiffError(); +} + +BGD_DECLARE(void *) gdImageTiffPtr(gdImagePtr im, int *size) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpen(FILE *fd) { + ARG_NOT_USED(fd); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenCtx(gdIOCtxPtr in) { + ARG_NOT_USED(in); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdTiffReadPtr) gdTiffReadOpenPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(void) gdTiffReadClose(gdTiffReadPtr tiff) { + ARG_NOT_USED(tiff); + _noTiffError(); +} + +BGD_DECLARE(int) gdTiffReadGetInfo(gdTiffReadPtr tiff, gdTiffInfo *info) { + ARG_NOT_USED(tiff); + ARG_NOT_USED(info); + _noTiffError(); + return 0; +} + +BGD_DECLARE(int) +gdTiffReadNextImage(gdTiffReadPtr tiff, gdTiffPageInfo *info, + gdImagePtr *image) { + ARG_NOT_USED(tiff); + ARG_NOT_USED(info); + ARG_NOT_USED(image); + _noTiffError(); + return -1; +} + +BGD_DECLARE(gdImagePtr) gdTiffReadCloneImage(gdTiffReadPtr tiff) { + ARG_NOT_USED(tiff); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(int) gdTiffIsMultiPage(FILE *fd) { + ARG_NOT_USED(fd); + _noTiffError(); + return -1; +} + +BGD_DECLARE(int) gdTiffIsMultiPageCtx(gdIOCtxPtr in) { + ARG_NOT_USED(in); + _noTiffError(); + return -1; +} + +BGD_DECLARE(int) gdTiffIsMultiPagePtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noTiffError(); + return -1; +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpen(FILE *outFile, const gdTiffWriteOptions *options) { + ARG_NOT_USED(outFile); + ARG_NOT_USED(options); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenCtx(gdIOCtxPtr out, const gdTiffWriteOptions *options) { + ARG_NOT_USED(out); + ARG_NOT_USED(options); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(gdTiffWritePtr) +gdTiffWriteOpenPtr(const gdTiffWriteOptions *options) { + ARG_NOT_USED(options); + _noTiffError(); + return NULL; +} + +BGD_DECLARE(int) gdTiffWriteAddImage(gdTiffWritePtr write, gdImagePtr image) { + ARG_NOT_USED(write); + ARG_NOT_USED(image); + _noTiffError(); + return 0; +} + +BGD_DECLARE(void) gdTiffWriteClose(gdTiffWritePtr write) { + ARG_NOT_USED(write); + _noTiffError(); +} + +BGD_DECLARE(void *) gdTiffWritePtrFinish(gdTiffWritePtr write, int *size) { + ARG_NOT_USED(write); + ARG_NOT_USED(size); + _noTiffError(); + return NULL; +} + +#endif diff --git a/ext/gd/libgd/gd_topal.c b/ext/gd/libgd/gd_topal.c index 2a9fb3d608dc..be15307a7361 100644 --- a/ext/gd/libgd/gd_topal.c +++ b/ext/gd/libgd/gd_topal.c @@ -28,6 +28,12 @@ * fundamental assumptions even hold with an irregularly spaced color map. */ +/** + * File: Color Quantization + * + * Functions for truecolor to palette conversion + */ + /* * THOMAS BOUTELL & BAREND GEHRELS, february 2003 * adapted the code to work within gd rather than within libjpeg. @@ -38,6 +44,11 @@ #include #include "gd.h" #include "gdhelpers.h" +#include + +#ifdef HAVE_LIBIMAGEQUANT +#include +#endif /* (Re)define some defines known by libjpeg */ #define QUANT_2PASS_SUPPORTED @@ -56,7 +67,6 @@ #define METHODDEF(type) static type #define LOCAL(type) static type - /* We assume that right shift corresponds to signed division by 2 with * rounding towards minus infinity. This is correct for typical "arithmetic * shift" instructions that shift in copies of the sign bit. But some @@ -70,17 +80,21 @@ #ifdef RIGHT_SHIFT_IS_UNSIGNED #define SHIFT_TEMPS INT32 shift_temp; #define RIGHT_SHIFT(x,shft) \ - ((shift_temp = (x)) < 0 ? \ - (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ - (shift_temp >> (shft))) + ((shift_temp = (x)) < 0 \ + ? (shift_temp >> (shft)) | ((~((INT32)0)) << (32 - (shft))) \ + : (shift_temp >> (shft))) #else #define SHIFT_TEMPS #define RIGHT_SHIFT(x,shft) ((x) >> (shft)) #endif - -#define range_limit(x) { if(x<0) x=0; if (x>255) x=255; } - +#define range_limit(x) \ + { \ + if (x < 0) \ + x = 0; \ + if (x > 255) \ + x = 255; \ + } #ifndef INT16 #define INT16 short @@ -98,8 +112,6 @@ #define FAR #endif - - #ifndef boolean #define boolean int #endif @@ -112,13 +124,11 @@ #define FALSE 0 #endif - #define input_buf (oim->tpixels) #define output_buf (nim->pixels) #ifdef QUANT_2PASS_SUPPORTED - /* * This module implements the well-known Heckbert paradigm for color * quantization. Most of the ideas used here can be traced back to @@ -192,7 +202,6 @@ #define C2_SCALE B_SCALE #endif - /* * First we have the histogram data structure and routines for creating it. * @@ -238,7 +247,6 @@ #define C1_SHIFT (BITS_IN_JSAMPLE-HIST_C1_BITS) #define C2_SHIFT (BITS_IN_JSAMPLE-HIST_C2_BITS) - typedef UINT16 histcell; /* histogram cell; prefer an unsigned type */ typedef histcell FAR *histptr; /* for pointers to histogram cells */ @@ -247,7 +255,6 @@ typedef histcell hist1d[HIST_C2_ELEMS]; /* typedefs for the array */ typedef hist1d FAR *hist2d; /* type for the 2nd-level pointers */ typedef hist2d *hist3d; /* type for top-level pointer */ - /* Declarations for Floyd-Steinberg dithering. * * Errors are accumulated into the array fserrors[], at a resolution of @@ -282,27 +289,22 @@ typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ - /* Private subobject */ -typedef struct -{ +typedef struct { /* Variables for accumulating image statistics */ hist3d histogram; /* pointer to the histogram */ - /* Variables for Floyd-Steinberg dithering */ FSERRPTR fserrors; /* accumulated errors */ boolean on_odd_row; /* flag to remember which row we are on */ int *error_limiter; /* table for clamping the applied error */ int *error_limiter_storage; /* gdMalloc'd storage for the above */ -} -my_cquantizer; +} my_cquantizer; typedef my_cquantizer *my_cquantize_ptr; - /* * Prescan some rows of pixels. * In this module the prescan simply updates the histogram, which has been @@ -313,8 +315,7 @@ typedef my_cquantizer *my_cquantize_ptr; */ METHODDEF (void) -prescan_quantize (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) -{ +prescan_quantize(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) { register JSAMPROW ptr; register histptr histp; register hist3d histogram = cquantize->histogram; @@ -323,18 +324,17 @@ prescan_quantize (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) int width = oim->sx; int num_rows = oim->sy; - for (row = 0; row < num_rows; row++) - { + (void)nim; + + for (row = 0; row < num_rows; row++) { ptr = input_buf[row]; - for (col = width; col > 0; col--) - { + for (col = width; col > 0; col--) { int r = gdTrueColorGetRed (*ptr) >> C0_SHIFT; int g = gdTrueColorGetGreen (*ptr) >> C1_SHIFT; int b = gdTrueColorGetBlue (*ptr) >> C2_SHIFT; /* 2.0.12: Steven Brown: support a single totally transparent color in the original. */ - if ((oim->transparent >= 0) && (*ptr == oim->transparent)) - { + if ((oim->transparent >= 0) && (*ptr == oim->transparent)) { ptr++; continue; } @@ -348,7 +348,6 @@ prescan_quantize (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) } } - /* * Next we have the really interesting routines: selection of a colormap * given the completed histogram. @@ -356,8 +355,7 @@ prescan_quantize (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) * subset of the input color space (to histogram precision). */ -typedef struct -{ +typedef struct { /* The bounds of the box (inclusive); expressed as histogram indexes */ int c0min, c0max; int c1min, c1max; @@ -366,13 +364,12 @@ typedef struct INT32 volume; /* The number of nonzero histogram cells within this box */ long colorcount; -} -box; +} box; typedef box *boxptr; - -LOCAL (boxptr) find_biggest_color_pop (boxptr boxlist, int numboxes) +LOCAL(boxptr) +find_biggest_color_pop(boxptr boxlist, int numboxes) /* Find the splittable box with the largest color population */ /* Returns NULL if no splittable boxes remain */ { @@ -381,10 +378,8 @@ LOCAL (boxptr) find_biggest_color_pop (boxptr boxlist, int numboxes) register long maxc = 0; boxptr which = NULL; - for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) - { - if (boxp->colorcount > maxc && boxp->volume > 0) - { + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->colorcount > maxc && boxp->volume > 0) { which = boxp; maxc = boxp->colorcount; } @@ -392,8 +387,8 @@ LOCAL (boxptr) find_biggest_color_pop (boxptr boxlist, int numboxes) return which; } - -LOCAL (boxptr) find_biggest_volume (boxptr boxlist, int numboxes) +LOCAL(boxptr) +find_biggest_volume(boxptr boxlist, int numboxes) /* Find the splittable box with the largest (scaled) volume */ /* Returns NULL if no splittable boxes remain */ { @@ -402,10 +397,8 @@ LOCAL (boxptr) find_biggest_volume (boxptr boxlist, int numboxes) register INT32 maxv = 0; boxptr which = NULL; - for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) - { - if (boxp->volume > maxv) - { + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->volume > maxv) { which = boxp; maxv = boxp->volume; } @@ -413,16 +406,17 @@ LOCAL (boxptr) find_biggest_volume (boxptr boxlist, int numboxes) return which; } - LOCAL (void) - update_box (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, boxptr boxp) -{ +update_box(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, + boxptr boxp) { hist3d histogram = cquantize->histogram; histptr histp; int c0, c1, c2; int c0min, c0max, c1min, c1max, c2min, c2max; INT32 dist0, dist1, dist2; long ccount; + (void)oim; + (void)nim; c0min = boxp->c0min; c0max = boxp->c0max; @@ -433,12 +427,10 @@ LOCAL (void) if (c0max > c0min) for (c0 = c0min; c0 <= c0max; c0++) - for (c1 = c1min; c1 <= c1max; c1++) - { + for (c1 = c1min; c1 <= c1max; c1++) { histp = &histogram[c0][c1][c2min]; for (c2 = c2min; c2 <= c2max; c2++) - if (*histp++ != 0) - { + if (*histp++ != 0) { boxp->c0min = c0min = c0; goto have_c0min; } @@ -446,12 +438,10 @@ LOCAL (void) have_c0min: if (c0max > c0min) for (c0 = c0max; c0 >= c0min; c0--) - for (c1 = c1min; c1 <= c1max; c1++) - { + for (c1 = c1min; c1 <= c1max; c1++) { histp = &histogram[c0][c1][c2min]; for (c2 = c2min; c2 <= c2max; c2++) - if (*histp++ != 0) - { + if (*histp++ != 0) { boxp->c0max = c0max = c0; goto have_c0max; } @@ -459,12 +449,10 @@ LOCAL (void) have_c0max: if (c1max > c1min) for (c1 = c1min; c1 <= c1max; c1++) - for (c0 = c0min; c0 <= c0max; c0++) - { + for (c0 = c0min; c0 <= c0max; c0++) { histp = &histogram[c0][c1][c2min]; for (c2 = c2min; c2 <= c2max; c2++) - if (*histp++ != 0) - { + if (*histp++ != 0) { boxp->c1min = c1min = c1; goto have_c1min; } @@ -472,12 +460,10 @@ LOCAL (void) have_c1min: if (c1max > c1min) for (c1 = c1max; c1 >= c1min; c1--) - for (c0 = c0min; c0 <= c0max; c0++) - { + for (c0 = c0min; c0 <= c0max; c0++) { histp = &histogram[c0][c1][c2min]; for (c2 = c2min; c2 <= c2max; c2++) - if (*histp++ != 0) - { + if (*histp++ != 0) { boxp->c1max = c1max = c1; goto have_c1max; } @@ -485,12 +471,10 @@ LOCAL (void) have_c1max: if (c2max > c2min) for (c2 = c2min; c2 <= c2max; c2++) - for (c0 = c0min; c0 <= c0max; c0++) - { + for (c0 = c0min; c0 <= c0max; c0++) { histp = &histogram[c0][c1min][c2]; for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) - if (*histp != 0) - { + if (*histp != 0) { boxp->c2min = c2min = c2; goto have_c2min; } @@ -498,12 +482,10 @@ LOCAL (void) have_c2min: if (c2max > c2min) for (c2 = c2max; c2 >= c2min; c2--) - for (c0 = c0min; c0 <= c0max; c0++) - { + for (c0 = c0min; c0 <= c0max; c0++) { histp = &histogram[c0][c1min][c2]; for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) - if (*histp != 0) - { + if (*histp != 0) { boxp->c2max = c2max = c2; goto have_c2max; } @@ -526,19 +508,16 @@ LOCAL (void) /* Now scan remaining volume of box and compute population */ ccount = 0; for (c0 = c0min; c0 <= c0max; c0++) - for (c1 = c1min; c1 <= c1max; c1++) - { + for (c1 = c1min; c1 <= c1max; c1++) { histp = &histogram[c0][c1][c2min]; for (c2 = c2min; c2 <= c2max; c2++, histp++) - if (*histp != 0) - { + if (*histp != 0) { ccount++; } } boxp->colorcount = ccount; } - LOCAL (int) median_cut (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, boxptr boxlist, int numboxes, int desired_colors) @@ -548,17 +527,13 @@ median_cut (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int c0, c1, c2, cmax; register boxptr b1, b2; - while (numboxes < desired_colors) - { + while (numboxes < desired_colors) { /* Select box to split. * Current algorithm: by population for first half, then by volume. */ - if (numboxes * 2 <= desired_colors) - { + if (numboxes * 2 <= desired_colors) { b1 = find_biggest_color_pop (boxlist, numboxes); - } - else - { + } else { b1 = find_biggest_volume (boxlist, numboxes); } if (b1 == NULL) /* no splittable boxes left! */ @@ -584,25 +559,21 @@ median_cut (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, #if RGB_RED == 0 cmax = c1; n = 1; - if (c0 > cmax) - { + if (c0 > cmax) { cmax = c0; n = 0; - } - if (c2 > cmax) - { + } + if (c2 > cmax) { n = 2; - } + } #else cmax = c1; n = 1; - if (c2 > cmax) - { + if (c2 > cmax) { cmax = c2; n = 2; } - if (c0 > cmax) - { + if (c0 > cmax) { n = 0; } #endif @@ -612,8 +583,7 @@ median_cut (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, * any split will produce two nonempty subboxes.) * Note that lb value is max for lower box, so must be < old max. */ - switch (n) - { + switch (n) { case 0: lb = (b1->c0max + b1->c0min) / 2; b1->c0max = lb; @@ -638,14 +608,9 @@ median_cut (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, return numboxes; } - LOCAL (void) compute_color (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, - boxptr boxp, int icolor) -/* Compute representative color for a box, put it in colormap[icolor] */ -{ - /* Current algorithm: mean weighted by pixels (not colors) */ - /* Note it is important to get the rounding correct! */ + boxptr boxp, int icolor) { hist3d histogram = cquantize->histogram; histptr histp; int c0, c1, c2; @@ -655,6 +620,7 @@ LOCAL (void) long c0total = 0; long c1total = 0; long c2total = 0; + (void)oim; c0min = boxp->c0min; c0max = boxp->c0max; @@ -664,13 +630,10 @@ LOCAL (void) c2max = boxp->c2max; for (c0 = c0min; c0 <= c0max; c0++) - for (c1 = c1min; c1 <= c1max; c1++) - { + for (c1 = c1min; c1 <= c1max; c1++) { histp = &histogram[c0][c1][c2min]; - for (c2 = c2min; c2 <= c2max; c2++) - { - if ((count = *histp++) != 0) - { + for (c2 = c2min; c2 <= c2max; c2++) { + if ((count = *histp++) != 0) { total += count; c0total += ((c0 << C0_SHIFT) + ((1 << C0_SHIFT) >> 1)) * count; @@ -683,14 +646,11 @@ LOCAL (void) } /* 2.0.16: Paul den Dulk found an occasion where total can be 0 */ - if (total) - { + if (total) { nim->red[icolor] = (int) ((c0total + (total >> 1)) / total); nim->green[icolor] = (int) ((c1total + (total >> 1)) / total); nim->blue[icolor] = (int) ((c2total + (total >> 1)) / total); - } - else - { + } else { nim->red[icolor] = 255; nim->green[icolor] = 255; nim->blue[icolor] = 255; @@ -698,9 +658,9 @@ LOCAL (void) nim->open[icolor] = 0; } - LOCAL (void) -select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int desired_colors) +select_colors(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, + int desired_colors) /* Master routine for color selection */ { boxptr boxlist; @@ -708,7 +668,15 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d int i; /* Allocate workspace for box list */ - boxlist = (boxptr) safe_emalloc(desired_colors, sizeof (box), 1); + /* This can't happen because we clamp desired_colors at gdMaxColors, + but anyway */ + if (overflow2(desired_colors, sizeof(box))) { + return; + } + boxlist = (boxptr)gdMalloc(desired_colors * sizeof(box)); + if (!boxlist) { + return; + } /* Initialize one box containing whole space */ numboxes = 1; boxlist[0].c0min = 0; @@ -720,7 +688,8 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d /* Shrink it to actually-used volume and set its statistics */ update_box (oim, nim, cquantize, &boxlist[0]); /* Perform median-cut to produce final box list */ - numboxes = median_cut (oim, nim, cquantize, boxlist, numboxes, desired_colors); + numboxes = + median_cut(oim, nim, cquantize, boxlist, numboxes, desired_colors); /* Compute the representative color for each box, fill colormap */ for (i = 0; i < numboxes; i++) compute_color (oim, nim, cquantize, &boxlist[i], i); @@ -730,8 +699,7 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d * Skip incrementing the color count so that the dither / matching phase * won't use it on pixels that shouldn't have been transparent. We'll * increment it after all that finishes. */ - if (oim->transparent >= 0) - { + if (oim->transparent >= 0) { /* Save the transparent color. */ nim->red[nim->colorsTotal] = gdTrueColorGetRed (oim->transparent); nim->green[nim->colorsTotal] = gdTrueColorGetGreen (oim->transparent); @@ -743,7 +711,6 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d gdFree (boxlist); } - /* * These routines are concerned with the time-critical task of mapping input * colors to the nearest color in the selected colormap. @@ -797,7 +764,6 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d * it might not be any faster, and it's certainly more complicated. */ - /* log2(histogram cells in update box) for each axis; this can be adjusted */ #define BOX_C0_LOG (HIST_C0_BITS-3) #define BOX_C1_LOG (HIST_C1_BITS-3) @@ -811,7 +777,6 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d #define BOX_C1_SHIFT (C1_SHIFT + BOX_C1_LOG) #define BOX_C2_SHIFT (C2_SHIFT + BOX_C2_LOG) - /* * The next three routines implement inverse colormap filling. They could * all be folded into one big routine, but splitting them up this way saves @@ -821,8 +786,7 @@ select_colors (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int d */ LOCAL (int) -find_nearby_colors ( - gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, +find_nearby_colors(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int minc0, int minc1, int minc2, JSAMPLE colorlist[]) /* Locate the colormap entries close enough to an update box to be candidates * for the nearest entry to some cell(s) in the update box. The update box @@ -839,6 +803,8 @@ find_nearby_colors ( int i, x, ncolors; INT32 minmaxdist, min_dist, max_dist, tdist; INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */ + (void)oim; + (void)cquantize; /* Compute true coordinates of update box's upper corner and center. * Actually we compute the coordinates of the center of the upper-corner @@ -863,95 +829,71 @@ find_nearby_colors ( */ minmaxdist = 0x7FFFFFFFL; - for (i = 0; i < numcolors; i++) - { - /* We compute the squared-c0-distance term, then add in the other two. */ + for (i = 0; i < numcolors; i++) { + /* We compute the squared-c0-distance term, then add in the other two. + */ x = nim->red[i]; - if (x < minc0) - { + if (x < minc0) { tdist = (x - minc0) * C0_SCALE; min_dist = tdist * tdist; tdist = (x - maxc0) * C0_SCALE; max_dist = tdist * tdist; - } - else if (x > maxc0) - { + } else if (x > maxc0) { tdist = (x - maxc0) * C0_SCALE; min_dist = tdist * tdist; tdist = (x - minc0) * C0_SCALE; max_dist = tdist * tdist; - } - else - { + } else { /* within cell range so no contribution to min_dist */ min_dist = 0; - if (x <= centerc0) - { + if (x <= centerc0) { tdist = (x - maxc0) * C0_SCALE; max_dist = tdist * tdist; - } - else - { + } else { tdist = (x - minc0) * C0_SCALE; max_dist = tdist * tdist; } } x = nim->green[i]; - if (x < minc1) - { + if (x < minc1) { tdist = (x - minc1) * C1_SCALE; min_dist += tdist * tdist; tdist = (x - maxc1) * C1_SCALE; max_dist += tdist * tdist; - } - else if (x > maxc1) - { + } else if (x > maxc1) { tdist = (x - maxc1) * C1_SCALE; min_dist += tdist * tdist; tdist = (x - minc1) * C1_SCALE; max_dist += tdist * tdist; - } - else - { + } else { /* within cell range so no contribution to min_dist */ - if (x <= centerc1) - { + if (x <= centerc1) { tdist = (x - maxc1) * C1_SCALE; max_dist += tdist * tdist; - } - else - { + } else { tdist = (x - minc1) * C1_SCALE; max_dist += tdist * tdist; } } x = nim->blue[i]; - if (x < minc2) - { + if (x < minc2) { tdist = (x - minc2) * C2_SCALE; min_dist += tdist * tdist; tdist = (x - maxc2) * C2_SCALE; max_dist += tdist * tdist; - } - else if (x > maxc2) - { + } else if (x > maxc2) { tdist = (x - maxc2) * C2_SCALE; min_dist += tdist * tdist; tdist = (x - minc2) * C2_SCALE; max_dist += tdist * tdist; - } - else - { + } else { /* within cell range so no contribution to min_dist */ - if (x <= centerc2) - { + if (x <= centerc2) { tdist = (x - maxc2) * C2_SCALE; max_dist += tdist * tdist; - } - else - { + } else { tdist = (x - minc2) * C2_SCALE; max_dist += tdist * tdist; } @@ -967,20 +909,17 @@ find_nearby_colors ( * within minmaxdist of some part of the box need be considered. */ ncolors = 0; - for (i = 0; i < numcolors; i++) - { + for (i = 0; i < numcolors; i++) { if (mindist[i] <= minmaxdist) colorlist[ncolors++] = (JSAMPLE) i; } return ncolors; } - -LOCAL (void) find_best_colors ( - gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, - int minc0, int minc1, int minc2, - int numcolors, JSAMPLE colorlist[], - JSAMPLE bestcolor[]) +LOCAL(void) +find_best_colors(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, + int minc0, int minc1, int minc2, int numcolors, + JSAMPLE colorlist[], JSAMPLE bestcolor[]) /* Find the closest colormap entry for each cell in the update box, * given the list of candidate colors prepared by find_nearby_colors. * Return the indexes of the closest entries in the bestcolor[] array. @@ -997,8 +936,11 @@ LOCAL (void) find_best_colors ( INT32 xx0, xx1; /* distance increments */ register INT32 xx2; INT32 inc0, inc1, inc2; /* initial values for increments */ - /* This array holds the distance to the nearest-so-far color for each cell */ + /* This array holds the distance to the nearest-so-far color for each cell + */ INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + (void)oim; + (void)cquantize; /* Initialize best-distance for each cell of the update box */ bptr = bestdist; @@ -1007,7 +949,8 @@ LOCAL (void) find_best_colors ( /* For each color selected by find_nearby_colors, * compute its distance to the center of each cell in the box. - * If that's less than best-so-far, update best distance and color number. + * If that's less than best-so-far, update best distance and color + * number. */ /* Nominal steps between cell centers ("x" in Thomas article) */ @@ -1015,8 +958,7 @@ LOCAL (void) find_best_colors ( #define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE) #define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE) - for (i = 0; i < numcolors; i++) - { + for (i = 0; i < numcolors; i++) { int r, g, b; icolor = colorlist[i]; r = nim->red[icolor]; @@ -1034,22 +976,19 @@ LOCAL (void) find_best_colors ( inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; - /* Now loop over all cells in box, updating distance per Thomas method */ + /* Now loop over all cells in box, updating distance per Thomas method + */ bptr = bestdist; cptr = bestcolor; xx0 = inc0; - for (ic0 = BOX_C0_ELEMS - 1; ic0 >= 0; ic0--) - { + for (ic0 = BOX_C0_ELEMS - 1; ic0 >= 0; ic0--) { dist1 = dist0; xx1 = inc1; - for (ic1 = BOX_C1_ELEMS - 1; ic1 >= 0; ic1--) - { + for (ic1 = BOX_C1_ELEMS - 1; ic1 >= 0; ic1--) { dist2 = dist1; xx2 = inc2; - for (ic2 = BOX_C2_ELEMS - 1; ic2 >= 0; ic2--) - { - if (dist2 < *bptr) - { + for (ic2 = BOX_C2_ELEMS - 1; ic2 >= 0; ic2--) { + if (dist2 < *bptr) { *bptr = dist2; *cptr = (JSAMPLE) icolor; } @@ -1067,10 +1006,8 @@ LOCAL (void) find_best_colors ( } } - LOCAL (void) -fill_inverse_cmap ( - gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, +fill_inverse_cmap(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize, int c0, int c1, int c2) /* Fill the inverse-colormap entries in the update box that contains */ /* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ @@ -1113,27 +1050,22 @@ fill_inverse_cmap ( c1 <<= BOX_C1_LOG; c2 <<= BOX_C2_LOG; cptr = bestcolor; - for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) - { - for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) - { + for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { + for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { cachep = &histogram[c0 + ic0][c1 + ic1][c2]; - for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) - { + for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { *cachep++ = (histcell) ((*cptr++) + 1); } } } } - /* * Map some rows of pixels to the output colormapped representation. */ METHODDEF (void) -pass2_no_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) -{ +pass2_no_dither(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) { register int *inptr; register unsigned char *outptr; int width = oim->sx; @@ -1144,13 +1076,10 @@ pass2_no_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) JDIMENSION col; register histptr cachep; - - for (row = 0; row < num_rows; row++) - { + for (row = 0; row < num_rows; row++) { inptr = input_buf[row]; outptr = output_buf[row]; - for (col = width; col > 0; col--) - { + for (col = width; col > 0; col--) { /* get pixel value and index into the cache */ int r, g, b; r = gdTrueColorGetRed (*inptr); @@ -1164,8 +1093,7 @@ pass2_no_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) /* If the pixel is transparent, we assign it the palette index that * will later be added at the end of the palette as the transparent * index. */ - if ((oim->transparent >= 0) && (oim->transparent == *inptr)) - { + if ((oim->transparent >= 0) && (oim->transparent == *inptr)) { *outptr++ = nim->colorsTotal; inptr++; continue; @@ -1175,7 +1103,8 @@ pass2_no_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) c1 = g >> C1_SHIFT; c2 = b >> C2_SHIFT; cachep = &histogram[c0][c1][c2]; - /* If we have not seen this color before, find nearest colormap entry */ + /* If we have not seen this color before, find nearest colormap + * entry */ /* and update the cache */ if (*cachep == 0) fill_inverse_cmap (oim, nim, cquantize, c0, c1, c2); @@ -1185,10 +1114,8 @@ pass2_no_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) } } - METHODDEF (void) -pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) -{ +pass2_fs_dither(gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) { hist3d histogram = cquantize->histogram; register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */ LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */ @@ -1208,26 +1135,23 @@ pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) int *colormap2 = nim->blue; int *error_limit = cquantize->error_limiter; - - SHIFT_TEMPS for (row = 0; row < num_rows; row++) - { + SHIFT_TEMPS for (row = 0; row < num_rows; row++) { inptr = input_buf[row]; outptr = output_buf[row]; - if (cquantize->on_odd_row) - { + if (cquantize->on_odd_row) { /* work right to left in this row */ inptr += (width - 1) * 3; /* so point to rightmost pixel */ outptr += width - 1; dir = -1; dir3 = -3; - errorptr = cquantize->fserrors + (width + 1) * 3; /* => entry after last column */ - } - else - { + errorptr = cquantize->fserrors + + (width + 1) * 3; /* => entry after last column */ + } else { /* work left to right in this row */ dir = 1; dir3 = 3; - errorptr = cquantize->fserrors; /* => entry before first real column */ + errorptr = + cquantize->fserrors; /* => entry before first real column */ } /* Preset error values: no error propagated to first pixel from left */ cur0 = cur1 = cur2 = 0; @@ -1235,14 +1159,12 @@ pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) belowerr0 = belowerr1 = belowerr2 = 0; bpreverr0 = bpreverr1 = bpreverr2 = 0; - for (col = width; col > 0; col--) - { + for (col = width; col > 0; col--) { /* If this pixel is transparent, we want to assign it to the special * transparency color index past the end of the palette rather than * go through matching / dithering. */ - if ((oim->transparent >= 0) && (*inptr == oim->transparent)) - { + if ((oim->transparent >= 0) && (*inptr == oim->transparent)) { *outptr = nim->colorsTotal; errorptr[0] = 0; errorptr[1] = 0; @@ -1282,8 +1204,8 @@ pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) range_limit (cur2); /* Index into the cache with adjusted pixel value */ - cachep = - &histogram[cur0 >> C0_SHIFT][cur1 >> C1_SHIFT][cur2 >> C2_SHIFT]; + cachep = &histogram[cur0 >> C0_SHIFT][cur1 >> C1_SHIFT] + [cur2 >> C2_SHIFT]; /* If we have not seen this color before, find nearest colormap */ /* entry and update the cache */ if (*cachep == 0) @@ -1334,7 +1256,8 @@ pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) } /* At this point curN contains the 7/16 error value to be propagated * to the next pixel on the current line, and all the errors for the - * next line have been shifted over. We are therefore ready to move on. + * next line have been shifted over. We are therefore ready to move + * on. */ inptr += dir; /* Advance pixel pointers to next column */ outptr += dir; @@ -1350,7 +1273,6 @@ pass2_fs_dither (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) } } - /* * Initialize the error-limiting transfer function (lookup table). * The raw F-S error computation can potentially compute error values of up to @@ -1374,13 +1296,14 @@ init_error_limit (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) { int *table; int in, out; + (void)oim; + (void)nim; cquantize->error_limiter_storage = - (int *) safe_emalloc ((MAXJSAMPLE * 2 + 1), sizeof (int), 0); - if (!cquantize->error_limiter_storage) - { + (int *)gdMalloc((MAXJSAMPLE * 2 + 1) * sizeof(int)); + if (!cquantize->error_limiter_storage) { return; - } + } table = cquantize->error_limiter_storage; table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ @@ -1389,47 +1312,127 @@ init_error_limit (gdImagePtr oim, gdImagePtr nim, my_cquantize_ptr cquantize) #define STEPSIZE ((MAXJSAMPLE+1)/16) /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ out = 0; - for (in = 0; in < STEPSIZE; in++, out++) - { + for (in = 0; in < STEPSIZE; in++, out++) { table[in] = out; table[-in] = -out; } /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ - for (; in < STEPSIZE * 3; in++, out += (in & 1) ? 0 : 1) - { + for (; in < STEPSIZE * 3; in++, out += (in & 1) ? 0 : 1) { table[in] = out; table[-in] = -out; } /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ - for (; in <= MAXJSAMPLE; in++) - { + for (; in <= MAXJSAMPLE; in++) { table[in] = out; table[-in] = -out; } #undef STEPSIZE } - /* * Finish up at the end of each pass. */ -static void -zeroHistogram (hist3d histogram) -{ +static void zeroHistogram(hist3d histogram) { int i; /* Zero the histogram or inverse color map */ - for (i = 0; i < HIST_C0_ELEMS; i++) - { - memset (histogram[i], - 0, HIST_C1_ELEMS * HIST_C2_ELEMS * sizeof (histcell)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + memset(histogram[i], 0, + HIST_C1_ELEMS * HIST_C2_ELEMS * sizeof(histcell)); } } -static int gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colorsWanted, gdImagePtr *cimP); +/** + * Function: gdImageTrueColorToPaletteSetMethod + * + * Selects the quantization method + * + * That quantization method is used for all subsequent + * and calls. + * + * Parameters: + * im - The image. + * method - The quantization method, see . + * speed - The quantization speed between 1 (highest quality) and + * 10 (fastest). 0 selects a method-specific default (recommended). + * + * Returns: + * Zero if the given method is invalid or not available; non-zero otherwise. + * + * See also: + * - + */ +BGD_DECLARE(int) +gdImageTrueColorToPaletteSetMethod(gdImagePtr im, int method, int speed) { +#ifndef HAVE_LIBIMAGEQUANT + if (method == GD_QUANT_LIQ) { + return FALSE; + } +#endif + + if (method >= GD_QUANT_DEFAULT && method <= GD_QUANT_LIQ) { + im->paletteQuantizationMethod = method; + + if (speed < 0 || speed > 10) { + speed = 0; + } + im->paletteQuantizationSpeed = speed; + } + return TRUE; +} + +/** + * Function: gdImageTrueColorToPaletteSetQuality + * + * Chooses a quality range for quantization + * + * That quality range is used in all subsequent calls to + * and + * if the quantization method is . + * + * Parameters: + * im - The image. + * min_quality - The minimum quality in range 1-100 (1 = ugly, 100 = perfect). + * If the palette cannot represent the image with at least + * min_quality, then no conversion is done. + * max_quality - The maximum quality in range 1-100 (1 = ugly, 100 = perfect), + * which must be higher than the min_quality. If the palette can + * represent the image with a quality better than max_quality, + * then fewer colors than requested will be used. + */ +BGD_DECLARE(void) +gdImageTrueColorToPaletteSetQuality(gdImagePtr im, int min_quality, + int max_quality) { + if (min_quality >= 0 && min_quality <= 100 && max_quality >= 0 && + max_quality <= 100 && min_quality <= max_quality) { + im->paletteQuantizationMinQuality = min_quality; + im->paletteQuantizationMaxQuality = max_quality; + } +} + +static int gdImageTrueColorToPaletteBody(gdImagePtr oim, int dither, + int colorsWanted, gdImagePtr *cimP); -gdImagePtr gdImageCreatePaletteFromTrueColor (gdImagePtr im, int dither, int colorsWanted) -{ +/** + * Function: gdImageCreatePaletteFromTrueColor + * + * Creates a new palette image from a truecolor image + * + * Parameters: + * im - The image. + * dither - Whether dithering should be applied. + * colorsWanted - The number of desired palette entries. + * + * Returns: + * A newly create palette image; NULL on failure. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(gdImagePtr) +gdImageCreatePaletteFromTrueColor(gdImagePtr im, int dither, int colorsWanted) { gdImagePtr nim; if (TRUE == gdImageTrueColorToPaletteBody(im, dither, colorsWanted, &nim)) { return nim; @@ -1437,243 +1440,418 @@ gdImagePtr gdImageCreatePaletteFromTrueColor (gdImagePtr im, int dither, int col return NULL; } -int gdImageTrueColorToPalette (gdImagePtr im, int dither, int colorsWanted) -{ +/** + * Function: gdImageTrueColorToPalette + * + * Converts a truecolor image to a palette image + * + * Parameters: + * im - The image. + * dither - Whether dithering should be applied. + * colorsWanted - The number of desired palette entries. + * + * Returns: + * Non-zero if the conversion succeeded, zero otherwise. + * + * See also: + * - + * - + * - + */ +BGD_DECLARE(int) +gdImageTrueColorToPalette(gdImagePtr im, int dither, int colorsWanted) { return gdImageTrueColorToPaletteBody(im, dither, colorsWanted, 0); } -static void free_truecolor_image_data(gdImagePtr oim) -{ +#ifdef HAVE_LIBIMAGEQUANT +/** + LIQ library needs pixels in RGBA order with alpha 0-255 (opaque 255). + This callback is run whenever source rows need to be converted from GD's + format. +*/ +static void convert_gdpixel_to_rgba(liq_color output_row[], int y, int width, + void *userinfo) { + gdImagePtr oim = userinfo; + int x; + for (x = 0; x < width; x++) { + int pixel = input_buf[y][x]; + output_row[x].r = gdTrueColorGetRed(pixel) * 255 / gdRedMax; + output_row[x].g = gdTrueColorGetGreen(pixel) * 255 / gdGreenMax; + output_row[x].b = gdTrueColorGetBlue(pixel) * 255 / gdBlueMax; + if (oim->transparent >= 0 && pixel == oim->transparent) { + output_row[x].a = 0; + } else { + int alpha = gdTrueColorGetAlpha(pixel); + if (gdAlphaOpaque < gdAlphaTransparent) { + alpha = gdAlphaTransparent - alpha; + } + output_row[x].a = alpha * 255 / gdAlphaMax; + } + } +} +#endif + +static int ensure_transparent_palette_entry(gdImagePtr im, + int transparentColor) { + int transparent; + + if (transparentColor < 0) { + return -1; + } + + transparent = im->transparent; + if (transparent >= 0 && transparent < im->colorsTotal) { + im->alpha[transparent] = gdAlphaTransparent; + return transparent; + } + + if (im->colorsTotal >= gdMaxColors) { + return -1; + } + + transparent = im->colorsTotal; + im->red[transparent] = gdTrueColorGetRed(transparentColor); + im->green[transparent] = gdTrueColorGetGreen(transparentColor); + im->blue[transparent] = gdTrueColorGetBlue(transparentColor); + im->alpha[transparent] = gdAlphaTransparent; + im->open[transparent] = 0; + im->transparent = transparent; + im->colorsTotal++; + + return transparent; +} + +static void remap_transparent_pixels(gdImagePtr src, gdImagePtr dst, + int transparentColor, + int transparentIndex) { + int x, y; + + if (transparentColor < 0 || transparentIndex < 0) { + return; + } + + for (y = 0; y < src->sy; y++) { + for (x = 0; x < src->sx; x++) { + if (src->tpixels[y][x] == transparentColor) { + dst->pixels[y][x] = transparentIndex; + } + } + } +} + +static void copy_palette_image_data(gdImagePtr dst, gdImagePtr src) { + int i, y; + + for (y = 0; y < src->sy; y++) { + memcpy(dst->pixels[y], src->pixels[y], src->sx); + } + + dst->trueColor = 0; + dst->colorsTotal = src->colorsTotal; + dst->transparent = src->transparent; + for (i = 0; i < gdMaxColors; i++) { + dst->red[i] = src->red[i]; + dst->green[i] = src->green[i]; + dst->blue[i] = src->blue[i]; + dst->alpha[i] = src->alpha[i]; + dst->open[i] = src->open[i]; + } +} + +static void free_truecolor_image_data(gdImagePtr oim) { int i; oim->trueColor = 0; /* Junk the truecolor pixels */ - for (i = 0; i < oim->sy; i++) - { + for (i = 0; i < oim->sy; i++) { gdFree (oim->tpixels[i]); } gdFree (oim->tpixels); oim->tpixels = 0; } +#ifdef HAVE_LIBIMAGEQUANT +/* liq requires 16 byte aligned heap memory */ +static void *malloc16(size_t size) { +#ifndef _WIN32 + void *p; + return posix_memalign(&p, 16, size) == 0 ? p : NULL; +#else + return _aligned_malloc(size, 16); +#endif +} +static void free16(void *ptr) { +#ifndef _WIN32 + free(ptr); +#else + _aligned_free(ptr); +#endif +} +#endif + /* * Module initialization routine for 2-pass color quantization. */ -static int gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colorsWanted, gdImagePtr *cimP) -{ - my_cquantize_ptr cquantize = NULL; - int i, conversionSucceeded=0; - - /* Allocate the JPEG palette-storage */ - size_t arraysize; - int maxColors = gdMaxColors; - gdImagePtr nim; - if (cimP) { - nim = gdImageCreate(oim->sx, oim->sy); - *cimP = nim; - if (!nim) { - return FALSE; - } - } else { - nim = oim; - } - if (!oim->trueColor) - { - /* (Almost) nothing to do! */ - if (cimP) { - gdImageCopy(nim, oim, 0, 0, 0, 0, oim->sx, oim->sy); - *cimP = nim; - } - return TRUE; - } +static int gdImageTrueColorToPaletteBody(gdImagePtr oim, int dither, + int colorsWanted, gdImagePtr *cimP) { + my_cquantize_ptr cquantize = NULL; + int i, conversionSucceeded = 0; + int transparentColor; - /* If we have a transparent color (the alphaless mode of transparency), we - * must reserve a palette entry for it at the end of the palette. */ - if (oim->transparent >= 0) - { - maxColors--; - } - if (colorsWanted > maxColors) - { - colorsWanted = maxColors; - } - if (!cimP) { - nim->pixels = gdCalloc (oim->sy, sizeof (unsigned char *)); - if (!nim->pixels) - { - /* No can do */ - goto outOfMemory; - } - for (i = 0; (i < nim->sy); i++) - { - nim->pixels[i] = gdCalloc (oim->sx, sizeof (unsigned char *)); - if (!nim->pixels[i]) - { - goto outOfMemory; - } - } - } + /* Allocate the JPEG palette-storage */ + size_t arraysize; + int maxColors = gdMaxColors; + gdImagePtr nim; - cquantize = (my_cquantize_ptr) gdCalloc (1, sizeof (my_cquantizer)); - if (!cquantize) - { - /* No can do */ - goto outOfMemory; - } - cquantize->fserrors = NULL; /* flag optional arrays not allocated */ - cquantize->error_limiter = NULL; - - - /* Allocate the histogram/inverse colormap storage */ - cquantize->histogram = (hist3d) safe_emalloc (HIST_C0_ELEMS, sizeof (hist2d), 0); - for (i = 0; i < HIST_C0_ELEMS; i++) - { - cquantize->histogram[i] = - (hist2d) safe_emalloc (HIST_C1_ELEMS * HIST_C2_ELEMS, sizeof (histcell), 0); - if (!cquantize->histogram[i]) - { - goto outOfMemory; + if (cimP) { + nim = gdImageCreate(oim->sx, oim->sy); + *cimP = nim; + if (!nim) { + return FALSE; + } + } else { + nim = oim; } - } - cquantize->fserrors = (FSERRPTR) safe_emalloc (3, sizeof (FSERROR), 0); - init_error_limit (oim, nim, cquantize); - arraysize = (size_t) ((nim->sx + 2) * (3 * sizeof (FSERROR))); - /* Allocate Floyd-Steinberg workspace. */ - cquantize->fserrors = gdRealloc(cquantize->fserrors, arraysize); - memset(cquantize->fserrors, 0, arraysize); - if (!cquantize->fserrors) - { - goto outOfMemory; - } - cquantize->on_odd_row = FALSE; - - /* Do the work! */ - zeroHistogram (cquantize->histogram); - prescan_quantize (oim, nim, cquantize); - /* TBB 2.0.5: pass colorsWanted, not 256! */ - select_colors (oim, nim, cquantize, colorsWanted); - zeroHistogram (cquantize->histogram); - if (dither) - { - pass2_fs_dither (oim, nim, cquantize); - } - else - { - pass2_no_dither (oim, nim, cquantize); - } -#if 0 /* 2.0.12; we no longer attempt full alpha in palettes */ - if (cquantize->transparentIsPresent) - { - int mt = -1; - int mtIndex = -1; - for (i = 0; (i < im->colorsTotal); i++) - { - if (im->alpha[i] > mt) - { - mtIndex = i; - mt = im->alpha[i]; - } + if (!oim->trueColor) { + /* (Almost) nothing to do! */ + if (cimP) { + gdImageCopy(nim, oim, 0, 0, 0, 0, oim->sx, oim->sy); + *cimP = nim; + } + return TRUE; } - for (i = 0; (i < im->colorsTotal); i++) - { - if (im->alpha[i] == mt) - { - im->alpha[i] = gdAlphaTransparent; - } + + transparentColor = oim->transparent; + + /* If we have a transparent color (the alphaless mode of transparency), we + * must reserve a palette entry for it at the end of the palette. */ + if (transparentColor >= 0) { + maxColors--; } - } - if (cquantize->opaqueIsPresent) - { - int mo = 128; - int moIndex = -1; - for (i = 0; (i < im->colorsTotal); i++) - { - if (im->alpha[i] < mo) - { - moIndex = i; - mo = im->alpha[i]; - } + if (colorsWanted > maxColors) { + colorsWanted = maxColors; } - for (i = 0; (i < im->colorsTotal); i++) - { - if (im->alpha[i] == mo) - { - im->alpha[i] = gdAlphaOpaque; - } + if (!cimP) { + nim->pixels = gdCalloc(sizeof(unsigned char *), oim->sy); + if (!nim->pixels) { + /* No can do */ + goto outOfMemory; + } + for (i = 0; (i < nim->sy); i++) { + nim->pixels[i] = + (unsigned char *)gdCalloc(sizeof(unsigned char), oim->sx); + if (!nim->pixels[i]) { + goto outOfMemory; + } + } + } + + if (oim->paletteQuantizationMethod == GD_QUANT_NEUQUANT) { + if (cimP) { /* NeuQuant always creates a copy, so the new blank image + can't be used */ + gdImageDestroy(nim); + } + nim = gdImageNeuQuant( + oim, colorsWanted, + oim->paletteQuantizationSpeed ? oim->paletteQuantizationSpeed : 2); + if (!nim) { + return FALSE; + } + if (transparentColor >= 0) { + int transparent = + ensure_transparent_palette_entry(nim, transparentColor); + if (transparent < 0) { + gdImageDestroy(nim); + return FALSE; + } + remap_transparent_pixels(oim, nim, transparentColor, transparent); + } + if (cimP) { + *cimP = nim; + return TRUE; + } + free_truecolor_image_data(oim); + copy_palette_image_data(oim, nim); + gdImageDestroy(nim); + return TRUE; + } + +#ifdef HAVE_LIBIMAGEQUANT + if (oim->paletteQuantizationMethod == GD_QUANT_DEFAULT || + oim->paletteQuantizationMethod == GD_QUANT_LIQ) { + liq_attr *attr = liq_attr_create_with_allocator(malloc16, free16); + liq_image *image; + liq_result *remap; + int remapped_ok = 0; + + liq_set_max_colors(attr, colorsWanted); + + /* by default make it fast to match speed of previous implementation */ + liq_set_speed(attr, oim->paletteQuantizationSpeed + ? oim->paletteQuantizationSpeed + : 9); + if (oim->paletteQuantizationMaxQuality) { + liq_set_quality(attr, oim->paletteQuantizationMinQuality, + oim->paletteQuantizationMaxQuality); + } + image = liq_image_create_custom(attr, convert_gdpixel_to_rgba, oim, + oim->sx, oim->sy, 0); + remap = liq_quantize_image(attr, image); + if (!remap) { /* minimum quality not met, leave image unmodified */ + liq_image_destroy(image); + liq_attr_destroy(attr); + goto outOfMemory; + } + + liq_set_dithering_level(remap, dither ? 1 : 0); + if (LIQ_OK == liq_write_remapped_image_rows(remap, image, output_buf)) { + remapped_ok = 1; + const liq_palette *pal = liq_get_palette(remap); + nim->transparent = -1; + unsigned int icolor; + for (icolor = 0; icolor < pal->count; icolor++) { + nim->open[icolor] = 0; + nim->red[icolor] = pal->entries[icolor].r * gdRedMax / 255; + nim->green[icolor] = pal->entries[icolor].g * gdGreenMax / 255; + nim->blue[icolor] = pal->entries[icolor].b * gdBlueMax / 255; + int alpha = pal->entries[icolor].a * gdAlphaMax / 255; + if (gdAlphaOpaque < gdAlphaTransparent) { + alpha = gdAlphaTransparent - alpha; + } + nim->alpha[icolor] = alpha; + if (nim->transparent == -1 && alpha == gdAlphaTransparent) { + nim->transparent = icolor; + } + } + nim->colorsTotal = pal->count; + if (transparentColor >= 0) { + int transparent = + ensure_transparent_palette_entry(nim, transparentColor); + if (transparent >= 0) { + remap_transparent_pixels(oim, nim, transparentColor, + transparent); + } else { + remapped_ok = 0; + } + } + } + liq_result_destroy(remap); + liq_image_destroy(image); + liq_attr_destroy(attr); + + if (remapped_ok) { + if (!cimP) { + free_truecolor_image_data(oim); + } + return TRUE; + } } - } #endif - /* If we had a 'transparent' color, increment the color count so it's - * officially in the palette and convert the transparent variable to point to - * an index rather than a color (Its data already exists and transparent - * pixels have already been mapped to it by this point, it is done late as to - * avoid color matching / dithering with it). */ - if (oim->transparent >= 0) - { - nim->transparent = nim->colorsTotal; - nim->colorsTotal++; - } + cquantize = (my_cquantize_ptr)gdCalloc(sizeof(my_cquantizer), 1); + if (!cquantize) { + /* No can do */ + goto outOfMemory; + } + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d)gdMalloc(HIST_C0_ELEMS * sizeof(hist2d)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + cquantize->histogram[i] = + (hist2d)gdMalloc(HIST_C1_ELEMS * HIST_C2_ELEMS * sizeof(histcell)); + if (!cquantize->histogram[i]) { + goto outOfMemory; + } + } - /* Success! Get rid of the truecolor image data. */ - conversionSucceeded = TRUE; - if (!cimP) - { - free_truecolor_image_data(oim); - } + cquantize->fserrors = (FSERRPTR)gdMalloc(3 * sizeof(FSERROR)); + init_error_limit(oim, nim, cquantize); + arraysize = (size_t)((nim->sx + 2) * (3 * sizeof(FSERROR))); + /* Allocate Floyd-Steinberg workspace. */ + cquantize->fserrors = gdReallocEx(cquantize->fserrors, arraysize); + if (!cquantize->fserrors) { + goto outOfMemory; + } + memset(cquantize->fserrors, 0, arraysize); + cquantize->on_odd_row = FALSE; + + /* Do the work! */ + zeroHistogram(cquantize->histogram); + prescan_quantize(oim, nim, cquantize); + /* TBB 2.0.5: pass colorsWanted, not 256! */ + select_colors(oim, nim, cquantize, colorsWanted); + zeroHistogram(cquantize->histogram); + if (dither) { + if (cquantize->error_limiter == NULL) { + goto outOfMemory; + } + pass2_fs_dither(oim, nim, cquantize); + } else { + pass2_no_dither(oim, nim, cquantize); + } - goto freeQuantizeData; - /* Tediously free stuff. */ + /* If we had a 'transparent' color, increment the color count so it's + * officially in the palette and convert the transparent variable to point + * to an index rather than a color (Its data already exists and transparent + * pixels have already been mapped to it by this point, it is done late as + * to avoid color matching / dithering with it). */ + if (transparentColor >= 0) { + nim->transparent = nim->colorsTotal; + nim->colorsTotal++; + } + + /* Success! Get rid of the truecolor image data. */ + conversionSucceeded = TRUE; + if (!cimP) { + free_truecolor_image_data(oim); + } + + goto freeQuantizeData; + /* Tediously free stuff. */ outOfMemory: - conversionSucceeded = FALSE; - if (oim->trueColor) - { - if (!cimP) { - /* On failure only */ - for (i = 0; i < nim->sy; i++) - { - if (nim->pixels[i]) - { - gdFree (nim->pixels[i]); - } - } - if (nim->pixels) - { - gdFree (nim->pixels); - } - nim->pixels = 0; - } else { - gdImageDestroy(nim); - *cimP = 0; - } - } + conversionSucceeded = FALSE; + if (oim->trueColor) { + if (!cimP) { + /* On failure only */ + if (nim->pixels) { + for (i = 0; i < nim->sy; i++) { + if (nim->pixels[i]) { + gdFree(nim->pixels[i]); + } + } + gdFree(nim->pixels); + } + nim->pixels = NULL; + } else { + gdImageDestroy(nim); + *cimP = 0; + } + } + freeQuantizeData: - for (i = 0; i < HIST_C0_ELEMS; i++) - { - if (cquantize->histogram[i]) - { - gdFree (cquantize->histogram[i]); + if (cquantize) { + if (cquantize->histogram) { + for (i = 0; i < HIST_C0_ELEMS; i++) { + if (cquantize->histogram[i]) { + gdFree(cquantize->histogram[i]); + } + } + } + if (cquantize->histogram) { + gdFree(cquantize->histogram); + } + if (cquantize->fserrors) { + gdFree(cquantize->fserrors); + } + if (cquantize->error_limiter_storage) { + gdFree(cquantize->error_limiter_storage); + } + gdFree(cquantize); } - } - if (cquantize->histogram) - { - gdFree (cquantize->histogram); - } - if (cquantize->fserrors) - { - gdFree (cquantize->fserrors); - } - if (cquantize->error_limiter_storage) - { - gdFree (cquantize->error_limiter_storage); - } - if (cquantize) - { - gdFree (cquantize); - } - return conversionSucceeded; + return conversionSucceeded; } - #endif diff --git a/ext/gd/libgd/gd_transform.c b/ext/gd/libgd/gd_transform.c index 9051525eecca..78b2ccf13a54 100644 --- a/ext/gd/libgd/gd_transform.c +++ b/ext/gd/libgd/gd_transform.c @@ -1,6 +1,6 @@ #include "gd.h" -void gdImageFlipVertical(gdImagePtr im) +BGD_DECLARE(void) gdImageFlipVertical(gdImagePtr im) { register int x, y; @@ -28,7 +28,7 @@ void gdImageFlipVertical(gdImagePtr im) return; } -void gdImageFlipHorizontal(gdImagePtr im) +BGD_DECLARE(void) gdImageFlipHorizontal(gdImagePtr im) { int x, y; @@ -64,7 +64,7 @@ void gdImageFlipHorizontal(gdImagePtr im) } } -void gdImageFlipBoth(gdImagePtr im) +BGD_DECLARE(void) gdImageFlipBoth(gdImagePtr im) { gdImageFlipVertical(im); gdImageFlipHorizontal(im); diff --git a/ext/gd/libgd/gd_uhdr.c b/ext/gd/libgd/gd_uhdr.c new file mode 100644 index 000000000000..f3e4558b704d --- /dev/null +++ b/ext/gd/libgd/gd_uhdr.c @@ -0,0 +1,1365 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/** + * File: UltraHDR IO + * + * Read and write UltraHDR images with gain map preservation. + */ + +#include +#include +#include +#include + +#include "gd.h" +#include "gd_errors.h" +#include "gd_intern.h" +#include "gdhelpers.h" + +#ifdef HAVE_LIBUHDR +#include +#endif + +typedef enum { + GD_UHDR_OP_RESIZE = 0, + GD_UHDR_OP_CROP, + GD_UHDR_OP_ROTATE, + GD_UHDR_OP_MIRROR +} gdUhdrOpType; + +typedef struct { + gdUhdrOpType type; + int p1; + int p2; + int p3; + int p4; +} gdUhdrOp; + +struct gdUhdrImageStruct { + int format; + int width; + int height; + int has_gain_map; + void *blob; + int blob_size; + gdUhdrOp *ops; + int op_count; + int op_capacity; +}; + +static void gdUhdrSetError(gdUhdrErrorPtr err, int code, int provider_code, + const char *message) { + if (!err) { + return; + } + + err->code = code; + err->provider_code = provider_code; + err->message[0] = '\0'; + if (message) { + strncpy(err->message, message, sizeof(err->message) - 1); + err->message[sizeof(err->message) - 1] = '\0'; + } +} + +#ifdef HAVE_LIBUHDR + +static int gdUhdrIsSupportedFormat(int format) { + return format == GD_UHDR_FORMAT_JPEG; +} + +static int gdUhdrIntAddOverflow(int a, int b) { + if (a < 0 || b < 0) { + return 1; + } + + if (a > INT_MAX - b) { + return 1; + } + + return 0; +} + +static int gdUhdrReadAllFromCtx(gdIOCtxPtr ctx, void **out_data, + int *out_size) { + const int step = 4096; + unsigned char buf[4096]; + unsigned char *data = NULL; + int size = 0; + + if (!ctx || !out_data || !out_size) { + return GD_UHDR_E_INVALID; + } + + for (;;) { + int n = gdGetBuf(buf, step, ctx); + int new_size; + if (n <= 0) { + break; + } + + if (gdUhdrIntAddOverflow(size, n)) { + gd_error("gd-uhdr read size overflow: size=%d n=%d\n", size, n); + gdFree(data); + return GD_UHDR_E_INVALID; + } + + new_size = size + n; + + { + unsigned char *tmp = + (unsigned char *)gdRealloc(data, (size_t)new_size); + if (!tmp) { + gd_error("gd-uhdr realloc failed: requested=%d\n", new_size); + gdFree(data); + return GD_UHDR_E_DECODE; + } + data = tmp; + } + + memcpy(data + size, buf, (size_t)n); + size = new_size; + } + + if (!data || size <= 0) { + gdFree(data); + return GD_UHDR_E_DECODE; + } + + *out_data = data; + *out_size = size; + return GD_UHDR_SUCCESS; +} + +static void gdUhdrInitCompressedImage(uhdr_compressed_image_t *image, + void *data, int size) { + memset(image, 0, sizeof(*image)); + image->data = data; + image->data_sz = (size_t)size; + image->capacity = (size_t)size; + image->cg = UHDR_CG_UNSPECIFIED; + image->ct = UHDR_CT_UNSPECIFIED; + image->range = UHDR_CR_FULL_RANGE; +} + +static int gdUhdrCopyMetadataProfile(gdImageMetadata **dst, + const gdImageMetadata *src, + const char *key, gdUhdrErrorPtr err) { + const unsigned char *profile; + size_t profile_size; + int status; + + profile = gdImageMetadataGetProfile(src, key, &profile_size); + if (!profile) { + return GD_UHDR_SUCCESS; + } + + if (!*dst) { + *dst = gdImageMetadataCreate(); + if (!*dst) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Out of memory copying JPEG metadata"); + return GD_UHDR_E_ENCODE; + } + } + + status = gdImageMetadataSetProfile(*dst, key, profile, profile_size); + if (status != GD_META_OK) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, status, + "Failed to copy JPEG metadata"); + return GD_UHDR_E_ENCODE; + } + + return GD_UHDR_SUCCESS; +} + +static int gdUhdrCreateJpegMetadataFromBlock(uhdr_mem_block_t *block, + int copy_exif, int copy_icc, + gdImageMetadata **out, + gdUhdrErrorPtr err) { + gdImageMetadata *src_metadata; + gdImagePtr decoded; + int status; + + *out = NULL; + if (!block || !block->data || block->data_sz == 0) { + return GD_UHDR_SUCCESS; + } + if (block->data_sz > (size_t)INT_MAX) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "JPEG metadata block is too large"); + return GD_UHDR_E_INVALID; + } + + src_metadata = gdImageMetadataCreate(); + if (!src_metadata) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Out of memory reading JPEG metadata"); + return GD_UHDR_E_ENCODE; + } + + decoded = gdImageCreateFromJpegPtrWithMetadata((int)block->data_sz, + block->data, src_metadata); + if (!decoded) { + gdImageMetadataFree(src_metadata); + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Failed to read JPEG metadata"); + return GD_UHDR_E_DECODE; + } + gdImageDestroy(decoded); + + if (copy_exif) { + status = gdUhdrCopyMetadataProfile(out, src_metadata, "exif", err); + if (status != GD_UHDR_SUCCESS) { + gdImageMetadataFree(src_metadata); + return status; + } + } + if (copy_icc) { + status = gdUhdrCopyMetadataProfile(out, src_metadata, "icc", err); + if (status != GD_UHDR_SUCCESS) { + gdImageMetadataFree(src_metadata); + return status; + } + } + + gdImageMetadataFree(src_metadata); + return GD_UHDR_SUCCESS; +} + +static gdImagePtr gdUhdrCreateGdImageFromJpegBlock(uhdr_mem_block_t *block, + const char *label, + gdUhdrErrorPtr err) { + gdImagePtr image; + + if (!block || !block->data || block->data_sz == 0 || + block->data_sz > (size_t)INT_MAX) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Invalid compressed UltraHDR component"); + return NULL; + } + + image = gdImageCreateFromJpegPtr((int)block->data_sz, block->data); + if (!image) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + label && label[0] + ? label + : "Failed to decode UltraHDR JPEG component"); + return NULL; + } + + return image; +} + +static int gdUhdrScaleValue(int value, int from_extent, int to_extent, + int *out) { + long long scaled; + + if (!out || value < 0 || from_extent <= 0 || to_extent <= 0) { + return 0; + } + + scaled = ((long long)value * (long long)to_extent + from_extent / 2) / + from_extent; + if (scaled < 0 || scaled > INT_MAX) { + return 0; + } + + *out = (int)scaled; + return 1; +} + +static int gdUhdrReplaceImage(gdImagePtr *image, gdImagePtr replacement, + gdUhdrErrorPtr err) { + if (!replacement) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to transform UltraHDR image component"); + return GD_UHDR_E_ENCODE; + } + + gdImageDestroy(*image); + *image = replacement; + return GD_UHDR_SUCCESS; +} + +static gdImagePtr gdUhdrScaleImage(gdImagePtr image, unsigned int width, + unsigned int height) { + /* Mitchell uses GD's two-pass scaler and reduces downscale aliasing. */ + if (!gdImageSetInterpolationMethod(image, GD_MITCHELL)) { + return NULL; + } + + return gdImageScale(image, width, height); +} + +static int gdUhdrApplyGdOps(gdImagePtr *base_image, gdImagePtr *gainmap_image, + gdUhdrImagePtr im, gdUhdrErrorPtr err) { + int i; + + if (!base_image || !*base_image || !gainmap_image || !*gainmap_image || + !im) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Invalid UltraHDR transform state"); + return GD_UHDR_E_INVALID; + } + + for (i = 0; i < im->op_count; i++) { + gdUhdrOp *op = &im->ops[i]; + int base_w = gdImageSX(*base_image); + int base_h = gdImageSY(*base_image); + int gain_w = gdImageSX(*gainmap_image); + int gain_h = gdImageSY(*gainmap_image); + int status; + + switch (op->type) { + case GD_UHDR_OP_RESIZE: { + int scaled_gain_w; + int scaled_gain_h; + + if (!gdUhdrScaleValue(op->p1, base_w, gain_w, &scaled_gain_w) || + !gdUhdrScaleValue(op->p2, base_h, gain_h, &scaled_gain_h)) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Invalid UltraHDR resize operation"); + return GD_UHDR_E_INVALID; + } + if (scaled_gain_w <= 0) { + scaled_gain_w = 1; + } + if (scaled_gain_h <= 0) { + scaled_gain_h = 1; + } + + status = gdUhdrReplaceImage(base_image, + gdUhdrScaleImage(*base_image, + (unsigned int)op->p1, + (unsigned int)op->p2), + err); + if (status != GD_UHDR_SUCCESS) { + return status; + } + status = gdUhdrReplaceImage( + gainmap_image, + gdUhdrScaleImage(*gainmap_image, (unsigned int)scaled_gain_w, + (unsigned int)scaled_gain_h), + err); + if (status != GD_UHDR_SUCCESS) { + return status; + } + break; + } + case GD_UHDR_OP_CROP: { + gdRect base_crop; + gdRect gain_crop; + int gain_left; + int gain_right; + int gain_top; + int gain_bottom; + + if (op->p1 < 0 || op->p3 < 0 || op->p2 <= op->p1 || + op->p4 <= op->p3 || op->p2 > base_w || op->p4 > base_h) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Invalid UltraHDR crop operation"); + return GD_UHDR_E_INVALID; + } + if (!gdUhdrScaleValue(op->p1, base_w, gain_w, &gain_left) || + !gdUhdrScaleValue(op->p2, base_w, gain_w, &gain_right) || + !gdUhdrScaleValue(op->p3, base_h, gain_h, &gain_top) || + !gdUhdrScaleValue(op->p4, base_h, gain_h, &gain_bottom) || + gain_right <= gain_left || gain_bottom <= gain_top) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Invalid UltraHDR gain map crop operation"); + return GD_UHDR_E_INVALID; + } + + base_crop.x = op->p1; + base_crop.y = op->p3; + base_crop.width = op->p2 - op->p1; + base_crop.height = op->p4 - op->p3; + gain_crop.x = gain_left; + gain_crop.y = gain_top; + gain_crop.width = gain_right - gain_left; + gain_crop.height = gain_bottom - gain_top; + + status = gdUhdrReplaceImage( + base_image, gdImageCrop(*base_image, &base_crop), err); + if (status != GD_UHDR_SUCCESS) { + return status; + } + status = gdUhdrReplaceImage( + gainmap_image, gdImageCrop(*gainmap_image, &gain_crop), err); + if (status != GD_UHDR_SUCCESS) { + return status; + } + break; + } + case GD_UHDR_OP_ROTATE: { + gdImagePtr rotated_base = NULL; + gdImagePtr rotated_gainmap = NULL; + + if (op->p1 == 90) { + rotated_base = gdImageRotate90(*base_image, 0); + rotated_gainmap = gdImageRotate90(*gainmap_image, 0); + } else if (op->p1 == 180) { + rotated_base = gdImageRotate180(*base_image, 0); + rotated_gainmap = gdImageRotate180(*gainmap_image, 0); + } else if (op->p1 == 270) { + rotated_base = gdImageRotate270(*base_image, 0); + rotated_gainmap = gdImageRotate270(*gainmap_image, 0); + } + + status = gdUhdrReplaceImage(base_image, rotated_base, err); + if (status != GD_UHDR_SUCCESS) { + if (rotated_gainmap) { + gdImageDestroy(rotated_gainmap); + } + return status; + } + status = gdUhdrReplaceImage(gainmap_image, rotated_gainmap, err); + if (status != GD_UHDR_SUCCESS) { + return status; + } + break; + } + case GD_UHDR_OP_MIRROR: + if (op->p1 == GD_UHDR_MIRROR_HORIZONTAL) { + gdImageFlipHorizontal(*base_image); + gdImageFlipHorizontal(*gainmap_image); + } else { + gdImageFlipVertical(*base_image); + gdImageFlipVertical(*gainmap_image); + } + break; + default: + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Unknown queued UltraHDR operation"); + return GD_UHDR_E_INVALID; + } + } + + return GD_UHDR_SUCCESS; +} + +static int gdUhdrEncodeJpegComponent(gdImagePtr image, int quality, + const gdImageMetadata *metadata, + int no_subsampling, void **out_data, + int *out_size, gdUhdrErrorPtr err) { + void *jpeg; + int jpeg_size = 0; + + if (no_subsampling) { + jpeg = gdImageJpegPtrWithMetadataNoSubsampling(image, &jpeg_size, + quality, metadata); + } else { + jpeg = gdImageJpegPtrWithMetadata(image, &jpeg_size, quality, metadata); + } + if (!jpeg || jpeg_size <= 0) { + gdFree(jpeg); + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to encode UltraHDR JPEG component"); + return GD_UHDR_E_ENCODE; + } + + *out_data = jpeg; + *out_size = jpeg_size; + return GD_UHDR_SUCCESS; +} + +static int gdUhdrQueueOp(gdUhdrImagePtr im, gdUhdrOpType type, int p1, int p2, + int p3, int p4) { + if (im->op_count == im->op_capacity) { + int new_cap = im->op_capacity == 0 ? 8 : im->op_capacity * 2; + gdUhdrOp *tmp; + + if (overflow2(new_cap, (int)sizeof(gdUhdrOp))) { + return GD_UHDR_E_INVALID; + } + + tmp = + (gdUhdrOp *)gdRealloc(im->ops, (size_t)new_cap * sizeof(gdUhdrOp)); + if (!tmp) { + return GD_UHDR_E_INVALID; + } + + im->ops = tmp; + im->op_capacity = new_cap; + } + + im->ops[im->op_count].type = type; + im->ops[im->op_count].p1 = p1; + im->ops[im->op_count].p2 = p2; + im->ops[im->op_count].p3 = p3; + im->ops[im->op_count].p4 = p4; + im->op_count++; + + return GD_UHDR_SUCCESS; +} + +static gdUhdrImagePtr gdUhdrImageCreateFromData(void *data, int size, + int format, + gdUhdrErrorPtr err) { + gdUhdrImagePtr im; + uhdr_codec_private_t *dec; + uhdr_compressed_image_t input; + uhdr_error_info_t rc; + + if (!data || size <= 0 || !gdUhdrIsSupportedFormat(format)) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid UltraHDR input"); + return NULL; + } + + if (!is_uhdr_image(data, size)) { + gdUhdrSetError(err, GD_UHDR_E_UNSUPPORTED, 0, + "Input is not a valid UltraHDR image"); + return NULL; + } + + dec = uhdr_create_decoder(); + if (!dec) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Failed to create UltraHDR decoder"); + return NULL; + } + + input.data = data; + input.data_sz = (size_t)size; + input.capacity = (size_t)size; + input.cg = UHDR_CG_UNSPECIFIED; + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_FULL_RANGE; + + rc = uhdr_dec_set_image(dec, &input); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_dec_set_image failed"); + uhdr_release_decoder(dec); + return NULL; + } + + rc = uhdr_dec_probe(dec); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_dec_probe failed"); + uhdr_release_decoder(dec); + return NULL; + } + + im = (gdUhdrImagePtr)gdCalloc(1, sizeof(*im)); + if (!im) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, "Out of memory"); + uhdr_release_decoder(dec); + return NULL; + } + + im->blob = gdMalloc((size_t)size); + if (!im->blob) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, "Out of memory"); + gdFree(im); + uhdr_release_decoder(dec); + return NULL; + } + + memcpy(im->blob, data, (size_t)size); + im->blob_size = size; + im->format = format; + im->width = uhdr_dec_get_image_width(dec); + im->height = uhdr_dec_get_image_height(dec); + im->has_gain_map = (uhdr_dec_get_gainmap_width(dec) > 0 && + uhdr_dec_get_gainmap_height(dec) > 0) + ? 1 + : 0; + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + uhdr_release_decoder(dec); + return im; +} + +#endif + +#ifndef HAVE_LIBUHDR + +static int gdUhdrUnavailableCode(void) { return GD_UHDR_NOT_AVAILABLE; } + +static const char *gdUhdrUnavailableMessage(void) { + return "UltraHDR support is not enabled in this build"; +} + +#endif + +/* + Function: gdUhdrIsAvailable + + Returns whether UltraHDR support is available in this libgd build. + + Returns: + + 1 if available, 0 otherwise. +*/ +BGD_DECLARE(int) gdUhdrIsAvailable(void) { +#ifdef HAVE_LIBUHDR + return 1; +#else + return 0; +#endif +} + +/* + Function: gdUhdrImageCreateFromFile + + Loads an UltraHDR image from a file. + + Variants: + + loads from a . + + loads from memory. + + Parameters: + + filename - input file path. + format - input format (currently ). + err - optional output error details. + + Returns: + + A new on success, or NULL on error. +*/ +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromFile(const char *filename, int format, + gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + FILE *fp; + gdIOCtxPtr in; + gdUhdrImagePtr im; + + if (!filename) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "filename must not be NULL"); + return NULL; + } + + fp = fopen(filename, "rb"); + if (!fp) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, "Failed to open input file"); + return NULL; + } + + in = gdNewFileCtx(fp); + if (!in) { + fclose(fp); + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, "Failed to create IO context"); + return NULL; + } + + im = gdUhdrImageCreateFromCtx(in, format, err); + in->gd_free(in); + fclose(fp); + return im; +#else + ARG_NOT_USED(filename); + ARG_NOT_USED(format); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return NULL; +#endif +} + +/* + Function: gdUhdrImageCreateFromCtx + + See . +*/ +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromCtx(gdIOCtxPtr ctx, int format, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + void *data = NULL; + int size = 0; + int rc; + gdUhdrImagePtr im; + + if (!gdUhdrIsSupportedFormat(format)) { + gdUhdrSetError(err, GD_UHDR_E_UNSUPPORTED, 0, + "Unsupported UltraHDR format"); + return NULL; + } + + rc = gdUhdrReadAllFromCtx(ctx, &data, &size); + if (rc != GD_UHDR_SUCCESS) { + gdUhdrSetError(err, rc, 0, "Failed to read UltraHDR stream"); + return NULL; + } + + im = gdUhdrImageCreateFromData(data, size, format, err); + gdFree(data); + return im; +#else + ARG_NOT_USED(ctx); + ARG_NOT_USED(format); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return NULL; +#endif +} + +/* + Function: gdUhdrImageCreateFromPtr + + See . +*/ +BGD_DECLARE(gdUhdrImagePtr) +gdUhdrImageCreateFromPtr(int size, void *data, int format, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + if (!gdUhdrIsSupportedFormat(format)) { + gdUhdrSetError(err, GD_UHDR_E_UNSUPPORTED, 0, + "Unsupported UltraHDR format"); + return NULL; + } + + return gdUhdrImageCreateFromData(data, size, format, err); +#else + ARG_NOT_USED(size); + ARG_NOT_USED(data); + ARG_NOT_USED(format); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return NULL; +#endif +} + +/* + Function: gdUhdrImageDestroy + + Releases an UltraHDR image created by or its + variants. +*/ +BGD_DECLARE(void) gdUhdrImageDestroy(gdUhdrImagePtr im) { + if (!im) { + return; + } + + gdFree(im->ops); + gdFree(im->blob); + gdFree(im); +} + +/* + Function: gdUhdrImageWidth + + Returns the image width in pixels. +*/ +BGD_DECLARE(int) gdUhdrImageWidth(gdUhdrImagePtr im) { + if (!im) { + return 0; + } + return im->width; +} + +/* + Function: gdUhdrImageHeight + + Returns the image height in pixels. +*/ +BGD_DECLARE(int) gdUhdrImageHeight(gdUhdrImagePtr im) { + if (!im) { + return 0; + } + return im->height; +} + +/* + Function: gdUhdrImageHasGainMap + + Returns nonzero when the loaded image contains a gain map. +*/ +BGD_DECLARE(int) gdUhdrImageHasGainMap(gdUhdrImagePtr im) { + if (!im) { + return 0; + } + return im->has_gain_map; +} + +/* + Function: gdUhdrImageResize + + Queues a resize operation to be applied at save time. +*/ +BGD_DECLARE(int) +gdUhdrImageResize(gdUhdrImagePtr im, int width, int height, + gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + int rc; + if (!im || width <= 0 || height <= 0) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid resize arguments"); + return GD_UHDR_E_INVALID; + } + + rc = gdUhdrQueueOp(im, GD_UHDR_OP_RESIZE, width, height, 0, 0); + if (rc != GD_UHDR_SUCCESS) { + gdUhdrSetError(err, rc, 0, "Failed to queue resize effect"); + return rc; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return GD_UHDR_SUCCESS; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(width); + ARG_NOT_USED(height); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageCrop + + Queues a crop operation to be applied at save time. +*/ +BGD_DECLARE(int) +gdUhdrImageCrop(gdUhdrImagePtr im, int left, int top, int width, int height, + gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + int rc; + int right; + int bottom; + + if (!im || left < 0 || top < 0 || width <= 0 || height <= 0) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid crop arguments"); + return GD_UHDR_E_INVALID; + } + + if (gdUhdrIntAddOverflow(left, width) || + gdUhdrIntAddOverflow(top, height)) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid crop arguments"); + return GD_UHDR_E_INVALID; + } + + right = left + width; + bottom = top + height; + + rc = gdUhdrQueueOp(im, GD_UHDR_OP_CROP, left, right, top, bottom); + if (rc != GD_UHDR_SUCCESS) { + gdUhdrSetError(err, rc, 0, "Failed to queue crop effect"); + return rc; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return GD_UHDR_SUCCESS; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(left); + ARG_NOT_USED(top); + ARG_NOT_USED(width); + ARG_NOT_USED(height); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageRotate + + Queues a clockwise rotation (90, 180, or 270 degrees) to be applied at + save time. +*/ +BGD_DECLARE(int) +gdUhdrImageRotate(gdUhdrImagePtr im, int degrees, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + int rc; + if (!im || (degrees != 90 && degrees != 180 && degrees != 270)) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid rotation angle"); + return GD_UHDR_E_INVALID; + } + + rc = gdUhdrQueueOp(im, GD_UHDR_OP_ROTATE, degrees, 0, 0, 0); + if (rc != GD_UHDR_SUCCESS) { + gdUhdrSetError(err, rc, 0, "Failed to queue rotation effect"); + return rc; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return GD_UHDR_SUCCESS; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(degrees); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageMirror + + Queues a mirror operation to be applied at save time. +*/ +BGD_DECLARE(int) +gdUhdrImageMirror(gdUhdrImagePtr im, int axis, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + int rc; + if (!im || (axis != GD_UHDR_MIRROR_HORIZONTAL && + axis != GD_UHDR_MIRROR_VERTICAL)) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid mirror axis"); + return GD_UHDR_E_INVALID; + } + + rc = gdUhdrQueueOp(im, GD_UHDR_OP_MIRROR, axis, 0, 0, 0); + if (rc != GD_UHDR_SUCCESS) { + gdUhdrSetError(err, rc, 0, "Failed to queue mirror effect"); + return rc; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return GD_UHDR_SUCCESS; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(axis); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageFile + + Saves an UltraHDR image to a file path. + + Variants: + + writes via . + + writes to memory. +*/ +BGD_DECLARE(int) +gdUhdrImageFile(gdUhdrImagePtr im, const char *filename, int format, + int quality, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + FILE *fp; + gdIOCtxPtr out; + int rc; + + if (!filename) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "filename must not be NULL"); + return GD_UHDR_E_INVALID; + } + + fp = fopen(filename, "wb"); + if (!fp) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, "Failed to open output file"); + return GD_UHDR_E_ENCODE; + } + + out = gdNewFileCtx(fp); + if (!out) { + fclose(fp); + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, "Failed to create IO context"); + return GD_UHDR_E_ENCODE; + } + + rc = gdUhdrImageCtx(im, out, format, quality, err); + out->gd_free(out); + fclose(fp); + return rc; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(filename); + ARG_NOT_USED(format); + ARG_NOT_USED(quality); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageCtx + + See . +*/ +BGD_DECLARE(int) +gdUhdrImageCtx(gdUhdrImagePtr im, gdIOCtxPtr ctx, int format, int quality, + gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + uhdr_codec_private_t *dec = NULL; + uhdr_codec_private_t *enc = NULL; + uhdr_compressed_image_t source; + uhdr_compressed_image_t base_jpeg; + uhdr_compressed_image_t gainmap_jpeg; + uhdr_error_info_t rc; + uhdr_compressed_image_t *encoded; + uhdr_gainmap_metadata_t *source_metadata; + uhdr_gainmap_metadata_t metadata; + uhdr_mem_block_t *base_block = NULL; + uhdr_mem_block_t *gainmap_block = NULL; + gdImageMetadata *base_metadata = NULL; + gdImageMetadata *gainmap_metadata = NULL; + gdImagePtr base_image = NULL; + gdImagePtr gainmap_image = NULL; + void *base_jpeg_data = NULL; + void *gainmap_jpeg_data = NULL; + int base_jpeg_size = 0; + int gainmap_jpeg_size = 0; + int write_result; + int status = GD_UHDR_SUCCESS; + + if (!im || !ctx || !im->blob || im->blob_size <= 0) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, + "Invalid UltraHDR image or output context"); + return GD_UHDR_E_INVALID; + } + + if (!gdUhdrIsSupportedFormat(format)) { + gdUhdrSetError(err, GD_UHDR_E_UNSUPPORTED, 0, + "Unsupported UltraHDR output format"); + return GD_UHDR_E_UNSUPPORTED; + } + + if (quality < 1 || quality > 95) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "quality must be in [1..95]"); + return GD_UHDR_E_INVALID; + } + + if (im->op_count == 0) { + write_result = gdPutBuf(im->blob, im->blob_size, ctx); + if (write_result != im->blob_size) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to write UltraHDR output"); + return GD_UHDR_E_ENCODE; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return GD_UHDR_SUCCESS; + } + + dec = uhdr_create_decoder(); + enc = uhdr_create_encoder(); + if (!dec || !enc) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to create UltraHDR codec contexts"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + gdUhdrInitCompressedImage(&source, im->blob, im->blob_size); + + rc = uhdr_dec_set_image(dec, &source); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_dec_set_image failed"); + status = GD_UHDR_E_DECODE; + goto cleanup; + } + + rc = uhdr_dec_probe(dec); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_dec_probe failed"); + status = GD_UHDR_E_DECODE; + goto cleanup; + } + + source_metadata = uhdr_dec_get_gainmap_metadata(dec); + if (!source_metadata) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "UltraHDR gain map metadata is missing"); + status = GD_UHDR_E_DECODE; + goto cleanup; + } + metadata = *source_metadata; + + base_block = uhdr_dec_get_base_image(dec); + gainmap_block = uhdr_dec_get_gainmap_image(dec); + + /* + * Geometry transforms make source EXIF dimensions, orientation, and + * thumbnails stale. Preserve ICC, but copy EXIF only for pass-through + * writes unless GD learns to rewrite those tags. + */ + status = gdUhdrCreateJpegMetadataFromBlock(base_block, im->op_count == 0, 1, + &base_metadata, err); + if (status != GD_UHDR_SUCCESS) { + goto cleanup; + } + + if (!metadata.use_base_cg) { + status = gdUhdrCreateJpegMetadataFromBlock(gainmap_block, 0, 1, + &gainmap_metadata, err); + if (status != GD_UHDR_SUCCESS) { + goto cleanup; + } + } + + base_image = gdUhdrCreateGdImageFromJpegBlock( + base_block, "Failed to decode UltraHDR base image", err); + if (!base_image) { + status = GD_UHDR_E_DECODE; + goto cleanup; + } + + gainmap_image = gdUhdrCreateGdImageFromJpegBlock( + gainmap_block, "Failed to decode UltraHDR gain map image", err); + if (!gainmap_image) { + status = GD_UHDR_E_DECODE; + goto cleanup; + } + + status = gdUhdrApplyGdOps(&base_image, &gainmap_image, im, err); + if (status != GD_UHDR_SUCCESS) { + goto cleanup; + } + + status = gdUhdrEncodeJpegComponent(base_image, quality, base_metadata, 0, + &base_jpeg_data, &base_jpeg_size, err); + if (status != GD_UHDR_SUCCESS) { + goto cleanup; + } + + status = + gdUhdrEncodeJpegComponent(gainmap_image, quality, gainmap_metadata, 1, + &gainmap_jpeg_data, &gainmap_jpeg_size, err); + if (status != GD_UHDR_SUCCESS) { + goto cleanup; + } + + gdUhdrInitCompressedImage(&base_jpeg, base_jpeg_data, base_jpeg_size); + gdUhdrInitCompressedImage(&gainmap_jpeg, gainmap_jpeg_data, + gainmap_jpeg_size); + + rc = uhdr_enc_set_output_format(enc, UHDR_CODEC_JPG); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, rc.error_code, + rc.has_detail ? rc.detail + : "uhdr_enc_set_output_format failed"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + rc = uhdr_enc_set_compressed_image(enc, &base_jpeg, UHDR_BASE_IMG); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, rc.error_code, + rc.has_detail + ? rc.detail + : "uhdr_enc_set_compressed_image(base) failed"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + rc = uhdr_enc_set_gainmap_image(enc, &gainmap_jpeg, &metadata); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, rc.error_code, + rc.has_detail ? rc.detail + : "uhdr_enc_set_gainmap_image failed"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + rc = uhdr_encode(enc); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_encode failed"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + encoded = uhdr_get_encoded_stream(enc); + if (!encoded || !encoded->data || encoded->data_sz == 0) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Encoded UltraHDR stream is empty"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + write_result = gdPutBuf(encoded->data, (int)encoded->data_sz, ctx); + if (write_result != (int)encoded->data_sz) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to write UltraHDR output"); + status = GD_UHDR_E_ENCODE; + goto cleanup; + } + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + status = GD_UHDR_SUCCESS; + +cleanup: + gdFree(gainmap_jpeg_data); + gdFree(base_jpeg_data); + if (gainmap_image) { + gdImageDestroy(gainmap_image); + } + if (base_image) { + gdImageDestroy(base_image); + } + if (gainmap_metadata) { + gdImageMetadataFree(gainmap_metadata); + } + if (base_metadata) { + gdImageMetadataFree(base_metadata); + } + if (enc) { + uhdr_release_encoder(enc); + } + if (dec) { + uhdr_release_decoder(dec); + } + return status; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(ctx); + ARG_NOT_USED(format); + ARG_NOT_USED(quality); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return gdUhdrUnavailableCode(); +#endif +} + +/* + Function: gdUhdrImageWritePtr + + See . +*/ +BGD_DECLARE(void *) +gdUhdrImageWritePtr(gdUhdrImagePtr im, int *size, int format, int quality, + gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + gdIOCtxPtr out; + void *rv; + + if (size) { + *size = 0; + } + + out = gdNewDynamicCtx(2048, NULL); + if (!out) { + gdUhdrSetError(err, GD_UHDR_E_ENCODE, 0, + "Failed to create dynamic output context"); + return NULL; + } + + if (gdUhdrImageCtx(im, out, format, quality, err) != GD_UHDR_SUCCESS) { + out->gd_free(out); + return NULL; + } + + rv = gdDPExtractData(out, size); + out->gd_free(out); + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + return rv; +#else + ARG_NOT_USED(im); + ARG_NOT_USED(format); + ARG_NOT_USED(quality); + if (size) { + *size = 0; + } + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return NULL; +#endif +} + +/* + Function: gdUhdrImageGetSdr + + Decodes and returns the SDR base image as a . +*/ +BGD_DECLARE(gdImagePtr) +gdUhdrImageGetSdr(gdUhdrImagePtr im, gdUhdrErrorPtr err) { +#ifdef HAVE_LIBUHDR + uhdr_codec_private_t *dec; + uhdr_compressed_image_t input; + uhdr_error_info_t rc; + uhdr_raw_image_t *raw; + gdImagePtr out; + int x, y; + unsigned int stride; + unsigned char *row; + + if (!im || !im->blob || im->blob_size <= 0) { + gdUhdrSetError(err, GD_UHDR_E_INVALID, 0, "Invalid UltraHDR image"); + return NULL; + } + + dec = uhdr_create_decoder(); + if (!dec) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Failed to create UltraHDR decoder"); + return NULL; + } + + input.data = im->blob; + input.data_sz = (size_t)im->blob_size; + input.capacity = (size_t)im->blob_size; + input.cg = UHDR_CG_UNSPECIFIED; + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_FULL_RANGE; + + rc = uhdr_dec_set_image(dec, &input); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_dec_set_image failed"); + uhdr_release_decoder(dec); + return NULL; + } + + rc = uhdr_dec_set_out_img_format(dec, UHDR_IMG_FMT_32bppRGBA8888); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail + : "uhdr_dec_set_out_img_format failed"); + uhdr_release_decoder(dec); + return NULL; + } + + rc = uhdr_dec_set_out_color_transfer(dec, UHDR_CT_SRGB); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail + ? rc.detail + : "uhdr_dec_set_out_color_transfer failed"); + uhdr_release_decoder(dec); + return NULL; + } + + rc = uhdr_decode(dec); + if (rc.error_code != UHDR_CODEC_OK) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, rc.error_code, + rc.has_detail ? rc.detail : "uhdr_decode failed"); + uhdr_release_decoder(dec); + return NULL; + } + + raw = uhdr_get_decoded_image(dec); + if (!raw || !raw->planes[UHDR_PLANE_PACKED] || raw->w == 0 || raw->h == 0) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Decoded SDR image unavailable"); + uhdr_release_decoder(dec); + return NULL; + } + + out = gdImageCreateTrueColor((int)raw->w, (int)raw->h); + if (!out) { + gdUhdrSetError(err, GD_UHDR_E_DECODE, 0, + "Failed to allocate SDR gdImage"); + uhdr_release_decoder(dec); + return NULL; + } + + stride = raw->stride[UHDR_PLANE_PACKED] ? raw->stride[UHDR_PLANE_PACKED] + : raw->w; + for (y = 0; y < (int)raw->h; y++) { + row = (unsigned char *)raw->planes[UHDR_PLANE_PACKED] + + ((size_t)y * stride * 4); + for (x = 0; x < (int)raw->w; x++) { + unsigned char r = row[(size_t)x * 4 + 0]; + unsigned char g = row[(size_t)x * 4 + 1]; + unsigned char b = row[(size_t)x * 4 + 2]; + unsigned char a8 = row[(size_t)x * 4 + 3]; + int a7 = gdAlphaMax - (a8 >> 1); + out->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a7); + } + } + out->saveAlphaFlag = 1; + + gdUhdrSetError(err, GD_UHDR_SUCCESS, 0, NULL); + uhdr_release_decoder(dec); + return out; +#else + ARG_NOT_USED(im); + gdUhdrSetError(err, gdUhdrUnavailableCode(), 0, gdUhdrUnavailableMessage()); + return NULL; +#endif +} diff --git a/ext/gd/libgd/gd_vector2d.h b/ext/gd/libgd/gd_vector2d.h new file mode 100644 index 000000000000..19b3bd18b7ab --- /dev/null +++ b/ext/gd/libgd/gd_vector2d.h @@ -0,0 +1,125 @@ +#ifndef GD_VECTOR2D_H +#define GD_VECTOR2D_H + +#include "gd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Experimental API: source and ABI compatibility are not yet guaranteed. */ +#define GD_VECTOR2D_EXPERIMENTAL 1 +#define GD_VECTOR2D_VERSION 1 + +typedef struct gdContextStruct gdContext; +typedef gdContext *gdContextPtr; +typedef struct gdPathStruct gdPath; +typedef gdPath *gdPathPtr; +typedef struct gdPaintStruct gdPaint; +typedef gdPaint *gdPaintPtr; +typedef struct gdPathPatternStruct gdPathPattern; +typedef gdPathPattern *gdPathPatternPtr; + +typedef struct gdPathMatrixStruct { + double m00, m10, m01, m11, m02, m12; +} gdPathMatrix; +typedef gdPathMatrix *gdPathMatrixPtr; + +typedef enum { + GD_EXTEND_NONE, GD_EXTEND_REPEAT, GD_EXTEND_REFLECT, GD_EXTEND_PAD +} gdExtendMode; +typedef enum { gdLineCapButt, gdLineCapRound, gdLineCapSquare } gdLineCap; +typedef enum { gdLineJoinMiter, gdLineJoinRound, gdLineJoinBevel } gdLineJoin; +typedef enum { gdFillRuleNonZero, gdFillRuleEvenOdd } gdFillRule; +/* Compatibility for the spelling used by the original experimental branch. */ +#define gdFillRulEvenOdd gdFillRuleEvenOdd + +typedef enum { + GD_OP_CLEAR, GD_OP_SOURCE, GD_OP_OVER, GD_OP_IN, GD_OP_OUT, GD_OP_ATOP, + GD_OP_DEST, GD_OP_DEST_OVER, GD_OP_DEST_IN, GD_OP_DEST_OUT, GD_OP_DEST_ATOP, + GD_OP_XOR, GD_OP_ADD, GD_OP_SATURATE, GD_OP_MULTIPLY, GD_OP_SCREEN, + GD_OP_OVERLAY, GD_OP_DARKEN, GD_OP_LIGHTEN, GD_OP_COLOR_DODGE, + GD_OP_COLOR_BURN, GD_OP_HARD_LIGHT, GD_OP_SOFT_LIGHT, GD_OP_DIFFERENCE, + GD_OP_EXCLUSION, GD_OP_HSL_HUE, GD_OP_HSL_SATURATION, GD_OP_HSL_COLOR, + GD_OP_HSL_LUMINOSITY, GD_OP_COUNT +} gdCompositeOperator; +typedef gdCompositeOperator gdImageOp; +#define gdImageOpsSrc GD_OP_SOURCE +#define gdImageOpsSrcOver GD_OP_OVER +#define gdImageOpsDstIn GD_OP_DEST_IN +#define gdImageOpsDstOut GD_OP_DEST_OUT + +BGD_DECLARE(gdContextPtr) gdContextCreateForImage(gdImagePtr image); +BGD_DECLARE(void) gdContextFlushImage(gdContextPtr context); +BGD_DECLARE(gdImagePtr) gdContextGetImage(gdContextPtr context); +BGD_DECLARE(void) gdContextDestroy(gdContextPtr context); +BGD_DECLARE(void) gdContextClip(gdContextPtr context); +BGD_DECLARE(void) gdContextClipPreserve(gdContextPtr context); +BGD_DECLARE(void) gdContextNewPath(gdContextPtr context); +BGD_DECLARE(void) gdContextAppendPath(gdContextPtr context, gdPathPtr path); +BGD_DECLARE(void) gdContextSetSourceRgba(gdContextPtr context, double r, double g, double b, double a); +BGD_DECLARE(void) gdContextSetSourceRgb(gdContextPtr context, double r, double g, double b); +BGD_DECLARE(void) gdContextSetSourceImage(gdContextPtr context, gdImagePtr image, double x, double y); +BGD_DECLARE(void) gdContextSetSource(gdContextPtr context, gdPaintPtr source); +BGD_DECLARE(void) gdContextSetOperator(gdContextPtr context, gdCompositeOperator op); +BGD_DECLARE(void) gdContextSetOpacity(gdContextPtr context, double opacity); +BGD_DECLARE(void) gdContextMoveTo(gdContextPtr context, double x, double y); +BGD_DECLARE(void) gdContextLineTo(gdContextPtr context, double x, double y); +BGD_DECLARE(void) gdContextRelLineTo(gdContextPtr context, double x, double y); +BGD_DECLARE(void) gdContextCurveTo(gdContextPtr context, double x1, double y1, double x2, double y2, double x3, double y3); +BGD_DECLARE(void) gdContextQuadTo(gdContextPtr context, double x1, double y1, double x2, double y2); +BGD_DECLARE(void) gdContextArc(gdContextPtr context, double cx, double cy, double radius, double a0, double a1); +BGD_DECLARE(void) gdContextNegativeArc(gdContextPtr context, double cx, double cy, double radius, double a0, double a1); +BGD_DECLARE(void) gdContextRectangle(gdContextPtr context, double x, double y, double width, double height); +BGD_DECLARE(void) gdContextClosePath(gdContextPtr context); +BGD_DECLARE(void) gdContextScale(gdContextPtr context, double x, double y); +BGD_DECLARE(void) gdContextTranslate(gdContextPtr context, double x, double y); +BGD_DECLARE(void) gdContextRotate(gdContextPtr context, double radians); +BGD_DECLARE(void) gdContextTransform(gdContextPtr context, const gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdContextSetLineWidth(gdContextPtr context, double width); +BGD_DECLARE(void) gdContextSetLineCap(gdContextPtr context, gdLineCap cap); +BGD_DECLARE(void) gdContextSetLineJoin(gdContextPtr context, gdLineJoin join); +BGD_DECLARE(void) gdContextSetDash(gdContextPtr context, double offset, const double *data, int size); +BGD_DECLARE(void) gdContextSetFillRule(gdContextPtr context, gdFillRule rule); +BGD_DECLARE(void) gdContextStroke(gdContextPtr context); +BGD_DECLARE(void) gdContextStrokePreserve(gdContextPtr context); +BGD_DECLARE(void) gdContextFill(gdContextPtr context); +BGD_DECLARE(void) gdContextFillPreserve(gdContextPtr context); +BGD_DECLARE(void) gdContextPaint(gdContextPtr context); + +BGD_DECLARE(gdPaintPtr) gdPaintCreateFromPattern(gdPathPatternPtr pattern); +BGD_DECLARE(gdPaintPtr) gdPaintCreateLinear(double x0, double y0, double x1, double y1); +BGD_DECLARE(gdPaintPtr) gdPaintCreateRadial(double x0, double y0, double r0, double x1, double y1, double r1); +BGD_DECLARE(int) gdPaintAddColorStopRgb(gdPaintPtr paint, double offset, double r, double g, double b); +BGD_DECLARE(int) gdPaintAddColorStopRgba(gdPaintPtr paint, double offset, double r, double g, double b, double a); +BGD_DECLARE(int) gdPaintSetExtend(gdPaintPtr paint, gdExtendMode extend); +BGD_DECLARE(int) gdPaintSetMatrix(gdPaintPtr paint, const gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdPaintDestroy(gdPaintPtr paint); + +BGD_DECLARE(gdPathPatternPtr) gdPathPatternCreateForImage(gdImagePtr image); +BGD_DECLARE(void) gdPathPatternDestroy(gdPathPatternPtr pattern); +BGD_DECLARE(void) gdPathPatternSetExtend(gdPathPatternPtr pattern, gdExtendMode extend); +BGD_DECLARE(void) gdPathPatternSetMatrix(gdPathPatternPtr pattern, gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdPathPatternSetOpacity(gdPathPatternPtr pattern, double opacity); + +BGD_DECLARE(void) gdPathMatrixInitIdentity(gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdPathMatrixInitScale(gdPathMatrixPtr matrix, double x, double y); +BGD_DECLARE(void) gdPathMatrixInitTranslate(gdPathMatrixPtr matrix, double x, double y); +BGD_DECLARE(void) gdPathMatrixRotate(gdPathMatrixPtr matrix, double radians); +BGD_DECLARE(void) gdPathMatrixScale(gdPathMatrixPtr matrix, double x, double y); + +BGD_DECLARE(gdPathPtr) gdPathCreate(void); +BGD_DECLARE(void) gdPathDestroy(gdPathPtr path); +BGD_DECLARE(void) gdPathAppendPath(gdPathPtr path, const gdPathPtr source); +BGD_DECLARE(void) gdPathTransform(gdPathPtr path, const gdPathMatrixPtr matrix); +BGD_DECLARE(void) gdPathMoveTo(gdPathPtr path, double x, double y); +BGD_DECLARE(void) gdPathLineTo(gdPathPtr path, double x, double y); +BGD_DECLARE(void) gdPathRelLineTo(gdPathPtr path, double dx, double dy); +BGD_DECLARE(void) gdPathQuadTo(gdPathPtr path, double x1, double y1, double x2, double y2); +BGD_DECLARE(void) gdPathCurveTo(gdPathPtr path, double x1, double y1, double x2, double y2, double x3, double y3); +BGD_DECLARE(void) gdPathClose(gdPathPtr path); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/gd/libgd/gd_vector2d_private.h b/ext/gd/libgd/gd_vector2d_private.h new file mode 100644 index 000000000000..70a358ec30e7 --- /dev/null +++ b/ext/gd/libgd/gd_vector2d_private.h @@ -0,0 +1,97 @@ +#ifndef GD_VECTOR2D_PRIVATE_H +#define GD_VECTOR2D_PRIVATE_H + +#include "gd_vector2d.h" +#include "gd_array.h" + +#if defined(__GNUC__) && !defined(_WIN32) +# define GD_VECTOR2D_INTERNAL __attribute__((visibility("hidden"))) +#else +# define GD_VECTOR2D_INTERNAL +#endif + +typedef enum { GD_SURFACE_NONE, GD_SURFACE_ARGB32, GD_SURFACE_XRGB32, GD_SURFACE_A8, GD_SURFACE_COUNT } gdSurfaceType; +typedef struct gdSurfaceStruct { + int ref; + unsigned char *data; + gdSurfaceType type; + int gdOwned; + int width, height, stride; +} gdSurface; +typedef gdSurface *gdSurfacePtr; + +typedef struct gdRectFStruct { double x, y, w, h; } gdRectF; +typedef gdRectF *gdRectFPtr; +typedef struct gdSpanStruct { short x, y; unsigned short len; unsigned char coverage; } gdSpan; +typedef gdSpan *gdSpanPtr; +typedef struct gdSpanRleStruct { + struct { gdSpanPtr data; int size, capacity; } spans; + int x, y, w, h; +} gdSpanRle; +typedef gdSpanRle *gdSpanRlePtr; + +typedef enum { gdPaintTypeColor, gdPaintTypeGradient, gdPaintTypeSurface, gdPaintTypePattern } gdPaintType; +typedef struct gdColorStruct { double r, g, b, a; } gdColor; +typedef gdColor *gdColorPtr; +struct gdPathPatternStruct { + int ref; + gdExtendMode extend; + gdSurfacePtr surface; + gdPathMatrix matrix; + double opacity; +}; +typedef struct gdGradientStruct gdGradient; +struct gdPaintStruct { + int ref; + gdPaintType type; + union { gdColorPtr color; gdSurfacePtr surface; gdPathPatternPtr pattern; gdGradient *gradient; }; +}; +typedef struct gdPathDashStruct { double *data; int size; double offset; } gdPathDash; +typedef gdPathDash *gdPathDashPtr; +typedef struct gdStrokeStruct { + double width, miterlimit; + gdLineCap cap; + gdLineJoin join; + gdPathDashPtr dash; +} gdStroke; +typedef gdStroke *gdStrokePtr; +typedef struct gdStateStruct { + gdSpanRlePtr clippath; + gdPaintPtr source; + gdPathMatrix matrix; + gdFillRule winding; + gdStroke stroke; + gdCompositeOperator op; + double opacity; + struct gdStateStruct *next; +} gdState; +typedef gdState *gdStatePtr; +typedef enum { gdPathOpsMoveTo, gdPathOpsLineTo, gdPathOpsCubicTo, gdPathOpsQuadTo, gdPathOpsClose } gdPathOps; +typedef gdPathOps *gdPathOpsPtr; +struct gdPathStruct { + int ref, contours; + gdPointF start; + gdArray elements; + gdArray points; +}; +struct gdContextStruct { + int ref; + gdSurfacePtr surface; + gdPathPtr path; + gdStatePtr state; + gdSpanRlePtr rle, clippath; + gdRectF clip; + gdImagePtr image; + int imageOwned; +}; + +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceCreate(int width, int height, unsigned int type); +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceCreateForData(unsigned char *data, int width, int height, int stride, unsigned int type); +GD_VECTOR2D_INTERNAL gdSurfacePtr gdSurfaceAddRef(gdSurfacePtr surface); +GD_VECTOR2D_INTERNAL void gdSurfaceDestroy(gdSurfacePtr surface); +GD_VECTOR2D_INTERNAL gdContextPtr gdContextCreate(gdSurfacePtr surface); +GD_VECTOR2D_INTERNAL gdPathPatternPtr gdPathPatternCreate(gdSurfacePtr surface); +GD_VECTOR2D_INTERNAL void gdContextSetSourceSurface(gdContextPtr context, gdSurfacePtr surface, double x, double y); +GD_VECTOR2D_INTERNAL gdPathPtr gdPathStrokeToPath(const gdPathPtr path, const gdStrokePtr stroke, const gdPathMatrixPtr matrix); + +#endif diff --git a/ext/gd/libgd/gd_version.c b/ext/gd/libgd/gd_version.c new file mode 100644 index 000000000000..38d378db3c76 --- /dev/null +++ b/ext/gd/libgd/gd_version.c @@ -0,0 +1,34 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "gd.h" + +/* These functions return the version information. We use functions + * so that changes in the shared library will automatically be + * reflected in executables using it without recompiling them. */ + +/* + Function: gdMajorVersion +*/ +BGD_DECLARE(int) gdMajorVersion() { return GD_MAJOR_VERSION; } + +/* + Function: gdMinorVersion +*/ +BGD_DECLARE(int) gdMinorVersion() { return GD_MINOR_VERSION; } + +/* + Function: gdReleaseVersion +*/ +BGD_DECLARE(int) gdReleaseVersion() { return GD_RELEASE_VERSION; } + +/* + Function: gdExtraVersion +*/ +BGD_DECLARE(const char *) gdExtraVersion() { return GD_EXTRA_VERSION; } + +/* + Function: gdVersionString +*/ +BGD_DECLARE(const char *) gdVersionString() { return GD_VERSION_STRING; } diff --git a/ext/gd/libgd/gd_wbmp.c b/ext/gd/libgd/gd_wbmp.c index 22d1c4f4c66d..585d3f938b3b 100644 --- a/ext/gd/libgd/gd_wbmp.c +++ b/ext/gd/libgd/gd_wbmp.c @@ -1,112 +1,113 @@ /* - WBMP: Wireless Bitmap Type 0: B/W, Uncompressed Bitmap - Specification of the WBMP format can be found in the file: - SPEC-WAESpec-19990524.pdf - You can download the WAP specification on: http://www.wapforum.com/ - - gd_wbmp.c - - Copyright (C) Johan Van den Brande (johan@vandenbrande.com) - - Fixed: gdImageWBMPPtr, gdImageWBMP - - Recoded: gdImageWBMPCtx for use with my wbmp library - (wbmp library included, but you can find the latest distribution - at http://www.vandenbrande.com/wbmp) - - Implemented: gdImageCreateFromWBMPCtx, gdImageCreateFromWBMP - - --------------------------------------------------------------------------- - - Parts of this code are from Maurice Smurlo. - - + * WBMP: Wireless Bitmap Type 0: B/W, Uncompressed Bitmap + * Specification of the WBMP format can be found in the file: + * SPEC-WAESpec-19990524.pdf + * You can download the WAP specification on: http://www.wapforum.com/ + * + * gd_wbmp.c + * + * Copyright (C) Johan Van den Brande (johan@vandenbrande.com) + * + * Fixed: gdImageWBMPPtr, gdImageWBMP + * + * Recoded: gdImageWBMPCtx for use with my wbmp library + * (wbmp library included, but you can find the latest distribution + * at http://www.vandenbrande.com/wbmp) + * + * Implemented: gdImageCreateFromWBMPCtx, gdImageCreateFromWBMP + * + *-------------------------------------------------------------------------- + * + * Parts of this code are from Maurice Smurlo. + * ** Copyright (C) Maurice Szmurlo --- T-SIT --- January 2000 ** (Maurice.Szmurlo@info.unicaen.fr) - + ** ** Permission to use, copy, modify, and distribute this software and its ** documentation for any purpose and without fee is hereby granted, provided ** that the above copyright notice appear in all copies and that both that ** copyright notice and this permission notice appear in supporting ** documentation. This software is provided "as is" without express or ** implied warranty. - - --------------------------------------------------------------------------- - Parts od this code are inspired by 'pbmtowbmp.c' and 'wbmptopbm.c' by - Terje Sannum . - ** + * + *-------------------------------------------------------------------------- + * + * Parts of this code are inspired by 'pbmtowbmp.c' and 'wbmptopbm.c' by + * Terje Sannum . + * ** Permission to use, copy, modify, and distribute this software and its ** documentation for any purpose and without fee is hereby granted, provided ** that the above copyright notice appear in all copies and that both that ** copyright notice and this permission notice appear in supporting ** documentation. This software is provided "as is" without express or ** implied warranty. - ** - --------------------------------------------------------------------------- - - Todo: - - gdCreateFromWBMP function for reading WBMP files + * + *-------------------------------------------------------------------------- + * + * Todo: + * + * gdCreateFromWBMP function for reading WBMP files + * + *-------------------------------------------------------------------------- + */ - ---------------------------------------------------------------------------- +/** + * File: WBMP IO + * + * Read and write WBMP images. */ -#include -#include -#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include "gd.h" -#include "gdfonts.h" #include "gd_errors.h" -#include "wbmp.h" +#include "gdfonts.h" +#include +#include +#include +#include "wbmp.h" /* gd_putout - ** --------- - ** Wrapper around gdPutC for use with writewbmp - ** + * --------- + * Wrapper around gdPutC for use with writewbmp */ -void gd_putout (int i, void *out) -{ - gdPutC(i, (gdIOCtx *) out); -} - +static void gd_putout(int i, void *out) { gdPutC(i, (gdIOCtx *)out); } /* gd_getin - ** -------- - ** Wrapper around gdGetC for use with readwbmp - ** + * -------- + * Wrapper around gdGetC for use with readwbmp */ -int gd_getin (void *in) -{ - return (gdGetC((gdIOCtx *) in)); -} +static int gd_getin(void *in) { return (gdGetC((gdIOCtx *)in)); } static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out); -/* gdImageWBMPCtx - ** -------------- - ** Write the image as a wbmp file - ** Parameters are: - ** image: gd image structure; - ** fg: the index of the foreground color. any other value will be - ** considered as background and will not be written - ** out: the stream where to write +/* + Function: gdImageWBMPCtx + + Write the image as a wbmp file + + Parameters: + image - gd image structure + fg - the index of the foreground color. any other value will be + considered as background and will not be written + out - the stream where to write */ -void gdImageWBMPCtx (gdImagePtr image, int fg, gdIOCtx * out) -{ +BGD_DECLARE(void) gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out) { _gdImageWBMPCtx(image, fg, out); } /* returns 0 on success, 1 on failure */ -static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out) -{ +static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out) { int x, y, pos; Wbmp *wbmp; /* create the WBMP */ - if ((wbmp = createwbmp (gdImageSX (image), gdImageSY (image), WBMP_WHITE)) == NULL) { - gd_error("Could not create WBMP"); + if ((wbmp = createwbmp(gdImageSX(image), gdImageSY(image), WBMP_WHITE)) == + NULL) { + gd_error("Could not create WBMP\n"); return 1; } @@ -124,7 +125,7 @@ static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out) /* write the WBMP to a gd file descriptor */ if (writewbmp (wbmp, &gd_putout, out)) { freewbmp(wbmp); - gd_error("Could not save WBMP"); + gd_error("Could not save WBMP\n"); return 1; } @@ -134,20 +135,20 @@ static int _gdImageWBMPCtx(gdImagePtr image, int fg, gdIOCtx *out) return 0; } -/* gdImageCreateFromWBMPCtx - ** ------------------------ - ** Create a gdImage from a WBMP file input from an gdIOCtx +/* + Function: gdImageCreateFromWBMPCtx + + Reads in a WBMP image via a struct. See + . */ -gdImagePtr gdImageCreateFromWBMPCtx (gdIOCtx * infile) -{ - /* FILE *wbmp_file; */ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMPCtx(gdIOCtx *infile) { Wbmp *wbmp; gdImagePtr im = NULL; int black, white; int col, row, pos; if (readwbmp (&gd_getin, infile, &wbmp)) { - return NULL; + return (NULL); } if (!(im = gdImageCreate (wbmp->width, wbmp->height))) { @@ -177,51 +178,105 @@ gdImagePtr gdImageCreateFromWBMPCtx (gdIOCtx * infile) return im; } -/* gdImageCreateFromWBMP - ** --------------------- +/* + Function: gdImageCreateFromWBMP + + is called to load images from WBMP format + files. Invoke with an already opened + pointer to a file containing the desired + image. returns a gdImagePtr to the new + image, or NULL if unable to load the image (most often because the + file is corrupt or does not contain a WBMP + image). does not close the file. You can + inspect the sx and sy members of the image to determine its + size. The image must eventually be destroyed using + . + + Variants: + + creates an image from WBMP data (i.e. the + contents of a WBMP file) already in memory. + + reads in an image using the functions in + a struct. + + Parameters: + + infile - The input FILE pointer + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + FILE *in; + in = fopen("mywbmp.wbmp", "rb"); + im = gdImageCreateFromWBMP(in); + fclose(in); + // ... Use the image ... + gdImageDestroy(im); + + (end code) */ -gdImagePtr gdImageCreateFromWBMP (FILE * inFile) -{ + +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMP(FILE *inFile) { gdImagePtr im; gdIOCtx *in = gdNewFileCtx(inFile); + if (in == NULL) + return NULL; im = gdImageCreateFromWBMPCtx(in); in->gd_free(in); - return im; } -gdImagePtr gdImageCreateFromWBMPPtr (int size, void *data) -{ +/* + Function: gdImageCreateFromWBMPPtr + + Parameters: + + size - size of WBMP data in bytes. + data - WBMP data (i.e. contents of a WBMP file). + + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWBMPPtr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); + if (!in) { + return 0; + } im = gdImageCreateFromWBMPCtx(in); in->gd_free(in); return im; } -/* gdImageWBMP - ** ----------- +/* + Function: gdImageWBMP */ -void gdImageWBMP (gdImagePtr im, int fg, FILE * outFile) -{ +BGD_DECLARE(void) gdImageWBMP(gdImagePtr im, int fg, FILE *outFile) { gdIOCtx *out = gdNewFileCtx(outFile); + if (out == NULL) + return; gdImageWBMPCtx(im, fg, out); out->gd_free(out); } -/* gdImageWBMPPtr - ** -------------- +/* + Function: gdImageWBMPPtr */ -void * gdImageWBMPPtr (gdImagePtr im, int *size, int fg) -{ +BGD_DECLARE(void *) gdImageWBMPPtr(gdImagePtr im, int *size, int fg) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) + return NULL; if (!_gdImageWBMPCtx(im, fg, out)) { rv = gdDPExtractData(out, size); } else { rv = NULL; } out->gd_free(out); - return rv; } diff --git a/ext/gd/libgd/gd_webp.c b/ext/gd/libgd/gd_webp.c index 6277e09710f9..2a0143624098 100644 --- a/ext/gd/libgd/gd_webp.c +++ b/ext/gd/libgd/gd_webp.c @@ -1,32 +1,317 @@ -#include -#include -#include -#include +/** + * File: WebP IO + * + * Read and write WebP images. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + #include "gd.h" #include "gd_errors.h" +#include "gd_intern.h" #include "gdhelpers.h" +#include +#include +#include +#include #ifdef HAVE_LIBWEBP #include "webp/decode.h" +#include "webp/demux.h" #include "webp/encode.h" +#include "webp/mux.h" + +#define GD_WEBP_ALLOC_STEP (4 * 1024) + +struct gdWebpReadStruct { + uint8_t *data; + size_t size; + WebPDemuxer *demux; + WebPAnimDecoder *decoder; + WebPIterator iter; + int haveIter; + int rawIndex; + int imageIndex; + int rawTimestamp; + int imageTimestamp; + gdImagePtr rawFrame; + gdImagePtr canvas; +}; + +struct gdWebpWriteStruct { + gdIOCtxPtr out; + int ownsCtx; + int memoryWriter; + WebPAnimEncoder *encoder; + gdWebpWriteOptions options; + int canvasWidth; + int canvasHeight; + int timestamp; + int frameCount; + int finalized; +}; + +static uint8_t *WebpReadCtxData(gdIOCtx *infile, size_t *size) { + uint8_t *filedata = NULL, *temp, *read; + ssize_t n; + + *size = 0; + do { + temp = gdRealloc(filedata, *size + GD_WEBP_ALLOC_STEP); + if (temp == NULL) { + gdFree(filedata); + gd_error("WebP decode: realloc failed"); + return NULL; + } + filedata = temp; + read = temp + *size; + n = gdGetBuf(read, GD_WEBP_ALLOC_STEP, infile); + if (n > 0 && n != EOF) { + *size += n; + } + } while (n > 0 && n != EOF); + + if (*size == 0) { + gdFree(filedata); + return NULL; + } + + return filedata; +} + +static int WebpGdAlphaToWebp(int alpha) { + if (alpha == gdAlphaTransparent) { + return 0; + } + return 255 - ((alpha << 1) + (alpha >> 6)); +} + +static int WebpWebpAlphaToGd(uint8_t alpha) { + return gdAlphaMax - (alpha >> 1); +} + +static gdImagePtr WebpImageFromRGBA(const uint8_t *rgba, int width, + int height) { + gdImagePtr im; + const uint8_t *p; + int x, y; + + if (rgba == NULL || width <= 0 || height <= 0) { + return NULL; + } + im = gdImageCreateTrueColor(width, height); + if (im == NULL) { + return NULL; + } + gdImageAlphaBlending(im, 0); + gdImageSaveAlpha(im, 1); + for (y = 0, p = rgba; y < height; y++) { + for (x = 0; x < width; x++) { + uint8_t r = *(p++); + uint8_t g = *(p++); + uint8_t b = *(p++); + uint8_t a = *(p++); + im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, WebpWebpAlphaToGd(a)); + } + } + return im; +} + +static gdImagePtr WebpImageFromARGB(const uint8_t *argb, int width, + int height) { + gdImagePtr im; + const uint8_t *p; + int x, y; + + if (argb == NULL || width <= 0 || height <= 0) { + return NULL; + } + im = gdImageCreateTrueColor(width, height); + if (im == NULL) { + return NULL; + } + gdImageAlphaBlending(im, 0); + gdImageSaveAlpha(im, 1); + for (y = 0, p = argb; y < height; y++) { + for (x = 0; x < width; x++) { + uint8_t a = *(p++); + uint8_t r = *(p++); + uint8_t g = *(p++); + uint8_t b = *(p++); + im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, WebpWebpAlphaToGd(a)); + } + } + return im; +} + +static gdImagePtr WebpCloneImage(gdImagePtr src) { + gdImagePtr dst; + int x, y; + + if (src == NULL) { + return NULL; + } + dst = gdImageCreateTrueColor(gdImageSX(src), gdImageSY(src)); + if (dst == NULL) { + return NULL; + } + gdImageAlphaBlending(dst, 0); + gdImageSaveAlpha(dst, src->saveAlphaFlag); + for (y = 0; y < gdImageSY(src); y++) { + for (x = 0; x < gdImageSX(src); x++) { + dst->tpixels[y][x] = gdImageGetPixel(src, x, y); + } + } + return dst; +} + +static void WebpFillInfo(const WebPDemuxer *demux, gdWebpInfo *info) { + if (info == NULL) { + return; + } + info->width = (int)WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + info->height = (int)WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + info->frameCount = (int)WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT); + info->loopCount = (int)WebPDemuxGetI(demux, WEBP_FF_LOOP_COUNT); + info->backgroundColor = (int)WebPDemuxGetI(demux, WEBP_FF_BACKGROUND_COLOR); + info->formatFlags = (int)WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); +} + +static void WebpFillFrameInfo(const WebPIterator *iter, int frameIndex, + int timestamp, gdWebpFrameInfo *info) { + if (info == NULL || iter == NULL) { + return; + } + info->frameIndex = frameIndex; + info->x = iter->x_offset; + info->y = iter->y_offset; + info->width = iter->width; + info->height = iter->height; + info->duration = iter->duration; + info->timestamp = timestamp; + info->dispose = iter->dispose_method; + info->blend = iter->blend_method; + info->hasAlpha = iter->has_alpha; + info->complete = iter->complete; +} + +static int WebpProbeData(const uint8_t *data, size_t size) { + WebPData webpData; + WebPDemuxer *demux; + uint32_t flags; + int frameCount; + + if (data == NULL || size == 0) { + return -1; + } + webpData.bytes = data; + webpData.size = size; + demux = WebPDemux(&webpData); + if (demux == NULL) { + return -1; + } + flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); + frameCount = (int)WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT); + WebPDemuxDelete(demux); + return ((flags & ANIMATION_FLAG) != 0 || frameCount > 1) ? 1 : 0; +} + +static gdImagePtr WebpDecodeFirstImage(const uint8_t *filedata, size_t size) { + WebPData webpData; + WebPAnimDecoderOptions options; + WebPAnimDecoder *decoder; + uint8_t *rgba = NULL; + int timestamp = 0; + gdImagePtr im; + + if (!WebPAnimDecoderOptionsInit(&options)) { + return NULL; + } + options.color_mode = MODE_RGBA; + webpData.bytes = filedata; + webpData.size = size; + decoder = WebPAnimDecoderNew(&webpData, &options); + if (decoder == NULL) { + return NULL; + } + if (!WebPAnimDecoderGetNext(decoder, &rgba, ×tamp)) { + WebPAnimDecoderDelete(decoder); + return NULL; + } + { + WebPAnimInfo info; + if (!WebPAnimDecoderGetInfo(decoder, &info)) { + WebPAnimDecoderDelete(decoder); + return NULL; + } + im = WebpImageFromRGBA(rgba, (int)info.canvas_width, + (int)info.canvas_height); + } + WebPAnimDecoderDelete(decoder); + return im; +} + +/* + Function: gdImageCreateFromWebp + + is called to load truecolor images from + WebP format files. Invoke with an + already opened pointer to a file containing the desired + image. returns a to the new + truecolor image, or NULL if unable to load the image (most often + because the file is corrupt or does not contain a WebP + image). does not close the file. + + You can inspect the sx and sy members of the image to determine + its size. The image must eventually be destroyed using + . + + *The returned image is always a truecolor image.* + + Variants: + + creates an image from WebP data + already in memory. + + reads its data via the function + pointers in a structure. -#define GD_WEBP_ALLOC_STEP (4*1024) + Parameters: -gdImagePtr gdImageCreateFromWebp (FILE * inFile) -{ + infile - The input FILE pointer. + + Returns: + + A pointer to the new *truecolor* image. This will need to be + destroyed with once it is no longer needed. + + On error, returns NULL. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebp(FILE *inFile) { gdImagePtr im; gdIOCtx *in = gdNewFileCtx(inFile); - if (!in) + if (!in) { return 0; + } im = gdImageCreateFromWebpCtx(in); in->gd_free(in); return im; } +/* + Function: gdImageCreateFromWebpPtr + + See . + + Parameters: -gdImagePtr gdImageCreateFromWebpPtr (int size, void *data) -{ + size - size of WebP data in bytes. + data - pointer to WebP data. +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpPtr(int size, void *data) { gdImagePtr im; gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0); if (!in) @@ -36,86 +321,329 @@ gdImagePtr gdImageCreateFromWebpPtr (int size, void *data) return im; } -gdImagePtr gdImageCreateFromWebpCtx (gdIOCtx * infile) -{ - int width, height; - uint8_t *filedata = NULL; - uint8_t *argb = NULL; - size_t size = 0, n; - gdImagePtr im; - int x, y; - uint8_t *p; +/* + Function: gdImageCreateFromWebpCtx - do { - unsigned char *read, *temp; - - temp = gdRealloc(filedata, size+GD_WEBP_ALLOC_STEP); - if (temp) { - filedata = temp; - read = temp + size; - } else { - if (filedata) { - gdFree(filedata); - } - gd_error("WebP decode: realloc failed"); - return NULL; - } - - n = gdGetBuf(read, GD_WEBP_ALLOC_STEP, infile); - if (n>0 && n!=EOF) { - size += n; - } - } while (n>0 && n!=EOF); + See . +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpCtx(gdIOCtx *infile) { + int width, height; + uint8_t *filedata = NULL; + uint8_t *argb = NULL; + size_t size = 0; + gdImagePtr im; - if (WebPGetInfo(filedata,size, &width, &height) == 0) { + filedata = WebpReadCtxData(infile, &size); + if (filedata == NULL) { gd_error("gd-webp cannot get webp info"); - gdFree(filedata); return NULL; } - im = gdImageCreateTrueColor(width, height); - if (!im) { + if (WebPGetInfo(filedata, size, &width, &height) == 0) { + im = WebpDecodeFirstImage(filedata, size); + if (im == NULL) { + gd_error("gd-webp cannot get webp info"); + } gdFree(filedata); - return NULL; + return im; } argb = WebPDecodeARGB(filedata, size, &width, &height); if (!argb) { - gd_error("gd-webp cannot allocate temporary buffer"); + im = WebpDecodeFirstImage(filedata, size); + if (im == NULL) { + gd_error("gd-webp cannot allocate temporary buffer"); + } gdFree(filedata); - gdImageDestroy(im); + return im; + } + im = WebpImageFromARGB(argb, width, height); + /* do not use gdFree here, in case gdFree/alloc is mapped to something else + * than libc */ + WebPFree(argb); + gdFree(filedata); + return im; +} + +BGD_DECLARE(int) gdWebpIsAnimated(FILE *fdFile) { + gdIOCtx *fd; + uint8_t *data; + size_t size; + int result, pos; + + if (fdFile == NULL) { + return -1; + } + fd = gdNewFileCtx(fdFile); + if (fd == NULL) { + return -1; + } + pos = (int)gdTell(fd); + if (pos < 0) { + fd->gd_free(fd); + return -1; + } + data = WebpReadCtxData(fd, &size); + result = WebpProbeData(data, size); + gdFree(data); + if (!gdSeek(fd, pos)) { + result = -1; + } + fd->gd_free(fd); + return result; +} + +BGD_DECLARE(int) gdWebpIsAnimatedCtx(gdIOCtxPtr in) { + uint8_t *data; + size_t size; + int result, pos; + + if (in == NULL || in->tell == NULL || in->seek == NULL) { + return -1; + } + pos = (int)gdTell(in); + if (pos < 0) { + return -1; + } + data = WebpReadCtxData(in, &size); + result = WebpProbeData(data, size); + gdFree(data); + if (!gdSeek(in, pos)) { + return -1; + } + return result; +} + +BGD_DECLARE(int) gdWebpIsAnimatedPtr(int size, void *data) { + if (size <= 0 || data == NULL) { + return -1; + } + return WebpProbeData((const uint8_t *)data, (size_t)size); +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpen(FILE *fdFile) { + gdIOCtx *fd; + gdWebpReadPtr webp; + + if (fdFile == NULL) { return NULL; } - for (y = 0, p = argb; y < height; y++) { - for (x = 0; x < width; x++) { - register uint8_t a = gdAlphaMax - (*(p++) >> 1); - register uint8_t r = *(p++); - register uint8_t g = *(p++); - register uint8_t b = *(p++); - im->tpixels[y][x] = gdTrueColorAlpha(r, g, b, a); + fd = gdNewFileCtx(fdFile); + if (fd == NULL) { + return NULL; + } + webp = gdWebpReadOpenCtx(fd); + fd->gd_free(fd); + return webp; +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenPtr(int size, void *data) { + gdIOCtx *in; + gdWebpReadPtr webp; + + if (size <= 0 || data == NULL) { + return NULL; + } + in = gdNewDynamicCtxEx(size, data, 0); + if (in == NULL) { + return NULL; + } + webp = gdWebpReadOpenCtx(in); + in->gd_free(in); + return webp; +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenCtx(gdIOCtxPtr in) { + gdWebpReadPtr webp; + WebPData webpData; + WebPAnimDecoderOptions decOptions; + + if (in == NULL) { + return NULL; + } + webp = (gdWebpReadPtr)gdCalloc(1, sizeof(struct gdWebpReadStruct)); + if (webp == NULL) { + return NULL; + } + webp->data = WebpReadCtxData(in, &webp->size); + if (webp->data == NULL) { + gdFree(webp); + return NULL; + } + webpData.bytes = webp->data; + webpData.size = webp->size; + webp->demux = WebPDemux(&webpData); + if (webp->demux == NULL || !WebPAnimDecoderOptionsInit(&decOptions)) { + gdWebpReadClose(webp); + return NULL; + } + decOptions.color_mode = MODE_RGBA; + webp->decoder = WebPAnimDecoderNew(&webpData, &decOptions); + if (webp->decoder == NULL) { + gdWebpReadClose(webp); + return NULL; + } + return webp; +} + +BGD_DECLARE(void) gdWebpReadClose(gdWebpReadPtr webp) { + if (webp == NULL) { + return; + } + if (webp->haveIter) { + WebPDemuxReleaseIterator(&webp->iter); + } + if (webp->decoder != NULL) { + WebPAnimDecoderDelete(webp->decoder); + } + if (webp->demux != NULL) { + WebPDemuxDelete(webp->demux); + } + if (webp->rawFrame != NULL) { + gdImageDestroy(webp->rawFrame); + } + if (webp->canvas != NULL) { + gdImageDestroy(webp->canvas); + } + gdFree(webp->data); + gdFree(webp); +} + +BGD_DECLARE(int) gdWebpReadGetInfo(gdWebpReadPtr webp, gdWebpInfo *info) { + if (webp == NULL || info == NULL || webp->demux == NULL) { + return 0; + } + WebpFillInfo(webp->demux, info); + return 1; +} + +BGD_DECLARE(int) +gdWebpReadNextFrame(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *frame) { + uint8_t *rgba; + int width, height; + + if (frame != NULL) { + *frame = NULL; + } + if (webp == NULL || webp->demux == NULL) { + return -1; + } + if (webp->haveIter) { + if (!WebPDemuxNextFrame(&webp->iter)) { + return 0; } + } else { + if (!WebPDemuxGetFrame(webp->demux, 1, &webp->iter)) { + return 0; + } + webp->haveIter = 1; } - gdFree(filedata); - /* do not use gdFree here, in case gdFree/alloc is mapped to something else than libc */ - free(argb); - im->saveAlphaFlag = 1; - return im; + if (webp->rawFrame != NULL) { + gdImageDestroy(webp->rawFrame); + webp->rawFrame = NULL; + } + rgba = WebPDecodeRGBA(webp->iter.fragment.bytes, webp->iter.fragment.size, + &width, &height); + if (rgba == NULL) { + return -1; + } + webp->rawFrame = WebpImageFromRGBA(rgba, width, height); + WebPFree(rgba); + if (webp->rawFrame == NULL) { + return -1; + } + WebpFillFrameInfo(&webp->iter, webp->rawIndex, webp->rawTimestamp, info); + webp->rawTimestamp += webp->iter.duration; + webp->rawIndex++; + if (frame != NULL) { + *frame = webp->rawFrame; + } + return 1; } -void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality) -{ +BGD_DECLARE(int) +gdWebpReadNextImage(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *image) { + uint8_t *rgba; + int timestamp, duration; + WebPIterator iter; + int haveFrameInfo = 0; + WebPAnimInfo animInfo; + + if (image != NULL) { + *image = NULL; + } + if (webp == NULL || webp->decoder == NULL) { + return -1; + } + if (!WebPAnimDecoderGetNext(webp->decoder, &rgba, ×tamp)) { + return 0; + } + if (!WebPAnimDecoderGetInfo(webp->decoder, &animInfo)) { + return -1; + } + duration = timestamp - webp->imageTimestamp; + if (duration < 0) { + duration = 0; + } + if (WebPDemuxGetFrame(webp->demux, webp->imageIndex + 1, &iter)) { + haveFrameInfo = 1; + WebpFillFrameInfo(&iter, webp->imageIndex, webp->imageTimestamp, info); + if (info != NULL) { + info->duration = duration; + } + WebPDemuxReleaseIterator(&iter); + } else if (info != NULL) { + memset(info, 0, sizeof(*info)); + info->frameIndex = webp->imageIndex; + info->width = (int)animInfo.canvas_width; + info->height = (int)animInfo.canvas_height; + info->duration = duration; + info->timestamp = webp->imageTimestamp; + } + if (webp->canvas != NULL) { + gdImageDestroy(webp->canvas); + webp->canvas = NULL; + } + webp->canvas = WebpImageFromRGBA(rgba, (int)animInfo.canvas_width, + (int)animInfo.canvas_height); + if (webp->canvas == NULL) { + return -1; + } + if (!haveFrameInfo && info != NULL) { + info->complete = 1; + } + webp->imageTimestamp = timestamp; + webp->imageIndex++; + if (image != NULL) { + *image = webp->canvas; + } + return 1; +} + +BGD_DECLARE(gdImagePtr) gdWebpReadCloneImage(gdWebpReadPtr webp) { + if (webp == NULL || webp->canvas == NULL) { + return NULL; + } + return WebpCloneImage(webp->canvas); +} + +/* returns 0 on success, 1 on failure */ +static int _gdImageWebpCtx(gdImagePtr im, gdIOCtx *outfile, int quality) { uint8_t *argb; int x, y; uint8_t *p; uint8_t *out; size_t out_size; + size_t ret = 0; if (im == NULL) { - return; + return 1; } if (!gdImageTrueColor(im)) { gd_error("Palette image not supported by webp"); - return; + return 1; } if (quality == -1) { @@ -123,16 +651,16 @@ void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality) } if (overflow2(gdImageSX(im), 4)) { - return; + return 1; } if (overflow2(gdImageSX(im) * 4, gdImageSY(im))) { - return; + return 1; } argb = (uint8_t *)gdMalloc(gdImageSX(im) * 4 * gdImageSY(im)); if (!argb) { - return; + return 1; } p = argb; for (y = 0; y < gdImageSY(im); y++) { @@ -152,56 +680,601 @@ void gdImageWebpCtx (gdImagePtr im, gdIOCtx * outfile, int quality) *(p++) = a; } } - 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) { gd_error("gd-webp encoding failed"); + ret = 1; goto freeargb; } - gdPutBuf(out, out_size, outfile); - free(out); + + int res = gdPutBuf(out, out_size, outfile); + WebPFree(out); + if (res < 0 || (size_t)res != out_size) { + gd_error("gd-webp write error\n"); + ret = 1; + } freeargb: gdFree(argb); + + return ret; } -void gdImageWebpEx (gdImagePtr im, FILE * outFile, int quality) -{ +/* + Function: gdImageWebpCtx + + Write the image as WebP data via a . See + for more details. + + Parameters: + + im - The image to write. + outfile - The output sink. + quality - Image quality. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImageWebpCtx(gdImagePtr im, gdIOCtx *outfile, int quality) { + _gdImageWebpCtx(im, outfile, quality); +} + +/* + Function: gdImageWebpEx + + outputs the specified image to the specified file in + WebP format. The file must be open for writing. Under MSDOS and + all versions of Windows, it is important to use "wb" as opposed to + simply "w" as the mode when opening the file, and under Unix there + is no penalty for doing so. does not close the file; + your code must do so. + + If _quality_ is -1, a reasonable quality value (which should yield a + good general quality / size tradeoff for most situations) is used. Otherwise + _quality_ should be a value in the range 0-100, higher quality values + usually implying both higher quality and larger image sizes. + + If _quality_ is greater than or equal to then the image + will be written in the lossless WebP format. + + Variants: + + stores the image using a struct. + + stores the image to RAM. + + Parameters: + + im - The image to save. + outFile - The FILE pointer to write to. + quality - Compression quality (0-100). + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImageWebpEx(gdImagePtr im, FILE *outFile, int quality) { gdIOCtx *out = gdNewFileCtx(outFile); - gdImageWebpCtx(im, out, quality); + if (out == NULL) { + return; + } + _gdImageWebpCtx(im, out, quality); out->gd_free(out); } -void gdImageWebp (gdImagePtr im, FILE * outFile) -{ +/* + Function: gdImageWebp + + Variant of which uses the default quality (-1). + + Parameters: + + im - The image to save + outFile - The FILE pointer to write to. + + Returns: + + Nothing. +*/ +BGD_DECLARE(void) gdImageWebp(gdImagePtr im, FILE *outFile) { gdIOCtx *out = gdNewFileCtx(outFile); - gdImageWebpCtx(im, out, -1); + if (out == NULL) { + return; + } + _gdImageWebpCtx(im, out, -1); out->gd_free(out); } -void * gdImageWebpPtr (gdImagePtr im, int *size) -{ +/* + Function: gdImageWebpPtr + + See . +*/ +BGD_DECLARE(void *) gdImageWebpPtr(gdImagePtr im, int *size) { void *rv; gdIOCtx *out = gdNewDynamicCtx(2048, NULL); - gdImageWebpCtx(im, out, -1); - rv = gdDPExtractData(out, size); + if (out == NULL) { + return NULL; + } + if (_gdImageWebpCtx(im, out, -1)) { + rv = NULL; + } else { + rv = gdDPExtractData(out, size); + } out->gd_free(out); return rv; } -void * gdImageWebpPtrEx (gdImagePtr im, int *size, int quality) -{ +/* + Function: gdImageWebpPtrEx + + See . +*/ +BGD_DECLARE(void *) gdImageWebpPtrEx(gdImagePtr im, int *size, int quality) { void *rv; + gdIOCtx *out = gdNewDynamicCtx(2048, NULL); - gdImageWebpCtx(im, out, quality); - rv = gdDPExtractData(out, size); + if (out == NULL) { + return NULL; + } + if (_gdImageWebpCtx(im, out, quality)) { + rv = NULL; + } else { + rv = gdDPExtractData(out, size); + } out->gd_free(out); return rv; } + +static void WebpWriteFree(gdWebpWritePtr webp) { + if (webp == NULL) { + return; + } + if (webp->encoder != NULL) { + WebPAnimEncoderDelete(webp->encoder); + } + if (webp->ownsCtx && webp->out != NULL) { + webp->out->gd_free(webp->out); + } + gdFree(webp); +} + +static int WebpWriteEnsureEncoder(gdWebpWritePtr webp, gdImagePtr image) { + WebPAnimEncoderOptions encOptions; + + if (webp->encoder != NULL) { + return 1; + } + if (image == NULL) { + return 0; + } + webp->canvasWidth = webp->options.canvasWidth > 0 + ? webp->options.canvasWidth + : gdImageSX(image); + webp->canvasHeight = webp->options.canvasHeight > 0 + ? webp->options.canvasHeight + : gdImageSY(image); + if (webp->canvasWidth <= 0 || webp->canvasHeight <= 0) { + return 0; + } + if (!WebPAnimEncoderOptionsInit(&encOptions)) { + return 0; + } + encOptions.anim_params.loop_count = webp->options.loopCount; + encOptions.anim_params.bgcolor = (uint32_t)webp->options.backgroundColor; + if (webp->options.minimizeSize) { + encOptions.minimize_size = webp->options.minimizeSize; + } + if (webp->options.kmin || webp->options.kmax) { + encOptions.kmin = webp->options.kmin; + encOptions.kmax = webp->options.kmax; + } + if (webp->options.allowMixed) { + encOptions.allow_mixed = webp->options.allowMixed; + } + webp->encoder = + WebPAnimEncoderNew(webp->canvasWidth, webp->canvasHeight, &encOptions); + return webp->encoder != NULL; +} + +static int WebpImageToRGBA(gdImagePtr im, uint8_t **rgba) { + uint8_t *p; + int x, y; + + *rgba = NULL; + if (im == NULL || !gdImageTrueColor(im)) { + gd_error("Palette image not supported by webp"); + return 0; + } + if (overflow2(gdImageSX(im), 4) || + overflow2(gdImageSX(im) * 4, gdImageSY(im))) { + return 0; + } + *rgba = (uint8_t *)gdMalloc(gdImageSX(im) * 4 * gdImageSY(im)); + if (*rgba == NULL) { + return 0; + } + p = *rgba; + for (y = 0; y < gdImageSY(im); y++) { + for (x = 0; x < gdImageSX(im); x++) { + int c = im->tpixels[y][x]; + *(p++) = gdTrueColorGetRed(c); + *(p++) = gdTrueColorGetGreen(c); + *(p++) = gdTrueColorGetBlue(c); + *(p++) = WebpGdAlphaToWebp(gdTrueColorGetAlpha(c)); + } + } + return 1; +} + +static int WebpWriteAssemble(gdWebpWritePtr webp) { + WebPData webpData; + int ok = 0; + + if (webp == NULL || webp->out == NULL || webp->finalized) { + return 0; + } + if (webp->encoder == NULL || webp->frameCount == 0) { + gd_error("gd-webp animation has no frames"); + return 0; + } + WebPDataInit(&webpData); + if (!WebPAnimEncoderAdd(webp->encoder, NULL, webp->timestamp, NULL)) { + gd_error("gd-webp animation flush failed: %s", + WebPAnimEncoderGetError(webp->encoder)); + goto done; + } + if (!WebPAnimEncoderAssemble(webp->encoder, &webpData)) { + gd_error("gd-webp animation assembly failed: %s", + WebPAnimEncoderGetError(webp->encoder)); + goto done; + } + if ((size_t)gdPutBuf(webpData.bytes, webpData.size, webp->out) != + webpData.size) { + gd_error("gd-webp animation write error"); + goto done; + } + webp->finalized = 1; + ok = 1; +done: + WebPDataClear(&webpData); + return ok; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpen(FILE *outFile, const gdWebpWriteOptions *options) { + gdIOCtx *out; + gdWebpWritePtr webp; + + if (outFile == NULL) { + return NULL; + } + out = gdNewFileCtx(outFile); + if (out == NULL) { + return NULL; + } + webp = gdWebpWriteOpenCtx(out, options); + if (webp == NULL) { + out->gd_free(out); + return NULL; + } + webp->ownsCtx = 1; + return webp; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenCtx(gdIOCtxPtr out, const gdWebpWriteOptions *options) { + gdWebpWritePtr webp; + + if (out == NULL) { + return NULL; + } + webp = (gdWebpWritePtr)gdCalloc(1, sizeof(struct gdWebpWriteStruct)); + if (webp == NULL) { + return NULL; + } + webp->out = out; + webp->ownsCtx = 0; + if (options != NULL) { + webp->options = *options; + } + if (webp->options.quality == 0) { + webp->options.quality = -1; + } + return webp; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenPtr(const gdWebpWriteOptions *options) { + gdIOCtx *out; + gdWebpWritePtr webp; + + out = gdNewDynamicCtx(2048, NULL); + if (out == NULL) { + return NULL; + } + webp = gdWebpWriteOpenCtx(out, options); + if (webp == NULL) { + out->gd_free(out); + return NULL; + } + webp->ownsCtx = 1; + webp->memoryWriter = 1; + return webp; +} + +BGD_DECLARE(int) +gdWebpWriteAddImage(gdWebpWritePtr webp, gdImagePtr image, int durationMs) { + WebPConfig config; + WebPPicture picture; + uint8_t *rgba = NULL; + int ok = 0; + + if (webp == NULL || image == NULL || durationMs < 0 || webp->finalized) { + return 0; + } + if (!WebpWriteEnsureEncoder(webp, image)) { + return 0; + } + if (gdImageSX(image) != webp->canvasWidth || + gdImageSY(image) != webp->canvasHeight) { + gd_error("gd-webp animation frames must match canvas size"); + return 0; + } + if (!WebpImageToRGBA(image, &rgba)) { + return 0; + } + if (!WebPConfigInit(&config) || !WebPPictureInit(&picture)) { + goto done; + } + config.quality = + webp->options.quality == -1 ? 80.0f : (float)webp->options.quality; + if (webp->options.lossless || webp->options.quality >= gdWebpLossless) { + config.lossless = 1; + if (config.quality > 100.0f) { + config.quality = 100.0f; + } + } + if (webp->options.method >= 0) { + config.method = webp->options.method; + } + if (!WebPValidateConfig(&config)) { + gd_error("gd-webp invalid animation encoder configuration"); + goto done; + } + picture.width = gdImageSX(image); + picture.height = gdImageSY(image); + picture.use_argb = 1; + if (!WebPPictureImportRGBA(&picture, rgba, gdImageSX(image) * 4)) { + goto free_picture; + } + if (!WebPAnimEncoderAdd(webp->encoder, &picture, webp->timestamp, + &config)) { + gd_error("gd-webp animation add frame failed: %s", + WebPAnimEncoderGetError(webp->encoder)); + goto free_picture; + } + webp->timestamp += durationMs; + webp->frameCount++; + ok = 1; +free_picture: + WebPPictureFree(&picture); +done: + gdFree(rgba); + return ok; +} + +BGD_DECLARE(void) gdWebpWriteClose(gdWebpWritePtr webp) { + if (webp == NULL) { + return; + } + if (!webp->memoryWriter) { + WebpWriteAssemble(webp); + } + WebpWriteFree(webp); +} + +BGD_DECLARE(void *) gdWebpWritePtrFinish(gdWebpWritePtr webp, int *size) { + void *rv = NULL; + + if (size != NULL) { + *size = 0; + } + if (webp == NULL || !webp->memoryWriter) { + WebpWriteFree(webp); + return NULL; + } + if (WebpWriteAssemble(webp)) { + rv = gdDPExtractData(webp->out, size); + } + WebpWriteFree(webp); + return rv; +} + +#else /* !HAVE_LIBWEBP */ + +static void _noWebpError(void) { + gd_error("WEBP image support has been disabled\n"); +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebp(FILE *inFile) { + ARG_NOT_USED(inFile); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdImagePtr) gdImageCreateFromWebpCtx(gdIOCtx *infile) { + ARG_NOT_USED(infile); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(void) gdImageWebpCtx(gdImagePtr im, gdIOCtx *outfile, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(outfile); + ARG_NOT_USED(quality); + _noWebpError(); +} + +BGD_DECLARE(void) gdImageWebpEx(gdImagePtr im, FILE *outFile, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + ARG_NOT_USED(quality); + _noWebpError(); +} + +BGD_DECLARE(void) gdImageWebp(gdImagePtr im, FILE *outFile) { + ARG_NOT_USED(im); + ARG_NOT_USED(outFile); + _noWebpError(); +} + +BGD_DECLARE(void *) gdImageWebpPtr(gdImagePtr im, int *size) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(void *) gdImageWebpPtrEx(gdImagePtr im, int *size, int quality) { + ARG_NOT_USED(im); + ARG_NOT_USED(size); + ARG_NOT_USED(quality); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(int) gdWebpIsAnimated(FILE *fd) { + ARG_NOT_USED(fd); + _noWebpError(); + return -1; +} + +BGD_DECLARE(int) gdWebpIsAnimatedCtx(gdIOCtxPtr in) { + ARG_NOT_USED(in); + _noWebpError(); + return -1; +} + +BGD_DECLARE(int) gdWebpIsAnimatedPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noWebpError(); + return -1; +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpen(FILE *fd) { + ARG_NOT_USED(fd); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenCtx(gdIOCtxPtr in) { + ARG_NOT_USED(in); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdWebpReadPtr) gdWebpReadOpenPtr(int size, void *data) { + ARG_NOT_USED(size); + ARG_NOT_USED(data); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(void) gdWebpReadClose(gdWebpReadPtr webp) { + ARG_NOT_USED(webp); + _noWebpError(); +} + +BGD_DECLARE(int) gdWebpReadGetInfo(gdWebpReadPtr webp, gdWebpInfo *info) { + ARG_NOT_USED(webp); + ARG_NOT_USED(info); + _noWebpError(); + return 0; +} + +BGD_DECLARE(int) +gdWebpReadNextFrame(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *frame) { + ARG_NOT_USED(webp); + ARG_NOT_USED(info); + ARG_NOT_USED(frame); + _noWebpError(); + return -1; +} + +BGD_DECLARE(int) +gdWebpReadNextImage(gdWebpReadPtr webp, gdWebpFrameInfo *info, + gdImagePtr *image) { + ARG_NOT_USED(webp); + ARG_NOT_USED(info); + ARG_NOT_USED(image); + _noWebpError(); + return -1; +} + +BGD_DECLARE(gdImagePtr) gdWebpReadCloneImage(gdWebpReadPtr webp) { + ARG_NOT_USED(webp); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpen(FILE *outFile, const gdWebpWriteOptions *options) { + ARG_NOT_USED(outFile); + ARG_NOT_USED(options); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenCtx(gdIOCtxPtr out, const gdWebpWriteOptions *options) { + ARG_NOT_USED(out); + ARG_NOT_USED(options); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(gdWebpWritePtr) +gdWebpWriteOpenPtr(const gdWebpWriteOptions *options) { + ARG_NOT_USED(options); + _noWebpError(); + return NULL; +} + +BGD_DECLARE(int) +gdWebpWriteAddImage(gdWebpWritePtr webp, gdImagePtr image, int durationMs) { + ARG_NOT_USED(webp); + ARG_NOT_USED(image); + ARG_NOT_USED(durationMs); + _noWebpError(); + return 0; +} + +BGD_DECLARE(void) gdWebpWriteClose(gdWebpWritePtr webp) { + ARG_NOT_USED(webp); + _noWebpError(); +} + +BGD_DECLARE(void *) gdWebpWritePtrFinish(gdWebpWritePtr webp, int *size) { + ARG_NOT_USED(webp); + ARG_NOT_USED(size); + _noWebpError(); + return NULL; +} + #endif /* HAVE_LIBWEBP */ diff --git a/ext/gd/libgd/gd_xbm.c b/ext/gd/libgd/gd_xbm.c index bd81b8685b20..43d374af01ac 100644 --- a/ext/gd/libgd/gd_xbm.c +++ b/ext/gd/libgd/gd_xbm.c @@ -20,6 +20,7 @@ #include #include "gd.h" #include "gdhelpers.h" +#include "gd_intern.h" #include "gd_errors.h" #include "php.h" @@ -27,7 +28,7 @@ #define MAX_XBM_LINE_SIZE 255 /* {{{ gdImagePtr gdImageCreateFromXbm */ -gdImagePtr gdImageCreateFromXbm(FILE * fd) +BGD_DECLARE(gdImagePtr) gdImageCreateFromXbm(FILE * fd) { char fline[MAX_XBM_LINE_SIZE]; char iname[MAX_XBM_LINE_SIZE]; @@ -172,7 +173,7 @@ void gdCtxPrintf(gdIOCtx * out, const char *format, ...) /* }}} */ /* {{{ gdImageXbmCtx */ -void gdImageXbmCtx(gdImagePtr image, char* file_name, int fg, gdIOCtx * out) +BGD_DECLARE(void) gdImageXbmCtx(gdImagePtr image, char* file_name, int fg, gdIOCtx * out) { int x, y, c, b, sx, sy, p; char *name, *f; @@ -182,7 +183,7 @@ void gdImageXbmCtx(gdImagePtr image, char* file_name, int fg, gdIOCtx * out) if ((f = strrchr(name, '/')) != NULL) name = f+1; if ((f = strrchr(name, '\\')) != NULL) name = f+1; name = estrdup(name); - if ((f = strrchr(name, '.')) != NULL && !strcasecmp(f, ".XBM")) *f = '\0'; + if ((f = strrchr(name, '.')) != NULL && !gd_strcasecmp(f, ".XBM")) *f = '\0'; if ((l = strlen(name)) == 0) { efree(name); name = estrdup("image"); diff --git a/ext/gd/libgd/gdcache.c b/ext/gd/libgd/gdcache.c index 0d6e65194a8f..ccaa11200500 100644 --- a/ext/gd/libgd/gdcache.c +++ b/ext/gd/libgd/gdcache.c @@ -17,7 +17,9 @@ * John Ellson (ellson@graphviz.org) Oct 31, 1997 * * Test this with: - * gcc -o gdcache -g -Wall -DTEST gdcache.c + * gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c -lgd + * or + * gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c libgd.a * * The cache is implemented by a singly-linked list of elements * each containing a pointer to a user struct that is being managed by @@ -53,54 +55,46 @@ /* create a new cache */ -gdCache_head_t * -gdCacheCreate ( - int size, - gdCacheTestFn_t gdCacheTest, - gdCacheFetchFn_t gdCacheFetch, - gdCacheReleaseFn_t gdCacheRelease) -{ +gdCache_head_t *gdCacheCreate (int size, gdCacheTestFn_t gdCacheTest, gdCacheFetchFn_t gdCacheFetch,gdCacheReleaseFn_t gdCacheRelease) { gdCache_head_t *head; - head = (gdCache_head_t *) gdPMalloc(sizeof (gdCache_head_t)); + head = (gdCache_head_t *)gdMalloc(sizeof(gdCache_head_t)); + if (!head) { + return NULL; + } + head->mru = NULL; head->size = size; head->gdCacheTest = gdCacheTest; head->gdCacheFetch = gdCacheFetch; head->gdCacheRelease = gdCacheRelease; + return head; } -void -gdCacheDelete (gdCache_head_t * head) -{ +void gdCacheDelete(gdCache_head_t *head) { gdCache_element_t *elem, *prev; elem = head->mru; - while (elem) - { - (*(head->gdCacheRelease)) (elem->userdata); - prev = elem; - elem = elem->next; - gdPFree ((char *) prev); - } - gdPFree ((char *) head); + while (elem) { + (*(head->gdCacheRelease)) (elem->userdata); + prev = elem; + elem = elem->next; + gdFree((char *)prev); + } + gdFree((char *)head); } -void * -gdCacheGet (gdCache_head_t * head, void *keydata) -{ +void *gdCacheGet(gdCache_head_t *head, void *keydata) { int i = 0; gdCache_element_t *elem, *prev = NULL, *prevprev = NULL; void *userdata; elem = head->mru; - while (elem) - { - if ((*(head->gdCacheTest)) (elem->userdata, keydata)) - { - if (i) - { /* if not already most-recently-used */ + while (elem) { + if ((*(head->gdCacheTest))(elem->userdata, keydata)) { + if (i) { + /* if not already most-recently-used */ /* relink to top of list */ prev->next = elem->next; elem->next = head->mru; @@ -114,79 +108,88 @@ gdCacheGet (gdCache_head_t * head, void *keydata) i++; } userdata = (*(head->gdCacheFetch)) (&(head->error), keydata); - if (!userdata) - { - /* if there was an error in the fetch then don't cache */ - return NULL; - } - if (i < head->size) - { /* cache still growing - add new elem */ - elem = (gdCache_element_t *) gdPMalloc(sizeof (gdCache_element_t)); + if (!userdata) { + /* if there was an error in the fetch then don't cache */ + return NULL; + } + + if (i < head->size) { + /* cache still growing - add new elem */ + elem = (gdCache_element_t *)gdMalloc(sizeof(gdCache_element_t)); + if (!elem) { + (*(head->gdCacheRelease))(userdata); + return NULL; } - else - { /* cache full - replace least-recently-used */ - /* preveprev becomes new end of list */ + } else { + /* cache full - replace least-recently-used */ + if (!prevprev) { + /* cache size is 1 */ + head->mru = NULL; + } else { + /* prevprev becomes new end of list */ prevprev->next = NULL; + } elem = prev; + if (!elem) { + /* Happen only with an invalid cache size (<= 0). Bad state but + * still worth handling it here before deref */ + (*(head->gdCacheRelease))(userdata); + return NULL; + } (*(head->gdCacheRelease)) (elem->userdata); } + /* relink to top of list */ elem->next = head->mru; head->mru = elem; elem->userdata = userdata; + return userdata; } - - /*********************************************************/ /* test stub */ /*********************************************************/ - #ifdef TEST #include -typedef struct -{ +typedef struct { int key; int value; -} -key_value_t; +} key_value_t; -static int -cacheTest (void *map, void *key) -{ +static int cacheTest(void *map, void *key) { return (((key_value_t *) map)->key == *(int *) key); } -static void * -cacheFetch (char **error, void *key) -{ +static void *cacheFetch(char **error, void *key) { key_value_t *map; map = (key_value_t *) gdMalloc (sizeof (key_value_t)); + if (!map) { + *error = "gdMalloc failed"; + return NULL; + } map->key = *(int *) key; map->value = 3; *error = NULL; + return (void *) map; } -static void -cacheRelease (void *map) -{ - gdFree ((char *) map); -} +static void cacheRelease(void *map) { gdFree((char *)map); } -int -main (char *argv[], int argc) -{ +int main(int argc, char **argv) { gdCache_head_t *cacheTable; int elem, key; cacheTable = gdCacheCreate (3, cacheTest, cacheFetch, cacheRelease); + if (!cacheTable) { + exit(1); + } key = 20; elem = *(int *) gdCacheGet (cacheTable, &key); diff --git a/ext/gd/libgd/gdcache.h b/ext/gd/libgd/gdcache.h index dd262e3e98da..9e9229efe16a 100644 --- a/ext/gd/libgd/gdcache.h +++ b/ext/gd/libgd/gdcache.h @@ -1,3 +1,7 @@ +#ifdef __cplusplus +extern "C" { +#endif + /* * gdcache.h * @@ -8,7 +12,9 @@ * John Ellson (ellson@graphviz.org) Oct 31, 1997 * * Test this with: - * gcc -o gdcache -g -Wall -DTEST gdcache.c + * gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c -lgd + * or + * gcc -o gdcache -g -Wall -DTEST -DNEED_CACHE gdcache.c libgd.a * * The cache is implemented by a singly-linked list of elements * each containing a pointer to a user struct that is being managed by @@ -69,15 +75,14 @@ struct gdCache_head_s { }; /* function templates */ -gdCache_head_t * -gdCacheCreate( - int size, - gdCacheTestFn_t gdCacheTest, +gdCache_head_t *gdCacheCreate(int size, gdCacheTestFn_t gdCacheTest, gdCacheFetchFn_t gdCacheFetch, gdCacheReleaseFn_t gdCacheRelease ); -void -gdCacheDelete( gdCache_head_t *head ); +void gdCacheDelete(gdCache_head_t *head); -void * -gdCacheGet( gdCache_head_t *head, void *keydata ); +void *gdCacheGet(gdCache_head_t *head, void *keydata); + +#ifdef __cplusplus +} +#endif diff --git a/ext/gd/libgd/gdfontg.c b/ext/gd/libgd/gdfontg.c index edddd0990d8e..d93b30b955d7 100644 --- a/ext/gd/libgd/gdfontg.c +++ b/ext/gd/libgd/gdfontg.c @@ -1,5 +1,3 @@ - - /* This is a header file for gd font, generated using bdftogd version 0.51 by Jan Pazdziora, adelton@fi.muni.cz @@ -10,11 +8,17 @@ "Libor Skarvada, libor@informatics.muni.cz" */ - +/** + * File: Giant Font + * + * A very large ISO-8859-2 raster font (9x15 pixels). + * + * The font is supposed to be used with and + * and their variants. + */ #include "gdfontg.h" -static const char gdFontGiantData[] = -{ +char gdFontGiantData[] = { /* Char 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -4370,20 +4374,15 @@ static const char gdFontGiantData[] = }; -gdFont gdFontGiantRep = -{ - 256, - 0, - 9, - 15, - (char*)gdFontGiantData -}; +gdFont gdFontGiantRep = {256, 0, 9, 15, gdFontGiantData}; -gdFontPtr gdFontGiant = &gdFontGiantRep; +BGD_EXPORT_DATA_PROT gdFontPtr gdFontGiant = &gdFontGiantRep; -gdFontPtr gdFontGetGiant(void) -{ - return gdFontGiant; -} +/** + * Function: gdFontGetGiant + * + * Returns the built-in giant font. + */ +BGD_DECLARE(gdFontPtr) gdFontGetGiant(void) { return gdFontGiant; } /* This file has not been truncated. */ diff --git a/ext/gd/libgd/gdfontg.h b/ext/gd/libgd/gdfontg.h index 8a3e95e8bc82..525cb3d996fe 100644 --- a/ext/gd/libgd/gdfontg.h +++ b/ext/gd/libgd/gdfontg.h @@ -1,4 +1,3 @@ - #ifndef _GDFONTG_H_ #define _GDFONTG_H_ 1 @@ -16,15 +15,13 @@ extern "C" { "Libor Skarvada, libor@informatics.muni.cz" */ - #include "gd.h" -extern gdFontPtr gdFontGiant; -extern gdFontPtr gdFontGetGiant(void); +extern BGD_EXPORT_DATA_PROT gdFontPtr gdFontGiant; +BGD_DECLARE(gdFontPtr) gdFontGetGiant(void); #ifdef __cplusplus } #endif #endif - diff --git a/ext/gd/libgd/gdfontl.c b/ext/gd/libgd/gdfontl.c index 093954ba3c61..3e96ead24651 100644 --- a/ext/gd/libgd/gdfontl.c +++ b/ext/gd/libgd/gdfontl.c @@ -11,10 +11,18 @@ "Libor Skarvada, libor@informatics.muni.cz" */ +/** + * File: Large Font + * + * A large ISO-8859-2 raster font (8x16 pixels). + * + * The font is supposed to be used with and + * and their variants. + */ #include "gdfontl.h" -static const char gdFontLargeData[] = +char gdFontLargeData[] = { /* Char 0 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -4626,21 +4634,16 @@ static const char gdFontLargeData[] = }; +gdFont gdFontLargeRep = {256, 0, 8, 16, gdFontLargeData}; -gdFont gdFontLargeRep = -{ - 256, - 0, - 8, - 16, - (char*)gdFontLargeData -}; - -gdFontPtr gdFontLarge = &gdFontLargeRep; +BGD_EXPORT_DATA_PROT gdFontPtr gdFontLarge = &gdFontLargeRep; -gdFontPtr gdFontGetLarge(void) -{ - return gdFontLarge; -} +/** + * Function: gdFontGetLarge + * + * Returns the built-in large font. + */ +BGD_DECLARE(gdFontPtr) +gdFontGetLarge(void) { return gdFontLarge; } /* This file has not been truncated. */ diff --git a/ext/gd/libgd/gdfontl.h b/ext/gd/libgd/gdfontl.h index 92fee14ff334..24d8cb18da7a 100644 --- a/ext/gd/libgd/gdfontl.h +++ b/ext/gd/libgd/gdfontl.h @@ -1,4 +1,3 @@ - #ifndef _GDFONTL_H_ #define _GDFONTL_H_ 1 @@ -17,15 +16,13 @@ extern "C" { "Libor Skarvada, libor@informatics.muni.cz" */ - #include "gd.h" -extern gdFontPtr gdFontLarge; -extern gdFontPtr gdFontGetLarge(void); +extern BGD_EXPORT_DATA_PROT gdFontPtr gdFontLarge; +BGD_DECLARE(gdFontPtr) gdFontGetLarge(void); #ifdef __cplusplus } #endif #endif - diff --git a/ext/gd/libgd/gdfontmb.c b/ext/gd/libgd/gdfontmb.c index b60110507251..f8d0fff06886 100644 --- a/ext/gd/libgd/gdfontmb.c +++ b/ext/gd/libgd/gdfontmb.c @@ -1,5 +1,3 @@ - - /* This is a header file for gd font, generated using bdftogd version 0.5 by Jan Pazdziora, adelton@fi.muni.cz @@ -9,10 +7,18 @@ No copyright info was found in the original bdf. */ +/** + * File: Medium Bold Font + * + * A medium bold ISO-8859-2 raster font (7x13 pixels). + * + * The font is supposed to be used with and + * and their variants. + */ #include "gdfontmb.h" -static const char gdFontMediumBoldData[] = +char gdFontMediumBoldData[] = { /* Char 0 */ 0, 0, 0, 0, 0, 0, 0, @@ -3857,20 +3863,16 @@ static const char gdFontMediumBoldData[] = }; -gdFont gdFontMediumBoldRep = -{ - 256, - 0, - 7, - 13, - (char*)gdFontMediumBoldData -}; +gdFont gdFontMediumBoldRep = {256, 0, 7, 13, gdFontMediumBoldData}; -gdFontPtr gdFontMediumBold = &gdFontMediumBoldRep; +BGD_EXPORT_DATA_PROT gdFontPtr gdFontMediumBold = &gdFontMediumBoldRep; -gdFontPtr gdFontGetMediumBold(void) -{ - return gdFontMediumBold; -} +/** + * Function: gdFontGetMediumBold + * + * Returns the built-in medium bold font. + */ +BGD_DECLARE(gdFontPtr) +gdFontGetMediumBold(void) { return gdFontMediumBold; } /* This file has not been truncated. */ diff --git a/ext/gd/libgd/gdfontmb.h b/ext/gd/libgd/gdfontmb.h index 2e2f0cbe4798..5ce9b3766491 100644 --- a/ext/gd/libgd/gdfontmb.h +++ b/ext/gd/libgd/gdfontmb.h @@ -1,4 +1,3 @@ - #ifndef _GDFONTMB_H_ #define _GDFONTMB_H_ 1 @@ -15,15 +14,13 @@ extern "C" { No copyright info was found in the original bdf. */ - #include "gd.h" -extern gdFontPtr gdFontMediumBold; -extern gdFontPtr gdFontGetMediumBold(void); +extern BGD_EXPORT_DATA_PROT gdFontPtr gdFontMediumBold; +BGD_DECLARE(gdFontPtr) gdFontGetMediumBold(void); #ifdef __cplusplus } #endif #endif - diff --git a/ext/gd/libgd/gdfonts.c b/ext/gd/libgd/gdfonts.c index bcc0717cafe4..8b1eaa76e722 100644 --- a/ext/gd/libgd/gdfonts.c +++ b/ext/gd/libgd/gdfonts.c @@ -1,5 +1,3 @@ - - /* This is a header file for gd font, generated using bdftogd version 0.5 by Jan Pazdziora, adelton@fi.muni.cz @@ -9,10 +7,18 @@ No copyright info was found in the original bdf. */ +/** + * File: Small Font + * + * A small ISO-8859-2 raster font (6x13 pixels). + * + * The font is supposed to be used with and + * and their variants. + */ #include "gdfonts.h" -static const char gdFontSmallData[] = +char gdFontSmallData[] = { /* Char 0 */ 0, 0, 0, 0, 0, 0, @@ -3856,21 +3862,16 @@ static const char gdFontSmallData[] = }; +gdFont gdFontSmallRep = {256, 0, 6, 13, gdFontSmallData}; -gdFont gdFontSmallRep = -{ - 256, - 0, - 6, - 13, - (char*)gdFontSmallData -}; - -gdFontPtr gdFontSmall = &gdFontSmallRep; +BGD_EXPORT_DATA_PROT gdFontPtr gdFontSmall = &gdFontSmallRep; -gdFontPtr gdFontGetSmall(void) -{ - return gdFontSmall; -} +/** + * Function: gdFontGetSmall + * + * Returns the built-in small font. + */ +BGD_DECLARE(gdFontPtr) +gdFontGetSmall(void) { return gdFontSmall; } /* This file has not been truncated. */ diff --git a/ext/gd/libgd/gdfonts.h b/ext/gd/libgd/gdfonts.h index 55d0e1f0b2ad..d37544587577 100644 --- a/ext/gd/libgd/gdfonts.h +++ b/ext/gd/libgd/gdfonts.h @@ -1,4 +1,3 @@ - #ifndef _GDFONTS_H_ #define _GDFONTS_H_ 1 @@ -15,15 +14,13 @@ extern "C" { No copyright info was found in the original bdf. */ - #include "gd.h" -extern gdFontPtr gdFontSmall; -extern gdFontPtr gdFontGetSmall(void); +extern BGD_EXPORT_DATA_PROT gdFontPtr gdFontSmall; +BGD_DECLARE(gdFontPtr) gdFontGetSmall(void); #ifdef __cplusplus } #endif #endif - diff --git a/ext/gd/libgd/gdfontt.c b/ext/gd/libgd/gdfontt.c index d4e0cf9573d2..2a9b4c2c149f 100644 --- a/ext/gd/libgd/gdfontt.c +++ b/ext/gd/libgd/gdfontt.c @@ -1,5 +1,3 @@ - - /* This is a header file for gd font, generated using bdftogd version 0.5 by Jan Pazdziora, adelton@fi.muni.cz @@ -10,10 +8,17 @@ "Libor Skarvada, libor@informatics.muni.cz" */ - +/** + * File: Tiny Font + * + * A very small ISO-8859-2 raster font (5x8 pixels). + * + * The font is supposed to be used with and + * and their variants. + */ #include "gdfontt.h" -static const char gdFontTinyData[] = +char gdFontTinyData[] = { /* Char 0 */ 0, 0, 0, 0, 0, @@ -2578,20 +2583,16 @@ static const char gdFontTinyData[] = }; -gdFont gdFontTinyRep = -{ - 256, - 0, - 5, - 8, - (char*)gdFontTinyData -}; +gdFont gdFontTinyRep = {256, 0, 5, 8, gdFontTinyData}; -gdFontPtr gdFontTiny = &gdFontTinyRep; +BGD_EXPORT_DATA_PROT gdFontPtr gdFontTiny = &gdFontTinyRep; -gdFontPtr gdFontGetTiny(void) -{ - return gdFontTiny; -} +/** + * Function: gdFontGetTiny + * + * Returns the built-in tiny font. + */ +BGD_DECLARE(gdFontPtr) +gdFontGetTiny(void) { return gdFontTiny; } /* This file has not been truncated. */ diff --git a/ext/gd/libgd/gdfontt.h b/ext/gd/libgd/gdfontt.h index 102fec991b4b..2616a283ebb3 100644 --- a/ext/gd/libgd/gdfontt.h +++ b/ext/gd/libgd/gdfontt.h @@ -1,4 +1,3 @@ - #ifndef _GDFONTT_H_ #define _GDFONTT_H_ 1 @@ -16,15 +15,13 @@ extern "C" { "Libor Skarvada, libor@informatics.muni.cz" */ - #include "gd.h" -extern gdFontPtr gdFontTiny; -extern gdFontPtr gdFontGetTiny(void); +extern BGD_EXPORT_DATA_PROT gdFontPtr gdFontTiny; +BGD_DECLARE(gdFontPtr) gdFontGetTiny(void); #ifdef __cplusplus } #endif #endif - diff --git a/ext/gd/libgd/gdft.c b/ext/gd/libgd/gdft.c index 34a4064f5fcb..a953cb2517ba 100644 --- a/ext/gd/libgd/gdft.c +++ b/ext/gd/libgd/gdft.c @@ -1,69 +1,109 @@ - /********************************************/ /* gd interface to freetype library */ /* */ /* John Ellson ellson@graphviz.org */ /********************************************/ +/** + * File: FreeType font rendering + */ + +#include #include #include #include -#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include "gd.h" +#include "gd_intern.h" #include "gdhelpers.h" -#ifndef _WIN32 +/* The bundled PHP font cache can outlive the request which populated it. */ +#define GD_FT_CACHE_MALLOC(size) gdPMalloc(size) +#define GD_FT_CACHE_FREE(ptr) gdPFree(ptr) +#define GD_FT_ACCESS(path, mode) VCWD_ACCESS(path, mode) +#define GD_FT_IS_PATH(path) \ + (IS_ABSOLUTE_PATH(path, strlen(path)) || strchr(path, '/') || \ + strchr(path, '\\')) + +/* 2.0.10: WIN32, not MSWIN32 */ +#if !defined(_WIN32) && !defined(_WIN32_WCE) #include -#else +#elif defined(_WIN32_WCE) +#include /* getenv() */ +#include /* access() */ +#define getenv wceex_getenv +#define access wceex_access +#else /* _WIN32_WCE */ #include #ifndef R_OK -# define R_OK 04 /* Needed in Windows */ -#endif -#endif - -#ifdef _WIN32 -#define access _access -#ifndef R_OK -#define R_OK 2 +#define R_OK 04 /* Needed in Windows */ #endif #endif /* number of antialised colors for indexed bitmaps */ -/* overwrite Windows GDI define in case of windows build */ -#ifdef NUMCOLORS -#undef NUMCOLORS -#endif -#define NUMCOLORS 8 - -char * -gdImageStringTTF (gdImage * im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string) -{ - /* 2.0.6: valid return */ - return gdImageStringFT (im, brect, fg, fontlist, ptsize, angle, x, y, string); -} +#define GD_NUMCOLORS 8 #ifndef HAVE_LIBFREETYPE -char * -gdImageStringFTEx (gdImage * im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string, - gdFTStringExtraPtr strex) -{ +BGD_DECLARE(char *) gdImageStringFTEx(gdImagePtr im, int *brect, int fg, const char *fontlist, double ptsize, double angle, int x, int y, const char *string, gdFTStringExtraPtr strex) { + (void)im; + (void)brect; + (void)fg; + (void)fontlist; + (void)ptsize; + (void)angle; + (void)x; + (void)y; + (void)string; + (void)strex; + return "libgd was not built with FreeType font support\n"; } -char * -gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string) -{ +BGD_DECLARE(char *) +gdImageStringFT(gdImagePtr im, int *brect, int fg, const char *fontlist, double ptsize, double angle, int x, int y, const char *string) { + (void)im; + (void)brect; + (void)fg; + (void)fontlist; + (void)ptsize; + (void)angle; + (void)x; + (void)y; + (void)string; + return "libgd was not built with FreeType font support\n"; } #else - #include "gdcache.h" +/* 2.0.16 Christophe Thomas: starting with FreeType 2.1.6, this is + mandatory, and it has been supported for a long while. */ +#ifdef HAVE_FT2BUILD_H #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_SIZES_H +#else +#include +#include +#include +#endif + +#ifdef HAVE_LIBFONTCONFIG +static int fontConfigFlag = 0; + +/* translate a fontconfig fontpattern into a fontpath. + return NULL if OK, else return error string */ +static char *font_pattern(char **fontpath, char *fontpattern); +#endif + +#ifdef HAVE_LIBFREETYPE +#include "entities.h" +static char *font_path(char **fontpath, char *name_list); +#endif /* number of fonts cached before least recently used is replaced */ #define FONTCACHESIZE 6 @@ -80,7 +120,8 @@ gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist, /* * The character (space) used to separate alternate fonts in the - * fontlist parameter to gdImageStringFT. 2.0.18: space was a oor choice for this. + * fontlist parameter to gdImageStringFT. 2.0.18: space was a + * poor choice for this. */ #define LISTSEPARATOR ";" @@ -92,69 +133,75 @@ gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist, */ #ifndef DEFAULT_FONTPATH -# if defined(_WIN32) -# define DEFAULT_FONTPATH "C:\\WINDOWS\\FONTS;C:\\WINNT\\FONTS" -# elif defined(__APPLE__) || (defined(__MWERKS__) && defined(macintosh)) -# define DEFAULT_FONTPATH "/usr/share/fonts/truetype:/System/Library/Fonts:/Library/Fonts" -# else - /* default fontpath for unix systems - whatever happened to standards ! */ -# define DEFAULT_FONTPATH "/usr/X11R6/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/truetype:/usr/X11R6/lib/X11/fonts/TTF:/usr/share/fonts/TrueType:/usr/share/fonts/truetype:/usr/openwin/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/Type1:/usr/lib/X11/fonts/Type1:/usr/openwin/lib/X11/fonts/Type1" -# endif +#if defined(_WIN32) +#define DEFAULT_FONTPATH "C:\\WINDOWS\\FONTS;C:\\WINNT\\FONTS" +#elif defined(__APPLE__) || (defined(__MWERKS__) && defined(macintosh)) +#define DEFAULT_FONTPATH "/usr/share/fonts/truetype:/System/Library/Fonts:/Library/Fonts" +#else +/* default fontpath for unix systems - whatever happened to standards ! */ +#define DEFAULT_FONTPATH \ + "/usr/X11R6/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/truetype:/" \ + "usr/X11R6/lib/X11/fonts/TTF:/usr/share/fonts/TrueType:/usr/share/fonts/" \ + "truetype:/usr/openwin/lib/X11/fonts/TrueType:/usr/X11R6/lib/X11/fonts/" \ + "Type1:/usr/lib/X11/fonts/Type1:/usr/openwin/lib/X11/fonts/Type1" +#endif #endif #ifndef PATHSEPARATOR -# if defined(_WIN32) -# define PATHSEPARATOR ";" -# else -# define PATHSEPARATOR ":" -# endif +#if defined(_WIN32) +#define PATHSEPARATOR ";" +#else +#define PATHSEPARATOR ":" +#endif #endif - #ifndef TRUE #define FALSE 0 #define TRUE !FALSE #endif -#ifndef MAX -#define MAX(a,b) ((a)>(b)?(a):(b)) -#endif +/** + * Function: gdImageStringTTF + * + * Alias of . + */ +BGD_DECLARE(char *) +gdImageStringTTF(gdImagePtr im, int *brect, int fg, const char *fontlist, + double ptsize, double angle, int x, int y, + const char *string) { + /* 2.0.6: valid return */ + return gdImageStringFT(im, brect, fg, fontlist, ptsize, angle, x, y, + string); +} -#ifndef MIN -#define MIN(a,b) ((a)<(b)?(a):(b)) -#endif -typedef struct -{ - char *fontlist; /* key */ +typedef struct { + char *fontlist; /* key */ + int flags; /* key */ + char *fontpath; FT_Library *library; FT_Face face; - FT_Bool have_char_map_unicode, have_char_map_big5, have_char_map_sjis, have_char_map_apple_roman; - gdCache_head_t *glyphCache; } font_t; -typedef struct -{ - char *fontlist; /* key */ - int preferred_map; +typedef struct { + const char *fontlist; /* key */ + int flags; /* key */ FT_Library *library; } fontkey_t; -typedef struct -{ - int pixel; /* key */ - int bgcolor; /* key */ - int fgcolor; /* key *//* -ve means no antialias */ - gdImagePtr im; /* key */ +typedef struct { + int pixel; /* key */ + int bgcolor; /* key */ + int fgcolor; /* key */ /* -ve means no antialias */ + gdImagePtr im; /* key */ int tweencolor; } tweencolor_t; -typedef struct -{ - int pixel; /* key */ - int bgcolor; /* key */ - int fgcolor; /* key *//* -ve means no antialias */ - gdImagePtr im; /* key */ +typedef struct { + int pixel; /* key */ + int bgcolor; /* key */ + int fgcolor; /* key */ /* -ve means no antialias */ + gdImagePtr im; /* key */ } tweencolorkey_t; /******************************************************************** @@ -203,7 +250,13 @@ typedef struct #include "jisx0208.h" #endif -extern int any2eucjp (char *, char *, unsigned int); +static int comp_entities(const void *e1, const void *e2) { + struct entities_s *en1 = (struct entities_s *)e1; + struct entities_s *en2 = (struct entities_s *)e2; + return strcmp(en1->name, en2->name); +} + +extern int any2eucjp(char *, const char *, unsigned int); /* Persistent font cache until explicitly cleared */ /* Fonts can be used across multiple images */ @@ -215,23 +268,27 @@ static FT_Library library; #define Tcl_UniChar int #define TCL_UTF_MAX 3 -static int gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr) +static int gdTcl_UtfToUniChar(const char *str, Tcl_UniChar *chPtr) /* str is the UTF8 next character pointer */ /* chPtr is the int for the result */ { int byte; + char entity_name_buf[ENTITY_NAME_LENGTH_MAX + 1]; + char *p; + struct entities_s key, *res; /* HTML4.0 entities in decimal form, e.g. Å */ - byte = *((unsigned char *) str); + /* or in hexadecimal form, e.g. 水 */ + byte = *((unsigned char *)str); if (byte == '&') { int i, n = 0; - byte = *((unsigned char *) (str + 1)); + byte = *((unsigned char *)(str + 1)); if (byte == '#') { - byte = *((unsigned char *) (str + 2)); + byte = *((unsigned char *)(str + 2)); if (byte == 'x' || byte == 'X') { for (i = 3; i < 8; i++) { - byte = *((unsigned char *) (str + i)); + byte = *((unsigned char *)(str + i)); if (byte >= 'A' && byte <= 'F') byte = byte - 'A' + 10; else if (byte >= 'a' && byte <= 'f') @@ -244,7 +301,7 @@ static int gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr) } } else { for (i = 2; i < 8; i++) { - byte = *((unsigned char *) (str + i)); + byte = *((unsigned char *)(str + i)); if (byte >= '0' && byte <= '9') { n = (n * 10) + (byte - '0'); } else { @@ -253,15 +310,32 @@ static int gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr) } } if (byte == ';') { - *chPtr = (Tcl_UniChar) n; + *chPtr = (Tcl_UniChar)n; return ++i; } + } else { + key.name = p = entity_name_buf; + for (i = 1; i <= 1 + ENTITY_NAME_LENGTH_MAX; i++) { + byte = *((unsigned char *)(str + i)); + if (byte == '\0') + break; + if (byte == ';') { + *p++ = '\0'; + res = bsearch(&key, entities, NR_OF_ENTITIES, + sizeof(entities[0]), *comp_entities); + if (res) { + *chPtr = (Tcl_UniChar)res->value; + return ++i; + } + break; + } + *p++ = byte; + } } } /* Unroll 1 to 3 byte UTF-8 sequences, use loop to handle longer ones. */ - - byte = *((unsigned char *) str); + byte = *((unsigned char *)str); #ifdef JISX0208 if (0xA1 <= byte && byte <= 0xFE) { int ku, ten; @@ -269,333 +343,324 @@ static int gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr) ku = (byte & 0x7F) - 0x20; ten = (str[1] & 0x7F) - 0x20; if ((ku < 1 || ku > 92) || (ten < 1 || ten > 94)) { - *chPtr = (Tcl_UniChar) byte; + *chPtr = (Tcl_UniChar)byte; return 1; } - *chPtr = (Tcl_UniChar) UnicodeTbl[ku - 1][ten - 1]; + *chPtr = (Tcl_UniChar)UnicodeTbl[ku - 1][ten - 1]; return 2; } else #endif /* JISX0208 */ - if (byte < 0xC0) { - /* Handles properly formed UTF-8 characters between - * 0x01 and 0x7F. Also treats \0 and naked trail - * bytes 0x80 to 0xBF as valid characters representing - * themselves. - */ - - *chPtr = (Tcl_UniChar) byte; - return 1; - } else if (byte < 0xE0) { - if ((str[1] & 0xC0) == 0x80) { - /* Two-byte-character lead-byte followed by a trail-byte. */ - - *chPtr = (Tcl_UniChar) (((byte & 0x1F) << 6) | (str[1] & 0x3F)); - return 2; - } - /* - * A two-byte-character lead-byte not followed by trail-byte - * represents itself. - */ + if (byte < 0xC0) { + /* Handles properly formed UTF-8 characters between + * 0x01 and 0x7F. Also treats \0 and naked trail + * bytes 0x80 to 0xBF as valid characters representing + * themselves. + */ - *chPtr = (Tcl_UniChar) byte; - return 1; - } else if (byte < 0xF0) { - if (((str[1] & 0xC0) == 0x80) && ((str[2] & 0xC0) == 0x80)) { - /* Three-byte-character lead byte followed by two trail bytes. */ + *chPtr = (Tcl_UniChar)byte; + return 1; + } else if (byte < 0xE0) { + if ((str[1] & 0xC0) == 0x80) { + /* Two-byte-character lead-byte followed by a trail-byte. */ + *chPtr = (Tcl_UniChar)(((byte & 0x1F) << 6) | (str[1] & 0x3F)); + return 2; + } + /* + * A two-byte-character lead-byte not followed by trail-byte + * represents itself. + */ - *chPtr = (Tcl_UniChar) (((byte & 0x0F) << 12) | ((str[1] & 0x3F) << 6) | (str[2] & 0x3F)); - return 3; + *chPtr = (Tcl_UniChar)byte; + return 1; + } else if (byte < 0xF0) { + if (((str[1] & 0xC0) == 0x80) && ((str[2] & 0xC0) == 0x80)) { + /* Three-byte-character lead byte followed by two trail bytes. */ + *chPtr = (Tcl_UniChar)(((byte & 0x0F) << 12) | ((str[1] & 0x3F) << 6) | (str[2] & 0x3F)); + return 3; + } + /* A three-byte-character lead-byte not followed by two trail-bytes represents itself. */ + *chPtr = (Tcl_UniChar)byte; + return 1; } - /* A three-byte-character lead-byte not followed by two trail-bytes represents itself. */ - - *chPtr = (Tcl_UniChar) byte; - return 1; - } #if TCL_UTF_MAX > 3 - else { - int ch, total, trail; - - total = totalBytes[byte]; - trail = total - 1; - - if (trail > 0) { - ch = byte & (0x3F >> trail); - do { - str++; - if ((*str & 0xC0) != 0x80) { - *chPtr = byte; - return 1; - } - ch <<= 6; - ch |= (*str & 0x3F); - trail--; - } while (trail > 0); - *chPtr = ch; - return total; + else { + int ch, total, trail; + + total = totalBytes[byte]; + trail = total - 1; + if (trail > 0) { + ch = byte & (0x3F >> trail); + do { + str++; + if ((*str & 0xC0) != 0x80) { + *chPtr = byte; + return 1; + } + ch <<= 6; + ch |= (*str & 0x3F); + trail--; + } while (trail > 0); + *chPtr = ch; + return total; + } } - } #endif - *chPtr = (Tcl_UniChar) byte; + *chPtr = (Tcl_UniChar)byte; return 1; } -/********************************************************************/ -/* font cache functions */ +#ifdef HAVE_LIBRAQM +#include +#else +#define RAQM_VERSION_ATLEAST(a, b, c) 0 +#endif -static int fontTest (void *element, void *key) -{ - font_t *a = (font_t *) element; - fontkey_t *b = (fontkey_t *) key; - - if (strcmp (a->fontlist, b->fontlist) == 0) { - switch (b->preferred_map) { - case gdFTEX_Unicode: - if (a->have_char_map_unicode) { - return 1; - } - break; - case gdFTEX_Shift_JIS: - if (a->have_char_map_sjis) { - return 1; - } - break; - case gdFTEX_Big5: - if (a->have_char_map_sjis) { - return 1; - } - break; - } +typedef struct { + unsigned int index; + FT_Pos x_advance; + FT_Pos y_advance; + FT_Pos x_offset; + FT_Pos y_offset; + uint32_t cluster; +} glyphInfo; + +static ssize_t textLayout(uint32_t *text, size_t len, FT_Face face, + gdFTStringExtraPtr strex, glyphInfo **glyph_info) { + size_t count; + glyphInfo *info; + + if (!len) { + return 0; } - return 0; -} -static void *fontFetch (char **error, void *key) -{ - font_t *a; - fontkey_t *b = (fontkey_t *) key; - int n; - int font_found = 0; - unsigned short platform, encoding; - char *fontsearchpath, *fontlist; - char fullname[MAXPATHLEN], cur_dir[MAXPATHLEN]; - char *name, *path=NULL, *dir; - char *strtok_ptr; - FT_Error err; - FT_CharMap found = 0; - FT_CharMap charmap; +#ifdef HAVE_LIBRAQM + size_t i; + raqm_glyph_t *glyphs; + raqm_t *rq = raqm_create(); - a = (font_t *) gdPMalloc(sizeof(font_t)); - a->fontlist = gdPEstrdup(b->fontlist); - a->library = b->library; + if (!rq || !raqm_set_text(rq, text, len) || + !raqm_set_freetype_face(rq, face) || + !raqm_set_par_direction(rq, RAQM_DIRECTION_DEFAULT) || + !raqm_layout(rq)) { + raqm_destroy(rq); + return -1; + } - /* - * Search the pathlist for any of a list of font names. - */ - fontsearchpath = getenv ("GDFONTPATH"); - if (!fontsearchpath) { - fontsearchpath = DEFAULT_FONTPATH; + glyphs = raqm_get_glyphs(rq, &count); + if (!glyphs) { + raqm_destroy(rq); + return -1; } - fontlist = gdEstrdup(a->fontlist); - /* - * Must use gd_strtok_r becasuse strtok() isn't thread safe - */ - for (name = gd_strtok_r (fontlist, LISTSEPARATOR, &strtok_ptr); name; name = gd_strtok_r (0, LISTSEPARATOR, &strtok_ptr)) { - char *strtok_ptr_path; - /* make a fresh copy each time - strtok corrupts it. */ - path = gdEstrdup (fontsearchpath); - - /* if name is an absolute filename then test directly */ - /* Actual length doesn't matter, just the minimum does up to length 2. */ - unsigned int min_length = 0; - if (name[0] != '\0') { - if (name[1] != '\0') { - min_length = 2; - } else { - min_length = 1; - } - } - ZEND_IGNORE_VALUE(min_length); /* On Posix systems this may be unused */ - if (IS_ABSOLUTE_PATH(name, min_length)) { - snprintf(fullname, sizeof(fullname) - 1, "%s", name); - if (access(fullname, R_OK) == 0) { - font_found++; - break; - } + info = (glyphInfo *)gdMalloc(sizeof(glyphInfo) * count); + if (!info) { + raqm_destroy(rq); + return -1; + } + + for (i = 0; i < count; i++) { + info[i].index = glyphs[i].index; + info[i].x_offset = glyphs[i].x_offset; + info[i].y_offset = glyphs[i].y_offset; + info[i].x_advance = glyphs[i].x_advance; + info[i].y_advance = glyphs[i].y_advance; + info[i].cluster = glyphs[i].cluster; + } + + raqm_destroy(rq); +#else + FT_UInt glyph_index = 0, previous = 0; + FT_Vector delta; + FT_Error err; + info = (glyphInfo *)gdMalloc(sizeof(glyphInfo) * len); + if (!info) { + return -1; + } + for (count = 0; count < len; count++) { + /* Convert character code to glyph index */ + glyph_index = FT_Get_Char_Index(face, text[count]); + + /* retrieve kerning distance */ + if (!(strex && (strex->flags & gdFTEX_DISABLE_KERNING)) && + !FT_IS_FIXED_WIDTH(face) && FT_HAS_KERNING(face) && previous && + glyph_index) + FT_Get_Kerning(face, previous, glyph_index, ft_kerning_default, + &delta); + else + delta.x = delta.y = 0; + + err = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (err) { + gdFree(info); + return -1; } - for (dir = gd_strtok_r (path, PATHSEPARATOR, &strtok_ptr_path); dir; - dir = gd_strtok_r (0, PATHSEPARATOR, &strtok_ptr_path)) { - if (!strcmp(dir, ".")) { - #ifdef HAVE_GETCWD - dir = VCWD_GETCWD(cur_dir, MAXPATHLEN); -#elif defined(HAVE_GETWD) - dir = VCWD_GETWD(cur_dir); + info[count].index = glyph_index; + info[count].x_offset = 0; + info[count].y_offset = 0; + if (delta.x != 0) + info[count - 1].x_advance += delta.x; + info[count].x_advance = face->glyph->metrics.horiAdvance; + info[count].y_advance = 0; + info[count].cluster = count; + + /* carriage returns or newlines */ + if (text[count] == '\r' || text[count] == '\n') + previous = 0; /* clear kerning flag */ + else + previous = glyph_index; + } #endif - if (!dir) { - continue; - } - } -#define GD_CHECK_FONT_PATH(ext) \ - snprintf(fullname, sizeof(fullname) - 1, "%s/%s%s", dir, name, ext); \ - if (access(fullname, R_OK) == 0) { \ - font_found++; \ - break; \ - } \ - - GD_CHECK_FONT_PATH(""); - GD_CHECK_FONT_PATH(".ttf"); - GD_CHECK_FONT_PATH(".pfa"); - GD_CHECK_FONT_PATH(".pfb"); - GD_CHECK_FONT_PATH(".dfont"); - } - gdFree(path); - path = NULL; - if (font_found) { - break; - } + *glyph_info = info; + if (count <= (size_t)SSIZE_MAX) { + return (ssize_t)count; } + return -1; +} - if (path) { - gdFree(path); +/********************************************************************/ +/* font cache functions */ + +static int fontTest(void *element, void *key) { + font_t *a = (font_t *)element; + fontkey_t *b = (fontkey_t *)key; + + return a->flags == b->flags && strcmp(a->fontlist, b->fontlist) == 0; +} + +#ifdef HAVE_LIBFONTCONFIG +static int useFontConfig(int flag) { + if (fontConfigFlag) { + return (!(flag & gdFTEX_FONTPATHNAME)); } + return flag & gdFTEX_FONTCONFIG; +} +#endif - gdFree(fontlist); +static void *fontFetch(char **error, void *key) { + font_t *a; + fontkey_t *b = (fontkey_t *)key; + char *fontpath = NULL; + char *suffix; + FT_Error err; + const unsigned int b_font_list_len = strlen(b->fontlist); - if (!font_found) { - gdPFree(a->fontlist); - gdPFree(a); - *error = "Could not find/open font"; + *error = NULL; + + a = (font_t *)GD_FT_CACHE_MALLOC(sizeof(font_t)); + if (!a) { + *error = "could not alloc font cache entry"; return NULL; } - err = FT_New_Face (*b->library, fullname, 0, &a->face); - if (err) { - gdPFree(a->fontlist); - gdPFree(a); - *error = "Could not read font"; + a->fontlist = (char *)GD_FT_CACHE_MALLOC(b_font_list_len + 1); + if (a->fontlist == NULL) { + GD_FT_CACHE_FREE(a); + *error = "could not alloc full list of fonts"; return NULL; } + memcpy(a->fontlist, b->fontlist, b_font_list_len); + a->fontlist[b_font_list_len] = 0; - /* FIXME - This mapping stuff is incomplete - where is the spec? */ - /* EAM - It's worse than that. It's pointless to match character encodings here. - * As currently written, the stored a->face->charmap only matches one of - * the actual charmaps and we cannot know at this stage if it is the right - * one. We should just skip all this stuff, and check in gdImageStringFTEx - * if some particular charmap is preferred and if so whether it is held in - * one of the a->face->charmaps[0..num_charmaps]. - * And why is it so bad not to find any recognized charmap? The user may - * still know what mapping to use, even if we do not. In that case we can - * just use the map in a->face->charmaps[num_charmaps] and be done with it. - */ + a->flags = b->flags; + a->library = b->library; + a->fontpath = NULL; + +#ifdef HAVE_LIBFONTCONFIG + if (!useFontConfig(b->flags)) + *error = font_path(&fontpath, a->fontlist); + else + *error = font_pattern(&fontpath, a->fontlist); +#else + *error = font_path(&fontpath, a->fontlist); +#endif /* HAVE_LIBFONTCONFIG */ + if (*error || !fontpath || !fontpath[0]) { + GD_FT_CACHE_FREE(a->fontlist); + gdFree(fontpath); + GD_FT_CACHE_FREE(a); + + if (!*error) + *error = "font_path() returned an empty font pathname"; - for (n = 0; n < a->face->num_charmaps; n++) { - charmap = a->face->charmaps[n]; - platform = charmap->platform_id; - encoding = charmap->encoding_id; - - /* Whatever is the last value is what should be set */ - a->have_char_map_unicode = 0; - a->have_char_map_big5 = 0; - a->have_char_map_sjis = 0; - a->have_char_map_apple_roman = 0; - -/* EAM DEBUG - Newer versions of libfree2 make it easier by defining encodings */ -#if (defined(FREETYPE_MAJOR) && ((FREETYPE_MAJOR == 2 && ((FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 3) || FREETYPE_MINOR > 1) || FREETYPE_MAJOR > 2))) - if (charmap->encoding == FT_ENCODING_MS_SYMBOL - || charmap->encoding == FT_ENCODING_ADOBE_CUSTOM - || charmap->encoding == FT_ENCODING_ADOBE_STANDARD) { - a->have_char_map_unicode = 1; - found = charmap; - a->face->charmap = charmap; - return (void *)a; + return NULL; } -#endif /* Freetype 2.1.3 or better */ -/* EAM DEBUG */ - - if ((platform == 3 && encoding == 1) /* Windows Unicode */ - || (platform == 3 && encoding == 0) /* Windows Symbol */ - || (platform == 2 && encoding == 1) /* ISO Unicode */ - || (platform == 0)) - { /* Apple Unicode */ - a->have_char_map_unicode = 1; - found = charmap; - if (b->preferred_map == gdFTEX_Unicode) { - break; - } - } else if (platform == 3 && encoding == 4) { /* Windows Big5 */ - a->have_char_map_big5 = 1; - found = charmap; - if (b->preferred_map == gdFTEX_Big5) { - break; - } - } else if (platform == 3 && encoding == 2) { /* Windows Sjis */ - a->have_char_map_sjis = 1; - found = charmap; - if (b->preferred_map == gdFTEX_Shift_JIS) { - break; - } - } else if ((platform == 1 && encoding == 0) /* Apple Roman */ - || (platform == 2 && encoding == 0)) - { /* ISO ASCII */ - a->have_char_map_apple_roman = 1; - found = charmap; - if (b->preferred_map == gdFTEX_MacRoman) { - break; - } - } + a->fontpath = (char *)GD_FT_CACHE_MALLOC(strlen(fontpath) + 1); + if (!a->fontpath) { + GD_FT_CACHE_FREE(a->fontlist); + gdFree(fontpath); + GD_FT_CACHE_FREE(a); + *error = "could not alloc font path"; + return NULL; + } + memcpy(a->fontpath, fontpath, strlen(fontpath) + 1); + gdFree(fontpath); + + err = FT_New_Face(*b->library, a->fontpath, 0, &a->face); + + /* Read kerning metrics for Postscript fonts. */ + if (!err && + ((suffix = strstr(a->fontpath, ".pfa")) || + (suffix = strstr(a->fontpath, ".pfb"))) && + ((strcpy(suffix, ".afm") && (GD_FT_ACCESS(a->fontpath, R_OK) == 0)) || + (strcpy(suffix, ".pfm") && (GD_FT_ACCESS(a->fontpath, R_OK) == 0)))) { + err = FT_Attach_File(a->face, a->fontpath); } - if (!found) { - gdPFree(a->fontlist); - gdPFree(a); - *error = "Unable to find a CharMap that I can handle"; + + if (err) { + GD_FT_CACHE_FREE(a->fontlist); + GD_FT_CACHE_FREE(a->fontpath); + GD_FT_CACHE_FREE(a); + *error = "Could not read font"; return NULL; } - /* 2.0.5: we should actually return this */ - a->face->charmap = found; - return (void *) a; + return (void *)a; } -static void fontRelease (void *element) -{ - font_t *a = (font_t *) element; +static void fontRelease(void *element) { + font_t *a = (font_t *)element; - FT_Done_Face (a->face); - gdPFree(a->fontlist); - gdPFree((char *) element); + FT_Done_Face(a->face); + GD_FT_CACHE_FREE(a->fontlist); + GD_FT_CACHE_FREE(a->fontpath); + GD_FT_CACHE_FREE((char *)element); } /********************************************************************/ /* tweencolor cache functions */ -static int tweenColorTest (void *element, void *key) -{ - tweencolor_t *a = (tweencolor_t *) element; - tweencolorkey_t *b = (tweencolorkey_t *) key; +static int tweenColorTest(void *element, void *key) { + tweencolor_t *a = (tweencolor_t *)element; + tweencolorkey_t *b = (tweencolorkey_t *)key; - return (a->pixel == b->pixel && a->bgcolor == b->bgcolor && a->fgcolor == b->fgcolor && a->im == b->im); + return (a->pixel == b->pixel && a->bgcolor == b->bgcolor && + a->fgcolor == b->fgcolor && a->im == b->im); } /* * Computes a color in im's color table that is part way between * the background and foreground colors proportional to the gray - * pixel value in the range 0-NUMCOLORS. The fg and bg colors must already + * pixel value in the range 0-GD_NUMCOLORS. The fg and bg colors must already * be in the color table for palette images. For truecolor images the * returned value simply has an alpha component and gdImageAlphaBlend * does the work so that text can be alpha blended across a complex * background (TBB; and for real in 2.0.2). */ -static void * tweenColorFetch (char **error, void *key) -{ +static void *tweenColorFetch(char **error, void *key) { tweencolor_t *a; - tweencolorkey_t *b = (tweencolorkey_t *) key; + tweencolorkey_t *b = (tweencolorkey_t *)key; int pixel, npixel, bg, fg; gdImagePtr im; - a = (tweencolor_t *) gdMalloc (sizeof (tweencolor_t)); + (void)error; + + a = (tweencolor_t *)gdMalloc(sizeof(tweencolor_t)); + if (!a) { + return NULL; + } + pixel = a->pixel = b->pixel; bg = a->bgcolor = b->bgcolor; fg = a->fgcolor = b->fgcolor; @@ -603,47 +668,43 @@ static void * tweenColorFetch (char **error, void *key) /* if fg is specified by a negative color idx, then don't antialias */ if (fg < 0) { - if ((pixel + pixel) >= NUMCOLORS) { + if ((pixel + pixel) >= GD_NUMCOLORS) a->tweencolor = -fg; - } else { + else a->tweencolor = bg; - } } else { - npixel = NUMCOLORS - pixel; + npixel = GD_NUMCOLORS - pixel; if (im->trueColor) { /* 2.0.1: use gdImageSetPixel to do the alpha blending work, - * or to just store the alpha level. All we have to do here - * is incorporate our knowledge of the percentage of this - * pixel that is really "lit" by pushing the alpha value - * up toward transparency in edge regions. - */ + or to just store the alpha level. All we have to do here + is incorporate our knowledge of the percentage of this + pixel that is really "lit" by pushing the alpha value + up toward transparency in edge regions. */ a->tweencolor = gdTrueColorAlpha( - gdTrueColorGetRed(fg), - gdTrueColorGetGreen(fg), - gdTrueColorGetBlue(fg), - gdAlphaMax - (gdTrueColorGetAlpha (fg) * pixel / NUMCOLORS)); + gdTrueColorGetRed(fg), gdTrueColorGetGreen(fg), + gdTrueColorGetBlue(fg), + gdAlphaMax - (gdTrueColorGetAlpha(fg) * pixel / GD_NUMCOLORS)); } else { - a->tweencolor = gdImageColorResolve(im, - (pixel * im->red[fg] + npixel * im->red[bg]) / NUMCOLORS, - (pixel * im->green[fg] + npixel * im->green[bg]) / NUMCOLORS, - (pixel * im->blue[fg] + npixel * im->blue[bg]) / NUMCOLORS); + a->tweencolor = gdImageColorResolve( + im, (pixel * im->red[fg] + npixel * im->red[bg]) / GD_NUMCOLORS, + (pixel * im->green[fg] + npixel * im->green[bg]) / GD_NUMCOLORS, + (pixel * im->blue[fg] + npixel * im->blue[bg]) / GD_NUMCOLORS); } } - return (void *) a; + return (void *)a; } -static void tweenColorRelease (void *element) -{ - gdFree((char *) element); -} +static void tweenColorRelease(void *element) { gdFree((char *)element); } /* draw_bitmap - transfers glyph bitmap to GD image */ -static char * gdft_draw_bitmap (gdCache_head_t *tc_cache, gdImage * im, int fg, FT_Bitmap bitmap, int pen_x, int pen_y) -{ +static char *gdft_draw_bitmap(gdCache_head_t *tc_cache, gdImagePtr im, int fg, + FT_Bitmap bitmap, int pen_x, int pen_y) { unsigned char *pixel = NULL; int *tpixel = NULL; int opixel; - int x, y, row, col, pc, pcr; + int x, y, pc, pcr; + unsigned int col; + unsigned int row; tweencolor_t *tc_elem; tweencolorkey_t tc_key; @@ -659,39 +720,43 @@ static char * gdft_draw_bitmap (gdCache_head_t *tc_cache, gdImage * im, int fg, y = pen_y + row; /* clip if out of bounds */ /* 2.0.16: clipping rectangle, not image bounds */ - if ((y > im->cy2) || (y < im->cy1)) { + if ((y > im->cy2) || (y < im->cy1)) continue; - } for (col = 0; col < bitmap.width; col++, pc++) { int level; if (bitmap.pixel_mode == ft_pixel_mode_grays) { - /* Scale to 128 levels of alpha for gd use. + /* + * Scale to 128 levels of alpha for gd use. * alpha 0 is opacity, so be sure to invert at the end */ - level = (bitmap.buffer[pc] * gdAlphaMax / (bitmap.num_grays - 1)); + level = (bitmap.buffer[pc] * gdAlphaMax / + (bitmap.num_grays - 1)); } else if (bitmap.pixel_mode == ft_pixel_mode_mono) { /* 2.0.5: mode_mono fix from Giuliano Pochini */ - level = ((bitmap.buffer[(col>>3)+pcr]) & (1<<(~col&0x07))) ? gdAlphaTransparent : gdAlphaOpaque; + level = ((bitmap.buffer[(col >> 3) + pcr]) & + (1 << (~col & 0x07))) + ? gdAlphaTransparent + : gdAlphaOpaque; } else { return "Unsupported ft_pixel_mode"; } - if (level == 0) /* if background */ + if (level == 0) /* if background */ continue; + if ((fg >= 0) && (im->trueColor)) { /* Consider alpha in the foreground color itself to be an - * upper bound on how opaque things get, when truecolor is - * available. Without truecolor this results in far too many - * color indexes. - */ - level = level * (gdAlphaMax - gdTrueColorGetAlpha(fg)) / gdAlphaMax; + upper bound on how opaque things get, when truecolor is + available. Without truecolor this results in far too many + color indexes. */ + level = level * (gdAlphaMax - gdTrueColorGetAlpha(fg)) / + gdAlphaMax; } - level = gdAlphaMax - level; + level = gdAlphaMax - level; /* inverting to get alpha */ x = pen_x + col; /* clip if out of bounds */ /* 2.0.16: clip to clipping rectangle, Matt McNabb */ - if ((x > im->cx2) || (x < im->cx1)) { + if ((x > im->cx2) || (x < im->cx1)) continue; - } /* get pixel location in gd buffer */ tpixel = &im->tpixels[y][x]; if (fg < 0) { @@ -702,8 +767,8 @@ static char * gdft_draw_bitmap (gdCache_head_t *tc_cache, gdImage * im, int fg, if (im->alphaBlendingFlag) { opixel = *tpixel; if (gdTrueColorGetAlpha(opixel) != gdAlphaTransparent) { - *tpixel = gdAlphaBlend (opixel, - (level << 24) + (fg & 0xFFFFFF)); + *tpixel = gdAlphaBlend(opixel, (level << 24) + + (fg & 0xFFFFFF)); } else { *tpixel = (level << 24) + (fg & 0xFFFFFF); } @@ -713,100 +778,147 @@ static char * gdft_draw_bitmap (gdCache_head_t *tc_cache, gdImage * im, int fg, } } } - return (char *) NULL; + return (char *)NULL; } /* Non-truecolor case, restored to its more or less original form */ for (row = 0; row < bitmap.rows; row++) { int pcr; pc = row * bitmap.pitch; pcr = pc; - if (bitmap.pixel_mode==ft_pixel_mode_mono) { - pc *= 8; /* pc is measured in bits for monochrome images */ - } + if (bitmap.pixel_mode == ft_pixel_mode_mono) + pc *= 8; /* pc is measured in bits for monochrome images */ + y = pen_y + row; /* clip if out of bounds */ - if (y > im->cy2 || y < im->cy1) { + if (y > im->cy2 || y < im->cy1) continue; - } for (col = 0; col < bitmap.width; col++, pc++) { if (bitmap.pixel_mode == ft_pixel_mode_grays) { /* - * Round to NUMCOLORS levels of antialiasing for + * Round to GD_NUMCOLORS levels of antialiasing for * index color images since only 256 colors are * available. */ - tc_key.pixel = ((bitmap.buffer[pc] * NUMCOLORS) + bitmap.num_grays / 2) / (bitmap.num_grays - 1); + tc_key.pixel = ((bitmap.buffer[pc] * GD_NUMCOLORS) + + bitmap.num_grays / 2) / + (bitmap.num_grays - 1); } else if (bitmap.pixel_mode == ft_pixel_mode_mono) { - tc_key.pixel = ((bitmap.buffer[pc / 8] << (pc % 8)) & 128) ? NUMCOLORS : 0; /* 2.0.5: mode_mono fix from Giuliano Pochini */ - tc_key.pixel = ((bitmap.buffer[(col>>3)+pcr]) & (1<<(~col&0x07))) ? NUMCOLORS : 0; + tc_key.pixel = + ((bitmap.buffer[(col >> 3) + pcr]) & (1 << (~col & 0x07))) + ? GD_NUMCOLORS + : 0; } else { return "Unsupported ft_pixel_mode"; } - if (tc_key.pixel > 0) { /* if not background */ - x = pen_x + col; + if (tc_key.pixel == 0) /* if background */ + continue; - /* clip if out of bounds */ - if (x > im->cx2 || x < im->cx1) { - continue; - } - /* get pixel location in gd buffer */ - pixel = &im->pixels[y][x]; - if (tc_key.pixel == NUMCOLORS) { - /* use fg color directly. gd 2.0.2: watch out for - * negative indexes (thanks to David Marwood). - */ - *pixel = (fg < 0) ? -fg : fg; - } else { - /* find antialised color */ - tc_key.bgcolor = *pixel; - tc_elem = (tweencolor_t *) gdCacheGet(tc_cache, &tc_key); - *pixel = tc_elem->tweencolor; - } + x = pen_x + col; + + /* clip if out of bounds */ + if (x > im->cx2 || x < im->cx1) + continue; + /* get pixel location in gd buffer */ + pixel = &im->pixels[y][x]; + if (tc_key.pixel == GD_NUMCOLORS) { + /* use fg color directly. gd 2.0.2: watch out for + negative indexes (thanks to David Marwood). */ + *pixel = (fg < 0) ? -fg : fg; + } else { + /* find antialised color */ + + tc_key.bgcolor = *pixel; + tc_elem = (tweencolor_t *)gdCacheGet(tc_cache, &tc_key); + if (!tc_elem) + return tc_cache->error; + *pixel = tc_elem->tweencolor; } } } - return (char *) NULL; + return (char *)NULL; } -static int -gdroundupdown (FT_F26Dot6 v1, int roundup) -{ - return (!roundup) ? v1 >> 6 : (v1 + 63) >> 6; +/** + * Function: gdFreeFontCache + * + * Alias of . + */ +BGD_DECLARE(void) gdFreeFontCache() { gdFontCacheShutdown(); } + +BGD_DECLARE(void) gdFontCacheMutexSetup(void) { + gdMutexSetup(gdFontCacheMutex); } -void gdFontCacheShutdown() -{ - gdMutexLock(gdFontCacheMutex); +BGD_DECLARE(void) gdFontCacheMutexShutdown(void) { + gdMutexShutdown(gdFontCacheMutex); +} +/** + * Function: gdFontCacheShutdown + * + * Shut down the font cache and free the allocated resources. + * + * Important: + * This function has to be called whenever FreeType operations have been + * invoked, to avoid resource leaks. It doesn't harm to call this function + * multiple times. + */ +BGD_DECLARE(void) gdFontCacheShutdown() { if (fontCache) { + gdMutexLock(gdFontCacheMutex); gdCacheDelete(fontCache); - fontCache = NULL; + /* 2.0.16: Gustavo Scotti: make sure we don't free this twice */ + fontCache = 0; + gdMutexUnlock(gdFontCacheMutex); FT_Done_FreeType(library); } - - gdMutexUnlock(gdFontCacheMutex); -} - -void gdFreeFontCache(void) -{ - gdFontCacheShutdown(); -} - -void gdFontCacheMutexSetup() -{ - gdMutexSetup(gdFontCacheMutex); } -void gdFontCacheMutexShutdown() -{ - gdMutexShutdown(gdFontCacheMutex); +/** + * Function: gdImageStringFT + * + * Render an UTF-8 string onto a gd image. + * + * Parameters: + * im - The image to draw onto. + * brect - The bounding rectangle as array of 8 integers where each pair + * represents the x- and y-coordinate of a point. The points + * specify the lower left, lower right, upper right and upper left + * corner. + * fg - The font color. + * fontlist - The semicolon delimited list of font filenames to look for. + * ptsize - The height of the font in typographical points (pt). + * angle - The angle in radian to rotate the font counter-clockwise. + * x - The x-coordinate of the basepoint (roughly the lower left corner) + *of the first letter. y - The y-coordinate of the basepoint (roughly the + *lower left corner) of the first letter. string - The string to render. + * + * Variant: + * - + * + * See also: + * - + */ +BGD_DECLARE(char *) +gdImageStringFT(gdImagePtr im, int *brect, int fg, const char *fontlist, + double ptsize, double angle, int x, int y, const char *string) { + return gdImageStringFTEx(im, brect, fg, fontlist, ptsize, angle, x, y, + string, 0); } -int gdFontCacheSetup(void) -{ +/** + * Function: gdFontCacheSetup + * + * Set up the font cache. + * + * This is called automatically from the string rendering functions, if it + * has not already been called. So there's no need to call this function + * explicitly. + */ +BGD_DECLARE(int) gdFontCacheSetup(void) { if (fontCache) { /* Already set up */ return 0; @@ -814,427 +926,979 @@ int gdFontCacheSetup(void) if (FT_Init_FreeType(&library)) { return -1; } - fontCache = gdCacheCreate (FONTCACHESIZE, fontTest, fontFetch, fontRelease); + fontCache = gdCacheCreate(FONTCACHESIZE, fontTest, fontFetch, fontRelease); + if (!fontCache) { + return -2; + } return 0; } +/* + Function: gdImageStringFTEx -/********************************************************************/ -/* gdImageStringFT - render a utf8 string onto a gd image */ + gdImageStringFTEx extends the capabilities of gdImageStringFT by + providing a way to pass additional parameters. -char * -gdImageStringFT (gdImage * im, int *brect, int fg, char *fontlist, - double ptsize, double angle, int x, int y, char *string) -{ - return gdImageStringFTEx(im, brect, fg, fontlist, ptsize, angle, x, y, string, 0); -} + If the strex parameter is not null, it must point to a + gdFTStringExtra structure. As of gd 2.0.5, this structure is defined + as follows: + (start code) -char * -gdImageStringFTEx (gdImage * im, int *brect, int fg, char *fontlist, double ptsize, double angle, int x, int y, char *string, gdFTStringExtraPtr strex) -{ - FT_BBox bbox, glyph_bbox; - FT_Matrix matrix; - FT_Vector pen, delta, penf; - FT_Face face; - FT_Glyph image; - FT_GlyphSlot slot; - FT_Bool use_kerning; - FT_UInt glyph_index, previous; - double sin_a = sin (angle); - double cos_a = cos (angle); - int len, i = 0, ch; - int x1 = 0, y1 = 0; - font_t *font; - fontkey_t fontkey; - char *next; - char *tmpstr = NULL; - int render = (im && (im->trueColor || (fg <= 255 && fg >= -255))); - FT_BitmapGlyph bm; - /* 2.0.13: Bob Ostermann: don't force autohint, that's just for testing freetype and doesn't look as good */ - int render_mode = FT_LOAD_DEFAULT; - int m, mfound; - /* Now tuneable thanks to Wez Furlong */ - double linespace = LINESPACE; - /* 2.0.6: put this declaration with the other declarations! */ - /* - * make a new tweenColorCache on every call - * because caching colormappings between calls - * is not safe. If the im-pointer points to a - * brand new image, the cache gives out bogus - * colorindexes. -- 27.06.2001 - */ - gdCache_head_t *tc_cache; - /* Tuneable horizontal and vertical resolution in dots per inch */ - int hdpi, vdpi; + typedef struct { + // logical OR of gdFTEX_ values + int flags; - if (strex && ((strex->flags & gdFTEX_LINESPACE) == gdFTEX_LINESPACE)) { - linespace = strex->linespacing; - } - tc_cache = gdCacheCreate(TWEENCOLORCACHESIZE, tweenColorTest, tweenColorFetch, tweenColorRelease); + // fine tune line spacing for '\n' + double linespacing; - /***** initialize font library and font cache on first call ******/ + // Preferred character mapping + int charmap; + + // Rendering resolution + int hdpi; + int vdpi; + char *xshow; + char *fontpath; + } gdFTStringExtra, *gdFTStringExtraPtr; - gdMutexLock(gdFontCacheMutex); - if (!fontCache) { - if (gdFontCacheSetup() != 0) { + (end code) + + To output multiline text with a specific line spacing, include + gdFTEX_LINESPACE in the setting of flags: + + > flags |= gdFTEX_LINESPACE; + + And also set linespacing to the desired spacing, expressed as a + multiple of the font height. Thus a line spacing of 1.0 is the + minimum to guarantee that lines of text do not collide. + + If gdFTEX_LINESPACE is not present, or strex is null, or + gdImageStringFT is called, linespacing defaults to 1.05. + + To specify a preference for Unicode, Shift_JIS Big5 character + encoding, set or To output multiline text with a specific line + spacing, include gdFTEX_CHARMAP in the setting of flags: + + > flags |= gdFTEX_CHARMAP; + + And set charmap to the desired value, which can be any of + gdFTEX_Unicode, gdFTEX_Shift_JIS, gdFTEX_Big5, or + gdFTEX_Adobe_Custom. If you do not specify a preference, Unicode + will be tried first. If the preferred character mapping is not found + in the font, other character mappings are attempted. + + GD operates on the assumption that the output image will be rendered + to a computer screen. By default, gd passes a resolution of 96 dpi + to the freetype text rendering engine. This influences the "hinting" + decisions made by the renderer. To specify a different resolution, + set hdpi and vdpi accordingly (in dots per inch) and add + gdFTEX_RESOLUTION to flags: + + > flags | gdFTEX_RESOLUTION; + + GD 2.0.29 and later will normally attempt to apply kerning tables, + if fontconfig is available, to adjust the relative positions of + consecutive characters more ideally for that pair of + characters. This can be turn off by specifying the + gdFTEX_DISABLE_KERNING flag: + + > flags | gdFTEX_DISABLE_KERNING; + + GD 2.0.29 and later can return a vector of individual character + position advances, occasionally useful in applications that must + know exactly where each character begins. This is returned in the + xshow element of the gdFTStringExtra structure if the gdFTEX_XSHOW + flag is set: + + > flags | gdFTEX_XSHOW; + + The caller is responsible for calling gdFree() on the xshow element + after the call if gdFTEX_XSHOW is set. + + GD 2.0.29 and later can also return the path to the actual font file + used if the gdFTEX_RETURNFONTPATHNAME flag is set. This is useful + because GD 2.0.29 and above are capable of selecting a font + automatically based on a fontconfig font pattern when fontconfig is + available. This information is returned in the fontpath element of + the gdFTStringExtra structure. + + > flags | gdFTEX_RETURNFONTPATHNAME; + + The caller is responsible for calling gdFree() on the fontpath + element after the call if gdFTEX_RETURNFONTPATHNAME is set. + + GD 2.0.29 and later can use fontconfig to resolve font names, + including fontconfig patterns, if the gdFTEX_FONTCONFIG flag is + set. As a convenience, this behavior can be made the default by + calling with a nonzero value. In that situation it + is not necessary to set the gdFTEX_FONTCONFIG flag on every call; + however explicit font path names can still be used if the + gdFTEX_FONTPATHNAME flag is set: + + > flags | gdFTEX_FONTPATHNAME; + + Unless has been called with a nonzero value, GD + 2.0.29 and later will still expect the fontlist argument to the + freetype text output functions to be a font file name or list + thereof as in previous versions. If you do not wish to make + fontconfig the default, it is still possible to force the use of + fontconfig for a single call to the freetype text output functions + by setting the gdFTEX_FONTCONFIG flag: + + > flags | gdFTEX_FONTCONFIG; + + GD 2.0.29 and above can use fontconfig to resolve font names, + including fontconfig patterns, if the gdFTEX_FONTCONFIG flag is + set. As a convenience, this behavior can be made the default by + calling with a nonzero value. In that situation it + is not necessary to set the gdFTEX_FONTCONFIG flag on every call; + however explicit font path names can still be used if the + gdFTEX_FONTPATHNAME flag is set: + + > flags | gdFTEX_FONTPATHNAME; + + For more information, see . +*/ + +/* the platform-independent resolution used for size and position calculations + */ +/* the size of the error introduced by rounding is affected by this number */ +#define METRIC_RES 300 + +BGD_DECLARE(char *) +gdImageStringFTEx(gdImagePtr im, int *brect, int fg, const char *fontlist, + double ptsize, double angle, int x, int y, const char *string, + gdFTStringExtraPtr strex) { + FT_Matrix matrix; + FT_Vector penf, oldpenf, total_min = {0, 0}, total_max = {0, 0}, glyph_min, + glyph_max; + FT_Face face; + FT_CharMap charmap = NULL; + FT_CharMap fallback_charmap = NULL; + FT_Glyph image; + FT_GlyphSlot slot; + FT_Error err; + FT_UInt glyph_index; + double sin_a = sin(angle); + double cos_a = cos(angle); + int i, ch; + font_t *font; + fontkey_t fontkey; + const char *next; + char *tmpstr = 0; + uint32_t *text; + glyphInfo *info = NULL; + ssize_t count; + int render = (im && (im->trueColor || (fg <= 255 && fg >= -255))); + FT_BitmapGlyph bm; + /* 2.0.13: Bob Ostermann: don't force autohint, that's just for testing + freetype and doesn't look as good */ + int render_mode = FT_LOAD_DEFAULT; + int encoding, encodingfound; + /* Now tuneable thanks to Wez Furlong */ + double linespace = LINESPACE; + /* 2.0.6: put this declaration with the other declarations! */ + /* + * make a new tweenColorCache on every call + * because caching colormappings between calls + * is not safe. If the im-pointer points to a + * brand new image, the cache gives out bogus + * colorindexes. -- 27.06.2001 + */ + gdCache_head_t *tc_cache; + /* Tuneable horizontal and vertical resolution in dots per inch */ + int hdpi, vdpi, horiAdvance, vertAdvance, xshow_alloc = 0, xshow_pos = 0; + FT_Size platform_specific = NULL, platform_independent; + + if (strex) { + if ((strex->flags & gdFTEX_LINESPACE) == gdFTEX_LINESPACE) { + linespace = strex->linespacing; + } + } + tc_cache = gdCacheCreate(TWEENCOLORCACHESIZE, tweenColorTest, + tweenColorFetch, tweenColorRelease); + + /***** initialize font library and font cache on first call ******/ + if (!fontCache) { + if (gdFontCacheSetup() != 0) { gdCacheDelete(tc_cache); - gdMutexUnlock(gdFontCacheMutex); return "Failure to initialize font library"; } } /*****/ - - /* 2.0.12: allow explicit specification of the preferred map; - * but we still fall back if it is not available. - */ - m = gdFTEX_Unicode; - if (strex && (strex->flags & gdFTEX_CHARMAP)) { - m = strex->charmap; - } - + gdMutexLock(gdFontCacheMutex); /* get the font (via font cache) */ fontkey.fontlist = fontlist; + if (strex) + fontkey.flags = + strex->flags & (gdFTEX_FONTPATHNAME | gdFTEX_FONTCONFIG); + else + fontkey.flags = 0; fontkey.library = &library; - fontkey.preferred_map = m; - font = (font_t *) gdCacheGet (fontCache, &fontkey); + font = (font_t *)gdCacheGet(fontCache, &fontkey); if (!font) { gdCacheDelete(tc_cache); gdMutexUnlock(gdFontCacheMutex); return fontCache->error; } - face = font->face; /* shortcut */ - slot = face->glyph; /* shortcut */ + face = font->face; /* shortcut */ + slot = face->glyph; /* shortcut */ + + if (brect) { + total_min.x = total_min.y = 0; + total_max.x = total_max.y = 0; + } /* - * Added hdpi and vdpi to support images at non-screen resolutions, i.e. 300 dpi TIFF, - * or 100h x 50v dpi FAX format. 2.0.23. - * 2004/02/27 Mark Shackelford, mark.shackelford@acs-inc.com + * Added hdpi and vdpi to support images at non-screen resolutions, i.e. 300 + * dpi TIFF, or 100h x 50v dpi FAX format. 2.0.23. 2004/02/27 Mark + * Shackelford, mark.shackelford@acs-inc.com */ hdpi = GD_RESOLUTION; vdpi = GD_RESOLUTION; - if (strex && (strex->flags & gdFTEX_RESOLUTION)) { - hdpi = strex->hdpi; - vdpi = strex->vdpi; + encoding = gdFTEX_Unicode; + if (strex) { + if (strex->flags & gdFTEX_RESOLUTION) { + hdpi = strex->hdpi; + vdpi = strex->vdpi; + } + if (strex->flags & gdFTEX_XSHOW) { + strex->xshow = NULL; + } + /* 2.0.12: allow explicit specification of the preferred map; + but we still fall back if it is not available. */ + if (strex->flags & gdFTEX_CHARMAP) { + encoding = strex->charmap; + } + /* 2.0.29: we can return the font path if desired */ + if (strex->flags & gdFTEX_RETURNFONTPATHNAME) { + const unsigned int fontpath_len = strlen(font->fontpath); + + strex->fontpath = (char *)gdMalloc(fontpath_len + 1); + if (strex->fontpath == NULL) { + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + return "could not alloc full list of fonts"; + } + memcpy(strex->fontpath, font->fontpath, fontpath_len); + strex->fontpath[fontpath_len] = 0; + } } - if (FT_Set_Char_Size(face, 0, (FT_F26Dot6) (ptsize * 64), hdpi, vdpi)) { + matrix.xx = (FT_Fixed)(cos_a * (1 << 16)); + matrix.yx = (FT_Fixed)(sin_a * (1 << 16)); + matrix.xy = -matrix.yx; + matrix.yy = matrix.xx; + + /* Keep RAQM layout in text space; apply transform later only for + * the render glyph load. */ +#ifdef HAVE_LIBRAQM + FT_Set_Transform(face, NULL, NULL); +#else + /* set rotation transform */ + FT_Set_Transform(face, &matrix, NULL); +#endif + + FT_New_Size(face, &platform_independent); + FT_Activate_Size(platform_independent); + if (FT_Set_Char_Size(face, 0, (FT_F26Dot6)(ptsize * 64), METRIC_RES, + METRIC_RES)) { gdCacheDelete(tc_cache); gdMutexUnlock(gdFontCacheMutex); return "Could not set character size"; } - matrix.xx = (FT_Fixed) (cos_a * (1 << 16)); - matrix.yx = (FT_Fixed) (sin_a * (1 << 16)); - matrix.xy = -matrix.yx; - matrix.yy = matrix.xx; - - penf.x = penf.y = 0; /* running position of non-rotated string */ - pen.x = pen.y = 0; /* running position of rotated string */ - bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; + if (render) { + FT_New_Size(face, &platform_specific); + FT_Activate_Size(platform_specific); + if (FT_Set_Char_Size(face, 0, (FT_F26Dot6)(ptsize * 64), hdpi, vdpi)) { + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + return "Could not set character size"; + } + } - use_kerning = FT_HAS_KERNING (face); - previous = 0; - if (fg < 0) { + if (fg < 0) render_mode |= FT_LOAD_MONOCHROME; - } - /* Try all three types of maps, but start with the specified one */ - mfound = 0; - for (i = 0; i < 3; i++) { - switch (m) { - case gdFTEX_Unicode: - if (font->have_char_map_unicode) { - mfound = 1; - } + /* find requested charmap */ + encodingfound = 0; + for (i = 0; i < face->num_charmaps; i++) { + charmap = face->charmaps[i]; + + if (encoding == gdFTEX_Unicode) { + if (charmap->encoding == FT_ENCODING_UNICODE) { + encodingfound++; break; - case gdFTEX_Shift_JIS: - if (font->have_char_map_sjis) { - mfound = 1; - } + } + if (!fallback_charmap && + (charmap->encoding == FT_ENCODING_MS_SYMBOL || + charmap->encoding == FT_ENCODING_ADOBE_CUSTOM || + charmap->encoding == FT_ENCODING_ADOBE_STANDARD)) { + fallback_charmap = charmap; + } + } else if (encoding == gdFTEX_Adobe_Custom) { + if (charmap->encoding == FT_ENCODING_ADOBE_CUSTOM) { + encodingfound++; break; - case gdFTEX_Big5: - /* This was the 'else' case, we can't really 'detect' it */ - mfound = 1; + } + if (!fallback_charmap && + charmap->encoding == FT_ENCODING_APPLE_ROMAN) { + fallback_charmap = charmap; + } + } else if (encoding == gdFTEX_Big5) { + /* renamed sometime after freetype-2.1.4 */ +#ifndef FT_ENCODING_BIG5 +#define FT_ENCODING_BIG5 FT_ENCODING_MS_BIG5 +#endif + if (charmap->encoding == FT_ENCODING_BIG5) { + encodingfound++; break; + } + } else if (encoding == gdFTEX_Shift_JIS) { + /* renamed sometime after freetype-2.1.4 */ +#ifndef FT_ENCODING_SJIS +#define FT_ENCODING_SJIS FT_ENCODING_MS_SJIS +#endif + if (charmap->encoding == FT_ENCODING_SJIS) { + encodingfound++; + break; + } } - if (mfound) { - break; - } - m++; - m %= 3; } - if (!mfound) { + if (!encodingfound && fallback_charmap) { + charmap = fallback_charmap; + encodingfound = 1; + } + if (encodingfound) { + FT_Set_Charmap(face, charmap); + } else { /* No character set found! */ + gdCacheDelete(tc_cache); gdMutexUnlock(gdFontCacheMutex); return "No character set found"; } #ifndef JISX0208 - if (font->have_char_map_sjis) { + if (encoding == gdFTEX_Shift_JIS) { #endif - tmpstr = (char *) gdMalloc(BUFSIZ); - any2eucjp(tmpstr, string, BUFSIZ); - next = tmpstr; + if ((tmpstr = (char *)gdMalloc(BUFSIZ))) { + any2eucjp(tmpstr, string, BUFSIZ); + next = tmpstr; + } else { + next = string; + } #ifndef JISX0208 } else { next = string; } #endif + oldpenf.x = oldpenf.y = 0; /* for postscript xshow operator */ + penf.x = penf.y = 0; /* running position of non-rotated glyphs */ + text = (uint32_t *)gdCalloc(sizeof(uint32_t), strlen(next)); i = 0; while (*next) { + int len; ch = *next; + switch (encoding) { + case gdFTEX_Unicode: { + /* use UTF-8 mapping from ASCII */ + len = gdTcl_UtfToUniChar(next, &ch); + /* EAM DEBUG */ + /* TBB: get this exactly right: 2.1.3 *or better*, all possible + * cases. */ + /* 2.0.24: David R. Morrison: use the more complete ifdef here. */ + if (charmap->encoding == FT_ENCODING_MS_SYMBOL) { + /* I do not know the significance of the constant 0xf000. */ + /* It was determined by inspection of the character codes */ + /* stored in Microsoft font symbol.ttf */ + ch |= 0xf000; + } + /* EAM DEBUG */ + next += len; + } break; + case gdFTEX_Shift_JIS: { + unsigned char c; + int jiscode; + c = *next; + if (0xA1 <= c && c <= 0xFE) { + next++; + jiscode = 0x100 * (c & 0x7F) + ((*next) & 0x7F); + + ch = (jiscode >> 8) & 0xFF; + jiscode &= 0xFF; + + if (ch & 1) + jiscode += 0x40 - 0x21; + else + jiscode += 0x9E - 0x21; + + if (jiscode >= 0x7F) + jiscode++; + ch = (ch - 0x21) / 2 + 0x81; + if (ch >= 0xA0) + ch += 0x40; + + ch = (ch << 8) + jiscode; + } else { + ch = c & 0xFF; /* don't extend sign */ + } + if (*next) + next++; + } break; + case gdFTEX_Big5: { + /* + * Big 5 mapping: + * use "JIS-8 half-width katakana" coding from 8-bit characters. + * Ref: + * ftp://ftp.ora.com/pub/examples/nutshell/ujip/doc/japan.inf-032092.sjs + */ + ch = (*next) & 0xFF; /* don't extend sign */ + next++; + if (ch >= 161 /* first code of JIS-8 pair */ + && *next) { + /* don't advance past '\0' */ + /* TBB: Fix from Kwok Wah On: & 255 needed */ + ch = (ch * 256) + ((*next) & 255); + next++; + } + } break; + + case gdFTEX_Adobe_Custom: + default: + ch &= 0xFF; + next++; + break; + } + text[i] = ch; + i++; + } + + FT_Activate_Size(platform_independent); + + count = textLayout(text, i, face, strex, &info); + + if (count < 0) { + gdFree(text); + gdFree(tmpstr); + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + if (info) { + gdFree(info); + } + return "Problem doing text layout"; + } + + for (i = 0; i < count; i++) { + FT_Activate_Size(platform_independent); + + ch = text[info[i].cluster]; /* carriage returns */ if (ch == '\r') { penf.x = 0; - x1 = (int)(- penf.y * sin_a + 32) / 64; - y1 = (int)(- penf.y * cos_a + 32) / 64; - pen.x = pen.y = 0; - previous = 0; /* clear kerning flag */ - next++; continue; } + /* newlines */ if (ch == '\n') { - if (!*(++next)) break; /* 2.0.13: reset penf.x. Christopher J. Grayce */ penf.x = 0; - penf.y -= (long)(face->size->metrics.height * linespace); - penf.y = (penf.y - 32) & -64; /* round to next pixel row */ - x1 = (int)(- penf.y * sin_a + 32) / 64; - y1 = (int)(- penf.y * cos_a + 32) / 64; - pen.x = pen.y = 0; - previous = 0; /* clear kerning flag */ - continue; + penf.y += linespace * ptsize * 64 * METRIC_RES / 72; + penf.y &= ~63; /* round down to 1/METRIC_RES */ + continue; } -/* EAM DEBUG */ -#if (defined(FREETYPE_MAJOR) && ((FREETYPE_MAJOR == 2 && ((FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 3) || FREETYPE_MINOR > 1) || FREETYPE_MAJOR > 2))) - if (font->face->family_name && font->face->charmap->encoding && - font->face->charmap->encoding == FT_ENCODING_MS_SYMBOL && strcmp(font->face->family_name, "Symbol") == 0) { - /* I do not know the significance of the constant 0xf000. - * It was determined by inspection of the character codes - * stored in Microsoft font symbol. - * Added by Pierre (pajoye@php.net): - * Convert to the Symbol glyph range only for a Symbol family member - */ - len = gdTcl_UtfToUniChar (next, &ch); - ch |= 0xf000; - next += len; - } else -#endif /* Freetype 2.1 or better */ -/* EAM DEBUG */ - - switch (m) { - case gdFTEX_Unicode: - if (font->have_char_map_unicode) { - /* use UTF-8 mapping from ASCII */ - len = gdTcl_UtfToUniChar(next, &ch); - next += len; - } - break; - case gdFTEX_Shift_JIS: - if (font->have_char_map_sjis) { - unsigned char c; - int jiscode; - c = *next; - if (0xA1 <= c && c <= 0xFE) { - next++; - jiscode = 0x100 * (c & 0x7F) + ((*next) & 0x7F); - - ch = (jiscode >> 8) & 0xFF; - jiscode &= 0xFF; - - if (ch & 1) { - jiscode += 0x40 - 0x21; - } else { - jiscode += 0x9E - 0x21; - } - - if (jiscode >= 0x7F) { - jiscode++; - } - ch = (ch - 0x21) / 2 + 0x81; - if (ch >= 0xA0) { - ch += 0x40; - } - - ch = (ch << 8) + jiscode; - } else { - ch = c & 0xFF; /* don't extend sign */ - } - if (*next) next++; + glyph_index = info[i].index; + /* When we know the position of the second or subsequent character, + save the (kerned) advance from the preceeding character in the + xshow vector */ + if (i && strex && (strex->flags & gdFTEX_XSHOW)) { + /* make sure we have enough allocation for two numbers + so we don't have to recheck for the terminating number */ + if (!xshow_alloc) { + xshow_alloc = 100; + strex->xshow = gdMalloc(xshow_alloc); + if (!strex->xshow) { + if (tmpstr) + gdFree(tmpstr); + gdFree(text); + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + gdFree(info); + return "Problem allocating memory"; } - break; - case gdFTEX_Big5: { - /* - * Big 5 mapping: - * use "JIS-8 half-width katakana" coding from 8-bit characters. Ref: - * ftp://ftp.ora.com/pub/examples/nutshell/ujip/doc/japan.inf-032092.sjs - */ - ch = (*next) & 0xFF; /* don't extend sign */ - next++; - if (ch >= 161 /* first code of JIS-8 pair */ - && *next) { /* don't advance past '\0' */ - /* TBB: Fix from Kwok Wah On: & 255 needed */ - ch = (ch * 256) + ((*next) & 255); - next++; + xshow_pos = 0; + } else if (xshow_pos + 20 > xshow_alloc) { + xshow_alloc += 100; + strex->xshow = gdReallocEx(strex->xshow, xshow_alloc); + if (!strex->xshow) { + if (tmpstr) + gdFree(tmpstr); + gdFree(text); + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + gdFree(info); + return "Problem allocating memory"; } } - break; + xshow_pos += sprintf(strex->xshow + xshow_pos, "%g ", + (double)(penf.x - oldpenf.x) * hdpi / + (64 * METRIC_RES)); } + oldpenf.x = penf.x; - /* set rotation transform */ - FT_Set_Transform(face, &matrix, NULL); - /* Convert character code to glyph index */ - glyph_index = FT_Get_Char_Index(face, ch); - - /* retrieve kerning distance and move pen position */ - if (use_kerning && previous && glyph_index) { - FT_Get_Kerning(face, previous, glyph_index, ft_kerning_default, &delta); - pen.x += (int)(delta.x * cos_a); - pen.y -= (int)(delta.x * sin_a); - penf.x += delta.x; +#ifdef HAVE_LIBRAQM + /* Keep RAQM brect/layout metrics in unrotated text space. */ + FT_Set_Transform(face, NULL, NULL); +#endif + + /* load glyph image into the slot (erase previous one) */ + err = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (err) { + if (tmpstr) + gdFree(tmpstr); + gdFree(text); + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + gdFree(info); + return "Problem loading glyph"; } - if (brect) { /* only if need brect */ - /* load glyph image into the slot (erase previous one) */ - if (FT_Load_Glyph(face, glyph_index, render_mode | FT_LOAD_IGNORE_TRANSFORM)) { - if (tmpstr) { - gdFree(tmpstr); - } - gdCacheDelete(tc_cache); - gdMutexUnlock(gdFontCacheMutex); - return "Problem loading glyph"; - } + horiAdvance = info[i].x_advance; + vertAdvance = info[i].y_advance; - /* transform glyph image */ - if (FT_Get_Glyph(slot, &image)) { - if (tmpstr) { - gdFree(tmpstr); - } - gdCacheDelete(tc_cache); - gdMutexUnlock(gdFontCacheMutex); - return "Problem loading glyph"; - } + if (brect) { + /* only if need brect */ + + glyph_min.x = penf.x + slot->metrics.horiBearingX; + glyph_min.y = penf.y - slot->metrics.horiBearingY; - FT_Glyph_Get_CBox(image, ft_glyph_bbox_gridfit, &glyph_bbox); - glyph_bbox.xMin += penf.x; - glyph_bbox.yMin += penf.y; - glyph_bbox.xMax += penf.x; - glyph_bbox.yMax += penf.y; - if (ch == ' ') { /* special case for trailing space */ - glyph_bbox.xMax += slot->metrics.horiAdvance; +#if 0 + if (ch == ' ') { /* special case for trailing space */ + glyph_max.x = penf.x + horiAdvance; + } else { + glyph_max.x = glyph_min.x + slot->metrics.width; } - if (!i) { /* if first character, init BB corner values */ - bbox.xMin = glyph_bbox.xMin; - bbox.yMin = glyph_bbox.yMin; - bbox.xMax = glyph_bbox.xMax; - bbox.yMax = glyph_bbox.yMax; +#else + glyph_max.x = penf.x + horiAdvance; +#endif + glyph_max.y = glyph_min.y + slot->metrics.height; + + if (i == 0) { + total_min = glyph_min; + total_max = glyph_max; } else { - if (bbox.xMin > glyph_bbox.xMin) { - bbox.xMin = glyph_bbox.xMin; - } - if (bbox.yMin > glyph_bbox.yMin) { - bbox.yMin = glyph_bbox.yMin; - } - if (bbox.xMax < glyph_bbox.xMax) { - bbox.xMax = glyph_bbox.xMax; - } - if (bbox.yMax < glyph_bbox.yMax) { - bbox.yMax = glyph_bbox.yMax; - } + if (glyph_min.x < total_min.x) + total_min.x = glyph_min.x; + if (glyph_min.y < total_min.y) + total_min.y = glyph_min.y; + if (glyph_max.x > total_max.x) + total_max.x = glyph_max.x; + if (glyph_max.y > total_max.y) + total_max.y = glyph_max.y; } - i++; } - /* increment (unrotated) pen position */ - penf.x += slot->metrics.horiAdvance; - if (render) { - if (!brect || angle != 0) { - /* reload the rotated glyph (for bbox we needed FT_LOAD_IGNORE_TRANSFORM - bbox is rotated later) */ - FT_Done_Glyph(image); + FT_Activate_Size(platform_specific); - /* load glyph image into the slot (erase previous one) */ - if (FT_Load_Glyph(face, glyph_index, render_mode)) { - if (tmpstr) { - gdFree(tmpstr); - } - gdCacheDelete(tc_cache); - gdMutexUnlock(gdFontCacheMutex); - return "Problem loading glyph"; - } +#ifdef HAVE_LIBRAQM + /* Render rotated glyph bitmaps while RAQM pen advances remain in + * layout space. */ + FT_Set_Transform(face, &matrix, NULL); +#endif + + /* load glyph again into the slot (erase previous one) - this time + * with scaling */ + err = FT_Load_Glyph(face, glyph_index, render_mode); + if (err) { + if (tmpstr) + gdFree(tmpstr); + gdFree(text); + gdCacheDelete(tc_cache); + gdMutexUnlock(gdFontCacheMutex); + gdFree(info); + return "Problem loading glyph"; + } - /* transform glyph image */ - if (FT_Get_Glyph(slot, &image)) { - if (tmpstr) { + /* load and transform glyph image */ + FT_Get_Glyph(slot, &image); + + if (image->format != ft_glyph_format_bitmap) { + err = FT_Glyph_To_Bitmap(&image, ft_render_mode_normal, 0, 1); + if (err) { + FT_Done_Glyph(image); + if (tmpstr) gdFree(tmpstr); - } + gdFree(text); gdCacheDelete(tc_cache); gdMutexUnlock(gdFontCacheMutex); - return "Problem loading glyph"; + gdFree(info); + return "Problem rendering glyph"; } } - if (image->format != ft_glyph_format_bitmap && FT_Glyph_To_Bitmap(&image, ft_render_mode_normal, 0, 1)) { - FT_Done_Glyph(image); - if (tmpstr) { - gdFree(tmpstr); - } - gdCacheDelete(tc_cache); - gdMutexUnlock(gdFontCacheMutex); - return "Problem rendering glyph"; - } - /* now, draw to our target surface */ - bm = (FT_BitmapGlyph) image; - gdft_draw_bitmap(tc_cache, im, fg, bm->bitmap, x + x1 + ((pen.x + 31) >> 6) + bm->left, y + y1 + ((pen.y + 31) >> 6) - bm->top); + bm = (FT_BitmapGlyph)image; + /* position rounded down to nearest pixel at current dpi + (the estimate was rounded up to next 1/METRIC_RES, so this should + fit) */ + FT_Pos pen_x = penf.x + info[i].x_offset; + FT_Pos pen_y = penf.y - info[i].y_offset; + gdft_draw_bitmap(tc_cache, im, fg, bm->bitmap, + (int)(x + + (pen_x * cos_a + pen_y * sin_a) * hdpi / + (METRIC_RES * 64) + + bm->left), + (int)(y - + (pen_x * sin_a - pen_y * cos_a) * vdpi / + (METRIC_RES * 64) - + bm->top)); + + FT_Done_Glyph(image); } - /* record current glyph index for kerning */ - previous = glyph_index; + penf.x += horiAdvance; + penf.y += vertAdvance; + } - /* increment pen position */ - pen.x += image->advance.x >> 10; - pen.y -= image->advance.y >> 10; + gdFree(text); + if (info) { + gdFree(info); + } - FT_Done_Glyph(image); + /* Save the (unkerned) advance from the last character in the xshow vector + */ + if (strex && (strex->flags & gdFTEX_XSHOW) && strex->xshow) { + sprintf(strex->xshow + xshow_pos, "%g", + (double)(penf.x - oldpenf.x) * hdpi / (64 * METRIC_RES)); } - if (brect) { /* only if need brect */ - /* For perfect rounding, must get sin(a + pi/4) and sin(a - pi/4). */ - double d1 = sin (angle + 0.78539816339744830962); - double d2 = sin (angle - 0.78539816339744830962); - - /* rotate bounding rectangle (at 0, 0) */ - brect[0] = (int) (bbox.xMin * cos_a - bbox.yMin * sin_a); - brect[1] = (int) (bbox.xMin * sin_a + bbox.yMin * cos_a); - brect[2] = (int) (bbox.xMax * cos_a - bbox.yMin * sin_a); - brect[3] = (int) (bbox.xMax * sin_a + bbox.yMin * cos_a); - brect[4] = (int) (bbox.xMax * cos_a - bbox.yMax * sin_a); - brect[5] = (int) (bbox.xMax * sin_a + bbox.yMax * cos_a); - brect[6] = (int) (bbox.xMin * cos_a - bbox.yMax * sin_a); - brect[7] = (int) (bbox.xMin * sin_a + bbox.yMax * cos_a); - - /* scale, round and offset brect */ - brect[0] = x + gdroundupdown(brect[0], d2 > 0); - brect[1] = y - gdroundupdown(brect[1], d1 < 0); - brect[2] = x + gdroundupdown(brect[2], d1 > 0); - brect[3] = y - gdroundupdown(brect[3], d2 > 0); - brect[4] = x + gdroundupdown(brect[4], d2 < 0); - brect[5] = y - gdroundupdown(brect[5], d1 > 0); - brect[6] = x + gdroundupdown(brect[6], d1 < 0); - brect[7] = y - gdroundupdown(brect[7], d2 < 0); + if (brect) { + /* only if need brect */ + double scalex = (double)hdpi / (64 * METRIC_RES); + double scaley = (double)vdpi / (64 * METRIC_RES); + + /* rotate bounding rectangle, scale and round to int pixels, and + * translate */ + brect[0] = x + (total_min.x * cos_a + total_max.y * sin_a) * scalex; + brect[1] = y - (total_min.x * sin_a - total_max.y * cos_a) * scaley; + brect[2] = x + (total_max.x * cos_a + total_max.y * sin_a) * scalex; + brect[3] = y - (total_max.x * sin_a - total_max.y * cos_a) * scaley; + brect[4] = x + (total_max.x * cos_a + total_min.y * sin_a) * scalex; + brect[5] = y - (total_max.x * sin_a - total_min.y * cos_a) * scaley; + brect[6] = x + (total_min.x * cos_a + total_min.y * sin_a) * scalex; + brect[7] = y - (total_min.x * sin_a - total_min.y * cos_a) * scaley; } - if (tmpstr) { + FT_Done_Size(platform_independent); + if (render) + FT_Done_Size(platform_specific); + + if (tmpstr) gdFree(tmpstr); - } gdCacheDelete(tc_cache); gdMutexUnlock(gdFontCacheMutex); - return (char *) NULL; + return (char *)NULL; } #endif /* HAVE_LIBFREETYPE */ + +#ifdef HAVE_LIBFONTCONFIG +/* Code to find font path, with special mapping for Postscript font names. + * + * Dag Lem + */ + +#include + +/* #define NO_POSTSCRIPT_ALIAS 1 */ +#ifndef NO_POSTSCRIPT_ALIAS +typedef struct _PostscriptAlias { + char *name; + char *family; + char *style; +} PostscriptAlias; + +/* This table maps standard Postscript font names to URW Type 1 fonts. + The mapping is converted from Ghostscript (Fontmap.GS) + for use with fontconfig. */ +static PostscriptAlias postscript_alias[] = { + {"AvantGarde-Book", "URW Gothic L", "Book"}, + {"AvantGarde-BookOblique", "URW Gothic L", "Book Oblique"}, + {"AvantGarde-Demi", "URW Gothic L", "Demi"}, + {"AvantGarde-DemiOblique", "URW Gothic L", "Demi Oblique"}, + + {"Bookman-Demi", "URW Bookman L", "Demi Bold"}, + {"Bookman-DemiItalic", "URW Bookman L", "Demi Bold Italic"}, + {"Bookman-Light", "URW Bookman L", "Light"}, + {"Bookman-LightItalic", "URW Bookman L", "Light Italic"}, + + {"Courier", "Nimbus Mono L", "Regular"}, + {"Courier-Oblique", "Nimbus Mono L", "Regular Oblique"}, + {"Courier-Bold", "Nimbus Mono L", "Bold"}, + {"Courier-BoldOblique", "Nimbus Mono L", "Bold Oblique"}, + + {"Helvetica", "Nimbus Sans L", "Regular"}, + {"Helvetica-Oblique", "Nimbus Sans L", "Regular Italic"}, + {"Helvetica-Bold", "Nimbus Sans L", "Bold"}, + {"Helvetica-BoldOblique", "Nimbus Sans L", "Bold Italic"}, + + {"Helvetica-Narrow", "Nimbus Sans L", "Regular Condensed"}, + {"Helvetica-Narrow-Oblique", "Nimbus Sans L", "Regular Condensed Italic"}, + {"Helvetica-Narrow-Bold", "Nimbus Sans L", "Bold Condensed"}, + {"Helvetica-Narrow-BoldOblique", "Nimbus Sans L", "Bold Condensed Italic"}, + + {"NewCenturySchlbk-Roman", "Century Schoolbook L", "Roman"}, + {"NewCenturySchlbk-Italic", "Century Schoolbook L", "Italic"}, + {"NewCenturySchlbk-Bold", "Century Schoolbook L", "Bold"}, + {"NewCenturySchlbk-BoldItalic", "Century Schoolbook L", "Bold Italic"}, + + {"Palatino-Roman", "URW Palladio L", "Roman"}, + {"Palatino-Italic", "URW Palladio L", "Italic"}, + {"Palatino-Bold", "URW Palladio L", "Bold"}, + {"Palatino-BoldItalic", "URW Palladio L", "Bold Italic"}, + + {"Symbol", "Standard Symbols L", "Regular"}, + + {"Times-Roman", "Nimbus Roman No9 L", "Regular"}, + {"Times-Italic", "Nimbus Roman No9 L", "Regular Italic"}, + {"Times-Bold", "Nimbus Roman No9 L", "Medium"}, + {"Times-BoldItalic", "Nimbus Roman No9 L", "Medium Italic"}, + + {"ZapfChancery-MediumItalic", "URW Chancery L", "Medium Italic"}, + + {"ZapfDingbats", "Dingbats", ""}, +}; +#endif + +static FcPattern *find_font(FcPattern *pattern) { + FcResult result; + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcConfigSubstitute(0, pattern, FcMatchFont); + FcDefaultSubstitute(pattern); + + return FcFontMatch(0, pattern, &result); +} + +#ifndef NO_POSTSCRIPT_ALIAS +static char *find_postscript_font(FcPattern **fontpattern, char *fontname) { + FcPattern *font = NULL; + size_t i; + + *fontpattern = NULL; + for (i = 0; i < sizeof(postscript_alias) / sizeof(*postscript_alias); i++) { + if (strcmp(fontname, postscript_alias[i].name) == 0) { + FcChar8 *family; + + FcPattern *pattern = FcPatternBuild( + 0, FC_FAMILY, FcTypeString, postscript_alias[i].family, + FC_STYLE, FcTypeString, postscript_alias[i].style, (char *)0); + font = find_font(pattern); + FcPatternDestroy(pattern); + + if (!font) + return "fontconfig: Couldn't find font."; + if (FcPatternGetString(font, FC_FAMILY, 0, &family) != + FcResultMatch) { + FcPatternDestroy(font); + return "fontconfig: Couldn't retrieve font family name."; + } + + /* Check whether we got the font family we wanted. */ + if (strcmp((const char *)family, postscript_alias[i].family) != 0) { + FcPatternDestroy(font); + return "fontconfig: Didn't find expected font family. Perhaps " + "URW Type 1 fonts need installing?"; + } + break; + } + } + + *fontpattern = font; + return NULL; +} +#endif + +static char *font_pattern(char **fontpath, char *fontpattern) { + FcPattern *font = NULL; + FcChar8 *file; + FcPattern *pattern; +#ifndef NO_POSTSCRIPT_ALIAS + char *error; +#endif + + *fontpath = NULL; +#ifndef NO_POSTSCRIPT_ALIAS + error = find_postscript_font(&font, fontpattern); + + if (!font) { + if (error) + return error; +#endif + pattern = FcNameParse((const FcChar8 *)fontpattern); + font = find_font(pattern); + FcPatternDestroy(pattern); +#ifndef NO_POSTSCRIPT_ALIAS + } +#endif + + if (!font) + return "fontconfig: Couldn't find font."; + if (FcPatternGetString(font, FC_FILE, 0, &file) != FcResultMatch) { + FcPatternDestroy(font); + return "fontconfig: Couldn't retrieve font file name."; + } else { + const unsigned int file_len = strlen((const char *)file); + + *fontpath = (char *)gdMalloc(file_len + 1); + if (*fontpath == NULL) { + return "could not alloc font path"; + } + memcpy(*fontpath, (const char *)file, file_len); + (*fontpath)[file_len] = 0; + } + FcPatternDestroy(font); + + return NULL; +} + +#endif /* HAVE_LIBFONTCONFIG */ + +#ifdef HAVE_LIBFREETYPE +/* Look up font using font names as file names. */ +static char *font_path(char **fontpath, char *name_list) { + int font_found = 0; + char *fontsearchpath, *fontlist; + char *fullname = NULL; + char *name, *dir; + char *path; + char *strtok_ptr = NULL; + const unsigned int name_list_len = strlen(name_list); + + /* + * Search the pathlist for any of a list of font names. + */ + *fontpath = NULL; + fontsearchpath = getenv("GDFONTPATH"); + if (!fontsearchpath) + fontsearchpath = DEFAULT_FONTPATH; + path = (char *)gdMalloc(sizeof(char) * strlen(fontsearchpath) + 1); + if (path == NULL) { + return "could not alloc full list of fonts"; + } + path[0] = 0; + + fontlist = (char *)gdMalloc(name_list_len + 1); + if (fontlist == NULL) { + gdFree(path); + return "could not alloc full list of fonts"; + } + memcpy(fontlist, name_list, name_list_len); + fontlist[name_list_len] = 0; + + /* + * Must use gd_strtok_r else pointer corrupted by strtok in nested loop. + */ + for (name = gd_strtok_r(fontlist, LISTSEPARATOR, &strtok_ptr); name; + name = gd_strtok_r(0, LISTSEPARATOR, &strtok_ptr)) { + char *path_ptr = NULL; + + /* make a fresh copy each time - strtok corrupts it. */ + sprintf(path, "%s", fontsearchpath); + /* + * Allocate an oversized buffer that is guaranteed to be + * big enough for all paths to be tested. + */ + /* 2.0.22: Thorben Kundinger: +8 is needed, not +6. */ + fullname = + gdReallocEx(fullname, strlen(fontsearchpath) + strlen(name) + 8); + if (!fullname) { + gdFree(fontlist); + gdFree(path); + return "could not alloc full path of font"; + } + /* if name is an absolute or relative pathname then test directly */ + if (GD_FT_IS_PATH(name)) { + sprintf(fullname, "%s", name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } + } + for (dir = gd_strtok_r(path, PATHSEPARATOR, &path_ptr); dir; + dir = gd_strtok_r(0, PATHSEPARATOR, &path_ptr)) { + if (strchr(name, '.')) { + sprintf(fullname, "%s/%s", dir, name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } else { + continue; + } + } + sprintf(fullname, "%s/%s.ttf", dir, name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } + sprintf(fullname, "%s/%s.pfa", dir, name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } + sprintf(fullname, "%s/%s.pfb", dir, name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } + sprintf(fullname, "%s/%s.dfont", dir, name); + if (GD_FT_ACCESS(fullname, R_OK) == 0) { + font_found++; + break; + } + } + + if (font_found) + break; + } + gdFree(path); + if (fontlist != NULL) { + gdFree(fontlist); + fontlist = NULL; + } + if (!font_found) { + gdFree(fullname); + return "Could not find/open font"; + } + + *fontpath = fullname; + return NULL; +} +#endif + +/** + * Function: gdFTUseFontConfig + * + * Enable or disable fontconfig by default. + * + * If GD is built without libfontconfig support, this function is a NOP. + * + * Parameters: + * flag - Zero to disable, nonzero to enable. + * + * See also: + * - + */ +BGD_DECLARE(int) gdFTUseFontConfig(int flag) { +#ifdef HAVE_LIBFONTCONFIG + fontConfigFlag = flag; + return 1; +#else + (void)flag; + return 0; +#endif /* HAVE_LIBFONTCONFIG */ +} diff --git a/ext/gd/libgd/gdfx.c b/ext/gd/libgd/gdfx.c new file mode 100644 index 000000000000..6540100d4568 --- /dev/null +++ b/ext/gd/libgd/gdfx.c @@ -0,0 +1,493 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "gd.h" +#include "gd_errors.h" +#include "gd_intern.h" +#include + +/* In tests this is sufficient to prevent obvious artifacts */ +#define MAG 4 + +#define PI 3.141592 +#define DEG2RAD(x) ((x) * PI / 180.) + +#define MAX4(x, y, z, w) \ + ((MAX((x), (y))) > (MAX((z), (w))) ? (MAX((x), (y))) : (MAX((z), (w)))) +#define MIN4(x, y, z, w) \ + ((MIN((x), (y))) < (MIN((z), (w))) ? (MIN((x), (y))) : (MIN((z), (w)))) + +#define MAXX(x) MAX4(x[0], x[2], x[4], x[6]) +#define MINX(x) MIN4(x[0], x[2], x[4], x[6]) +#define MAXY(x) MAX4(x[1], x[3], x[5], x[7]) +#define MINY(x) MIN4(x[1], x[3], x[5], x[7]) + +/** + * Function: gdImageStringFTCircle + * + * Draw text curved along the top and bottom of a circular area of an image. + * + * Parameters: + * im - The image to draw onto. + * cx - The x-coordinate of the center of the circular area. + * cy - The y-coordinate of the center of the circular area. + * radius - The radius of the circular area. + * textRadius - The height of each character; if textRadius is 1/2 of radius, + * characters extend halfway from the edge to the center. + * fillPortion - The percentage of the 180 degrees of the circular area + * assigned to each section of text, that is actually occupied + * by text. The value has to be in range 0.0 to 1.0, with useful + * values from about 0.4 to 0.9; 0.9 looks better than 1.0 which + * is rather crowded. + * font - The fontlist that is passed to . + * points - The point size, which functions as a hint. Although the size + * of the text is determined by radius, textRadius and + * fillPortion, a point size that 'hints' appropriately should be + * passed. If it's known that the text will be large, a large + * point size such as 24.0 should be passed to get the best + * results. + * top - The text to draw clockwise at the top of the circular area. + * bottom - The text to draw counterclockwise at the bottom of the + * circular area. + * fgcolor - The font color. + * + * Returns: + * NULL on success, or an error string on failure. + */ +BGD_DECLARE(char *) +gdImageStringFTCircle(gdImagePtr im, int cx, int cy, double radius, + double textRadius, double fillPortion, char *font, + double points, char *top, char *bottom, int fgcolor) { + char *err; + int w; + int brect[8]; + int sx1, sx2, sy1, sy2, sx, sy; + int x, y; + int fr, fg, fb, fa; + int ox, oy; + double prop; + gdImagePtr im1; + gdImagePtr im2; + gdImagePtr im3; + /* obtain brect so that we can size the image */ + err = gdImageStringFT((gdImagePtr)NULL, &brect[0], 0, font, points * MAG, 0, + 0, 0, bottom); + if (err) { + return err; + } + sx1 = MAXX(brect) - MINX(brect) + 6; + sy1 = MAXY(brect) - MINY(brect) + 6; + err = gdImageStringFT((gdImagePtr)NULL, &brect[0], 0, font, points * MAG, 0, + 0, 0, top); + if (err) { + return err; + } + sx2 = MAXX(brect) - MINX(brect) + 6; + sy2 = MAXY(brect) - MINY(brect) + 6; + /* Pad by 4 pixels to allow for slight errors + observed in the bounding box returned by freetype */ + if (sx1 > sx2) { + sx = sx1 * 2 + 4; + } else { + sx = sx2 * 2 + 4; + } + if (sy1 > sy2) { + sy = sy1; + } else { + sy = sy2; + } + im1 = gdImageCreateTrueColor(sx, sy); + if (!im1) { + return "could not create first image"; + } + err = + gdImageStringFT(im1, 0, gdTrueColor(255, 255, 255), font, points * MAG, + 0, ((sx / 2) - sx1) / 2, points * MAG, bottom); + if (err) { + gdImageDestroy(im1); + return err; + } + /* We don't know the descent, which would be needed to do this + with the angle parameter. Instead, implement a simple + flip operation ourselves. */ + err = + gdImageStringFT(im1, 0, gdTrueColor(255, 255, 255), font, points * MAG, + 0, sx / 2 + ((sx / 2) - sx2) / 2, points * MAG, top); + if (err) { + gdImageDestroy(im1); + return err; + } + /* Flip in place is tricky, be careful not to double-swap things */ + if (sy & 1) { + for (y = 0; (y <= (sy / 2)); y++) { + int xlimit = sx - 2; + if (y == (sy / 2)) { + /* If there is a "middle" row, be careful + not to swap twice! */ + xlimit -= (sx / 4); + } + for (x = (sx / 2) + 2; (x < xlimit); x++) { + int t; + int ox = sx - x + (sx / 2) - 1; + int oy = sy - y - 1; + t = im1->tpixels[oy][ox]; + im1->tpixels[oy][ox] = im1->tpixels[y][x]; + im1->tpixels[y][x] = t; + } + } + } else { + for (y = 0; (y < (sy / 2)); y++) { + int xlimit = sx - 2; + for (x = (sx / 2) + 2; (x < xlimit); x++) { + int t; + int ox = sx - x + (sx / 2) - 1; + int oy = sy - y - 1; + t = im1->tpixels[oy][ox]; + im1->tpixels[oy][ox] = im1->tpixels[y][x]; + im1->tpixels[y][x] = t; + } + } + } +#if STEP_PNGS + { + FILE *out = fopen("gdfx1.png", "wb"); + gdImagePng(im1, out); + fclose(out); + } +#endif /* STEP_PNGS */ + /* Resample taller; the exact proportions of the text depend on the + ratio of textRadius to radius, and the value of fillPortion */ + if (sx > sy * 10) { + w = sx; + } else { + w = sy * 10; + } + im2 = gdImageCreateTrueColor(w, w); + if (!im2) { + gdImageDestroy(im1); + return "could not create resampled image"; + } + prop = textRadius / radius; + gdImageCopyResampled(im2, im1, gdImageSX(im2) * (1.0 - fillPortion) / 4, + sy * 10 * (1.0 - prop), 0, 0, + gdImageSX(im2) * fillPortion / 2, sy * 10 * prop, + gdImageSX(im1) / 2, gdImageSY(im1)); + gdImageCopyResampled(im2, im1, + (gdImageSX(im2) / 2) + + gdImageSX(im2) * (1.0 - fillPortion) / 4, + sy * 10 * (1.0 - prop), gdImageSX(im1) / 2, 0, + gdImageSX(im2) * fillPortion / 2, sy * 10 * prop, + gdImageSX(im1) / 2, gdImageSY(im1)); +#if STEP_PNGS + { + FILE *out = fopen("gdfx2.png", "wb"); + gdImagePng(im2, out); + fclose(out); + } +#endif /* STEP_PNGS */ + + gdImageDestroy(im1); + + /* Ready to produce a circle */ + im3 = gdImageSquareToCircle(im2, radius); + if (im3 == NULL) { + gdImageDestroy(im2); + return 0; + } + gdImageDestroy(im2); + /* Now blend im3 with the destination. Cheat a little. The + source (im3) is white-on-black, so we can use the + red component as a basis for alpha as long as we're + careful to shift off the extra bit and invert + (alpha ranges from 0 to 127 where 0 is OPAQUE). + Also be careful to allow for an alpha component + in the fgcolor parameter itself (gug!) */ + fr = gdTrueColorGetRed(fgcolor); + fg = gdTrueColorGetGreen(fgcolor); + fb = gdTrueColorGetBlue(fgcolor); + fa = gdTrueColorGetAlpha(fgcolor); + ox = cx - (im3->sx / 2); + oy = cy - (im3->sy / 2); + for (y = 0; (y < im3->sy); y++) { + for (x = 0; (x < im3->sx); x++) { + int a = gdTrueColorGetRed(im3->tpixels[y][x]) >> 1; + a *= (127 - fa); + a /= 127; + a = 127 - a; + gdImageSetPixel(im, x + ox, y + oy, + gdTrueColorAlpha(fr, fg, fb, a)); + } + } + gdImageDestroy(im3); + return 0; +} + +#if GDFX_MAIN + +int main(int argc, char *argv[]) { + FILE *in; + FILE *out; + gdImagePtr im; + int radius; + /* Create an image of text on a circle, with an + alpha channel so that we can copy it onto a + background */ + in = fopen("eleanor.jpg", "rb"); + if (!in) { + im = gdImageCreateTrueColor(300, 300); + } else { + im = gdImageCreateFromJpeg(in); + fclose(in); + } + if (gdImageSX(im) < gdImageSY(im)) { + radius = gdImageSX(im) / 2; + } else { + radius = gdImageSY(im) / 2; + } + gdImageStringFTCircle(im, gdImageSX(im) / 2, gdImageSY(im) / 2, radius, + radius / 2, 0.8, "arial", 24, "top text", + "bottom text", gdTrueColorAlpha(240, 240, 255, 32)); + out = fopen("gdfx.png", "wb"); + if (!out) { + gd_error("Can't create gdfx.png\n"); + return 1; + } + gdImagePng(im, out); + fclose(out); + gdImageDestroy(im); + return 0; +} + +#endif /* GDFX_MAIN */ + +/* Note: don't change these */ +#define SUPER 2 +#define SUPERBITS1 1 +#define SUPERBITS2 2 + +/** + * Function: gdImageSquareToCircle + * + * Apply polar coordinate transformation to an image. + * + * The X axis of the original will be remapped to theta (angle) and the Y axis + * of the original will be remapped to rho (distance from center). + * + * Parameters: + * im - The image, which must be square, i.e. width == height. + * radius - The radius of the new image, i.e. width == height == radius * 2. + * + * Returns: + * The transformed image, or NULL on failure. + */ +BGD_DECLARE(gdImagePtr) +gdImageSquareToCircle(gdImagePtr im, int radius) { + int x, y; + double c; + gdImagePtr im2; + if (im->sx != im->sy) { + /* Source image must be square */ + return 0; + } + im2 = gdImageCreateTrueColor(radius * 2, radius * 2); + if (!im2) { + return 0; + } + /* Supersampling for a nicer result */ + c = (im2->sx / 2) * SUPER; + for (y = 0; (y < im2->sy * SUPER); y++) { + for (x = 0; (x < im2->sx * SUPER); x++) { + double rho = sqrt((x - c) * (x - c) + (y - c) * (y - c)); + int pix; + int cpix; + double theta; + double ox; + double oy; + int red, green, blue, alpha; + if (rho > c) { + continue; + } + theta = atan2(x - c, y - c) + PI / 2; + if (theta < 0) { + theta += 2 * PI; + } + /* Undo supersampling */ + oy = (rho * im->sx) / (im2->sx * SUPER / 2); + ox = theta * im->sx / (3.141592653 * 2); + pix = gdImageGetPixel(im, ox, oy); + cpix = im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1]; + red = (gdImageRed(im, pix) >> SUPERBITS2) + gdTrueColorGetRed(cpix); + green = (gdImageGreen(im, pix) >> SUPERBITS2) + + gdTrueColorGetGreen(cpix); + blue = + (gdImageBlue(im, pix) >> SUPERBITS2) + gdTrueColorGetBlue(cpix); + alpha = (gdImageAlpha(im, pix) >> SUPERBITS2) + + gdTrueColorGetAlpha(cpix); + im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1] = + gdTrueColorAlpha(red, green, blue, alpha); + } + } + /* Restore full dynamic range, 0-63 yields 0-252. Replication of + first 2 bits in last 2 bits has the desired effect. Note + slightly different arithmetic for alpha which is 7-bit. + NOTE: only correct for SUPER == 2 */ + for (y = 0; (y < im2->sy); y++) { + for (x = 0; (x < im2->sx); x++) { + /* Copy first 2 bits to last 2 bits, matching the + dynamic range of the original cheaply */ + int cpix = im2->tpixels[y][x]; + + im2->tpixels[y][x] = + gdTrueColorAlpha((gdTrueColorGetRed(cpix) & 0xFC) + + ((gdTrueColorGetRed(cpix) & 0xC0) >> 6), + (gdTrueColorGetGreen(cpix) & 0xFC) + + ((gdTrueColorGetGreen(cpix) & 0xC0) >> 6), + (gdTrueColorGetBlue(cpix) & 0xFC) + + ((gdTrueColorGetBlue(cpix) & 0xC0) >> 6), + (gdTrueColorGetAlpha(cpix) & 0x7C) + + ((gdTrueColorGetAlpha(cpix) & 0x60) >> 6)); + } + } + return im2; +} + +/* 2.0.16: Called by gdImageSharpen to avoid excessive code repetition + Added on 2003-11-19 by + Paul Troughton (paultroughtonieeeorg) + Given filter coefficents and colours of three adjacent pixels, +returns new colour for centre pixel +*/ + +static int gdImageSubSharpen(int pc, int c, int nc, float inner_coeff, + float outer_coeff) { + float red, green, blue, alpha; + + red = inner_coeff * gdTrueColorGetRed(c) + + outer_coeff * (gdTrueColorGetRed(pc) + gdTrueColorGetRed(nc)); + green = inner_coeff * gdTrueColorGetGreen(c) + + outer_coeff * (gdTrueColorGetGreen(pc) + gdTrueColorGetGreen(nc)); + blue = inner_coeff * gdTrueColorGetBlue(c) + + outer_coeff * (gdTrueColorGetBlue(pc) + gdTrueColorGetBlue(nc)); + alpha = gdTrueColorGetAlpha(c); + + /* Clamping, as can overshoot bounds in either direction */ + if (red > 255.0f) { + red = 255.0f; + } + if (green > 255.0f) { + green = 255.0f; + } + if (blue > 255.0f) { + blue = 255.0f; + } + if (red < 0.0f) { + red = 0.0f; + } + if (green < 0.0f) { + green = 0.0f; + } + if (blue < 0.0f) { + blue = 0.0f; + } + + return gdTrueColorAlpha((int)red, (int)green, (int)blue, (int)alpha); +} + +/** + * Function: gdImageSharpen + * + * Sharpen an image. + * + * Uses a simple 3x3 convolution kernel and makes use of separability. + * It's faster, but less flexible, than full-blown unsharp masking. + * Silently does nothing to non-truecolor images and for pct<0, as it's not a + * useful blurring function. + * + * Parameters: + * pct - The sharpening percentage, which can be greater than 100. + * + * Author: + * Paul Troughton (paultroughtonieeeorg) + */ +BGD_DECLARE(void) +gdImageSharpen(gdImagePtr im, int pct) { + int x, y; + int sx, sy; + float inner_coeff, outer_coeff; + + sx = im->sx; + sy = im->sy; + + /* Must sum to 1 to avoid overall change in brightness. + * Scaling chosen so that pct=100 gives 1-D filter [-1 6 -1]/4, + * resulting in a 2-D filter [1 -6 1; -6 36 -6; 1 -6 1]/16, + * which gives noticeable, but not excessive, sharpening + */ + + outer_coeff = -pct / 400.0; + inner_coeff = 1 - 2 * outer_coeff; + + /* Don't try to do anything with non-truecolor images, as + pointless, + * nor for pct<=0, as small kernel size leads to nasty + artefacts when blurring + */ + if ((im->trueColor) && (pct > 0)) { + + /* First pass, 1-D convolution column-wise */ + for (x = 0; x < sx; x++) { + + /* pc is colour of previous pixel; c of the + current pixel and nc of the next */ + int pc, c, nc; + + /* Replicate edge pixel at image boundary */ + pc = gdImageGetPixel(im, x, 0); + + /* Stop looping before last pixel to avoid + conditional within loop */ + for (y = 0; y < sy - 1; y++) { + + c = gdImageGetPixel(im, x, y); + + nc = gdImageGetTrueColorPixel(im, x, y + 1); + + /* Update centre pixel to new colour */ + gdImageSetPixel( + im, x, y, + gdImageSubSharpen(pc, c, nc, inner_coeff, outer_coeff)); + + /* Save original colour of current + pixel for next time round */ + pc = c; + } + + /* Deal with last pixel, replicating current + pixel at image boundary */ + c = gdImageGetPixel(im, x, y); + gdImageSetPixel( + im, x, y, + gdImageSubSharpen(pc, c, c, inner_coeff, outer_coeff)); + } + + /* Second pass, 1-D convolution row-wise */ + for (y = 0; y < sy; y++) { + int pc, c; + pc = gdImageGetPixel(im, 0, y); + for (x = 0; x < sx - 1; x++) { + int c, nc; + c = gdImageGetPixel(im, x, y); + nc = gdImageGetTrueColorPixel(im, x + 1, y); + gdImageSetPixel( + im, x, y, + gdImageSubSharpen(pc, c, nc, inner_coeff, outer_coeff)); + pc = c; + } + c = gdImageGetPixel(im, x, y); + gdImageSetPixel( + im, x, y, + gdImageSubSharpen(pc, c, c, inner_coeff, outer_coeff)); + } + } +} diff --git a/ext/gd/libgd/gdfx.h b/ext/gd/libgd/gdfx.h new file mode 100644 index 000000000000..f4864bdbdac0 --- /dev/null +++ b/ext/gd/libgd/gdfx.h @@ -0,0 +1,21 @@ +#ifndef GDFX_H +#define GDFX_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +BGD_DECLARE(gdImagePtr) gdImageSquareToCircle(gdImagePtr im, int radius); + +BGD_DECLARE(char *) +gdImageStringFTCircle(gdImagePtr im, int cx, int cy, double radius, + double textRadius, double fillPortion, char *font, + double points, char *top, char *bottom, int fgcolor); + +BGD_DECLARE(void) gdImageSharpen(gdImagePtr im, int pct); + +#ifdef __cplusplus +} +#endif + +#endif /* GDFX_H */ diff --git a/ext/gd/libgd/gdhelpers.c b/ext/gd/libgd/gdhelpers.c index 44b79ac77f32..5a416e3da108 100644 --- a/ext/gd/libgd/gdhelpers.c +++ b/ext/gd/libgd/gdhelpers.c @@ -4,6 +4,11 @@ #include "gd.h" #include "gdhelpers.h" + +#ifdef HAVE_LIBPNG +#include + +#endif #include #include @@ -72,3 +77,12 @@ gd_strtok_r (char *s, char *sep, char **state) *state = s; return result; } + +void *gdReallocEx(void *ptr, size_t size) +{ + void *newPtr = gdRealloc(ptr, size); + if (!newPtr && ptr) { + gdFree(ptr); + } + return newPtr; +} diff --git a/ext/gd/libgd/gdhelpers.h b/ext/gd/libgd/gdhelpers.h index 202ccfce7636..4917fc89b652 100644 --- a/ext/gd/libgd/gdhelpers.h +++ b/ext/gd/libgd/gdhelpers.h @@ -21,6 +21,10 @@ extern char *gd_strtok_r(char *s, char *sep, char **state); #define gdPFree(ptr) pefree(ptr, 1) #define gdPEstrdup(ptr) pestrdup(ptr, 1) +/* The extended version of gdReallocEx will free *ptr if the + * realloc fails. */ +void *gdReallocEx(void *ptr, size_t size); + /* Returns nonzero if multiplying the two quantities will result in integer overflow. Also returns nonzero if either quantity is negative. By Phil Knirsch based on @@ -49,4 +53,3 @@ int overflowMul3(int a, int b, int c); #define DPI2DPM(dpi) (unsigned int)((dpi)/0.0254 + 0.5) #endif /* GDHELPERS_H */ - diff --git a/ext/gd/libgd/gdkanji.c b/ext/gd/libgd/gdkanji.c index ef769f89badd..6f43e2d522f7 100644 --- a/ext/gd/libgd/gdkanji.c +++ b/ext/gd/libgd/gdkanji.c @@ -2,21 +2,55 @@ /* gdkanji.c (Kanji code converter) */ /* written by Masahito Yamaga (ma@yama-ga.com) */ -#include -#include -#include #include "gd.h" +#include "gd_errors.h" #include "gdhelpers.h" - +#include #include +#include +#include +#include #if defined(HAVE_ICONV_H) || defined(HAVE_ICONV) #include #include #endif + #if defined(HAVE_ICONV_H) && !defined(HAVE_ICONV) #define HAVE_ICONV 1 #endif +#ifndef HAVE_ICONV_T_DEF +typedef void *iconv_t; +#endif + +#ifndef HAVE_ICONV +#define ICONV_CONST /**/ +iconv_t iconv_open(const char *, const char *); +size_t iconv(iconv_t, ICONV_CONST char **, size_t *, char **, size_t *); +int iconv_close(iconv_t); + +iconv_t iconv_open(const char *tocode, const char *fromcode) { + (void)tocode; + (void)fromcode; + return (iconv_t)(-1); +} + +size_t iconv(iconv_t cd, ICONV_CONST char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) { + (void)cd; + (void)inbuf; + (void)inbytesleft; + (void)outbuf; + (void)outbytesleft; + return 0; +} + +int iconv_close(iconv_t cd) { + (void)cd; + return 0; +} + +#endif /* !HAVE_ICONV */ #define LIBNAME "any2eucjp()" @@ -53,38 +87,9 @@ #define ESC 27 #define SS2 142 -static void -debug (const char *format,...) -{ -#ifdef DEBUG - va_list args; - - va_start (args, format); - fprintf (stdout, "%s: ", LIBNAME); - vfprintf (stdout, format, args); - fprintf (stdout, "\n"); - va_end (args); -#endif -} - -static void -error (const char *format,...) -{ - va_list args; - char *tmp; - - va_start(args, format); - vspprintf(&tmp, 0, format, args); - va_end(args); - php_error_docref(NULL, E_WARNING, "%s: %s", LIBNAME, tmp); - efree(tmp); -} - /* DetectKanjiCode() derived from DetectCodeType() by Ken Lunde. */ -static int -DetectKanjiCode (unsigned char *str) -{ +static int DetectKanjiCode(const unsigned char *str) { static int whatcode = ASCII; int oldcode = ASCII; int c, i; @@ -93,60 +98,46 @@ DetectKanjiCode (unsigned char *str) c = '\1'; i = 0; - if (whatcode != EUCORSJIS && whatcode != ASCII) - { + if (whatcode != EUCORSJIS && whatcode != ASCII) { oldcode = whatcode; whatcode = ASCII; } - while ((whatcode == EUCORSJIS || whatcode == ASCII) && c != '\0') - { - if ((c = str[i++]) != '\0') - { - if (c == ESC) - { + while ((whatcode == EUCORSJIS || whatcode == ASCII) && c != '\0') { + if ((c = str[i++]) != '\0') { + if (c == ESC) { c = str[i++]; - if (c == '$') - { + if (c == '$') { c = str[i++]; if (c == 'B') whatcode = NEW; else if (c == '@') whatcode = OLD; - } - else if (c == '(') - { + } else if (c == '(') { c = str[i++]; if (c == 'I') whatcode = ESCI; - } - else if (c == 'K') + } else if (c == 'K') whatcode = NEC; - } - else if ((c >= 129 && c <= 141) || (c >= 143 && c <= 159)) + } else if ((c >= 129 && c <= 141) || (c >= 143 && c <= 159)) whatcode = SJIS; - else if (c == SS2) - { + else if (c == SS2) { c = str[i++]; - if ((c >= 64 && c <= 126) || (c >= 128 && c <= 160) || (c >= 224 && c <= 252)) + if ((c >= 64 && c <= 126) || (c >= 128 && c <= 160) || + (c >= 224 && c <= 252)) whatcode = SJIS; else if (c >= 161 && c <= 223) whatcode = EUCORSJIS; - } - else if (c >= 161 && c <= 223) - { + } else if (c >= 161 && c <= 223) { c = str[i++]; if (c >= 240 && c <= 254) whatcode = EUC; else if (c >= 161 && c <= 223) whatcode = EUCORSJIS; - else if (c >= 224 && c <= 239) - { + else if (c >= 224 && c <= 239) { whatcode = EUCORSJIS; - while (c >= 64 && whatcode == EUCORSJIS) - { - if (c >= 129) - { + while (c >= 64 && whatcode == EUCORSJIS) { + if (c >= 129) { if (c <= 141 || (c >= 143 && c <= 159)) whatcode = SJIS; else if (c >= 253 && c <= 254) @@ -154,14 +145,11 @@ DetectKanjiCode (unsigned char *str) } c = str[i++]; } - } - else if (c <= 159) + } else if (c <= 159) whatcode = SJIS; - } - else if (c >= 240 && c <= 254) + } else if (c >= 240 && c <= 254) whatcode = EUC; - else if (c >= 224 && c <= 239) - { + else if (c >= 224 && c <= 239) { c = str[i++]; if ((c >= 64 && c <= 126) || (c >= 128 && c <= 160)) whatcode = SJIS; @@ -175,18 +163,17 @@ DetectKanjiCode (unsigned char *str) #ifdef DEBUG if (whatcode == ASCII) - debug ("Kanji code not included."); + gd_error_ex(GD_DEBUG, "Kanji code not included."); else if (whatcode == EUCORSJIS) - debug ("Kanji code not detected."); + gd_error_ex(GD_DEBUG, "Kanji code not detected."); else - debug ("Kanji code detected at %d byte.", i); + gd_error_ex(GD_DEBUG, "Kanji code detected at %d byte.", i); #endif if (whatcode == EUCORSJIS && oldcode != ASCII) whatcode = oldcode; - if (whatcode == EUCORSJIS) - { + if (whatcode == EUCORSJIS) { if (getenv ("LC_ALL")) lang = getenv ("LC_ALL"); else if (getenv ("LC_CTYPE")) @@ -194,8 +181,7 @@ DetectKanjiCode (unsigned char *str) else if (getenv ("LANG")) lang = getenv ("LANG"); - if (lang) - { + if (lang) { if (strcmp (lang, "ja_JP.SJIS") == 0 || #ifdef hpux strcmp (lang, "japanese") == 0 || @@ -224,9 +210,7 @@ DetectKanjiCode (unsigned char *str) /* SJIStoJIS() is sjis2jis() by Ken Lunde. */ -static void -SJIStoJIS (int *p1, int *p2) -{ +static void SJIStoJIS(int *p1, int *p2) { register unsigned char c1 = *p1; register unsigned char c2 = *p2; register int adjust = c2 < 159; @@ -242,14 +226,11 @@ SJIStoJIS (int *p1, int *p2) #define IS_DAKU(c) ((c >= 182 && c <= 196) || (c >= 202 && c <= 206) || (c == 179)) #define IS_HANDAKU(c) (c >= 202 && c <= 206) -static void -han2zen (int *p1, int *p2) -{ +static void han2zen(int *p1, int *p2) { int c = *p1; int daku = FALSE; int handaku = FALSE; - int mtable[][2] = - { + int mtable[][2] = { {129, 66}, {129, 117}, {129, 118}, @@ -323,254 +304,221 @@ han2zen (int *p1, int *p2) *p1 = mtable[c - 161][0]; *p2 = mtable[c - 161][1]; - if (daku) - { + if (daku) { if ((*p2 >= 74 && *p2 <= 103) || (*p2 >= 110 && *p2 <= 122)) (*p2)++; else if (*p2 == 131 || *p2 == 69) *p2 = 148; - } - else if (handaku && *p2 >= 110 && *p2 <= 122) + } else if (handaku && *p2 >= 110 && *p2 <= 122) (*p2) += 2; } /* Recast strcpy to handle unsigned chars used below. */ #define ustrcpy(A,B) (strcpy((char*)(A),(const char*)(B))) - -static void -do_convert (unsigned char *to, unsigned char *from, const char *code) -{ +#define ustrncpy(A, B, maxsize) (strncpy((char *)(A), (const char *)(B), maxsize)) +static void do_convert(unsigned char **to, const unsigned char **from, const char *code) { #ifdef HAVE_ICONV - iconv_t cd; - size_t from_len, to_len; - - if ((cd = iconv_open (EUCSTR, code)) == (iconv_t) - 1) - { - error ("iconv_open() error"); - if (errno == EINVAL) - error ("invalid code specification: \"%s\" or \"%s\"", - EUCSTR, code); - strcpy ((char *) to, (const char *) from); - return; + iconv_t cd; + size_t from_len, to_len; + + if ((cd = iconv_open(EUCSTR, code)) == (iconv_t)-1) { + gd_error("iconv_open() error"); + if (errno == EINVAL) + gd_error("invalid code specification: \"%s\" or \"%s\"", EUCSTR, code); + ustrcpy(*to, *from); + return; } - from_len = strlen ((const char *) from) + 1; - to_len = BUFSIZ; - - if ((int) iconv(cd, (char **) &from, &from_len, (char **) &to, &to_len) == -1) - { - if (errno == EINVAL) - error ("invalid end of input string"); - else if (errno == EILSEQ) - error ("invalid code in input string"); - else if (errno == E2BIG) - error ("output buffer overflow at do_convert()"); - else - error ("something happen"); - strcpy ((char *) to, (const char *) from); - if (iconv_close (cd) != 0) - error ("iconv_close() error"); - return; + from_len = strlen((const char *)*from) + 1; + to_len = BUFSIZ; + + if ((int)(iconv(cd, (char **)from, &from_len, (char **)to, &to_len)) == -1) { + if (errno == EINVAL) + gd_error("invalid end of input string"); + else if (errno == EILSEQ) + gd_error("invalid code in input string"); + else if (errno == E2BIG) + gd_error("output buffer overflow at do_convert()"); + else + gd_error("something happen"); + ustrcpy(*to, *from); + if (iconv_close(cd) != 0) + gd_error("iconv_close() error"); + return; } - if (iconv_close (cd) != 0) - { - error ("iconv_close() error"); - } + if (iconv_close(cd) != 0) + gd_error("iconv_close() error"); #else - int p1, p2, i, j; - int jisx0208 = FALSE; - int hankaku = FALSE; - - j = 0; - if (strcmp (code, NEWJISSTR) == 0 || strcmp (code, OLDJISSTR) == 0) - { - for (i = 0; from[i] != '\0' && j < BUFSIZ; i++) - { - if (from[i] == ESC) - { - i++; - if (from[i] == '$') - { - jisx0208 = TRUE; - hankaku = FALSE; - i++; - } - else if (from[i] == '(') - { - jisx0208 = FALSE; - i++; - if (from[i] == 'I') /* Hankaku Kana */ - hankaku = TRUE; - else - hankaku = FALSE; - } - } - else - { - if (jisx0208) - to[j++] = from[i] + 128; - else if (hankaku) - { - to[j++] = SS2; - to[j++] = from[i] + 128; - } - else - to[j++] = from[i]; - } - } - } - else if (strcmp (code, SJISSTR) == 0) - { - for (i = 0; from[i] != '\0' && j < BUFSIZ; i++) - { - p1 = from[i]; - if (p1 < 127) - to[j++] = p1; - else if ((p1 >= 161) && (p1 <= 223)) - { /* Hankaku Kana */ - to[j++] = SS2; - to[j++] = p1; - } - else - { - p2 = from[++i]; - SJIStoJIS (&p1, &p2); - to[j++] = p1 + 128; - to[j++] = p2 + 128; - } - } - } - else - { - error ("invalid code specification: \"%s\"", code); - return; + int p1, p2, i, j; + int jisx0208 = FALSE; + int hankaku = FALSE; + unsigned char *to_buf = *to; + const unsigned char *from_buf = *from; + + j = 0; + if (strcmp(code, NEWJISSTR) == 0 || strcmp(code, OLDJISSTR) == 0) { + for (i = 0; from_buf[i] != '\0' && j < BUFSIZ; i++) { + if (from_buf[i] == ESC) { + i++; + if (from_buf[i] == '$') { + jisx0208 = TRUE; + hankaku = FALSE; + i++; + } else if (from_buf[i] == '(') { + jisx0208 = FALSE; + i++; + if (from_buf[i] == 'I') + hankaku = TRUE; + else + hankaku = FALSE; + } + } else { + if (jisx0208) + to_buf[j++] = from_buf[i] + 128; + else if (hankaku) { + to_buf[j++] = SS2; + to_buf[j++] = from_buf[i] + 128; + } else + to_buf[j++] = from_buf[i]; + } + } + } else if (strcmp(code, SJISSTR) == 0) { + for (i = 0; from_buf[i] != '\0' && j < BUFSIZ; i++) { + p1 = from_buf[i]; + if (p1 < 127) + to_buf[j++] = p1; + else if ((p1 >= 161) && (p1 <= 223)) { + to_buf[j++] = SS2; + to_buf[j++] = p1; + } else { + p2 = from_buf[++i]; + SJIStoJIS(&p1, &p2); + to_buf[j++] = p1 + 128; + to_buf[j++] = p2 + 128; + } + } + } else { + gd_error("invalid code specification: \"%s\"", code); + return; } - if (j >= BUFSIZ) - { - error ("output buffer overflow at do_convert()"); - ustrcpy (to, from); - } - else - to[j] = '\0'; -#endif /* HAVE_ICONV */ + if (j >= BUFSIZ) { + gd_error("output buffer overflow at do_convert()"); + ustrcpy(*to, *from); + } else + to_buf[j] = '\0'; +#endif } -static int -do_check_and_conv (unsigned char *to, unsigned char *from) -{ +static int do_check_and_conv(unsigned char *to, const unsigned char *from) { static unsigned char tmp[BUFSIZ]; + unsigned char *tmp_p = &tmp[0]; int p1, p2, i, j; int kanji = TRUE; + int copy_string = FALSE; - switch (DetectKanjiCode (from)) - { + switch (DetectKanjiCode(from)) { case NEW: - debug ("Kanji code is New JIS."); - do_convert (tmp, from, NEWJISSTR); + gd_error_ex(GD_DEBUG, "Kanji code is New JIS."); + do_convert(&tmp_p, &from, NEWJISSTR); break; case OLD: - debug ("Kanji code is Old JIS."); - do_convert (tmp, from, OLDJISSTR); + gd_error_ex(GD_DEBUG, "Kanji code is Old JIS."); + do_convert(&tmp_p, &from, OLDJISSTR); break; case ESCI: - debug ("This string includes Hankaku-Kana (jisx0201) escape sequence [ESC] + ( + I."); - do_convert (tmp, from, NEWJISSTR); + gd_error_ex(GD_DEBUG, "This string includes Hankaku-Kana (jisx0201) " + "escape sequence [ESC] + ( + I."); + do_convert(&tmp_p, &from, NEWJISSTR); break; case NEC: - debug ("Kanji code is NEC Kanji."); - error ("cannot convert NEC Kanji."); - ustrcpy (tmp, from); + gd_error_ex(GD_DEBUG, "Kanji code is NEC Kanji."); + gd_error("cannot convert NEC Kanji."); + copy_string = TRUE; kanji = FALSE; break; case EUC: - debug ("Kanji code is EUC."); - ustrcpy (tmp, from); + gd_error_ex(GD_DEBUG, "Kanji code is EUC."); + copy_string = TRUE; break; case SJIS: - debug ("Kanji code is SJIS."); - do_convert (tmp, from, SJISSTR); + gd_error_ex(GD_DEBUG, "Kanji code is SJIS."); + do_convert(&tmp_p, &from, SJISSTR); break; case EUCORSJIS: - debug ("Kanji code is EUC or SJIS."); - ustrcpy (tmp, from); + gd_error_ex(GD_DEBUG, "Kanji code is EUC or SJIS."); + copy_string = TRUE; kanji = FALSE; break; case ASCII: - debug ("This is ASCII string."); - ustrcpy (tmp, from); + gd_error_ex(GD_DEBUG, "This is ASCII string."); + copy_string = TRUE; kanji = FALSE; break; default: - debug ("This string includes unknown code."); - ustrcpy (tmp, from); + gd_error_ex(GD_DEBUG, "This string includes unknown code."); + copy_string = TRUE; kanji = FALSE; break; + } + + if (copy_string) { + ustrncpy(tmp, from, BUFSIZ); + tmp[BUFSIZ - 1] = '\0'; } /* Hankaku Kana ---> Zenkaku Kana */ - if (kanji) - { + if (kanji) { j = 0; - for (i = 0; tmp[i] != '\0' && j < BUFSIZ; i++) - { - if (tmp[i] == SS2) - { + for (i = 0; tmp[i] != '\0' && j < BUFSIZ; i++) { + if (tmp[i] == SS2) { p1 = tmp[++i]; - if (tmp[i + 1] == SS2) - { + if (tmp[i + 1] == SS2) { p2 = tmp[i + 2]; if (p2 == 222 || p2 == 223) i += 2; else p2 = 0; - } - else + } else p2 = 0; han2zen (&p1, &p2); SJIStoJIS (&p1, &p2); to[j++] = p1 + 128; to[j++] = p2 + 128; - } - else + } else to[j++] = tmp[i]; } - if (j >= BUFSIZ) - { - error ("output buffer overflow at Hankaku --> Zenkaku"); + if (j >= BUFSIZ) { + gd_error("output buffer overflow at Hankaku --> Zenkaku"); ustrcpy (to, tmp); - } - else + } else to[j] = '\0'; - } - else + } else ustrcpy (to, tmp); return kanji; } -int -any2eucjp (unsigned char *dest, unsigned char *src, unsigned int dest_max) -{ +int any2eucjp(unsigned char *dest, const unsigned char *src, + unsigned int dest_max) { static unsigned char tmp_dest[BUFSIZ]; int ret; - if (strlen ((const char *) src) >= BUFSIZ) - { - error ("input string too large"); + if (strlen((const char *)src) >= BUFSIZ) { + gd_error("input string too large"); return -1; } - if (dest_max > BUFSIZ) - { - error ("invalid maximum size of destination\nit should be less than %d.", BUFSIZ); + if (dest_max > BUFSIZ) { + gd_error( + "invalid maximum size of destination\nit should be less than %d.", + BUFSIZ); return -1; } ret = do_check_and_conv (tmp_dest, src); - if (strlen ((const char *) tmp_dest) >= dest_max) - { - error ("output buffer overflow"); + if (strlen((const char *)tmp_dest) >= dest_max) { + gd_error("output buffer overflow"); ustrcpy (dest, src); return -1; } diff --git a/ext/gd/libgd/gdxpm.c b/ext/gd/libgd/gdxpm.c index 8db7fe043826..baf4932352fa 100644 --- a/ext/gd/libgd/gdxpm.c +++ b/ext/gd/libgd/gdxpm.c @@ -1,25 +1,79 @@ - /* - add ability to load xpm files to gd, requires the xpm - library. - Caolan.McNamara@ul.ie - http://www.csn.ul.ie/~caolan + * Add ability to load xpm files to gd, requires the xpm + * library. + * Caolan.McNamara@ul.ie + * http://www.csn.ul.ie/~caolan + */ + +/** + * File: XPM Input + * + * Read XPM images. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gd.h" +#include "gd_color_map.h" +#include "gd_errors.h" +#include "gdhelpers.h" #include #include #include -#include "gd.h" -#include "gdhelpers.h" -#ifdef HAVE_XPM +#ifndef HAVE_LIBXPM +BGD_DECLARE(gdImagePtr) gdImageCreateFromXpm(char *filename) { + (void)filename; + gd_error_ex(GD_ERROR, "libgd was not built with xpm support\n"); + return NULL; +} +#else #include -gdImagePtr gdImageCreateFromXpm (char *filename) -{ +/* + Function: gdImageCreateFromXpm + + is called to load images from XPM X Window + System color bitmap format files. This function is available only + if HAVE_XPM is selected in the Makefile and the Xpm library is + linked with the application. Unlike most gd file functions, the + Xpm functions *require filenames*, not file + pointers. returns a to the new + image, or NULL if unable to load the image (most often because the + file is corrupt or does not contain an XPM bitmap format + image). You can inspect the sx and sy members of the image to + determine its size. The image must eventually be destroyed using + . + + Parameters: + + filename - The input filename (*not* FILE pointer) + + Returns: + + A pointer to the new image or NULL if an error occurred. + + Example: + (start code) + + gdImagePtr im; + FILE *in; + in = fopen("myxpm.xpm", "rb"); + im = gdImageCreateFromXpm(in); + fclose(in); + // ... Use the image ... + gdImageDestroy(im); + + (end code) + +*/ +BGD_DECLARE(gdImagePtr) gdImageCreateFromXpm(char *filename) { XpmInfo info = {0}; XpmImage image; - int i, j, k, number; + unsigned int i, j, k, number, len; char buf[5]; gdImagePtr im = 0; int *pointer; @@ -31,109 +85,159 @@ gdImagePtr gdImageCreateFromXpm (char *filename) if (ret != XpmSuccess) { return 0; } + number = image.ncolors; - for(i = 0; i < number; i++) { + if (overflow2(sizeof(int), number)) { + goto done; + } + for (i = 0; i < number; i++) { + /* + avoid NULL pointer dereference + TODO better fix need to manage monochrome/monovisual + see m_color or g4_color or g_color + */ if (!image.colorTable[i].c_color) { goto done; } } + colors = (int *)gdMalloc(sizeof(int) * number); + if (colors == NULL) { + goto done; + } + if (!(im = gdImageCreate(image.width, image.height))) { + gdFree(colors); goto done; } - colors = (int *) safe_emalloc(number, sizeof(int), 0); for (i = 0; i < number; i++) { - switch (strlen (image.colorTable[i].c_color)) { + char *c_color = image.colorTable[i].c_color; + int valid_color = 1; + if (strcmp(c_color, "None") == 0) { + colors[i] = gdImageGetTransparent(im); + if (colors[i] == -1) + colors[i] = gdImageColorAllocate(im, 0, 0, 0); + if (colors[i] != -1) + gdImageColorTransparent(im, colors[i]); + continue; + } + len = strlen(c_color); + if (len < 1) { + valid_color = 0; + } + if (c_color[0] == '#') { + switch (len) { case 4: - buf[1] = '\0'; - buf[0] = image.colorTable[i].c_color[1]; + buf[2] = '\0'; + buf[0] = buf[1] = c_color[1]; red = strtol(buf, NULL, 16); - buf[0] = image.colorTable[i].c_color[2]; + buf[0] = buf[1] = c_color[2]; green = strtol(buf, NULL, 16); - buf[0] = image.colorTable[i].c_color[3]; + buf[0] = buf[1] = c_color[3]; blue = strtol(buf, NULL, 16); break; case 7: buf[2] = '\0'; - buf[0] = image.colorTable[i].c_color[1]; - buf[1] = image.colorTable[i].c_color[2]; + buf[0] = c_color[1]; + buf[1] = c_color[2]; red = strtol(buf, NULL, 16); - buf[0] = image.colorTable[i].c_color[3]; - buf[1] = image.colorTable[i].c_color[4]; + buf[0] = c_color[3]; + buf[1] = c_color[4]; green = strtol(buf, NULL, 16); - buf[0] = image.colorTable[i].c_color[5]; - buf[1] = image.colorTable[i].c_color[6]; + buf[0] = c_color[5]; + buf[1] = c_color[6]; blue = strtol(buf, NULL, 16); break; case 10: buf[3] = '\0'; - buf[0] = image.colorTable[i].c_color[1]; - buf[1] = image.colorTable[i].c_color[2]; - buf[2] = image.colorTable[i].c_color[3]; + buf[0] = c_color[1]; + buf[1] = c_color[2]; + buf[2] = c_color[3]; red = strtol(buf, NULL, 16); red /= 64; - buf[0] = image.colorTable[i].c_color[4]; - buf[1] = image.colorTable[i].c_color[5]; - buf[2] = image.colorTable[i].c_color[6]; + buf[0] = c_color[4]; + buf[1] = c_color[5]; + buf[2] = c_color[6]; green = strtol(buf, NULL, 16); green /= 64; - buf[0] = image.colorTable[i].c_color[7]; - buf[1] = image.colorTable[i].c_color[8]; - buf[2] = image.colorTable[i].c_color[9]; + buf[0] = c_color[7]; + buf[1] = c_color[8]; + buf[2] = c_color[9]; blue = strtol(buf, NULL, 16); blue /= 64; break; case 13: buf[4] = '\0'; - buf[0] = image.colorTable[i].c_color[1]; - buf[1] = image.colorTable[i].c_color[2]; - buf[2] = image.colorTable[i].c_color[3]; - buf[3] = image.colorTable[i].c_color[4]; + buf[0] = c_color[1]; + buf[1] = c_color[2]; + buf[2] = c_color[3]; + buf[3] = c_color[4]; red = strtol(buf, NULL, 16); red /= 256; - buf[0] = image.colorTable[i].c_color[5]; - buf[1] = image.colorTable[i].c_color[6]; - buf[2] = image.colorTable[i].c_color[7]; - buf[3] = image.colorTable[i].c_color[8]; + buf[0] = c_color[5]; + buf[1] = c_color[6]; + buf[2] = c_color[7]; + buf[3] = c_color[8]; green = strtol(buf, NULL, 16); green /= 256; - buf[0] = image.colorTable[i].c_color[9]; - buf[1] = image.colorTable[i].c_color[10]; - buf[2] = image.colorTable[i].c_color[11]; - buf[3] = image.colorTable[i].c_color[12]; + buf[0] = c_color[9]; + buf[1] = c_color[10]; + buf[2] = c_color[11]; + buf[3] = c_color[12]; blue = strtol(buf, NULL, 16); blue /= 256; break; + default: + valid_color = 0; + break; + } + } else if (!gdColorMapLookup(GD_COLOR_MAP_X11, c_color, &red, &green, + &blue)) { + valid_color = 0; } + if (!valid_color) { + gdFree(colors); + gdImageDestroy(im); + im = NULL; + goto done; + } colors[i] = gdImageColorResolve(im, red, green, blue); } - pointer = (int *) image.data; + pointer = (int *)image.data; + for (i = 0; i < image.height; i++) { for (j = 0; j < image.width; j++) { k = *pointer++; + if (k >= number) { + gdFree(colors); + gdImageDestroy(im); + im = NULL; + goto done; + } gdImageSetPixel(im, j, i, colors[k]); } } gdFree(colors); - done: + +done: XpmFreeXpmImage(&image); XpmFreeXpmInfo(&info); return im; } -#endif +#endif /* HAVE_LIBXPM */ diff --git a/ext/gd/tests/bug43073.phpt b/ext/gd/tests/bug43073.phpt index 80c6480fea57..9867eb98a268 100644 --- a/ext/gd/tests/bug43073.phpt +++ b/ext/gd/tests/bug43073.phpt @@ -13,21 +13,21 @@ gd = $exp[$i][$j] - 1 && $bbox[$j] <= $exp[$i][$j] + 1) { echo '.'; } else { - echo "(expected $exp[$i][$j], got $bbox[$j])"; + echo "(expected {$exp[$i][$j]}, got {$bbox[$j]})"; } } echo "\n"; diff --git a/ext/gd/tests/bug48732-mb.phpt b/ext/gd/tests/bug48732-mb.phpt index 361c1ac4d88f..e57a913de007 100644 --- a/ext/gd/tests/bug48732-mb.phpt +++ b/ext/gd/tests/bug48732-mb.phpt @@ -22,6 +22,7 @@ imagepng($g, "$cwd/bug48732私はガラスを食べられます.png"); echo 'Left Bottom: (' . $bbox[0] . ', ' . $bbox[1] . ')'; ?> --CLEAN-- - + --EXPECT-- -Left Bottom: (0, 46) +Left Bottom: (0, 40) diff --git a/ext/gd/tests/bug48732.phpt b/ext/gd/tests/bug48732.phpt index ed73954de333..7a3775776d64 100644 --- a/ext/gd/tests/bug48732.phpt +++ b/ext/gd/tests/bug48732.phpt @@ -22,6 +22,7 @@ imagepng($g, "$cwd/bug48732.png"); echo 'Left Bottom: (' . $bbox[0] . ', ' . $bbox[1] . ')'; ?> --CLEAN-- - + --EXPECT-- -Left Bottom: (0, 46) +Left Bottom: (0, 40) diff --git a/ext/gd/tests/bug48801-mb.phpt b/ext/gd/tests/bug48801-mb.phpt index 247161518b19..f07361694a2e 100644 --- a/ext/gd/tests/bug48801-mb.phpt +++ b/ext/gd/tests/bug48801-mb.phpt @@ -18,7 +18,7 @@ echo '(' . $bbox[4] . ', ' . $bbox[5] . ")\n"; echo '(' . $bbox[6] . ', ' . $bbox[7] . ")\n"; ?> --EXPECTREGEX-- -\(4, 15\) -\(16[0-1], 15\) -\(16[0-1], -4[7-8]\) -\(4, -4[7-8]\) +\(4, 1[4-5]\) +\(16[0-1], 1[4-5]\) +\(16[0-1], -4[6-8]\) +\(4, -4[6-8]\) diff --git a/ext/gd/tests/bug48801.phpt b/ext/gd/tests/bug48801.phpt index 361bf59a65b9..c9ca3c16c1fa 100644 --- a/ext/gd/tests/bug48801.phpt +++ b/ext/gd/tests/bug48801.phpt @@ -17,7 +17,7 @@ echo '(' . $bbox[4] . ', ' . $bbox[5] . ")\n"; echo '(' . $bbox[6] . ', ' . $bbox[7] . ")\n"; ?> --EXPECTREGEX-- -\(4, 15\) -\(16[0-1], 15\) -\(16[0-1], -4[7-8]\) -\(4, -4[7-8]\) +\(4, 14\) +\(16[0-1], 14\) +\(16[0-1], -4[6-8]\) +\(4, -4[5-8]\) diff --git a/ext/gd/tests/bug53504.phpt b/ext/gd/tests/bug53504.phpt index 5bd81f35aa0c..fb96ac3c8f2a 100644 --- a/ext/gd/tests/bug53504.phpt +++ b/ext/gd/tests/bug53504.phpt @@ -22,35 +22,35 @@ $blue = imagecolorallocate($g, 0, 0, 255); $tests = [ // Kerning examples (unfortunately not available in "Tuffy" test font): - ['fontSize' => 50, 'angle' => 0, 'x' => 20, 'y' => 70, 'text' => 'AV Teg', 'exp' => [2,15, 208,15, 208,-48, 2,-48]], - ['fontSize' => 50, 'angle' => 90, 'x' => 70, 'y' => 350, 'text' => 'AV Teg', 'exp' => [15,-1, 15,-208, -48,-208, -48,-2]], - ['fontSize' => 50, 'angle' => 40, 'x' => 130, 'y' => 280, 'text' => 'AV Teg', 'exp' => [11,11, 169,-122, 129,-171, -30,-39]], + ['fontSize' => 50, 'angle' => 0, 'x' => 20, 'y' => 70, 'text' => 'AV Teg', 'exp' => [2,14, 209,14, 209,-47, 2,-47]], + ['fontSize' => 50, 'angle' => 90, 'x' => 70, 'y' => 350, 'text' => 'AV Teg', 'exp' => [14,-2, 14,-209, -47,-209, -47,-2]], + ['fontSize' => 50, 'angle' => 40, 'x' => 130, 'y' => 280, 'text' => 'AV Teg', 'exp' => [11,9, 169,-123, 129,-170, -28,-37]], // Shift-Test: - ['fontSize' => 100, 'angle' => 0, 'x' => 350, 'y' => 110, 'text' => 'H-Shift', 'exp' => [8,2, 386,2, 386,-97, 8,-97]], + ['fontSize' => 100, 'angle' => 0, 'x' => 350, 'y' => 110, 'text' => 'H-Shift', 'exp' => [8,1, 393,1, 393,-97, 8,-97]], // Small/single chars: - ['fontSize' => 100, 'angle' => 0, 'x' => 350, 'y' => 220, 'text' => '-', 'exp' => [7,-37, 51,-37, 51,-46, 7,-46]], - ['fontSize' => 100, 'angle' => 0, 'x' => 430, 'y' => 220, 'text' => ',', 'exp' => [7,15, 21,15, 21,-13, 7,-13]], - ['fontSize' => 100, 'angle' => 0, 'x' => 510, 'y' => 220, 'text' => '.', 'exp' => [7,1, 21,1, 21,-13, 7,-13]], - ['fontSize' => 100, 'angle' => 0, 'x' => 590, 'y' => 220, 'text' => '|', 'exp' => [8,0, 17,0, 17,-95, 8,-95]], - ['fontSize' => 100, 'angle' => 0, 'x' => 670, 'y' => 220, 'text' => 'g', 'exp' => [5,29, 60,29, 60,-72, 5,-72]], + ['fontSize' => 100, 'angle' => 0, 'x' => 350, 'y' => 220, 'text' => '-', 'exp' => [7,-36, 56,-36, 56,-46, 7,-46]], + ['fontSize' => 100, 'angle' => 0, 'x' => 430, 'y' => 220, 'text' => ',', 'exp' => [6,14, 25,14, 25,-13, 6,-13]], + ['fontSize' => 100, 'angle' => 0, 'x' => 510, 'y' => 220, 'text' => '.', 'exp' => [6,0, 26,0, 26,-13, 6,-13]], + ['fontSize' => 100, 'angle' => 0, 'x' => 590, 'y' => 220, 'text' => '|', 'exp' => [8,0, 23,0, 23,-95, 8,-95]], + ['fontSize' => 100, 'angle' => 0, 'x' => 670, 'y' => 220, 'text' => 'g', 'exp' => [4,29, 66,29, 66,-71, 4,-71]], // Multi-Line + rotation: - ['fontSize' => 30, 'angle' => 0, 'x' => 20, 'y' => 400, 'text' => "Multi\nLine\nTest", 'exp' => [2,107, 80,107, 80,-29, 2,-29]], - ['fontSize' => 30, 'angle' => 40, 'x' => 150, 'y' => 420, 'text' => "Multi\nLine\nTest", 'exp' => [70,81, 131,31, 43,-74, -18,-24]], - ['fontSize' => 30, 'angle' => 90, 'x' => 250, 'y' => 340, 'text' => "Multi\nLine\nTest", 'exp' => [107,-1, 107,-80, -29,-80, -29,-2]], + ['fontSize' => 30, 'angle' => 0, 'x' => 20, 'y' => 400, 'text' => "Multi\nLine\nTest", 'exp' => [1,84, 81,84, 81,-28, 1,-28]], + ['fontSize' => 30, 'angle' => 40, 'x' => 150, 'y' => 420, 'text' => "Multi\nLine\nTest", 'exp' => [55,63, 116,12, 43,-74, -17,-22]], + ['fontSize' => 30, 'angle' => 90, 'x' => 250, 'y' => 340, 'text' => "Multi\nLine\nTest", 'exp' => [84,-1, 84,-81, -28,-81, -28,-1]], // Some edge case glyphs: - ['fontSize' => 50, 'angle' => 90, 'x' => 70, 'y' => 750, 'text' => "iiiiiiiiiiii", 'exp' => [0,-4, 0,-165, -47,-165, -47,-4]], - ['fontSize' => 50, 'angle' => 90, 'x' => 150, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [-19,-2, -18,-167, -29,-167, -29,-2]], - ['fontSize' => 50, 'angle' => 50, 'x' => 210, 'y' => 750, 'text' => "iiiiiiiiiiii", 'exp' => [3,-3, 107,-127, 70,-157, -34,-33]], - ['fontSize' => 50, 'angle' => 50, 'x' => 300, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [-13,-13, 93,-141, 85,-147, -21,-20]], - ['fontSize' => 50, 'angle' => 0, 'x' => 430, 'y' => 650, 'text' => "iiiiiiiiiiii", 'exp' => [4,0, 165,0, 165,-47, 4,-47]], - ['fontSize' => 50, 'angle' => 0, 'x' => 430, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [2,-19, 167,-19, 167,-29, 2,-29]], + ['fontSize' => 50, 'angle' => 90, 'x' => 70, 'y' => 750, 'text' => "iiiiiiiiiiii", 'exp' => [0,-4, 0,-165, -46,-165, -46,-4]], + ['fontSize' => 50, 'angle' => 90, 'x' => 150, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [-18,-1, -18,-168, -27,-168, -27,-1]], + ['fontSize' => 50, 'angle' => 50, 'x' => 210, 'y' => 750, 'text' => "iiiiiiiiiiii", 'exp' => [2,-3, 108,-129, 73,-159, -32,-33]], + ['fontSize' => 50, 'angle' => 50, 'x' => 300, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [-12,-13, 94,-140, 86,-146, -20,-19]], + ['fontSize' => 50, 'angle' => 0, 'x' => 430, 'y' => 650, 'text' => "iiiiiiiiiiii", 'exp' => [4,0, 165,0, 165,-46, 4,-46]], + ['fontSize' => 50, 'angle' => 0, 'x' => 430, 'y' => 750, 'text' => "~~~~~~~", 'exp' => [1,-18, 168,-18, 168,-27, 1,-27]], // "Big" test: - ['fontSize' => 200, 'angle' => 0, 'x' => 400, 'y' => 500, 'text' => "Big", 'exp' => [16,59, 329,59, 329,-190, 16,-190]], + ['fontSize' => 200, 'angle' => 0, 'x' => 400, 'y' => 500, 'text' => "Big", 'exp' => [15,58, 342,58, 342,-189, 15,-189]], ]; foreach ($tests as $testnum => $test) { @@ -71,8 +71,8 @@ foreach ($tests as $testnum => $test) { // check if both bboxes match when adding x/y offset: for ($i = 0; $i < count($bbox); $i += 2) { - if ($bbox[$i] + $test['x'] !== $bboxDrawn[$i]) echo "imageftbbox and imagefttext differ!\n"; - if ($bbox[$i + 1] + $test['y'] !== $bboxDrawn[$i + 1]) echo "imageftbbox and imagefttext differ!\n"; + if (abs($bbox[$i] + $test['x'] - $bboxDrawn[$i]) > 1) echo "imageftbbox and imagefttext differ!\n"; + if (abs($bbox[$i + 1] + $test['y'] - $bboxDrawn[$i + 1]) > 1) echo "imageftbbox and imagefttext differ!\n"; } // draw bounding box: diff --git a/ext/gd/tests/bug65148.phpt b/ext/gd/tests/bug65148.phpt index 0d71655aad10..77f4a384f186 100644 --- a/ext/gd/tests/bug65148.phpt +++ b/ext/gd/tests/bug65148.phpt @@ -46,128 +46,128 @@ Array ( [IMG_BELL] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BESSEL] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BICUBIC] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BICUBIC_FIXED] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BILINEAR_FIXED] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BLACKMAN] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BOX] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_BSPLINE] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_CATMULLROM] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_GAUSSIAN] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_GENERALIZED_CUBIC] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_HERMITE] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_HAMMING] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_HANNING] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_MITCHELL] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_POWER] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_QUADRATIC] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_SINC] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_NEAREST_NEIGHBOUR] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_WEIGHTED4] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) [IMG_TRIANGLE] => Array ( - [x] => 40 - [y] => 23 + [x] => 42 + [y] => 24 ) ) diff --git a/ext/gd/tests/bug73272.png b/ext/gd/tests/bug73272.png index 514e012a9298..6de8ac4715bd 100644 Binary files a/ext/gd/tests/bug73272.png and b/ext/gd/tests/bug73272.png differ diff --git a/ext/gd/tests/bug73291.phpt b/ext/gd/tests/bug73291.phpt index e23b1fc7d17a..3d54c2878a18 100644 --- a/ext/gd/tests/bug73291.phpt +++ b/ext/gd/tests/bug73291.phpt @@ -25,7 +25,7 @@ foreach ([0.1, 0.5, 1.0, 10.0] as $threshold) { ?> --EXPECT-- -size: 247*247 -size: 237*237 -size: 229*229 -size: 175*175 +size: 255*255 +size: 253*253 +size: 253*253 +size: 225*225 diff --git a/ext/gd/tests/createfromwbmp2_extern.phpt b/ext/gd/tests/createfromwbmp2_extern.phpt index 5cd178cb59e5..68895f9a3570 100644 --- a/ext/gd/tests/createfromwbmp2_extern.phpt +++ b/ext/gd/tests/createfromwbmp2_extern.phpt @@ -2,10 +2,6 @@ imagecreatefromwbmp with invalid wbmp --EXTENSIONS-- gd ---SKIPIF-- - --FILE-- +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/main/php_compat.h b/main/php_compat.h index 68bc4e7550f8..852b4fbb1ff8 100644 --- a/main/php_compat.h +++ b/main/php_compat.h @@ -266,6 +266,8 @@ #define gdImageCreateFromWBMPCtx php_gd_gdImageCreateFromWBMPCtx #define gdImageCreateFromWBMPPtr php_gd_gdImageCreateFromWBMPPtr #define gdImageCreateFromXbm php_gd_gdImageCreateFromXbm +#define gdImageWebp php_gd_gdImageWebp +#define gdImageHeif php_gd_gdImageHeif #define gdImageCreatePaletteFromTrueColor php_gd_gdImageCreatePaletteFromTrueColor #define gdImageCreateTrueColor php_gd_gdImageCreateTrueColor #define gdImageDashedLine php_gd_gdImageDashedLine @@ -307,6 +309,7 @@ #define gdImagePngPtr php_gd_gdImagePngPtr #define gdImagePngPtrEx php_gd_gdImagePngPtrEx #define gdImagePngToSink php_gd_gdImagePngToSink +#define gdPngGetVersionString php_gd_gdPngGetVersionString #define gdImagePolygon php_gd_gdImagePolygon #define gdImageRectangle php_gd_gdImageRectangle #define gdImageRotate php_gd_gdImageRotate @@ -375,7 +378,10 @@ #define gdCacheGet php_gd_gdCacheGet #define gdFontCacheSetup php_gd_gdFontCacheSetup #define gdFontCacheShutdown php_gd_gdFontCacheShutdown +#define gdFontCacheMutexSetup php_gd_gdFontCacheMutexSetup +#define gdFontCacheMutexShutdown php_gd_gdFontCacheMutexShutdown #define gdFreeFontCache php_gd_gdFreeFontCache +#define gdFTUseFontConfig php_gd_gdFTUseFontConfig #endif /* HAVE_GD_BUNDLED */ /* Define to specify how much context to retain around the current parse