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 }