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