1 /** 2 Gamut public API. This is the main image abstraction. 3 4 Copyright: Copyright Guillaume Piolat 2022 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module gamut.image; 8 9 import core.stdc.stdio; 10 import core.stdc.stdlib: malloc, free, realloc; 11 import core.stdc.string: strlen; 12 13 import gamut.types; 14 import gamut.io; 15 import gamut.plugin; 16 import gamut.internals.cstring; 17 import gamut.internals.errors; 18 import gamut.internals.types; 19 20 public import gamut.types: ImageFormat; 21 22 nothrow @nogc @safe: 23 24 /// Deallocate pixel data. Everything allocated with `allocatePixelStorage` or disowned eventually needs 25 /// to be through that function. 26 void freeImageData(void* mallocArea) @trusted 27 { 28 deallocatePixelStorage(mallocArea); 29 } 30 31 /// Image type. 32 /// Image has disabled copy ctor and postblit, to avoid accidental allocations. 33 /// 34 /// IMPORTANT 35 /// 36 /// Images are subtyped like this: 37 /// 38 /// Image All image can also be: errored() or not. 39 /// / \ 40 /// no-type or hasType() 41 /// / \ 42 /// hasData() or no-data Also: hasNonZeroSize(). 43 /// ___/ | \______ Images with a type have a width and height (that can be zero). 44 /// / | \ Also: isOwned() exist for image that are hasData(). 45 /// isPlanar or hasPlainPixels or isCompressed Planar and compressed images are not implemented. 46 /// Only image with hasData() have to follow the LayoutConstraints, 47 /// though all image have a LayoutConstraints. 48 /// 49 /// Public Functions are labelled this way: 50 /// #type => the calling Image must have a type. 51 /// #data => the calling Image must have data (implies #type) 52 /// #plain => the calling Image must have plain-pixels. 53 /// #own => the calling Image must have data AND own it. 54 /// It is a programming error to call a function that doesn't follow the tag constraints. 55 /// 56 struct Image 57 { 58 nothrow @nogc @safe: 59 public: 60 61 // 62 // <BASIC STORAGE> 63 // 64 65 /// Get the pixel type. 66 /// See_also: `PixelType`. 67 /// Tags: none. 68 PixelType type() pure const 69 { 70 return _type; 71 } 72 73 /// Returns: Width of image in pixels. 74 /// Tags: #type 75 int width() pure const 76 { 77 assert(hasType()); 78 return _width; 79 } 80 81 /// Returns: Height of image in pixels. 82 /// Tags: #type 83 int height() pure const 84 { 85 assert(hasType()); 86 return _height; 87 } 88 89 /// Get the image pitch (byte distance between rows), in bytes. 90 /// 91 /// Warning: This pitch can be, or not be, a negative integer. 92 /// When the image has layout constraint LAYOUT_VERT_FLIPPED, 93 /// it is always kept <= 0 (if the image has data). 94 /// When the image has layout constraint LAYOUT_VERT_STRAIGHT, 95 /// it is always kept >= 0 (if the image has data). 96 /// 97 /// See_also: `scanlineInBytes`. 98 /// Tags: #type #data 99 int pitchInBytes() pure const 100 { 101 assert(hasType() && hasData()); 102 103 bool forceVFlip = (_layoutConstraints & LAYOUT_VERT_FLIPPED) != 0; 104 bool forceNoVFlip = (_layoutConstraints & LAYOUT_VERT_STRAIGHT) != 0; 105 if (forceVFlip) 106 assert(_pitch <= 0); // Note if height were zero, _pitch could perhaps be zero. 107 if (forceNoVFlip) 108 assert(_pitch >= 0); 109 110 return _pitch; 111 } 112 113 /// Length of the managed scanline pixels, in bytes. 114 /// 115 /// This is NOT the pointer offset between two scanlines (`pitchInBytes`). 116 /// This is just `width() * size-of-one-pixel`. 117 /// Those bytes are "part of the image", while the trailing and border pixels are not. 118 /// 119 /// See_also: `pitchInBytes`. 120 /// Tags: #type #data 121 int scanlineInBytes() pure const 122 { 123 assert(hasData()); 124 return _width * pixelTypeSize(type); 125 } 126 127 /// A compressed image doesn't have its pixels available. 128 /// Warning: only makes sense for image that `hasData()`, with non-zero height. 129 /// Tags: #type #data 130 bool isStoredUpsideDown() pure const 131 { 132 assert(hasData()); 133 return _pitch < 0; 134 } 135 136 /// Returns a pointer to the `y` nth line of pixels. 137 /// Only possible if the image has plain pixels. 138 /// What pixel format it points to, depends on the image `type()`. 139 /// 140 /// --- 141 /// Guarantees by layout constraints: 142 /// * next scanline (if any) is returned pointer + pitchInBytes() bytes. 143 /// * scanline pointer are aligned by given scanline alignment flags (at least). 144 /// * after each scanline there is at least a number of trailing pixels given by layout flags 145 /// * scanline pixels can be processed by multiplicity given by layout flags 146 /// * around the image, there is a border whose width is at least the one given by layout flags. 147 /// --- 148 /// 149 /// For each scanline pointer, you can _always_ READ `ptr[0..pitchInBytes()]` without memory error. 150 /// However, WRITING to this scanline doesn't guarantee anything by itself since the image 151 /// could be a sub-image, and the underlying buffer could be shared. 152 /// 153 /// Returns: The scanline start. 154 /// Next scanline (if any) is returned pointer + pitchInBytes() bytes 155 /// Tags: #type #data #plain 156 inout(ubyte)* scanline(int y) inout pure @trusted 157 { 158 assert(isPlainPixels()); 159 assert(y >= 0 && y < _height); 160 return _data + _pitch * y; 161 } 162 163 /// Returns a slice of all pixels at once in O(1). 164 /// This is only possible if the image is stored non-flipped, and without space 165 /// between scanline. 166 /// To avoid accidental correctness, the image need the layout constraints: 167 /// `LAYOUT_GAPLESS | LAYOUT_VERT_STRAIGHT`. 168 /// Tags: #type #data #plain 169 inout(ubyte)[] allPixelsAtOnce() inout pure @trusted 170 { 171 assert(isPlainPixels()); 172 173 // the image need the LAYOUT_GAPLESS flag. 174 assert(isGapless()); 175 176 // the image need the LAYOUT_VERT_STRAIGHT flag. 177 assert(mustNotBeStoredUpsideDown()); 178 179 // Why there is no #overflow here: 180 int psize = pixelTypeSize(_type); 181 assert(psize < GAMUT_MAX_PIXEL_SIZE); 182 assert(cast(long)_width * _height < GAMUT_MAX_IMAGE_WIDTH_x_HEIGHT); 183 assert(cast(long)GAMUT_MAX_IMAGE_WIDTH_x_HEIGHT * GAMUT_MAX_PIXEL_SIZE < 0x7fffffffUL); 184 int ofs = _width * _height * psize; 185 return _data[0..ofs]; 186 } 187 188 // 189 // </BASIC STORAGE> 190 // 191 192 // 193 // <RESOLUTION> 194 // 195 196 /// Returns: Horizontal resolution in Dots Per Inch (DPI). 197 /// `GAMUT_UNKNOWN_RESOLUTION` if unknown. 198 /// Tags: none. 199 float dotsPerInchX() pure const 200 { 201 if (_resolutionY == GAMUT_UNKNOWN_RESOLUTION || _pixelAspectRatio == GAMUT_UNKNOWN_ASPECT_RATIO) 202 return GAMUT_UNKNOWN_RESOLUTION; 203 return _resolutionY * _pixelAspectRatio; 204 } 205 206 /// Returns: Vertical resolution in Dots Per Inch (DPI). 207 /// `GAMUT_UNKNOWN_RESOLUTION` if unknown. 208 /// Tags: none. 209 float dotsPerInchY() pure const 210 { 211 return _resolutionY; 212 } 213 214 /// Returns: Pixel Aspect Ratio for the image (PAR). 215 /// `GAMUT_UNKNOWN_ASPECT_RATIO` if unknown. 216 /// 217 /// This is physical width of a pixel / physical height of a pixel. 218 /// 219 /// Reference: https://en.wikipedia.org/wiki/Pixel_aspect_ratio 220 /// Tags: none. 221 float pixelAspectRatio() pure const 222 { 223 return _pixelAspectRatio; 224 } 225 226 /// Returns: Horizontal resolution in Pixels Per Meters (PPM). 227 /// `GAMUT_UNKNOWN_RESOLUTION` if unknown. 228 /// Tags: none. 229 float pixelsPerMeterX() pure const 230 { 231 float dpi = dotsPerInchX(); 232 if (dpi == GAMUT_UNKNOWN_RESOLUTION) 233 return GAMUT_UNKNOWN_RESOLUTION; 234 return convertMetersToInches(dpi); 235 } 236 237 /// Returns: Vertical resolution in Pixels Per Meters (PPM). 238 /// `GAMUT_UNKNOWN_RESOLUTION` if unknown. 239 /// Tags: none. 240 float pixelsPerMeterY() pure const 241 { 242 float dpi = dotsPerInchY(); 243 if (dpi == GAMUT_UNKNOWN_RESOLUTION) 244 return GAMUT_UNKNOWN_RESOLUTION; 245 return convertMetersToInches(dpi); 246 } 247 248 // 249 // </RESOLUTION> 250 // 251 252 253 // 254 // <GETTING STATUS AND CAPABILITIES> 255 // 256 257 /// Was there an error as a result of calling a public method of `Image`? 258 /// It is now unusable. 259 /// Tags: none. 260 bool errored() pure const 261 { 262 return _error !is null; 263 } 264 265 /// The error message (null if no error currently held). 266 /// This slice is followed by a '\0' zero terminal character, so 267 /// it can be safely given to `print`. 268 /// Tags: none. 269 const(char)[] errorMessage() pure const @trusted 270 { 271 if (_error is null) 272 return null; 273 return _error[0..strlen(_error)]; 274 } 275 276 /// An image can have a pixel type (usually pixels), or not. 277 /// Not a lot of operations are available if there is no type. 278 /// Note: An image that has no must necessarily have no data. 279 /// Tags: none. 280 bool hasType() pure const 281 { 282 return _type != PixelType.unknown; 283 } 284 285 /// An image can have data (usually pixels), or not. 286 /// "Data" refers to pixel content, that can be in a decoded form, but also in more 287 /// complicated forms such as planar, compressed, etc. (FUTURE) 288 /// 289 /// Note: An image that has no data doesn't have to follow its `LayoutConstraints`. 290 /// But an image with zero size must. 291 /// An image that "has data" also "has a type". 292 /// Tags: none. 293 bool hasData() pure const 294 { 295 if (_data) 296 { 297 // Having _data is a superset of having a _type. Else, it's a gamut internal error. 298 assert(hasType()); 299 } 300 301 return _data !is null; 302 } 303 304 /// An that has data can own it (will free it in destructor) or can borrow it. 305 /// An image that has no data, cannot own it. 306 /// Tags: none. 307 bool isOwned() pure const 308 { 309 return hasData() && (_allocArea !is null); 310 } 311 312 /// Disown the image allocation data. 313 /// This return both the pixel _data (same as and the allocation data 314 /// The data MUST be freed with `freeImageData`. 315 /// The image still points into that data, and you must ensure the data lifetime exceeeds 316 /// the image lifetime. 317 /// Tags: #type #own #data 318 /// Warning: this return the malloc'ed area, NOT the image data itself. 319 /// However, with the constraints 320 ubyte* disownData() pure 321 { 322 assert(isOwned()); 323 ubyte* r = _allocArea; 324 _allocArea = null; 325 assert(!isOwned()); 326 return r; 327 } 328 329 /// An image can have plain pixels, which means: 330 /// 1. it has data (and as such, a type) 331 /// 2. those are in a plain decoded format (not a compressed texture, not planar, etc). 332 /// Tags: none. 333 deprecated alias hasPlainPixels = isPlainPixels; 334 bool isPlainPixels() pure const 335 { 336 return hasData() && pixelTypeIsPlain(_type); // Note: all formats are plain, for now. 337 } 338 339 /// A planar image is for example YUV420. 340 /// If the image is planar, its lines are not accessible like that. 341 /// Currently not supported. 342 /// Tags: none. 343 bool isPlanar() pure const 344 { 345 return hasData() && pixelTypeIsPlanar(_type); 346 } 347 348 /// A compressed image doesn't have its pixels available. 349 /// Currently not supported. 350 /// Tags: none. 351 bool isCompressed() pure const 352 { 353 return hasData() && pixelTypeIsCompressed(_type); 354 } 355 356 /// An image for which width > 0 and height > 0. 357 /// Tags: none. 358 bool hasNonZeroSize() pure const 359 { 360 return width() != 0 && height() != 0; 361 } 362 363 // 364 // </GETTING STATUS AND CAPABILITIES> 365 // 366 367 368 // 369 // <INITIALIZE> 370 // 371 372 /// Clear the image and initialize a new image, with given dimensions and plain pixels. 373 /// Tags: none. 374 this(int width, 375 int height, 376 PixelType type = PixelType.rgba8, 377 LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 378 { 379 setSize(width, height, type, layoutConstraints); 380 } 381 ///ditto 382 void setSize(int width, 383 int height, 384 PixelType type = PixelType.rgba8, 385 LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 386 { 387 // PERF: Pessimized, because we don't know if we have been borrowed from... 388 // Not sure what to do. 389 cleanupBitmapAndTypeIfAny(); 390 391 clearError(); 392 393 if (width < 0 || height < 0) 394 { 395 error(kStrIllegalNegativeDimension); 396 return; 397 } 398 399 if (!imageIsValidSize(width, height)) 400 { 401 error(kStrImageTooLarge); 402 return; 403 } 404 405 if (!setStorage(width, height, type, layoutConstraints)) 406 { 407 // precise error set by setStorage 408 return; 409 } 410 } 411 412 /// Initialize an image with no data, for example if you wanted an image without the pixel content. 413 /// Tags: none. 414 void initWithNoData(int width, 415 int height, 416 PixelType type = PixelType.rgba8, 417 LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 418 { 419 cleanupBitmapAndTypeIfAny(); 420 clearError(); 421 422 if (width < 0 || height < 0) 423 { 424 error(kStrIllegalNegativeDimension); 425 return; 426 } 427 428 if (!imageIsValidSize(width, height)) 429 { 430 error(kStrImageTooLarge); 431 return; 432 } 433 434 if (!layoutConstraintsValid(layoutConstraints)) 435 { 436 error(kStrIllegalLayoutConstraints); 437 return; 438 } 439 440 _data = null; // no data 441 _allocArea = null; // not owned 442 _type = type; 443 _width = width; 444 _height = height; 445 _pitch = 0; 446 _layoutConstraints = layoutConstraints; 447 } 448 449 /// Clone an existing image. 450 /// This image should have plain pixels. 451 /// Tags: #type #data #plain. 452 Image clone() const 453 { 454 assert(isPlainPixels()); 455 456 Image r; 457 r.setSize(_width, _height, _type, _layoutConstraints); 458 if (r.errored) 459 return r; 460 461 copyPixelsTo(r); 462 return r; 463 } 464 465 /// Copy pixels to an image with same size and type. Both images should have plain pixels. 466 /// Tags: #type #data #plain. 467 void copyPixelsTo(ref Image img) const @trusted 468 { 469 assert(isPlainPixels()); 470 471 assert(img._width == _width); 472 assert(img._height == _height); 473 assert(img._type == _type); 474 475 // PERF: if both are gapless, can do a single memcpy 476 477 int scanlineLen = _width * pixelTypeSize(type); 478 479 const(ubyte)* dataSrc = _data; 480 ubyte* dataDst = img._data; 481 482 for (int y = 0; y < _height; ++y) 483 { 484 dataDst[0..scanlineLen] = dataSrc[0..scanlineLen]; 485 dataSrc += _pitch; 486 dataDst += img._pitch; 487 } 488 } 489 490 // 491 // </INITIALIZE> 492 // 493 494 495 // 496 // <SAVING AND LOADING IMAGES> 497 // 498 499 /// Load an image from a file location. 500 /// 501 /// Params: 502 /// path A string containing the file path. 503 /// flags Flags can contain LOAD_xxx flags and LAYOUT_xxx flags. 504 /// 505 /// Returns: `true` if successfull. The image will be in errored state if there is a problem. 506 /// See_also: `LoadFlags`, `LayoutConstraints`. 507 /// Tags: none. 508 bool loadFromFile(const(char)[] path, int flags = 0) @trusted 509 { 510 cleanupBitmapAndTypeIfAny(); 511 512 CString cstr = CString(path); 513 514 // Deduce format. 515 ImageFormat fif = identifyFormatFromFile(cstr.storage); 516 if (fif == ImageFormat.unknown) 517 { 518 fif = identifyImageFormatFromFilename(cstr.storage); // try to guess the file format from the file extension 519 } 520 521 loadFromFileInternal(fif, cstr.storage, flags); 522 return !errored(); 523 } 524 525 /// Load an image from a memory location. 526 /// 527 /// Params: 528 /// bytes Arrays containing the encoded image to decode. 529 /// flags Flags can contain LOAD_xxx flags and LAYOUT_xxx flags. 530 /// 531 /// Returns: `true` if successfull. The image will be in errored state if there is a problem. 532 /// 533 /// See_also: `LoadFlags`, `LayoutConstraints`. 534 /// Tags: none. 535 bool loadFromMemory(const(ubyte)[] bytes, int flags = 0) @trusted 536 { 537 cleanupBitmapAndTypeIfAny(); 538 539 MemoryFile mem; 540 mem.initFromExistingSlice(bytes); 541 542 // Deduce format. 543 ImageFormat fif = identifyFormatFromMemoryFile(mem); 544 545 IOStream io; 546 io.setupForMemoryIO(); 547 loadFromStreamInternal(fif, io, cast(IOHandle)&mem, flags); 548 549 return !errored(); 550 } 551 ///ditto 552 bool loadFromMemory(const(void)[] bytes, int flags = 0) @trusted 553 { 554 return loadFromMemory(cast(const(ubyte)[])bytes, flags); 555 } 556 557 /// Load an image from a set of user-defined I/O callbacks. 558 /// 559 /// Params: 560 /// fif The target image format. 561 /// io The user-defined callbacks. 562 /// handle A void* user pointer to pass to I/O callbacks. 563 /// flags Flags can contain LOAD_xxx flags and LAYOUT_xxx flags. 564 /// 565 /// Tags: none. 566 bool loadFromStream(ref IOStream io, IOHandle handle, int flags = 0) @system 567 { 568 cleanupBitmapAndTypeIfAny(); 569 570 // Deduce format from stream. 571 ImageFormat fif = identifyFormatFromStream(io, handle); 572 573 loadFromStreamInternal(fif, io, handle, flags); 574 return !errored(); 575 } 576 577 /// Saves an image to a file, detecting the format from the path extension. 578 /// 579 /// Params: 580 /// path The path of output file. 581 /// 582 /// Returns: `true` if file successfully written. 583 /// Tags: none. 584 bool saveToFile(const(char)[] path, int flags = 0) @trusted 585 { 586 assert(!errored); // else, nothing to save 587 CString cstr = CString(path); 588 589 ImageFormat fif = identifyImageFormatFromFilename(cstr.storage); 590 591 return saveToFileInternal(fif, cstr.storage, flags); 592 } 593 /// Save the image into a file, with a given file format. 594 /// 595 /// Params: 596 /// fif The `ImageFormat` to use. 597 /// path The path of output file. 598 /// 599 /// Returns: `true` if file successfully written. 600 /// Tags: none. 601 bool saveToFile(ImageFormat fif, const(char)[] path, int flags = 0) const @trusted 602 { 603 assert(!errored); // else, nothing to save 604 CString cstr = CString(path); 605 return saveToFileInternal(fif, cstr.storage, flags); 606 } 607 608 /// Saves the image into a new memory location. 609 /// The returned data must be released with a call to `free`. 610 /// Returns: `null` if saving failed. 611 /// Warning: this is NOT GC-allocated. 612 /// Tags: none. 613 ubyte[] saveToMemory(ImageFormat fif, int flags = 0) const @trusted 614 { 615 assert(!errored); // else, nothing to save 616 617 // Open stream for read/write access. 618 MemoryFile mem; 619 mem.initEmpty(); 620 621 IOStream io; 622 io.setupForMemoryIO(); 623 if (saveToStream(fif, io, cast(IOHandle)&mem, flags)) 624 return mem.releaseData(); 625 else 626 return null; 627 } 628 629 /// Save an image with a set of user-defined I/O callbacks. 630 /// 631 /// Params: 632 /// fif The `ImageFormat` to use. 633 /// io User-defined stream object. 634 /// handle User provided `void*` pointer passed to the I/O callbacks. 635 /// 636 /// Returns: `true` if file successfully written. 637 /// Tags: none. 638 bool saveToStream(ImageFormat fif, ref IOStream io, IOHandle handle, int flags = 0) const @trusted 639 { 640 assert(!errored); // else, nothing to save 641 642 if (fif == ImageFormat.unknown) 643 { 644 // No format given for save. 645 return false; 646 } 647 648 if (!isPlainPixels) 649 return false; // no data that is pixels, impossible to save that. 650 651 const(ImageFormatPlugin)* plugin = &g_plugins[fif]; 652 void* data = null; // probably exist to pass metadata stuff 653 if (plugin.saveProc is null) 654 return false; 655 bool r = plugin.saveProc(this, &io, handle, 0, flags, data); 656 return r; 657 } 658 659 // 660 // </SAVING AND LOADING IMAGES> 661 // 662 663 664 // 665 // <FILE FORMAT IDENTIFICATION> 666 // 667 668 /// Identify the format of an image by minimally reading it. 669 /// Read first bytes of a file to identify it. 670 /// You can use a filename, a memory location, or your own `IOStream`. 671 /// Returns: Its `ImageFormat`, or `ImageFormat.unknown` in case of identification failure or input error. 672 static ImageFormat identifyFormatFromFile(const(char)*filename) @trusted 673 { 674 FILE* f = fopen(filename, "rb"); 675 if (f is null) 676 return ImageFormat.unknown; 677 IOStream io; 678 io.setupForFileIO(); 679 ImageFormat type = identifyFormatFromStream(io, cast(IOHandle)f); 680 fclose(f); // TODO: Note sure what to do if fclose fails here. 681 return type; 682 } 683 ///ditto 684 static ImageFormat identifyFormatFromMemory(const(ubyte)[] bytes) @trusted 685 { 686 MemoryFile mem; 687 mem.initFromExistingSlice(bytes); 688 return identifyFormatFromMemoryFile(mem); 689 } 690 ///ditto 691 static ImageFormat identifyFormatFromStream(ref IOStream io, IOHandle handle) 692 { 693 for (ImageFormat fif = ImageFormat.first; fif <= ImageFormat.max; ++fif) 694 { 695 if (detectFormatFromStream(fif, io, handle)) 696 return fif; 697 } 698 return ImageFormat.unknown; 699 } 700 701 /// Identify the format of an image by looking at its extension. 702 /// Returns: Its `ImageFormat`, or `ImageFormat.unknown` in case of identification failure or input error. 703 /// Maybe then you can try `identifyFormatFromFile` instead, which minimally reads the input. 704 static ImageFormat identifyFormatFromFileName(const(char) *filename) 705 { 706 return identifyImageFormatFromFilename(filename); 707 } 708 709 // 710 // </FILE FORMAT IDENTIFICATION> 711 // 712 713 714 // 715 // <CONVERSION> 716 // 717 718 /// Get the image layout constraints. 719 /// Tags: none. 720 LayoutConstraints layoutConstraints() pure const 721 { 722 return _layoutConstraints; 723 } 724 725 /// Keep the same pixels and type, but change how they are arranged in memory to fit some constraints. 726 /// Tags: #type 727 bool changeLayout(LayoutConstraints layoutConstraints) 728 { 729 return convertTo(_type, layoutConstraints); 730 } 731 732 /// Convert the image to greyscale, using a greyscale transformation (all channels weighted equally). 733 /// Alpha is preserved if existing. 734 /// Tags: #type 735 bool convertToGreyscale(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 736 { 737 return convertTo( convertPixelTypeToGreyscale(_type), layoutConstraints); 738 } 739 740 /// Convert the image to a greyscale + alpha equivalent, using duplication and/or adding an opaque alpha channel. 741 /// Tags: #type 742 bool convertToGreyscaleAlpha(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 743 { 744 return convertTo( convertPixelTypeToAddAlphaChannel( convertPixelTypeToGreyscale(_type) ), layoutConstraints); 745 } 746 747 /// Convert the image to a RGB equivalent, using duplication if greyscale. 748 /// Alpha is preserved if existing. 749 /// Tags: #type 750 bool convertToRGB(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 751 { 752 return convertTo( convertPixelTypeToRGB(_type), layoutConstraints); 753 } 754 755 /// Convert the image to a RGBA equivalent, using duplication and/or adding an opaque alpha channel. 756 /// Tags: #type 757 bool convertToRGBA(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 758 { 759 return convertTo( convertPixelTypeToAddAlphaChannel( convertPixelTypeToRGB(_type) ), layoutConstraints); 760 } 761 762 /// Add an opaque alpha channel if not-existing already. 763 /// Tags: #type 764 bool addAlphaChannel(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 765 { 766 return convertTo( convertPixelTypeToAddAlphaChannel(_type), layoutConstraints); 767 } 768 769 /// Removes the alpha channel if not-existing already. 770 /// Tags: #type 771 bool dropAlphaChannel(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 772 { 773 return convertTo( convertPixelTypeToDropAlphaChannel(_type), layoutConstraints); 774 } 775 776 /// Convert the image bit-depth to 8-bit per component. 777 /// Tags: #type 778 bool convertTo8Bit(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 779 { 780 return convertTo( convertPixelTypeTo8Bit(_type), layoutConstraints); 781 } 782 783 /// Convert the image bit-depth to 16-bit per component. 784 /// Tags: #type. 785 bool convertTo16Bit(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 786 { 787 return convertTo( convertPixelTypeTo16Bit(_type), layoutConstraints); 788 } 789 790 /// Convert the image bit-depth to 32-bit float per component. 791 /// Tags: #type. 792 bool convertToFP32(LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) 793 { 794 return convertTo( convertPixelTypeToFP32(_type), layoutConstraints); 795 } 796 797 /// Convert the image to the following format. 798 /// This can destruct channels, loose precision, etc. 799 /// You can also change the layout constraints at the same time. 800 /// 801 /// Returns: true on success. 802 /// Tags: #type. 803 bool convertTo(PixelType targetType, LayoutConstraints layoutConstraints = LAYOUT_DEFAULT) @trusted 804 { 805 assert(!errored()); // this should have been caught before. 806 assert(hasType()); 807 808 if (targetType == PixelType.unknown) 809 { 810 error(kStrUnsupportedTypeConversion); 811 return false; 812 } 813 814 // The asked for layout must be valid itself. 815 assert(layoutConstraintsValid(layoutConstraints)); 816 817 if (!hasData()) 818 { 819 _layoutConstraints = layoutConstraints; 820 return true; // success, no pixel data, so everything was "converted", layout constraints do not hold 821 } 822 823 // Detect if the particular hazard of allocation have given the image "ad-hoc" constraints 824 // we didn't strictly require. Typically, if the image is already vertically straight, no need to 825 // reallocate just for that. 826 LayoutConstraints adhocConstraints = getAdHocLayoutConstraints(); 827 828 enum bool useAdHoc = true; // FUTURE: remove once deemed harmless 829 830 // Are the new layout constraints already valid? 831 bool compatibleLayout = layoutConstraintsCompatible(layoutConstraints, useAdHoc ? adhocConstraints : _layoutConstraints); 832 833 if (_type == targetType && compatibleLayout) 834 { 835 // PERF: it would be possible, if the layout only differ for stance with Vflip, to flip 836 // lines in place here. But this can be handled below with reallocation. 837 _layoutConstraints = layoutConstraints; 838 return true; // success, same type already, and compatible constraints 839 } 840 841 if ((width() == 0 || height()) == 0 && compatibleLayout) 842 { 843 // Image dimension is zero, and compatible constraints, everything fine 844 // No need for reallocation or copy. 845 _layoutConstraints = layoutConstraints; 846 return true; 847 } 848 849 ubyte* source = _data; 850 int sourcePitch = _pitch; 851 852 // Do not realloc the same block to avoid invalidating previous data. 853 // We'll manage this manually. 854 assert(_data !is null); 855 856 // PERF: do some conversions in place? if target type is smaller then input, always possible 857 858 // Do we need to convert scanline by scanline, using a scratch buffer? 859 bool needConversionWithIntermediateType = targetType != _type; 860 PixelType interType = intermediateConversionType(_type, targetType); 861 int interBufSize = width * pixelTypeSize(interType); 862 int bonusBytes = needConversionWithIntermediateType ? interBufSize : 0; 863 864 ubyte* dest; // first scanline 865 ubyte* newAllocArea; // the result of realloc-ed 866 int destPitch; 867 bool err; 868 allocatePixelStorage(null, // so that the former allocation keep existing for the copy 869 targetType, 870 width, 871 height, 872 layoutConstraints, 873 bonusBytes, 874 dest, 875 newAllocArea, 876 destPitch, 877 err); 878 879 if (err) 880 { 881 error(kStrOutOfMemory); 882 return false; 883 } 884 885 // Do we need a conversion of just a memcpy? 886 bool ok = false; 887 if (targetType == _type) 888 { 889 ok = copyScanlines(targetType, 890 source, sourcePitch, 891 dest, destPitch, 892 width, height); 893 } 894 else 895 { 896 // Need an intermediate buffer. We allocated one in the new image buffer. 897 // After that conversion, noone will ever talk about it, and the bonus bytes will stay unused. 898 ubyte* interBuf = newAllocArea; 899 900 ok = convertScanlines(_type, source, sourcePitch, 901 targetType, dest, destPitch, 902 width, height, 903 interType, interBuf); 904 } 905 906 if (!ok) 907 { 908 // Keep former image 909 deallocatePixelStorage(newAllocArea); 910 error(kStrUnsupportedTypeConversion); 911 return false; 912 } 913 914 cleanupBitmapAndTypeIfAny(); // forget about former image 915 916 _layoutConstraints = layoutConstraints; 917 _data = dest; 918 _allocArea = newAllocArea; // now own the new one. 919 _type = targetType; 920 _pitch = destPitch; 921 return true; 922 } 923 924 /// Reinterpret cast the image content. 925 /// For example if you want to consider a RGBA8 image to be uint8, but with a 4x larger width. 926 /// This doesn't allocates new data storage. 927 /// 928 /// Warning: This fails if the cast is impossible, for example casting a uint8 image to RGBA8 only 929 /// works if the width is a multiple of 4. 930 /// 931 /// So it is a bit like casting slices in D. 932 /// TODO: castTo breaks layout constraints, what to do with them? 933 /// Tags: #type. 934 bool castTo(PixelType targetType) @trusted 935 { 936 assert(hasType()); 937 assert(!errored()); // this should have been caught before. 938 if (targetType == PixelType.unknown) 939 { 940 error(kStrInvalidPixelTypeCast); 941 return false; 942 } 943 944 if (_type == targetType) 945 return true; // success, nothing to do 946 947 if (!hasData()) 948 { 949 _type = targetType; 950 return true; // success, no pixel data, so everything was "cast" 951 } 952 953 if (width() == 0 || height() == 0) 954 { 955 return true; // image dimension is zero, everything fine 956 } 957 958 // Basically, you can cast if the source type size is a multiple of the dest type. 959 int srcBytes = pixelTypeSize(_type); 960 int destBytes = pixelTypeSize(_type); 961 962 // Byte length of source line. 963 int sourceLineSize = width * srcBytes; 964 assert(sourceLineSize >= 0); 965 966 // Is it dividable by destBytes? If yes, cast is successful. 967 if ( (sourceLineSize % destBytes) == 0) 968 { 969 _width = sourceLineSize / destBytes; 970 _type = targetType; 971 return true; 972 } 973 else 974 { 975 error(kStrInvalidPixelTypeCast); 976 return false; 977 } 978 } 979 980 // 981 // </CONVERSION> 982 // 983 984 985 // 986 // <LAYOUT> 987 // 988 989 /// On how many bytes each scanline is aligned. 990 /// Useful to know for SIMD. 991 /// The actual alignment could be higher than what the layout constraints strictly tells. 992 /// See_also: `LayoutConstraints`. 993 /// Tags: none. 994 int scanlineAlignment() 995 { 996 return layoutScanlineAlignment(_layoutConstraints); 997 } 998 999 /// Get the number of border pixels around the image. 1000 /// This is an area that can be safely accessed, using -pitchInBytes() and pointer offsets. 1001 /// The actual border width could well be higher, but there is no way of safely knowing that. 1002 /// See_also: `LayoutConstraints`. 1003 /// Tags: none. 1004 int borderWidth() pure 1005 { 1006 return layoutBorderWidth(_layoutConstraints); 1007 } 1008 1009 /// Get the multiplicity of pixels in a single scanline. 1010 /// The actual multiplicity could well be higher. 1011 /// See_also: `LayoutConstraints`. 1012 /// Tags: none. 1013 int pixelMultiplicity() 1014 { 1015 return layoutMultiplicity(_layoutConstraints); 1016 } 1017 1018 /// Get the guaranteed number of scanline trailing pixels, from the layout constraints. 1019 /// Each scanline is followed by at least that much out-of-image pixels, that can be safely 1020 /// READ. 1021 /// The actual number of trailing pixels can well be larger than what the layout strictly tells, 1022 /// but we'll never know. 1023 /// See_also: `LayoutConstraints`. 1024 /// Tags: none. 1025 int trailingPixels() pure 1026 { 1027 return layoutTrailingPixels(_layoutConstraints); 1028 } 1029 1030 /// Get if being gapless is guaranteed by the layout constraints. 1031 /// Note that this only holds if there is some data in the first place. 1032 /// See_also: `allPixels()`, `LAYOUT_GAPLESS`, `LAYOUT_VERT_STRAIGHT`. 1033 /// Tags: none. 1034 bool isGapless() pure const 1035 { 1036 return layoutGapless(_layoutConstraints); 1037 } 1038 1039 /// Returns: `true` is the image is constrained to be stored upside-down. 1040 /// Tags: none. 1041 bool mustBeStoredUpsideDown() pure const 1042 { 1043 return (_layoutConstraints & LAYOUT_VERT_FLIPPED) != 0; 1044 } 1045 1046 /// Returns: `true` is the image is constrained to NOT be stored upside-down. 1047 /// Tags: none. 1048 bool mustNotBeStoredUpsideDown() pure const 1049 { 1050 return (_layoutConstraints & LAYOUT_VERT_STRAIGHT) != 0; 1051 } 1052 1053 // 1054 // </LAYOUT> 1055 // 1056 1057 // 1058 // <TRANSFORM> 1059 // 1060 1061 /// Flip the image data horizontally. 1062 /// If the image has no data, the operation is successful. 1063 /// Tags: #type. 1064 bool flipHorizontally() pure @trusted 1065 { 1066 assert(hasType()); 1067 1068 if (!hasData()) 1069 return true; // Nothing to do 1070 1071 ubyte[GAMUT_MAX_PIXEL_SIZE] temp; 1072 1073 int W = width(); 1074 int H = height(); 1075 int Xdiv2 = W / 2; 1076 int scanBytes = scanlineInBytes(); 1077 int psize = pixelTypeSize(type); 1078 1079 // Stupid pixel per pixel swap 1080 for (int y = 0; y < H; ++y) 1081 { 1082 ubyte* scan = scanline(y); 1083 for (int x = 0; x < Xdiv2; ++x) 1084 { 1085 ubyte* pixelA = &scan[x * psize]; 1086 ubyte* pixelB = &scan[(W - 1 - x) * psize]; 1087 temp[0..psize] = pixelA[0..psize]; 1088 pixelA[0..psize] = pixelB[0..psize]; 1089 pixelB[0..psize] = temp[0..psize]; 1090 } 1091 } 1092 return true; 1093 } 1094 1095 /// Flip the image vertically. 1096 /// If the image has no data, the operation is successful. 1097 /// 1098 /// - If the layout allows it, `flipVerticallyLogical` is called. The scanline pointers are 1099 /// inverted, and pitch is negated. This just flips the "view" of the image. 1100 /// 1101 /// - If there is a constraint to keep the image strictly upside-down, or strictly not 1102 /// upside-down, then `flipVerticallyPhysical` is called instead. 1103 /// 1104 /// Returns: `true` on success, sets an error else and return `false`. 1105 /// Tags: #type. 1106 bool flipVertically() pure 1107 { 1108 assert(hasType()); 1109 1110 if (mustBeStoredUpsideDown() || mustNotBeStoredUpsideDown()) 1111 return flipVerticallyPhysical(); 1112 else 1113 return flipVerticallyLogical(); 1114 } 1115 ///ditto 1116 bool flipVerticallyLogical() pure @trusted 1117 { 1118 if (!hasData()) 1119 return true; // Nothing to do 1120 1121 if (mustBeStoredUpsideDown() || mustNotBeStoredUpsideDown()) 1122 { 1123 error(kStrUnsupportedVFlip); 1124 return false; 1125 } 1126 1127 // Note: flipping the image preserve all layout properties! 1128 // What a nice design here. 1129 // Border, trailing pixels, scanline alignment... they all survive vertical flip. 1130 flipScanlinePointers(_width, _height, _data, _pitch); 1131 1132 return true; 1133 } 1134 ///ditto 1135 bool flipVerticallyPhysical() pure @trusted 1136 { 1137 if (!hasData()) 1138 return true; // Nothing to do 1139 1140 int H = height(); 1141 int Ydiv2 = H / 2; 1142 int scanBytes = scanlineInBytes(); 1143 1144 // Stupid byte per byte swap 1145 for (int y = 0; y < Ydiv2; ++y) 1146 { 1147 ubyte* scanA = scanline(y); 1148 ubyte* scanB = scanline(H - 1 - y); 1149 for (int b = 0; b < scanBytes; ++b) 1150 { 1151 ubyte ch = scanA[b]; 1152 scanA[b] = scanB[b]; 1153 scanB[b] = ch; 1154 } 1155 } 1156 return true; 1157 } 1158 1159 // 1160 // </TRANSFORM> 1161 // 1162 1163 @disable this(this); // Non-copyable. This would clone the image, and be expensive. 1164 1165 1166 /// Destructor. Everything is reclaimed. 1167 ~this() 1168 { 1169 cleanupBitmapAndTypeIfAny(); 1170 } 1171 1172 package: 1173 1174 // Available only inside gamut. 1175 1176 /// Clear the error, if any. This is only for use inside Gamut. 1177 /// Each operations that "recreates" the image, such a loading, clear the existing error and leave 1178 /// the Image in a clean-up state. 1179 void clearError() pure 1180 { 1181 _error = null; 1182 } 1183 1184 /// Set the image in an errored state, with `msg` as a message. 1185 /// Note: `msg` MUST be zero-terminated. 1186 void error(const(char)[] msg) pure 1187 { 1188 assert(msg !is null); 1189 _error = assumeZeroTerminated(msg); 1190 } 1191 1192 /// The type of the data pointed to. 1193 PixelType _type = PixelType.unknown; 1194 1195 /// The data layout constraints, in flags. 1196 /// See_also: `LayoutConstraints`. 1197 LayoutConstraints _layoutConstraints = LAYOUT_DEFAULT; 1198 1199 /// Pointer to the pixel data. What is pointed to depends on `_type`. 1200 /// The amount of what is pointed to depends upon the dimensions. 1201 /// it is possible to have `_data` null but `_type` is known. 1202 ubyte* _data = null; 1203 1204 /// Pointer to the `malloc` area holding the data. 1205 /// _allocArea being null signify that there is no data, or that the data is borrowed. 1206 /// _allocArea not being null signify that the image is owning its data. 1207 ubyte* _allocArea = null; 1208 1209 /// Width of the image in pixels, when pixels makes sense. 1210 /// By default, this width is 0 (but as the image has no pixel data, this doesn't matter). 1211 int _width = 0; 1212 1213 /// Height of the image in pixels, when pixels makes sense. 1214 /// By default, this height is 0 (but as the image has no pixel data, this doesn't matter). 1215 int _height = 0; 1216 1217 /// Pitch in bytes between lines, when a pitch makes sense. This pitch can be, or not be, a negative integer. 1218 /// When the image has layout constraint LAYOUT_VERT_FLIPPED, it is always kept <= 0. 1219 /// When the image has layout constraint LAYOUT_VERT_STRAIGHT, it is always kept >= 0. 1220 int _pitch = 0; 1221 1222 /// Pointer to last known error. `null` means "no errors". 1223 /// Once an error has occured, continuing to use the image is Undefined Behaviour. 1224 /// Must be zero-terminated. 1225 /// By default, a T.init image is errored(). 1226 const(char)* _error = kStrImageNotInitialized; 1227 1228 /// Pixel aspect ratio. 1229 /// https://en.wikipedia.org/wiki/Pixel_aspect_ratio 1230 float _pixelAspectRatio = GAMUT_UNKNOWN_ASPECT_RATIO; 1231 1232 /// Physical image resolution in vertical pixel-per-inch. 1233 float _resolutionY = GAMUT_UNKNOWN_RESOLUTION; 1234 1235 private: 1236 1237 /// Compute a suitable pitch when making an image. 1238 /// FUTURE some flags that change alignment constraints? 1239 deprecated int computePitch(PixelType type, int width) 1240 { 1241 return width * pixelTypeSize(type); 1242 } 1243 1244 void cleanupBitmapAndTypeIfAny() @safe 1245 { 1246 cleanupBitmapIfAny(); 1247 cleanupTypeIfAny(); 1248 } 1249 1250 void cleanupBitmapIfAny() @trusted 1251 { 1252 cleanupBitmapIfOwned(); 1253 _data = null; 1254 assert(!hasData()); 1255 } 1256 1257 void cleanupTypeIfAny() 1258 { 1259 _type = PixelType.unknown; 1260 assert(!hasType()); 1261 } 1262 1263 // If owning an allocation, free it, else keep it. 1264 void cleanupBitmapIfOwned() @trusted 1265 { 1266 if (isOwned()) 1267 { 1268 deallocatePixelStorage(_allocArea); 1269 _allocArea = null; 1270 _data = null; 1271 } 1272 } 1273 1274 /// Discard ancient data, and reallocate stuff. 1275 /// Returns true on success, false on OOM. 1276 /// When failing, sets the errored state. 1277 bool setStorage(int width, 1278 int height, 1279 PixelType type, 1280 LayoutConstraints constraints) @trusted 1281 { 1282 if (!layoutConstraintsValid(constraints)) 1283 { 1284 error(kStrIllegalLayoutConstraints); 1285 return false; 1286 } 1287 1288 ubyte* dataPointer; 1289 ubyte* mallocArea; 1290 int pitchBytes; 1291 bool err; 1292 1293 allocatePixelStorage(_allocArea, 1294 type, 1295 width, 1296 height, 1297 constraints, 1298 0, 1299 dataPointer, 1300 mallocArea, 1301 pitchBytes, 1302 err); 1303 if (err) 1304 { 1305 error(kStrOutOfMemory); 1306 return false; 1307 } 1308 1309 _data = dataPointer; 1310 _allocArea = mallocArea; 1311 _type = type; 1312 _width = width; 1313 _height = height; 1314 _pitch = pitchBytes; 1315 _layoutConstraints = constraints; 1316 return true; 1317 } 1318 1319 void loadFromFileInternal(ImageFormat fif, const(char)* filename, int flags = 0) @system 1320 { 1321 FILE* f = fopen(filename, "rb"); 1322 if (f is null) 1323 { 1324 error(kStrCannotOpenFile); 1325 return; 1326 } 1327 1328 IOStream io; 1329 io.setupForFileIO(); 1330 loadFromStreamInternal(fif, io, cast(IOHandle)f, flags); 1331 1332 if (0 != fclose(f)) 1333 { 1334 // TODO cleanup image? 1335 error(kStrFileCloseFailed); 1336 } 1337 } 1338 1339 void loadFromStreamInternal(ImageFormat fif, ref IOStream io, IOHandle handle, int flags = 0) @system 1340 { 1341 // By loading an image, we agreed to forget about past mistakes. 1342 clearError(); 1343 1344 if (fif == ImageFormat.unknown) 1345 { 1346 error(kStrImageFormatUnidentified); 1347 return; 1348 } 1349 1350 const(ImageFormatPlugin)* plugin = &g_plugins[fif]; 1351 1352 int page = 0; 1353 void *data = null; 1354 if (plugin.loadProc is null) 1355 { 1356 error(kStrImageFormatNoLoadSupport); 1357 return; 1358 } 1359 plugin.loadProc(this, &io, handle, page, flags, data); 1360 } 1361 1362 bool saveToFileInternal(ImageFormat fif, const(char)* filename, int flags = 0) const @trusted 1363 { 1364 FILE* f = fopen(filename, "wb"); 1365 if (f is null) 1366 return false; 1367 1368 IOStream io; 1369 io.setupForFileIO(); 1370 bool r = saveToStream(fif, io, cast(IOHandle)f, flags); 1371 bool fcloseOK = fclose(f) == 0; 1372 return r && fcloseOK; 1373 } 1374 1375 static ImageFormat identifyFormatFromMemoryFile(ref MemoryFile mem) @trusted 1376 { 1377 IOStream io; 1378 io.setupForMemoryIO(); 1379 return identifyFormatFromStream(io, cast(IOHandle)&mem); 1380 } 1381 1382 static bool detectFormatFromStream(ImageFormat fif, ref IOStream io, IOHandle handle) @trusted 1383 { 1384 assert(fif != ImageFormat.unknown); 1385 const(ImageFormatPlugin)* plugin = &g_plugins[fif]; 1386 assert(plugin.detectProc !is null); 1387 if (plugin.detectProc(&io, handle)) 1388 return true; 1389 return false; 1390 } 1391 1392 // When we look at this Image, what are some constraints that it could spontaneously follow? 1393 // Also look at existing _layoutConstraints. 1394 // Params: 1395 // preferGapless Generates a LayoutConstraints with LAYOUT_GAPLESS rather than other things. 1396 // 1397 // Warning: the LayoutConstraints it returns is not necessarilly user-valid, it can contain both 1398 // scanline alignment and gapless constraints. This should NEVER be kept as actual constraints. 1399 LayoutConstraints getAdHocLayoutConstraints() 1400 { 1401 assert(hasData()); 1402 1403 // An image that doesn't own its data can't infer some adhoc constraints, or the conditions are stricter. 1404 bool owned = isOwned; 1405 1406 int pitch = pitchInBytes(); 1407 int absPitch = pitch >= 0 ? pitch : -pitch; 1408 int scanLen = scanlineInBytes(); 1409 int pixelSize = pixelTypeSize(type); 1410 int width = _width; 1411 int excessBytes = scanLen - absPitch; 1412 int excessPixels = excessBytes / pixelSize; 1413 assert(excessBytes >= 0 && excessPixels >= 0); 1414 1415 LayoutConstraints c = 0; 1416 1417 // Multiplicity constraint: take largest of inferred, and _layoutConstraints-related. 1418 { 1419 int multi = pixelMultiplicity(); // as much is guaranteed by the _constraint 1420 1421 // the multiplicity inferred by looking at how many pixel can fit at the end of the scanline 1422 int inferredWithGap = 1; 1423 if (excessPixels >= 7) 1424 inferredWithGap = 8; 1425 else if (excessPixels >= 3) 1426 inferredWithGap = 4; 1427 else if (excessPixels >= 1) 1428 inferredWithGap = 2; 1429 1430 // the multiplicity inferred by looking at width divisibility 1431 // Slight note: this is not fully complete, a 2-width + 2 trailing pixels => 4-multiplicity 1432 int inferredWithWidth = 1; 1433 if ( (width % 2) == 0) inferredWithWidth = 2; 1434 if ( (width % 4) == 0) inferredWithWidth = 4; 1435 if ( (width % 8) == 0) inferredWithWidth = 8; 1436 1437 // take max 1438 if (multi < inferredWithGap) multi = inferredWithGap; 1439 if (multi < inferredWithWidth) multi = inferredWithWidth; 1440 assert(multi == 1 || multi == 2 || multi == 4 || multi == 8); 1441 1442 if (multi == 8) 1443 c |= LAYOUT_MULTIPLICITY_8; 1444 else if (multi == 4) 1445 c |= LAYOUT_MULTIPLICITY_4; 1446 else if (multi == 2) 1447 c |= LAYOUT_MULTIPLICITY_2; 1448 } 1449 1450 // Trailing bytes constraint: infer is the largest, no need to look at _layoutConstraints. 1451 { 1452 if (excessPixels >= 7) 1453 c |= LAYOUT_TRAILING_7; 1454 else if (excessPixels >= 3) 1455 c |= LAYOUT_TRAILING_3; 1456 else if (excessPixels >= 1) 1457 c |= LAYOUT_TRAILING_1; 1458 } 1459 1460 // scanline alignment: infer is the largest, since the constraints shows in pitch and pointer address 1461 { 1462 LayoutConstraints firstScanAlign = getPointerAlignment(cast(size_t)_data); 1463 LayoutConstraints pitchAlign = getPointerAlignment(cast(size_t)absPitch); 1464 LayoutConstraints allScanlinesAlign = firstScanAlign < pitchAlign ? firstScanAlign : pitchAlign; 1465 c |= allScanlinesAlign; 1466 } 1467 1468 // vertical 1469 if (pitch >= 0) 1470 c |= LAYOUT_VERT_STRAIGHT; 1471 if (pitch <= 0) 1472 c |= LAYOUT_VERT_FLIPPED; 1473 1474 // gapless 1475 if (pitch == absPitch) 1476 c |= LAYOUT_GAPLESS; 1477 1478 // Border constraint: can only trust the _constraint. Cannot infer more. 1479 c |= (_layoutConstraints & LAYOUT_BORDER_MASK); 1480 1481 return c; 1482 } 1483 } 1484 1485 1486 private: 1487 1488 // FUTURE: this will also manage color conversion. 1489 PixelType intermediateConversionType(PixelType srcType, PixelType destType) 1490 { 1491 if (pixelTypeExpressibleInRGBA8(srcType) && pixelTypeExpressibleInRGBA8(destType)) 1492 return PixelType.rgba8; 1493 1494 return PixelType.rgbaf32; 1495 } 1496 1497 // This converts scanline per scanline, using an intermediate format to lessen the number of conversions. 1498 bool convertScanlines(PixelType srcType, const(ubyte)* src, int srcPitch, 1499 PixelType destType, ubyte* dest, int destPitch, 1500 int width, int height, 1501 PixelType interType, ubyte* interBuf) @system 1502 { 1503 assert(srcType != destType); 1504 assert(srcType != PixelType.unknown && destType != PixelType.unknown); 1505 1506 if (pixelTypeIsPlanar(srcType) || pixelTypeIsPlanar(destType)) 1507 return false; // No support 1508 if (pixelTypeIsCompressed(srcType) || pixelTypeIsCompressed(destType)) 1509 return false; // No support 1510 1511 if (srcType == interType) 1512 { 1513 // Source type is already in the intermediate type format. 1514 // Do not use the interbuf. 1515 for (int y = 0; y < height; ++y) 1516 { 1517 convertFromIntermediate(srcType, src, destType, dest, width); 1518 src += srcPitch; 1519 dest += destPitch; 1520 } 1521 } 1522 else if (destType == interType) 1523 { 1524 // Destination type is the intermediate type. 1525 // Do not use the interbuf. 1526 for (int y = 0; y < height; ++y) 1527 { 1528 convertToIntermediateScanline(srcType, src, destType, dest, width); 1529 src += srcPitch; 1530 dest += destPitch; 1531 } 1532 } 1533 else 1534 { 1535 // For each scanline 1536 for (int y = 0; y < height; ++y) 1537 { 1538 convertToIntermediateScanline(srcType, src, interType, interBuf, width); 1539 convertFromIntermediate(interType, interBuf, destType, dest, width); 1540 src += srcPitch; 1541 dest += destPitch; 1542 } 1543 } 1544 return true; 1545 } 1546 1547 // This copy scanline per scanline of the same type 1548 bool copyScanlines(PixelType type, 1549 const(ubyte)* src, int srcPitch, 1550 ubyte* dest, int destPitch, 1551 int width, int height) @system 1552 { 1553 if (pixelTypeIsPlanar(type)) 1554 return false; // No support 1555 if (pixelTypeIsCompressed(type)) 1556 return false; // No support 1557 1558 int scanlineBytes = pixelTypeSize(type) * width; 1559 for (int y = 0; y < height; ++y) 1560 { 1561 dest[0..scanlineBytes] = src[0..scanlineBytes]; 1562 src += srcPitch; 1563 dest += destPitch; 1564 } 1565 return true; 1566 } 1567 1568 1569 /// See_also: OpenGL ES specification 2.3.5.1 and 2.3.5.2 for details about converting from 1570 /// floating-point to integers, and the other way around. 1571 void convertToIntermediateScanline(PixelType srcType, 1572 const(ubyte)* src, 1573 PixelType dstType, 1574 ubyte* dest, int width) @system 1575 { 1576 if (dstType == PixelType.rgba8) 1577 { 1578 ubyte* outb = dest; 1579 switch(srcType) with (PixelType) 1580 { 1581 case l8: 1582 { 1583 for (int x = 0; x < width; ++x) 1584 { 1585 ubyte b = src[x]; 1586 *outb++ = b; 1587 *outb++ = b; 1588 *outb++ = b; 1589 *outb++ = 255; 1590 } 1591 break; 1592 } 1593 case la8: 1594 { 1595 for (int x = 0; x < width; ++x) 1596 { 1597 ubyte b = src[x*2]; 1598 *outb++ = b; 1599 *outb++ = b; 1600 *outb++ = b; 1601 *outb++ = src[x*2+1]; 1602 } 1603 break; 1604 } 1605 case rgb8: 1606 { 1607 for (int x = 0; x < width; ++x) 1608 { 1609 *outb++ = src[x*3+0]; 1610 *outb++ = src[x*3+1]; 1611 *outb++ = src[x*3+2]; 1612 *outb++ = 255; 1613 } 1614 break; 1615 } 1616 case rgba8: 1617 { 1618 for (int x = 0; x < width; ++x) 1619 { 1620 *outb++ = src[x*4+0]; 1621 *outb++ = src[x*4+1]; 1622 *outb++ = src[x*4+2]; 1623 *outb++ = src[x*4+3]; 1624 } 1625 break; 1626 } 1627 1628 default: 1629 assert(false); // should not use rgba8 as intermediate type 1630 } 1631 } 1632 else if (dstType == PixelType.rgbaf32) 1633 { 1634 float* outp = cast(float*) dest; 1635 1636 final switch(srcType) with (PixelType) 1637 { 1638 case unknown: assert(false); 1639 case l8: 1640 { 1641 const(ubyte)* s = src; 1642 for (int x = 0; x < width; ++x) 1643 { 1644 float b = s[x] / 255.0f; 1645 *outp++ = b; 1646 *outp++ = b; 1647 *outp++ = b; 1648 *outp++ = 1.0f; 1649 } 1650 break; 1651 } 1652 case l16: 1653 { 1654 const(ushort)* s = cast(const(ushort)*) src; 1655 for (int x = 0; x < width; ++x) 1656 { 1657 float b = s[x] / 65535.0f; 1658 *outp++ = b; 1659 *outp++ = b; 1660 *outp++ = b; 1661 *outp++ = 1.0f; 1662 } 1663 break; 1664 } 1665 case lf32: 1666 { 1667 const(float)* s = cast(const(float)*) src; 1668 for (int x = 0; x < width; ++x) 1669 { 1670 float b = s[x]; 1671 *outp++ = b; 1672 *outp++ = b; 1673 *outp++ = b; 1674 *outp++ = 1.0f; 1675 } 1676 break; 1677 } 1678 case la8: 1679 { 1680 const(ubyte)* s = src; 1681 for (int x = 0; x < width; ++x) 1682 { 1683 float b = *s++ / 255.0f; 1684 float a = *s++ / 255.0f; 1685 *outp++ = b; 1686 *outp++ = b; 1687 *outp++ = b; 1688 *outp++ = a; 1689 } 1690 break; 1691 } 1692 case la16: 1693 { 1694 const(ushort)* s = cast(const(ushort)*) src; 1695 for (int x = 0; x < width; ++x) 1696 { 1697 float b = *s++ / 65535.0f; 1698 float a = *s++ / 65535.0f; 1699 *outp++ = b; 1700 *outp++ = b; 1701 *outp++ = b; 1702 *outp++ = a; 1703 } 1704 break; 1705 } 1706 case laf32: 1707 { 1708 const(float)* s = cast(const(float)*) src; 1709 for (int x = 0; x < width; ++x) 1710 { 1711 float b = *s++; 1712 float a = *s++; 1713 *outp++ = b; 1714 *outp++ = b; 1715 *outp++ = b; 1716 *outp++ = a; 1717 } 1718 break; 1719 } 1720 case rgb8: 1721 { 1722 const(ubyte)* s = src; 1723 for (int x = 0; x < width; ++x) 1724 { 1725 float r = *s++ / 255.0f; 1726 float g = *s++ / 255.0f; 1727 float b = *s++ / 255.0f; 1728 *outp++ = r; 1729 *outp++ = g; 1730 *outp++ = b; 1731 *outp++ = 1.0f; 1732 } 1733 break; 1734 } 1735 case rgb16: 1736 { 1737 const(ushort)* s = cast(const(ushort)*) src; 1738 for (int x = 0; x < width; ++x) 1739 { 1740 float r = *s++ / 65535.0f; 1741 float g = *s++ / 65535.0f; 1742 float b = *s++ / 65535.0f; 1743 *outp++ = r; 1744 *outp++ = g; 1745 *outp++ = b; 1746 *outp++ = 1.0f; 1747 } 1748 break; 1749 } 1750 case rgbf32: 1751 { 1752 const(float)* s = cast(const(float)*) src; 1753 for (int x = 0; x < width; ++x) 1754 { 1755 float r = *s++; 1756 float g = *s++; 1757 float b = *s++; 1758 *outp++ = r; 1759 *outp++ = g; 1760 *outp++ = b; 1761 *outp++ = 1.0f; 1762 } 1763 break; 1764 } 1765 case rgba8: 1766 { 1767 const(ubyte)* s = src; 1768 for (int x = 0; x < width; ++x) 1769 { 1770 float r = *s++ / 255.0f; 1771 float g = *s++ / 255.0f; 1772 float b = *s++ / 255.0f; 1773 float a = *s++ / 255.0f; 1774 *outp++ = r; 1775 *outp++ = g; 1776 *outp++ = b; 1777 *outp++ = a; 1778 } 1779 break; 1780 } 1781 case rgba16: 1782 { 1783 const(ushort)* s = cast(const(ushort)*) src; 1784 for (int x = 0; x < width; ++x) 1785 { 1786 float r = *s++ / 65535.0f; 1787 float g = *s++ / 65535.0f; 1788 float b = *s++ / 65535.0f; 1789 float a = *s++ / 65535.0f; 1790 *outp++ = r; 1791 *outp++ = g; 1792 *outp++ = b; 1793 *outp++ = a; 1794 } 1795 break; 1796 } 1797 case rgbaf32: 1798 { 1799 const(float)* s = cast(const(float)*) src; 1800 for (int x = 0; x < width; ++x) 1801 { 1802 float r = *s++; 1803 float g = *s++; 1804 float b = *s++; 1805 float a = *s++; 1806 *outp++ = r; 1807 *outp++ = g; 1808 *outp++ = b; 1809 *outp++ = a; 1810 } 1811 break; 1812 } 1813 } 1814 } 1815 else 1816 assert(false); 1817 1818 } 1819 1820 void convertFromIntermediate(PixelType srcType, const(ubyte)* src, PixelType dstType, ubyte* dest, int width) @system 1821 { 1822 if (srcType == PixelType.rgba8) 1823 { 1824 alias inp = src; 1825 switch(dstType) with (PixelType) 1826 { 1827 case l8: 1828 { 1829 for (int x = 0; x < width; ++x) 1830 dest[x] = inp[4*x]; 1831 break; 1832 } 1833 case la8: 1834 { 1835 for (int x = 0; x < width; ++x) 1836 { 1837 dest[2*x+0] = inp[4*x+0]; 1838 dest[2*x+1] = inp[4*x+3]; 1839 } 1840 break; 1841 } 1842 case rgb8: 1843 { 1844 for (int x = 0; x < width; ++x) 1845 { 1846 dest[3*x+0] = inp[4*x+0]; 1847 dest[3*x+1] = inp[4*x+1]; 1848 dest[3*x+2] = inp[4*x+2]; 1849 } 1850 break; 1851 } 1852 case rgba8: 1853 { 1854 for (int x = 0; x < width; ++x) 1855 { 1856 dest[4*x+0] = inp[4*x+0]; 1857 dest[4*x+1] = inp[4*x+1]; 1858 dest[4*x+2] = inp[4*x+2]; 1859 dest[4*x+3] = inp[4*x+3]; 1860 } 1861 break; 1862 } 1863 1864 default: 1865 assert(false); // should not use rgba8 as intermediate type 1866 } 1867 } 1868 else if (srcType == PixelType.rgbaf32) 1869 { 1870 const(float)* inp = cast(const(float)*) src; 1871 1872 final switch(dstType) with (PixelType) 1873 { 1874 case unknown: assert(false); 1875 case l8: 1876 { 1877 ubyte* s = dest; 1878 for (int x = 0; x < width; ++x) 1879 { 1880 ubyte b = cast(ubyte)(0.5f + (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) * 255.0f / 3.0f); 1881 *s++ = b; 1882 } 1883 break; 1884 } 1885 case l16: 1886 { 1887 ushort* s = cast(ushort*) dest; 1888 for (int x = 0; x < width; ++x) 1889 { 1890 ushort b = cast(ushort)(0.5f + (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) * 65535.0f / 3.0f); 1891 *s++ = b; 1892 } 1893 break; 1894 } 1895 case lf32: 1896 { 1897 float* s = cast(float*) dest; 1898 for (int x = 0; x < width; ++x) 1899 { 1900 float b = (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) / 3.0f; 1901 *s++ = b; 1902 } 1903 break; 1904 } 1905 case la8: 1906 { 1907 ubyte* s = dest; 1908 for (int x = 0; x < width; ++x) 1909 { 1910 ubyte b = cast(ubyte)(0.5f + (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) * 255.0f / 3.0f); 1911 ubyte a = cast(ubyte)(0.5f + inp[4*x+3] * 255.0f); 1912 *s++ = b; 1913 *s++ = a; 1914 } 1915 break; 1916 } 1917 case la16: 1918 { 1919 ushort* s = cast(ushort*) dest; 1920 for (int x = 0; x < width; ++x) 1921 { 1922 ushort b = cast(ushort)(0.5f + (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) * 65535.0f / 3.0f); 1923 ushort a = cast(ushort)(0.5f + inp[4*x+3] * 65535.0f); 1924 *s++ = b; 1925 *s++ = a; 1926 } 1927 break; 1928 } 1929 case laf32: 1930 { 1931 float* s = cast(float*) dest; 1932 for (int x = 0; x < width; ++x) 1933 { 1934 float b = (inp[4*x+0] + inp[4*x+1] + inp[4*x+2]) / 3.0f; 1935 float a = inp[4*x+3]; 1936 *s++ = b; 1937 *s++ = a; 1938 } 1939 break; 1940 } 1941 case rgb8: 1942 { 1943 ubyte* s = dest; 1944 for (int x = 0; x < width; ++x) 1945 { 1946 ubyte r = cast(ubyte)(0.5f + inp[4*x+0] * 255.0f); 1947 ubyte g = cast(ubyte)(0.5f + inp[4*x+1] * 255.0f); 1948 ubyte b = cast(ubyte)(0.5f + inp[4*x+2] * 255.0f); 1949 *s++ = r; 1950 *s++ = g; 1951 *s++ = b; 1952 } 1953 break; 1954 } 1955 case rgb16: 1956 { 1957 ushort* s = cast(ushort*) dest; 1958 for (int x = 0; x < width; ++x) 1959 { 1960 ushort r = cast(ushort)(0.5f + inp[4*x+0] * 65535.0f); 1961 ushort g = cast(ushort)(0.5f + inp[4*x+1] * 65535.0f); 1962 ushort b = cast(ushort)(0.5f + inp[4*x+2] * 65535.0f); 1963 *s++ = r; 1964 *s++ = g; 1965 *s++ = b; 1966 } 1967 break; 1968 } 1969 case rgbf32: 1970 { 1971 float* s = cast(float*) dest; 1972 for (int x = 0; x < width; ++x) 1973 { 1974 *s++ = inp[4*x+0]; 1975 *s++ = inp[4*x+1]; 1976 *s++ = inp[4*x+2]; 1977 } 1978 break; 1979 } 1980 case rgba8: 1981 { 1982 ubyte* s = dest; 1983 for (int x = 0; x < width; ++x) 1984 { 1985 ubyte r = cast(ubyte)(0.5f + inp[4*x+0] * 255.0f); 1986 ubyte g = cast(ubyte)(0.5f + inp[4*x+1] * 255.0f); 1987 ubyte b = cast(ubyte)(0.5f + inp[4*x+2] * 255.0f); 1988 ubyte a = cast(ubyte)(0.5f + inp[4*x+3] * 255.0f); 1989 *s++ = r; 1990 *s++ = g; 1991 *s++ = b; 1992 *s++ = a; 1993 } 1994 break; 1995 } 1996 case rgba16: 1997 { 1998 ushort* s = cast(ushort*)dest; 1999 for (int x = 0; x < width; ++x) 2000 { 2001 ushort r = cast(ushort)(0.5f + inp[4*x+0] * 65535.0f); 2002 ushort g = cast(ushort)(0.5f + inp[4*x+1] * 65535.0f); 2003 ushort b = cast(ushort)(0.5f + inp[4*x+2] * 65535.0f); 2004 ushort a = cast(ushort)(0.5f + inp[4*x+3] * 65535.0f); 2005 *s++ = r; 2006 *s++ = g; 2007 *s++ = b; 2008 *s++ = a; 2009 } 2010 break; 2011 } 2012 case rgbaf32: 2013 { 2014 float* s = cast(float*) dest; 2015 for (int x = 0; x < width; ++x) 2016 { 2017 *s++ = inp[4*x+0]; 2018 *s++ = inp[4*x+1]; 2019 *s++ = inp[4*x+2]; 2020 *s++ = inp[4*x+3]; 2021 } 2022 break; 2023 } 2024 } 2025 } 2026 else 2027 assert(false); 2028 } 2029 2030 2031 // Test gapless pixel access 2032 unittest 2033 { 2034 Image image; 2035 image.setSize(16, 16, PixelType.rgba8, LAYOUT_GAPLESS | LAYOUT_VERT_STRAIGHT); 2036 assert(image.isGapless); 2037 2038 ubyte[] all = image.allPixelsAtOnce(); 2039 assert(all !is null); 2040 } 2041 2042 // Semantics for image without pixel data type. 2043 // You can do very little with it apart from calling an initializing function. 2044 unittest 2045 { 2046 Image image; 2047 2048 // An image that is uninitialized as pixel type. 2049 assert(image.type() == PixelType.unknown); 2050 assert(!image.hasType()); 2051 2052 // No type implies "no-data". 2053 assert(!image.hasData()); 2054 2055 // You can load an image. If it fails, it will have no type. 2056 image.loadFromFile("unkonwn-special-file"); 2057 assert(!image.hasType()); 2058 assert(image.errored()); 2059 assert(!image.hasData()); 2060 2061 assert(!image.hasPlainPixels()); 2062 assert(!image.isPlanar()); 2063 assert(!image.isCompressed()); 2064 } 2065 2066 // Semantics for image without data (but with a type). 2067 unittest 2068 { 2069 Image image; 2070 image.initWithNoData(450, 614, PixelType.rgba8); 2071 assert(!image.hasData()); 2072 assert(!image.isOwned()); 2073 assert(image.hasType()); 2074 assert(image.width == 450); 2075 assert(image.height == 614); 2076 assert(image.hasType()); 2077 assert(!image.errored()); 2078 assert(!image.hasData()); 2079 assert(!image.hasPlainPixels()); 2080 assert(!image.isPlanar()); 2081 assert(!image.isCompressed()); 2082 assert(image.hasNonZeroSize()); 2083 } 2084 2085 // Semantics for image with plain pixels 2086 unittest 2087 { 2088 Image image; 2089 image.setSize(3, 5, PixelType.rgba8); 2090 assert(image.hasType()); 2091 assert(image.isOwned()); 2092 assert(image.width == 3); 2093 assert(image.height == 5); 2094 assert(image.hasType()); 2095 assert(!image.errored()); 2096 assert(image.hasData()); 2097 assert(image.hasPlainPixels()); 2098 assert(!image.isPlanar()); 2099 assert(!image.isCompressed()); 2100 assert(image.hasNonZeroSize()); 2101 image.convertTo16Bit(); 2102 Image B = image.clone(); 2103 } 2104 2105 // Semantics for image with plain pixels, but with zero width and height. 2106 // Basically all operations are available to it. 2107 unittest 2108 { 2109 Image image; 2110 image.setSize(0, 0, PixelType.rgba8); 2111 2112 static void zeroSizeChecks(ref Image image) @safe 2113 { 2114 assert(image.hasType()); 2115 assert(image.isOwned()); 2116 assert(image.width == 0); 2117 assert(image.height == 0); 2118 assert(image.hasType()); 2119 assert(!image.errored()); 2120 assert(image.hasData()); // It has data, just, it has a zero size. 2121 assert(image.hasPlainPixels()); 2122 assert(!image.isPlanar()); 2123 assert(!image.isCompressed()); 2124 assert(!image.hasNonZeroSize()); 2125 } 2126 zeroSizeChecks(image); 2127 image.convertTo16Bit(); 2128 zeroSizeChecks(image); 2129 Image B = image.clone(); 2130 zeroSizeChecks(B); 2131 }