Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/core/StarImage.cpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarImage.cpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/core/StarImage.cpp')
-rw-r--r--source/core/StarImage.cpp525
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);
+}
+
+}