Implemented the gdScatter filter I wrote almost 10 years ago

This filter is available as of libgd 2.1.0 which is our bare minimum for
external compilation of ext/gd.

The scatter filter works by iterating over all pixels in the image and
shifting them randomly based on two modifier (`plus` and `sub`) values:

dest_x = (int)(x + ((rand() % (plus - sub)) + sub));
dest_y = (int)(y + ((rand() % (plus - sub)) + sub));

Additionally the scatter filter also supports by only shifting pixels where
the current pixel being iterated is one or more colors, allowing the scatter
filter to only effect solid colors in part of an image.

Note, due to the nature of randomness and implementation, pixels who were
shifted ahead of iteration will be shifted once more and therefore the
bottom right of an image may contain a slight scatter effect due to this.
This commit is contained in:
Kalle Sommer Nielsen 2019-05-10 02:23:40 +03:00
parent ec518aef10
commit bcd11a1dfb
4 changed files with 175 additions and 2 deletions

View File

@ -112,7 +112,8 @@ int overflow2(int a, int b);
#define IMAGE_FILTER_MEAN_REMOVAL 9
#define IMAGE_FILTER_SMOOTH 10
#define IMAGE_FILTER_PIXELATE 11
#define IMAGE_FILTER_MAX 11
#define IMAGE_FILTER_SCATTER 12
#define IMAGE_FILTER_MAX 12
#define IMAGE_FILTER_MAX_ARGS 6
static void php_image_filter_negate(INTERNAL_FUNCTION_PARAMETERS);
static void php_image_filter_grayscale(INTERNAL_FUNCTION_PARAMETERS);
@ -126,6 +127,7 @@ static void php_image_filter_selective_blur(INTERNAL_FUNCTION_PARAMETERS);
static void php_image_filter_mean_removal(INTERNAL_FUNCTION_PARAMETERS);
static void php_image_filter_smooth(INTERNAL_FUNCTION_PARAMETERS);
static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS);
static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS);
/* End Section filters declarations */
static gdImagePtr _php_image_create_from_string (zval *Data, char *tn, gdImagePtr (*ioctx_func_p)());
@ -1191,6 +1193,7 @@ PHP_MINIT_FUNCTION(gd)
REGISTER_LONG_CONSTANT("IMG_FILTER_MEAN_REMOVAL", IMAGE_FILTER_MEAN_REMOVAL, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMG_FILTER_SMOOTH", IMAGE_FILTER_SMOOTH, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMG_FILTER_PIXELATE", IMAGE_FILTER_PIXELATE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("IMG_FILTER_SCATTER", IMAGE_FILTER_SCATTER, CONST_CS | CONST_PERSISTENT);
/* End Section Filters */
#ifdef GD_VERSION_STRING
@ -4455,6 +4458,50 @@ static void php_image_filter_pixelate(INTERNAL_FUNCTION_PARAMETERS)
RETURN_FALSE;
}
static void php_image_filter_scatter(INTERNAL_FUNCTION_PARAMETERS)
{
zval *IM;
zval *hash_colors = NULL;
gdImagePtr im;
zend_long tmp;
zend_long scatter_sub, scatter_plus;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "rlll|a", &IM, &tmp, &scatter_sub, &scatter_plus, &hash_colors) == FAILURE) {
RETURN_FALSE;
}
if ((im = (gdImagePtr)zend_fetch_resource(Z_RES_P(IM), "Image", le_gd)) == NULL) {
RETURN_FALSE;
}
if (hash_colors) {
uint32_t i = 0;
uint32_t num_colors = zend_hash_num_elements(Z_ARRVAL_P(hash_colors));
zval *color;
int *colors;
if (num_colors == 0) {
RETURN_BOOL(gdImageScatter(im, (int)scatter_sub, (int)scatter_plus));
}
colors = emalloc(num_colors * sizeof(int));
zend_hash_internal_pointer_reset(Z_ARRVAL_P(hash_colors));
while ((color = zend_hash_get_current_data(Z_ARRVAL_P(hash_colors))) != NULL) {
zend_hash_move_forward(Z_ARRVAL_P(hash_colors));
*(colors + i++) = (int) zval_get_long(color);
}
RETVAL_BOOL(gdImageScatterColor(im, (int)scatter_sub, (int)scatter_plus, colors, num_colors));
efree(colors);
} else {
RETURN_BOOL(gdImageScatter(im, (int) scatter_sub, (int) scatter_plus))
}
}
/* {{{ proto bool imagefilter(resource src_im, int filtertype[, int arg1 [, int arg2 [, int arg3 [, int arg4 ]]]] )
Applies Filter an image using a custom angle */
PHP_FUNCTION(imagefilter)
@ -4476,7 +4523,8 @@ PHP_FUNCTION(imagefilter)
php_image_filter_selective_blur,
php_image_filter_mean_removal,
php_image_filter_smooth,
php_image_filter_pixelate
php_image_filter_pixelate,
php_image_filter_scatter
};
if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > IMAGE_FILTER_MAX_ARGS) {

View File

@ -717,6 +717,18 @@ enum gdPixelateMode {
int gdImagePixelate(gdImagePtr im, int block_size, const unsigned int mode);
typedef struct {
int sub;
int plus;
unsigned int num_colors;
int *colors;
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. */
/* Returns nonzero if the image is a truecolor image,

View File

@ -6,12 +6,114 @@
#include "gd_intern.h"
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
#endif
#include <stdlib.h>
#include <time.h>
/* 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)
**/
/* Begin filters function */
#define GET_PIXEL_FUNCTION(src)(src->trueColor?gdImageGetTrueColorPixel:gdImageGetPixel)
#ifdef _WIN32
# define GD_SCATTER_SEED() (unsigned int)(time(0) * GetCurrentProcessId())
#else
# define GD_SCATTER_SEED() (unsigned int)(time(0) * getpid())
#endif
int gdImageScatter(gdImagePtr im, int sub, int plus)
{
gdScatter s;
s.sub = sub;
s.plus = plus;
s.num_colors = 0;
s.seed = GD_SCATTER_SEED();
return gdImageScatterEx(im, &s);
}
int gdImageScatterColor(gdImagePtr im, int sub, int plus, int colors[], unsigned int num_colors)
{
gdScatter s;
s.sub = sub;
s.plus = plus;
s.colors = colors;
s.num_colors = num_colors;
s.seed = GD_SCATTER_SEED();
return gdImageScatterEx(im, &s);
}
int gdImageScatterEx(gdImagePtr im, gdScatterPtr scatter)
{
register int x, y;
int dest_x, dest_y;
int pxl, new_pxl;
unsigned int n;
int sub = scatter->sub, plus = scatter->plus;
if (plus == 0 && sub == 0) {
return 1;
}
else if (sub >= plus) {
return 0;
}
(void)srand(scatter->seed);
if (scatter->num_colors) {
for (y = 0; y < im->sy; y++) {
for (x = 0; x < im->sx; x++) {
dest_x = (int)(x + ((rand() % (plus - sub)) + sub));
dest_y = (int)(y + ((rand() % (plus - sub)) + sub));
if (!gdImageBoundsSafe(im, dest_x, dest_y)) {
continue;
}
pxl = gdImageGetPixel(im, x, y);
new_pxl = gdImageGetPixel(im, dest_x, dest_y);
for (n = 0; n < scatter->num_colors; n++) {
if (pxl == scatter->colors[n]) {
gdImageSetPixel(im, dest_x, dest_y, pxl);
gdImageSetPixel(im, x, y, new_pxl);
}
}
}
}
}
else {
for (y = 0; y < im->sy; y++) {
for (x = 0; x < im->sx; x++) {
dest_x = (int)(x + ((rand() % (plus - sub)) + sub));
dest_y = (int)(y + ((rand() % (plus - sub)) + sub));
if (!gdImageBoundsSafe(im, dest_x, dest_y)) {
continue;
}
pxl = gdImageGetPixel(im, x, y);
new_pxl = gdImageGetPixel(im, dest_x, dest_y);
gdImageSetPixel(im, dest_x, dest_y, pxl);
gdImageSetPixel(im, x, y, new_pxl);
}
}
}
return 1;
}
/* invert src image */
int gdImageNegate(gdImagePtr src)
{

View File

@ -82,6 +82,16 @@ $SOURCE_IMG = $SAVE_DIR . "/test.png";
} else {
echo "IMG_FILTER_PIXELATE failed\n";
}
$im = imagecreatefrompng($SOURCE_IMG);
if (imagefilter($im, IMG_FILTER_SCATTER, 3, 5)) {
imagepng($im, $SAVE_DIR . "/IMG_FILTER_SCATTER.png");
echo "IMG_FILTER_SCATTER success\n";
unlink($SAVE_DIR . "/IMG_FILTER_SCATTER.png");
} else {
echo "IMG_FILTER_SCATTER failed\n";
}
?>
--EXPECT--
IMG_FILTER_NEGATE success
@ -96,3 +106,4 @@ IMG_FILTER_COLORIZE success
IMG_FILTER_CONTRAST success
IMG_FILTER_BRIGHTNESS success
IMG_FILTER_PIXELATE success
IMG_FILTER_SCATTER success