1 module dlibwebp.spec;
2 
3 import dlibwebp.api;
4 import dlib.image;
5 import std.exception;
6 import exceptions;
7 
8 /**
9  * Run the tests like this:
10  * dub test --debug=featureTest
11  *
12  */
13 debug (featureTest) {
14     import feature_test;
15     import std.math;
16 
17     private SuperImage createImageWithColour(PixelFormat format)(int w, int h, Color4f c) {
18         SuperImage img = new Image!(format)(w, h);
19         foreach(int x; 0..img.width) {
20             foreach(int y; 0..img.height) {
21                 img[x, y] = c;
22             }
23         }
24         return img;
25     }
26 
27     private void colorTestLossless(PixelFormat format)(in string fn, Color4f c) {
28         {
29             SuperImage redNonTransparent = createImageWithColour!(format)(500, 400, c);
30             redNonTransparent.saveWEBP(fn, WEBPQuality.LOSSLESS);
31         }
32         SuperImage result = loadWEBP(fn);
33         foreach(int x; 0..result.width) {
34             foreach(int y; 0..result.height) {
35                 // WebP supports maximum 8-bit per channel.
36                 Color4 expected = c.convert(8);
37                 Color4 actual = result[x, y].convert(8);
38                 expected.r.shouldEqual(actual.r);
39                 expected.g.shouldEqual(actual.g);
40                 expected.b.shouldEqual(actual.b);
41                 expected.a.shouldEqual(actual.a);
42             }
43         }
44     }
45 
46     private void assertTheSame8bitWithAlpha(SuperImage source, SuperImage result) {
47         foreach(int x; 0..result.width) {
48             foreach(int y; 0..result.height) {
49                 Color4 expected = source[x, y].convert(8);
50                 Color4 actual = result[x, y].convert(8);
51                 expected.r.shouldEqual(actual.r);
52                 expected.g.shouldEqual(actual.g);
53                 expected.b.shouldEqual(actual.b);
54                 expected.a.shouldEqual(actual.a);
55             }
56         }
57     }
58 
59     unittest {
60 
61         import dlibwebp.random;
62 
63         feature("Filesystem errors.", (f) {
64             f.scenario("Invalid filename lossless.", {
65                 auto invalidFileName = "tttest";
66                 for (int i = 0; i < 10000; i++) {
67                     invalidFileName ~= "_";
68                 }
69                 invalidFileName ~= "test:*?.webp";
70 
71                 // Invalid for the most of file systems.
72                 // https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
73 
74                 bool thrownIoException = false;
75                 try {
76                     colorTestLossless!(PixelFormat.RGBA8)(
77                         invalidFileName,
78                         Color4f(1f, 0f, 0f, 1f)
79                     );
80                 } catch (IOException e) {
81                     thrownIoException = true;
82                 }
83                 thrownIoException.shouldBeTrue();
84             });
85         });
86 
87         feature("Filesystem i/o RGBA8. Lossless.", (f) {
88             f.scenario("Red 1.0", {
89                 colorTestLossless!(PixelFormat.RGBA8)(
90                     "ll_RGBA8_red.webp",
91                     Color4f(1f, 0f, 0f, 1f)
92                 );
93             });
94             f.scenario("Red 0.5", {
95                 colorTestLossless!(PixelFormat.RGBA8)(
96                     "ll_RGBA8_red_0_5.webp",
97                     Color4f(0.5f, 0f, 0f, 1f)
98                 );
99             });
100             f.scenario("Red 0.01", {
101                 colorTestLossless!(PixelFormat.RGBA8)(
102                     "ll_RGBA8_red_0_01.webp",
103                     Color4f(0.01f, 0f, 0f, 1f)
104                 );
105             });
106             f.scenario("Green 1.0", {
107                 colorTestLossless!(PixelFormat.RGBA8)(
108                     "ll_RGBA8_green.webp",
109                     Color4f(0f, 1f, 0f, 1f)
110                 );
111             });
112             f.scenario("Blue 1.0", {
113                 colorTestLossless!(PixelFormat.RGBA8)(
114                     "ll_RGBA8_blue.webp",
115                     Color4f(0f, 0f, 1f, 1f)
116                 );
117             });
118             f.scenario("Blue 1.0. Opacity 0.8.", {
119                 colorTestLossless!(PixelFormat.RGBA8)(
120                     "ll_RGBA8_blue_alpha_0_8.webp",
121                     Color4f(0f, 0f, 1f, 0.8f)
122                 );
123             });
124             f.scenario("Random.", {
125                 const fn = "ll_RGBA8_random.webp";
126                 const fnDefault = "ll_RGBA8_random_default.webp";
127 
128                 SuperImage img = RandomImages.circles!(PixelFormat.RGBA8)(500, 400);
129                 // Alpha pixel.
130                 img[0, 0] = Color4f(
131                     img[0, 0].r,
132                     img[0, 0].g,
133                     img[0, 0].b,
134                     0.8f);
135                 img.saveWEBP(fn, WEBPQuality.LOSSLESS);
136 
137                 SuperImage result = loadWEBP(fn);
138                 result.pixelFormat.shouldEqual(PixelFormat.RGBA8);
139                 result.width.shouldEqual(img.width);
140                 result.height.shouldEqual(img.height);
141 
142                 abs(result[0, 0].a - 0.8f).shouldBeLessThan(0.02f); // Alpha pixel!
143                 abs(result[1, 0].a - 1.0f).shouldBeLessThan(0.01f);
144                 assertTheSame8bitWithAlpha(img, result);
145 
146                 img.saveWEBP(fnDefault);
147                 SuperImage resultDefault = loadWEBP(fnDefault);
148                 resultDefault.width.shouldEqual(img.width);
149                 resultDefault.height.shouldEqual(img.height);
150             });
151         });
152 
153 
154         feature("Filesystem i/o RGBA16. Lossless.", (f) {
155             f.scenario("Red 1.0", {
156                 colorTestLossless!(PixelFormat.RGBA16)(
157                     "ll_RGBA16_red.webp",
158                     Color4f(1f, 0f, 0f, 1f)
159                 );
160             });
161             f.scenario("Red 0.5", {
162                 colorTestLossless!(PixelFormat.RGBA16)(
163                     "ll_RGBA16_red_0_5.webp",
164                     Color4f(0.5f, 0f, 0f, 1f)
165                 );
166             });
167             f.scenario("Red 0.01", {
168                 colorTestLossless!(PixelFormat.RGBA16)(
169                     "ll_RGBA16_red_0_01.webp",
170                     Color4f(0.01f, 0f, 0f, 1f)
171                 );
172             });
173             f.scenario("Green 1.0", {
174                 colorTestLossless!(PixelFormat.RGBA16)(
175                     "ll_RGBA16_green.webp",
176                     Color4f(0f, 1f, 0f, 1f)
177                 );
178             });
179             f.scenario("Blue 1.0", {
180                 colorTestLossless!(PixelFormat.RGBA16)(
181                     "ll_RGBA16_blue.webp",
182                     Color4f(0f, 0f, 1f, 1f)
183                 );
184             });
185             f.scenario("Blue 1.0. Opacity 0.8.", {
186                 colorTestLossless!(PixelFormat.RGBA16)(
187                     "ll_RGBA16_blue_alpha_0_8.webp",
188                     Color4f(0f, 0f, 1f, 0.8f)
189                 );
190             });
191             f.scenario("Random.", {
192                 const fn = "ll_RGBA16_random.webp";
193 
194                 SuperImage img = RandomImages.circles!(PixelFormat.RGBA16)(500, 400);
195                 // Alpha pixel.
196                 img[0, 0] = Color4f(
197                     img[0, 0].r,
198                     img[0, 0].g,
199                     img[0, 0].b,
200                     0.8f);
201                 img.saveWEBP(fn, WEBPQuality.LOSSLESS);
202 
203                 SuperImage result = loadWEBP(fn);
204                 result.pixelFormat.shouldEqual(PixelFormat.RGBA8);  // Webp does not support 16-bit colors.
205                 result.width.shouldEqual(img.width);
206                 result.height.shouldEqual(img.height);
207 
208                 abs(result[0, 0].a - 0.8f).shouldBeLessThan(0.02f); // Alpha pixel!
209                 abs(result[1, 0].a - 1.0f).shouldBeLessThan(0.01f);
210                 assertTheSame8bitWithAlpha(img, result);
211             });
212         });
213 
214         feature("Filesystem i/o RGB-8. Lossless.", (f) {
215             f.scenario("Red 1.0", {
216                 colorTestLossless!(PixelFormat.RGB8)(
217                     "ll_RGB8_red.webp",
218                     Color4f(1f, 0f, 0f, 1f)
219                 );
220             });
221             f.scenario("Red 0.5", {
222                 colorTestLossless!(PixelFormat.RGB8)(
223                     "ll_RGB8_red_0_5.webp",
224                     Color4f(0.5f, 0f, 0f, 1f)
225                 );
226             });
227             f.scenario("Red 0.01", {
228                 colorTestLossless!(PixelFormat.RGB8)(
229                     "ll_RGB8_red_0_01.webp",
230                     Color4f(0.01f, 0f, 0f, 1f)
231                 );
232             });
233             f.scenario("Green 1.0", {
234                 colorTestLossless!(PixelFormat.RGB8)(
235                     "ll_RGB8_green.webp",
236                     Color4f(0f, 1f, 0f, 1f)
237                 );
238             });
239             f.scenario("Blue 1.0", {
240                 colorTestLossless!(PixelFormat.RGB8)(
241                     "ll_RGB8_blue.webp",
242                     Color4f(0f, 0f, 1f, 1f)
243                 );
244             });
245             f.scenario("Random.", {
246                 const fn = "ll_RGB8_random.webp";
247                 SuperImage source = RandomImages.circles!(PixelFormat.RGB8)(500, 400);
248                 source.saveWEBP(fn, WEBPQuality.LOSSLESS);
249                 SuperImage result = loadWEBP(fn);
250                 assertTheSame8bitWithAlpha(source, result);
251             });
252         });
253 
254 
255         feature("Filesystem i/o RGB-16. Lossless.", (f) {
256             f.scenario("Red 1.0", {
257                 colorTestLossless!(PixelFormat.RGB16)(
258                     "ll_RGB16_red.webp",
259                     Color4f(1f, 0f, 0f, 1f)
260                 );
261             });
262             f.scenario("Red 0.5", {
263                 colorTestLossless!(PixelFormat.RGB16)(
264                     "ll_RGB16_red_0_5.webp",
265                     Color4f(0.5f, 0f, 0f, 1f)
266                 );
267             });
268             f.scenario("Red 0.01", {
269                 colorTestLossless!(PixelFormat.RGB16)(
270                     "ll_RGB16_red_0_01.webp",
271                     Color4f(0.01f, 0f, 0f, 1f)
272                 );
273             });
274             f.scenario("Green 1.0", {
275                 colorTestLossless!(PixelFormat.RGB16)(
276                     "ll_RGB16_green.webp",
277                     Color4f(0f, 1f, 0f, 1f)
278                 );
279             });
280             f.scenario("Blue 1.0", {
281                 colorTestLossless!(PixelFormat.RGB16)(
282                     "ll_RGB16_blue.webp",
283                     Color4f(0f, 0f, 1f, 1f)
284                 );
285             });
286             f.scenario("Random.", {
287                 const fn = "ll_RGB16_random.webp";
288                 SuperImage source = RandomImages.circles!(PixelFormat.RGB16)(500, 400);
289                 source.saveWEBP(fn, WEBPQuality.LOSSLESS);
290                 SuperImage result = loadWEBP(fn);
291                 // WebP supports only 8 bits per channel anyway.
292                 // And alpha channel will equal 1, just like in the source image.
293                 assertTheSame8bitWithAlpha(source, result);
294             });
295         });
296     }
297 }