Skip to content

Instantly share code, notes, and snippets.

@Journeyman1337
Last active January 30, 2022 02:29
Show Gist options
  • Save Journeyman1337/ea61022399e2a2c16231b4611268c45b to your computer and use it in GitHub Desktop.
Save Journeyman1337/ea61022399e2a2c16231b4611268c45b to your computer and use it in GitHub Desktop.
libpng image class
/*
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;
}
/*
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