Merge pull request #1933 from uploadcare/resample-vertical-pass

Resample horizontal + vertical pass
This commit is contained in:
wiredfool 2016-06-08 14:14:41 +01:00
commit 62551a8b49
3 changed files with 262 additions and 89 deletions

View File

@ -39,7 +39,9 @@ def getmode(mode):
for m, (basemode, basetype, bands) in Image._MODEINFO.items():
_modes[m] = ModeDescriptor(m, bands, basemode, basetype)
# extra experimental modes
_modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L")
_modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L")
_modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L")
_modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L")
# mapping modes
_modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L")

View File

@ -1,5 +1,5 @@
from helper import unittest, PillowTestCase, hopper
from PIL import Image, ImageDraw
from PIL import Image, ImageDraw, ImageMode
class TestImagingResampleVulnerability(PillowTestCase):
@ -35,7 +35,7 @@ class TestImagingResampleVulnerability(PillowTestCase):
class TestImagingCoreResampleAccuracy(PillowTestCase):
def make_case(self, size, color):
def make_case(self, mode, size, color):
"""Makes a sample image with two dark and two bright squares.
For example:
e0 e0 1f 1f
@ -43,14 +43,12 @@ class TestImagingCoreResampleAccuracy(PillowTestCase):
1f 1f e0 e0
1f 1f e0 e0
"""
dark = (255 - color, 255 - color, 255 - color, 255 - color)
bright = (color, color, color, color)
case = Image.new('L', size, 255 - color)
rectangle = ImageDraw.Draw(case).rectangle
rectangle((0, 0, size[0] // 2 - 1, size[1] // 2 - 1), color)
rectangle((size[0] // 2, size[1] // 2, size[0], size[1]), color)
i = Image.new('RGBX', size, dark)
rectangle = ImageDraw.Draw(i).rectangle
rectangle((0, 0, size[0] // 2 - 1, size[1] // 2 - 1), bright)
rectangle((size[0] // 2, size[1] // 2, size[0], size[1]), bright)
return i
return Image.merge(mode, [case] * len(mode))
def make_sample(self, data, size):
"""Restores a sample image from given data string which contains
@ -70,17 +68,16 @@ class TestImagingCoreResampleAccuracy(PillowTestCase):
return sample
def check_case(self, case, sample):
for channel in case.split():
s_px = sample.load()
c_px = channel.load()
for y in range(case.size[1]):
for x in range(case.size[0]):
if c_px[x, y] != s_px[x, y]:
message = '\nHave: \n{}\n\nExpected: \n{}'.format(
self.serialize_image(channel),
self.serialize_image(sample),
)
self.assertEqual(s_px[x, y], c_px[x, y], message)
s_px = sample.load()
c_px = case.load()
for y in range(case.size[1]):
for x in range(case.size[0]):
if c_px[x, y] != s_px[x, y]:
message = '\nHave: \n{}\n\nExpected: \n{}'.format(
self.serialize_image(case),
self.serialize_image(sample),
)
self.assertEqual(s_px[x, y], c_px[x, y], message)
def serialize_image(self, image):
s_px = image.load()
@ -93,61 +90,67 @@ class TestImagingCoreResampleAccuracy(PillowTestCase):
)
def test_reduce_bilinear(self):
case = self.make_case((8, 8), 0xe1)
data = ('e1 c9'
'c9 b7')
self.check_case(
case.resize((4, 4), Image.BILINEAR),
self.make_sample(data, (4, 4)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (8, 8), 0xe1)
case = case.resize((4, 4), Image.BILINEAR)
data = ('e1 c9'
'c9 b7')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_reduce_bicubic(self):
case = self.make_case((12, 12), 0xe1)
data = ('e1 e3 d4'
'e3 e5 d6'
'd4 d6 c9')
self.check_case(
case.resize((6, 6), Image.BICUBIC),
self.make_sample(data, (6, 6)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (12, 12), 0xe1)
case = case.resize((6, 6), Image.BICUBIC)
data = ('e1 e3 d4'
'e3 e5 d6'
'd4 d6 c9')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (6, 6)))
def test_reduce_lanczos(self):
case = self.make_case((16, 16), 0xe1)
data = ('e1 e0 e4 d7'
'e0 df e3 d6'
'e4 e3 e7 da'
'd7 d6 d9 ce')
self.check_case(
case.resize((8, 8), Image.LANCZOS),
self.make_sample(data, (8, 8)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (16, 16), 0xe1)
case = case.resize((8, 8), Image.LANCZOS)
data = ('e1 e0 e4 d7'
'e0 df e3 d6'
'e4 e3 e7 da'
'd7 d6 d9 ce')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
def test_enlarge_bilinear(self):
case = self.make_case((2, 2), 0xe1)
data = ('e1 b0'
'b0 98')
self.check_case(
case.resize((4, 4), Image.BILINEAR),
self.make_sample(data, (4, 4)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (2, 2), 0xe1)
case = case.resize((4, 4), Image.BILINEAR)
data = ('e1 b0'
'b0 98')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_enlarge_bicubic(self):
case = self.make_case((4, 4), 0xe1)
data = ('e1 e5 ee b9'
'e5 e9 f3 bc'
'ee f3 fd c1'
'b9 bc c1 a2')
self.check_case(
case.resize((8, 8), Image.BICUBIC),
self.make_sample(data, (8, 8)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (4, 4), 0xe1)
case = case.resize((8, 8), Image.BICUBIC)
data = ('e1 e5 ee b9'
'e5 e9 f3 bc'
'ee f3 fd c1'
'b9 bc c1 a2')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
def test_enlarge_lanczos(self):
case = self.make_case((6, 6), 0xe1)
data = ('e1 e0 db ed f5 b8'
'e0 df da ec f3 b7'
'db db d6 e7 ee b5'
'ed ec e6 fb ff bf'
'f5 f4 ee ff ff c4'
'b8 b7 b4 bf c4 a0')
self.check_case(
case.resize((12, 12), Image.LANCZOS),
self.make_sample(data, (12, 12)))
for mode in ['RGBX', 'RGB', 'La', 'L']:
case = self.make_case(mode, (6, 6), 0xe1)
case = case.resize((12, 12), Image.LANCZOS)
data = ('e1 e0 db ed f5 b8'
'e0 df da ec f3 b7'
'db db d6 e7 ee b5'
'ed ec e6 fb ff bf'
'f5 f4 ee ff ff c4'
'b8 b7 b4 bf c4 a0')
for channel in case.split():
self.check_case(channel, self.make_sample(data, (12, 12)))
class CoreResampleConsistencyTest(PillowTestCase):

View File

@ -91,7 +91,7 @@ ImagingPrecompute(int inSize, int outSize, struct filter *filterp,
/* determine support size (length of resampling filter) */
support = filterp->support * filterscale;
/* maximum number of coofs */
/* maximum number of coeffs */
kmax = (int) ceil(support) * 2 + 1;
// check for overflow
@ -103,11 +103,11 @@ ImagingPrecompute(int inSize, int outSize, struct filter *filterp,
return 0;
/* coefficient buffer */
kk = calloc(outSize * kmax, sizeof(double));
kk = malloc(outSize * kmax * sizeof(double));
if ( ! kk)
return 0;
xbounds = calloc(outSize * 2, sizeof(int));
xbounds = malloc(outSize * 2 * sizeof(int));
if ( ! xbounds) {
free(kk);
return 0;
@ -134,6 +134,10 @@ ImagingPrecompute(int inSize, int outSize, struct filter *filterp,
if (ww != 0.0)
k[x] /= ww;
}
// Remaining values should stay empty if they are used despite of xmax.
for (; x < kmax; x++) {
k[x] = 0;
}
xbounds[xx * 2 + 0] = xmin;
xbounds[xx * 2 + 1] = xmax;
}
@ -160,7 +164,7 @@ ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp)
return (Imaging) ImagingError_MemoryError();
}
kk = calloc(xsize * kmax, sizeof(int));
kk = malloc(xsize * kmax * sizeof(int));
if ( ! kk) {
free(xbounds);
free(prekk);
@ -200,13 +204,13 @@ ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp)
xmin = xbounds[xx * 2 + 0];
xmax = xbounds[xx * 2 + 1];
k = &kk[xx * kmax];
ss0 = ss1 = 1 << (PRECISION_BITS -1);
ss0 = ss3 = 1 << (PRECISION_BITS -1);
for (x = 0; x < xmax; x++) {
ss0 += ((UINT8) imIn->image[yy][(x + xmin)*4 + 0]) * k[x];
ss1 += ((UINT8) imIn->image[yy][(x + xmin)*4 + 3]) * k[x];
ss3 += ((UINT8) imIn->image[yy][(x + xmin)*4 + 3]) * k[x];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 3] = clip8(ss1);
imOut->image[yy][xx*4 + 3] = clip8(ss3);
}
}
} else if (imIn->bands == 3) {
@ -255,6 +259,118 @@ ImagingResampleHorizontal_8bpc(Imaging imIn, int xsize, struct filter *filterp)
}
Imaging
ImagingResampleVertical_8bpc(Imaging imIn, int ysize, struct filter *filterp)
{
ImagingSectionCookie cookie;
Imaging imOut;
int ss0, ss1, ss2, ss3;
int xx, yy, y, kmax, ymin, ymax;
int *xbounds;
int *k, *kk;
double *prekk;
kmax = ImagingPrecompute(imIn->ysize, ysize, filterp, &xbounds, &prekk);
if ( ! kmax) {
return (Imaging) ImagingError_MemoryError();
}
kk = malloc(ysize * kmax * sizeof(int));
if ( ! kk) {
free(xbounds);
free(prekk);
return (Imaging) ImagingError_MemoryError();
}
for (y = 0; y < ysize * kmax; y++) {
kk[y] = (int) (0.5 + prekk[y] * (1 << PRECISION_BITS));
}
free(prekk);
imOut = ImagingNew(imIn->mode, imIn->xsize, ysize);
if ( ! imOut) {
free(kk);
free(xbounds);
return NULL;
}
ImagingSectionEnter(&cookie);
if (imIn->image8) {
for (yy = 0; yy < ysize; yy++) {
k = &kk[yy * kmax];
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
for (xx = 0; xx < imOut->xsize; xx++) {
ss0 = 1 << (PRECISION_BITS -1);
for (y = 0; y < ymax; y++)
ss0 += ((UINT8) imIn->image8[y + ymin][xx]) * k[y];
imOut->image8[yy][xx] = clip8(ss0);
}
}
} else if (imIn->type == IMAGING_TYPE_UINT8) {
if (imIn->bands == 2) {
for (yy = 0; yy < ysize; yy++) {
k = &kk[yy * kmax];
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
for (xx = 0; xx < imOut->xsize; xx++) {
ss0 = ss3 = 1 << (PRECISION_BITS -1);
for (y = 0; y < ymax; y++) {
ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y];
ss3 += ((UINT8) imIn->image[y + ymin][xx*4 + 3]) * k[y];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 3] = clip8(ss3);
}
}
} else if (imIn->bands == 3) {
for (yy = 0; yy < ysize; yy++) {
k = &kk[yy * kmax];
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
for (xx = 0; xx < imOut->xsize; xx++) {
ss0 = ss1 = ss2 = 1 << (PRECISION_BITS -1);
for (y = 0; y < ymax; y++) {
ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y];
ss1 += ((UINT8) imIn->image[y + ymin][xx*4 + 1]) * k[y];
ss2 += ((UINT8) imIn->image[y + ymin][xx*4 + 2]) * k[y];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 1] = clip8(ss1);
imOut->image[yy][xx*4 + 2] = clip8(ss2);
}
}
} else {
for (yy = 0; yy < ysize; yy++) {
k = &kk[yy * kmax];
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
for (xx = 0; xx < imOut->xsize; xx++) {
ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS -1);
for (y = 0; y < ymax; y++) {
ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y];
ss1 += ((UINT8) imIn->image[y + ymin][xx*4 + 1]) * k[y];
ss2 += ((UINT8) imIn->image[y + ymin][xx*4 + 2]) * k[y];
ss3 += ((UINT8) imIn->image[y + ymin][xx*4 + 3]) * k[y];
}
imOut->image[yy][xx*4 + 0] = clip8(ss0);
imOut->image[yy][xx*4 + 1] = clip8(ss1);
imOut->image[yy][xx*4 + 2] = clip8(ss2);
imOut->image[yy][xx*4 + 3] = clip8(ss3);
}
}
}
}
ImagingSectionLeave(&cookie);
free(kk);
free(xbounds);
return imOut;
}
Imaging
ImagingResampleHorizontal_32bpc(Imaging imIn, int xsize, struct filter *filterp)
{
@ -315,13 +431,74 @@ ImagingResampleHorizontal_32bpc(Imaging imIn, int xsize, struct filter *filterp)
}
Imaging
ImagingResampleVertical_32bpc(Imaging imIn, int ysize, struct filter *filterp)
{
ImagingSectionCookie cookie;
Imaging imOut;
double ss;
int xx, yy, y, kmax, ymin, ymax;
int *xbounds;
double *k, *kk;
kmax = ImagingPrecompute(imIn->ysize, ysize, filterp, &xbounds, &kk);
if ( ! kmax) {
return (Imaging) ImagingError_MemoryError();
}
imOut = ImagingNew(imIn->mode, imIn->xsize, ysize);
if ( ! imOut) {
free(kk);
free(xbounds);
return NULL;
}
ImagingSectionEnter(&cookie);
switch(imIn->type) {
case IMAGING_TYPE_INT32:
for (yy = 0; yy < ysize; yy++) {
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
k = &kk[yy * kmax];
for (xx = 0; xx < imOut->xsize; xx++) {
ss = 0.0;
for (y = 0; y < ymax; y++)
ss += IMAGING_PIXEL_I(imIn, xx, y + ymin) * k[y];
IMAGING_PIXEL_I(imOut, xx, yy) = ROUND_UP(ss);
}
}
break;
case IMAGING_TYPE_FLOAT32:
for (yy = 0; yy < ysize; yy++) {
ymin = xbounds[yy * 2 + 0];
ymax = xbounds[yy * 2 + 1];
k = &kk[yy * kmax];
for (xx = 0; xx < imOut->xsize; xx++) {
ss = 0.0;
for (y = 0; y < ymax; y++)
ss += IMAGING_PIXEL_F(imIn, xx, y + ymin) * k[y];
IMAGING_PIXEL_F(imOut, xx, yy) = ss;
}
}
break;
}
ImagingSectionLeave(&cookie);
free(kk);
free(xbounds);
return imOut;
}
Imaging
ImagingResample(Imaging imIn, int xsize, int ysize, int filter)
{
Imaging imTemp1, imTemp2, imTemp3;
Imaging imTemp;
Imaging imOut;
struct filter *filterp;
Imaging (*ResampleHorizontal)(Imaging imIn, int xsize, struct filter *filterp);
Imaging (*ResampleVertical)(Imaging imIn, int xsize, struct filter *filterp);
if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0)
return (Imaging) ImagingError_ModeError();
@ -330,14 +507,17 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter)
return (Imaging) ImagingError_ModeError();
} else if (imIn->image8) {
ResampleHorizontal = ImagingResampleHorizontal_8bpc;
ResampleVertical = ImagingResampleVertical_8bpc;
} else {
switch(imIn->type) {
case IMAGING_TYPE_UINT8:
ResampleHorizontal = ImagingResampleHorizontal_8bpc;
ResampleVertical = ImagingResampleVertical_8bpc;
break;
case IMAGING_TYPE_INT32:
case IMAGING_TYPE_FLOAT32:
ResampleHorizontal = ImagingResampleHorizontal_32bpc;
ResampleVertical = ImagingResampleVertical_32bpc;
break;
default:
return (Imaging) ImagingError_ModeError();
@ -362,25 +542,13 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter)
}
/* two-pass resize, first pass */
imTemp1 = ResampleHorizontal(imIn, xsize, filterp);
if ( ! imTemp1)
return NULL;
/* transpose image once */
imTemp2 = ImagingTransposeToNew(imTemp1);
ImagingDelete(imTemp1);
if ( ! imTemp2)
imTemp = ResampleHorizontal(imIn, xsize, filterp);
if ( ! imTemp)
return NULL;
/* second pass */
imTemp3 = ResampleHorizontal(imTemp2, ysize, filterp);
ImagingDelete(imTemp2);
if ( ! imTemp3)
return NULL;
/* transpose result */
imOut = ImagingTransposeToNew(imTemp3);
ImagingDelete(imTemp3);
imOut = ResampleVertical(imTemp, ysize, filterp);
ImagingDelete(imTemp);
if ( ! imOut)
return NULL;