diff --git a/PIL/Image.py b/PIL/Image.py index 480410eff..5e1416a33 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1910,6 +1910,16 @@ class Image: im = self.im.transpose(method) return self._new(im) + def effect_spread(self, distance): + """ + Randomly spread pixels in an image. + + :param distance: Distance to spread pixels. + """ + self.load() + im = self.im.effect_spread(distance) + return self._new(im) + # -------------------------------------------------------------------- # Lazy operations @@ -2419,3 +2429,32 @@ def _show(image, **options): def _showxv(image, title=None, **options): from PIL import ImageShow ImageShow.show(image, title, **options) + + +# -------------------------------------------------------------------- +# Effects + +def effect_mandelbrot(size, extent, quality): + """ + Generate a Mandelbrot set covering the given extent. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param extent: The extent to cover, as a 4-tuple: + (x0, y0, x1, y2). + :param quality: Quality. + """ + return Image()._new(core.effect_mandelbrot(size, extent, quality)) + + +def effect_noise(size, sigma): + """ + Generate Gaussian noise centered around 128. + + :param size: The requested size in pixels, as a 2-tuple: + (width, height). + :param sigma: Standard deviation of noise. + """ + return Image()._new(core.effect_noise(size, sigma)) + +# End of file diff --git a/Tests/images/effect_mandelbrot.png b/Tests/images/effect_mandelbrot.png new file mode 100644 index 000000000..d8167e721 Binary files /dev/null and b/Tests/images/effect_mandelbrot.png differ diff --git a/Tests/images/effect_spread.png b/Tests/images/effect_spread.png new file mode 100644 index 000000000..df7cfa474 Binary files /dev/null and b/Tests/images/effect_spread.png differ diff --git a/Tests/test_image.py b/Tests/test_image.py index cd46c9713..46669db4b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, lena from PIL import Image +import sys class TestImage(PillowTestCase): @@ -140,6 +141,60 @@ class TestImage(PillowTestCase): img_colors = sorted(img.getcolors()) self.assertEqual(img_colors, expected_colors) + def test_effect_mandelbrot(self): + # Arrange + size = (512, 512) + extent = (-3, -2.5, 2, 2.5) + quality = 100 + + # Act + im = Image.effect_mandelbrot(size, extent, quality) + + # Assert + self.assertEqual(im.size, (512, 512)) + im2 = Image.open('Tests/images/effect_mandelbrot.png') + self.assert_image_equal(im, im2) + + def test_effect_mandelbrot_bad_arguments(self): + # Arrange + size = (512, 512) + # Get coordinates the wrong way round: + extent = (+3, +2.5, -2, -2.5) + # Quality < 2: + quality = 1 + + # Act/Assert + self.assertRaises( + ValueError, + lambda: Image.effect_mandelbrot(size, extent, quality)) + + @unittest.skipUnless(sys.platform.startswith('win32'), + "Stalls on Travis CI, passes on Windows") + def test_effect_noise(self): + # Arrange + size = (100, 100) + sigma = 128 + + # Act + im = Image.effect_noise(size, sigma) + + # Assert + self.assertEqual(im.size, (100, 100)) + self.assertEqual(im.getpixel((0, 0)), 60) + self.assertEqual(im.getpixel((0, 1)), 28) + + def test_effect_spread(self): + # Arrange + im = lena() + distance = 10 + + # Act + im2 = im.effect_spread(distance) + + # Assert + self.assertEqual(im.size, (128, 128)) + im3 = Image.open('Tests/images/effect_spread.png') + self.assert_image_similar(im2, im3, 80) if __name__ == '__main__': unittest.main() diff --git a/libImaging/Effects.c b/libImaging/Effects.c index db6e72989..eb598d968 100644 --- a/libImaging/Effects.c +++ b/libImaging/Effects.c @@ -48,25 +48,25 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) for (y = 0; y < ysize; y++) { UINT8* buf = im->image8[y]; - for (x = 0; x < xsize; x++) { - x1 = y1 = xi2 = yi2 = 0.0; - cr = x*dr + extent[0]; - ci = y*di + extent[1]; - for (k = 1;; k++) { - y1 = 2*x1*y1 + ci; - x1 = xi2 - yi2 + cr; - xi2 = x1*x1; - yi2 = y1*y1; - if ((xi2 + yi2) > radius) { - buf[x] = k*255/quality; - break; - } - if (k > quality) { - buf[x] = 0; - break; - } - } - } + for (x = 0; x < xsize; x++) { + x1 = y1 = xi2 = yi2 = 0.0; + cr = x*dr + extent[0]; + ci = y*di + extent[1]; + for (k = 1;; k++) { + y1 = 2*x1*y1 + ci; + x1 = xi2 - yi2 + cr; + xi2 = x1*x1; + yi2 = y1*y1; + if ((xi2 + yi2) > radius) { + buf[x] = k*255/quality; + break; + } + if (k > quality) { + buf[x] = 0; + break; + } + } + } } return im; } @@ -74,7 +74,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) Imaging ImagingEffectNoise(int xsize, int ysize, float sigma) { - /* Generate gaussian noise centered around 128 */ + /* Generate Gaussian noise centered around 128 */ Imaging imOut; int x, y; @@ -83,19 +83,19 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) imOut = ImagingNew("L", xsize, ysize); if (!imOut) - return NULL; + return NULL; next = 0.0; nextok = 0; for (y = 0; y < imOut->ysize; y++) { UINT8* out = imOut->image8[y]; - for (x = 0; x < imOut->xsize; x++) { + for (x = 0; x < imOut->xsize; x++) { if (nextok) { this = next; nextok = 0; } else { - /* after numerical recepies */ + /* after numerical recipes */ double v1, v2, radius, factor; do { v1 = rand()*(2.0/32767.0) - 1.0; @@ -113,14 +113,6 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) return imOut; } -Imaging -ImagingEffectPerlinTurbulence(int xsize, int ysize) -{ - /* Perlin turbulence (In progress) */ - - return NULL; -} - Imaging ImagingEffectSpread(Imaging imIn, int distance) { @@ -132,11 +124,11 @@ ImagingEffectSpread(Imaging imIn, int distance) imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize); if (!imOut) - return NULL; + return NULL; -#define SPREAD(type, image)\ +#define SPREAD(type, image)\ for (y = 0; y < imIn->ysize; y++)\ - for (x = 0; x < imIn->xsize; x++) {\ + for (x = 0; x < imIn->xsize; x++) {\ int xx = x + (rand() % distance) - distance/2;\ int yy = y + (rand() % distance) - distance/2;\ if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\ @@ -147,9 +139,9 @@ ImagingEffectSpread(Imaging imIn, int distance) } if (imIn->image8) { - SPREAD(UINT8, image8); + SPREAD(UINT8, image8); } else { - SPREAD(INT32, image32); + SPREAD(INT32, image32); } ImagingCopyInfo(imOut, imIn); @@ -157,217 +149,4 @@ ImagingEffectSpread(Imaging imIn, int distance) return imOut; } -/* -------------------------------------------------------------------- */ -/* Taken from the "C" code in the W3C SVG specification. Translated - to C89 by Fredrik Lundh */ - -#if 0 - -/* Produces results in the range [1, 2**31 - 2]. -Algorithm is: r = (a * r) mod m -where a = 16807 and m = 2**31 - 1 = 2147483647 -See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988 -To test: the algorithm should produce the result 1043618065 -as the 10,000th generated number if the original seed is 1. -*/ -#define RAND_m 2147483647 /* 2**31 - 1 */ -#define RAND_a 16807 /* 7**5; primitive root of m */ -#define RAND_q 127773 /* m / a */ -#define RAND_r 2836 /* m % a */ - -static long -perlin_setup_seed(long lSeed) -{ - if (lSeed <= 0) lSeed = -(lSeed % (RAND_m - 1)) + 1; - if (lSeed > RAND_m - 1) lSeed = RAND_m - 1; - return lSeed; -} - -static long -perlin_random(long lSeed) -{ - long result; - result = RAND_a * (lSeed % RAND_q) - RAND_r * (lSeed / RAND_q); - if (result <= 0) result += RAND_m; - return result; -} - -#define BSize 0x100 -#define BM 0xff -#define PerlinN 0x1000 -#define NP 12 /* 2^PerlinN */ -#define NM 0xfff -static int perlin_uLatticeSelector[BSize + BSize + 2]; -static double perlin_fGradient[4][BSize + BSize + 2][2]; -typedef struct -{ - int nWidth; /* How much to subtract to wrap for stitching. */ - int nHeight; - int nWrapX; /* Minimum value to wrap. */ - int nWrapY; -} StitchInfo; - -static void -perlin_init(long lSeed) -{ - double s; - int i, j, k; - lSeed = perlin_setup_seed(lSeed); - for(k = 0; k < 4; k++) - { - for(i = 0; i < BSize; i++) - { - perlin_uLatticeSelector[i] = i; - for (j = 0; j < 2; j++) - perlin_fGradient[k][i][j] = (double)(((lSeed = perlin_random(lSeed)) % (BSize + BSize)) - BSize) / BSize; - s = (double) (sqrt(perlin_fGradient[k][i][0] * perlin_fGradient[k][i][0] + perlin_fGradient[k][i][1] * perlin_fGradient[k][i][1])); - perlin_fGradient[k][i][0] /= s; - perlin_fGradient[k][i][1] /= s; - } - } - while(--i) - { - k = perlin_uLatticeSelector[i]; - perlin_uLatticeSelector[i] = perlin_uLatticeSelector[j = (lSeed = perlin_random(lSeed)) % BSize]; - perlin_uLatticeSelector[j] = k; - } - for(i = 0; i < BSize + 2; i++) - { - perlin_uLatticeSelector[BSize + i] = perlin_uLatticeSelector[i]; - for(k = 0; k < 4; k++) - for(j = 0; j < 2; j++) - perlin_fGradient[k][BSize + i][j] = perlin_fGradient[k][i][j]; - } -} - -#define s_curve(t) ( t * t * (3. - 2. * t) ) -#define lerp(t, a, b) ( a + t * (b - a) ) -static double -perlin_noise2(int nColorChannel, double vec[2], StitchInfo *pStitchInfo) -{ - int bx0, bx1, by0, by1, b00, b10, b01, b11; - double rx0, rx1, ry0, ry1, *q, sx, sy, a, b, t, u, v; - register int i, j; - - t = vec[0] + (double) PerlinN; - bx0 = (int)t; - bx1 = bx0+1; - rx0 = t - (int)t; - rx1 = rx0 - 1.0f; - t = vec[1] + (double) PerlinN; - by0 = (int)t; - by1 = by0+1; - ry0 = t - (int)t; - ry1 = ry0 - 1.0f; - - /* If stitching, adjust lattice points accordingly. */ - if(pStitchInfo != NULL) - { - if(bx0 >= pStitchInfo->nWrapX) - bx0 -= pStitchInfo->nWidth; - if(bx1 >= pStitchInfo->nWrapX) - bx1 -= pStitchInfo->nWidth; - if(by0 >= pStitchInfo->nWrapY) - by0 -= pStitchInfo->nHeight; - if(by1 >= pStitchInfo->nWrapY) - by1 -= pStitchInfo->nHeight; - } - - bx0 &= BM; - bx1 &= BM; - by0 &= BM; - by1 &= BM; - - i = perlin_uLatticeSelector[bx0]; - j = perlin_uLatticeSelector[bx1]; - b00 = perlin_uLatticeSelector[i + by0]; - b10 = perlin_uLatticeSelector[j + by0]; - b01 = perlin_uLatticeSelector[i + by1]; - b11 = perlin_uLatticeSelector[j + by1]; - sx = (double) (s_curve(rx0)); - sy = (double) (s_curve(ry0)); - q = perlin_fGradient[nColorChannel][b00]; u = rx0 * q[0] + ry0 * q[1]; - q = perlin_fGradient[nColorChannel][b10]; v = rx1 * q[0] + ry0 * q[1]; - a = lerp(sx, u, v); - q = perlin_fGradient[nColorChannel][b01]; u = rx0 * q[0] + ry1 * q[1]; - q = perlin_fGradient[nColorChannel][b11]; v = rx1 * q[0] + ry1 * q[1]; - b = lerp(sx, u, v); - return lerp(sy, a, b); -} - -double -perlin_turbulence( - int nColorChannel, double *point, double fBaseFreqX, double fBaseFreqY, - int nNumOctaves, int bFractalSum, int bDoStitching, - double fTileX, double fTileY, double fTileWidth, double fTileHeight) -{ - StitchInfo stitch; - StitchInfo *pStitchInfo = NULL; /* Not stitching when NULL. */ - - double fSum = 0.0f; - double vec[2]; - double ratio = 1; - - int nOctave; - - vec[0] = point[0] * fBaseFreqX; - vec[1] = point[1] * fBaseFreqY; - - /* Adjust the base frequencies if necessary for stitching. */ - if(bDoStitching) - { - /* When stitching tiled turbulence, the frequencies must be adjusted */ - /* so that the tile borders will be continuous. */ - if(fBaseFreqX != 0.0) - { - double fLoFreq = (double) (floor(fTileWidth * fBaseFreqX)) / fTileWidth; - double fHiFreq = (double) (ceil(fTileWidth * fBaseFreqX)) / fTileWidth; - if(fBaseFreqX / fLoFreq < fHiFreq / fBaseFreqX) - fBaseFreqX = fLoFreq; - else - fBaseFreqX = fHiFreq; - } - - if(fBaseFreqY != 0.0) - { - double fLoFreq = (double) (floor(fTileHeight * fBaseFreqY)) / fTileHeight; - double fHiFreq = (double) (ceil(fTileHeight * fBaseFreqY)) / fTileHeight; - if(fBaseFreqY / fLoFreq < fHiFreq / fBaseFreqY) - fBaseFreqY = fLoFreq; - else - fBaseFreqY = fHiFreq; - } - - /* Set up initial stitch values. */ - pStitchInfo = &stitch; - stitch.nWidth = (int) (fTileWidth * fBaseFreqX + 0.5f); - stitch.nWrapX = (int) (fTileX * fBaseFreqX + PerlinN + stitch.nWidth); - stitch.nHeight = (int) (fTileHeight * fBaseFreqY + 0.5f); - stitch.nWrapY = (int) (fTileY * fBaseFreqY + PerlinN + stitch.nHeight); - } - - for(nOctave = 0; nOctave < nNumOctaves; nOctave++) - { - if(bFractalSum) - fSum += (double) (perlin_noise2(nColorChannel, vec, pStitchInfo) / ratio); - else - fSum += (double) (fabs(perlin_noise2(nColorChannel, vec, pStitchInfo)) / ratio); - - vec[0] *= 2; - vec[1] *= 2; - ratio *= 2; - - if(pStitchInfo != NULL) - { - /* Update stitch values. Subtracting PerlinN before the multiplication and */ - /* adding it afterward simplifies to subtracting it once. */ - stitch.nWidth *= 2; - stitch.nWrapX = 2 * stitch.nWrapX - PerlinN; - stitch.nHeight *= 2; - stitch.nWrapY = 2 * stitch.nWrapY - PerlinN; - } - } - return fSum; -} - -#endif +// End of file