/* +----------------------------------------------------------------------+ | PHP Version 5 / Imagick | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Dan Ackroyd | +----------------------------------------------------------------------+ */ #include "php_imagick.h" #include "php_imagick_defs.h" #include "php_imagick_macros.h" #include "php_imagick_helpers.h" #ifdef IMAGICK_WITH_KERNEL #if MagickLibVersion < 0x691 // These function defines are required currently as the functions are // currently not available in the public ImageMagick header files // This is being fixed for the next version of ImageMagick. Image *MorphologyApply(const Image *,const ChannelType,const MorphologyMethod, const ssize_t,const KernelInfo *,const CompositeOperator,const double, ExceptionInfo *); void ScaleKernelInfo(KernelInfo *,const double,const GeometryFlags), UnityAddKernelInfo(KernelInfo *,const double), ZeroKernelNans(KernelInfo *); #endif static void php_imagickkernelvalues_to_zval(zval *zv, KernelInfo *kernel_info) { int count; double value; unsigned int x, y; #if PHP_VERSION_ID >= 70000 zval row; #else zval *row; #endif zval *p_row; count = 0; for (y=0; yheight ; y++) { #if PHP_VERSION_ID >= 70000 p_row = &row; #else MAKE_STD_ZVAL(row); p_row = row; #endif array_init(p_row); for (x=0; xwidth ; x++) { value = kernel_info->values[count]; count++; //nan is not equal to itself if (value != value) { //this will be broken by some compilers - need to investigate more... add_next_index_bool(p_row, 0); } else { add_next_index_double(p_row, value); } } add_next_index_zval(zv, p_row); } } #if PHP_VERSION_ID >= 80000 HashTable* php_imagickkernel_get_debug_info(zend_object *obj, int *is_temp TSRMLS_DC) /* {{{ */ #else HashTable* php_imagickkernel_get_debug_info(zval *obj, int *is_temp TSRMLS_DC) /* {{{ */ #endif { php_imagickkernel_object *internp; HashTable *debug_info; KernelInfo *kernel_info; #if PHP_VERSION_ID >= 70000 zval matrix; #else zval *matrix; #endif *is_temp = 1; //var_dump will destroy the hashtable #if PHP_VERSION_ID >= 80000 internp = php_imagickkernel_fetch_object(obj); #else internp = Z_IMAGICKKERNEL_P(obj); #endif kernel_info = internp->kernel_info; ALLOC_HASHTABLE(debug_info); ZEND_INIT_SYMTABLE_EX(debug_info, 1, 0); while (kernel_info != NULL) { #if PHP_VERSION_ID >= 70000 array_init(&matrix); php_imagickkernelvalues_to_zval(&matrix, kernel_info); zend_hash_next_index_insert(debug_info, &matrix); #else MAKE_STD_ZVAL(matrix); array_init(matrix); php_imagickkernelvalues_to_zval(matrix, kernel_info); zend_hash_next_index_insert(debug_info, &matrix, sizeof(zval *), NULL); #endif kernel_info = kernel_info->next; } return debug_info; } static void im_CalcKernelMetaData(KernelInfo *kernel) { size_t i; kernel->minimum = kernel->maximum = 0.0; kernel->negative_range = kernel->positive_range = 0.0; for (i=0; i < (kernel->width*kernel->height); i++) { if (fabs(kernel->values[i]) < MagickEpsilon) { kernel->values[i] = 0.0; } if (kernel->values[i] < 0) { kernel->negative_range += kernel->values[i]; } else { kernel->positive_range += kernel->values[i]; } if (kernel->values[i] < kernel->minimum) { kernel->minimum = kernel->values[i]; } if (kernel->values[i] > kernel->maximum) { kernel->maximum = kernel->values[i]; } } return; } #if MagickLibVersion > 0x661 static KernelInfo *imagick_createKernel(KernelValueType *values, size_t width, size_t height, size_t origin_x, size_t origin_y) { KernelInfo *kernel_info; #if MagickLibVersion >= 0x700 unsigned int i; ExceptionInfo *_exception_info = (ExceptionInfo *) NULL; //TODO - inspect exception info kernel_info=AcquireKernelInfo(NULL, _exception_info); #else kernel_info=AcquireKernelInfo(NULL); #endif if (kernel_info == (KernelInfo *) NULL) { return NULL; } kernel_info->width = width; kernel_info->height = height; kernel_info->x = origin_x; kernel_info->y = origin_y; //Need to free old values? if (kernel_info->values != NULL) { RelinquishAlignedMemory(kernel_info->values); } #if MagickLibVersion >= 0x700 kernel_info->values = (MagickRealType *)AcquireAlignedMemory(width*height, sizeof(MagickRealType)); for (i=0; ivalues[i] = (MagickRealType)values[i]; } #else kernel_info->values = values; #endif im_CalcKernelMetaData(kernel_info); return kernel_info; } #endif static void createKernelZval(zval *pzval, KernelInfo *kernel_info TSRMLS_DC) { php_imagickkernel_object *intern_return; object_init_ex(pzval, php_imagickkernel_sc_entry); intern_return = Z_IMAGICKKERNEL_P(pzval); intern_return->kernel_info = kernel_info; } /* {{{ proto ImagickKernel ImagickKernel::__construct() The ImagickKernel constructor */ PHP_METHOD(ImagickKernel, __construct) { // This suppresses an 'unused parameter' warning. (void)return_value; if (zend_parse_parameters_none() == FAILURE) { return; } // this method is private. } /* }}} */ #define MATRIX_ERROR_EMPTY "Cannot create kernel, matrix is empty." #define MATRIX_ERROR_UNEVEN "Values must be matrix, with the same number of columns in each row." #define MATRIX_ERROR_BAD_VALUE "Only numbers or false are valid values in a kernel matrix." #define MATRIX_ORIGIN_REQUIRED "For kernels with even numbered rows or columns, the origin position must be specified." /* {{{ proto ImagickKernel ImagickKernel::fromMatrix(array matrix, [array origin]) Create a kernel from an 2d matrix of values. Each value should either be a float (if the element should be used) or 'false' if the element should be skipped. For matrixes that are odd sizes in both dimensions the the origin pixel will default to the centre of the kernel. For all other kernel sizes the origin pixel must be specified. */ #if PHP_VERSION_ID >= 70000 PHP_METHOD(ImagickKernel, fromMatrix) { zval *kernel_array; zval *origin_array; HashTable *inner_array; KernelInfo *kernel_info; unsigned long num_rows, num_columns = 0; unsigned int previous_num_columns = (unsigned int)-1; unsigned int row, column; zval *pzval_outer; zval *pzval_inner; int count = 0; size_t origin_x, origin_y; zval *tmp; KernelValueType *values = NULL; double notanumber = sqrt((double)-1.0); /* Special Value : Not A Number */ count = 0; row = 0; origin_array = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|a", &kernel_array, &origin_array) == FAILURE) { return; } num_rows = zend_hash_num_elements(Z_ARRVAL_P(kernel_array)); if (num_rows == 0) { //error - array has zero elements. php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ERROR_EMPTY TSRMLS_CC); return; } for (row=0 ; row> 1; origin_y = (num_rows - 1) >> 1; } else { HashTable *origin_array_ht; origin_array_ht = Z_ARRVAL_P(origin_array); // parse the origin_x tmp = zend_hash_index_find(origin_array_ht, 0); if (tmp != NULL) { ZVAL_DEREF(tmp); origin_x = Z_LVAL_P(tmp); } else { php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ORIGIN_REQUIRED TSRMLS_CC); goto cleanup; } // origin_x is unsigned, so checking for > num_columns, also // checks for < 0 if (origin_x>=num_columns) { zend_throw_exception_ex( php_imagickkernel_exception_class_entry, 5 TSRMLS_CC, "origin_x for matrix is outside bounds of columns: " ZEND_LONG_FMT, origin_x ); goto cleanup; } // parse the origin_y tmp = zend_hash_index_find(origin_array_ht, 1); if (tmp != NULL) { ZVAL_DEREF(tmp); origin_y = Z_LVAL_P(tmp); } else { php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ORIGIN_REQUIRED TSRMLS_CC); goto cleanup; } // origin_y is unsigned, so checking for > num_rows, also // checks for < 0 if (origin_y>=num_rows) { zend_throw_exception_ex( php_imagickkernel_exception_class_entry, 5 TSRMLS_CC, "origin_y for matrix is outside bounds of rows: " ZEND_LONG_FMT, origin_x ); goto cleanup; } } kernel_info = imagick_createKernel(values, num_columns, num_rows, origin_x, origin_y); createKernelZval(return_value, kernel_info TSRMLS_CC); return; cleanup: if (values != NULL) { RelinquishAlignedMemory(values); } } #else // PHP 5 PHP_METHOD(ImagickKernel, fromMatrix) { zval *kernel_array; zval *origin_array; HashTable *inner_array; KernelInfo *kernel_info; unsigned long num_rows, num_columns = 0; unsigned int previous_num_columns = (unsigned int)-1; unsigned int row, column; HashTable *origin_array_ht; zval **ppzval_outer; zval **ppzval_inner; int count = 0; size_t origin_x, origin_y; zval **tmp; KernelValueType *values = NULL; double notanumber = sqrt((double)-1.0); /* Special Value : Not A Number */ previous_num_columns = -1; count = 0; row = 0; origin_array = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|a", &kernel_array, &origin_array) == FAILURE) { return; } num_rows = zend_hash_num_elements(Z_ARRVAL_P(kernel_array)); if (num_rows == 0) { //error - array has zero elements. php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ERROR_EMPTY TSRMLS_CC); return; } for (row=0 ; row> 1; origin_y = (num_rows - 1) >> 1; } else { origin_array_ht = Z_ARRVAL_P(origin_array); // parse and check the origin_x if (zend_hash_index_find(origin_array_ht, 0, (void**)&tmp) == SUCCESS) { origin_x = Z_LVAL_PP(tmp); } else { php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ORIGIN_REQUIRED TSRMLS_CC); goto cleanup; } // origin_x is unsigned, so checking for > num_columns, also // checks for < 0 if (origin_x>=num_columns) { zend_throw_exception_ex( php_imagickkernel_exception_class_entry, 5 TSRMLS_CC, "origin_x for matrix is outside bounds of columns: %d", origin_x ); goto cleanup; } // parse and check the origin_y if (zend_hash_index_find(origin_array_ht, 1, (void**)&tmp) == SUCCESS) { origin_y = Z_LVAL_PP(tmp); } else { php_imagick_throw_exception(IMAGICKKERNEL_CLASS, MATRIX_ORIGIN_REQUIRED TSRMLS_CC); goto cleanup; } // origin_y is unsigned, so checking for > num_rows, also // checks for < 0 if (origin_y>=num_rows) { zend_throw_exception_ex( php_imagickkernel_exception_class_entry, 5 TSRMLS_CC, "origin_y for matrix is outside bounds of rows: %d", origin_y ); goto cleanup; } } kernel_info = imagick_createKernel(values, num_columns, num_rows, origin_x, origin_y); createKernelZval(return_value, kernel_info TSRMLS_CC); return; cleanup: if (values != NULL) { RelinquishAlignedMemory(values); } } #endif //end of zend_engine_3 /* }}} */ static void imagick_fiddle_with_geometry_info(ssize_t type, GeometryFlags flags, GeometryInfo *geometry_info) { /* special handling of missing values in input string */ switch( type ) { /* Shape Kernel Defaults */ case UnityKernel: { if ((flags & WidthValue) == 0) geometry_info->rho = 1.0; /* Default scale = 1.0, zero is valid */ break; } case SquareKernel: case DiamondKernel: case OctagonKernel: case DiskKernel: case PlusKernel: case CrossKernel: { if ( (flags & HeightValue) == 0 ) { geometry_info->sigma = 1.0; /* Default scale = 1.0, zero is valid */ } break; } case RingKernel: { if ((flags & XValue) == 0) { geometry_info->xi = 1.0; /* Default scale = 1.0, zero is valid */ } break; } case RectangleKernel: { /* Rectangle - set size defaults */ if ((flags & WidthValue) == 0) { /* if no width then */ geometry_info->rho = geometry_info->sigma; /* then width = height */ } if (geometry_info->rho < 1.0) { /* if width too small */ geometry_info->rho = 3; /* then width = 3 */ } if (geometry_info->sigma < 1.0) { /* if height too small */ geometry_info->sigma = geometry_info->rho; /* then height = width */ } if ((flags & XValue) == 0) { /* center offset if not defined */ geometry_info->xi = (double)(((ssize_t)geometry_info->rho-1)/2); } if ((flags & YValue) == 0) { geometry_info->psi = (double)(((ssize_t)geometry_info->sigma-1)/2); } break; } /* Distance Kernel Defaults */ case ChebyshevKernel: case ManhattanKernel: case OctagonalKernel: case EuclideanKernel: { if ((flags & HeightValue) == 0) { /* no distance scale */ geometry_info->sigma = 100.0; /* default distance scaling */ } else if ((flags & AspectValue ) != 0) { /* '!' flag */ geometry_info->sigma = QuantumRange/(geometry_info->sigma+1); /* maximum pixel distance */ } else if ((flags & PercentValue ) != 0) { /* '%' flag */ geometry_info->sigma *= QuantumRange/100.0; /* percentage of color range */ } break; } default: { break; } } } /* {{{ proto ImagickKernel ImagickKernel::fromBuiltin(type, string) Create a kernel from a builtin in kernel. See http://www.imagemagick.org/Usage/morphology/#kernel for examples. Currently the 'rotation' symbols are not supported. Example: $diamondKernel = ImagickKernel::fromBuiltIn(\Imagick::KERNEL_DIAMOND, "2"); */ PHP_METHOD(ImagickKernel, fromBuiltin) { im_long kernel_type; GeometryInfo geometry_info = { 0, //rho, 0, //sigma, 0, //xi, 0, //psi, 0, //chi; }; KernelInfo *kernel_info; char *string; IM_LEN_TYPE string_len; GeometryFlags flags; #if MagickLibVersion >= 0x700 ExceptionInfo *_exception_info = NULL; #endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &kernel_type, &string, &string_len) == FAILURE) { return; } flags = ParseGeometry(string, &geometry_info); imagick_fiddle_with_geometry_info(kernel_type, flags, &geometry_info); #if MagickLibVersion >= 0x700 //TODO - inspect exception info kernel_info = AcquireKernelBuiltIn(kernel_type, &geometry_info, _exception_info); #else kernel_info = AcquireKernelBuiltIn(kernel_type, &geometry_info); #endif createKernelZval(return_value, kernel_info TSRMLS_CC); return; } /* }}} */ /* {{{ proto void ImagickKernel::addKernel(ImagickKernel kernel) Attach another kernel to this kernel to allow them to both be applied in a single morphology or filter function. Returns the new combined kernel. */ PHP_METHOD(ImagickKernel, addKernel) { zval *objvar; KernelInfo *kernel_info_add_clone; KernelInfo *kernel_info; KernelInfo *kernel_info_target; php_imagickkernel_object *kernel; php_imagickkernel_object *internp; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &objvar, php_imagickkernel_sc_entry) == FAILURE) { return; } kernel = Z_IMAGICKKERNEL_P(objvar); internp = Z_IMAGICKKERNEL_P(getThis()); if (kernel->kernel_info == NULL) { zend_throw_exception(php_imagickkernel_exception_class_entry, "ImagickKernel is empty, cannot be used", (long)0 TSRMLS_CC); RETURN_NULL(); } kernel_info = internp->kernel_info; do { kernel_info_target = kernel_info; kernel_info = kernel_info->next; } while (kernel_info != NULL); kernel_info_add_clone = CloneKernelInfo(kernel->kernel_info); kernel_info_target->next = kernel_info_add_clone; return; } /* }}} */ /* {{{ proto ImagickKernel[] ImagickKernel::separate(void) Separates a linked set of kernels and returns an array of ImagickKernels. */ PHP_METHOD(ImagickKernel, separate) { php_imagickkernel_object *internp; KernelInfo *kernel_info; KernelInfo *kernel_info_copy; int number_values; KernelValueType * values_copy; #if PHP_VERSION_ID >= 70000 zval separate_object; #else zval *separate_object; #endif if (zend_parse_parameters_none() == FAILURE) { return; } internp = Z_IMAGICKKERNEL_P(getThis()); IMAGICK_KERNEL_NOT_NULL_EMPTY(internp); kernel_info = internp->kernel_info; array_init(return_value); while (kernel_info != NULL) { number_values = kernel_info->width * kernel_info->height; values_copy = (KernelValueType *)AcquireAlignedMemory(kernel_info->width, kernel_info->height*sizeof(KernelValueType)); memcpy(values_copy, kernel_info->values, number_values * sizeof(KernelValueType)); kernel_info_copy = imagick_createKernel( values_copy, kernel_info->width, kernel_info->height, kernel_info->x, kernel_info->y ); #if PHP_VERSION_ID >= 70000 createKernelZval(&separate_object, kernel_info_copy TSRMLS_CC); add_next_index_zval(return_value, &separate_object); #else MAKE_STD_ZVAL(separate_object); createKernelZval(separate_object, kernel_info_copy TSRMLS_CC); add_next_index_zval(return_value, separate_object); #endif kernel_info = kernel_info->next; } return; } /* }}} */ /* {{{ proto [] ImagickKernel::getMatrix(void) Get the 2d matrix of values used in this kernel. The elements are either float for elements that are used or 'false' if the element should be skipped. */ PHP_METHOD(ImagickKernel, getMatrix) { php_imagickkernel_object *internp; if (zend_parse_parameters_none() == FAILURE) { return; } internp = Z_IMAGICKKERNEL_P(getThis()); IMAGICK_KERNEL_NOT_NULL_EMPTY(internp); array_init(return_value); php_imagickkernelvalues_to_zval(return_value, internp->kernel_info); return; } /* }}} */ /* {{{ proto [] ImagickKernel::scale(float scaling_factor[, int NORMALIZE_KERNEL_FLAG]) ScaleKernelInfo() scales the given kernel list by the given amount, with or without normalization of the sum of the kernel values (as per given flags). The exact behaviour of this function depends on the normalization type being used please see http://www.imagemagick.org/api/morphology.php#ScaleKernelInfo for details. Flag should be one of NORMALIZE_KERNEL_VALUE, NORMALIZE_KERNEL_CORRELATE, NORMALIZE_KERNEL_PERCENT or not set. */ PHP_METHOD(ImagickKernel, scale) { php_imagickkernel_object *internp; double scale; im_long normalize_flag = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|l", &scale, &normalize_flag) == FAILURE) { return; } internp = Z_IMAGICKKERNEL_P(getThis()); IMAGICK_KERNEL_NOT_NULL_EMPTY(internp); ScaleKernelInfo(internp->kernel_info, scale, normalize_flag); return; } /* }}} */ /* {{{ proto [] ImagickKernel::addUnityKernel(float scale) Adds a given amount of the 'Unity' Convolution Kernel to the given pre-scaled and normalized Kernel. This in effect adds that amount of the original image into the resulting convolution kernel. The resulting effect is to convert the defined kernels into blended soft-blurs, unsharp kernels or into sharpening kernels. */ PHP_METHOD(ImagickKernel, addUnityKernel) { php_imagickkernel_object *internp; double scale; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d", &scale) == FAILURE) { return; } internp = Z_IMAGICKKERNEL_P(getThis()); IMAGICK_KERNEL_NOT_NULL_EMPTY(internp); UnityAddKernelInfo(internp->kernel_info, scale); return; } /* }}} */ #endif