Last active
October 16, 2021 23:33
-
-
Save Journeyman1337/e1e11a36f427f6e7b4908e212884dc41 to your computer and use it in GitHub Desktop.
libpng read and write
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
#include "Image.hpp" | |
#include "libpng_ext.hpp" | |
#include <png.h> | |
#include <exception> | |
#include <memory> | |
#include <algorithm> | |
#include <filesystem> | |
#include <exception> | |
#include <fmt/format.h> | |
#include "log.hpp" | |
#include "except.hpp" | |
#include <magic_enum.hpp> | |
#include <stb_image_resize.h> | |
constexpr int jmk::Image::GetColorTypeElementsPerPixel(const jmk::Image::ColorType& color_type) | |
{ | |
switch (color_type) | |
{ | |
case jmk::Image::ColorType::G: | |
return 1; | |
case jmk::Image::ColorType::GA: | |
return 2; | |
case jmk::Image::ColorType::RGB: | |
return 3; | |
case jmk::Image::ColorType::RGBA: | |
return 4; | |
default: | |
return 0; | |
} | |
} | |
jmk::Image::Image() noexcept | |
: width(0) | |
, height(0) | |
, color_type(jmk::Image::ColorType::Default) | |
, page_count(0) | |
, pixel_count(0) | |
, pixel_data_size(0) | |
, pixel_data(nullptr) | |
, capacity(0) | |
{ | |
} | |
jmk::Image::Image(const size_t capacity) | |
: width(0) | |
, height(0) | |
, color_type(jmk::Image::ColorType::Default) | |
, page_count(0) | |
, pixel_count(0) | |
, pixel_data_size(0) | |
, pixel_data(new uint8_t[capacity]) | |
, capacity(capacity) | |
{ | |
} | |
jmk::Image::Image(const int width, const int height, const int page_count, const jmk::Image::ColorType color_type) | |
{ | |
if (width <= 0 || height <= 0 || page_count <= 0) | |
{ | |
throw jmk::Exception("invalid image create dimensions"); | |
} | |
this->width = static_cast<size_t>(width); | |
this->height = static_cast<size_t>(height); | |
this->page_count = static_cast<size_t>(page_count); | |
this->color_type = color_type; | |
this->pixel_count = this->width * this->height * this->page_count; | |
this->pixel_data_size = this->pixel_count * jmk::Image::GetColorTypeElementsPerPixel(color_type); | |
this->capacity = this->pixel_data_size; | |
this->pixel_data = std::make_unique<uint8_t[]>(this->capacity); | |
} | |
jmk::Image::Image(std::string_view path, const jmk::Image::ColorType color_type) | |
{ | |
std::filesystem::path fpath(path); | |
if (!std::filesystem::exists(fpath) || !std::filesystem::is_regular_file(fpath)) | |
{ | |
throw jmk::Exception("file not exist: Path=\"{}\"", path); | |
} | |
this->color_type = color_type; | |
this->page_count = 1; | |
this->pixel_data = std::unique_ptr<uint8_t[]>(jmk::ext::libpng::load_png(path, &this->width, &this->height, &this->pixel_count, &this->pixel_data_size, this->color_type)); | |
this->capacity = this->pixel_data_size; | |
} | |
jmk::Image::Image(const Image& other) noexcept | |
: width(other.width) | |
, height(other.height) | |
, page_count(other.page_count) | |
, color_type(other.color_type) | |
, pixel_data_size(other.pixel_data_size) | |
, pixel_count(other.pixel_count) | |
, capacity(other.capacity) | |
{ | |
this->pixel_data = std::make_unique<uint8_t[]>(this->pixel_data_size); | |
std::memcpy(this->pixel_data.get(), other.pixel_data.get(), this->pixel_data_size); | |
} | |
jmk::Image::Image(Image&& other) noexcept | |
{ | |
if (this != &other) | |
{ | |
this->width = other.width; | |
this->height = other.height; | |
this->page_count = other.page_count; | |
this->color_type = other.color_type; | |
this->pixel_count = other.pixel_count; | |
this->pixel_data_size = other.pixel_data_size; | |
this->pixel_data = std::move(other.pixel_data); | |
this->capacity = other.capacity; | |
other.width = 0; | |
other.height = 0; | |
other.page_count = 0; | |
other.color_type = jmk::Image::ColorType::Default; | |
other.pixel_count = 0; | |
other.pixel_data_size = 0; | |
other.capacity = 0; | |
} | |
} | |
jmk::Image& jmk::Image::operator=(const Image& other) noexcept | |
{ | |
this->width = other.width; | |
this->height = other.height; | |
this->page_count = other.page_count; | |
this->pixel_count = other.pixel_count; | |
this->capacity = other.capacity; | |
this->color_type = other.color_type; | |
this->pixel_data_size = other.pixel_data_size; | |
this->pixel_data = std::make_unique<uint8_t[]>(this->pixel_data_size); | |
std::memcpy(this->pixel_data.get(), other.pixel_data.get(), this->pixel_data_size); | |
return *this; | |
} | |
jmk::Image& jmk::Image::operator=(Image&& other) noexcept | |
{ | |
if (this != &other) | |
{ | |
this->pixel_data = std::move(other.pixel_data); | |
this->width = other.width; | |
this->height = other.height; | |
this->color_type = other.color_type; | |
this->page_count = other.page_count; | |
this->pixel_data_size = other.pixel_data_size; | |
this->pixel_count = other.pixel_count; | |
this->capacity = other.capacity; | |
other.width = 0; | |
other.height = 0; | |
other.color_type = jmk::Image::ColorType::Default; | |
other.page_count = 0; | |
other.pixel_data_size = 0; | |
other.pixel_count = 0; | |
other.capacity = 0; | |
} | |
return *this; | |
} | |
int jmk::Image::GetWidth() const noexcept | |
{ | |
return static_cast<int>(this->width); | |
} | |
int jmk::Image::GetHeight() const noexcept | |
{ | |
return static_cast<int>(this->height); | |
} | |
int jmk::Image::GetPageCount() const noexcept | |
{ | |
return static_cast<int>(this->page_count); | |
} | |
size_t jmk::Image::GetCapacity() const noexcept | |
{ | |
return this->capacity; | |
} | |
void jmk::Image::Reserve(const size_t capacity) | |
{ | |
this->capacity += capacity; | |
auto new_pixels = std::make_unique<uint8_t[]>(this->capacity); | |
std::memcpy(new_pixels.get(), this->pixel_data.get(), this->pixel_data_size); | |
this->pixel_data = std::move(new_pixels); | |
} | |
void jmk::Image::TrimCapacity() | |
{ | |
if (this->capacity < this->pixel_data_size) | |
{ | |
this->capacity = this->pixel_data_size; | |
auto new_pixels = std::make_unique<uint8_t[]>(this->capacity); | |
std::memcpy(new_pixels.get(), this->pixel_data.get(), this->pixel_data_size); | |
this->pixel_data = std::move(new_pixels); | |
} | |
} | |
jmk::Image::ColorType jmk::Image::GetColorType() const noexcept | |
{ | |
return this->color_type; | |
} | |
uint8_t* jmk::Image::GetPixelData() const noexcept | |
{ | |
return this->pixel_data.get(); | |
} | |
int jmk::Image::GetPixelCount() const noexcept | |
{ | |
return static_cast<int>(this->pixel_count); | |
} | |
size_t jmk::Image::GetDataSize() const noexcept | |
{ | |
return this->pixel_data_size; | |
} | |
bool jmk::Image::IsEmpty() const noexcept | |
{ | |
return this->width == 0; | |
} | |
void jmk::Image::Clear() noexcept | |
{ | |
std::memset(this->pixel_data.get(), 0, this->pixel_data_size); | |
} | |
void jmk::Image::VerticalFlip() noexcept | |
{ | |
size_t elements_per_pixel = jmk::Image::GetColorTypeElementsPerPixel(this->color_type); | |
size_t bytes_per_row = this->width * elements_per_pixel; | |
uint8_t* pixels = this->pixel_data.get(); | |
uint8_t temp[2048]; | |
for (size_t page_i = 0; page_i < this->page_count; page_i++) | |
{ | |
for (size_t row_i = 0; row_i < (this->height >> 1); row_i++) | |
{ | |
uint8_t* row0 = pixels + row_i * bytes_per_row; | |
uint8_t* row1 = pixels + (this->height - row_i - 1) * bytes_per_row; | |
// swap row0 with row1 | |
size_t bytes_left = bytes_per_row; | |
while (bytes_left) | |
{ | |
size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); | |
memcpy(temp, row0, bytes_copy); | |
memcpy(row0, row1, bytes_copy); | |
memcpy(row1, temp, bytes_copy); | |
row0 += bytes_copy; | |
row1 += bytes_copy; | |
bytes_left -= bytes_copy; | |
} | |
pixels += this->width * this->height * elements_per_pixel; | |
} | |
} | |
} | |
static inline void resize_page(const size_t page_i, const uint8_t* in, const size_t in_width, const size_t in_height, const size_t in_page_size, uint8_t* const out, const size_t out_width, const size_t out_height, const size_t out_page_size, const size_t elements_per_pixel) | |
{ | |
int stb_result = stbir_resize_uint8(&in[in_page_size * page_i], in_width, in_height, 0, | |
&out[out_page_size * page_i], out_width, out_height, 0, | |
elements_per_pixel); | |
if (stb_result == 0) | |
{ | |
throw jmk::Exception("image resize failure"); | |
} | |
} | |
void jmk::Image::Resize(const int width, const int height, bool trim_capacity) | |
{ | |
if (width <= 0 || height <= 0) | |
{ | |
throw jmk::Exception("invalid image resize dimensions"); | |
} | |
size_t elements_per_pixel = static_cast<size_t>(jmk::Image::GetColorTypeElementsPerPixel(this->color_type)); | |
size_t new_pixel_page_size = static_cast<size_t>(width) * static_cast<size_t>(height) * elements_per_pixel; | |
size_t new_pixel_data_size = new_pixel_page_size * this->page_count; | |
size_t old_pixel_page_size = this->width * this->height * elements_per_pixel; | |
const bool realloc = trim_capacity || this->capacity < new_pixel_data_size; | |
uint8_t* pixels = this->pixel_data.get(); | |
if (realloc) | |
{ | |
pixels = new uint8_t[new_pixel_data_size]; | |
} | |
if (!realloc || this->pixel_data_size < new_pixel_data_size) | |
{ | |
for (size_t page_i = 0; page_i < this->page_count; page_i++) | |
{ | |
resize_page(page_i, this->pixel_data.get(), this->width, this->height, old_pixel_page_size, pixels, width, height, new_pixel_page_size, elements_per_pixel); | |
} | |
} | |
else //in reverse to prevent pages from overwritting pixels of the next ones. | |
{ | |
for (int page_i = this->page_count - 1; page_i >= 0; page_i--) | |
{ | |
resize_page(static_cast<size_t>(page_i), this->pixel_data.get(), this->width, this->height, old_pixel_page_size, pixels, width, height, new_pixel_page_size, elements_per_pixel); | |
} | |
} | |
this->width = static_cast<size_t>(width); | |
this->height = static_cast<size_t>(height); | |
this->pixel_data_size = new_pixel_data_size; | |
if (realloc) | |
{ | |
this->pixel_data = std::unique_ptr<uint8_t[]>(pixels); | |
} | |
} | |
jmk::Image jmk::Image::GetResized(const int width, const int height) | |
{ | |
if (width <= 0 || height <= 0) | |
{ | |
throw jmk::Exception("invalid image resize dimensions"); | |
} | |
auto ret = jmk::Image(width, height, this->page_count, this->color_type); | |
size_t elements_per_pixel = static_cast<size_t>(jmk::Image::GetColorTypeElementsPerPixel(this->color_type)); | |
size_t new_pixel_page_size = static_cast<size_t>(width) * static_cast<size_t>(height) * elements_per_pixel; | |
size_t this_pixel_page_size = this->width * this->height * elements_per_pixel; | |
for (size_t page_i = 0; page_i < this->page_count; page_i++) | |
{ | |
int stb_result = stbir_resize_uint8(&this->pixel_data[this_pixel_page_size * page_i], this->width, this->height, 0, | |
&ret.pixel_data[new_pixel_page_size * page_i], ret.width, ret.height, 0, | |
elements_per_pixel); | |
if (stb_result == 0) | |
{ | |
throw jmk::Exception("image resize failure"); | |
} | |
} | |
return ret; | |
} | |
void jmk::Image::WriteToPNG(std::string_view path, const int page) const | |
{ | |
if (page < 0 || page >= this->page_count) | |
{ | |
throw jmk::Exception("png write invalid page: Path=\"{}\"", path); | |
} | |
jmk::ext::libpng::save_png(path, this->width, this->height, this->color_type, this->pixel_data.get()); | |
} | |
void jmk::Image::Emplace(const Image& to_emplace, const int x, const int y, const int z) | |
{ | |
if (((to_emplace.color_type == jmk::Image::ColorType::G || to_emplace.color_type == jmk::Image::ColorType::GA) && this->color_type != jmk::Image::ColorType::G && this->color_type != jmk::Image::ColorType::GA) || | |
((to_emplace.color_type == jmk::Image::ColorType::RGB || to_emplace.color_type == jmk::Image::ColorType::RGBA) && this->color_type != jmk::Image::ColorType::RGB && this->color_type != jmk::Image::ColorType::RGBA)) | |
{ | |
throw jmk::Exception("emplace incompatible images"); | |
} | |
const int this_sx = std::max(0, x); // start x position to write to this image | |
if (this_sx >= this->width) return; | |
const int emplace_sx = (this_sx == x) ? 0 : 0 - x; // start x position to read from emplace image | |
if (emplace_sx >= to_emplace.width) return; | |
const int this_sy = std::max(0, y); // start y position to write to this image | |
if (this_sy >= this->height) return; | |
const int emplace_sy = (this_sy == y) ? 0 : 0 - y; // start y position to read from emplace image | |
if (emplace_sy >= to_emplace.height) return; | |
const int this_sz = std::max(0, z); // start y position to write to this image | |
if (this_sz >= this->height) return; | |
const int emplace_sz = (this_sz == z) ? 0 : 0 - z; // start y position to read from emplace image | |
if (emplace_sz >= to_emplace.page_count) return; | |
const int this_ex = std::min(this->width, this_sx + to_emplace.width); // 1 more than end x position to write to this image | |
const int this_ey = std::min(this->height, this_sy + to_emplace.height); // 1 more than end y position to write to this image | |
const int this_ez = std::min(this->page_count, this_sz + to_emplace.page_count); // 1 more than end z position to write to this image | |
const int this_element_count = jmk::Image::GetColorTypeElementsPerPixel(this->color_type); | |
const int to_emplace_element_count = jmk::Image::GetColorTypeElementsPerPixel(to_emplace.color_type); | |
const int elements = std::min(this_element_count, to_emplace_element_count); // the amount of channels to emplace | |
const size_t opaque_alpha_index = (elements == 3 && this_element_count == 4) ? 3 : (elements == 1 && this_element_count == 2) ? 1 : 0; // if an opaque alpha channel is needed to be emplaced per pixel due to unmatching channel count | |
for ( | |
size_t this_x = static_cast<size_t>(this_sx), | |
emplace_x = static_cast<size_t>(emplace_sx); | |
this_x < this_ex; | |
this_x++, | |
emplace_x++ | |
) | |
{ | |
for ( | |
size_t this_y = static_cast<size_t>(this_sy), | |
emplace_y = static_cast<size_t>(emplace_sy); | |
this_y < this_ey; | |
this_y++, | |
emplace_y++ | |
) | |
{ | |
for ( | |
size_t this_z = static_cast<size_t>(this_sz), | |
emplace_z = static_cast<size_t>(emplace_sz); | |
this_z < this_ez; | |
this_z++, | |
emplace_z++ | |
) | |
{ | |
for (size_t element = 0; element < element; element++) | |
{ | |
const size_t this_index = | |
(((this_x)+ | |
(this_y * this->width) + | |
(this_z * this->width * this->height)) | |
* this_element_count) + | |
element; | |
const size_t emplace_index = | |
(((emplace_x)+ | |
(emplace_y * to_emplace.width) + | |
(emplace_z * to_emplace.width * to_emplace.height)) | |
* to_emplace_element_count) + | |
element; | |
this->pixel_data[this_index] = to_emplace.pixel_data[emplace_index]; | |
} | |
if (opaque_alpha_index != 0) | |
{ | |
const size_t this_index = | |
(((this_x)+ | |
(this_y * this->width) + | |
(this_z * this->width * this->height)) | |
* this_element_count) + | |
opaque_alpha_index; | |
this->pixel_data[this_index] = 255; | |
} | |
} | |
} | |
} | |
} |
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
#pragma once | |
#include <memory> | |
#include <string> | |
#include <cstdint> | |
namespace jmk | |
{ | |
class Font; | |
class Image | |
{ | |
friend class Font; //for rendering glyphs | |
public: | |
enum class ColorType : uint8_t | |
{ | |
G, | |
GA, | |
RGB, | |
RGBA, | |
Default = RGBA | |
}; | |
static constexpr int GetColorTypeElementsPerPixel(const jmk::Image::ColorType& color_type); | |
private: | |
std::unique_ptr<uint8_t[]> pixel_data; | |
size_t width; | |
size_t height; | |
jmk::Image::ColorType color_type; | |
size_t page_count; | |
size_t pixel_data_size; | |
size_t pixel_count; | |
size_t capacity; | |
public: | |
Image() noexcept; | |
Image(const size_t capacity); | |
Image(const int width, const int height, const int page_count = 1, const jmk::Image::ColorType color_type = jmk::Image::ColorType::Default); | |
Image(std::string_view path, const jmk::Image::ColorType color_type = jmk::Image::ColorType::Default); | |
Image(const Image& other) noexcept; | |
Image(Image&& other) noexcept; | |
~Image() = default; | |
Image& operator=(const Image& other) noexcept; | |
Image& operator=(Image&& other) noexcept; | |
// get the pixel width | |
int GetWidth() const noexcept; | |
// get the pixel height | |
int GetHeight() const noexcept; | |
// get the amount of pages | |
int GetPageCount() const noexcept; | |
size_t GetCapacity() const noexcept; | |
void Reserve(const size_t capacity); | |
void TrimCapacity(); | |
jmk::Image::ColorType GetColorType() const noexcept; | |
// get a pointer to the image pixel data | |
uint8_t* GetPixelData() const noexcept; | |
// get the amount of pixels in the image | |
int GetPixelCount() const noexcept; | |
// get the size of the image pixel data in bytes. | |
size_t GetDataSize() const noexcept; | |
// is this an empty image with no pixel data? | |
bool IsEmpty() const noexcept; | |
//clear the pixels to 0 | |
void Clear() noexcept; | |
//flip the image vertially | |
void VerticalFlip() noexcept; | |
// change the width and height of the image | |
void Resize(const int width, const int height, bool trim_capacity = false); | |
// get a new resized variant of this image. | |
jmk::Image GetResized(const int width, const int height); | |
// convert the image to a different color type | |
void Convert(const jmk::Image::ColorType color_type); | |
// emplace an image in another image, starting at the given coordinates | |
void Emplace(const Image& to_emplace, const int x, const int y, const int z); | |
// extract an image from this image at a given position | |
jmk::Image Extract(const int x, const int y, const int z, const int width, const int height, const int depth); | |
// crop this image | |
void Crop(const int x, const int y, const int z, const int width, const int height, const int depth); | |
// get a cropped image from this image | |
jmk::Image GetCrop(const int x, const int y, const int z, const int width, const int height, const int depth); | |
// turn this image into an sdf image (must be ColorType::G). | |
void MakeSDF(const int passes = 2); | |
// get an sdf variant of this image (must be ColorType::G). | |
jmk::Image GetSDF(const int passes = 2); | |
// write an image to a png file. | |
void WriteToPNG(std::string_view path, const int page = 0) const; // TODO customize filtering and compression | |
}; | |
} |
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
#include "libpng_ext.hpp" | |
#include "log.hpp" | |
#include "except.hpp" | |
void jmk::ext::libpng::user_error_fn(png_structp png_ptr, png_const_charp error_msg) | |
{ | |
libpng_err* err = reinterpret_cast<libpng_err*>(png_get_error_ptr(png_ptr)); | |
err->errors.push_back(reinterpret_cast<const char*>(error_msg)); | |
} | |
void jmk::ext::libpng::user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) | |
{ | |
libpng_err* err = reinterpret_cast<libpng_err*>(png_get_error_ptr(png_ptr)); | |
err->warnings.push_back(reinterpret_cast<const char*>(warning_msg)); | |
} | |
constexpr jmk::Image::ColorType jmk::ext::libpng::libpngColorTypeToImageColorType(const png_byte png_color_type) | |
{ | |
switch (png_color_type) | |
{ | |
case PNG_COLOR_TYPE_GRAY: | |
return jmk::Image::ColorType::G; | |
case PNG_COLOR_TYPE_GRAY_ALPHA: | |
return jmk::Image::ColorType::GA; | |
case PNG_COLOR_TYPE_RGB: | |
return jmk::Image::ColorType::RGB; | |
case PNG_COLOR_TYPE_RGBA: | |
return jmk::Image::ColorType::RGBA; | |
case PNG_COLOR_TYPE_PALETTE: | |
return jmk::Image::ColorType::RGB; | |
default: | |
return jmk::Image::ColorType::Default; | |
} | |
} | |
constexpr png_byte jmk::ext::libpng::ImageColorTypeToLibpngColorType(const jmk::Image::ColorType color_type) | |
{ | |
switch (color_type) | |
{ | |
case jmk::Image::ColorType::G: | |
return PNG_COLOR_TYPE_GRAY; | |
case jmk::Image::ColorType::GA: | |
return PNG_COLOR_TYPE_GRAY_ALPHA; | |
case jmk::Image::ColorType::RGB: | |
return PNG_COLOR_TYPE_RGB; | |
case jmk::Image::ColorType::RGBA: | |
return PNG_COLOR_TYPE_RGBA; | |
default: | |
return PNG_COLOR_TYPE_RGBA; | |
} | |
} | |
uint8_t* jmk::ext::libpng::load_png(std::string_view path, size_t* const width, size_t* const height, size_t* const pixel_count, size_t* const pixel_data_size, const jmk::Image::ColorType color_type) | |
{ | |
char header[8]; | |
FILE* f = fopen(path.data(), "rb"); | |
if (!f) | |
{ | |
throw jmk::Exception("fail open file: Path=\"{}\"", path); | |
} | |
fread(header, 1, 8, f); | |
if (png_sig_cmp((png_const_bytep)&header[0], 0, 8)) | |
{ | |
fclose(f); | |
throw jmk::Exception("file not png: Path=\"{}\"", path); | |
} | |
const char* generic_failure_log = "fail read png: Path=\"{}\""; | |
auto err = jmk::ext::libpng::libpng_err(path); | |
png_uint_32 png_width, png_height; | |
int png_bit_depth, png_color_type, png_interlace_type, png_compression_method, png_filter_method; | |
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, &err, jmk::ext::libpng::user_error_fn, jmk::ext::libpng::user_warning_fn); | |
if (!png_ptr) | |
{ | |
err.log_all(); | |
fclose(f); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_infop info_ptr = png_create_info_struct(png_ptr); | |
if (!info_ptr) | |
{ | |
err.log_all(); | |
fclose(f); | |
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
err.log_all(); | |
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); | |
fclose(f); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
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, &png_interlace_type, &png_compression_method, &png_filter_method); | |
// if the image has bit depth 16, auto convert it to bit depth 8 | |
#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 (color_type == jmk::Image::ColorType::RGB || color_type == jmk::Image::ColorType::G) | |
{ | |
png_set_strip_alpha(png_ptr); | |
} | |
// if gray and bit depth is less than 8, up it to 8 | |
png_set_packing(png_ptr); | |
png_set_packswap(png_ptr); | |
if ((png_color_type == PNG_COLOR_TYPE_GRAY || png_color_type == PNG_COLOR_TYPE_GA) && 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); | |
} | |
// add filled alpha channel if not in the image but wanted to be read | |
if ((color_type == jmk::Image::ColorType::RGBA || color_type == jmk::Image::ColorType::GA) && | |
(png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_GRAY || png_color_type == PNG_COLOR_TYPE_PALETTE)) | |
{ | |
png_set_filler(png_ptr, 0xffff, PNG_FILLER_AFTER); | |
} | |
// 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 background color to white when transparency is removed. | |
if ((color_type == jmk::Image::ColorType::RGB || color_type == jmk::Image::ColorType::G) && | |
(png_color_type == PNG_COLOR_TYPE_RGBA || png_color_type == PNG_COLOR_TYPE_GA)) | |
{ | |
png_color_16 background; | |
background.red = 0xffff; | |
background.green = 0xffff; | |
background.blue = 0xffff; | |
background.gray = 0xffff; | |
png_set_background(png_ptr, &background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); | |
} | |
// set rgb image to gray output | |
if ((color_type == jmk::Image::ColorType::G || color_type == jmk::Image::ColorType::GA) && | |
(png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_RGBA || png_color_type == PNG_COLOR_TYPE_PALETTE)) | |
{ | |
png_set_rgb_to_gray(png_ptr, 2, 1.0, 1.0); // error action 2 causes warning if image was not actually gray | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
err.log_all(); | |
fclose(f); | |
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
const size_t output_bytes_per_pixel = jmk::Image::GetColorTypeElementsPerPixel(color_type); | |
const size_t output_pixel_data_size = static_cast<size_t>(png_width) * static_cast<size_t>(png_height) * output_bytes_per_pixel; | |
uint8_t* pixel_data = new uint8_t[output_pixel_data_size]; | |
{ | |
std::unique_ptr<png_bytep[]> row_pointers = std::make_unique<png_bytep[]>(png_height); | |
for (size_t y = 0; y < png_height; y++) | |
{ | |
size_t cur_row_start_i = y * (png_width * output_bytes_per_pixel); | |
row_pointers[y] = (png_bytep)&pixel_data[cur_row_start_i]; | |
} | |
png_read_image(png_ptr, row_pointers.get()); | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
err.log_all(); | |
delete[] pixel_data; | |
fclose(f); | |
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
err.log_all(); | |
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); | |
fclose(f); | |
*width = png_width; | |
*height = png_height; | |
*pixel_data_size = output_pixel_data_size; | |
return pixel_data; | |
} | |
void jmk::ext::libpng::save_png(std::string_view path, const size_t pixel_width, const size_t pixel_height, jmk::Image::ColorType color_type, uint8_t* const pixel_data) | |
{ | |
const char* generic_failure_log = "fail write png: Path=\"{}\""; | |
FILE* f = fopen(path.data(), "wb"); | |
if (!f) | |
{ | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
if (!png_ptr) | |
{ | |
fclose(f); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_infop info_ptr = png_create_info_struct(png_ptr); | |
if (!info_ptr) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, NULL); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_init_io(png_ptr, f); | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_byte png_color_type = jmk::ext::libpng::ImageColorTypeToLibpngColorType(color_type); | |
png_set_IHDR(png_ptr, info_ptr, pixel_width, pixel_height, 8, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); | |
png_write_info(png_ptr, info_ptr); | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
{ | |
auto row_pointers = std::make_unique<png_bytep[]>(sizeof(png_bytep) * pixel_height); | |
const auto pixel_size = jmk::Image::GetColorTypeElementsPerPixel(color_type); | |
for (size_t y = 0; y < pixel_height; y++) | |
row_pointers[y] = &pixel_data[y * pixel_width * pixel_size]; | |
png_write_image(png_ptr, row_pointers.get()); | |
} | |
if (setjmp(png_jmpbuf(png_ptr))) | |
{ | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
throw jmk::Exception(generic_failure_log, path); | |
} | |
png_write_end(png_ptr, NULL); | |
fclose(f); | |
png_destroy_write_struct(&png_ptr, &info_ptr); | |
} | |
jmk::ext::libpng::libpng_err::libpng_err(std::string_view path) noexcept | |
: errors() | |
, warnings() | |
, path(path) | |
{ | |
} | |
void jmk::ext::libpng::libpng_err::log_all() | |
{ | |
for (auto& warning : this->warnings) | |
{ | |
jmk::log::Warn("[libpng-Warn] {}: Path=\"{}\"", warning, path); | |
} | |
for (auto& error : this->errors) | |
{ | |
jmk::log::Warn("[libpng-Errr] {}: Path=\"{}\"", error); | |
} | |
this->warnings.clear(); | |
this->errors.clear(); | |
} |
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
#pragma once | |
#include <vector> | |
#include <string> | |
#include <png.h> | |
#include "Image.hpp" | |
namespace jmk::ext::libpng | |
{ | |
struct libpng_err | |
{ | |
std::vector<std::string> errors; | |
std::vector<std::string> warnings; | |
std::string path; | |
libpng_err(std::string_view path) noexcept; | |
~libpng_err() = default; | |
void log_all(); | |
}; | |
void user_error_fn(png_structp png_ptr, png_const_charp error_msg); | |
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg); | |
extern constexpr jmk::Image::ColorType libpngColorTypeToImageColorType(const png_byte png_color_type); | |
extern constexpr png_byte ImageColorTypeToLibpngColorType(const jmk::Image::ColorType color_type); | |
uint8_t* load_png(std::string_view path, size_t* const width, size_t* const height, size_t* const pixel_count, size_t* const pixel_data_size, const jmk::Image::ColorType color_type); | |
void save_png(std::string_view path, const size_t pixel_width, const size_t pixel_height, jmk::Image::ColorType color_type, uint8_t* const pixel_data); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment