Skip to content

Commit 7777260

Browse files
authored
Merge pull request #8438 from radarhere/filter
2 parents dab9559 + 418ae7c commit 7777260

File tree

2 files changed

+111
-20
lines changed

2 files changed

+111
-20
lines changed

Tests/test_image_filter.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,25 @@
3535
ImageFilter.UnsharpMask(10),
3636
),
3737
)
38-
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
39-
def test_sanity(filter_to_apply: ImageFilter.Filter, mode: str) -> None:
38+
@pytest.mark.parametrize(
39+
"mode", ("L", "I", "I;16", "I;16L", "I;16B", "I;16N", "RGB", "CMYK")
40+
)
41+
def test_sanity(
42+
filter_to_apply: ImageFilter.Filter | type[ImageFilter.Filter], mode: str
43+
) -> None:
4044
im = hopper(mode)
41-
if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter):
45+
if mode[0] != "I" or (
46+
callable(filter_to_apply)
47+
and issubclass(filter_to_apply, ImageFilter.BuiltinFilter)
48+
):
4249
out = im.filter(filter_to_apply)
4350
assert out.mode == im.mode
4451
assert out.size == im.size
4552

4653

47-
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
54+
@pytest.mark.parametrize(
55+
"mode", ("L", "I", "I;16", "I;16L", "I;16B", "I;16N", "RGB", "CMYK")
56+
)
4857
def test_sanity_error(mode: str) -> None:
4958
im = hopper(mode)
5059
with pytest.raises(TypeError):
@@ -145,7 +154,9 @@ def test_kernel_not_enough_coefficients() -> None:
145154
ImageFilter.Kernel((3, 3), (0, 0))
146155

147156

148-
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
157+
@pytest.mark.parametrize(
158+
"mode", ("L", "LA", "I", "I;16", "I;16L", "I;16B", "I;16N", "RGB", "CMYK")
159+
)
149160
def test_consistency_3x3(mode: str) -> None:
150161
with Image.open("Tests/images/hopper.bmp") as source:
151162
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
@@ -161,7 +172,9 @@ def test_consistency_3x3(mode: str) -> None:
161172
assert_image_equal(source.filter(kernel), reference)
162173

163174

164-
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
175+
@pytest.mark.parametrize(
176+
"mode", ("L", "LA", "I", "I;16", "I;16L", "I;16B", "I;16N", "RGB", "CMYK")
177+
)
165178
def test_consistency_5x5(mode: str) -> None:
166179
with Image.open("Tests/images/hopper.bmp") as source:
167180
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:

src/libImaging/Filter.c

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include "Imaging.h"
2828

29+
#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F))
30+
2931
static inline UINT8
3032
clip8(float in) {
3133
if (in <= 0.0) {
@@ -105,6 +107,22 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
105107
return imOut;
106108
}
107109

110+
float
111+
kernel_i16(int size, UINT8 *in0, int x, const float *kernel, int bigendian) {
112+
int i;
113+
float result = 0;
114+
int half_size = (size - 1) / 2;
115+
for (i = 0; i < size; i++) {
116+
int x1 = x + i - half_size;
117+
result += _i2f(
118+
in0[x1 * 2 + (bigendian ? 1 : 0)] +
119+
(in0[x1 * 2 + (bigendian ? 0 : 1)] >> 8)
120+
) *
121+
kernel[i];
122+
}
123+
return result;
124+
}
125+
108126
void
109127
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
110128
#define KERNEL1x3(in0, x, kernel, d) \
@@ -135,21 +153,48 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
135153
out[x] = in0[x];
136154
}
137155
} else {
156+
int bigendian = 0;
157+
if (im->type == IMAGING_TYPE_SPECIAL) {
158+
if (strcmp(im->mode, "I;16B") == 0
159+
#ifdef WORDS_BIGENDIAN
160+
|| strcmp(im->mode, "I;16N") == 0
161+
#endif
162+
) {
163+
bigendian = 1;
164+
}
165+
}
138166
for (y = 1; y < im->ysize - 1; y++) {
139167
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
140168
UINT8 *in0 = (UINT8 *)im->image[y];
141169
UINT8 *in1 = (UINT8 *)im->image[y + 1];
142170
UINT8 *out = (UINT8 *)imOut->image[y];
143171

144172
out[0] = in0[0];
173+
if (im->type == IMAGING_TYPE_SPECIAL) {
174+
out[1] = in0[1];
175+
}
145176
for (x = 1; x < im->xsize - 1; x++) {
146177
float ss = offset;
147-
ss += KERNEL1x3(in1, x, &kernel[0], 1);
148-
ss += KERNEL1x3(in0, x, &kernel[3], 1);
149-
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
150-
out[x] = clip8(ss);
178+
if (im->type == IMAGING_TYPE_SPECIAL) {
179+
ss += kernel_i16(3, in1, x, &kernel[0], bigendian);
180+
ss += kernel_i16(3, in0, x, &kernel[3], bigendian);
181+
ss += kernel_i16(3, in_1, x, &kernel[6], bigendian);
182+
int ss_int = ROUND_UP(ss);
183+
out[x * 2 + (bigendian ? 1 : 0)] = clip8(ss_int % 256);
184+
out[x * 2 + (bigendian ? 0 : 1)] = clip8(ss_int >> 8);
185+
} else {
186+
ss += KERNEL1x3(in1, x, &kernel[0], 1);
187+
ss += KERNEL1x3(in0, x, &kernel[3], 1);
188+
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
189+
out[x] = clip8(ss);
190+
}
191+
}
192+
if (im->type == IMAGING_TYPE_SPECIAL) {
193+
out[x * 2] = in0[x * 2];
194+
out[x * 2 + 1] = in0[x * 2 + 1];
195+
} else {
196+
out[x] = in0[x];
151197
}
152-
out[x] = in0[x];
153198
}
154199
}
155200
} else {
@@ -261,6 +306,16 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
261306
out[x + 1] = in0[x + 1];
262307
}
263308
} else {
309+
int bigendian = 0;
310+
if (im->type == IMAGING_TYPE_SPECIAL) {
311+
if (strcmp(im->mode, "I;16B") == 0
312+
#ifdef WORDS_BIGENDIAN
313+
|| strcmp(im->mode, "I;16N") == 0
314+
#endif
315+
) {
316+
bigendian = 1;
317+
}
318+
}
264319
for (y = 2; y < im->ysize - 2; y++) {
265320
UINT8 *in_2 = (UINT8 *)im->image[y - 2];
266321
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
@@ -271,17 +326,39 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
271326

272327
out[0] = in0[0];
273328
out[1] = in0[1];
329+
if (im->type == IMAGING_TYPE_SPECIAL) {
330+
out[2] = in0[2];
331+
out[3] = in0[3];
332+
}
274333
for (x = 2; x < im->xsize - 2; x++) {
275334
float ss = offset;
276-
ss += KERNEL1x5(in2, x, &kernel[0], 1);
277-
ss += KERNEL1x5(in1, x, &kernel[5], 1);
278-
ss += KERNEL1x5(in0, x, &kernel[10], 1);
279-
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
280-
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
281-
out[x] = clip8(ss);
335+
if (im->type == IMAGING_TYPE_SPECIAL) {
336+
ss += kernel_i16(5, in2, x, &kernel[0], bigendian);
337+
ss += kernel_i16(5, in1, x, &kernel[5], bigendian);
338+
ss += kernel_i16(5, in0, x, &kernel[10], bigendian);
339+
ss += kernel_i16(5, in_1, x, &kernel[15], bigendian);
340+
ss += kernel_i16(5, in_2, x, &kernel[20], bigendian);
341+
int ss_int = ROUND_UP(ss);
342+
out[x * 2 + (bigendian ? 1 : 0)] = clip8(ss_int % 256);
343+
out[x * 2 + (bigendian ? 0 : 1)] = clip8(ss_int >> 8);
344+
} else {
345+
ss += KERNEL1x5(in2, x, &kernel[0], 1);
346+
ss += KERNEL1x5(in1, x, &kernel[5], 1);
347+
ss += KERNEL1x5(in0, x, &kernel[10], 1);
348+
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
349+
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
350+
out[x] = clip8(ss);
351+
}
352+
}
353+
if (im->type == IMAGING_TYPE_SPECIAL) {
354+
out[x * 2 + 0] = in0[x * 2 + 0];
355+
out[x * 2 + 1] = in0[x * 2 + 1];
356+
out[x * 2 + 2] = in0[x * 2 + 2];
357+
out[x * 2 + 3] = in0[x * 2 + 3];
358+
} else {
359+
out[x + 0] = in0[x + 0];
360+
out[x + 1] = in0[x + 1];
282361
}
283-
out[x + 0] = in0[x + 0];
284-
out[x + 1] = in0[x + 1];
285362
}
286363
}
287364
} else {
@@ -383,7 +460,8 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o
383460
Imaging imOut;
384461
ImagingSectionCookie cookie;
385462

386-
if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
463+
if (im->type == IMAGING_TYPE_FLOAT32 ||
464+
(im->type == IMAGING_TYPE_SPECIAL && im->bands != 1)) {
387465
return (Imaging)ImagingError_ModeError();
388466
}
389467

0 commit comments

Comments
 (0)