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 }