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 }