1 // Written in the D programming language. 2 3 /** 4 * Copyright: Copyright 2012 - 5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 * Authors: Callum Anderson 7 * Date: June 8, 2012 8 */ 9 module imaged.image; 10 11 import 12 std.file, 13 std.math, 14 std.stdio, 15 std.conv, 16 std.path, 17 undead.stream; 18 19 import 20 imaged.jpeg, 21 imaged.png; 22 23 24 // Convenience function for loading from a file 25 Image load(string filename, out IMGError err, bool logging = false) 26 { 27 Decoder dcd = getDecoder(filename, logging); 28 29 if (dcd is null) 30 { 31 return null; 32 } 33 else 34 { 35 err = dcd.errorState; 36 dcd.parseFile(filename); 37 return dcd.image; 38 } 39 } 40 41 42 // Convenience function for getting a decoder for a given filename 43 Decoder getDecoder(string filename, bool logging = false) 44 { 45 Decoder dcd = null; 46 47 switch(extension(filename)) 48 { 49 case ".jpg": 50 case ".jpeg": dcd = new JpegDecoder(logging); break; 51 case ".png": dcd = new PngDecoder(logging); break; 52 default: writeln("Imaged: no loader for extension " ~ extension(filename)); 53 } 54 return dcd; 55 } 56 57 // Convenience function for getting an encoder for a given filename 58 Encoder getEncoder(string filename) 59 { 60 Encoder enc = null; 61 62 switch(extension(filename)) 63 { 64 case ".png": enc = new PngEncoder(); break; 65 default: writeln("Imaged: no loader for extension " ~ extension(filename)); 66 } 67 return enc; 68 } 69 70 71 /** 72 * The functions below allow you to create an OpenGL texture from an Image, and then 73 * throw away the image. These functions assume that you have already called DerelictGL.load(). 74 */ 75 version(OpenGL) 76 { 77 import derelict.opengl3.gl; 78 79 /** 80 * Set internal format to force a specific format, else the format will 81 * be chosen based on the image type. 82 */ 83 GLuint loadTexture(string filename, GLuint internalFormat = 0, 84 bool logging = false, 85 IMGError err = IMGError()) 86 { 87 // Keep a static lookup table for images/textures which have already been loaded 88 static GLuint[string] loadedTextures; 89 90 auto ptr = filename in loadedTextures; 91 if (ptr !is null) 92 { 93 // Texture has already been loaded, just return the GL handle 94 return *ptr; 95 } 96 else 97 { 98 // Load the texture and store it in the lookup table 99 GLuint tex = makeGLTexture(filename, internalFormat, logging, err); 100 loadedTextures[filename] = tex; 101 return tex; 102 } 103 } 104 105 GLuint makeGLTexture(string filename, GLuint internalFormat = 0, 106 bool logging = false, 107 IMGError err = IMGError()) 108 { 109 debug { writeln("Making GL Texture from: " ~ filename); } 110 111 GLuint tex = 0; 112 GLenum texformat; 113 GLint nchannels; 114 115 glGenTextures(1, &tex); 116 auto img = load(filename, err, logging); 117 118 if (img.pixelFormat == Px.R8G8B8) 119 { 120 nchannels = 3; 121 texformat = GL_RGB; 122 debug { writeln("Texture format is: GL_RGB"); } 123 } 124 else if (img.pixelFormat == Px.R8G8B8A8) 125 { 126 nchannels = 4; 127 texformat = GL_RGBA; 128 debug { writeln("Texture format is: GL_RGBA"); } 129 } 130 else if (img.pixelFormat == Px.L8) 131 { 132 nchannels = 1; 133 texformat = GL_LUMINANCE; 134 debug { writeln("Texture format is: GL_LUMINANCE"); } 135 } 136 137 GLuint useFormat = nchannels; 138 if (internalFormat != 0) 139 useFormat = internalFormat; 140 141 /// Bind the texture object. 142 glBindTexture(GL_TEXTURE_2D, tex); 143 144 /// Set the texture interp properties. 145 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 146 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 147 148 /// Create the tex data. 149 if (DerelictGL3.loadedVersion < GLVersion.GL30) 150 { 151 glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); 152 glTexImage2D(GL_TEXTURE_2D, 0, useFormat, cast(int)img.width, cast(int)img.height, 0, 153 texformat, GL_UNSIGNED_BYTE, img.pixels.ptr); 154 } 155 else 156 { 157 glTexImage2D(GL_TEXTURE_2D, 0, useFormat, cast(int)img.width, cast(int)img.height, 0, 158 texformat, GL_UNSIGNED_BYTE, img.pixels.ptr); 159 glGenerateMipmap(GL_TEXTURE_2D); 160 } 161 162 return tex; 163 } // makeGLTexture 164 } 165 166 167 // Structure to report loading/decoding errors 168 struct IMGError 169 { 170 string message; 171 int code; 172 } 173 174 175 // Interface for an image decoder 176 abstract class Decoder 177 { 178 // Parse a single byte 179 void parseByte(ubyte bite); 180 181 182 // Parse a file directly 183 void parseFile(in string filename) 184 { 185 // Loop through the image data 186 auto data = cast(ubyte[]) read(filename); 187 foreach (bite; data) 188 { 189 if (m_errorState.code == 0) 190 { 191 parseByte(bite); 192 } 193 else 194 { 195 debug 196 { 197 if (m_logging) writeln("IMAGE ERROR: ", m_errorState.message); 198 } 199 break; 200 } 201 } 202 } // parseFile 203 204 205 // Parse from the stream. Returns the amount of data left in the stream. 206 size_t parseStream(Stream stream, in size_t chunkSize = 100000) 207 { 208 if (!stream.readable) 209 { 210 m_errorState.code = 1; 211 m_errorState.message = "DECODER: Stream is not readable"; 212 return 0; 213 } 214 215 ubyte[] buffer; 216 buffer.length = chunkSize; 217 size_t rlen = stream.readBlock(cast(void*)buffer.ptr, chunkSize*ubyte.sizeof); 218 if (rlen) 219 { 220 foreach(bite; buffer) 221 { 222 if (m_errorState.code != 0) 223 { 224 rlen = 0; 225 break; 226 } 227 parseByte(bite); 228 } 229 } 230 return rlen; 231 } 232 233 // Getters 234 @property Image image() { return m_image; } 235 @property IMGError errorState() const { return m_errorState; } // ditto 236 237 protected: 238 bool m_logging = false; // if true, will emit logs when in debug mode 239 IMGError m_errorState; 240 Image m_image; 241 } 242 243 244 // Interface for an image encoder 245 abstract class Encoder 246 { 247 bool write(in Image img, string filename); 248 } 249 250 251 // Currently allowed pixel formats 252 enum Px 253 { 254 L8, 255 L8A8, 256 R8G8B8, 257 R8G8B8A8, 258 L16, 259 L16A16, 260 R16G16B16, 261 R16G16B16A16 262 } 263 264 // Enumerate the image formats 265 enum ImageFormat 266 { 267 GETFROMEXTENSION, 268 PNG, 269 JPG, 270 JPEG 271 } 272 273 274 // Container for RGBA values 275 struct Pixel 276 { 277 /** 278 * Note that alpha defaults to opaque for _8 bit_ formats. 279 * For 16 bit formats, be aware of this. 280 */ 281 ushort r, g, b, a = 255; 282 283 this(int r, int g, int b, int a = 255) 284 { 285 this.r = cast(ushort) r; 286 this.g = cast(ushort) g; 287 this.b = cast(ushort) b; 288 this.a = cast(ushort) a; 289 } 290 } 291 292 293 // Interface for an Image 294 interface Image 295 { 296 // Algorithm for image resizing 297 enum ResizeAlgo { 298 CROP, 299 NEAREST, 300 BILINEAR, 301 BICUBIC 302 } 303 304 // Overload index operator to return pixel at (x,y) coords (y is measured from the top down) 305 Pixel opIndex(size_t x, size_t y, bool scaleToByte = true); 306 307 // Simply calls opIndex 308 Pixel getPixel(size_t x, size_t y, bool scaleToByte = true); 309 310 // Set the pixel at (x,y) from the given Pixel 311 void setPixel(size_t x, size_t y, Pixel p); 312 313 // Set the pixel at (x,y) from the given ubyte array 314 void setPixel(size_t x, size_t y, const(ubyte[]) data); 315 316 // Set a complete row of the image, from the supplied buffer 317 void setRow(size_t y, const(ubyte[]) data); 318 319 // Return a copy of the current image 320 Image copy() const; 321 322 // Resize the image, either by cropping, nearest neighbor or bilinear algorithms 323 bool resize(uint newWidth, uint newHeight, ResizeAlgo algo = ResizeAlgo.NEAREST); 324 325 // Write image to disk as 326 bool write(string filename, ImageFormat fmt = ImageFormat.GETFROMEXTENSION); 327 328 // Getters 329 @property const(uint) width() const; 330 @property const(uint) height() const; // ditto 331 @property const(int) stride() const; // ditto 332 @property const(uint) bitDepth() const; // ditto 333 @property const(Px) pixelFormat() const; // ditto 334 @property const(ubyte[]) pixels() const; // ditto 335 @property ref ubyte[] pixels(); // ditto 336 @property ubyte* pixelsPtr(); // ditto 337 } 338 339 340 // The image class backend, parameterized by pixel format 341 class Img(Px F) : Image 342 { 343 this(uint width, uint height, bool noAlloc = false) 344 { 345 static if (F == Px.L8) 346 { 347 m_bitDepth = 8; 348 m_channels = 1; 349 } 350 else if (F == Px.L8A8) 351 { 352 m_bitDepth = 8; 353 m_channels = 2; 354 } 355 else if (F == Px.L8A8) 356 { 357 m_bitDepth = 8; 358 m_channels = 2; 359 } 360 else if (F == Px.R8G8B8) 361 { 362 m_bitDepth = 8; 363 m_channels = 3; 364 } 365 else if (F == Px.R8G8B8A8) 366 { 367 m_bitDepth = 8; 368 m_channels = 4; 369 } 370 else if (F == Px.L16) 371 { 372 m_bitDepth = 16; 373 m_channels = 1; 374 } 375 else if (F == Px.L16A16) 376 { 377 m_bitDepth = 16; 378 m_channels = 2; 379 } 380 else if (F == Px.R16G16B16) 381 { 382 m_bitDepth = 16; 383 m_channels = 3; 384 } 385 else if (F == Px.R16G16B16A16) 386 { 387 m_bitDepth = 16; 388 m_channels = 4; 389 } 390 391 m_width = width; 392 m_height = height; 393 m_stride = (m_bitDepth/8)*m_channels; 394 395 // Allocate data array 396 if (!noAlloc) 397 m_data = new ubyte[](width*height*m_stride); 398 } 399 400 401 // Create an image using a pre-existing buffer 402 this(uint width, uint height, ubyte[] data) 403 { 404 this(width, height, true); 405 m_data = data; 406 } 407 408 409 /** 410 * Get the pixel at the given index. If scaleToByte is set, 411 * 16 bit formats will only return the high bytes, effectively 412 * reducing precision to 8bit. 413 */ 414 Pixel opIndex(size_t x, size_t y, bool scaleToByte = true) 415 { 416 auto index = getIndex(x, y); 417 418 static if (F == Px.L8) 419 { 420 auto v = m_data[index]; 421 return Pixel(v,v,v); 422 } 423 else if (F == Px.L8A8) 424 { 425 auto v = m_data[index]; 426 return Pixel(v, v, v, m_data[index+1]); 427 } 428 else if (F == Px.R8G8B8) 429 { 430 return Pixel(m_data[index],m_data[index+1],m_data[index+2]); 431 } 432 else if (F == Px.R8G8B8A8) 433 { 434 return Pixel(m_data[index],m_data[index+1],m_data[index+2],m_data[index+3]); 435 } 436 else if (F == Px.L16) 437 { 438 int v; 439 if (scaleToByte) 440 { 441 v = m_data[index]; 442 return Pixel(v,v,v,ubyte.max); 443 } 444 else 445 { 446 v = m_data[index] << 8 | m_data[index+1]; 447 return Pixel(v,v,v,ushort.max); 448 } 449 450 } 451 else if (F == Px.L16A16) 452 { 453 int v, a; 454 if (scaleToByte) 455 { 456 v = m_data[index]; 457 a = m_data[index+2]; 458 } 459 else 460 { 461 v = m_data[index] << 8 | m_data[index+1]; 462 a = m_data[index+2] << 8 | m_data[index+3]; 463 } 464 return Pixel(v,v,v,a); 465 } 466 else if (F == Px.R16G16B16) 467 { 468 int r, g, b; 469 if (scaleToByte) 470 { 471 r = m_data[index]; 472 g = m_data[index+2]; 473 b = m_data[index+4]; 474 return Pixel(r,g,b,ubyte.max); 475 } 476 else 477 { 478 r = m_data[index] << 8 | m_data[index+1]; 479 g = m_data[index+2] << 8 | m_data[index+3]; 480 b = m_data[index+4] << 8 | m_data[index+5]; 481 return Pixel(r,g,b,ushort.max); 482 } 483 } 484 else if (F == Px.R16G16B16A16) 485 { 486 int r, g, b, a; 487 if (scaleToByte) 488 { 489 r = m_data[index]; 490 g = m_data[index+2]; 491 b = m_data[index+4]; 492 a = m_data[index+6]; 493 } 494 else 495 { 496 r = m_data[index] << 8 | m_data[index+1]; 497 g = m_data[index+2] << 8 | m_data[index+3]; 498 b = m_data[index+4] << 8 | m_data[index+5]; 499 a = m_data[index+6] << 8 | m_data[index+7]; 500 } 501 return Pixel(r,g,b,a); 502 } 503 } 504 505 506 // Simply a different way of calling opIndex 507 Pixel getPixel(size_t x, size_t y, bool scaleToByte = true) 508 { 509 return this[x,y,scaleToByte]; 510 } 511 512 513 // Set the pixel at the given index 514 void setPixel(size_t x, size_t y, Pixel p) 515 { 516 auto index = getIndex(x, y); 517 518 static if (F == Px.L8) 519 { 520 m_data[index] = cast(ubyte)p.r; 521 } 522 else if (F == Px.L8A8) 523 { 524 m_data[index] = cast(ubyte)p.r; 525 m_data[index+1] = cast(ubyte)p.a; 526 } 527 else if (F == Px.R8G8B8) 528 { 529 m_data[index] = cast(ubyte)p.r; 530 m_data[index+1] = cast(ubyte)p.g; 531 m_data[index+2] = cast(ubyte)p.b; 532 } 533 else if (F == Px.R8G8B8A8) 534 { 535 m_data[index] = cast(ubyte)p.r; 536 m_data[index+1] = cast(ubyte)p.g; 537 m_data[index+2] = cast(ubyte)p.b; 538 m_data[index+3] = cast(ubyte)p.a; 539 } 540 else if (F == Px.L16) 541 { 542 m_data[index] = cast(ubyte)(p.r >> 8); 543 m_data[index+1] = cast(ubyte)(p.r & 0xFF); 544 } 545 else if (F == Px.L16A16) 546 { 547 m_data[index] = cast(ubyte)(p.r >> 8); 548 m_data[index+1] = cast(ubyte)(p.r & 0xFF); 549 m_data[index+2] = cast(ubyte)(p.a >> 8); 550 m_data[index+3] = cast(ubyte)(p.a & 0xFF); 551 } 552 else if (F == Px.R16G16B16) 553 { 554 m_data[index] = cast(ubyte)(p.r >> 8); 555 m_data[index+1] = cast(ubyte)(p.r & 0xFF); 556 m_data[index+2] = cast(ubyte)(p.g >> 8); 557 m_data[index+3] = cast(ubyte)(p.g & 0xFF); 558 m_data[index+4] = cast(ubyte)(p.b >> 8); 559 m_data[index+5] = cast(ubyte)(p.b & 0xFF); 560 } 561 else if (F == Px.R16G16B16A16) 562 { 563 m_data[index] = cast(ubyte)(p.r >> 8); 564 m_data[index+1] = cast(ubyte)(p.r & 0xFF); 565 m_data[index+2] = cast(ubyte)(p.g >> 8); 566 m_data[index+3] = cast(ubyte)(p.g & 0xFF); 567 m_data[index+4] = cast(ubyte)(p.b >> 8); 568 m_data[index+5] = cast(ubyte)(p.b & 0xFF); 569 m_data[index+6] = cast(ubyte)(p.a >> 8); 570 m_data[index+7] = cast(ubyte)(p.a & 0xFF); 571 } 572 } 573 574 // Set the pixel at the given index 575 void setPixel(size_t x, size_t y, const(ubyte[]) data) 576 { 577 auto index = getIndex(x, y); 578 579 static if (F == Px.L8) 580 { 581 setPixel(x, y, Pixel(data[0],0,0,0)); 582 } 583 else if (F == Px.L8A8) 584 { 585 setPixel(x, y, Pixel(data[0], 586 0, 587 0, 588 data[1])); 589 } 590 else if (F == Px.R8G8B8) 591 { 592 setPixel(x, y, Pixel(data[0], 593 data[1], 594 data[2], 595 0)); 596 } 597 else if (F == Px.R8G8B8A8) 598 { 599 setPixel(x, y, Pixel(data[0], 600 data[1], 601 data[2], 602 data[3])); 603 } 604 else if (F == Px.L16) 605 { 606 setPixel(x, y, Pixel(data[0] << 8 | data[1],0,0,0)); 607 } 608 else if (F == Px.L16A16) 609 { 610 setPixel(x, y, Pixel(data[0] << 8 | data[1], 611 0, 612 0, 613 data[2] << 8 | data[3])); 614 } 615 else if (F == Px.R16G16B16) 616 { 617 setPixel(x, y, Pixel(data[0] << 8 | data[1], 618 data[2] << 8 | data[3], 619 data[4] << 8 | data[5], 620 0)); 621 } 622 else if (F == Px.R16G16B16A16) 623 { 624 setPixel(x, y, Pixel(data[0] << 8 | data[1], 625 data[2] << 8 | data[3], 626 data[4] << 8 | data[5], 627 data[6] << 8 | data[7])); 628 } 629 } 630 631 632 633 // Set a whole row (scanline) of data from the given buffer. Rows count down from the top. 634 void setRow(size_t y, const(ubyte[]) data) 635 { 636 auto takeBytes = m_width*m_stride; 637 auto index = getIndex(0, y); 638 639 debug // Check array bounds if debug mode 640 { 641 if (data.length < takeBytes) 642 { 643 writeln(takeBytes, ", ", data.length); 644 throw new Exception("Image setRow: buffer does not have required length!"); 645 } 646 } 647 m_data[index..index+takeBytes] = data[]; 648 } 649 650 651 // Return an Image which is a copy of this one 652 Img!F copy() const 653 { 654 auto copy = new Img!F(m_width, m_height); 655 copy.pixels = m_data.dup; 656 return copy; 657 } 658 659 660 /* 661 * Resize an image to the given dimensions, using the given algorithm. 662 * Returns: true on successful resize, else false. 663 */ 664 bool resize(uint newWidth, uint newHeight, ResizeAlgo algo) 665 { 666 // If new dimensions are same as old ones, flag success 667 if (newWidth == m_width && newHeight == m_height) 668 { 669 return true; 670 } 671 672 // If old dimensions are 1x1, and algo is BILINEAR, switch to NEAREST 673 if ((m_width == 1 || m_height == 1) && algo == ResizeAlgo.BILINEAR) 674 { 675 algo = ResizeAlgo.NEAREST; 676 } 677 678 // Create a delegate to define the resizing algorithm 679 ushort[4] delegate(Img!F, float, float, uint, uint) algorithmDelegate; 680 681 if (algo == ResizeAlgo.NEAREST) 682 { 683 algorithmDelegate = &getNearestNeighbour; 684 } 685 else if (algo == ResizeAlgo.BILINEAR) 686 { 687 algorithmDelegate = &getBilinearInterpolate; 688 } 689 else if (algo == ResizeAlgo.CROP) 690 { 691 algorithmDelegate = &getCropped; 692 } 693 else 694 { 695 return false; // Algorithm not implemented!! 696 } 697 698 // Make a copy of the current image, this is the 'source' 699 auto oldImg = this.copy(); 700 int oldWidth = oldImg.width; 701 int oldHeight = oldImg.height; 702 703 // Allocate a new array to hold the new image 704 m_data = new ubyte[](newWidth*newHeight*m_stride); 705 m_width = newWidth; 706 m_height = newHeight; 707 708 uint i = 0; // 1D array index 709 float x_ratio = cast(float)(oldWidth-1)/cast(float)(newWidth); 710 float y_ratio = cast(float)(oldHeight-1)/cast(float)(newHeight); 711 712 // Loop through rows and columns of the new image 713 foreach (row; 0..newHeight) 714 { 715 foreach (col; 0..newWidth) 716 { 717 float x = x_ratio * cast(float)col; 718 float y = y_ratio * cast(float)row; 719 720 // Use the selected algorithm to get the pixel value 721 ushort[4] p = algorithmDelegate(oldImg, x, y, col, row); 722 723 // Store the new pixel 724 static if (F == Px.L8) 725 { 726 m_data[i+col] = cast(ubyte)p[0]; 727 } 728 else if (F == Px.L8A8 || 729 F == Px.R8G8B8 || 730 F == Px.R8G8B8A8 ) 731 { 732 m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = to!(ubyte[])(p[0..m_stride]); 733 } 734 else if (F == Px.L16) 735 { 736 m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF]; 737 } 738 else if (F == Px.L16A16) 739 { 740 m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF, 741 p[3] >> 8, p[3] & 0xFF]; 742 } 743 else if (F == Px.R16G16B16) 744 { 745 m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF, 746 p[1] >> 8, p[1] & 0xFF, 747 p[2] >> 8, p[2] & 0xFF]; 748 } 749 else if (F == Px.R16G16B16A16) 750 { 751 m_data[(i+col)*m_stride..(i+col + 1)*m_stride] = [p[0] >> 8, p[0] & 0xFF, 752 p[1] >> 8, p[1] & 0xFF, 753 p[2] >> 8, p[2] & 0xFF, 754 p[3] >> 8, p[3] & 0xFF]; 755 } 756 757 } // columns 758 i += m_width; 759 } 760 761 return true; // successfully resized 762 } // resize 763 764 /** 765 * Write this image to disk with the given filename. Format can be inferred from 766 * filename extension by default, or supplied explicitly. 767 */ 768 bool write(string filename, ImageFormat fmt = ImageFormat.GETFROMEXTENSION) 769 { 770 Encoder enc; 771 if (fmt == ImageFormat.GETFROMEXTENSION) 772 { 773 enc = getEncoder(filename); 774 } 775 else if (fmt == ImageFormat.PNG) 776 { 777 enc = new PngEncoder(); 778 } 779 780 if (enc is null) 781 return false; 782 else 783 return enc.write(this, filename); 784 } 785 786 787 // Getters 788 @property const(uint) width() const { return m_width; } // ditto 789 @property const(uint) height() const { return m_height; } // ditto 790 @property const(int) stride() const { return m_stride; } // ditto 791 @property const(uint) bitDepth() const { return m_bitDepth; } // ditto 792 @property const(Px) pixelFormat() const { return F; } // ditto 793 @property const(ubyte[]) pixels() const { return m_data; } // ditto 794 @property ref ubyte[] pixels() { return m_data; } // ditto 795 @property ubyte* pixelsPtr() { return m_data.ptr; } // ditto 796 797 798 private: 799 800 // Get the byte index and bit offset for a given (x,y) 801 uint getIndex(size_t x, size_t y) 802 { 803 return cast(uint)(x + y*m_width)*m_stride; 804 } 805 806 807 // Cropping algorithm - If (x,y) is in the original, return that pixel, else return 0,0,0,0 808 ushort[4] getCropped(Img!F i, float x, float y, uint col, uint row) 809 { 810 Pixel p; 811 if (col < i.width && row < i.height) 812 { 813 p = i[col, row, false]; 814 } 815 else 816 { 817 p = Pixel(0,0,0); // note: alpha is left as default here, so it will appear black 818 } 819 return [p.r, p.g, p.b, p.a]; 820 } 821 822 823 // Nearest neighbour sampling (actually just the nearest neighbour to the left and down) 824 ushort[4] getNearestNeighbour(Img!F i, float x, float y, uint col, uint row) 825 { 826 int x0 = cast(int)x; 827 int y0 = cast(int)y; 828 Pixel p = i[x0, y0, false]; 829 return [p.r, p.g, p.b, p.a]; 830 } 831 832 /** 833 * Calculate a bilinear interpolate at x, y. This implementation is from: 834 * http://fastcpp.blogspot.com/2011/06/bilinear-pixel-interpolation-using-sse.html 835 */ 836 ushort[4] getBilinearInterpolate(Img!F i, float x, float y, uint col, uint row) 837 { 838 int x0 = cast(int)x; 839 int y0 = cast(int)y; 840 841 // Weighting factors 842 float fx = x - x0; 843 float fy = y - y0; 844 float fx1 = 1.0f - fx; 845 float fy1 = 1.0f - fy; 846 847 /** Get the locations in the src array of the 2x2 block surrounding (row,col) 848 * 01 ------- 11 849 * | (row,col) | 850 * 00 ------- 10 851 */ 852 Pixel p1 = i[x0, y0, false]; 853 Pixel p2 = i[x0+1, y0, false]; 854 Pixel p3 = i[x0, y0+1, false]; 855 Pixel p4 = i[x0+1, y0+1, false]; 856 857 int wgt1 = cast(int)(fx1 * fy1 * 256.0f); 858 int wgt2 = cast(int)(fx * fy1 * 256.0f); 859 int wgt3 = cast(int)(fx1 * fy * 256.0f); 860 int wgt4 = cast(int)(fx * fy * 256.0f); 861 862 int r = (p1.r * wgt1 + p2.r * wgt2 + p3.r * wgt3 + p4.r * wgt4) >> 8; 863 int g = (p1.g * wgt1 + p2.g * wgt2 + p3.g * wgt3 + p4.g * wgt4) >> 8; 864 int b = (p1.b * wgt1 + p2.b * wgt2 + p3.b * wgt3 + p4.b * wgt4) >> 8; 865 int a = (p1.a * wgt1 + p2.a * wgt2 + p3.a * wgt3 + p4.a * wgt4) >> 8; 866 867 return [cast(short)r, cast(short)g, cast(short)b, cast(short)a]; 868 } 869 870 uint m_width = 0, m_height = 0; 871 int m_stride = 0; // in bytes (minimum 1) 872 uint m_bitDepth = 0; 873 uint m_channels = 0; 874 ubyte[] m_data; 875 } 876