diff --git a/libs/gfx/include/psemek/gfx/pixmap.hpp b/libs/gfx/include/psemek/gfx/pixmap.hpp index 1a1e9b56..a71657da 100644 --- a/libs/gfx/include/psemek/gfx/pixmap.hpp +++ b/libs/gfx/include/psemek/gfx/pixmap.hpp @@ -27,6 +27,8 @@ namespace psemek::gfx pixmap_rgba read_png(io::istream && is); pixmap_monochrome read_png_monochrome(io::istream && is); + void write_png(pixmap_rgba const & p, io::ostream && os); + template auto to_srgb(util::array pm, float g = 1.f / 2.2f) { diff --git a/libs/gfx/source/png.cpp b/libs/gfx/source/png.cpp index 24d6c9f9..979e1623 100644 --- a/libs/gfx/source/png.cpp +++ b/libs/gfx/source/png.cpp @@ -8,76 +8,123 @@ namespace psemek::gfx { - template - basic_pixmap read_png_impl(io::istream & is, bool monochrome) + namespace { - png_error_ptr error = [](png_structp, png_const_charp str) + + template + basic_pixmap read_png_impl(io::istream & is, bool monochrome) { - throw std::runtime_error(str); - }; + png_error_ptr error = [](png_structp, png_const_charp str) + { + throw std::runtime_error(str); + }; - png_error_ptr warn = [](png_structp, png_const_charp str) + png_error_ptr warn = [](png_structp, png_const_charp str) + { + log::warning() << str; + }; + + auto png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, error, warn); + if (!png) throw std::runtime_error("png_create_read_struct returned null"); + + png_set_error_fn(png, nullptr, error, warn); + + png_infop info = nullptr; + png_infop end_info = nullptr; + + [[maybe_unused]] auto png_dtor = util::at_scope_exit([&]{ + png_destroy_read_struct(&png, &info, &end_info); + }); + + info = png_create_info_struct(png); + if (!info) throw std::runtime_error("png_create_info_struct returned null"); + + end_info = png_create_info_struct(png); + if (!end_info) throw std::runtime_error("png_create_info_struct returned null"); + + png_rw_ptr read = [](png_structp png, png_bytep ptr, size_t count){ + reinterpret_cast(png_get_io_ptr(png))->read(reinterpret_cast(ptr), count); + }; + png_set_read_fn(png, &is, read); + + png_read_info(png, info); + + auto const width = png_get_image_width(png, info); + auto const height = png_get_image_height(png, info); + auto const color_type = png_get_color_type(png, info); + auto const bit_depth = png_get_bit_depth(png, info); + + if (bit_depth == 16) + png_set_strip_16(png); + + if (monochrome && color_type != PNG_COLOR_TYPE_GRAY) + throw std::runtime_error("invalid color type for monochrome PNG"); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png); + + if (color_type == PNG_COLOR_TYPE_RGB) + png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER); + + png_read_update_info(png, info); + + basic_pixmap result({width, height}); + + auto const row_bytes = png_get_rowbytes(png, info); + if (row_bytes != width * sizeof(Pixel)) + throw std::runtime_error("PNG row bytes mismatch"); + + for (std::uint32_t i = 0; i < height; ++i) + png_read_row(png, reinterpret_cast(result.data() + i * width), nullptr); + + return result; + } + + void write_png_impl(pixmap_rgba const & p, io::ostream & os) { - log::warning() << str; - }; + png_error_ptr error = [](png_structp, png_const_charp str) + { + throw std::runtime_error(str); + }; - auto png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, error, warn); - if (!png) throw std::runtime_error("png_create_read_struct returned null"); + png_error_ptr warn = [](png_structp, png_const_charp str) + { + log::warning() << str; + }; - png_set_error_fn(png, nullptr, error, warn); + auto png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, error, warn); + if (!png) throw std::runtime_error("png_create_write_struct returned null"); - png_infop info = nullptr; - png_infop end_info = nullptr; + png_set_error_fn(png, nullptr, error, warn); - [[maybe_unused]] auto png_dtor = util::at_scope_exit([&]{ - png_destroy_read_struct(&png, &info, &end_info); - }); + png_infop info = nullptr; - info = png_create_info_struct(png); - if (!info) throw std::runtime_error("png_create_info_struct returned null"); + [[maybe_unused]] auto png_dtor = util::at_scope_exit([&]{ + png_destroy_write_struct(&png, &info); + }); - end_info = png_create_info_struct(png); - if (!end_info) throw std::runtime_error("png_create_info_struct returned null"); + info = png_create_info_struct(png); + if (!info) throw std::runtime_error("png_create_info_struct returned null"); - png_rw_ptr read = [](png_structp png, png_bytep ptr, size_t count){ - reinterpret_cast(png_get_io_ptr(png))->read(reinterpret_cast(ptr), count); - }; - png_set_read_fn(png, &is, read); + png_rw_ptr write = [](png_structp png, png_bytep ptr, size_t count){ + reinterpret_cast(png_get_io_ptr(png))->write(reinterpret_cast(ptr), count); + }; + png_flush_ptr flush = [](png_structp png){ + reinterpret_cast(png_get_io_ptr(png))->flush(); + }; + png_set_write_fn(png, &os, write, flush); - png_read_info(png, info); + png_set_IHDR(png, info, p.width(), p.height(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - auto const width = png_get_image_width(png, info); - auto const height = png_get_image_height(png, info); - auto const color_type = png_get_color_type(png, info); - auto const bit_depth = png_get_bit_depth(png, info); + png_write_info(png, info); - if (bit_depth == 16) - png_set_strip_16(png); + for (std::uint32_t i = 0; i < p.height(); ++i) + png_write_row(png, reinterpret_cast(p.data() + p.width() * i)); + } - if (monochrome && color_type != PNG_COLOR_TYPE_GRAY) - throw std::runtime_error("invalid color type for monochrome PNG"); - - if (color_type == PNG_COLOR_TYPE_PALETTE) - png_set_palette_to_rgb(png); - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_expand_gray_1_2_4_to_8(png); - - if (color_type == PNG_COLOR_TYPE_RGB) - png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER); - - png_read_update_info(png, info); - - basic_pixmap result({width, height}); - - auto const row_bytes = png_get_rowbytes(png, info); - if (row_bytes != width * sizeof(Pixel)) - throw std::runtime_error("PNG row bytes mismatch"); - - for (std::uint32_t i = 0; i < height; ++i) - png_read_row(png, reinterpret_cast(result.data() + i * width), nullptr); - - return result; } pixmap_rgba read_png(io::istream && is) @@ -90,4 +137,9 @@ namespace psemek::gfx return read_png_impl(is, true); } + void write_png(pixmap_rgba const & p, io::ostream && os) + { + write_png_impl(p, os); + } + }