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 }