#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_PIXELS_PIXELITERATOR_H #define NUCLEX_PIXELS_PIXELITERATOR_H #include "Nuclex/Pixels/Config.h" #include "Nuclex/Pixels/BitmapMemory.h" #include // for std::ptrdiff_t #include // for std::random_access_iterator_tag namespace Nuclex { namespace Pixels { // ------------------------------------------------------------------------------------------- // /// Allows an offset in lines to be passed to pixel accessors class NUCLEX_PIXELS_TYPE Lines { /// Initializes a new line offset /// Number of lines the pixel accessor should advance public: NUCLEX_PIXELS_API explicit Lines(std::ptrdiff_t offset) : offset(offset) {} /// Retrieves the vertical offset to advance /// The vertical offset the pixel accessor should advance public: NUCLEX_PIXELS_API std::ptrdiff_t GetOffset() const { return this->offset; } /// Number of lines the pixel accessor should advance private: std::ptrdiff_t offset; }; // ------------------------------------------------------------------------------------------- // /// Moves through an image's pixels and allows them to be accessed /// /// /// The pixel iterator is a convenient way to process and alter pixels in an image /// without having to do expensive bounds checks and address calculations per pixel. /// It follows the design of standard iterators and can be used to access each pixel /// in a bitmap like so: /// /// /// /// /// PixelIterator end = PixelIterator::GetEnd(myBitmapMemory); /// for(PixelIterator current(myBitmapMemory); current != end; ++current) { /// void *pixelAddress = *current; // do something with the pixel /// } /// /// /// /// /// Performance-wise, the pixel iterator is a bit slower than hand-coding pixel accesses, /// but not by much. For example, when stepping through an image's pixels like above, /// only a single clipping check is performed when it is advanced and the address is /// simply incremented instead of being recalculated. /// /// /// By design, the pixel access iterator only requires a /// instance to work on, so that it can be used on various targets, including locked /// textures of 3D rendering APIs or bitmaps in various windowing systems. /// /// /// In addition to being a random access iterator (that allows jumping multiple pixels /// and using the array indexing operator), the pixel access iterator can also move /// vertically or go to a specific position: /// /// /// /// /// PixelIterator current(myBitmapMemory) /// current.MoveTo(256, 256); // Jump to this X, Y position /// /// do { /// current += Lines(1); // Go down 1 pixel /// } while(current.GetY() < (myBitmapMemory.Height)) { /// /// /// /// class NUCLEX_PIXELS_TYPE PixelIterator { /// Type that results when the distance of two iterators is calculated public: typedef std::ptrdiff_t difference_type; /// Type of the values this iterator iterates over public: typedef void *value_type; /// Reference to an element addressed by this iterator public: typedef void *&reference; /// Type of pointer this iterator emulates public: typedef void **pointer; /// Which type of iterator this is public: typedef std::random_access_iterator_tag iterator_category; /// Builds the begin iterator for a pixel accessor to the specified memory /// Bitmap memory for which a begin iterator will be built /// The begin iterator for the specified bitmap memory public: NUCLEX_PIXELS_API static PixelIterator GetBegin(const BitmapMemory &memory) { return PixelIterator(memory); } /// Builds the end iterator for a pixel accessor to the specified memory /// Bitmap memory for which an end iterator will be built /// The end iterator for the specified bitmap memory public: NUCLEX_PIXELS_API static PixelIterator GetEnd(const BitmapMemory &memory) { return PixelIterator(memory) += (memory.Width * memory.Height); } /// Initializes a new pixel accessor for the specified bitmap memory /// Bitmap memory the pixel accessor will work on public: NUCLEX_PIXELS_API PixelIterator(const BitmapMemory &memory) : memory(memory), x(0), y(0), current(reinterpret_cast(memory.Pixels)), bytesPerPixel(CountBitsPerPixel(memory.PixelFormat) / 8) { enforceStrideLargerThanLineByteCount(); } /// Moves the pixel access iterator to the specified position /// X coordinate the pixel access iterator will be moved to /// Y coordinate the pixel access iterator will be moved to public: NUCLEX_PIXELS_API void MoveTo(std::size_t newX, std::size_t newY) { if((newX >= this->memory.Width) || (newY >= this->memory.Height)) { // It is valid to place the iterator *exactly* at one past the last pixel, // so we only fail if the position is out of bounds in any other way. enforceAtEndPosition(newX, newY); } this->x = newX; this->y = newY; this->current = static_cast(memory.Pixels); this->current += newY * memory.Stride; this->current += newX * this->bytesPerPixel; } /// Moves the pixel accessor vertically in negative direction /// Number of lines the pixel accessor will be moved back /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator -=(const Lines &lines) { if(lines.GetOffset() < 0) { moveDown(static_cast(-lines.GetOffset())); } else { moveUp(static_cast(lines.GetOffset())); } return *this; } /// Moves the pixel accessor vertically in positive direction /// Number of lines the pixel accessor will be moved forward /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator +=(const Lines &lines) { if(lines.GetOffset() < 0) { moveUp(static_cast(-lines.GetOffset())); } else { moveDown(static_cast(lines.GetOffset())); } return *this; } /// Moves the pixel accessor horizontally in negative direction /// Number of pixels the pixel accessor will be moved back /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator -=( PixelIterator::difference_type offset ) { if(offset < 0) { moveRight(static_cast(-offset)); } else { moveLeft(static_cast(offset)); } return *this; } /// Moves the pixel accessor horizontally in positive direction /// Number of pixels the pixel accessor will be moved forward /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator +=( PixelIterator::difference_type offset ) { if(offset < 0) { moveLeft(static_cast(-offset)); } else { moveRight(static_cast(offset)); } return *this; } /// Moves the pixel accessor to the previous pixel /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator --() { // Are we somewhere in the middle of a line? (normal, branch-predicted case) if(this->x > 0) { this->current -= this->bytesPerPixel; --this->x; } else { // Or have we reached the left image border? enforceIteratorCanRetreat(); // Checks and triggers an assertion if needed this->x = this->memory.Width - 1; this->current -= this->memory.Stride - (this->x * this->bytesPerPixel); --this->y; } return *this; } /// Moves the pixel accessor to the next pixel /// The pixel accessor public: NUCLEX_PIXELS_API PixelIterator &operator ++() { // Will we still be within the image area? (normal, branch-predicted case) if(this->x + 1 < this->memory.Width) { this->current += this->bytesPerPixel; ++this->x; } else { // We're hitting the right image border // Can we go forward by one line? if(this->y + 1 < this->memory.Height) { this->current += this->memory.Stride - (this->x * this->bytesPerPixel); this->x = 0; ++this->y; } else { // We're on the last line already enforceIteratorCanAdvance(); // Checks and triggers an assertion if needed ++this->x; // Go to special iterator end position (or commence undefined behavior) } } return *this; } /// Returns the memory address of the iterator's current position /// The memory address the pixel iterator is currently at public: NUCLEX_PIXELS_API void *operator *() { enforceNotAtEndPosition(); return this->current; } /// Returns the memory address of the iterator's current position /// The memory address the pixel iterator is currently at public: NUCLEX_PIXELS_API const void *operator *() const { enforceNotAtEndPosition(); return this->current; } /// Looks up the current X coordinate of the pixel accessor /// The X coordinate of the pixel currently being accessed public: NUCLEX_PIXELS_API std::size_t GetX() const { enforceNotAtEndPosition(); return this->x; } /// Looks up the current Y coordinate of the pixel accessor /// The Y coordinate of the pixel currently being accessed public: NUCLEX_PIXELS_API std::size_t GetY() const { enforceNotAtEndPosition(); return this->y; } /// Returns a pointer to the current pixel /// A pointer to the current pixel public: NUCLEX_PIXELS_API const void *GetAddress() const { enforceNotAtEndPosition(); return this->current; } /// Returns a pointer to the current pixel /// A pointer to the current pixel public: NUCLEX_PIXELS_API void *GetAddress() { enforceNotAtEndPosition(); return this->current; } /// Checks whether another pixel accessor is at the same position /// Other pixel accessor that will be compared /// True if the other pixel accessor is at the same position public: NUCLEX_PIXELS_API bool operator ==(const PixelIterator &other) const { return (this->current == other.current); } /// Checks whether another pixel accessor is at a different position /// Other pixel accessor that will be compared /// True if the other pixel accessor is at a different position public: NUCLEX_PIXELS_API bool operator !=(const PixelIterator &other) const { return (this->current != other.current); } /// Moves the pixel iterator a number of pixels to the left /// Number of pixels the pixel iterator will be moved private: void moveLeft(std::size_t pixelCount); /// Moves the pixel iterator a number of pixels to the right /// Number of pixels the pixel iterator will be moved private: void moveRight(std::size_t pixelCount); /// Moves the pixel iterator a number of lines upwards /// Number of lines the pixel iterator will be moved private: void moveUp(std::size_t lineCount); /// Moves the pixel iterator a number of lines downwards /// Number of lines the pixel iterator will be moved private: void moveDown(std::size_t lineCount); /// Fails an assertion if the iterator is at its end position private: void enforceNotAtEndPosition() const; /// /// Fails an assertion if the stride value is less than the number of bytes in /// a line of the bitmap /// private: void enforceStrideLargerThanLineByteCount() const; /// /// Fails an assertion if the iterator is at a position from where it can't retreat /// private: void enforceIteratorCanRetreat() const; /// /// Fails an assertion if the iterator is at a position from where it can't advance /// private: void enforceIteratorCanAdvance() const; /// /// Fails an assertion if the iterator is not at the end position /// /// X coordinate that will be checked /// Y coordinate that will be checked private: void enforceAtEndPosition(std::size_t checkX, std::size_t checkY) const; /// Bitmap memory the pixel iterator is accessing private: BitmapMemory memory; /// Current X coordinate of the iterator private: std::size_t x; /// Current Y coordinate of the iterator private: std::size_t y; /// Current address the pixel iterator is at private: std::uint8_t *current; /// Number of bytes in a single pixel private: std::size_t bytesPerPixel; }; // ------------------------------------------------------------------------------------------- // }} // namespace Nuclex::Pixels #endif // NUCLEX_PIXELS_PIXELITERATOR_H