Last active
January 30, 2022 02:29
-
-
Save Journeyman1337/ea61022399e2a2c16231b4611268c45b to your computer and use it in GitHub Desktop.
libpng image class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Copyright (c) 2022 Daniel Valcour | |
Permission is hereby granted, free of charge, to any person obtaining a copy of | |
this software and associated documentation files (the "Software"), to deal in | |
the Software without restriction, including without limitation the rights to | |
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
the Software, and to permit persons to whom the Software is furnished to do so, | |
subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#include "Image.hpp" | |
#include <stb_image.h> | |
#include <stb_image_write.h> | |
#include <stb_image_resize.h> | |
#include <stdio.h> | |
#include <cmath> | |
#include <algorithm> | |
#include <stdexcept> | |
#include <cstddef> | |
#include <cstring> | |
#include <png.h> | |
constexpr std::uint8_t colorToGrayscale(const std::uint8_t r, const std::uint8_t g, const std::uint8_t b) noexcept | |
{ | |
// This is the same equation libpng uses to convert rgb to grayscale | |
return ((((6969 * (static_cast<uint32_t>(r))) + (23434 * (static_cast<uint32_t>(g))) + (2365 * (static_cast<uint32_t>(b)))) / 32768)); | |
} | |
constexpr Image::ColorType pngColorToImageColor(const int png_color_type) | |
{ | |
switch (png_color_type) | |
{ | |
case PNG_COLOR_TYPE_GRAY: | |
return Image::ColorType::G; | |
case PNG_COLOR_TYPE_GRAY_ALPHA: | |
return Image::ColorType::GA; | |
case PNG_COLOR_TYPE_RGB: | |
return Image::ColorType::RGB; | |
case PNG_COLOR_TYPE_RGBA: | |
return Image::ColorType::RGBA; | |
case PNG_COLOR_TYPE_PALETTE: | |
return Image::ColorType::RGB; | |
default: | |
return Image::ColorType::None; | |
} | |
} | |
constexpr Image::File::ColorType pngColorToImageFileColor(const int png_color_type) | |
{ | |
switch (png_color_type) | |
{ | |
case PNG_COLOR_TYPE_GRAY: | |
return Image::File::ColorType::G; | |
case PNG_COLOR_TYPE_GRAY_ALPHA: | |
return Image::File::ColorType::GA; | |
case PNG_COLOR_TYPE_RGB: | |
return Image::File::ColorType::RGB; | |
case PNG_COLOR_TYPE_RGBA: | |
return Image::File::ColorType::RGBA; | |
case PNG_COLOR_TYPE_PALETTE: | |
return Image::File::ColorType::Palette; | |
default: | |
return Image::File::ColorType::None; | |
} | |
} | |
constexpr int imageColorToPngColor(const Image::ColorType color_type) | |
{ | |
switch (color_type) | |
{ | |
case Image::ColorType::G: | |
return PNG_COLOR_TYPE_GRAY; | |
case Image::ColorType::GA: | |
return PNG_COLOR_TYPE_GRAY_ALPHA; | |
case Image::ColorType::RGB: | |
return PNG_COLOR_TYPE_RGB; | |
case Image::ColorType::RGBA: | |
return PNG_COLOR_TYPE_RGBA; | |
default: | |
return PNG_COLOR_TYPE_RGB; | |
} | |
} | |
Image::Image(const Image& other) | |
: width(other.width) | |
, height(other.height) | |
, pages(other.pages) | |
, color_type(ColorType::None) | |
{ | |
std::size_t size = other.GetSize(); | |
this->data = std::make_unique<uint8_t[]>(size); | |
std::memcpy(this->data.get(), other.GetData(), size); | |
} | |
Image::Image(Image&& other) noexcept | |
: width(other.width) | |
, height(other.height) | |
, pages(other.pages) | |
, data(std::move(other.data)) | |
, color_type(other.color_type) | |
{ | |
other.width = 0; | |
other.height = 0; | |
other.pages = 0; | |
} | |
Image& Image::operator=(const Image& other) | |
{ | |
if (&other == this) return *this; | |
this->width = other.width; | |
this->height = other.height; | |
this->pages = other.pages; | |
this->color_type = other.color_type; | |
std::size_t size = other.GetSize(); | |
this->data = std::make_unique<uint8_t[]>(size); | |
std::memcpy(this->data.get(), other.GetData(), size); | |
return *this; | |
} | |
Image& Image::operator=(Image&& other) noexcept | |
{ | |
if (&other == this) return *this; | |
this->width = other.width; | |
this->height = other.height; | |
this->pages = other.pages; | |
this->data = std::move(other.data); | |
this->color_type = other.color_type; | |
other.width = 0; | |
other.height = 0; | |
other.pages = 0; | |
other.color_type = ColorType::None; | |
return *this; | |
} | |
const char* PNG_FILE_FAIL_OPEN = "Failed to open PNG file."; | |
const char* PNG_FILE_FAIL_CREATE = "Failed to create PNG file."; | |
const char* PNG_FILE_FORMAT_INVALID = "PNG file format invalid."; | |
const char* IMAGE_EMPTY = "Image is empty."; | |
const char* IMAGE_NOT_SAME = "Images do not have same color type."; | |
const char* IMAGE_NOT_FIT = "Image does not fit at position."; | |
const char* INVALID_IMAGE_PROPERTIES = "The Image properties are invalid."; | |
static inline void openPngFile(const char* path, FILE** f) | |
{ | |
char header[8]; | |
*f = fopen(path, "rb"); | |
if (*f == NULL) | |
{ | |
throw std::runtime_error(PNG_FILE_FAIL_OPEN); | |
} | |
fread(header, 1, 8, *f); | |
if (png_sig_cmp(reinterpret_cast<png_const_bytep>(&header[0]), 0, 8)) | |
{ | |
fclose(*f); | |
throw std::runtime_error(PNG_FILE_FORMAT_INVALID); | |
} | |
} | |
static inline void getPngFileInfo(FILE* const f, png_structpp png_ptr, png_infopp info_ptr, png_uint_32* const png_width, png_uint_32* const png_height, int* const png_bit_depth, int* const png_color_type) | |
{ | |
/* get image info */ | |
*png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
if (!*png_ptr) | |
{ | |
throw std::runtime_error(PNG_FILE_FORMAT_INVALID); | |
} | |
if (setjmp(png_jmpbuf(*png_ptr))) | |
{ | |
throw std::runtime_error(PNG_FILE_FORMAT_INVALID); | |
} | |
*info_ptr = png_create_info_struct(*png_ptr); | |
if (!info_ptr) | |
{ | |
throw std::runtime_error(PNG_FILE_FORMAT_INVALID); | |
} | |
png_init_io(*png_ptr, f); | |
png_set_sig_bytes(*png_ptr, 8); | |
png_read_info(*png_ptr, *info_ptr); | |
png_get_IHDR(*png_ptr, *info_ptr, png_width, png_height, png_bit_depth, png_color_type, NULL, NULL, NULL); | |
// update the info structure with the new png_ptr changes | |
png_read_update_info(*png_ptr, *info_ptr); | |
} | |
static inline void determineLoadColorTypes(Image::ColorType expected_color_type, int png_color_type, Image::ColorType* const load_color_type, int* const load_png_color_type) | |
{ | |
/* determine load color type */ | |
*load_png_color_type = (expected_color_type == Image::ColorType::None) ? png_color_type : imageColorToPngColor(expected_color_type); | |
*load_color_type = pngColorToImageColor(*load_png_color_type); | |
} | |
static inline void configurePixelLoad(png_structp png_ptr, png_infop info_ptr, const int png_bit_depth, const int png_color_type, const int load_png_color_type) | |
{ | |
/* configure pixel load */ | |
// if the image has bit depth 16, auto convert it to bit depth 8 | |
if (png_bit_depth == 16) | |
{ | |
#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED | |
png_set_scale_16(png_ptr); | |
#else | |
png_set_strip_16(png_ptr); //if scaling not supported, strip off the excess byte instead | |
#endif | |
} | |
// if alpha channel not wanted, strip it if the image has one. | |
if ((png_color_type & PNG_COLOR_MASK_ALPHA) && !(load_png_color_type & PNG_COLOR_MASK_ALPHA)) | |
{ | |
png_set_strip_alpha(png_ptr); | |
} | |
// if expecting an alpha channel and none exists in the image, add a fully opaque alpha value to each pixel | |
if ((load_png_color_type & PNG_COLOR_MASK_ALPHA) && | |
(png_color_type == PNG_COLOR_TYPE_GRAY || png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_PALETTE)) | |
{ | |
png_set_filler(png_ptr, 0xffff, PNG_FILLER_AFTER); | |
} | |
// if multiple pixels are packed per byte, seperate them into seperate bytes cleanly | |
if (png_bit_depth < 8) | |
{ | |
png_set_packing(png_ptr); | |
} | |
// if gray and bit depth is less than 8, up it to 8 | |
if ((png_color_type == PNG_COLOR_TYPE_GRAY || png_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) && png_bit_depth < 8) | |
{ | |
png_set_expand_gray_1_2_4_to_8(png_ptr); | |
} | |
// auto convert palette images into rgb or rgba | |
if (png_color_type == PNG_COLOR_TYPE_PALETTE) | |
{ | |
png_set_palette_to_rgb(png_ptr); | |
} | |
// set transparency to full alpha channels | |
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0) | |
{ | |
png_set_tRNS_to_alpha(png_ptr); | |
} | |
// set rgb image to gray output or vice versa | |
if ((load_png_color_type == PNG_COLOR_TYPE_GRAY || load_png_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) && | |
(png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_RGB_ALPHA || png_color_type == PNG_COLOR_TYPE_PALETTE)) | |
{ | |
// negative weights causes default calculation to be used ((6969 * R + 23434 * G + 2365 * B)/32768) | |
png_set_rgb_to_gray_fixed(png_ptr, 1, -1.0, -1.0); // error action 1 causes no waring warning if image was not actually gray | |
} | |
if ((load_png_color_type == PNG_COLOR_TYPE_RGB || load_png_color_type == PNG_COLOR_TYPE_RGBA) && | |
(png_color_type == PNG_COLOR_TYPE_GRAY || png_color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) | |
{ | |
png_set_gray_to_rgb(png_ptr); | |
} | |
} | |
static inline void pngLoadFinish(FILE** f, png_structpp png_ptr, png_infopp info_ptr) | |
{ | |
png_destroy_read_struct(png_ptr, info_ptr, NULL); | |
if (f != NULL) | |
{ | |
fclose(*f); | |
*f = NULL; | |
} | |
} | |
static inline void loadPixels(png_structp png_ptr, png_bytep start, const std::size_t width, const std::size_t height, const std::size_t row_pixel_offset, const Image::ColorType load_color_type) | |
{ | |
for (std::size_t y = 0; y < height; y++) | |
{ | |
png_bytep row_start = &start[(y * (row_pixel_offset)) * static_cast<std::size_t>(load_color_type)]; | |
png_read_row(png_ptr, row_start, NULL); | |
} | |
} | |
void Image::Load(std::string_view path, const ColorType color_type) | |
{ | |
FILE* f = NULL; | |
png_structp png_ptr = NULL; | |
png_infop info_ptr = NULL; | |
openPngFile(path.data(), &f); | |
try | |
{ | |
png_uint_32 png_width, png_height; | |
int png_bit_depth, png_color_type; | |
getPngFileInfo(f, &png_ptr, &info_ptr, &png_width, &png_height, &png_bit_depth, &png_color_type); | |
Image::ColorType load_color_type; | |
int load_png_color_type; | |
determineLoadColorTypes(color_type, png_color_type, &load_color_type, &load_png_color_type); | |
configurePixelLoad(png_ptr, info_ptr, png_bit_depth, png_color_type, load_png_color_type); | |
/* reserve bytes if needed */ | |
std::size_t width = static_cast<std::size_t>(png_width); | |
std::size_t height = static_cast<std::size_t>(png_height); | |
const std::size_t size = Image::CalculateSize(load_color_type, width, height, 1); | |
this->Reserve(size); | |
loadPixels(png_ptr, this->data.get(), width, height, width, load_color_type); | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
/* apply to class */ | |
this->width = width; | |
this->height = height; | |
this->pages = 1; | |
this->color_type = load_color_type; | |
} | |
catch (std::exception& e) | |
{ | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
throw e; | |
} | |
} | |
void Image::LoadEmplaced(std::string_view path, const std::size_t x, const std::size_t y, const std::size_t z) | |
{ | |
if (x >= this->width || y >= this->height || z >= this->pages) throw std::runtime_error(IMAGE_NOT_FIT); | |
FILE* f = NULL; | |
png_structp png_ptr = NULL; | |
png_infop info_ptr = NULL; | |
openPngFile(path.data(), &f); | |
try | |
{ | |
png_uint_32 png_width, png_height; | |
int png_bit_depth, png_color_type; | |
getPngFileInfo(f, &png_ptr, &info_ptr, &png_width, &png_height, &png_bit_depth, &png_color_type); | |
/* determine if image far pixels fit */ | |
std::size_t width = static_cast<std::size_t>(png_width); | |
std::size_t height = static_cast<std::size_t>(png_height); | |
if (x + width >= this->width || y + height >= this->height) throw std::runtime_error(IMAGE_NOT_FIT); | |
configurePixelLoad(png_ptr, info_ptr, png_bit_depth, png_color_type, imageColorToPngColor(this->color_type)); | |
png_bytep start = &this->data[(this->width* y * static_cast<std::size_t>(this->color_type)) + (x * static_cast<std::size_t>(this->color_type))]; | |
std::size_t row_offset = width + x + (this->width - (width + x)); | |
loadPixels(png_ptr, start, width, height, row_offset, this->color_type); | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
} | |
catch (std::exception& e) | |
{ | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
throw e; | |
} | |
} | |
void Image::Save(std::string_view path) | |
{ | |
if (this->IsEmpty()) throw std::runtime_error(IMAGE_EMPTY); | |
FILE* f = fopen(path.data(), "wb"); | |
if (!f) | |
{ | |
throw std::runtime_error(PNG_FILE_FAIL_CREATE); | |
} | |
png_structp png_ptr = NULL; | |
png_infop info_ptr = NULL; | |
try | |
{ | |
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
if (!png_ptr) | |
{ | |
throw std::runtime_error(PNG_FILE_FAIL_CREATE); | |
} | |
info_ptr = png_create_info_struct(png_ptr); | |
if (!info_ptr) | |
{ | |
throw std::runtime_error(PNG_FILE_FAIL_CREATE); | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
throw std::runtime_error(PNG_FILE_FAIL_CREATE); | |
} | |
png_init_io(png_ptr, f); | |
const int png_color_type = imageColorToPngColor(this->color_type); | |
png_set_IHDR(png_ptr, info_ptr, static_cast<png_uint_32>(this->width), static_cast<png_uint_32>(this->height), 8, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); | |
png_write_info(png_ptr, info_ptr); | |
for (std::size_t y = 0; y < this->height; y++) | |
{ | |
const std::size_t cur_row_start_i = y * this->width * static_cast<std::size_t>(this->color_type); | |
png_write_row(png_ptr, this->data.get() + cur_row_start_i); | |
} | |
png_write_end(png_ptr, NULL); | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
} | |
catch (std::exception& e) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
throw e; | |
} | |
} | |
void Image::Resize(const std::size_t new_width, const std::size_t new_height) | |
{ | |
if (this->IsEmpty()) throw std::runtime_error(IMAGE_EMPTY); | |
if (new_width == 0 || new_height == 0) throw std::runtime_error(INVALID_IMAGE_PROPERTIES); | |
std::size_t new_size = Image::CalculateSize(this->color_type, new_width, new_height, this->pages); | |
if (new_size == 0) throw std::exception(); | |
std::size_t new_page_size = Image::CalculateSize(this->color_type, new_width, new_height, 1); | |
std::size_t cur_size = this->GetSize(); | |
std::size_t cur_page_size = this->GetPageSize(); | |
this->Reserve(new_size); | |
auto resize_page = [&](std::size_t page_i) | |
{ | |
stbir_resize_uint8( | |
this->data.get() + (cur_page_size * page_i), | |
static_cast<int>(this->width), | |
static_cast<int>(this->height), | |
0, | |
this->data.get() + (new_page_size * page_i), | |
new_width, | |
new_height, | |
0, | |
static_cast<std::size_t>(this->color_type)); | |
}; | |
if (new_size > cur_size) | |
{ | |
for (std::size_t page_i = this->pages - 1; page_i-- != 0;) | |
{ | |
resize_page(page_i); | |
} | |
} | |
else | |
{ | |
for (std::size_t page_i = 0; page_i < this->pages; page_i++) | |
{ | |
resize_page(page_i); | |
} | |
} | |
this->width = new_width; | |
this->height = new_height; | |
} | |
void Image::Create(const Image::ColorType color_type, const std::size_t width, const std::size_t height, const std::size_t pages) | |
{ | |
if (color_type == ColorType::None || width == 0 || height == 0 || pages == 0) throw std::runtime_error(INVALID_IMAGE_PROPERTIES); | |
std::size_t size = Image::CalculateSize(color_type, width, height, pages); | |
if (size == 0) throw std::exception(); | |
this->Reserve(size); | |
std::memset(this->data.get(), 0, size); | |
this->width = width; | |
this->height = height; | |
this->pages = pages; | |
} | |
void Image::Create(uint8_t* const pixels, const Image::ColorType color_type, const std::size_t width, const std::size_t height, const std::size_t pages) | |
{ | |
if (color_type == ColorType::None || width == 0 || height == 0 || pages == 0) throw std::runtime_error(INVALID_IMAGE_PROPERTIES); | |
std::size_t size = Image::CalculateSize(color_type, width, height, pages); | |
this->Reserve(size); | |
std::memcpy(this->data.get(), pixels, size); | |
} | |
void Image::Blit(Image& const other, const int x, const int y, const int z) | |
{ | |
if (other.IsEmpty()) throw std::runtime_error(IMAGE_EMPTY); | |
this->Blit(other.data.get(), other.color_type, other.width, other.height, other.pages, x, y, z); | |
} | |
void Image::Blit(std::uint8_t* pixels, ColorType color_type, std::size_t width, std::size_t height, std::size_t pages, int x, int y, int z) | |
{ | |
if (this->IsEmpty()) throw std::runtime_error(IMAGE_EMPTY); | |
if (this->color_type != color_type) throw std::runtime_error(IMAGE_NOT_SAME); | |
int image_sx = std::max(0, x); // start x position to write to this image | |
if (image_sx >= this->width) return; | |
int emplace_sx = (image_sx == x) ? 0 : 0 - x; // start x position to read from emplace image | |
if (emplace_sx >= width) return; | |
int image_sy = std::max(0, y); // start y position to write to this image | |
if (image_sy >= this->height) return; | |
int emplace_sy = (image_sy == y) ? 0 : 0 - y; // start y position to read from emplace image | |
if (emplace_sy >= height) return; | |
int image_sz = std::max(0, z); // start y position to write to this image | |
if (image_sz >= this->height) return; | |
const int emplace_sz = (image_sz == z) ? 0 : 0 - z; // start y position to read from emplace image | |
if (emplace_sz >= pages) return; | |
int image_ex = std::min(static_cast<int>(this->width), image_sx + static_cast<int>(width)); // 1 more than end x position to write to this image | |
int image_ey = std::min(static_cast<int>(this->height), image_sy + static_cast<int>(height)); // 1 more than end y position to write to this image | |
int image_ez = std::min(static_cast<int>(this->pages), image_sz + static_cast<int>(pages)); // 1 more than end z position to write to this image | |
for ( | |
std::size_t image_x = static_cast<std::size_t>(image_sx), | |
emplace_x = static_cast<std::size_t>(emplace_sx); | |
image_x < static_cast<std::size_t>(image_ex); | |
image_x++, | |
emplace_x++ | |
) | |
{ | |
for ( | |
std::size_t image_y = static_cast<std::size_t>(image_sy), | |
emplace_y = static_cast<std::size_t>(emplace_sy); | |
image_y < static_cast<std::size_t>(image_ey); | |
image_y++, | |
emplace_y++ | |
) | |
{ | |
for ( | |
std::size_t image_z = static_cast<std::size_t>(image_sz), | |
emplace_z = static_cast<std::size_t>(emplace_sz); | |
image_z < image_ez; | |
image_z++, | |
emplace_z++ | |
) | |
{ | |
for (std::size_t channel = 0; channel < static_cast<std::size_t>(this->color_type); channel++) | |
{ | |
const std::size_t image_index = | |
(((image_x)+ | |
(image_y * this->width) + | |
(image_z * this->width * this->height)) | |
* static_cast<std::size_t>(this->color_type)) + | |
channel; | |
const std::size_t emplace_index = | |
(((emplace_x)+ | |
(emplace_y * width) + | |
(emplace_z * width * height)) | |
* static_cast<std::size_t>(color_type)) + | |
channel; | |
this->data[image_index] = data[emplace_index]; | |
} | |
} | |
} | |
} | |
} | |
void Image::Clear() | |
{ | |
std::size_t size = this->GetSize(); | |
std::memset(this->data.get(), 0, size); | |
} | |
void Image::Destroy() | |
{ | |
this->color_type = Image::ColorType::None; | |
this->width = 0; | |
this->height = 0; | |
this->pages = 0; | |
} | |
void Image::Reserve(const std::size_t size) | |
{ | |
if (this->capacity < size) | |
{ | |
if (this->IsEmpty()) | |
{ | |
this->data = std::make_unique<uint8_t[]>(size); | |
} | |
else | |
{ | |
auto new_data = std::make_unique<uint8_t[]>(size); | |
std::memcpy(new_data.get(), this->data.get(), size); | |
this->data = std::move(new_data); | |
} | |
this->capacity = size; | |
} | |
} | |
void Image::TrimToFit() | |
{ | |
std::size_t size = this->GetSize(); | |
if (size < this->capacity) | |
{ | |
if (this->IsEmpty()) | |
{ | |
this->data = nullptr; | |
} | |
else | |
{ | |
auto new_data = std::make_unique<uint8_t[]>(size); | |
std::memcpy(new_data.get(), this->data.get(), size); | |
this->data = std::move(new_data); | |
} | |
this->capacity = size; | |
} | |
} | |
void Image::Convert(const Image::ColorType color_type) | |
{ | |
if (this->IsEmpty()) throw std::runtime_error(IMAGE_EMPTY); | |
if (color_type == Image::ColorType::None) return; | |
std::size_t pixel_count = this->width * this->height * this->pages; | |
std::size_t new_size = pixel_count * static_cast<std::size_t>(color_type); | |
this->Reserve(new_size); | |
switch (this->color_type) | |
{ //loop through all pixels to convert. If input type has less bytes per pixel than output, the whole loop must be reversed. the channels per pixel must be set in reverse in this situation too unless the in color type has one channel per pixel | |
case ColorType::G: | |
switch (color_type) | |
{ | |
case ColorType::G: // G -> G | |
break; | |
case ColorType::GA: // G -> GA | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = i; | |
std::size_t new_i = i * 2; | |
this->data[new_i++] = this->data[old_i]; | |
this->data[new_i] = 255; | |
} | |
break; | |
case ColorType::RGB: // G -> RGB | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = i; | |
std::size_t new_i = i * 3; | |
this->data[new_i++] = this->data[old_i]; // G -> R | |
this->data[new_i++] = this->data[old_i]; // G -> G | |
this->data[new_i] = this->data[old_i]; // G -> B | |
} | |
break; | |
case ColorType::RGBA: // G -> RGBA | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = i; | |
std::size_t new_i = i * 4; | |
this->data[new_i++] = this->data[old_i]; // G -> R | |
this->data[new_i++] = this->data[old_i]; // G -> G | |
this->data[new_i++] = this->data[old_i]; // G -> B | |
this->data[new_i] = 255; // fill A | |
} | |
break; | |
} | |
break; | |
case ColorType::GA: | |
switch (color_type) | |
{ | |
case ColorType::G: // GA -> G | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 2; | |
std::size_t new_i = i; | |
this->data[new_i] = this->data[old_i]; // G -> G | |
} | |
break; | |
case ColorType::GA: // GA -> GA | |
break; | |
case ColorType::RGB: // GA -> RGB | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = i * 2; // alpha is not needed so start on G channel | |
std::size_t new_i = (i * 3) + 2; | |
this->data[new_i--] = this->data[old_i]; // G -> B | |
this->data[new_i--] = this->data[old_i]; // G -> G | |
this->data[new_i] = this->data[old_i]; // G -> R | |
} | |
break; | |
case ColorType::RGBA: // GA -> RGBA | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = (i * 2) + 1; | |
std::size_t new_i = (i * 4) + 3; | |
this->data[new_i--] = this->data[old_i--]; // A -> A | |
this->data[new_i--] = this->data[old_i]; // B -> B | |
this->data[new_i--] = this->data[old_i]; // G -> G | |
this->data[new_i--] = this->data[old_i]; // R -> R | |
} | |
break; | |
} | |
break; | |
case ColorType::RGB: | |
switch (color_type) | |
{ | |
case ColorType::G: // RGB -> G | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 3; | |
std::size_t new_i = i; | |
std::size_t old_r = this->data[old_i++]; | |
std::size_t old_g = this->data[old_i++]; | |
std::size_t old_b = this->data[old_i]; | |
uint8_t gray = colorToGrayscale(old_r, old_g, old_b); | |
this->data[new_i] = gray; | |
} | |
break; | |
case ColorType::GA: // RGB -> GA | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 3; | |
std::size_t new_i = i * 2; | |
std::size_t old_r = this->data[old_i++]; | |
std::size_t old_g = this->data[old_i++]; | |
std::size_t old_b = this->data[old_i]; | |
uint8_t gray = colorToGrayscale(old_r, old_g, old_b); | |
this->data[new_i++] = gray; | |
this->data[new_i] = 255; | |
} | |
break; | |
case ColorType::RGB: // RGB -> RGB | |
break; | |
case ColorType::RGBA: // RGB -> RGBA | |
for (std::size_t i = pixel_count; i-- != 0;) | |
{ | |
std::size_t old_i = (i * 3) + 2; | |
std::size_t new_i = (i * 4) + 3; // these assignments are reversed to prevent errors on the second and third pixels | |
this->data[new_i--] = 255; // A fill | |
this->data[new_i--] = this->data[old_i--]; // B -> B | |
this->data[new_i--] = this->data[old_i--]; // G -> G | |
this->data[new_i] = this->data[old_i]; // R -> R | |
} | |
break; | |
} | |
break; | |
case ColorType::RGBA: | |
switch (color_type) | |
{ | |
case ColorType::G: // RGBA -> G | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 4; | |
std::size_t new_i = i; | |
std::size_t old_r = this->data[old_i++]; | |
std::size_t old_g = this->data[old_i++]; | |
std::size_t old_b = this->data[old_i]; | |
uint8_t gray = colorToGrayscale(old_r, old_g, old_b); | |
this->data[new_i] = gray; | |
} | |
break; | |
case ColorType::GA: // RGBA -> GA | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 4; | |
std::size_t new_i = i * 2; | |
std::size_t old_r = this->data[old_i++]; | |
std::size_t old_g = this->data[old_i++]; | |
std::size_t old_b = this->data[old_i++]; | |
uint8_t gray = colorToGrayscale(old_r, old_g, old_b); | |
this->data[new_i++] = gray; | |
this->data[new_i] = this->data[old_i]; | |
} | |
break; | |
case ColorType::RGB: // RGBA -> RGB | |
for (std::size_t i = 0; i < pixel_count; i++) | |
{ | |
std::size_t old_i = i * 4; | |
std::size_t new_i = i * 3; | |
this->data[new_i++] = this->data[old_i++]; // R -> R | |
this->data[new_i++] = this->data[old_i++]; // G -> G | |
this->data[new_i] = this->data[old_i]; // B -> B | |
} | |
break; | |
case ColorType::RGBA : // RGBA -> RGBA | |
break; | |
} | |
break; | |
} | |
this->color_type = color_type; | |
} | |
bool Image::IsEmpty() const noexcept | |
{ | |
return this->width == 0; | |
} | |
std::size_t Image::GetWidth() const noexcept | |
{ | |
return this->width; | |
} | |
std::size_t Image::GetHeight() const noexcept | |
{ | |
return this->height; | |
} | |
std::size_t Image::GetPages() const noexcept | |
{ | |
return this->pages; | |
} | |
std::size_t Image::GetSize() const noexcept | |
{ | |
return Image::CalculateSize(this->color_type, this->width, this->height, this->pages); | |
} | |
std::size_t Image::GetPageSize() const noexcept | |
{ | |
return Image::CalculateSize(this->color_type, this->width, this->height, 1); | |
} | |
Image::ColorType Image::GetColorType() const noexcept | |
{ | |
return this->color_type; | |
} | |
const uint8_t* Image::GetData() const noexcept | |
{ | |
return this->data.get(); | |
} | |
Image::File::File(std::string_view path) | |
: width(0) | |
, height(0) | |
, bit_depth(0) | |
{ | |
FILE* f = NULL; | |
png_structp png_ptr = NULL; | |
png_infop info_ptr = NULL; | |
try | |
{ | |
openPngFile(path.data(), &f); | |
png_uint_32 png_width, png_height; | |
int png_bit_depth, png_color_type; | |
getPngFileInfo(f, &png_ptr, &info_ptr, &png_width, &png_height, &png_bit_depth, &png_color_type); | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
this->width = static_cast<std::size_t>(png_width); | |
this->height = static_cast<std::size_t>(png_height); | |
this->bit_depth = static_cast<std::size_t>(png_bit_depth); | |
this->color_type = pngColorToImageFileColor(png_color_type); | |
} | |
catch (std::exception& e) | |
{ | |
pngLoadFinish(&f, &png_ptr, &info_ptr); | |
this->color_type = Image::File::ColorType::None; | |
} | |
} | |
bool Image::File::IsEmpty() const noexcept | |
{ | |
return this->color_type == File::ColorType::None; | |
} | |
std::size_t Image::File::GetBitDepth() const noexcept | |
{ | |
return this->bit_depth; | |
} | |
Image::File::ColorType Image::File::GetColorType() const noexcept | |
{ | |
return this->color_type; | |
} | |
std::size_t Image::File::GetWidth() const noexcept | |
{ | |
return this->width; | |
} | |
std::size_t Image::File::GetHeight() const noexcept | |
{ | |
return this->height; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Copyright (c) 2022 Daniel Valcour | |
Permission is hereby granted, free of charge, to any person obtaining a copy of | |
this software and associated documentation files (the "Software"), to deal in | |
the Software without restriction, including without limitation the rights to | |
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
the Software, and to permit persons to whom the Software is furnished to do so, | |
subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#pragma once | |
#include <memory> | |
#include <string> | |
#include <cstdint> | |
// RGBA pixel image (possibly with multiple pages) | |
class Image | |
{ | |
public: | |
enum class ColorType | |
{ | |
None = 0, | |
G = 1, | |
GA = 2, | |
RGB = 3, | |
RGBA = 4 | |
}; | |
static constexpr std::size_t BYTES_PER_CHANNEL = sizeof(uint8_t); | |
static constexpr std::size_t BIT_DEPTH = 8; | |
static constexpr std::size_t CalculateSize(const Image::ColorType color_type, const std::size_t width, const std::size_t height, const std::size_t pages = 1) noexcept | |
{ | |
return width * height * pages * static_cast<std::size_t>(color_type) * Image::BYTES_PER_CHANNEL; | |
} | |
class File | |
{ | |
public: | |
enum class ColorType | |
{ | |
None, | |
G, | |
GA, | |
RGB, | |
RGBA, | |
Palette | |
}; | |
private: | |
File::ColorType color_type = File::ColorType::None; | |
std::size_t bit_depth = 0; | |
std::size_t width = 0; | |
std::size_t height = 0; | |
public: | |
constexpr File() noexcept = default; | |
File(std::string_view path); | |
bool IsEmpty() const noexcept; | |
std::size_t GetBitDepth() const noexcept; | |
File::ColorType GetColorType() const noexcept; | |
std::size_t GetWidth() const noexcept; | |
std::size_t GetHeight() const noexcept; | |
}; | |
private: | |
ColorType color_type = ColorType::None; | |
std::size_t width = 0; | |
std::size_t height = 0; | |
std::size_t pages = 0; | |
std::size_t capacity = 0; | |
std::unique_ptr<std::uint8_t[]> data = nullptr; | |
public: | |
constexpr Image() noexcept = default; | |
Image(const Image& other); | |
Image(Image&& other) noexcept; | |
~Image() = default; | |
Image& operator=(const Image& other); | |
Image& operator=(Image&& other) noexcept; | |
void Load(std::string_view path, ColorType color_type = ColorType::None); | |
void LoadEmplaced(std::string_view path, std::size_t x, std::size_t y, std::size_t z = 0); | |
void Save(std::string_view path); | |
void Resize(std::size_t new_width, std::size_t new_height); | |
void Create(Image::ColorType color_type, std::size_t width, std::size_t height, std::size_t pages = 1); | |
void Create(uint8_t* pixels, Image::ColorType color_type, std::size_t width, std::size_t height, std::size_t pages = 1); | |
void Blit(Image& other, int x, int y, int z = 0); | |
void Blit(std::uint8_t* pixels, ColorType color_type, std::size_t width, std::size_t height, std::size_t pages, int x, int y, int z); | |
void Clear(); | |
void Destroy(); | |
void Reserve(std::size_t size); | |
void TrimToFit(); | |
void Convert(Image::ColorType color_type); | |
bool IsEmpty() const noexcept; | |
std::size_t GetWidth() const noexcept; | |
std::size_t GetHeight() const noexcept; | |
std::size_t GetPages() const noexcept; | |
std::size_t GetSize() const noexcept; | |
std::size_t GetPageSize() const noexcept; | |
Image::ColorType GetColorType() const noexcept; | |
const std::uint8_t* GetData() const noexcept; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment