1 /+
2 The MIT License (MIT)
3 
4     Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)>
5 
6     Permission is hereby granted, free of charge, to any person obtaining a copy
7     of this software and associated documentation files (the "Software"), to deal
8     in the Software without restriction, including without limitation the rights
9     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10     copies of the Software, and to permit persons to whom the Software is
11     furnished to do so, subject to the following conditions:
12 
13     The above copyright notice and this permission notice shall be included in
14     all copies or substantial portions of the Software.
15 
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22     THE SOFTWARE.
23 +/
24 
25 module des.il.image;
26 
27 import std.exception;
28 
29 import std.algorithm;
30 import std.string;
31 import std.exception;
32 import std.range;
33 import std.traits;
34 import std.conv;
35 
36 import des.math.linear.vector;
37 import des.math.util.accessstring;
38 import des.il.region;
39 import des.il.util;
40 import des.util.testsuite;
41 import des.util.stdext.algorithm;
42 public import des.util.data.type;
43 
44 /++
45 Params:
46 N - dimensions count
47  +/
48 struct Image(size_t N) if( N > 0 )
49 {
50     /// is compatible image vector
51     template CIV(V) { enum CIV = isCompatibleVector!(N,CoordType,V); }
52 
53     ///
54     static struct Header
55     {
56         ///
57         ElemInfo info;
58         ///
59         SizeVector!N size;
60 
61         invariant() { assert( isAllCompPositive(size) ); }
62 
63         pure const @safe nothrow @property @nogc
64         {
65             /// image data size
66             size_t dataSize() { return pixelCount * info.bpe; }
67 
68             ///
69             size_t pixelCount()
70             {
71                 size_t sz = 1;
72                 foreach( v; size.data ) sz *= v;
73                 return sz;
74             }
75         }
76     }
77 
78     private Header head;
79 
80     ///
81     void[] data;
82 
83     invariant() { assert( data.length == head.dataSize ); }
84 
85 pure:
86     /// copy ctor
87     this(this) { data = data.dup; }
88 
89     /// from other image
90     this( in Image!N img )
91     {
92         head = img.head;
93         data = img.data.dup;
94     }
95 
96     /// from other image
97     immutable this( in Image!N img )
98     {
99         head = img.head;
100         data = img.data.idup;
101     }
102 
103     /// from header and data
104     this( Header hdr, in void[] data=[] )
105     {
106         head = hdr;
107 
108         if( data.length == 0 )
109             this.data = new void[]( head.dataSize );
110         else
111         {
112             enforce( data.length == head.dataSize );
113             this.data = data.dup;
114         }
115     }
116 
117     /// from size, element info and data
118     this(T)( in T[N] sz, in ElemInfo pt, in void[] data=[] )
119         if( isIntegral!T )
120     in { assert( isAllCompPositive(sz) ); } body
121     { this( Header(pt,SizeVector!N(sz)), data ); }
122 
123     /// from size, data type, channel count and data
124     this(T)( in T[N] sz, DataType dt, size_t ch, in void[] data=[] )
125         if( isIntegral!T )
126     in { assert( isAllCompPositive(sz) ); } body
127     { this( Header(ElemInfo(dt,ch),SizeVector!N(sz)), data ); }
128 
129     /// fill data zeros
130     void clear() { fill( cast(ubyte[])data, ubyte(0) ); }
131 
132     static if( N > 1 )
133     {
134         this( in Image!(N-1) img, size_t dim=N-1 )
135         in { assert( dim < N ); } body
136         {
137             SizeVector!N sz;
138             foreach( i; 0 .. N )
139                 if( i == dim ) sz[i] = 1;
140                 else sz[i] = img.size[i-(i>dim)];
141             head.size = sz;
142             head.info = img.info;
143             data = img.data.dup;
144         }
145     }
146 
147     const @property
148     {
149         /// get copy of image
150         auto dup() { return Image!N( this ); }
151 
152         /// get immutable copy of image
153         auto idup() { return immutable(Image!N)( this ); }
154     }
155 
156     ///
157     immutable(void[]) dump() const
158     { return (cast(void[])[head] ~ data).idup; }
159 
160     ///
161     static auto load( immutable(void[]) rawdata )
162     {
163         auto head = (cast(Header[])rawdata[0..Header.sizeof])[0];
164         return Image!N( head, rawdata[Header.sizeof .. $] );
165     }
166 
167     /// access to pixel
168     ref T pixel(T,C,size_t Z)( in C[Z] crd... )
169         if( isIntegral!C && Z == N )
170     in
171     {
172         assert( isAllCompPositive(crd), "negative coordinate" );
173         assert( all!"a[0]>a[1]"( zip( size.dup, crd.dup ) ), "range violation" );
174     }
175     body
176     {
177         checkDataType!T;
178         return (cast(T[])data)[index(crd)];
179     }
180 
181     /// ditto
182     ref const(T) pixel(T,C,size_t Z)( in C[Z] crd... ) const
183         if( isIntegral!C && Z == N )
184     in
185     {
186         assert( isAllCompPositive(crd), "negative coordinate" );
187         assert( all!"a[0]>a[1]"( zip( size.dup, crd.dup ) ), "range violation" );
188     }
189     body
190     {
191         checkDataType!T;
192         return (cast(const(T)[])data)[index(crd)];
193     }
194 
195     /// cast data to `T[]`
196     @property T[] mapAs(T)()
197     {
198         checkDataType!T;
199         return cast(T[])data; 
200     }
201 
202     /// ditto
203     @property const(T)[] mapAs(T)() const
204     {
205         checkDataType!T;
206         return cast(const(T)[])data; 
207     }
208 
209     private
210     {
211         private void checkDataType(T)() const
212         {
213             enforce( T.sizeof == head.info.bpe,
214                     new ImageException( "access with wrong type" ) );
215         }
216     }
217 
218     /// return line index by coordinate
219     size_t index(T,size_t Z)( in T[Z] crd... ) const
220         if( isIntegral!T && Z == N )
221     in { assert( isAllCompPositive(crd) ); } body
222     { return getIndex( size.data, to!(CoordType[N])(crd) ); }
223 
224     @property
225     {
226         /// get size
227         auto size() const { return head.size; }
228 
229         /// set size
230         auto size(V)( in V sz ) if( CIV!V )
231         in { assert( isAllCompPositive(sz) ); } body
232         {
233             auto old_size = head.size;
234             head.size = SizeVector!N( sz );
235             if( old_size != head.size )
236                 data = new void[]( head.dataSize );
237             return sz;
238         }
239 
240         /// get info
241         auto info() const { return head.info; }
242 
243         /// set info
244         auto info( in ElemInfo tp )
245         {
246             head.info = tp;
247             if( data.length != head.dataSize )
248                 data = new void[]( head.dataSize );
249             return tp;
250         }
251 
252         /// get header struct copy
253         auto header() const { return head; }
254     }
255 }
256 
257 ///
258 alias Image!1 Image1;
259 ///
260 alias Image!2 Image2;
261 ///
262 alias Image!3 Image3;
263 
264 ///
265 unittest
266 {
267     auto a = Image2( [3,3], ElemInfo( DataType.UBYTE, 3 ) );
268     assert( a.data.length != 0 );
269     assert( eq( a.size, [3,3] ) );
270     a.pixel!bvec3(0,0) = bvec3(1,2,3);
271     auto b = a;
272     assert( b == a );
273     assert( eq( b.pixel!bvec3(0,0), bvec3(1,2,3) ) );
274     a.pixel!bvec3(1,1) = bvec3(3,4,5);
275     assert( b != a );
276     assert( b == Image2.load( b.dump() ) );
277     assert( b == b.dup );
278     assert( b.data == b.idup.data.dup );
279     a = b;
280     assert( b == a );
281     assert( a == Image2.load( b.dump() ) );
282     assert( a == b.dup );
283     assert( a.data == b.idup.data.dup );
284     auto crd = ivec2(1,2);
285     b.pixel!bvec3(crd) = bvec3(5,6,8);
286     assert( eq( b.pixel!bvec3(1,2), bvec3(5,6,8) ) );
287 
288     b.clear();
289     assert( eq( b.pixel!bvec3(1,2), bvec3(0,0,0) ) );
290 }
291 
292 ///
293 unittest
294 {
295     auto a = Image2( [3,3], ElemInfo( DataType.UBYTE, 1 ), to!(ubyte[])([ 1,2,3,4,5,6,7,8,9 ]) );
296     auto b = Image2(a);
297 
298     assert( a.pixel!ubyte(0,0) == 1 );
299     assert( b.pixel!ubyte(0,0) == 1 );
300     a.pixel!ubyte(0,0) = 2;
301     assert( a.pixel!ubyte(0,0) == 2 );
302     assert( b.pixel!ubyte(0,0) == 1 );
303 
304     auto c = immutable Image2(a);
305     assert( c.pixel!ubyte(0,0) == 2 );
306 }
307 
308 ///
309 unittest
310 {
311     auto a = Image1( [3], DataType.UBYTE, 1, to!(ubyte[])([ 1,2,3 ]) );
312     assert(  mustExcept!Throwable({ a.pixel!(ubyte)(7) = 0; }) );
313     assert( !mustExcept({ a.pixel!(ubyte)(0) = 0; }) );
314 
315     assert( a.pixel!ubyte(0) == 0 );
316 
317     auto b = Image2(a);
318 
319     assert( b.header.size.w == 3 );
320     assert( b.header.size.h == 1 );
321 
322     assert( b.pixel!ubyte(0,0) == 0 );
323     assert( b.pixel!ubyte(1,0) == 2 );
324     assert( mustExcept!Throwable({ b.pixel!ubyte(1,1) = 2; }) );
325 
326     auto c = Image2(a,0);
327 
328     assert( c.header.size.w == 1 );
329     assert( c.header.size.h == 3 );
330 
331     assert( c.pixel!ubyte(0,0) == 0 );
332     assert( c.pixel!ubyte(0,1) == 2 );
333     assert( mustExcept!Throwable({ c.pixel!ubyte(1,1) = 2; }) );
334 
335     c.size = ivec2(2,2);
336 
337     assert( c.size.w == 2 );
338     assert( c.size.h == 2 );
339 }
340 
341 ///
342 unittest
343 {
344     auto a = Image2( [3,3], ElemInfo( DataType.FLOAT, 2 ) );
345 
346     assert( a.index(1,2) == 7 );
347     assert( a.index(ivec2(1,2)) == 7 );
348 
349     a.mapAs!(vec2)[a.index(1,2)] = vec2(1,1);
350     assert( a.pixel!vec2(1,2) == vec2(1,1) );
351 
352     a.pixel!vec2(1,2) = vec2(2,2);
353     assert( a.pixel!vec2(1,2) == vec2(2,2) );
354 }
355 
356 unittest
357 {
358     vec2 sum( in Image2 img ) pure
359     {
360         auto buf = img.mapAs!vec2;
361         return reduce!((a,b)=>(a+=b))(vec2(0,0),buf);
362     }
363 
364     auto a = Image2( [3,3], ElemInfo( DataType.FLOAT, 2 ) );
365 
366     a.pixel!vec2(0,0) = vec2(1,2);
367     a.pixel!vec2(1,2) = vec2(2,2);
368 
369     assert( sum(a) == vec2(3,4) );
370 }
371 
372 ///
373 unittest
374 {
375     Image2 img;
376     assert( img.data.length == 0 );
377     assert( img.data is null );
378     assert( img.size == SizeVector!2(0,0) );
379 
380     img.size = ivec2(3,3);
381     img.info = ElemInfo( DataType.NORM_FIXED, 3 );
382     img.clear();
383 
384     assert( img.data.length == 27 * float.sizeof );
385     assert( img.info.bpe == 3 * float.sizeof );
386 
387     img.pixel!col3(0,1) = col3( .2,.1,.3 );
388     assert( img.pixel!vec3(0,1) == vec3(.2,.1,.3) );
389 
390     auto di = Image2.load( img.dump() );
391     assert( di.size == img.size );
392     assert( di.info == img.info );
393     assert( di.data == img.data );
394 
395     auto ii = immutable(Image2)( img );
396     assert( ii.size == img.size );
397     assert( ii.info == img.info );
398     assert( ii.data == img.data );
399 
400     assert( ii.pixel!vec3(0,1) == vec3(.2,.1,.3) );
401 
402     auto dii = immutable(Image2).load( ii.dump() );
403     static assert( is( typeof(dii) == Image2 ) );
404     assert( dii.size == img.size );
405     assert( dii.info == img.info );
406     assert( dii.data == img.data );
407 
408     auto dd = ii.dup;
409     static assert( is( typeof(dd) == Image2 ) );
410     assert( dd.size == img.size );
411     assert( dd.info == img.info );
412     assert( dd.data == img.data );
413 
414     auto ddi = ii.idup;
415     static assert( is( typeof(ddi) == immutable(Image2) ) );
416     assert( ddi.size == img.size );
417     assert( ddi.info == img.info );
418     assert( ddi.data == img.data );
419 }
420 
421 ///
422 unittest
423 {
424     auto data = 
425     [
426         vec2( 1, 2 ), vec2( 3, 4 ), vec2( 5, 6 ),
427         vec2( 7, 8 ), vec2( 9, 1 ), vec2( 1, 2 ),
428         vec2( 2, 3 ), vec2( 4, 5 ), vec2( 6, 7 )
429     ];
430 
431     auto img = Image2( ivec2(3,3), DataType.FLOAT, 2, data );
432 
433     assert( img.size == ivec2(3,3) );
434     assert( img.info.bpe == 2 * float.sizeof );
435 
436     assert( img.pixel!vec2(1,1) == vec2(9,1) );
437     assert( img.info.comp == DataType.FLOAT );
438 
439     auto imdata = img.mapAs!vec2;
440     assert( data == imdata );
441 
442     img.clear();
443     assert( img.pixel!vec2(1,1) == vec2(0,0) );
444 
445     img.mapAs!(vec2)[] = data[];
446     imdata = img.mapAs!vec2;
447     assert( data == imdata );
448 
449     auto constdata = img.idup.mapAs!vec2;
450     assert( constdata == imdata );
451     assert( is( typeof(constdata) == const(vec2)[] ) );
452 }
453 
454 ///
455 unittest
456 {
457     assert( mustExcept({ Image2( [3,3], ElemInfo( DataType.UBYTE, 3 ), [ 1, 2, 3 ] ); }) );
458 
459     auto dt = [ vec2(1,0), vec2(0,1) ];
460     assert( mustExcept({ Image2( ivec2(3,3), DataType.FLOAT, 2, dt ); }) );
461 
462     auto img = Image2( ivec2(3,3), ElemInfo( DataType.NORM_FIXED, 3 ) );
463     assert( mustExcept({ auto d = img.mapAs!vec2; }) );
464 
465     assert( !mustExcept({ img.pixel!col3(1,0) = col3(1,1,1); }) );
466     assert(  mustExcept({ img.pixel!vec2(1,0) = vec2(1,1); }) );
467     static assert(  !__traits(compiles, { img.pixel!vec3(4,4) = vec3(1,1); }) );
468 }