Support I mode for BuiltinFilter

This commit is contained in:
Andrew Murray 2023-04-22 21:22:01 +10:00
parent 8df8b43edb
commit d0b41da094
4 changed files with 120 additions and 56 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -30,15 +30,16 @@ from .helper import assert_image_equal, hopper
ImageFilter.UnsharpMask(10), ImageFilter.UnsharpMask(10),
), ),
) )
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
def test_sanity(filter_to_apply, mode): def test_sanity(filter_to_apply, mode):
im = hopper(mode) im = hopper(mode)
if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter):
out = im.filter(filter_to_apply) out = im.filter(filter_to_apply)
assert out.mode == im.mode assert out.mode == im.mode
assert out.size == im.size assert out.size == im.size
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
def test_sanity_error(mode): def test_sanity_error(mode):
with pytest.raises(TypeError): with pytest.raises(TypeError):
im = hopper(mode) im = hopper(mode)
@ -130,10 +131,12 @@ def test_kernel_not_enough_coefficients():
ImageFilter.Kernel((3, 3), (0, 0)) ImageFilter.Kernel((3, 3), (0, 0))
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
def test_consistency_3x3(mode): def test_consistency_3x3(mode):
with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss.bmp") as reference: reference_name = "hopper_emboss"
reference_name += "_I.png" if mode == "I" else ".bmp"
with Image.open("Tests/images/" + reference_name) as reference:
kernel = ImageFilter.Kernel( kernel = ImageFilter.Kernel(
(3, 3), (3, 3),
# fmt: off # fmt: off
@ -146,16 +149,20 @@ def test_consistency_3x3(mode):
source = source.split() * 2 source = source.split() * 2
reference = reference.split() * 2 reference = reference.split() * 2
assert_image_equal( if mode == "I":
Image.merge(mode, source[: len(mode)]).filter(kernel), source = source[0].convert(mode)
Image.merge(mode, reference[: len(mode)]), else:
) source = Image.merge(mode, source[: len(mode)])
reference = Image.merge(mode, reference[: len(mode)])
assert_image_equal(source.filter(kernel), reference)
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
def test_consistency_5x5(mode): def test_consistency_5x5(mode):
with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: reference_name = "hopper_emboss_more"
reference_name += "_I.png" if mode == "I" else ".bmp"
with Image.open("Tests/images/" + reference_name) as reference:
kernel = ImageFilter.Kernel( kernel = ImageFilter.Kernel(
(5, 5), (5, 5),
# fmt: off # fmt: off
@ -170,10 +177,12 @@ def test_consistency_5x5(mode):
source = source.split() * 2 source = source.split() * 2
reference = reference.split() * 2 reference = reference.split() * 2
assert_image_equal( if mode == "I":
Image.merge(mode, source[: len(mode)]).filter(kernel), source = source[0].convert(mode)
Image.merge(mode, reference[: len(mode)]), else:
) source = Image.merge(mode, source[: len(mode)])
reference = Image.merge(mode, reference[: len(mode)])
assert_image_equal(source.filter(kernel), reference)
def test_invalid_box_blur_filter(): def test_invalid_box_blur_filter():

View File

@ -37,6 +37,17 @@ clip8(float in) {
return (UINT8)in; return (UINT8)in;
} }
static inline INT32
clip32(float in) {
if (in <= 0.0) {
return 0;
}
if (in >= pow(2, 31) - 1) {
return pow(2, 31) - 1;
}
return (INT32)in;
}
Imaging Imaging
ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) { ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
Imaging imOut; Imaging imOut;
@ -96,8 +107,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
void void
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
#define KERNEL1x3(in0, x, kernel, d) \ #define KERNEL1x3(in0, x, kernel, d) \
(_i2f((UINT8)in0[x - d]) * (kernel)[0] + _i2f((UINT8)in0[x]) * (kernel)[1] + \ (_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \
_i2f((UINT8)in0[x + d]) * (kernel)[2]) _i2f(in0[x + d]) * (kernel)[2])
int x = 0, y = 0; int x = 0, y = 0;
@ -105,6 +116,24 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
if (im->bands == 1) { if (im->bands == 1) {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
if (im->type == IMAGING_TYPE_INT32) {
for (y = 1; y < im->ysize - 1; y++) {
INT32 *in_1 = (INT32 *)im->image[y - 1];
INT32 *in0 = (INT32 *)im->image[y];
INT32 *in1 = (INT32 *)im->image[y + 1];
INT32 *out = (INT32 *)imOut->image[y];
out[0] = in0[0];
for (x = 1; x < im->xsize - 1; x++) {
float ss = offset;
ss += KERNEL1x3(in1, x, &kernel[0], 1);
ss += KERNEL1x3(in0, x, &kernel[3], 1);
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
out[x] = clip32(ss);
}
out[x] = in0[x];
}
} else {
for (y = 1; y < im->ysize - 1; y++) { for (y = 1; y < im->ysize - 1; y++) {
UINT8 *in_1 = (UINT8 *)im->image[y - 1]; UINT8 *in_1 = (UINT8 *)im->image[y - 1];
UINT8 *in0 = (UINT8 *)im->image[y]; UINT8 *in0 = (UINT8 *)im->image[y];
@ -121,6 +150,7 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
} }
out[x] = in0[x]; out[x] = in0[x];
} }
}
} else { } else {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
@ -195,10 +225,10 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
void void
ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
#define KERNEL1x5(in0, x, kernel, d) \ #define KERNEL1x5(in0, x, kernel, d) \
(_i2f((UINT8)in0[x - d - d]) * (kernel)[0] + \ (_i2f(in0[x - d - d]) * (kernel)[0] + \
_i2f((UINT8)in0[x - d]) * (kernel)[1] + _i2f((UINT8)in0[x]) * (kernel)[2] + \ _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \
_i2f((UINT8)in0[x + d]) * (kernel)[3] + \ _i2f(in0[x + d]) * (kernel)[3] + \
_i2f((UINT8)in0[x + d + d]) * (kernel)[4]) _i2f(in0[x + d + d]) * (kernel)[4])
int x = 0, y = 0; int x = 0, y = 0;
@ -207,6 +237,30 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
if (im->bands == 1) { if (im->bands == 1) {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
if (im->type == IMAGING_TYPE_INT32) {
for (y = 2; y < im->ysize - 2; y++) {
INT32 *in_2 = (INT32 *)im->image[y - 2];
INT32 *in_1 = (INT32 *)im->image[y - 1];
INT32 *in0 = (INT32 *)im->image[y];
INT32 *in1 = (INT32 *)im->image[y + 1];
INT32 *in2 = (INT32 *)im->image[y + 2];
INT32 *out = (INT32 *)imOut->image[y];
out[0] = in0[0];
out[1] = in0[1];
for (x = 2; x < im->xsize - 2; x++) {
float ss = offset;
ss += KERNEL1x5(in2, x, &kernel[0], 1);
ss += KERNEL1x5(in1, x, &kernel[5], 1);
ss += KERNEL1x5(in0, x, &kernel[10], 1);
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
out[x] = clip32(ss);
}
out[x + 0] = in0[x + 0];
out[x + 1] = in0[x + 1];
}
} else {
for (y = 2; y < im->ysize - 2; y++) { for (y = 2; y < im->ysize - 2; y++) {
UINT8 *in_2 = (UINT8 *)im->image[y - 2]; UINT8 *in_2 = (UINT8 *)im->image[y - 2];
UINT8 *in_1 = (UINT8 *)im->image[y - 1]; UINT8 *in_1 = (UINT8 *)im->image[y - 1];
@ -229,6 +283,7 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
out[x + 0] = in0[x + 0]; out[x + 0] = in0[x + 0];
out[x + 1] = in0[x + 1]; out[x + 1] = in0[x + 1];
} }
}
} else { } else {
// Add one time for rounding // Add one time for rounding
offset += 0.5; offset += 0.5;
@ -327,7 +382,7 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o
Imaging imOut; Imaging imOut;
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
if (!im || im->type != IMAGING_TYPE_UINT8) { if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
return (Imaging)ImagingError_ModeError(); return (Imaging)ImagingError_ModeError();
} }