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