Skip to content

Instantly share code, notes, and snippets.

@Journeyman1337
Last active October 16, 2021 23:33
Show Gist options
  • Save Journeyman1337/e1e11a36f427f6e7b4908e212884dc41 to your computer and use it in GitHub Desktop.
Save Journeyman1337/e1e11a36f427f6e7b4908e212884dc41 to your computer and use it in GitHub Desktop.
libpng read and write
#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;
}
}
}
}
}
#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
};
}
#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();
}
#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