1 /** 2 Module for image I/O. 3 4 Copyright: Copyright Relja Ljubobratovic 2016. 5 6 Authors: Relja Ljubobratovic 7 8 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 9 */ 10 module dcv.io.image; 11 /* 12 13 14 TODO: write wrappers and use libjpeg, libpng, libtiff, openexr. 15 16 v0.1 norm: 17 Implemented and tested Image class. 18 */ 19 20 import std.exception : enforce; 21 import std.range : array; 22 import std.algorithm : reduce; 23 import std..string : toLower; 24 import std.path : extension; 25 26 import imageformats; 27 28 import mir.ndslice.topology : reshape; 29 30 public import dcv.core.image; 31 32 version (unittest) 33 { 34 import std.algorithm : map; 35 import std.range : iota; 36 import std.random : uniform; 37 import std.array : array; 38 import std.functional : pipe; 39 import std.path : extension; 40 import std.file : dirEntries, SpanMode, remove; 41 42 alias imgen_8 = pipe!(iota, map!(v => cast(ubyte)uniform(0, ubyte.max)), std.array.array); 43 alias imgen_16 = pipe!(iota, map!(v => cast(ushort)uniform(0, ushort.max)), std.array.array); 44 45 auto im_ubyte_8_mono() 46 { 47 return (32 * 32).imgen_8; 48 } 49 50 auto im_ubyte_8_rgb() 51 { 52 return (32 * 32 * 3).imgen_8; 53 } 54 55 auto im_ubyte_8_rgba() 56 { 57 return (32 * 32 * 4).imgen_8; 58 } 59 60 auto im_ubyte_16_mono() 61 { 62 return (32 * 32).imgen_16; 63 } 64 65 auto im_ubyte_16_rgb() 66 { 67 return (32 * 32 * 3).imgen_16; 68 } 69 70 auto im_ubyte_16_rgba() 71 { 72 return (32 * 32 * 4).imgen_16; 73 } 74 } 75 76 /// Image reading parameter package type. 77 struct ReadParams 78 { 79 ImageFormat format = ImageFormat.IF_UNASSIGNED; 80 BitDepth depth = BitDepth.BD_UNASSIGNED; 81 } 82 83 /** 84 Read image from the file system. 85 86 params: 87 path = File system path to the image. 88 params = Reading parameters - desired format and depth of the image that's read. 89 Default parameters include no convertion, but loading image orignal data depth and 90 color format. To load original depth or format, set to _UNASSIGNED (ImageFormat.IF_UNASSIGNED, 91 BitDepth.BD_UNASSIGNED). 92 93 return: 94 Image read from the filesystem. 95 96 throws: 97 Exception and ImageIOException from imageformats library. 98 */ 99 Image imread(in string path, ReadParams params = ReadParams(ImageFormat.IF_UNASSIGNED, BitDepth.BD_UNASSIGNED)) 100 { 101 return imreadImpl_imageformats(path, params); 102 } 103 104 unittest 105 { 106 // should read all images. 107 foreach (f; dirEntries("./tests/", SpanMode.breadth)) 108 { 109 auto ext = f.extension.toLower; 110 if (ext == ".png" || ext == ".bmp" || ext == ".tga") 111 { 112 Image im = imread(f); 113 assert(im); 114 } 115 } 116 } 117 118 unittest 119 { 120 121 } 122 123 /** 124 Write image to the given path on the filesystem. 125 126 params: 127 path = Path where the image will be written. 128 width = Width of the image. 129 height = Height of the image. 130 format = Format of the image. 131 depth = Bit depth of the image. 132 data = Image data in unsigned bytes. 133 134 return: 135 Status of the writing as bool. 136 */ 137 bool imwrite(in string path, size_t width, size_t height, ImageFormat format, BitDepth depth, ubyte[] data) 138 { 139 assert(depth != BitDepth.BD_UNASSIGNED); 140 assert(width > 0 && height > 0); 141 if (depth == BitDepth.BD_8) 142 { 143 write_image(path, cast(long)width, cast(long)height, data, imageFormatChannelCount[format]); 144 } 145 else if (depth == BitDepth.BD_16) 146 { 147 throw new Exception("Writting image format not supported."); 148 } 149 else 150 { 151 throw new Exception("Writting image format not supported."); 152 } 153 return true; 154 } 155 156 /** 157 Convenience wrapper for imwrite with Image. 158 159 params: 160 image = Image to be written; 161 path = Path where the image will be written. 162 163 return: 164 Status of the writing as bool. 165 */ 166 bool imwrite(in Image image, in string path) 167 { 168 return imwrite(path, image.width, image.height, image.format, image.depth, image.data!ubyte); 169 } 170 171 /** 172 Convenience wrapper for imwrite with Slice type. 173 174 Params: 175 slice = Slice of the image data; 176 format = Explicit definition of the image format. 177 path = Path where the image will be written. 178 179 Returns: 180 Status of the writing as bool. 181 */ 182 bool imwrite(SliceKind kind, size_t []packs, T) 183 ( 184 Slice!(kind, packs, T*) slice, 185 ImageFormat format, 186 in string path 187 ) 188 { 189 static assert(packs.length == 1, "Packed slices are not allowed in imwrite."); 190 static assert(packs[0] == 2 || packs[0] == 3, "Slice has to be 2 or 3 dimensional."); 191 192 int err; 193 auto sdata = slice.reshape([slice.elementsCount], err).array; 194 assert(err == 0, "Internal error, cannot reshape the slice."); // should never happen, right? 195 196 static if (is(T == ubyte)) 197 { 198 return imwrite(path, slice.shape[1], slice.shape[0], format, BitDepth.BD_8, sdata); 199 } 200 else static if (is(T == ushort)) 201 { 202 throw new Exception("Writting image format not supported."); 203 } 204 else static if (is(T == float)) 205 { 206 throw new Exception("Writting image format not supported."); 207 } 208 else 209 { 210 throw new Exception("Writting image format not supported."); 211 } 212 } 213 214 unittest 215 { 216 // test 8-bit mono image writing 217 import std.algorithm.comparison : equal; 218 219 auto f = "__test__.png"; 220 auto fs = "__test__slice__.png"; 221 auto d = im_ubyte_8_mono; 222 auto w = 32; 223 auto h = 32; 224 auto imw = new Image(w, h, ImageFormat.IF_MONO, BitDepth.BD_8, d); 225 imwrite(imw, f); 226 imwrite(imw.sliced, ImageFormat.IF_MONO, fs); 227 Image im = imread(f, ReadParams(ImageFormat.IF_MONO, BitDepth.BD_8)); 228 Image ims = imread(fs, ReadParams(ImageFormat.IF_MONO, BitDepth.BD_8)); 229 230 // test read image comparing to the input arguments 231 assert(im.width == w); 232 assert(im.height == h); 233 assert(im.format == ImageFormat.IF_MONO); 234 assert(im.channels == 1); 235 assert(im.depth == BitDepth.BD_8); 236 assert(equal(im.data, d)); 237 238 // test slice written image compared to the Image writen one 239 assert(im.width == ims.width); 240 assert(im.height == ims.height); 241 assert(im.format == ims.format); 242 assert(im.channels == ims.channels); 243 assert(im.depth == ims.depth); 244 assert(equal(im.data, ims.data)); 245 try 246 { 247 remove(f); 248 remove(fs); 249 } 250 catch 251 { 252 } 253 } 254 255 unittest 256 { 257 // test 8-bit rgb image writing 258 import std.algorithm.comparison : equal; 259 260 auto f = "__test__.png"; 261 auto fs = "__test__slice__.png"; 262 auto d = im_ubyte_8_rgb; 263 auto w = 32; 264 auto h = 32; 265 auto imw = new Image(w, h, ImageFormat.IF_RGB, BitDepth.BD_8, d); 266 imwrite(imw, f); 267 imwrite(imw.sliced, ImageFormat.IF_RGB, fs); 268 Image im = imread(f, ReadParams(ImageFormat.IF_RGB, BitDepth.BD_8)); 269 Image ims = imread(fs, ReadParams(ImageFormat.IF_RGB, BitDepth.BD_8)); 270 271 // test read image comparing to the input arguments 272 assert(im.width == w); 273 assert(im.height == h); 274 assert(im.format == ImageFormat.IF_RGB); 275 assert(im.channels == 3); 276 assert(im.depth == BitDepth.BD_8); 277 assert(equal(im.data, d)); 278 279 // test slice written image compared to the Image writen one 280 assert(im.width == ims.width); 281 assert(im.height == ims.height); 282 assert(im.format == ims.format); 283 assert(im.channels == ims.channels); 284 assert(im.depth == ims.depth); 285 assert(equal(im.data, ims.data)); 286 try 287 { 288 remove(f); 289 remove(fs); 290 } 291 catch 292 { 293 } 294 } 295 296 unittest 297 { 298 // test 8-bit rgba image writing 299 import std.algorithm.comparison : equal; 300 301 auto f = "__test__.png"; 302 auto fs = "__test__slice__.png"; 303 auto d = im_ubyte_8_rgba; 304 auto w = 32; 305 auto h = 32; 306 auto imw = new Image(w, h, ImageFormat.IF_RGB_ALPHA, BitDepth.BD_8, d); 307 imwrite(imw, f); 308 imwrite(imw.sliced, ImageFormat.IF_RGB_ALPHA, fs); 309 Image im = imread(f, ReadParams(ImageFormat.IF_RGB_ALPHA, BitDepth.BD_8)); 310 Image ims = imread(fs, ReadParams(ImageFormat.IF_RGB_ALPHA, BitDepth.BD_8)); 311 312 // test read image comparing to the input arguments 313 assert(im.width == w); 314 assert(im.height == h); 315 assert(im.format == ImageFormat.IF_RGB_ALPHA); 316 assert(im.channels == 4); 317 assert(im.depth == BitDepth.BD_8); 318 assert(equal(im.data, d)); 319 320 // test slice written image compared to the Image writen one 321 assert(im.width == ims.width); 322 assert(im.height == ims.height); 323 assert(im.format == ims.format); 324 assert(im.channels == ims.channels); 325 assert(im.depth == ims.depth); 326 assert(equal(im.data, ims.data)); 327 328 try 329 { 330 remove(f); 331 remove(fs); 332 } 333 catch 334 { 335 } 336 } 337 338 private: 339 340 Image imreadImpl_imageformats(in string path, ReadParams params) 341 { 342 enforce(params.depth != BitDepth.BD_32, "Currenly reading of 32-bit image data is not supported"); 343 344 if (params.format == ImageFormat.IF_UNASSIGNED) 345 params.format = ImageFormat.IF_RGB; 346 347 Image im = null; 348 auto ch = imreadImpl_imageformats_adoptFormat(params.format); 349 350 if (params.depth == BitDepth.BD_UNASSIGNED || params.depth == BitDepth.BD_8) 351 { 352 IFImage ifim = read_image(path, ch); 353 im = new Image(cast(size_t)ifim.w, cast(size_t)ifim.h, params.format, BitDepth.BD_8, ifim.pixels); 354 } 355 else if (params.depth == BitDepth.BD_16) 356 { 357 enforce(path.extension.toLower == ".png", "Reading 16-bit image has to be in PNG format."); 358 IFImage16 ifim = read_png16(path, ch); 359 im = new Image(cast(size_t)ifim.w, cast(size_t)ifim.h, params.format, BitDepth.BD_16, cast(ubyte[])ifim.pixels); 360 } 361 else 362 { 363 throw new Exception("Reading image depth not supported."); 364 } 365 366 return im; 367 } 368 369 unittest 370 { 371 // test 8 bit read 372 auto f = "./tests/pngsuite/basi0g08.png"; 373 Image im1 = imreadImpl_imageformats(f, ReadParams(ImageFormat.IF_UNASSIGNED, BitDepth.BD_8)); 374 Image im2 = imreadImpl_imageformats(f, ReadParams(ImageFormat.IF_UNASSIGNED, BitDepth.BD_UNASSIGNED)); 375 assert(im1 && im2); 376 assert(im1.width == im2.width); 377 assert(im1.height == im2.height); 378 assert(im1.channels == im2.channels); 379 assert(im1.channels == 3); 380 assert(im1.depth == im2.depth); 381 assert(im1.depth == BitDepth.BD_8); 382 assert(im1.format == im2.format); 383 } 384 385 unittest 386 { 387 // test read as mono 388 auto f = "./tests/pngsuite/basi0g08.png"; 389 Image im = imreadImpl_imageformats(f, ReadParams(ImageFormat.IF_MONO, BitDepth.BD_8)); 390 assert(im); 391 assert(im.width == 32); 392 assert(im.height == 32); 393 assert(im.channels == 1); 394 assert(im.depth == BitDepth.BD_8); 395 assert(im.format == ImageFormat.IF_MONO); 396 } 397 398 unittest 399 { 400 // test 16 bit read 401 auto f = "./tests/pngsuite/pngtest16rgba.png"; 402 Image im = imreadImpl_imageformats(f, ReadParams(ImageFormat.IF_UNASSIGNED, BitDepth.BD_16)); 403 assert(im); 404 assert(im.width == 32); 405 assert(im.height == 32); 406 assert(im.channels == 3); 407 assert(im.depth == BitDepth.BD_16); 408 assert(im.format == ImageFormat.IF_RGB); 409 } 410 411 unittest 412 { 413 // test if 32-bit read request fails 414 // TODO: support, and remove the test. 415 try 416 { 417 imreadImpl_imageformats("", ReadParams(ImageFormat.IF_UNASSIGNED, BitDepth.BD_32)); 418 assert(0); 419 } 420 catch (Exception e) 421 { 422 // should enter here... 423 } 424 catch 425 { 426 assert(0); 427 } 428 } 429 430 int imreadImpl_imageformats_adoptFormat(ImageFormat format) 431 { 432 int ch = 0; 433 switch (format) 434 { 435 case ImageFormat.IF_RGB: 436 ch = ColFmt.RGB; 437 break; 438 case ImageFormat.IF_RGB_ALPHA: 439 ch = ColFmt.RGBA; 440 break; 441 case ImageFormat.IF_MONO: 442 ch = ColFmt.Y; 443 break; 444 case ImageFormat.IF_MONO_ALPHA: 445 ch = ColFmt.YA; 446 break; 447 default: 448 throw new Exception("Format not supported"); 449 } 450 return ch; 451 } 452 453 unittest 454 { 455 /// Test imageformats color format adoption 456 assert(imreadImpl_imageformats_adoptFormat(ImageFormat.IF_RGB) == ColFmt.RGB); 457 assert(imreadImpl_imageformats_adoptFormat(ImageFormat.IF_RGB_ALPHA) == ColFmt.RGBA); 458 assert(imreadImpl_imageformats_adoptFormat(ImageFormat.IF_MONO) == ColFmt.Y); 459 assert(imreadImpl_imageformats_adoptFormat(ImageFormat.IF_MONO_ALPHA) == ColFmt.YA); 460 try 461 { 462 imreadImpl_imageformats_adoptFormat(ImageFormat.IF_YUV); 463 assert(0); 464 } 465 catch (Exception e) 466 { 467 // should enter here... 468 } 469 catch 470 { 471 assert(0); 472 } 473 }