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