Fri, 09 Jul 2021 10:04:09 +0300
Merge
Source/Gorgon/Types.h | file | annotate | diff | comparison | revisions |
--- a/Source/Gorgon/Containers/Image.h Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Containers/Image.h Fri Jul 09 10:04:09 2021 +0300 @@ -10,293 +10,320 @@ #include "../IO/Stream.h" namespace Gorgon { - namespace Containers { + namespace Containers { + + enum class InterpolationMethod { + None, + NearestNeighbor = None, + Linear, + Bilinear = Linear, + Cubic, + Bicubic = Cubic, + }; - /// This class is a container for image data. It supports different color modes and access to the - /// underlying data through () operator. This object implements move semantics. Since copy constructor is - /// expensive, it is deleted against accidental use. If a copy of the object is required, use Duplicate function. - template<class T_> - class basic_Image { - public: - - /// Constructs an empty image data - basic_Image() { - } + template<class T_, class V_ = void> + struct FixImageValue; - /// Constructs a new image data with the given width, height and color mode. This constructor - /// does not initialize data inside the image - basic_Image(const Geometry::Size &size, Graphics::ColorMode mode) : size(size), mode(mode) { - cpp=Graphics::GetChannelsPerPixel(mode); - data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); + template<class T_> + struct FixImageValue<T_, typename std::enable_if<std::is_integral<T_>::value>::type> { + static T_ Fix(float val) { + return T_(Clamp<float>(std::round(val), std::numeric_limits<T_>::lowest(), std::numeric_limits<T_>::max())); + } + }; - alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; - } + template<> + struct FixImageValue<float, void> { + static float Fix(float val) { + return val; + } + }; - /// Copy constructor is disabled - basic_Image(const basic_Image &) = delete; + /// This class is a container for image data. It supports different color modes and access to the + /// underlying data through () operator. This object implements move semantics. Since copy constructor is + /// expensive, it is deleted against accidental use. If a copy of the object is required, use Duplicate function. + /// TODO Separate non-rgba related images, export/import with rgbaf + template<class T_> + class basic_Image { + public: - /// Move constructor - basic_Image(basic_Image &&data) : basic_Image() { - Swap(data); - } + /// Constructs an empty image data + basic_Image() { + } - /// Copy assignment is disabled - basic_Image &operator=(const basic_Image &) = delete; + /// Constructs a new image data with the given width, height and color mode. This constructor + /// does not initialize data inside the image + basic_Image(const Geometry::Size &size, Graphics::ColorMode mode) : size(size), mode(mode) { + cpp=Graphics::GetChannelsPerPixel(mode); + data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); + + alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; + } - /// Move assignment - basic_Image &operator=(basic_Image &&other) { - if(this == &other) return *this; - - Destroy(); - Swap(other); - - return *this; - } + /// Copy constructor is disabled + basic_Image(const basic_Image &) = delete; + + /// Move constructor + basic_Image(basic_Image &&data) : basic_Image() { + Swap(data); + } + + /// Copy assignment is disabled + basic_Image &operator=(const basic_Image &) = delete; - /// Destructor - ~basic_Image() { - Destroy(); - } + /// Move assignment + basic_Image &operator=(basic_Image &&other) { + if(this == &other) return *this; + + Destroy(); + Swap(other); + + return *this; + } - /// Duplicates this image, essentially performing the work of copy constructor - basic_Image Duplicate() const { - basic_Image n; - n.Assign(data, size, mode); + /// Destructor + ~basic_Image() { + Destroy(); + } - return n; - } + /// Duplicates this image, essentially performing the work of copy constructor + basic_Image Duplicate() const { + basic_Image n; + n.Assign(data, size, mode); - /// Resizes the image to the given size and color mode. This function discards the contents - /// of the image and does not perform any initialization. - void Resize(const Geometry::Size &size, Graphics::ColorMode mode) { + return n; + } + + /// Resizes the image to the given size and color mode. This function discards the contents + /// of the image and does not perform any initialization. + void Resize(const Geometry::Size &size, Graphics::ColorMode mode) { #ifndef NDEBUG - if(!size.IsValid()) - throw std::runtime_error("basic_Image size cannot be negative"); + if(!size.IsValid()) + throw std::runtime_error("basic_Image size cannot be negative"); #endif - // Check if resize is really necessary - if(this->size==size && this->cpp==Graphics::GetChannelsPerPixel(mode)) - return; + // Check if resize is really necessary + if(this->size==size && this->cpp==Graphics::GetChannelsPerPixel(mode)) + return; - this->size = size; - this->mode = mode; - this->cpp = Graphics::GetChannelsPerPixel(mode); - this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; + this->size = size; + this->mode = mode; + this->cpp = Graphics::GetChannelsPerPixel(mode); + this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; - if(data) { - free(data); - } - - data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); - } + if(data) { + free(data); + } + + data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); + } - /// Assigns the image to the copy of the given data. Ownership of the given data - /// is not transferred. If the given data is not required elsewhere, consider using - /// Assume function. This variant performs resize and copy at the same time. The given - /// data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). - /// This function does not perform any checks for the data size while copying it. - /// If width or height is 0, the newdata is not accessed and this method effectively - /// Destroys the current image. In this case, both width and height should be specified as 0. - void Assign(Byte *newdata, const Geometry::Size &size, Graphics::ColorMode mode) { + /// Assigns the image to the copy of the given data. Ownership of the given data + /// is not transferred. If the given data is not required elsewhere, consider using + /// Assume function. This variant performs resize and copy at the same time. The given + /// data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). + /// This function does not perform any checks for the data size while copying it. + /// If width or height is 0, the newdata is not accessed and this method effectively + /// Destroys the current image. In this case, both width and height should be specified as 0. + void Assign(Byte *newdata, const Geometry::Size &size, Graphics::ColorMode mode) { #ifndef NDEBUG - if(!size.IsValid()) - throw std::runtime_error("basic_Image size cannot be negative"); + if(!size.IsValid()) + throw std::runtime_error("basic_Image size cannot be negative"); #endif - this->size = size; - this->mode = mode; - this->cpp = Graphics::GetChannelsPerPixel(mode); - this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; + this->size = size; + this->mode = mode; + this->cpp = Graphics::GetChannelsPerPixel(mode); + this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; - if(data && data!=newdata) { - free(data); - } - - if(size.Area()*cpp>0) { - data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); - memcpy(data, newdata, size.Area()*cpp*sizeof(T_)); - } - else { - data=nullptr; - } - } + if(data && data!=newdata) { + free(data); + } + + if(size.Area()*cpp>0) { + data=(Byte*)malloc(size.Area()*cpp*sizeof(T_)); + memcpy(data, newdata, size.Area()*cpp*sizeof(T_)); + } + else { + data=nullptr; + } + } - /// Assigns the image to the copy of the given data. Ownership of the given data - /// is not transferred. If the given data is not required elsewhere, consider using - /// Assume function. The size and color mode of the image stays the same. The given - /// data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). - /// This function does not perform any checks for the data size while copying it.t - void Assign(Byte *newdata) { - memcpy(data, newdata, size.Area()*cpp*sizeof(T_)); - } + /// Assigns the image to the copy of the given data. Ownership of the given data + /// is not transferred. If the given data is not required elsewhere, consider using + /// Assume function. The size and color mode of the image stays the same. The given + /// data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). + /// This function does not perform any checks for the data size while copying it.t + void Assign(Byte *newdata) { + memcpy(data, newdata, size.Area()*cpp*sizeof(T_)); + } - /// Assumes the ownership of the given data. This variant changes the size and - /// color mode of the image. The given data should have the size of - /// width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). This function - /// does not perform any checks for the data size while assuming it. - /// newdata could be nullptr however, in this case - /// width, height should be 0. mode is not assumed to be ColorMode::Invalid while - /// the image is empty, therefore it could be specified as any value. - void Assume(Byte *newdata, const Geometry::Size &size, Graphics::ColorMode mode) { + /// Assumes the ownership of the given data. This variant changes the size and + /// color mode of the image. The given data should have the size of + /// width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). This function + /// does not perform any checks for the data size while assuming it. + /// newdata could be nullptr however, in this case + /// width, height should be 0. mode is not assumed to be ColorMode::Invalid while + /// the image is empty, therefore it could be specified as any value. + void Assume(Byte *newdata, const Geometry::Size &size, Graphics::ColorMode mode) { #ifndef NDEBUG - if(!size.IsValid()) - throw std::runtime_error("basic_Image size cannot be negative"); + if(!size.IsValid()) + throw std::runtime_error("basic_Image size cannot be negative"); #endif - this->size = size; - this->mode = mode; - this->cpp = Graphics::GetChannelsPerPixel(mode); - this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; + this->size = size; + this->mode = mode; + this->cpp = Graphics::GetChannelsPerPixel(mode); + this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; - if(data && data!=newdata) { - free(data); - } - - data=newdata; - } + if(data && data!=newdata) { + free(data); + } + + data=newdata; + } - /// Assumes the ownership of the given data. The size and color mode of the image stays the same. - /// The given data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). - /// This function does not perform any checks for the data size while assuming it. - void Assume(Byte *newdata) { - if(data && data!=newdata) { - free(data); - } - - data=newdata; - } + /// Assumes the ownership of the given data. The size and color mode of the image stays the same. + /// The given data should have the size of width*height*Graphics::GetBytesPerPixel(mode)*sizeof(T_). + /// This function does not perform any checks for the data size while assuming it. + void Assume(Byte *newdata) { + if(data && data!=newdata) { + free(data); + } + + data=newdata; + } - /// Returns and disowns the current data buffer. If image is empty, this method will return a nullptr. - Byte *Release() { - auto temp=data; - data=nullptr; - Destroy(); + /// Returns and disowns the current data buffer. If image is empty, this method will return a nullptr. + Byte *Release() { + auto temp=data; + data=nullptr; + Destroy(); - return temp; - } + return temp; + } - /// Cleans the contents of the buffer by setting every byte it contains to 0. - void Clear() { + /// Cleans the contents of the buffer by setting every byte it contains to 0. + void Clear() { #ifndef NDEBUG - if(!data) { - throw std::runtime_error("basic_Image data is empty"); - } + if(!data) { + throw std::runtime_error("basic_Image data is empty"); + } #endif - memset(data, 0, size.Area()*cpp*sizeof(T_)); - } + memset(data, 0, size.Area()*cpp*sizeof(T_)); + } - /// Destroys this image by setting width and height to 0 and freeing the memory - /// used by its data. Also color mode is set to ColorMode::Invalid - void Destroy() { - if(data) { - free(data); - data=nullptr; - } - size = {0, 0}; - cpp = 0; - mode = Graphics::ColorMode::Invalid; - } + /// Destroys this image by setting width and height to 0 and freeing the memory + /// used by its data. Also color mode is set to ColorMode::Invalid + void Destroy() { + if(data) { + free(data); + data=nullptr; + } + size = {0, 0}; + cpp = 0; + mode = Graphics::ColorMode::Invalid; + } - /// Swaps this image with another. This function is used to implement move semantics. - void Swap(basic_Image &other) { - using std::swap; + /// Swaps this image with another. This function is used to implement move semantics. + void Swap(basic_Image &other) { + using std::swap; - swap(data, other.data); - swap(size, other.size); - swap(mode, other.mode); - swap(cpp, other.cpp); - swap(alphaloc, other.alphaloc); - } + swap(data, other.data); + swap(size, other.size); + swap(mode, other.mode); + swap(cpp, other.cpp); + swap(alphaloc, other.alphaloc); + } - /// Returns the raw data pointer - Byte *RawData() { - return data; - } + /// Returns the raw data pointer + Byte *RawData() { + return data; + } - /// Returns the raw data pointer - const Byte *RawData() const { - return data; - } + /// Returns the raw data pointer + const Byte *RawData() const { + return data; + } - /// Converts this image data to RGBA buffer - void ConvertToRGBA() { - if(!data) return; + /// Converts this image data to RGBA buffer + void ConvertToRGBA() { + if(!data) return; - switch(mode) { - case Graphics::ColorMode::BGRA: - for(int i=0; i<size.Area(); i++) { - std::swap(data[i*4+2], data[i*4+0]); - } - break; + switch(mode) { + case Graphics::ColorMode::BGRA: + for(int i=0; i<size.Area(); i++) { + std::swap(data[i*4+2], data[i*4+0]); + } + break; - case Graphics::ColorMode::Grayscale_Alpha: { - auto pdata = data; - data = (Byte*)malloc(size.Area()*4*sizeof(T_)); + case Graphics::ColorMode::Grayscale_Alpha: { + auto pdata = data; + data = (Byte*)malloc(size.Area()*4*sizeof(T_)); - for(int i=0; i<size.Area(); i++) { - data[i*4+0] = pdata[i*2+0]; - data[i*4+1] = pdata[i*2+0]; - data[i*4+2] = pdata[i*2+0]; - data[i*4+3] = pdata[i*2+1]; - } - delete pdata; - } - break; + for(int i=0; i<size.Area(); i++) { + data[i*4+0] = pdata[i*2+0]; + data[i*4+1] = pdata[i*2+0]; + data[i*4+2] = pdata[i*2+0]; + data[i*4+3] = pdata[i*2+1]; + } + delete pdata; + } + break; - case Graphics::ColorMode::Grayscale: { - auto pdata = data; - data = (Byte*)malloc(size.Area()*4*sizeof(T_)); + case Graphics::ColorMode::Grayscale: { + auto pdata = data; + data = (Byte*)malloc(size.Area()*4*sizeof(T_)); - for(int i=0; i<size.Area(); i++) { - data[i*4+0] = pdata[i+0]; - data[i*4+1] = pdata[i+0]; - data[i*4+2] = pdata[i+0]; - data[i*4+3] = 255; - } - delete pdata; - } - break; + for(int i=0; i<size.Area(); i++) { + data[i*4+0] = pdata[i+0]; + data[i*4+1] = pdata[i+0]; + data[i*4+2] = pdata[i+0]; + data[i*4+3] = 255; + } + delete pdata; + } + break; - case Graphics::ColorMode::Alpha: { - auto pdata = data; - data = (Byte*)malloc(size.Area()*4*sizeof(T_)); + case Graphics::ColorMode::Alpha: { + auto pdata = data; + data = (Byte*)malloc(size.Area()*4*sizeof(T_)); - for(int i=0; i<size.Area(); i++) { - data[i*4+0] = 255; - data[i*4+1] = 255; - data[i*4+2] = 255; - data[i*4+3] = pdata[i+0]; - } - delete pdata; - } - break; + for(int i=0; i<size.Area(); i++) { + data[i*4+0] = 255; + data[i*4+1] = 255; + data[i*4+2] = 255; + data[i*4+3] = pdata[i+0]; + } + delete pdata; + } + break; - case Graphics::ColorMode::RGB: { - auto pdata = data; - data = (Byte*)malloc(size.Area()*4*sizeof(T_)); + case Graphics::ColorMode::RGB: { + auto pdata = data; + data = (Byte*)malloc(size.Area()*4*sizeof(T_)); - for(int i=0; i<size.Area(); i++) { - data[i*4+0] = pdata[i*3+0]; - data[i*4+1] = pdata[i*3+1]; - data[i*4+2] = pdata[i*3+2]; - data[i*4+3] = 255; - } - delete pdata; - } - break; + for(int i=0; i<size.Area(); i++) { + data[i*4+0] = pdata[i*3+0]; + data[i*4+1] = pdata[i*3+1]; + data[i*4+2] = pdata[i*3+2]; + data[i*4+3] = 255; + } + delete pdata; + } + break; - case Graphics::ColorMode::BGR: { - auto pdata = data; - data = (Byte*)malloc(size.Area()*4*sizeof(T_)); + case Graphics::ColorMode::BGR: { + auto pdata = data; + data = (Byte*)malloc(size.Area()*4*sizeof(T_)); - for(int i=0; i<size.Area(); i++) { - data[i*4+0] = pdata[i*3+2]; - data[i*4+1] = pdata[i*3+1]; - data[i*4+2] = pdata[i*3+0]; - data[i*4+3] = 255; - } - delete pdata; - } - break; + for(int i=0; i<size.Area(); i++) { + data[i*4+0] = pdata[i*3+2]; + data[i*4+1] = pdata[i*3+1]; + data[i*4+2] = pdata[i*3+0]; + data[i*4+3] = 255; + } + delete pdata; + } + break; case Graphics::ColorMode::RGBA: //do nothing @@ -305,17 +332,17 @@ default: throw std::runtime_error("Invalid color mode"); - } + } - mode = Graphics::ColorMode::RGBA; - } - + mode = Graphics::ColorMode::RGBA; + } + // cppcheck-suppress constParameter - /// Copies data from one image to another. This operation does not perform - /// blending. Additionally, color modes should be the same. However, this - /// function will do clipping for overflows. Do not use negative values for - /// target. Will return false if nothing is copied. - bool CopyTo(basic_Image &dest, Geometry::Point target = {0, 0}) const { + /// Copies data from one image to another. This operation does not perform + /// blending. Additionally, color modes should be the same. However, this + /// function will do clipping for overflows. Do not use negative values for + /// target. Will return false if nothing is copied. + bool CopyTo(basic_Image &dest, Geometry::Point target = {0, 0}) const { if(dest.GetMode() != mode || size.Area() == 0 || dest.GetSize().Area() == 0) return false; @@ -342,13 +369,13 @@ return true; } - + // cppcheck-suppress constParameter - /// Copies data from one image to another. This operation does not perform - /// blending. Additionally, color modes should be the same. However, this - /// function will do clipping. Source bounds should be within the image. - /// Will return false if nothing is copied. - bool CopyTo(basic_Image &dest, Geometry::Bounds source, Geometry::Point target = {0, 0}) const { + /// Copies data from one image to another. This operation does not perform + /// blending. Additionally, color modes should be the same. However, this + /// function will do clipping. Source bounds should be within the image. + /// Will return false if nothing is copied. + bool CopyTo(basic_Image &dest, Geometry::Bounds source, Geometry::Point target = {0, 0}) const { if(dest.GetMode() != mode || size.Area() == 0 || dest.GetSize().Area() == 0) return false; @@ -390,816 +417,1360 @@ return true; } - /// Copies this image to a RGBA buffer, buffer should be resize before calling this function - void CopyToRGBABuffer(Byte *buffer) const { - if(!data) return; + /// Copies this image to a RGBA buffer, buffer should be resize before calling this function + void CopyToRGBABuffer(Byte *buffer) const { + if(!data) return; - int i; + int i; - switch(mode) { - case Graphics::ColorMode::RGBA: - memcpy(buffer, data, size.Area()*4); - break; + switch(mode) { + case Graphics::ColorMode::RGBA: + memcpy(buffer, data, size.Area()*4); + break; - case Graphics::ColorMode::BGRA: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*4+2]; - buffer[i*4+1] = data[i*4+1]; - buffer[i*4+2] = data[i*4+0]; - buffer[i*4+3] = data[i*4+3]; - } - break; + case Graphics::ColorMode::BGRA: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*4+2]; + buffer[i*4+1] = data[i*4+1]; + buffer[i*4+2] = data[i*4+0]; + buffer[i*4+3] = data[i*4+3]; + } + break; - case Graphics::ColorMode::Grayscale_Alpha: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*2+0]; - buffer[i*4+1] = data[i*2+0]; - buffer[i*4+2] = data[i*2+0]; - buffer[i*4+3] = data[i*2+1]; - } - break; + case Graphics::ColorMode::Grayscale_Alpha: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*2+0]; + buffer[i*4+1] = data[i*2+0]; + buffer[i*4+2] = data[i*2+0]; + buffer[i*4+3] = data[i*2+1]; + } + break; - case Graphics::ColorMode::Grayscale: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i+0]; - buffer[i*4+1] = data[i+0]; - buffer[i*4+2] = data[i+0]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::Grayscale: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i+0]; + buffer[i*4+1] = data[i+0]; + buffer[i*4+2] = data[i+0]; + buffer[i*4+3] = 255; + } + break; - case Graphics::ColorMode::Alpha: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = 255; - buffer[i*4+1] = 255; - buffer[i*4+2] = 255; - buffer[i*4+3] = data[i+0]; - } - break; + case Graphics::ColorMode::Alpha: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = 255; + buffer[i*4+1] = 255; + buffer[i*4+2] = 255; + buffer[i*4+3] = data[i+0]; + } + break; - case Graphics::ColorMode::RGB: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*3+0]; - buffer[i*4+1] = data[i*3+1]; - buffer[i*4+2] = data[i*3+2]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::RGB: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*3+0]; + buffer[i*4+1] = data[i*3+1]; + buffer[i*4+2] = data[i*3+2]; + buffer[i*4+3] = 255; + } + break; - case Graphics::ColorMode::BGR: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*3+2]; - buffer[i*4+1] = data[i*3+1]; - buffer[i*4+2] = data[i*3+0]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::BGR: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*3+2]; + buffer[i*4+1] = data[i*3+1]; + buffer[i*4+2] = data[i*3+0]; + buffer[i*4+3] = 255; + } + break; default: throw std::runtime_error("Invalid mode"); - } - } + } + } - /// Copies this image to a RGBA buffer, buffer should be resize before calling this function. - /// This function is here mostly to create icon for Win32 - void CopyToBGRABuffer(Byte *buffer) const { - if(!data) return; + /// Copies this image to a RGBA buffer, buffer should be resize before calling this function. + /// This function is here mostly to create icon for Win32 + void CopyToBGRABuffer(Byte *buffer) const { + if(!data) return; - int i; + int i; - switch(mode) { - case Graphics::ColorMode::BGRA: - memcpy(buffer, data, size.Area()*4); - break; + switch(mode) { + case Graphics::ColorMode::BGRA: + memcpy(buffer, data, size.Area()*4); + break; - case Graphics::ColorMode::RGBA: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*4+2]; - buffer[i*4+1] = data[i*4+1]; - buffer[i*4+2] = data[i*4+0]; - buffer[i*4+3] = data[i*4+3]; - } - break; + case Graphics::ColorMode::RGBA: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*4+2]; + buffer[i*4+1] = data[i*4+1]; + buffer[i*4+2] = data[i*4+0]; + buffer[i*4+3] = data[i*4+3]; + } + break; - case Graphics::ColorMode::Grayscale_Alpha: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*2+0]; - buffer[i*4+1] = data[i*2+0]; - buffer[i*4+2] = data[i*2+0]; - buffer[i*4+3] = data[i*2+1]; - } - break; + case Graphics::ColorMode::Grayscale_Alpha: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*2+0]; + buffer[i*4+1] = data[i*2+0]; + buffer[i*4+2] = data[i*2+0]; + buffer[i*4+3] = data[i*2+1]; + } + break; - case Graphics::ColorMode::Grayscale: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i+0]; - buffer[i*4+1] = data[i+0]; - buffer[i*4+2] = data[i+0]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::Grayscale: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i+0]; + buffer[i*4+1] = data[i+0]; + buffer[i*4+2] = data[i+0]; + buffer[i*4+3] = 255; + } + break; - case Graphics::ColorMode::Alpha: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = 255; - buffer[i*4+1] = 255; - buffer[i*4+2] = 255; - buffer[i*4+3] = data[i+0]; - } - break; + case Graphics::ColorMode::Alpha: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = 255; + buffer[i*4+1] = 255; + buffer[i*4+2] = 255; + buffer[i*4+3] = data[i+0]; + } + break; - case Graphics::ColorMode::BGR: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*3+0]; - buffer[i*4+1] = data[i*3+1]; - buffer[i*4+2] = data[i*3+2]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::BGR: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*3+0]; + buffer[i*4+1] = data[i*3+1]; + buffer[i*4+2] = data[i*3+2]; + buffer[i*4+3] = 255; + } + break; - case Graphics::ColorMode::RGB: - for(i=0; i<size.Area(); i++) { - buffer[i*4+0] = data[i*3+2]; - buffer[i*4+1] = data[i*3+1]; - buffer[i*4+2] = data[i*3+0]; - buffer[i*4+3] = 255; - } - break; + case Graphics::ColorMode::RGB: + for(i=0; i<size.Area(); i++) { + buffer[i*4+0] = data[i*3+2]; + buffer[i*4+1] = data[i*3+1]; + buffer[i*4+2] = data[i*3+0]; + buffer[i*4+3] = 255; + } + break; default: throw std::runtime_error("Invalid mode"); - } - } - /// Copies this image to a RGBA buffer, buffer should be resize before calling this function. - /// This function is here mostly to create icon for X11 - void CopyToBGRABufferLong(unsigned long *buffer) const { - if(!data) return; + } + } + /// Copies this image to a RGBA buffer, buffer should be resize before calling this function. + /// This function is here mostly to create icon for X11 + void CopyToBGRABufferLong(unsigned long *buffer) const { + if(!data) return; - int i; + int i; - switch(mode) { - case Graphics::ColorMode::BGRA: - for(i=0; i<size.Area(); i++) { + switch(mode) { + case Graphics::ColorMode::BGRA: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*4+0]<<0) | (data[i*4+1]<<8) | (data[i*4+2]<<16) | (data[i*4+3]<<24); } - break; + break; - case Graphics::ColorMode::RGBA: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::RGBA: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*4+2]<<0) | (data[i*4+1]<<8) | (data[i*4+0]<<16) | (data[i*4+3]<<24); } - break; + break; - case Graphics::ColorMode::Grayscale_Alpha: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::Grayscale_Alpha: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*2+0]<<0) | (data[i*2+0]<<8) | (data[i*2+0]<<16) | (data[i*2+1]<<24); } - break; + break; - case Graphics::ColorMode::Grayscale: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::Grayscale: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*1+0]<<0) | (data[i*1+0]<<8) | (data[i*1+0]<<16) | (255<<24); } - break; + break; - case Graphics::ColorMode::Alpha: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::Alpha: + for(i=0; i<size.Area(); i++) { buffer[i] = (255<<0) | (255<<8) | (255<<16) | (data[i*1+0]<<24); - } - break; + } + break; - case Graphics::ColorMode::BGR: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::BGR: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*3+0]<<0) | (data[i*3+1]<<8) | (data[i*3+2]<<16) | (255<<24); - } - break; + } + break; - case Graphics::ColorMode::RGB: - for(i=0; i<size.Area(); i++) { + case Graphics::ColorMode::RGB: + for(i=0; i<size.Area(); i++) { buffer[i] = (data[i*3+2]<<0) | (data[i*3+1]<<8) | (data[i*3+0]<<16) | (255<<24); - } - break; + } + break; default: throw std::runtime_error("Invalid mode"); - } - } + } + } + + /// Shrinks the size of the image using integer area interpolation. + basic_Image ShrinkMultiple(const Geometry::Size& factor) const { + Geometry::Size newsize = {(int)std::ceil((float)size.Width/factor.Width), (int)std::ceil((float)size.Height/factor.Height)}; + basic_Image target(newsize, GetMode()); + + for(int y=0; y<newsize.Height; y++) { + auto th = std::min(y*factor.Height+factor.Height, size.Height); + auto ys = y * factor.Height; + + for(int x=0; x<newsize.Width; x++) { + auto tw = std::min(x*factor.Width+factor.Width, size.Width); + auto xs = x * factor.Width; + + int count = 0; + float sum[4]= {}; + + for(int yy=ys; yy<th; yy++) { + for(int xx=xs; xx<tw; xx++) { + count++; - /// Imports a given bitmap file. BMP RLE compression and colorspaces are not supported. - bool ImportBMP(const std::string &filename, bool dib = false) { - std::ifstream file(filename, std::ios::binary); + for(unsigned c=0; c<cpp; c++) { + sum[c] += operator()(xx, yy, c); + } + } + } + + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = FixImageValue<T_>::Fix(sum[c] / count); + } + } + } + + return target; + } - if(!file.is_open()) return false; + /// Scales this image to the given size. In the image is shrunk more than 2x its original size + /// Area interpolation is used along with the specified interpolation method. + basic_Image Scale(const Geometry::Size &newsize, InterpolationMethod method = InterpolationMethod::Cubic) const { + basic_Image target(newsize, GetMode()); + + float fx = (float)size.Width / newsize.Width; + float fy = (float)size.Height / newsize.Height; - return ImportBMP(file, dib); - } + if(fx > 2 || fy > 2) { + if(fx < 2) + fx = 1; + if(fy < 2) + fy = 1; + + auto img = ShrinkMultiple({int(fx), int(fy)}); - /// Imports a given bitmap file. BMP RLE compression and colorspaces are not supported. - bool ImportBMP(std::istream &file, bool dib = false) { - using namespace IO; + if(img.GetSize() == newsize) + return img; + else + return img.Scale(newsize, method); + } - unsigned long off = 0; + if(method == InterpolationMethod::NearestNeighbor) { + float yy = fy/2; + for(int y=0; y<newsize.Height; y++) { + float xx = fx/2; + for(int x=0; x<newsize.Width; x++) { + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = operator()((int)xx, (int)yy, c); + } - if(!dib) { - if(ReadString(file, 2) != "BM") return false; + xx += fx; + } - IO::ReadUInt32(file); + yy += fy; + } + } + else if(method == InterpolationMethod::Linear) { + float yy = 0.5f; + for(int y=0; y<newsize.Height; y++) { + float ly = yy - (int)yy; + float ly1 = 1 - ly; + int y1 = (int)yy; + int y2 = y1 + 1; - ReadUInt16(file); //reserved 1 - ReadUInt16(file); //reserved 2 + y1 = y1 < 0 ? 0 : y1; + y2 = y2 >= size.Height ? size.Height - 1 : y2; - off = ReadUInt32(file); - } + float xx = 0.5f; + for(int x=0; x<newsize.Width; x++) { + float lx = xx - (int)xx; + float lx1 = 1 - lx; + int x1 = (int)xx; + int x2 = x1 + 1; + + x1 = x1 < 0 ? 0 : x1; + x2 = x2 >= size.Width ? size.Width - 1 : x2; - auto headersize = IO::ReadUInt32(file); + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = FixImageValue<T_>::Fix( + lx1 * ly1 * operator()(x1, y1, c) + + lx * ly1 * operator()(x2, y1, c) + + lx * ly * operator()(x2, y2, c) + + lx1 * ly * operator()(x1, y2, c) + ); + } + + xx += fx; + } - int width = 0, height = 0; - int bpp = 0; - bool upsidedown = false; - bool grayscalepalette = true; - int colorsused = 0; - bool alpha = false; - int redshift, greenshift, blueshift, alphashift; - float redmult = 1, greenmult = 1, bluemult = 1, alphamult = 1; - uint32_t redmask = 0, greenmask = 0, bluemask = 0, alphamask = 0; + yy += fy; + } + } + else if(method == InterpolationMethod::Cubic) { + float yy = 0.f; + int ys[4]; + float wy[4], yf[4]; + int xs[4]; + float wx[4], xf[4]; + const float a = -0.5; + for(int y=0; y<newsize.Height; y++) { + yf[1] = yy - (int)yy; + yf[2] = 1 - yf[1]; + yf[3] = 2 - yf[1]; + yf[0] = 1 + yf[1]; + + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wy[i] = (a+2) * yf[i]*yf[i]*yf[i] - (a+3) * yf[i]*yf[i] + 1; + } + else { + wy[i] = a * yf[i]*yf[i]*yf[i] - 5*a * yf[i]*yf[i] + 8*a * yf[i] - 4*a; + } + ys[i] = Clamp((int)yy-1 + i, 0, size.Height-1); + } - std::vector<Graphics::RGBA> palette; + float xx = 0.f; + for(int x=0; x<newsize.Width; x++) { + xf[1] = xx - (int)xx; + xf[2] = 1 - xf[1]; + xf[3] = 2 - xf[1]; + xf[0] = 1 + xf[1]; - auto shiftcalc=[](uint32_t v) { - int pos = 0; - while(v) { - if(v&1) return pos; - v=v>>1; - pos++; - } + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wx[i] = (a+2) * xf[i]*xf[i]*xf[i] - (a+3) * xf[i]*xf[i] + 1; + } + else { + wx[i] = a * xf[i]*xf[i]*xf[i] - 5*a * xf[i]*xf[i] + 8*a * xf[i] - 4*a; + } + xs[i] = Clamp((int)xx-1 + i, 0, size.Width-1); + } - return pos; - }; + for(unsigned c=0; c<cpp; c++) { + float v = 0; + for(int j=0; j<4; j++) { + for(int i=0; i<4; i++) { + v += wx[i] * wy[j] * operator()(xs[i], ys[j], c); + } + } + + target(x, y, c) = FixImageValue<T_>::Fix(v); + } + + xx += fx; + } - if(headersize == 12) { //2x bmp - width = ReadInt16(file); - height = ReadInt16(file); - ReadUInt16(file); //planes - bpp = ReadUInt16(file); - } + yy += fy; + } + } + else { + throw std::runtime_error("Unknown interpolation method"); + } - if(headersize >= 40) { //3x - width = ReadInt32(file); - height = ReadInt32(file); - ReadUInt16(file); //planes - bpp = ReadUInt16(file); + return target; + } + + /// Rotates this image with the given angle. + basic_Image Rotate(Float angle, InterpolationMethod method = InterpolationMethod::Cubic) const { + return Rotate(angle, {size.Width/2.f, size.Height/2.f}, method); + } + + /// Rotates this image with the given angle. + basic_Image Rotate(Float angle, const Geometry::Pointf origin, InterpolationMethod method = InterpolationMethod::Cubic) const { + Geometry::Boundsf bnds = {0,0, Geometry::Sizef(size)}; + Geometry::Rotate(bnds, angle, origin); + Geometry::Bounds b{ + (int)std::floor(bnds.Left)-1, (int)std::floor(bnds.Top)-1, + (int)std::ceil(bnds.Right)+1, (int)std::ceil(bnds.Bottom)+1, + }; + + auto newsize = b.GetSize(); + + basic_Image target(newsize, GetMode()); - auto compress = ReadUInt32(file); + Float cosa = std::cos(-angle); //inverse transform + Float sina = std::sin(-angle); - if(compress != 0 && compress != 3) return false; + if(method == InterpolationMethod::NearestNeighbor) { + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; - bool bitcompress = compress != 0; + for(int x=0; x<newsize.Width; x++) { + for(unsigned c=0; c<cpp; c++) { + float xn = x - origin.X + b.Left; - ReadUInt32(file); //size of bitmap + target(x, y, c) = Get((int)std::round(xn * cosa - yn * sina + origin.X), (int)std::round(xn * sina + yn * cosa + origin.Y), c); + } + } + } + } + else if(method == InterpolationMethod::Linear) { + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; - ReadInt32(file); //horz resolution - ReadInt32(file); //vert resolution + for(int x=0; x<newsize.Width; x++) { + float xn = x - origin.X + b.Left; - colorsused = ReadUInt32(file); - ReadUInt32(file); //colors important + float xx = xn * cosa - yn * sina + origin.X; + float yy = xn * sina + yn * cosa + origin.Y; + + int y1 = (int)std::floor(yy); + float ly = yy - y1; + float ly1 = 1 - ly; + int y2 = y1 + 1; - if(bitcompress && headersize == 40) { - redmask = (uint32_t)ReadUInt32(file); - greenmask = (uint32_t)ReadUInt32(file); - bluemask = (uint32_t)ReadUInt32(file); - } - else if(bpp == 16) { - redmask = 0x7c00; - greenmask = 0x03e0; - bluemask = 0x001f; - } - else if(bpp == 32) { - redmask = 0x00ff0000; - greenmask = 0x0000ff00; - bluemask = 0x000000ff; - } - } + int x1 = (int)std::floor(xx); + float lx = xx - x1; + float lx1 = 1 - lx; + int x2 = x1 + 1; + + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = FixImageValue<T_>::Fix( + lx1 * ly1 * Get(x1, y1, c) + + lx * ly1 * Get(x2, y1, c) + + lx * ly * Get(x2, y2, c) + + lx1 * ly * Get(x1, y2, c) + ); + } + } + } + } + else if(method == InterpolationMethod::Cubic) { + int ys[4]; + float wy[4], yf[4]; + int xs[4]; + float wx[4], xf[4]; + const float a = -0.5; + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; + + for(int x=0; x<newsize.Width; x++) { + float xn = x - origin.X + b.Left; + + float xx = xn * cosa - yn * sina + origin.X; + float yy = xn * sina + yn * cosa + origin.Y; + + xf[1] = xx - std::floor(xx); + xf[2] = 1 - xf[1]; + xf[3] = 2 - xf[1]; + xf[0] = 1 + xf[1]; + + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wx[i] = (a+2) * xf[i]*xf[i]*xf[i] - (a+3) * xf[i]*xf[i] + 1; + } + else { + wx[i] = a * xf[i]*xf[i]*xf[i] - 5*a * xf[i]*xf[i] + 8*a * xf[i] - 4*a; + } + xs[i] = (int)floor(xx)-1 + i; + } + + yf[1] = yy - std::floor(yy); + yf[2] = 1 - yf[1]; + yf[3] = 2 - yf[1]; + yf[0] = 1 + yf[1]; - if(headersize >= 108) { - redmask = (uint32_t)ReadUInt32(file); - greenmask = (uint32_t)ReadUInt32(file); - bluemask = (uint32_t)ReadUInt32(file); - alphamask = (uint32_t)ReadUInt32(file); + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wy[i] = (a+2) * yf[i]*yf[i]*yf[i] - (a+3) * yf[i]*yf[i] + 1; + } + else { + wy[i] = a * yf[i]*yf[i]*yf[i] - 5*a * yf[i]*yf[i] + 8*a * yf[i] - 4*a; + } + ys[i] = (int)floor(yy)-1 + i; + } + + for(unsigned c=0; c<cpp; c++) { + float v = 0; + for(int j=0; j<4; j++) { + for(int i=0; i<4; i++) { + v += wx[i] * wy[j] * Get(xs[i], ys[j], c); + } + } + + target(x, y, c) = FixImageValue<T_>::Fix(v); + } + } + } + } + else { + throw std::runtime_error("Unknown interpolation method"); + } - file.seekg(13*4, std::ios::cur); //colorspace information - } + return target; + } + + /// Rotates this image with the given angle. + basic_Image SkewX(Float perpixel, InterpolationMethod method = InterpolationMethod::Cubic) const { + return SkewX(perpixel, {0.f, 0.f}, method); + } - redshift = shiftcalc(redmask); - greenshift = shiftcalc(greenmask); - blueshift = shiftcalc(bluemask); - alphashift = shiftcalc(alphamask); + /// Rotates this image with the given angle. + basic_Image SkewX(Float perpixel, const Geometry::Pointf origin, InterpolationMethod method = InterpolationMethod::Cubic) const { + Geometry::Boundsf bnds = {0,0, Geometry::Sizef(size)}; + Geometry::SkewX(bnds, perpixel, origin); + Geometry::Bounds b{ + (int)std::floor(bnds.Left)-1, (int)std::floor(bnds.Top)-1, + (int)std::ceil(bnds.Right)+1, (int)std::ceil(bnds.Bottom)+1, + }; + + auto newsize = b.GetSize(); + + basic_Image target(newsize, GetMode()); + + if(method == InterpolationMethod::NearestNeighbor) { + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; + + for(int x=0; x<newsize.Width; x++) { + for(unsigned c=0; c<cpp; c++) { + float xn = x + b.Left; - if(redmask) { - redmult = 255.f / (redmask>>redshift); - } - if(greenmask) { - greenmult = 255.f / (greenmask>>greenshift); - } - if(bluemask) { - bluemult = 255.f / (bluemask>>blueshift); - } - if(alphamask) { - alphamult = 255.f / (alphamask>>alphashift); - } + target(x, y, c) = Get((int)std::round(xn - yn * perpixel), y, c); + } + } + } + } + else if(method == InterpolationMethod::Linear) { + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; + + for(int x=0; x<newsize.Width; x++) { + float xn = x + b.Left; + + float xx = xn - yn * perpixel; + + int x1 = (int)std::floor(xx); + float lx = xx - x1; + float lx1 = 1 - lx; + int x2 = x1 + 1; - if(headersize > 108) { - file.seekg(headersize - 108, std::ios::cur); - } + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = FixImageValue<T_>::Fix( + lx1 * Get(x1, y, c) + + lx * Get(x2, y, c) + ); + } + } + } + } + else if(method == InterpolationMethod::Cubic) { + int xs[4]; + float wx[4], xf[4]; + const float a = -0.5; + for(int y=0; y<newsize.Height; y++) { + float yn = y - origin.Y + b.Top; - if(height > 0) - upsidedown = true; - else - height *= -1; + for(int x=0; x<newsize.Width; x++) { + float xn = x + b.Left; + + float xx = xn - yn * perpixel; + + xf[1] = xx - std::floor(xx); + xf[2] = 1 - xf[1]; + xf[3] = 2 - xf[1]; + xf[0] = 1 + xf[1]; - if(bpp <= 8) { //paletted - if(colorsused == 0) - colorsused = 1 << bpp; + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wx[i] = (a+2) * xf[i]*xf[i]*xf[i] - (a+3) * xf[i]*xf[i] + 1; + } + else { + wx[i] = a * xf[i]*xf[i]*xf[i] - 5*a * xf[i]*xf[i] + 8*a * xf[i] - 4*a; + } + xs[i] = (int)floor(xx)-1 + i; + } - palette.reserve(colorsused); + for(unsigned c=0; c<cpp; c++) { + float v = 0; + + for(int i=0; i<4; i++) { + v += wx[i] * Get(xs[i], y, c); + } + target(x, y, c) = FixImageValue<T_>::Fix(v); + } + } + } + } + else { + throw std::runtime_error("Unknown interpolation method"); + } - for(int i=0; i<colorsused; i++) { - Byte r, g, b, a = 255; - - b = ReadUInt8(file); - g = ReadUInt8(file); - r = ReadUInt8(file); + return target; + } - if(r!=b || b!=g) grayscalepalette = false; + /// Rotates this image with the given angle. + basic_Image SkewY(Float perpixel, InterpolationMethod method = InterpolationMethod::Cubic) const { + return SkewY(perpixel, {0.f, 0.f}, method); + } - if(headersize > 12) { - a = ReadUInt8(file); //reserved - } + /// Rotates this image with the given angle. + basic_Image SkewY(Float perpixel, const Geometry::Pointf origin, InterpolationMethod method = InterpolationMethod::Cubic) const { + Geometry::Boundsf bnds = {0,0, Geometry::Sizef(size)}; + Geometry::SkewY(bnds, perpixel, origin); + Geometry::Bounds b{ + (int)std::floor(bnds.Left)-1, (int)std::floor(bnds.Top)-1, + (int)std::ceil(bnds.Right)+1, (int)std::ceil(bnds.Bottom)+1, + }; - palette.emplace_back(r, g, b, a); - } + auto newsize = b.GetSize(); + + basic_Image target(newsize, GetMode()); + + if(method == InterpolationMethod::NearestNeighbor) { + for(int x=0; x<newsize.Width; x++) { + float xn = x - origin.X + b.Left; - for(int i=0; i<colorsused; i++) { - if(palette[i].A) { - alpha = true; - break; - } - } - } + for(int y=0; y<newsize.Height; y++) { + for(unsigned c=0; c<cpp; c++) { + float yn = y + b.Top; - if(!dib) - file.seekg(off, std::ios::beg); + target(x, y, c) = Get(x, (int)std::round(yn - xn * perpixel), c); + } + } + } + } + else if(method == InterpolationMethod::Linear) { + for(int x=0; x<newsize.Width; x++) { + float xn = x - origin.X + b.Left; + + for(int y=0; y<newsize.Height; y++) { + float yn = y + b.Top; + + float yy = yn - xn * perpixel; + + int y1 = (int)std::floor(yy); + float ly = yy - y1; + float ly1 = 1 - ly; + int y2 = y1 + 1; - if((bpp == 32 || bpp == 16) && alphamask != 0) { - Resize({width, height}, Graphics::ColorMode::RGBA); - alpha = true; - } - else if(bpp <= 8 && grayscalepalette) { - if(alpha) - Resize({width, height}, Graphics::ColorMode::Grayscale_Alpha); - else - Resize({width, height}, Graphics::ColorMode::Grayscale); - } - else if(alpha) { - if(redmask == 0 && greenmask == 0 && bluemask == 0) { - Resize({width, height}, Graphics::ColorMode::Alpha); - } - else { - Resize({width, height}, Graphics::ColorMode::RGBA); - } - } - else { - Resize({width, height}, Graphics::ColorMode::RGB); - } + for(unsigned c=0; c<cpp; c++) { + target(x, y, c) = FixImageValue<T_>::Fix( + ly1 * Get(x, y1, c) + + ly * Get(x, y2, c) + ); + } + } + } + } + else if(method == InterpolationMethod::Cubic) { + int ys[4]; + float wy[4], yf[4]; + const float a = -0.5; + for(int x=0; x<newsize.Width; x++) { + float xn = x - origin.X + b.Left; + + for(int y=0; y<newsize.Height; y++) { + float yn = y + b.Top; + + float yy = yn - xn * perpixel; + + yf[1] = yy - std::floor(yy); + yf[2] = 1 - yf[1]; + yf[3] = 2 - yf[1]; + yf[0] = 1 + yf[1]; + + for(int i=0; i<4; i++) { + if(i == 1 || i == 2) { + wy[i] = (a+2) * yf[i]*yf[i]*yf[i] - (a+3) * yf[i]*yf[i] + 1; + } + else { + wy[i] = a * yf[i]*yf[i]*yf[i] - 5*a * yf[i]*yf[i] + 8*a * yf[i] - 4*a; + } + ys[i] = (int)floor(yy)-1 + i; + } + + for(unsigned c=0; c<cpp; c++) { + float v = 0; + + for(int i=0; i<4; i++) { + v += wy[i] * Get(x, ys[i], c); + } + target(x, y, c) = FixImageValue<T_>::Fix(v); + } + } + } + } + else { + throw std::runtime_error("Unknown interpolation method"); + } - int ys, ye, yc; + return target; + } + + /// Mirrors this bitmap along X axis as a new one. + basic_Image MirrorX() const { + basic_Image target(size, GetMode()); + int yy = size.Height - 1; + for(int y=0; y<size.Height; y++) { + for(int x=0; x<size.Width; x++) { + for(int c=0; c<cpp; c++) { + target(x, y, c) = operator()(x, yy, c); + } + } + + yy--; + } + + return target; + } + + /// Mirrors this bitmap along Y axis as a new one. + basic_Image MirrorY() const { + basic_Image target(size, GetMode()); + int xx = size.Width- 1; + for(int x=0; x<size.Width; x++) { + for(int y=0; y<size.Height; y++) { + for(int c=0; c<cpp; c++) { + target(x, y, c) = operator()(xx, y, c); + } + } + + xx--; + } + + return target; + } - if(upsidedown) { - ys = height-1; - ye = -1; - yc = -1; - } - else { - ys = 0; - ye = height; - yc = 1; - } + /// Flips this bitmap along X axis as a new one. + basic_Image FlipX() const { + return MirrorY(); + } + + /// Flips this bitmap along Y axis as a new one. + basic_Image FlipY() const { + return MirrorX(); + } + + /// Imports a given bitmap file. BMP RLE compression and colorspaces are not supported. + bool ImportBMP(const std::string &filename, bool dib = false) { + std::ifstream file(filename, std::ios::binary); - if(bpp == 24) { - for(int y = ys; y!=ye; y += yc) { - int bytes = 0; - for(int x=0; x<width; x++) { - this->operator ()({x, y}, 0) = ReadUInt8(file); - this->operator ()({x, y}, 1) = ReadUInt8(file); - this->operator ()({x, y}, 2) = ReadUInt8(file); + if(!file.is_open()) return false; + + return ImportBMP(file, dib); + } - bytes += 3; - } + /// Imports a given bitmap file. BMP RLE compression and colorspaces are not supported. + bool ImportBMP(std::istream &file, bool dib = false) { + using namespace IO; + + unsigned long off = 0; + + if(!dib) { + if(ReadString(file, 2) != "BM") return false; - if(bytes%4) { - file.seekg(4-bytes%4, std::ios::cur); - } - } - } - else if(bpp == 16 || bpp == 32) { - for(int y = ys; y!=ye; y += yc) { - int bytes = 0; - for(int x=0; x<width; x++) { - uint32_t pix; - - if(bpp == 16) - pix = ReadUInt16(file); - else - pix = ReadUInt32(file); + IO::ReadUInt32(file); + + ReadUInt16(file); //reserved 1 + ReadUInt16(file); //reserved 2 + + off = ReadUInt32(file); + } + + auto headersize = IO::ReadUInt32(file); + + int width = 0, height = 0; + int bpp = 0; + bool upsidedown = false; + bool grayscalepalette = true; + int colorsused = 0; + bool alpha = false; + int redshift, greenshift, blueshift, alphashift; + float redmult = 1, greenmult = 1, bluemult = 1, alphamult = 1; + uint32_t redmask = 0, greenmask = 0, bluemask = 0, alphamask = 0; + + std::vector<Graphics::RGBA> palette; - if(redmask != 0 || greenmask != 0 || bluemask != 0) { - this->operator ()({x, y}, 0) = (Byte)std::round(((pix&redmask)>>redshift) * redmult); - this->operator ()({x, y}, 1) = (Byte)std::round(((pix&greenmask)>>greenshift) * greenmult); - this->operator ()({x, y}, 2) = (Byte)std::round(((pix&bluemask)>>blueshift) * bluemult); - } + auto shiftcalc=[](uint32_t v) { + int pos = 0; + while(v) { + if(v&1) return pos; + v=v>>1; + pos++; + } + + return pos; + }; + + if(headersize == 12) { //2x bmp + width = ReadInt16(file); + height = ReadInt16(file); + ReadUInt16(file); //planes + bpp = ReadUInt16(file); + } + + if(headersize >= 40) { //3x + width = ReadInt32(file); + height = ReadInt32(file); + ReadUInt16(file); //planes + bpp = ReadUInt16(file); + + auto compress = ReadUInt32(file); + + if(compress != 0 && compress != 3) return false; + + bool bitcompress = compress != 0; + + ReadUInt32(file); //size of bitmap + + ReadInt32(file); //horz resolution + ReadInt32(file); //vert resolution - if(alpha) { - if(redmask != 0 || greenmask != 0 || bluemask != 0) { - this->operator ()({x, y}, 3) = (Byte)std::round(((pix&alphamask)>>alphashift) * alphamult); - } - else { - this->operator ()({x, y}, 0) = (Byte)std::round(((pix&alphamask)>>alphashift) * alphamult); - } - } - - bytes += bpp/8; - } + colorsused = ReadUInt32(file); + ReadUInt32(file); //colors important + + if(bitcompress && headersize == 40) { + redmask = (uint32_t)ReadUInt32(file); + greenmask = (uint32_t)ReadUInt32(file); + bluemask = (uint32_t)ReadUInt32(file); + } + else if(bpp == 16) { + redmask = 0x7c00; + greenmask = 0x03e0; + bluemask = 0x001f; + } + else if(bpp == 32) { + redmask = 0x00ff0000; + greenmask = 0x0000ff00; + bluemask = 0x000000ff; + } + } + + if(headersize >= 108) { + redmask = (uint32_t)ReadUInt32(file); + greenmask = (uint32_t)ReadUInt32(file); + bluemask = (uint32_t)ReadUInt32(file); + alphamask = (uint32_t)ReadUInt32(file); + + file.seekg(13*4, std::ios::cur); //colorspace information + } - if(bytes%4) { - file.seekg(4-bytes%4, std::ios::cur); - } - } - } - else if(bpp < 8) { - Byte bitmask = (1 << bpp) - 1; - bitmask = bitmask << (8-bpp); - for(int y = ys; y!=ye; y += yc) { - int bytes = 0; - int bits = 0; - Byte v = 0; - for(int x=0; x<width; x++) { - int ind; - if(bits == 0) { - v = ReadUInt8(file); - bits = 8; - bytes++; - } + redshift = shiftcalc(redmask); + greenshift = shiftcalc(greenmask); + blueshift = shiftcalc(bluemask); + alphashift = shiftcalc(alphamask); + + if(redmask) { + redmult = 255.f / (redmask>>redshift); + } + if(greenmask) { + greenmult = 255.f / (greenmask>>greenshift); + } + if(bluemask) { + bluemult = 255.f / (bluemask>>blueshift); + } + if(alphamask) { + alphamult = 255.f / (alphamask>>alphashift); + } + + if(headersize > 108) { + file.seekg(headersize - 108, std::ios::cur); + } + + if(height > 0) + upsidedown = true; + else + height *= -1; + + if(bpp <= 8) { //paletted + if(colorsused == 0) + colorsused = 1 << bpp; - ind = (v&bitmask)>>(8-bpp); - if(ind >= colorsused) - continue; + palette.reserve(colorsused); + + for(int i=0; i<colorsused; i++) { + Byte r, g, b, a = 255; + + b = ReadUInt8(file); + g = ReadUInt8(file); + r = ReadUInt8(file); + + if(r!=b || b!=g) grayscalepalette = false; - auto col = palette[ind]; - - if(grayscalepalette) { - this->operator ()({x, y}, 0) = col.R; + if(headersize > 12) { + a = ReadUInt8(file); //reserved + } + + palette.emplace_back(r, g, b, a); + } + + for(int i=0; i<colorsused; i++) { + if(palette[i].A) { + alpha = true; + break; + } + } + } - if(alpha) - this->operator ()({x, y}, 1) = col.A; - } - else { - this->operator ()({x, y}, 0) = col.R; - this->operator ()({x, y}, 1) = col.G; - this->operator ()({x, y}, 2) = col.B; - + if(!dib) + file.seekg(off, std::ios::beg); - if(alpha) { - this->operator ()({x, y}, 3) = col.A; - } - } + if((bpp == 32 || bpp == 16) && alphamask != 0) { + Resize({width, height}, Graphics::ColorMode::RGBA); + alpha = true; + } + else if(bpp <= 8 && grayscalepalette) { + if(alpha) + Resize({width, height}, Graphics::ColorMode::Grayscale_Alpha); + else + Resize({width, height}, Graphics::ColorMode::Grayscale); + } + else if(alpha) { + if(redmask == 0 && greenmask == 0 && bluemask == 0) { + Resize({width, height}, Graphics::ColorMode::Alpha); + } + else { + Resize({width, height}, Graphics::ColorMode::RGBA); + } + } + else { + Resize({width, height}, Graphics::ColorMode::RGB); + } + + int ys, ye, yc; - v = v<<bpp; - bits -= bpp; - } + if(upsidedown) { + ys = height-1; + ye = -1; + yc = -1; + } + else { + ys = 0; + ye = height; + yc = 1; + } - if(bytes%4) { - file.seekg(4-bytes%4, std::ios::cur); - } - } - } + if(bpp == 24) { + for(int y = ys; y!=ye; y += yc) { + int bytes = 0; + for(int x=0; x<width; x++) { + this->operator ()({x, y}, 0) = ReadUInt8(file); + this->operator ()({x, y}, 1) = ReadUInt8(file); + this->operator ()({x, y}, 2) = ReadUInt8(file); + + bytes += 3; + } - return true; - } + if(bytes%4) { + file.seekg(4-bytes%4, std::ios::cur); + } + } + } + else if(bpp == 16 || bpp == 32) { + for(int y = ys; y!=ye; y += yc) { + int bytes = 0; + for(int x=0; x<width; x++) { + uint32_t pix; + + if(bpp == 16) + pix = ReadUInt16(file); + else + pix = ReadUInt32(file); - /// Exports the image as a bitmap. RGB is exported as 24-bit, RGBA, BGR, BGRA is exported - /// as 32-bit, Grayscale exported as 8-bit, Grayscale alpha, alpha only is exported as - /// 16-bit - bool ExportBMP(const std::string &filename, bool usev4 = false, bool dib = false) { - std::ofstream file(filename, std::ios::binary); + if(redmask != 0 || greenmask != 0 || bluemask != 0) { + this->operator ()({x, y}, 0) = (Byte)std::round(((pix&redmask)>>redshift) * redmult); + this->operator ()({x, y}, 1) = (Byte)std::round(((pix&greenmask)>>greenshift) * greenmult); + this->operator ()({x, y}, 2) = (Byte)std::round(((pix&bluemask)>>blueshift) * bluemult); + } - if(!file.is_open()) return false; - - return ExportBMP(file, usev4, dib); - } + if(alpha) { + if(redmask != 0 || greenmask != 0 || bluemask != 0) { + this->operator ()({x, y}, 3) = (Byte)std::round(((pix&alphamask)>>alphashift) * alphamult); + } + else { + this->operator ()({x, y}, 0) = (Byte)std::round(((pix&alphamask)>>alphashift) * alphamult); + } + } + + bytes += bpp/8; + } - /// Exports the image as a bitmap. RGB is exported as 24-bit, RGBA, BGR, BGRA is exported - /// as 32-bit, Grayscale exported as 8-bit, Grayscale alpha, alpha only is exported as - /// 16-bit - bool ExportBMP(std::ostream &file, bool usev4 = false, bool dib = false) { - using namespace IO; - using Graphics::ColorMode; + if(bytes%4) { + file.seekg(4-bytes%4, std::ios::cur); + } + } + } + else if(bpp < 8) { + Byte bitmask = (1 << bpp) - 1; + bitmask = bitmask << (8-bpp); + for(int y = ys; y!=ye; y += yc) { + int bytes = 0; + int bits = 0; + Byte v = 0; + for(int x=0; x<width; x++) { + int ind; + if(bits == 0) { + v = ReadUInt8(file); + bits = 8; + bytes++; + } + + ind = (v&bitmask)>>(8-bpp); + if(ind >= colorsused) + continue; + + auto col = palette[ind]; + + if(grayscalepalette) { + this->operator ()({x, y}, 0) = col.R; - long datasize = 0; - long headersize = 0; - long extraspace = 0; - int bpp = 0; - int compression = 0; - int stride = 0; + if(alpha) + this->operator ()({x, y}, 1) = col.A; + } + else { + this->operator ()({x, y}, 0) = col.R; + this->operator ()({x, y}, 1) = col.G; + this->operator ()({x, y}, 2) = col.B; + + + if(alpha) { + this->operator ()({x, y}, 3) = col.A; + } + } - switch(mode) { - case ColorMode::RGB: - case ColorMode::BGR: - stride = 3 * size.Width; - if(stride%4) stride += (4-(stride%4)); + v = v<<bpp; + bits -= bpp; + } - datasize = stride * size.Height; + if(bytes%4) { + file.seekg(4-bytes%4, std::ios::cur); + } + } + } - headersize = 40; - bpp = 24; - break; + return true; + } + + /// Exports the image as a bitmap. RGB is exported as 24-bit, RGBA, BGR, BGRA is exported + /// as 32-bit, Grayscale exported as 8-bit, Grayscale alpha, alpha only is exported as + /// 16-bit + bool ExportBMP(const std::string &filename, bool usev4 = false, bool dib = false) { + std::ofstream file(filename, std::ios::binary); - case ColorMode::RGBA: - case ColorMode::BGRA: - case ColorMode::Grayscale_Alpha: - stride = 4 * size.Width; - if(stride%4) stride += (4-(stride%4)); + if(!file.is_open()) return false; + + return ExportBMP(file, usev4, dib); + } + + /// Exports the image as a bitmap. RGB is exported as 24-bit, RGBA, BGR, BGRA is exported + /// as 32-bit, Grayscale exported as 8-bit, Grayscale alpha, alpha only is exported as + /// 16-bit + bool ExportBMP(std::ostream &file, bool usev4 = false, bool dib = false) { + using namespace IO; + using Graphics::ColorMode; - datasize = stride * size.Height; + long datasize = 0; + long headersize = 0; + long extraspace = 0; + int bpp = 0; + int compression = 0; + int stride = 0; - headersize = 108; - bpp = 32; - compression = 3; - break; + switch(mode) { + case ColorMode::RGB: + case ColorMode::BGR: + stride = 3 * size.Width; + if(stride%4) stride += (4-(stride%4)); + + datasize = stride * size.Height; + + headersize = 40; + bpp = 24; + break; - case ColorMode::Grayscale: - stride = size.Width; - if(stride%4) stride += (4-(stride%4)); + case ColorMode::RGBA: + case ColorMode::BGRA: + case ColorMode::Grayscale_Alpha: + stride = 4 * size.Width; + if(stride%4) stride += (4-(stride%4)); - datasize = stride * size.Height; + datasize = stride * size.Height; + + headersize = 108; + bpp = 32; + compression = 3; + break; - headersize = 40; - extraspace = 256 * 4; //palette - bpp = 8; - break; + case ColorMode::Grayscale: + stride = size.Width; + if(stride%4) stride += (4-(stride%4)); + + datasize = stride * size.Height; - case ColorMode::Alpha: - stride = 2 * size.Width; - if(stride%4) stride += (4-(stride%4)); + headersize = 40; + extraspace = 256 * 4; //palette + bpp = 8; + break; - datasize = stride * size.Height; + case ColorMode::Alpha: + stride = 2 * size.Width; + if(stride%4) stride += (4-(stride%4)); - headersize = 108; + datasize = stride * size.Height; + + headersize = 108; compression = 3; - bpp = 16; - break; + bpp = 16; + break; - default: - throw std::runtime_error("Unsupported color mode"); - } + default: + throw std::runtime_error("Unsupported color mode"); + } - - //header - if(!dib) { - WriteString(file, "BM"); - WriteUInt32(file, 14 + headersize + extraspace + datasize); - WriteUInt16(file, 0); //reserved 1 - WriteUInt16(file, 0); //reserved 2 - WriteUInt32(file, 14 + headersize + extraspace); - } + + //header + if(!dib) { + WriteString(file, "BM"); + WriteUInt32(file, 14 + headersize + extraspace + datasize); + WriteUInt16(file, 0); //reserved 1 + WriteUInt16(file, 0); //reserved 2 + WriteUInt32(file, 14 + headersize + extraspace); + } - WriteUInt32(file, headersize); - WriteInt32 (file, size.Width); - WriteInt32 (file, size.Height); - WriteUInt16(file, 1); - WriteUInt16(file, bpp); - WriteUInt32(file, compression); - WriteUInt32(file, datasize); - WriteInt32 (file, 2834); - WriteInt32 (file, 2834); - WriteInt32(file, 0); //colors used - WriteInt32(file, 0); //colors important + WriteUInt32(file, headersize); + WriteInt32 (file, size.Width); + WriteInt32 (file, size.Height); + WriteUInt16(file, 1); + WriteUInt16(file, bpp); + WriteUInt32(file, compression); + WriteUInt32(file, datasize); + WriteInt32 (file, 2834); + WriteInt32 (file, 2834); + WriteInt32(file, 0); //colors used + WriteInt32(file, 0); //colors important - if(compression == 3) { - if(mode == ColorMode::Alpha) { - WriteUInt32(file, 0x00000001); - WriteUInt32(file, 0x00000002); - WriteUInt32(file, 0x00000004); - } - else if(mode == ColorMode::BGRA) { - WriteUInt32(file, 0x00ff0000); - WriteUInt32(file, 0x0000ff00); - WriteUInt32(file, 0x000000ff); - } - else { - WriteUInt32(file, 0x000000ff); - WriteUInt32(file, 0x0000ff00); - WriteUInt32(file, 0x00ff0000); - } - } + if(compression == 3) { + if(mode == ColorMode::Alpha) { + WriteUInt32(file, 0x00000001); + WriteUInt32(file, 0x00000002); + WriteUInt32(file, 0x00000004); + } + else if(mode == ColorMode::BGRA) { + WriteUInt32(file, 0x00ff0000); + WriteUInt32(file, 0x0000ff00); + WriteUInt32(file, 0x000000ff); + } + else { + WriteUInt32(file, 0x000000ff); + WriteUInt32(file, 0x0000ff00); + WriteUInt32(file, 0x00ff0000); + } + } - if(headersize == 108 || usev4) { - if(compression != 3) { - WriteUInt32(file, 0x00000001); - WriteUInt32(file, 0x00000002); - WriteUInt32(file, 0x00000004); - } - if(mode == ColorMode::Alpha) { - WriteUInt32(file, 0x0000ff00); - } - else { - WriteUInt32(file, 0xff000000); - } - WriteUInt32(file, 1); //device dependent RGB - for(int i=0; i<12; i++) //color profile settings, all 0 - WriteUInt32(file, 0); - } + if(headersize == 108 || usev4) { + if(compression != 3) { + WriteUInt32(file, 0x00000001); + WriteUInt32(file, 0x00000002); + WriteUInt32(file, 0x00000004); + } + if(mode == ColorMode::Alpha) { + WriteUInt32(file, 0x0000ff00); + } + else { + WriteUInt32(file, 0xff000000); + } + WriteUInt32(file, 1); //device dependent RGB + for(int i=0; i<12; i++) //color profile settings, all 0 + WriteUInt32(file, 0); + } - int ostride; - switch(mode) { - case ColorMode::RGB: - ostride = size.Width*3; - for(int y=size.Height-1; y>=0; y--) { - WriteArray(file, data+y*ostride, ostride); + int ostride; + switch(mode) { + case ColorMode::RGB: + ostride = size.Width*3; + for(int y=size.Height-1; y>=0; y--) { + WriteArray(file, data+y*ostride, ostride); - for(int j=0; j<stride-ostride; j++) - WriteUInt8(file, 0); - } - break; + for(int j=0; j<stride-ostride; j++) + WriteUInt8(file, 0); + } + break; - case ColorMode::BGR: - ostride = size.Width*3; + case ColorMode::BGR: + ostride = size.Width*3; - for(int y=size.Height-1; y>=0; y--) { - WriteArray(file, data+y*ostride, ostride); + for(int y=size.Height-1; y>=0; y--) { + WriteArray(file, data+y*ostride, ostride); - for(int j=0; j<stride-ostride; j++) - WriteUInt8(file, 0); - } - break; + for(int j=0; j<stride-ostride; j++) + WriteUInt8(file, 0); + } + break; - case ColorMode::RGBA: - case ColorMode::BGRA: - ostride = size.Width*4; - for(int y=size.Height-1; y>=0; y--) { - WriteArray(file, data+y*ostride, ostride); - } - break; + case ColorMode::RGBA: + case ColorMode::BGRA: + ostride = size.Width*4; + for(int y=size.Height-1; y>=0; y--) { + WriteArray(file, data+y*ostride, ostride); + } + break; - case ColorMode::Grayscale: - //write palette - for(int i=0; i<256; i++) { - WriteUInt8(file, (Byte)i); - WriteUInt8(file, (Byte)i); - WriteUInt8(file, (Byte)i); - WriteUInt8(file, 0); - } + case ColorMode::Grayscale: + //write palette + for(int i=0; i<256; i++) { + WriteUInt8(file, (Byte)i); + WriteUInt8(file, (Byte)i); + WriteUInt8(file, (Byte)i); + WriteUInt8(file, 0); + } - //write data - ostride = size.Width; - for(int y=size.Height-1; y>=0; y--) { - WriteArray(file, data+y*ostride, ostride); + //write data + ostride = size.Width; + for(int y=size.Height-1; y>=0; y--) { + WriteArray(file, data+y*ostride, ostride); - for(int j=0; j<stride-ostride; j++) - WriteUInt8(file, 0); - } + for(int j=0; j<stride-ostride; j++) + WriteUInt8(file, 0); + } - break; - - - case ColorMode::Grayscale_Alpha: - ostride = size.Width*2; + break; + + + case ColorMode::Grayscale_Alpha: + ostride = size.Width*2; - for(int y=size.Height-1; y>=0; y--) { - for(int x=0; x<size.Width; x++) { - WriteUInt8(file, data[y*ostride+x*2]); - WriteUInt8(file, data[y*ostride+x*2]); - WriteUInt8(file, data[y*ostride+x*2]); - WriteUInt8(file, data[y*ostride+x*2+1]); - } - } - break; + for(int y=size.Height-1; y>=0; y--) { + for(int x=0; x<size.Width; x++) { + WriteUInt8(file, data[y*ostride+x*2]); + WriteUInt8(file, data[y*ostride+x*2]); + WriteUInt8(file, data[y*ostride+x*2]); + WriteUInt8(file, data[y*ostride+x*2+1]); + } + } + break; - case ColorMode::Alpha: - ostride = size.Width; + case ColorMode::Alpha: + ostride = size.Width; - for(int y=size.Height-1; y>=0; y--) { - for(int x=0; x<size.Width; x++) { - WriteUInt8(file, 0xff); - WriteUInt8(file, data[y*ostride+x]); - } + for(int y=size.Height-1; y>=0; y--) { + for(int x=0; x<size.Width; x++) { + WriteUInt8(file, 0xff); + WriteUInt8(file, data[y*ostride+x]); + } - for(int j=0; j<stride-ostride*2; j++) - WriteUInt8(file, 0); - } - break; + for(int j=0; j<stride-ostride*2; j++) + WriteUInt8(file, 0); + } + break; default: throw std::runtime_error("Invalid mode"); - } + } - return true; - } + return true; + } - /// Provides access to the given component in x and y coordinates. This - /// function performs bounds checking only on debug mode. - Byte &operator()(const Geometry::Point &p, unsigned component=0) { + /// Provides access to the given component in x and y coordinates. This + /// function performs bounds checking only on debug mode. + Byte &operator()(const Geometry::Point &p, unsigned component=0) { #ifndef NDEBUG - if(p.X<0 || p.Y<0 || p.X>=size.Width || p.Y>=size.Height || component>=cpp) { - throw std::runtime_error("Index out of bounds"); - } + if(p.X<0 || p.Y<0 || p.X>=size.Width || p.Y>=size.Height || component>=cpp) { + throw std::runtime_error("Index out of bounds"); + } #endif - return data[cpp*(size.Width*p.Y+p.X)+component]; - } + return data[cpp*(size.Width*p.Y+p.X)+component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function performs bounds checking only on debug mode. - Byte operator()(const Geometry::Point &p, unsigned component=0) const { + /// Provides access to the given component in x and y coordinates. This + /// function performs bounds checking only on debug mode. + Byte operator()(const Geometry::Point &p, unsigned component=0) const { #ifndef NDEBUG - if(p.X<0 || p.Y<0 || p.X>=size.Width || p.Y>=size.Height || component>=cpp) { - throw std::runtime_error("Index out of bounds"); - } + if(p.X<0 || p.Y<0 || p.X>=size.Width || p.Y>=size.Height || component>=cpp) { + throw std::runtime_error("Index out of bounds"); + } #endif - return data[cpp*(size.Width*p.Y+p.X)+component]; - } + return data[cpp*(size.Width*p.Y+p.X)+component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function returns 0 if the given coordinates are out of bounds. This - /// function works slower than the () operator. - Byte Get(const Geometry::Point &p, unsigned component = 0) const { - if (p.X < 0 || p.Y < 0 || p.X >= size.Width || p.Y >= size.Height || component >= cpp) { - return 0; - } + /// Provides access to the given component in x and y coordinates. This + /// function returns 0 if the given coordinates are out of bounds. This + /// function works slower than the () operator. + Byte Get(const Geometry::Point &p, unsigned component = 0) const { + if (p.X < 0 || p.Y < 0 || p.X >= size.Width || p.Y >= size.Height || component >= cpp) { + return 0; + } - return data[cpp*(size.Width*p.Y + p.X) + component]; - } + return data[cpp*(size.Width*p.Y + p.X) + component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function returns 0 if the given coordinates are out of bounds. This - /// function works slower than the () operator. - Byte Get(const Geometry::Point &p, Byte def, unsigned component = 0) const { - if (p.X < 0 || p.Y < 0 || p.X >= size.Width || p.Y >= size.Height || component >= cpp) { - return def; - } + /// Provides access to the given component in x and y coordinates. This + /// function returns 0 if the given coordinates are out of bounds. This + /// function works slower than the () operator. + Byte Get(const Geometry::Point &p, Byte def, unsigned component = 0) const { + if (p.X < 0 || p.Y < 0 || p.X >= size.Width || p.Y >= size.Height || component >= cpp) { + return def; + } - return data[cpp*(size.Width*p.Y + p.X) + component]; - } + return data[cpp*(size.Width*p.Y + p.X) + component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function performs bounds checking only on debug mode. - Byte &operator()(int x, int y, unsigned component=0) { + /// Provides access to the given component in x and y coordinates. This + /// function performs bounds checking only on debug mode. + Byte &operator()(int x, int y, unsigned component=0) { #ifndef NDEBUG - if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { - throw std::runtime_error("Index out of bounds"); - } + if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { + throw std::runtime_error("Index out of bounds"); + } #endif - return data[cpp*(size.Width*y+x)+component]; - } + return data[cpp*(size.Width*y+x)+component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function performs bounds checking only on debug mode. - Byte operator()(int x, int y, unsigned component=0) const { + /// Provides access to the given component in x and y coordinates. This + /// function performs bounds checking only on debug mode. + Byte operator()(int x, int y, unsigned component=0) const { #ifndef NDEBUG - if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { - throw std::runtime_error("Index out of bounds"); - } + if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { + throw std::runtime_error("Index out of bounds"); + } #endif - return data[cpp*(size.Width*y+x)+component]; - } + return data[cpp*(size.Width*y+x)+component]; + } - /// Provides access to the given component in x and y coordinates. This - /// function returns 0 if the given coordinates are out of bounds. This - /// function works slower than the () operator. - Byte Get(int x, int y, unsigned component=0) const { - if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { - return 0; - } + /// Provides access to the given component in x and y coordinates. This + /// function returns 0 if the given coordinates are out of bounds. This + /// function works slower than the () operator. + Byte Get(int x, int y, unsigned component=0) const { + if(x<0 || y<0 || x>=size.Width || y>=size.Height || component>=cpp) { + return 0; + } - return data[cpp*(size.Width*y+x)+component]; - } + return data[cpp*(size.Width*y+x)+component]; + } - /// Returns the alpha at the given location. If the given location does not exits - /// this function will return 0. If there is no alpha channel, image is assumed - /// to be opaque. - Byte GetAlphaAt(int x, int y) const { - if(x<0 || y<0 || x>=size.Width || y>=size.Height) { - return 0; - } + /// Returns the alpha at the given location. If the given location does not exits + /// this function will return 0. If there is no alpha channel, image is assumed + /// to be opaque. + Byte GetAlphaAt(int x, int y) const { + if(x<0 || y<0 || x>=size.Width || y>=size.Height) { + return 0; + } - if(alphaloc == -1) - return 255; + if(alphaloc == -1) + return 255; - return data[cpp*(size.Width*y+x)+alphaloc]; - } + return data[cpp*(size.Width*y+x)+alphaloc]; + } /// Returns the alpha at the given location. If the given location does not exits /// this function will return 0. If there is no alpha channel, image is assumed @@ -1287,81 +1858,81 @@ SetRGBAAt(p.X, p.Y, color); } - /// Returns the size of the image - Geometry::Size GetSize() const { - return size; - } + /// Returns the size of the image + Geometry::Size GetSize() const { + return size; + } - /// Returns the width of the image - int GetWidth() const { - return size.Width; - } + /// Returns the width of the image + int GetWidth() const { + return size.Width; + } - /// Returns the height of the image - int GetHeight() const { - return size.Height; - } + /// Returns the height of the image + int GetHeight() const { + return size.Height; + } - /// Total size of this image in number units - unsigned long GetTotalSize() const { - return size.Area()*cpp; - } + /// Total size of this image in number units + unsigned long GetTotalSize() const { + return size.Area()*cpp; + } - /// Returns the color mode of the image - Graphics::ColorMode GetMode() const { - return mode; - } - - /// Changes the color mode of the image. Only works if the bits/pixel - /// of the target mode is the same as the original - void ChangeMode(Graphics::ColorMode value) { + /// Returns the color mode of the image + Graphics::ColorMode GetMode() const { + return mode; + } + + /// Changes the color mode of the image. Only works if the bits/pixel + /// of the target mode is the same as the original + void ChangeMode(Graphics::ColorMode value) { if(Graphics::GetChannelsPerPixel(mode) != Graphics::GetChannelsPerPixel(value)) throw std::runtime_error("Modes differ in number of bits/pixel"); mode = value; - this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; - } + this->alphaloc = Graphics::HasAlpha(mode) ? Graphics::GetAlphaIndex(mode) : -1; + } - /// Returns the number units occupied by a single pixel of this image - unsigned GetChannelsPerPixel() const { - return cpp; - } + /// Returns the number units occupied by a single pixel of this image + unsigned GetChannelsPerPixel() const { + return cpp; + } - /// Returns if this image has alpha channel - bool HasAlpha() const { - return alphaloc != -1; - } + /// Returns if this image has alpha channel + bool HasAlpha() const { + return alphaloc != -1; + } - /// Returns the index of alpha channel. Value of -1 denotes no alpha channel - int GetAlphaIndex() const { - return alphaloc; - } + /// Returns the index of alpha channel. Value of -1 denotes no alpha channel + int GetAlphaIndex() const { + return alphaloc; + } - protected: - /// Data that stores pixels of the image - T_ *data = nullptr; + protected: + /// Data that stores pixels of the image + T_ *data = nullptr; - /// Width of the image - Geometry::Size size = {0, 0}; + /// Width of the image + Geometry::Size size = {0, 0}; - /// Color mode of the image - Graphics::ColorMode mode = Graphics::ColorMode::Invalid; + /// Color mode of the image + Graphics::ColorMode mode = Graphics::ColorMode::Invalid; - /// Channels per pixel information - unsigned cpp = 0; + /// Channels per pixel information + unsigned cpp = 0; - /// Location of the alpha channel, -1 means it does not exits - int alphaloc = -1; - }; + /// Location of the alpha channel, -1 means it does not exits + int alphaloc = -1; + }; - /// Swaps two images. Should be used unqualified for ADL. - template <class T_> - inline void swap(basic_Image<T_> &l, basic_Image<T_> &r) { - l.Swap(r); - } + /// Swaps two images. Should be used unqualified for ADL. + template <class T_> + inline void swap(basic_Image<T_> &l, basic_Image<T_> &r) { + l.Swap(r); + } - using Image = basic_Image<Byte>; + using Image = basic_Image<Byte>; - } + } }
--- a/Source/Gorgon/Graphics/AdvancedPrinter.cpp Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Graphics/AdvancedPrinter.cpp Fri Jul 09 10:04:09 2021 +0300 @@ -72,6 +72,12 @@ }, text, l, 0, false ); + + if(l.X > sz.Width) + sz.Width = l.X; + + if(l.Y > sz.Height) + sz.Height = l.Y; return sz; } @@ -84,27 +90,34 @@ [&sz]( const GlyphRenderer &renderer, Glyph g, const Geometry::Point &location, const RGBAf &, long - ) { - if(g != 0xffff) { - auto p = location + (Geometry::Point)renderer.GetSize(g) + renderer.GetOffset(g); - p.Y += int(renderer.GetBaseLine()); + ) { + if(g != 0xffff) { + auto p = location + (Geometry::Point)renderer.GetSize(g) + renderer.GetOffset(g); + p.Y += int(renderer.GetBaseLine()); - if(p.X > sz.Width) - sz.Width = p.X; + if(p.X > sz.Width) + sz.Width = p.X; - if(p.Y > sz.Height) - sz.Height = p.Y; - } - return true; - }, + if(p.Y > sz.Height) + sz.Height = p.Y; + } + return true; + }, [](const Geometry::Bounds &, const RGBAf &, int, RGBAf) { - }, + }, [](int, int, int, int, RGBAf) { - }, + }, [](Byte, const Geometry::Bounds &, const RGBAf &, bool) { - }, + }, text, l, width, true - ); + ); + + + if(l.X > sz.Width) + sz.Width = l.X; + + if(l.Y > sz.Height) + sz.Height = l.Y; return sz; }
--- a/Source/Gorgon/Graphics/AdvancedPrinterImpl.h Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Graphics/AdvancedPrinterImpl.h Fri Jul 09 10:04:09 2021 +0300 @@ -932,7 +932,8 @@ ind = acc.size(); newline = ind == 0; - cur.Y += lineh; + if(nl != -1) + cur.Y += lineh; int nextlinexstart = location.X + indent + hangingindent * beginparag; @@ -1075,7 +1076,7 @@ //END //if requested do paragraph - if(beginparag) + if(nl != -1 && beginparag) cur.Y += paragraphspacing(maxh, printer->GetParagraphSpacing()); //BEGIN Reset
--- a/Source/Gorgon/Graphics/Bitmap.cpp Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Graphics/Bitmap.cpp Fri Jul 09 10:04:09 2021 +0300 @@ -279,7 +279,7 @@ } } - Assume(img); + Assume(std::move(img)); } void Bitmap::StripAlpha() { @@ -302,7 +302,7 @@ } } - Assume(img); + Assume(std::move(img)); } void Bitmap::Grayscale(float ratio, GrayscaleConversionMethod method) { @@ -385,7 +385,7 @@ } } - Assume(img); + Assume(std::move(img)); } std::vector<Geometry::Bounds> Bitmap::CreateLinearAtlas(Containers::Collection<const Bitmap> list, AtlasMargin margins) { @@ -581,7 +581,7 @@ yy++; } - Assume(img); + Assume(std::move(img)); return ret; }
--- a/Source/Gorgon/Graphics/Bitmap.h Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Graphics/Bitmap.h Fri Jul 09 10:04:09 2021 +0300 @@ -221,10 +221,16 @@ data->Assign(newdata); } - /// Assumes the contents of the given image as image data. The given parameter is moved from - /// and will become an empty image. Notice that assuming data does not prepare the data to be drawn, + /// Assumes the contents of the given image as image data. Notice that assuming data does not prepare the data to be drawn, /// a separate call to Prepare function is necessary. void Assume(Containers::Image &image) { + delete data; + data = ℑ + } + + /// Assumes the contents of the given image as image data by moving it into the bitmap buffer. Notice that assuming data + /// does not prepare the data to be drawn, a separate call to Prepare function is necessary. + void Assume(Containers::Image &&image) { if(!data) { data=new Containers::Image; } @@ -695,6 +701,133 @@ return ret; } + /// Scales this bitmap as a new one using the supplied interpolation method. If the size is reduced more + /// than twice, integer part of the size reduction is done using area interpolation. + Bitmap Scale(int width, int height, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + return Scale({width, height}, method); + } + + /// Scales this bitmap as a new one using the supplied interpolation method. If the size is reduced more + /// than twice, integer part of the size reduction is done using area interpolation. + Bitmap Scale(const Geometry::Size &newsize, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->Scale(newsize, method)); + + return ret; + } + + /// Rotates this bitmap as a new one using the supplied interpolation method. + Bitmap Rotate(Float ang, const Geometry::Pointf &origin, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->Rotate(ang, origin, method)); + + return ret; + } + + /// Rotates this bitmap as a new one using the supplied interpolation method. + Bitmap Rotate(Float ang, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->Rotate(ang, method)); + + return ret; + } + + /// Shrinks the bitmap size to integer multiples. This method uses Area interpolation + Bitmap ShrinkMultiple(const Geometry::Size& factor) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->ShrinkMultiple(factor)); + + return ret; + } + + /// Skews this bitmap as a new one using the supplied interpolation method. Origin is assumed to be 0, 0 + Bitmap SkewX(Float perpixel, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->SkewX(perpixel, method)); + + return ret; + } + + /// Skews this bitmap as a new one using the supplied interpolation method. + Bitmap SkewX(Float perpixel, const Geometry::Pointf &origin, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->SkewX(perpixel, origin, method)); + + return ret; + } + + /// Skews this bitmap as a new one using the supplied interpolation method. Origin is assumed to be 0, 0 + Bitmap SkewY(Float perpixel, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->SkewY(perpixel, method)); + + return ret; + } + + /// Skews this bitmap as a new one using the supplied interpolation method. + Bitmap SkewY(Float perpixel, const Geometry::Pointf &origin, Containers::InterpolationMethod method = Containers::InterpolationMethod::Cubic) const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->SkewY(perpixel, origin, method)); + + return ret; + } + + /// Mirrors this bitmap along X axis as a new one using the supplied interpolation method. + Bitmap MirrorX() const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->MirrorX()); + + return ret; + } + + /// Mirrors this bitmap along Y axis as a new one using the supplied interpolation method. + Bitmap MirrorY() const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->MirrorY()); + + return ret; + } + + /// Flips this bitmap along X axis as a new one using the supplied interpolation method. + Bitmap FlipX() const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->FlipX()); + + return ret; + } + + /// Mirrors this bitmap along Y axis as a new one using the supplied interpolation method. + Bitmap FlipY() const { + ASSERT(data, "Bitmap data is not set"); + + Bitmap ret; + ret.Assume(data->FlipY()); + + return ret; + } + protected: /// When used as animation, an image is always persistent and it never finishes. bool Progress(unsigned &) override { return true; }
--- a/Source/Gorgon/Types.h Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Types.h Fri Jul 09 10:04:09 2021 +0300 @@ -161,6 +161,11 @@ return MultiLess(rest...); } + /// Converts the given degrees to radians + inline Float Angle(Float degrees) { + return degrees / 180 * PI; + } + /// Where acceptable, denotes that the object will assume the ownership class AssumeOwnershipTag { };
--- a/Source/Gorgon/Widgets/Textarea.cpp Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/Widgets/Textarea.cpp Fri Jul 09 10:04:09 2021 +0300 @@ -415,7 +415,6 @@ } void Textarea::updateselection() { - std::cout << selstart.glyph << " : " << sellen.glyph << std::endl; updatecursor(); if(sellen.byte != 0 && IsFocused()) { @@ -696,7 +695,7 @@ } if(text == "") { - stack.RemoveTagLocation(UI::ComponentTemplate::CaretTag); + stack.SetTagLocation(UI::ComponentTemplate::CaretTag, Geometry::Point{cursorlocation.X, cursorlocation.Y} - scrolloffset); } else { stack.SetTagLocation(UI::ComponentTemplate::CaretTag, Geometry::Point{cursorlocation.X, cursorlocation.Y} - scrolloffset); @@ -741,8 +740,7 @@ return byte; } - - + void Textarea::SetWordWrap(const bool &value) { if(wrap == value) return;
--- a/Source/Gorgon/WindowManager/DWM/DWM.h Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/WindowManager/DWM/DWM.h Fri Jul 09 10:04:09 2021 +0300 @@ -16,6 +16,8 @@ # undef CreateWindow # undef Rectangle +# undef max +# undef min ///@cond internal namespace Gorgon {
--- a/Source/Gorgon/WindowManager/DWM/DnD.cpp Fri Jul 09 09:55:29 2021 +0300 +++ b/Source/Gorgon/WindowManager/DWM/DnD.cpp Fri Jul 09 10:04:09 2021 +0300 @@ -6,6 +6,8 @@ #include <ShlObj.h> +#undef max + namespace Gorgon { namespace WindowManager { HRESULT __stdcall GGEDropTarget::DragEnter(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
--- a/Testing/Source/Manual/Generic.cpp Fri Jul 09 09:55:29 2021 +0300 +++ b/Testing/Source/Manual/Generic.cpp Fri Jul 09 10:04:09 2021 +0300 @@ -1,5 +1,5 @@ #include "GraphicsHelper.h" -#include <Gorgon/Graphics/ColorSpaces.h> +#include <Gorgon/Graphics/Bitmap.h> std::string helptext = @@ -12,21 +12,21 @@ int main() { Application app("generictest", "Test", helptext, 10); + app.wind.Resize(1000, 850); Graphics::Layer layer; app.wind.Add(layer); - for(int c=0; c<=125; c+=25) { - for(int l=0; l<=100; l+=10) { - for(int h=0; h<=360; h+=15) { - auto col = LChAf(l, c, h); - layer.Draw(h, l+c/25*110, 15, 10, col); - std::cout << col << RGBAf(col) << std::endl; - } - } - } + Bitmap bmp; + bmp.Import("../../Resources/Logo-large.png"); - std::cout << LChAf(0xff0000ff) << std::endl; + bmp = bmp.FlipX(); + bmp.Prepare(); + + bmp.Draw(layer, 0,0); + bmp.Export("test.png"); + + while(true) { Gorgon::NextFrame(); }