1 module des.il.image;
2 
3 import std.exception;
4 
5 import std.algorithm;
6 import std..string;
7 import std.exception;
8 import std.range;
9 import std.traits;
10 import std.conv;
11 
12 import des.math.linear.vector;
13 import des.math.util.accessstring;
14 import des.il.region;
15 import des.il.util;
16 import des.util.testsuite;
17 import des.util.stdext.algorithm;
18 public import des.util.data.type;
19 
20 import std.stdio;
21 
22 ///
23 struct Image
24 {
25     ///
26     CrdVector!0 size;
27     ///
28     ElemInfo info;
29     ///
30     void[] data;
31 
32     invariant()
33     {
34         enforce( isAllCompPositive( size ) );
35         if( data.length > 0 )
36             enforce( data.length == expectedDataLength );
37     }
38 
39 pure:
40 
41     /// copy ctor
42     this(this)
43     {
44         size = CrdVector!0( size );
45         data = data.dup;
46     }
47 
48     /// from other image
49     this( in Image img )
50     {
51         size = CrdVector!0( img.size );
52         info = img.info;
53         data = img.data.dup;
54     }
55 
56     /// from other image
57     immutable this( in Image img )
58     {
59         size = immutable CrdVector!0( img.size );
60         info = img.info;
61         data = img.data.idup;
62     }
63 
64     /// from size, element info and data
65     this(size_t N,T)( in Vector!(N,T) size, ElemInfo info, in void[] data=[] )
66         if( isIntegral!T )
67     in { assert( isAllCompPositive(size) ); } body
68     {
69         this.size = CrdVector!0( size );
70         this.info = info;
71 
72         if( data.length ) this.data = data.dup;
73         else this.data = new void[]( dataSize );
74     }
75 
76     /// from size, channel count, component type and data
77     this(size_t N,T)( in Vector!(N,T) size, size_t ch, DataType type, in void[] data=[] )
78         if( isIntegral!T )
79     in { assert( isAllCompPositive(size) ); } body
80     {
81         this.size = CrdVector!0( size );
82         this.info = ElemInfo( ch, type );
83 
84         if( data.length ) this.data = data.dup;
85         else this.data = new void[]( dataSize );
86     }
87 
88     static
89     {
90         /// use external memory (not copy)
91         auto external(size_t N,T)( in Vector!(N,T) size, ElemInfo info, void[] data )
92             if( isIntegral!T )
93         in { assert( isAllCompPositive(size) ); } body
94         {
95             Image ret;
96             ret.size = size;
97             ret.info = info;
98             ret.data = data;
99             return ret;
100         }
101 
102         /// ditto
103         auto external(size_t N,T)( in Vector!(N,T) size, size_t ch, DataType type, void[] data )
104             if( isIntegral!T )
105         in { assert( isAllCompPositive(size) ); } body
106         { return external( size, ElemInfo(ch,type), data ); }
107     }
108 
109     pure const @safe nothrow @property @nogc
110     {
111         /// image data size
112         size_t dataSize() { return pixelCount * info.bpe; }
113 
114         ///
115         size_t pixelCount()
116         {
117             size_t sz = 1;
118             foreach( v; size.data ) sz *= v;
119             return sz;
120         }
121 
122         size_t dims() { return size.data.length; }
123     }
124 
125     /// fill data zeros
126     void clear()
127     {
128         if( data ) fill( cast(ubyte[])data, ubyte(0) );
129         else data = new void[]( expectedDataLength );
130     }
131 
132     const @property
133     {
134         /// get copy of image
135         auto dup() { return Image( this ); }
136 
137         /// get immutable copy of image
138         auto idup() { return immutable(Image)( this ); }
139     }
140 
141     CrdVector!0 robSize( size_t K ) const
142     { return CrdVector!(0).fillOne( K, size, 1 ); }
143 
144     ///
145     immutable(void[]) dump() const
146     {
147         return (cast(void[])([size.length]) ~
148                 cast(void[])(size.data) ~
149                 cast(void[])([info]) ~ data).idup;
150     }
151 
152     ///
153     static auto load( immutable(void[]) rawdata )
154     {
155         immutable(void)[] readVals(T)( T* ptr, size_t cnt, immutable(void[]) arr )
156         {
157             auto data = cast(immutable(T[]))arr[0..T.sizeof*cnt];
158             foreach( i, val; data ) *ptr++ = val;
159             return arr[T.sizeof*cnt..$];
160         }
161 
162         size_t dims;
163         auto buf = readVals!size_t( &dims, 1, rawdata );
164 
165         auto szdata = new coord_t[](dims);
166         buf = readVals!coord_t( szdata.ptr, dims, buf );
167 
168         auto size = CrdVector!0( szdata );
169 
170         ElemInfo info;
171 
172         buf = readVals!ElemInfo( &info, 1, buf );
173 
174         return Image( size, info, buf );
175     }
176 
177     /// access to pixel
178     ref T pixel(T,C)( in C[] crd... )
179         if( isIntegral!C )
180     in
181     {
182         assert( isAllCompPositive(crd), "negative coordinate" );
183         assert( all!"a[0]>a[1]"( zip( size.dup, crd.dup ) ), "range violation" );
184         assert( crd.length == size.length );
185     }
186     body
187     {
188         checkDataType!T;
189         return (cast(T[])data)[index(crd)];
190     }
191 
192     /// ditto
193     ref const(T) pixel(T,C)( in C[] crd... ) const
194         if( isIntegral!C )
195     in
196     {
197         assert( isAllCompPositive(crd), "negative coordinate" );
198         assert( crd.length == size.length );
199         assert( all!"a[0]>a[1]"( zip( size.data, crd ) ), "range violation" );
200     }
201     body
202     {
203         checkDataType!T;
204         return (cast(const(T)[])data)[index(crd)];
205     }
206 
207     /// ditto
208     ref T pixel(T,C,size_t N)( in Vector!(N,C) crd )
209         if( isIntegral!C )
210     in
211     {
212         assert( isAllCompPositive(crd), "negative coordinate" );
213         assert( crd.length == size.length );
214         assert( all!"a[0]>a[1]"( zip( size.data, crd.data.dup ) ), "range violation" );
215     }
216     body
217     {
218         checkDataType!T;
219         return (cast(T[])data)[index(crd)];
220     }
221 
222     /// ditto
223     ref const(T) pixel(T,C,size_t N)( in Vector!(N,C) crd ) const
224         if( isIntegral!C )
225     in
226     {
227         assert( isAllCompPositive(crd), "negative coordinate" );
228         assert( crd.length == size.length );
229         assert( all!"a[0]>a[1]"( zip( size.data, crd.data.dup ) ), "range violation" );
230     }
231     body
232     {
233         checkDataType!T;
234         return (cast(const(T)[])data)[index(crd)];
235     }
236 
237     /// cast data to `T[]`
238     @property T[] mapAs(T)()
239     {
240         checkDataType!T;
241         return cast(T[])data;
242     }
243 
244     /// ditto
245     @property const(T)[] mapAs(T)() const
246     {
247         checkDataType!T;
248         return cast(const(T)[])data;
249     }
250 
251     /// return line index by coordinate
252     size_t index(T)( in T[] crd ) const
253         if( isIntegral!T )
254     in { assert( isAllCompPositive(crd) ); } body
255     { return getIndex( size, crd ); }
256 
257 private:
258 
259     void checkDataType(T)() const
260     {
261         enforce( T.sizeof == info.bpe,
262                 new ImageException( "access with wrong type" ) );
263     }
264 
265     size_t expectedDataLength() const @property
266     {
267         size_t sz = 1;
268         foreach( v; size.data ) sz *= v;
269         return sz * info.bpe;
270     }
271 }
272 
273 ///
274 unittest
275 {
276     import std.stdio;
277     auto a = Image( ivec2(3,3), 3, DataType.UBYTE );
278     assert( a.data.length != 0 );
279     assert( eq( a.size, [3,3] ) );
280     a.pixel!bvec3(0,0) = bvec3(1,2,3);
281     auto b = a;
282     assert( b == a );
283     assert( eq( b.pixel!bvec3(0,0), bvec3(1,2,3) ) );
284     a.pixel!bvec3(1,1) = bvec3(3,4,5);
285     assert( b != a );
286     assert( b == Image.load( b.dump() ) );
287     assert( b == b.dup );
288     assert( b.data == b.idup.data.dup );
289     a = b;
290     assert( b == a );
291     assert( a == Image.load( b.dump() ) );
292     assert( a == b.dup );
293     assert( a.data == b.idup.data.dup );
294     auto crd = ivec2(1,2);
295     b.pixel!bvec3(crd) = bvec3(5,6,8);
296     assert( eq( b.pixel!bvec3(1,2), bvec3(5,6,8) ) );
297 
298     b.clear();
299     assert( eq( b.pixel!bvec3(1,2), bvec3(0,0,0) ) );
300 }
301 
302 ///
303 unittest
304 {
305     auto a = Image( ivec2(3,3), ElemInfo( 1, DataType.UBYTE ), to!(ubyte[])([ 1,2,3,4,5,6,7,8,9 ]) );
306     auto b = Image(a);
307 
308     assert( a.pixel!ubyte(0,0) == 1 );
309     assert( b.pixel!ubyte(0,0) == 1 );
310     a.pixel!ubyte(0,0) = 2;
311     assert( a.pixel!ubyte(0,0) == 2 );
312     assert( b.pixel!ubyte(0,0) == 1 );
313 
314     auto c = immutable Image(a);
315     assert( c.pixel!ubyte(0,0) == 2 );
316 }
317 
318 ///
319 unittest
320 {
321     auto a = Image( ivec!1(3), 1, DataType.UBYTE, to!(ubyte[])([ 1,2,3 ]) );
322     assert(  mustExcept!Throwable({ a.pixel!(ubyte)(7) = 0; }) );
323     assert( !mustExcept({ a.pixel!(ubyte)(0) = 0; }) );
324 
325     assert( a.pixel!ubyte(0) == 0 );
326 
327     auto b = Image(a);
328     b.size.length = 2;
329     b.size[1] = 1;
330 
331     assert( b.size[0] == 3 );
332     assert( b.size[1] == 1 );
333 
334     assert( b.pixel!ubyte(0,0) == 0 );
335     assert( b.pixel!ubyte(1,0) == 2 );
336     assert( mustExcept!Throwable({ b.pixel!ubyte(1,1) = 2; }) );
337 
338     auto c = Image(a);
339     c.size.length = 2;
340     c.size = ivec2(1,3);
341 
342     assert( c.size[0] == 1 );
343     assert( c.size[1] == 3 );
344 
345     assert( c.pixel!ubyte(0,0) == 0 );
346     assert( c.pixel!ubyte(0,1) == 2 );
347     assert( mustExcept!Throwable({ c.pixel!ubyte(1,1) = 2; }) );
348 
349     c.size = ivec2(2,2);
350 
351     assert( c.size[0] == 2 );
352     assert( c.size[1] == 2 );
353 }
354 
355 ///
356 unittest
357 {
358     auto a = Image( ivec2(3,3), 2, DataType.FLOAT );
359 
360     assert( a.index([1,2]) == 7 );
361     assert( a.index(ivec2(1,2)) == 7 );
362 
363     a.mapAs!(vec2)[a.index([1,2])] = vec2(1,1);
364     assert( a.pixel!vec2(1,2) == vec2(1,1) );
365 
366     a.pixel!vec2(1,2) = vec2(2,2);
367     assert( a.pixel!vec2(1,2) == vec2(2,2) );
368 }
369 
370 unittest
371 {
372     vec2 sum( in Image img ) pure
373     {
374         auto buf = img.mapAs!vec2;
375         return reduce!((a,b)=>(a+=b))(vec2(0,0),buf);
376     }
377 
378     auto a = Image( ivec2(3,3), ElemInfo( 2, DataType.FLOAT ) );
379 
380     a.pixel!vec2(0,0) = vec2(1,2);
381     a.pixel!vec2(1,2) = vec2(2,2);
382 
383     assert( sum(a) == vec2(3,4) );
384 }
385 
386 /// use external memory
387 unittest
388 {
389     float[] data = [ 1.0, 2, 3, 4 ];
390     auto img = Image.external( ivec2(2,2), 1, DataType.FLOAT, data );
391 
392     img.pixel!float(0,0) = 8.0f;
393 
394     assert( data[0] == 8.0f );
395 }
396 
397 ///
398 unittest
399 {
400     Image img;
401     assert( img.size.length == 0 );
402     assert( img.data.length == 0 );
403 
404     img.size = ivec2(3,3);
405     img.info = ElemInfo( 3, DataType.NORM_FIXED );
406     img.clear();
407 
408     assert( img.data.length == 27 * float.sizeof );
409     assert( img.info.bpe == 3 * float.sizeof );
410 
411     img.pixel!vec3(0,1) = vec3( .2,.1,.3 );
412     assert( img.pixel!vec3(0,1) == vec3(.2,.1,.3) );
413 
414     auto di = Image.load( img.dump() );
415     assert( di.size == img.size );
416     assert( di.info == img.info );
417     assert( di.data == img.data );
418 
419     auto ii = immutable(Image)( img );
420     assert( ii.size == img.size );
421     assert( ii.info == img.info );
422     assert( ii.data == img.data );
423 
424     assert( ii.pixel!vec3(0,1) == vec3(.2,.1,.3) );
425 
426     auto dii = immutable(Image).load( ii.dump() );
427     static assert( is( typeof(dii) == Image ) );
428     assert( dii.size == img.size );
429     assert( dii.info == img.info );
430     assert( dii.data == img.data );
431 
432     auto dd = ii.dup;
433     static assert( is( typeof(dd) == Image ) );
434     assert( dd.size == img.size );
435     assert( dd.info == img.info );
436     assert( dd.data == img.data );
437 
438     auto ddi = ii.idup;
439     static assert( is( typeof(ddi) == immutable(Image) ) );
440     assert( ddi.size == img.size );
441     assert( ddi.info == img.info );
442     assert( ddi.data == img.data );
443 }
444 
445 ///
446 unittest
447 {
448     auto data =
449     [
450         vec2( 1, 2 ), vec2( 3, 4 ), vec2( 5, 6 ),
451         vec2( 7, 8 ), vec2( 9, 1 ), vec2( 1, 2 ),
452         vec2( 2, 3 ), vec2( 4, 5 ), vec2( 6, 7 )
453     ];
454 
455     auto img = Image( ivec2(3,3), 2, DataType.FLOAT, data );
456 
457     assert( img.size == ivec2(3,3) );
458     assert( img.info.bpe == 2 * float.sizeof );
459 
460     assert( img.pixel!vec2(1,1) == vec2(9,1) );
461     assert( img.info.type == DataType.FLOAT );
462 
463     auto imdata = img.mapAs!vec2;
464     assert( data == imdata );
465 
466     img.clear();
467     assert( img.pixel!vec2(1,1) == vec2(0,0) );
468 
469     img.mapAs!(vec2)[] = data[];
470     imdata = img.mapAs!vec2;
471     assert( data == imdata );
472 
473     auto constdata = img.idup.mapAs!vec2;
474     assertEq( constdata, imdata );
475     assert( is( typeof(constdata) == const(vec2)[] ) );
476 }
477 
478 ///
479 unittest
480 {
481     assert( mustExcept({ Image( ivec2(3,3), ElemInfo(3,DataType.UBYTE), [ 1, 2, 3 ] ); }) );
482 
483     auto dt = [ vec2(1,0), vec2(0,1) ];
484     assert( mustExcept({ Image( ivec2(3,3), 2, DataType.FLOAT, dt ); }) );
485 
486     auto img = Image( ivec2(3,3), ElemInfo( 3, DataType.NORM_FIXED ) );
487     assert( mustExcept({ auto d = img.mapAs!vec2; }) );
488 
489     assert( !mustExcept({ img.pixel!vec3(1,0) = vec3(1,1,1); }) );
490     assert(  mustExcept({ img.pixel!vec2(1,0) = vec2(1,1); }) );
491     static assert(  !__traits(compiles, { img.pixel!vec3(4,4) = vec3(1,1); }) );
492 }