1 /* 2 Copyright (c) 2011-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Generic image interface and its implementations for integer pixel formats 31 * 32 * Copyright: Timur Gafarov 2011-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov 35 */ 36 module dlib.image.image; 37 38 import std.stdio; 39 import std.math; 40 import std.conv; 41 import std.range; 42 import dlib.core.memory; 43 import dlib.math.vector; 44 import dlib.math.interpolation; 45 import dlib.image.color; 46 47 /// sRGBa integer pixel formats, 8 and 16 bits per channel 48 enum IntegerPixelFormat: uint 49 { 50 L8 = 0, 51 LA8 = 1, 52 RGB8 = 2, 53 RGBA8 = 3, 54 L16 = 4, 55 LA16 = 5, 56 RGB16 = 6, 57 RGBA16 = 7 58 } 59 60 /** 61 * Abstract image interface 62 */ 63 interface SuperImage: Freeable 64 { 65 /** 66 * Image width in pixels 67 */ 68 @property uint width(); 69 70 /** 71 * Image height in pixels 72 */ 73 @property uint height(); 74 75 /** 76 * Bits per channel 77 */ 78 @property uint bitDepth(); 79 80 /** 81 * Number of channels 82 */ 83 @property uint channels(); 84 85 /** 86 * Bytes per pixel 87 */ 88 @property uint pixelSize(); 89 90 /** 91 * This is compatible with IntegerPixelFormat and other internal format enums in dlib. 92 * Values from 0 to 255 are reserved for dlib. 93 * Values 256 and above are application-specific and can be used for custom SuperImage implementations 94 */ 95 @property uint pixelFormat(); 96 97 /** 98 * Returns raw buffer of image data in scan order. 99 * Pixel layout is specified by pixelFormat 100 */ 101 @property ubyte[] data(); 102 103 /** 104 * Pixel access operator. 105 * Should always return floating-point sRGBa or linear RGBa, 106 * depending on format family (IntegerPixelFormat or FloatPixelFormat) 107 */ 108 Color4f opIndex(int x, int y); 109 110 /** 111 * Pixel assignment operator. 112 * Accepts floating-point sRGBa or linear RGBa, 113 * depending on format family (IntegerPixelFormat or FloatPixelFormat) 114 */ 115 Color4f opIndexAssign(Color4f c, int x, int y); 116 117 /** 118 * Makes a copy of the image 119 */ 120 @property SuperImage dup(); 121 122 /** 123 * Makes a blank image of the same format 124 */ 125 SuperImage createSameFormat(uint w, uint h); 126 127 /** 128 * Range of x pixel indices 129 */ 130 final @property auto row() 131 { 132 return iota(0, width); 133 } 134 135 /** 136 * Range of y pixel indices 137 */ 138 final @property auto col() 139 { 140 return iota(0, height); 141 } 142 143 /** 144 * Enumerates all pixels of the image in scan order 145 */ 146 final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg) 147 { 148 int result = 0; 149 150 foreach(uint y; col) 151 { 152 foreach(uint x; row) 153 { 154 Color4f col = opIndex(x, y); 155 result = dg(col, x, y); 156 opIndexAssign(col, x, y); 157 158 if (result) 159 break; 160 } 161 162 if (result) 163 break; 164 } 165 166 return result; 167 } 168 } 169 170 /** 171 * SuperImage implementation template for integer pixel formats 172 */ 173 class Image(IntegerPixelFormat fmt): SuperImage 174 { 175 public: 176 177 override @property uint width() 178 { 179 return _width; 180 } 181 182 override @property uint height() 183 { 184 return _height; 185 } 186 187 override @property uint bitDepth() 188 { 189 return _bitDepth; 190 } 191 192 override @property uint channels() 193 { 194 return _channels; 195 } 196 197 override @property uint pixelSize() 198 { 199 return _pixelSize; 200 } 201 202 override @property uint pixelFormat() 203 { 204 return fmt; 205 } 206 207 override @property ubyte[] data() 208 { 209 return _data; 210 } 211 212 override @property SuperImage dup() 213 { 214 auto res = new Image!(fmt)(_width, _height); 215 res.data[] = data[]; 216 return res; 217 } 218 219 override SuperImage createSameFormat(uint w, uint h) 220 { 221 return new Image!(fmt)(w, h); 222 } 223 224 this(uint w, uint h) 225 { 226 _width = w; 227 _height = h; 228 229 _bitDepth = [ 230 IntegerPixelFormat.L8: 8, IntegerPixelFormat.LA8: 8, 231 IntegerPixelFormat.RGB8: 8, IntegerPixelFormat.RGBA8: 8, 232 IntegerPixelFormat.L16: 16, IntegerPixelFormat.LA16: 16, 233 IntegerPixelFormat.RGB16: 16, IntegerPixelFormat.RGBA16: 16 234 ][fmt]; 235 236 _channels = [ 237 IntegerPixelFormat.L8: 1, IntegerPixelFormat.LA8: 2, 238 IntegerPixelFormat.RGB8: 3, IntegerPixelFormat.RGBA8: 4, 239 IntegerPixelFormat.L16: 1, IntegerPixelFormat.LA16: 2, 240 IntegerPixelFormat.RGB16: 3, IntegerPixelFormat.RGBA16: 4 241 ][fmt]; 242 243 _pixelSize = (_bitDepth / 8) * _channels; 244 245 enum maxDimension = int.max; 246 247 if (w > maxDimension) 248 { 249 writeln("Image data is not allocated. Exceeded maximum image width ", maxDimension); 250 return; 251 } 252 253 if (h > maxDimension) 254 { 255 writeln("Image data is not allocated. Exceeded maximum image height ", maxDimension); 256 return; 257 } 258 259 allocateData(); 260 } 261 262 protected void allocateData() 263 { 264 size_t size = cast(size_t)_width * cast(size_t)_height * cast(size_t)_pixelSize; 265 _data = new ubyte[size]; 266 } 267 268 public Color4 getPixel(int x, int y) 269 { 270 ubyte[] pixData = data(); 271 272 if (x >= width) x = width-1; 273 else if (x < 0) x = 0; 274 275 if (y >= height) y = height-1; 276 else if (y < 0) y = 0; 277 278 size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize; 279 280 auto maxv = (2 ^^ bitDepth) - 1; 281 282 static if (fmt == IntegerPixelFormat.L8) 283 { 284 auto v = pixData[index]; 285 return Color4(v, v, v); 286 } 287 else if (fmt == IntegerPixelFormat.LA8) 288 { 289 auto v = pixData[index]; 290 return Color4(v, v, v, pixData[index+1]); 291 } 292 else if (fmt == IntegerPixelFormat.RGB8) 293 { 294 return Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv); 295 } 296 else if (fmt == IntegerPixelFormat.RGBA8) 297 { 298 return Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]); 299 } 300 else if (fmt == IntegerPixelFormat.L16) 301 { 302 ushort v = pixData[index] << 8 | pixData[index+1]; 303 return Color4(v, v, v); 304 } 305 else if (fmt == IntegerPixelFormat.LA16) 306 { 307 ushort v = pixData[index] << 8 | pixData[index+1]; 308 ushort a = pixData[index+2] << 8 | pixData[index+3]; 309 return Color4(v, v, v, a); 310 } 311 else if (fmt == IntegerPixelFormat.RGB16) 312 { 313 ushort r = pixData[index] << 8 | pixData[index+1]; 314 ushort g = pixData[index+2] << 8 | pixData[index+3]; 315 ushort b = pixData[index+4] << 8 | pixData[index+5]; 316 ushort a = cast(ushort)maxv; 317 return Color4(r, g, b, a); 318 } 319 else if (fmt == IntegerPixelFormat.RGBA16) 320 { 321 ushort r = pixData[index] << 8 | pixData[index+1]; 322 ushort g = pixData[index+2] << 8 | pixData[index+3]; 323 ushort b = pixData[index+4] << 8 | pixData[index+5]; 324 ushort a = pixData[index+6] << 8 | pixData[index+7]; 325 return Color4(r, g, b, a); 326 } 327 else 328 { 329 assert (0, "Image.opIndex is not implemented for IntegerPixelFormat." ~ to!string(fmt)); 330 } 331 } 332 333 public Color4 setPixel(Color4 c, int x, int y) 334 { 335 ubyte[] pixData = data(); 336 337 if (x >= width || y >= height || x < 0 || y < 0) 338 return c; 339 340 size_t index = (cast(size_t)y * cast(size_t)_width + cast(size_t)x) * cast(size_t)_pixelSize; 341 342 static if (fmt == IntegerPixelFormat.L8) 343 { 344 pixData[index] = cast(ubyte)c.r; 345 } 346 else if (fmt == IntegerPixelFormat.LA8) 347 { 348 pixData[index] = cast(ubyte)c.r; 349 pixData[index+1] = cast(ubyte)c.a; 350 } 351 else if (fmt == IntegerPixelFormat.RGB8) 352 { 353 pixData[index] = cast(ubyte)c.r; 354 pixData[index+1] = cast(ubyte)c.g; 355 pixData[index+2] = cast(ubyte)c.b; 356 } 357 else if (fmt == IntegerPixelFormat.RGBA8) 358 { 359 pixData[index] = cast(ubyte)c.r; 360 pixData[index+1] = cast(ubyte)c.g; 361 pixData[index+2] = cast(ubyte)c.b; 362 pixData[index+3] = cast(ubyte)c.a; 363 } 364 else if (fmt == IntegerPixelFormat.L16) 365 { 366 pixData[index] = cast(ubyte)(c.r >> 8); 367 pixData[index+1] = cast(ubyte)(c.r & 0xFF); 368 } 369 else if (fmt == IntegerPixelFormat.LA16) 370 { 371 pixData[index] = cast(ubyte)(c.r >> 8); 372 pixData[index+1] = cast(ubyte)(c.r & 0xFF); 373 pixData[index+2] = cast(ubyte)(c.a >> 8); 374 pixData[index+3] = cast(ubyte)(c.a & 0xFF); 375 } 376 else if (fmt == IntegerPixelFormat.RGB16) 377 { 378 pixData[index] = cast(ubyte)(c.r >> 8); 379 pixData[index+1] = cast(ubyte)(c.r & 0xFF); 380 pixData[index+2] = cast(ubyte)(c.g >> 8); 381 pixData[index+3] = cast(ubyte)(c.g & 0xFF); 382 pixData[index+4] = cast(ubyte)(c.b >> 8); 383 pixData[index+5] = cast(ubyte)(c.b & 0xFF); 384 } 385 else if (fmt == IntegerPixelFormat.RGBA16) 386 { 387 pixData[index] = cast(ubyte)(c.r >> 8); 388 pixData[index+1] = cast(ubyte)(c.r & 0xFF); 389 pixData[index+2] = cast(ubyte)(c.g >> 8); 390 pixData[index+3] = cast(ubyte)(c.g & 0xFF); 391 pixData[index+4] = cast(ubyte)(c.b >> 8); 392 pixData[index+5] = cast(ubyte)(c.b & 0xFF); 393 pixData[index+6] = cast(ubyte)(c.a >> 8); 394 pixData[index+7] = cast(ubyte)(c.a & 0xFF); 395 } 396 else 397 { 398 assert (0, "Image.opIndexAssign is not implemented for IntegerPixelFormat." ~ to!string(fmt)); 399 } 400 401 return c; 402 } 403 404 override Color4f opIndex(int x, int y) 405 { 406 return Color4f(getPixel(x, y), _bitDepth); 407 } 408 409 override Color4f opIndexAssign(Color4f c, int x, int y) 410 { 411 setPixel(c.convert(_bitDepth), x, y); 412 return c; 413 } 414 415 void free() 416 { 417 // Do nothing, let GC delete the object 418 } 419 420 protected: 421 422 uint _width; 423 uint _height; 424 uint _bitDepth; 425 uint _channels; 426 uint _pixelSize; 427 ubyte[] _data; 428 } 429 430 /// Specialization of Image for 8-bit luminance pixel format 431 alias ImageL8 = Image!(IntegerPixelFormat.L8); 432 /// Specialization of Image for 8-bit luminance-alpha pixel format 433 alias ImageLA8 = Image!(IntegerPixelFormat.LA8); 434 /// Specialization of Image for 8-bit RGB pixel format 435 alias ImageRGB8 = Image!(IntegerPixelFormat.RGB8); 436 /// Specialization of Image for 8-bit RGBA pixel format 437 alias ImageRGBA8 = Image!(IntegerPixelFormat.RGBA8); 438 439 /// Specialization of Image for 16-bit luminance pixel format 440 alias ImageL16 = Image!(IntegerPixelFormat.L16); 441 /// Specialization of Image for 16-bit luminance-alpha pixel format 442 alias ImageLA16 = Image!(IntegerPixelFormat.LA16); 443 /// Specialization of Image for 16-bit RGB pixel format 444 alias ImageRGB16 = Image!(IntegerPixelFormat.RGB16); 445 /// Specialization of Image for 16-bit RGBA pixel format 446 alias ImageRGBA16 = Image!(IntegerPixelFormat.RGBA16); 447 448 /** 449 * All-in-one image factory interface 450 */ 451 interface SuperImageFactory 452 { 453 SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1); 454 } 455 456 /** 457 * All-in-one image factory class 458 */ 459 class ImageFactory: SuperImageFactory 460 { 461 SuperImage createImage(uint w, uint h, uint channels, uint bitDepth, uint numFrames = 1) 462 { 463 return image(w, h, channels, bitDepth); 464 } 465 } 466 467 private SuperImageFactory _defaultImageFactory; 468 469 /** 470 * Get default image factory singleton 471 */ 472 SuperImageFactory defaultImageFactory() 473 { 474 if (!_defaultImageFactory) 475 _defaultImageFactory = new ImageFactory(); 476 return _defaultImageFactory; 477 } 478 479 /// Create an image with specified parameters 480 SuperImage image(uint w, uint h, uint channels = 3, uint bitDepth = 8) 481 in 482 { 483 assert(channels > 0 && channels <= 4); 484 assert(bitDepth == 8 || bitDepth == 16); 485 } 486 do 487 { 488 switch(channels) 489 { 490 case 1: 491 { 492 if (bitDepth == 8) 493 return new ImageL8(w, h); 494 else 495 return new ImageL16(w, h); 496 } 497 case 2: 498 { 499 if (bitDepth == 8) 500 return new ImageLA8(w, h); 501 else 502 return new ImageLA16(w, h); 503 } 504 case 3: 505 { 506 if (bitDepth == 8) 507 return new ImageRGB8(w, h); 508 else 509 return new ImageRGB16(w, h); 510 } 511 case 4: 512 { 513 if (bitDepth == 8) 514 return new ImageRGBA8(w, h); 515 else 516 return new ImageRGBA16(w, h); 517 } 518 default: 519 assert(0); 520 } 521 } 522 523 /// Convert image to specified pixel format 524 T convert(T)(SuperImage img) 525 { 526 auto res = new T(img.width, img.height); 527 foreach(x; 0..img.width) 528 foreach(y; 0..img.height) 529 res[x, y] = img[x, y]; 530 return res; 531 } 532 533 /// Get interpolated pixel value from an image 534 Color4f bilinearPixel(SuperImage img, float x, float y) 535 { 536 real intX; 537 real fracX = modf(x, intX); 538 real intY; 539 real fracY = modf(y, intY); 540 541 Color4f c1 = img[cast(int)intX, cast(int)intY]; 542 Color4f c2 = img[cast(int)(intX + 1.0f), cast(int)intY]; 543 Color4f c3 = img[cast(int)(intX + 1.0f), cast(int)(intY + 1.0f)]; 544 Color4f c4 = img[cast(int)intX, cast(int)(intY + 1.0f)]; 545 546 Color4f ic1 = lerp(c1, c2, fracX); 547 Color4f ic2 = lerp(c4, c3, fracX); 548 Color4f ic3 = lerp(ic1, ic2, fracY); 549 550 return ic3; 551 } 552 553 /** 554 * Rectangular region of an image that can be iterated with foreach 555 */ 556 struct ImageRegion 557 { 558 SuperImage img; 559 uint xstart; 560 uint ystart; 561 uint width; 562 uint height; 563 564 final int opApply(scope int delegate(ref Color4f p, uint x, uint y) dg) 565 { 566 int result = 0; 567 uint x1, y1; 568 569 foreach(uint y; 0..height) 570 { 571 y1 = ystart + y; 572 foreach(uint x; 0..width) 573 { 574 x1 = xstart + x; 575 Color4f col = img[x1, y1]; 576 result = dg(col, x, y); 577 img[x1, y1] = col; 578 579 if (result) 580 break; 581 } 582 583 if (result) 584 break; 585 } 586 587 return result; 588 } 589 } 590 591 /// ImageRegion factory function 592 ImageRegion region(SuperImage img, uint x, uint y, uint width, uint height) 593 { 594 return ImageRegion(img, x, y, width, height); 595 } 596 597 /** 598 An InputRange of windows (regions around pixels) of an image that can be iterated with foreach 599 */ 600 struct ImageWindowRange 601 { 602 SuperImage img; 603 uint width; 604 uint height; 605 606 private uint halfWidth; 607 private uint halfHeight; 608 private uint wx = 0; 609 private uint wy = 0; 610 611 this(SuperImage img, uint w, uint h) 612 { 613 this.img = img; 614 this.width = w; 615 this.height = h; 616 617 this.halfWidth = this.width / 2; 618 this.halfHeight = this.height / 2; 619 } 620 621 final int opApply(scope int delegate(ImageRegion w, uint x, uint y) dg) 622 { 623 int result = 0; 624 625 foreach(uint y; img.col) 626 { 627 uint ystart = y - halfWidth; 628 foreach(uint x; img.row) 629 { 630 uint xstart = x - halfHeight; 631 632 auto window = region(img, xstart, ystart, width, height); 633 result = dg(window, x, y); 634 635 if (result) 636 break; 637 } 638 639 if (result) 640 break; 641 } 642 643 return result; 644 } 645 646 bool empty = false; 647 648 void popFront() 649 { 650 wx++; 651 if (wx == img.width) 652 { 653 wx = 0; 654 wy++; 655 656 if (wy == img.height) 657 { 658 wy = 0; 659 empty = true; 660 } 661 } 662 } 663 664 @property ImageRegion front() 665 { 666 return region(img, wx - halfWidth, wy - halfHeight, width, height); 667 } 668 } 669 670 /** 671 ImageWindowRange factory function 672 673 Examples: 674 --- 675 // Convolution with emboss kernel 676 677 float[3][3] kernel = [ 678 [-1, -1, 0], 679 [-1, 0, 1], 680 [ 0, 1, 1], 681 ]; 682 683 foreach(window, x, y; inputImage.windows(3, 3)) 684 { 685 Color4f sum = Color4f(0, 0, 0); 686 foreach(ref Color4f pixel, x, y; window) 687 sum += pixel * kernel[y][x]; 688 outputImage[x, y] = sum / 4.0f + 0.5f; 689 } 690 --- 691 */ 692 ImageWindowRange windows(SuperImage img, uint width, uint height) 693 { 694 return ImageWindowRange(img, width, height); 695 }