diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarImage.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/core/StarImage.cpp')
-rw-r--r-- | source/core/StarImage.cpp | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/source/core/StarImage.cpp b/source/core/StarImage.cpp new file mode 100644 index 0000000..bd3e838 --- /dev/null +++ b/source/core/StarImage.cpp @@ -0,0 +1,525 @@ +#include "StarImage.hpp" +#include "StarLogging.hpp" + +#include <png.h> + +namespace Star { + +Image Image::readPng(IODevicePtr device) { + auto logPngError = [](png_structp png_ptr, png_const_charp c) { + Logger::debug("PNG error in file: '%s', %s", (char*)png_get_error_ptr(png_ptr), c); + }; + + auto readPngData = [](png_structp pngPtr, png_bytep data, png_size_t length) { + IODevice* device = (IODevice*)png_get_io_ptr(pngPtr); + device->readFull((char*)data, length); + }; + + png_byte header[8]; + device->readFull((char*)header, sizeof(header)); + + if (png_sig_cmp(header, 0, sizeof(header))) + throw ImageException(strf("File %s is not a png image!", device->deviceName())); + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + throw ImageException("Internal libPNG error"); + + // Use custom warning function to suppress cerr warnings + png_set_error_fn(png_ptr, (png_voidp)device->deviceName().utf8Ptr(), logPngError, logPngError); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + throw ImageException("Internal libPNG error"); + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + throw ImageException("Internal libPNG error"); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + throw ImageException("Internal error reading png."); + } + + png_set_read_fn(png_ptr, device.get(), readPngData); + + // Tell libPNG that we read some of the header. + png_set_sig_bytes(png_ptr, sizeof(header)); + + png_read_info(png_ptr, info_ptr); + + png_uint_32 img_width = png_get_image_width(png_ptr, info_ptr); + png_uint_32 img_height = png_get_image_height(png_ptr, info_ptr); + + png_uint_32 bitdepth = png_get_bit_depth(png_ptr, info_ptr); + png_uint_32 channels = png_get_channels(png_ptr, info_ptr); + + // Color type. (RGB, RGBA, Luminance, luminance alpha... palette... etc) + png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + channels = 3; + bitdepth = 8; + } + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + if (bitdepth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + bitdepth = 8; + } + png_set_gray_to_rgb(png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + channels = 4; + else + channels = 3; + } + + // If the image has a transperancy set, convert it to a full alpha channel + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + channels += 1; + } + + // We don't support 16 bit precision.. so if the image Has 16 bits per channel + // precision... round it down to 8. + if (bitdepth == 16) { + png_set_strip_16(png_ptr); + bitdepth = 8; + } + + if (bitdepth != 8 || (channels != 3 && channels != 4)) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + throw ImageException(strf("Unsupported PNG pixel format in file %s", device->deviceName())); + } + + Image image(img_width, img_height, channels == 3 ? PixelFormat::RGB24 : PixelFormat::RGBA32); + + std::unique_ptr<png_bytep[]> row_ptrs(new png_bytep[img_height]); + size_t stride = img_width * channels; + for (size_t i = 0; i < img_height; ++i) + row_ptrs[i] = (png_bytep)image.data() + (img_height - i - 1) * stride; + + png_read_image(png_ptr, row_ptrs.get()); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + + return image; +} + +tuple<Vec2U, PixelFormat> Image::readPngMetadata(IODevicePtr device) { + auto logPngError = [](png_structp png_ptr, png_const_charp c) { + Logger::debug("PNG error in file: '%s', %s", (char*)png_get_error_ptr(png_ptr), c); + }; + + auto readPngData = [](png_structp pngPtr, png_bytep data, png_size_t length) { + IODevice* device = (IODevice*)png_get_io_ptr(pngPtr); + device->readFull((char*)data, length); + }; + + png_byte header[8]; + device->readFull((char*)header, sizeof(header)); + + if (png_sig_cmp(header, 0, sizeof(header))) + throw ImageException(strf("File %s is not a png image!", device->deviceName())); + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + throw ImageException("Internal libPNG error"); + + // Use custom warning function to suppress cerr warnings + png_set_error_fn(png_ptr, (png_voidp)device->deviceName().utf8Ptr(), logPngError, logPngError); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + throw ImageException("Internal libPNG error"); + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + throw ImageException("Internal libPNG error"); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + throw ImageException("Internal error reading png."); + } + + png_set_read_fn(png_ptr, device.get(), readPngData); + + // Tell libPNG that we read some of the header. + png_set_sig_bytes(png_ptr, sizeof(header)); + + png_read_info(png_ptr, info_ptr); + + png_uint_32 img_width = png_get_image_width(png_ptr, info_ptr); + png_uint_32 img_height = png_get_image_height(png_ptr, info_ptr); + + png_uint_32 bitdepth = png_get_bit_depth(png_ptr, info_ptr); + png_uint_32 channels = png_get_channels(png_ptr, info_ptr); + + // Color type. (RGB, RGBA, Luminance, luminance alpha... palette... etc) + png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + channels = 3; + bitdepth = 8; + } + + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + if (bitdepth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + bitdepth = 8; + } + png_set_gray_to_rgb(png_ptr); + if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + channels = 4; + else + channels = 3; + } + + // If the image has a transperancy set, convert it to a full alpha channel + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + channels += 1; + } + + Vec2U imageSize{img_width, img_height}; + PixelFormat pixelFormat = channels == 3 ? PixelFormat::RGB24 : PixelFormat::RGBA32; + + return make_tuple(imageSize, pixelFormat); +} + +Image Image::filled(Vec2U size, Vec4B color, PixelFormat pf) { + Image image(size, pf); + image.fill(color); + return image; +} + +Image::Image(PixelFormat pf) + : m_data(nullptr), m_width(0), m_height(0), m_pixelFormat(pf) {} + +Image::Image(Vec2U size, PixelFormat pf) + : Image(size[0], size[1], pf) {} + +Image::Image(unsigned width, unsigned height, PixelFormat pf) + : Image(pf) { + reset(width, height, pf); +} + +Image::~Image() { + if (m_data) + Star::free(m_data); +} + +Image::Image(Image const& image) : Image() { + operator=(image); +} + +Image::Image(Image&& image) : Image() { + operator=(move(image)); +} + +Image& Image::operator=(Image const& image) { + reset(image.m_width, image.m_height, image.m_pixelFormat); + memcpy(data(), image.data(), m_width * m_height * bytesPerPixel()); + return *this; +} + +Image& Image::operator=(Image&& image) { + reset(0, 0, m_pixelFormat); + + m_data = take(image.m_data); + m_width = image.m_width; + m_height = image.m_height; + m_pixelFormat = image.m_pixelFormat; + return *this; +} + +void Image::reset(Vec2U size, Maybe<PixelFormat> pf) { + reset(size[0], size[1], pf); +} + +void Image::reset(unsigned width, unsigned height, Maybe<PixelFormat> pf) { + if (!pf) + pf = m_pixelFormat; + + if (m_width == width && m_height == height && m_pixelFormat == *pf) + return; + + size_t imageSize = width * height * Star::bytesPerPixel(*pf); + if (imageSize == 0) { + if (m_data) { + Star::free(m_data); + m_data = nullptr; + } + } else { + uint8_t* newData = nullptr; + if (!m_data) + newData = (uint8_t*)Star::malloc(imageSize); + else + newData = (uint8_t*)Star::realloc(m_data, imageSize); + + if (!newData) + throw MemoryException::format("Could not allocate memory for new Image size %s\n", imageSize); + + m_data = newData; + memset(m_data, 0, imageSize); + } + + m_pixelFormat = *pf; + m_width = width; + m_height = height; +} + +void Image::fill(Vec3B const& c) { + if (bitsPerPixel() == 24) { + for (unsigned y = 0; y < m_height; ++y) + for (unsigned x = 0; x < m_width; ++x) + set24(x, y, c); + } else { + for (unsigned y = 0; y < m_height; ++y) + for (unsigned x = 0; x < m_width; ++x) + set32(x, y, Vec4B(c, 255)); + } +} + +void Image::fill(Vec4B const& c) { + if (bitsPerPixel() == 24) { + for (unsigned y = 0; y < m_height; ++y) + for (unsigned x = 0; x < m_width; ++x) + set24(x, y, c.vec3()); + } else { + for (unsigned y = 0; y < m_height; ++y) + for (unsigned x = 0; x < m_width; ++x) + set32(x, y, c); + } +} + +void Image::fillRect(Vec2U const& pos, Vec2U const& size, Vec3B const& c) { + for (unsigned y = pos[1]; y < pos[1] + size[1] && y < m_height; ++y) + for (unsigned x = pos[0]; x < pos[0] + size[0] && x < m_width; ++x) + set(Vec2U(x, y), c); +} + +void Image::fillRect(Vec2U const& pos, Vec2U const& size, Vec4B const& c) { + for (unsigned y = pos[1]; y < pos[1] + size[1] && y < m_height; ++y) + for (unsigned x = pos[0]; x < pos[0] + size[0] && x < m_width; ++x) + set(Vec2U(x, y), c); +} + +void Image::set(Vec2U const& pos, Vec4B const& c) { + if (pos[0] >= m_width || pos[1] >= m_height) { + throw ImageException(strf("%s out of range in Image::set", pos)); + } else if (bytesPerPixel() == 4) { + size_t offset = pos[1] * m_width * 4 + pos[0] * 4; + m_data[offset] = c[0]; + m_data[offset + 1] = c[1]; + m_data[offset + 2] = c[2]; + m_data[offset + 3] = c[3]; + } else if (bytesPerPixel() == 3) { + size_t offset = pos[1] * m_width * 3 + pos[0] * 3; + m_data[offset] = c[0]; + m_data[offset + 1] = c[1]; + m_data[offset + 2] = c[2]; + } +} + +void Image::set(Vec2U const& pos, Vec3B const& c) { + if (pos[0] >= m_width || pos[1] >= m_height) { + throw ImageException(strf("%s out of range in Image::set", pos)); + } else if (bytesPerPixel() == 4) { + size_t offset = pos[1] * m_width * 4 + pos[0] * 4; + m_data[offset] = c[0]; + m_data[offset + 1] = c[1]; + m_data[offset + 2] = c[2]; + m_data[offset + 3] = 255; + } else if (bytesPerPixel() == 3) { + size_t offset = pos[1] * m_width * 3 + pos[0] * 3; + m_data[offset] = c[0]; + m_data[offset + 1] = c[1]; + m_data[offset + 2] = c[2]; + } +} + +Vec4B Image::get(Vec2U const& pos) const { + Vec4B c; + if (pos[0] >= m_width || pos[1] >= m_height) { + throw ImageException(strf("%s out of range in Image::get", pos)); + } else if (bytesPerPixel() == 4) { + size_t offset = pos[1] * m_width * 4 + pos[0] * 4; + c[0] = m_data[offset]; + c[1] = m_data[offset + 1]; + c[2] = m_data[offset + 2]; + c[3] = m_data[offset + 3]; + } else if (bytesPerPixel() == 3) { + size_t offset = pos[1] * m_width * 3 + pos[0] * 3; + c[0] = m_data[offset]; + c[1] = m_data[offset + 1]; + c[2] = m_data[offset + 2]; + c[3] = 255; + } + return c; +} + +void Image::setrgb(Vec2U const& pos, Vec4B const& c) { + if (m_pixelFormat == PixelFormat::BGR24 || m_pixelFormat == PixelFormat::BGRA32) + set(pos, Vec4B{c[2], c[1], c[0], c[3]}); + else + set(pos, c); +} + +void Image::setrgb(Vec2U const& pos, Vec3B const& c) { + if (m_pixelFormat == PixelFormat::BGR24 || m_pixelFormat == PixelFormat::BGRA32) + set(pos, Vec3B{c[2], c[1], c[0]}); + else + set(pos, c); +} + +Vec4B Image::getrgb(Vec2U const& pos) const { + auto c = get(pos); + if (m_pixelFormat == PixelFormat::BGR24 || m_pixelFormat == PixelFormat::BGRA32) + return Vec4B{c[2], c[1], c[0], c[3]}; + else + return c; +} + +Vec4B Image::clamp(Vec2I const& pos) const { + Vec4B c; + unsigned x = (unsigned)Star::clamp<int>(pos[0], 0, m_width - 1); + unsigned y = (unsigned)Star::clamp<int>(pos[1], 0, m_height - 1); + if (m_width == 0 || m_height == 0) { + return {0, 0, 0, 0}; + } else if (bytesPerPixel() == 4) { + size_t offset = y * m_width * 4 + x * 4; + c[0] = m_data[offset]; + c[1] = m_data[offset + 1]; + c[2] = m_data[offset + 2]; + c[3] = m_data[offset + 3]; + } else if (bytesPerPixel() == 3) { + size_t offset = y * m_width * 3 + x * 3; + c[0] = m_data[offset]; + c[1] = m_data[offset + 1]; + c[2] = m_data[offset + 2]; + c[3] = 255; + } + return c; +} + +Vec4B Image::clamprgb(Vec2I const& pos) const { + auto c = clamp(pos); + if (m_pixelFormat == PixelFormat::BGR24 || m_pixelFormat == PixelFormat::BGRA32) + return Vec4B{c[2], c[1], c[0], c[3]}; + else + return c; +} + +Image Image::subImage(Vec2U const& pos, Vec2U const& size) const { + if (pos[0] + size[0] > m_width || pos[1] + size[1] > m_height) + throw ImageException(strf("call to subImage with pos %s size %s out of image bounds (%s, %s)", pos, size, m_width, m_height)); + + Image sub(size[0], size[1], m_pixelFormat); + + for (unsigned y = 0; y < size[1]; ++y) { + for (unsigned x = 0; x < size[0]; ++x) { + sub.set({x, y}, get(pos + Vec2U(x, y))); + } + } + + return sub; +} + +void Image::copyInto(Vec2U const& min, Image const& image) { + Vec2U max = (min + image.size()).piecewiseMin(size()); + + for (unsigned y = min[1]; y < max[1]; ++y) { + for (unsigned x = min[0]; x < max[0]; ++x) + set(x, y, image.get(Vec2U(x, y) - min)); + } +} + +void Image::drawInto(Vec2U const& min, Image const& image) { + Vec2U max = (min + image.size()).piecewiseMin(size()); + + for (unsigned y = min[1]; y < max[1]; ++y) { + for (unsigned x = min[0]; x < max[0]; ++x) { + Vec4B dest = get(Vec2U(x, y)); + Vec4B src = image.get(Vec2U(x, y) - min); + + Vec3U destMultiplied = Vec3U(dest[0], dest[1], dest[2]) * dest[3] / 255; + Vec3U srcMultiplied = Vec3U(src[0], src[1], src[2]) * src[3] / 255; + + // Src over dest alpha composition + Vec3U over = srcMultiplied + destMultiplied * (255 - src[3]) / 255; + unsigned alpha = src[3] + dest[3] * (255 - src[3]) / 255; + + set(x, y, Vec4B(over[0], over[1], over[2], alpha)); + } + } +} + +Image Image::convert(PixelFormat pixelFormat) const { + Image converted(m_width, m_height, pixelFormat); + converted.copyInto(Vec2U(), *this); + return converted; +} + +void Image::writePng(IODevicePtr device) const { + auto writePngData = [](png_structp pngPtr, png_bytep data, png_size_t length) { + IODevice* device = (IODevice*)png_get_io_ptr(pngPtr); + device->writeFull((char*)data, length); + }; + + auto flushPngData = [](png_structp) {}; + + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + throw ImageException("Internal libPNG error"); + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, nullptr); + throw ImageException("Internal libPNG error"); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + throw ImageException("Internal error reading png."); + } + + unsigned channels = m_pixelFormat == PixelFormat::RGB24 ? 3 : 4; + + png_set_IHDR(png_ptr, + info_ptr, + m_width, + m_height, + 8, + channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + unique_ptr<png_bytep[]> row_ptrs(new png_bytep[m_height]); + size_t stride = m_width * 8 * channels / 8; + for (size_t i = 0; i < m_height; ++i) { + size_t q = (m_height - i - 1) * stride; + row_ptrs[i] = (png_bytep)m_data + q; + } + + png_set_write_fn(png_ptr, device.get(), writePngData, flushPngData); + png_set_rows(png_ptr, info_ptr, row_ptrs.get()); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + png_destroy_write_struct(&png_ptr, &info_ptr); +} + +} |