From 547c9b200d1dff745dee694c20a9f9e8e409a11d Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Fri, 7 May 2021 17:35:00 -0400 Subject: Change image library to `libpng` and improve how palettized textures are extracted (#133) * trying to change to libpng * It works again! * move the libpng abstraction to another file * GetBytesPerPixel * I'm dumb * fix warnings and remove dead code * Fix CRC being called multiples times and clean up code * textureDataRaw * code clean * Optimizations and removal of redundant data moves * ups * run format * use fs::path more * i'm dumb again * Add a Readme * remove texDeclarations * handle tluts better * vtx change and set segment file * small changes * Fix ZAPD ignoring textures declared in a scene in the XML * small cleanup before tluts * paletted images are broken * kinda works for automatic textures * fix automatic ci * TEXTURE_DEBUG * Remove extra `-j` * Fix extraction of ci4 * Finally palettes OK * ups * Move textures map to ZFile * Fix intersection warnings * Add `TlutOffset` attribute * Move Tlut declaration to a later step * Hopefully the last cleanup of this branch * Run format * Always calc hash * run format * Delete stb * fix merge issues * run format --- ZAPD/ImageBackend.cpp | 473 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 ZAPD/ImageBackend.cpp (limited to 'ZAPD/ImageBackend.cpp') diff --git a/ZAPD/ImageBackend.cpp b/ZAPD/ImageBackend.cpp new file mode 100644 index 0000000..0b4bce3 --- /dev/null +++ b/ZAPD/ImageBackend.cpp @@ -0,0 +1,473 @@ +#include "ImageBackend.h" + +#include +#include +#include +#include + +#include "StringHelper.h" + +/* ImageBackend */ + +ImageBackend::~ImageBackend() +{ + FreeImageData(); +} + +void ImageBackend::ReadPng(const char* filename) +{ + FreeImageData(); + + FILE* fp = fopen(filename, "rb"); + if (fp == nullptr) + throw std::runtime_error(StringHelper::Sprintf( + "ImageBackend::ReadPng: Error.\n\t Couldn't open file '%s'.", filename)); + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t Couldn't create png struct."); + + png_infop info = png_create_info_struct(png); + if (!info) + throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t Couldn't create png info."); + + if (setjmp(png_jmpbuf(png))) + throw std::runtime_error("ImageBackend::ReadPng: Error.\n\t setjmp(png_jmpbuf(png))."); + + png_init_io(png, fp); + + png_read_info(png, info); + + width = png_get_image_width(png, info); + height = png_get_image_height(png, info); + colorType = png_get_color_type(png, info); + bitDepth = png_get_bit_depth(png, info); + +#ifdef TEXTURE_DEBUG + printf("Width: %u\n", width); + printf("Height: %u\n", height); + printf("ColorType: "); + switch (colorType) + { + case PNG_COLOR_TYPE_RGBA: + printf("PNG_COLOR_TYPE_RGBA\n"); + break; + + case PNG_COLOR_TYPE_RGB: + printf("PNG_COLOR_TYPE_RGB\n"); + break; + + case PNG_COLOR_TYPE_PALETTE: + printf("PNG_COLOR_TYPE_PALETTE\n"); + break; + + default: + printf("%u\n", colorType); + break; + } + printf("BitDepth: %u\n", bitDepth); + printf("\n"); +#endif + + // Read any color_type into 8bit depth, RGBA format. + // See http://www.libpng.org/pub/png/libpng-manual.txt + + if (bitDepth == 16) + png_set_strip_16(png); + + if (colorType == PNG_COLOR_TYPE_PALETTE) + { + // png_set_palette_to_rgb(png); + isColorIndexed = true; + } + + // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) + png_set_expand_gray_1_2_4_to_8(png); + + /*if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png);*/ + + // These color_type don't have an alpha channel then fill it with 0xff. + /*if(*color_type == PNG_COLOR_TYPE_RGB || + *color_type == PNG_COLOR_TYPE_GRAY || + *color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER);*/ + + if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + + size_t rowBytes = png_get_rowbytes(png, info); + pixelMatrix = (uint8_t**)malloc(sizeof(uint8_t*) * height); + for (size_t y = 0; y < height; y++) + { + pixelMatrix[y] = (uint8_t*)malloc(rowBytes); + } + + png_read_image(png, pixelMatrix); + +#ifdef TEXTURE_DEBUG + printf("rowBytes: %zu\n", rowBytes); + + size_t bytePerPixel = GetBytesPerPixel(); + printf("imgData\n"); + for (size_t y = 0; y < height; y++) + { + for (size_t x = 0; x < width; x++) + { + for (size_t z = 0; z < bytePerPixel; z++) + { + printf("%02X ", pixelMatrix[y][x * bytePerPixel + z]); + } + printf(" "); + } + printf("\n"); + } + printf("\n"); +#endif + + fclose(fp); + + png_destroy_read_struct(&png, &info, nullptr); + + hasImageData = true; +} + +void ImageBackend::ReadPng(const fs::path& filename) +{ + ReadPng(filename.c_str()); +} + +void ImageBackend::WritePng(const char* filename) +{ + assert(hasImageData); + + FILE* fp = fopen(filename, "wb"); + if (!fp) + throw std::runtime_error(StringHelper::Sprintf( + "ImageBackend::WritePng: Error.\n\t Couldn't open file '%s' in write mode.", filename)); + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + throw std::runtime_error("ImageBackend::WritePng: Error.\n\t Couldn't create png struct."); + + png_infop info = png_create_info_struct(png); + if (!info) + throw std::runtime_error("ImageBackend::WritePng: Error.\n\t Couldn't create png info."); + + if (setjmp(png_jmpbuf(png))) + throw std::runtime_error("ImageBackend::WritePng: Error.\n\t setjmp(png_jmpbuf(png))."); + + png_init_io(png, fp); + + png_set_IHDR(png, info, width, height, + bitDepth, // 8, + colorType, // PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + if (isColorIndexed) + { + png_set_PLTE(png, info, static_cast(colorPalette), paletteSize); + +#ifdef TEXTURE_DEBUG + printf("palette\n"); + png_color* aux = (png_color*)colorPalette; + for (size_t y = 0; y < paletteSize; y++) + { + printf("#%02X%02X%02X ", aux[y].red, aux[y].green, aux[y].blue); + if ((y + 1) % 8 == 0) + printf("\n"); + } + printf("\n"); +#endif + + png_set_tRNS(png, info, alphaPalette, paletteSize, nullptr); + } + + png_write_info(png, info); + + // To remove the alpha channel for PNG_COLOR_TYPE_RGB format, + // Use png_set_filler(). + // png_set_filler(png, 0, PNG_FILLER_AFTER); + +#ifdef TEXTURE_DEBUG + size_t bytePerPixel = GetBytesPerPixel(); + printf("imgData\n"); + for (size_t y = 0; y < height; y++) + { + for (size_t x = 0; x < width * bytePerPixel; x++) + { + printf("%02X ", pixelMatrix[y][x]); + } + printf("\n"); + } + printf("\n"); +#endif + + png_write_image(png, pixelMatrix); + png_write_end(png, nullptr); + + fclose(fp); + + png_destroy_write_struct(&png, &info); +} + +void ImageBackend::WritePng(const fs::path& filename) +{ + WritePng(filename.c_str()); +} + +void ImageBackend::SetTextureData(const std::vector>& texData, + uint32_t nWidth, uint32_t nHeight, uint8_t nColorType, + uint8_t nBitDepth) +{ + FreeImageData(); + + width = nWidth; + height = nHeight; + colorType = nColorType; + bitDepth = nBitDepth; + + size_t bytePerPixel = GetBytesPerPixel(); + + pixelMatrix = static_cast(malloc(sizeof(uint8_t*) * height)); + for (size_t y = 0; y < height; y++) + { + pixelMatrix[y] = static_cast(malloc(sizeof(uint8_t*) * width * bytePerPixel)); + for (size_t x = 0; x < width; x++) + { + pixelMatrix[y][x * bytePerPixel + 0] = texData.at(y).at(x).r; + pixelMatrix[y][x * bytePerPixel + 1] = texData.at(y).at(x).g; + pixelMatrix[y][x * bytePerPixel + 2] = texData.at(y).at(x).b; + + if (colorType == PNG_COLOR_TYPE_RGBA) + pixelMatrix[y][x * bytePerPixel + 3] = texData.at(y).at(x).a; + } + } + hasImageData = true; +} + +void ImageBackend::InitEmptyRGBImage(uint32_t nWidth, uint32_t nHeight, bool alpha) +{ + FreeImageData(); + + width = nWidth; + height = nHeight; + colorType = PNG_COLOR_TYPE_RGB; + if (alpha) + colorType = PNG_COLOR_TYPE_RGBA; + bitDepth = 8; // nBitDepth; + + size_t bytePerPixel = GetBytesPerPixel(); + + pixelMatrix = static_cast(malloc(sizeof(uint8_t*) * height)); + for (size_t y = 0; y < height; y++) + { + pixelMatrix[y] = static_cast(calloc(width * bytePerPixel, sizeof(uint8_t*))); + } + + hasImageData = true; +} + +void ImageBackend::InitEmptyPaletteImage(uint32_t nWidth, uint32_t nHeight) +{ + FreeImageData(); + + width = nWidth; + height = nHeight; + colorType = PNG_COLOR_TYPE_PALETTE; + bitDepth = 8; + + size_t bytePerPixel = GetBytesPerPixel(); + + pixelMatrix = (uint8_t**)malloc(sizeof(uint8_t*) * height); + for (size_t y = 0; y < height; y++) + { + pixelMatrix[y] = static_cast(calloc(width * bytePerPixel, sizeof(uint8_t*))); + } + colorPalette = calloc(paletteSize, sizeof(png_color)); + alphaPalette = static_cast(calloc(paletteSize, sizeof(uint8_t))); + + hasImageData = true; + isColorIndexed = true; +} + +RGBAPixel ImageBackend::GetPixel(size_t y, size_t x) const +{ + assert(y < height); + assert(x < width); + assert(!isColorIndexed); + + RGBAPixel pixel; + size_t bytePerPixel = GetBytesPerPixel(); + pixel.r = pixelMatrix[y][x * bytePerPixel + 0]; + pixel.g = pixelMatrix[y][x * bytePerPixel + 1]; + pixel.b = pixelMatrix[y][x * bytePerPixel + 2]; + if (colorType == PNG_COLOR_TYPE_RGBA) + pixel.a = pixelMatrix[y][x * bytePerPixel + 3]; + return pixel; +} + +uint8_t ImageBackend::GetIndexedPixel(size_t y, size_t x) const +{ + assert(y < height); + assert(x < width); + assert(isColorIndexed); + + return pixelMatrix[y][x]; +} + +void ImageBackend::SetRGBPixel(size_t y, size_t x, uint8_t nR, uint8_t nG, uint8_t nB, uint8_t nA) +{ + assert(hasImageData); + assert(y < height); + assert(x < width); + + size_t bytePerPixel = GetBytesPerPixel(); + pixelMatrix[y][x * bytePerPixel + 0] = nR; + pixelMatrix[y][x * bytePerPixel + 1] = nG; + pixelMatrix[y][x * bytePerPixel + 2] = nB; + if (colorType == PNG_COLOR_TYPE_RGBA) + pixelMatrix[y][x * bytePerPixel + 3] = nA; +} + +void ImageBackend::SetGrayscalePixel(size_t y, size_t x, uint8_t grayscale, uint8_t alpha) +{ + assert(hasImageData); + assert(y < height); + assert(x < width); + + size_t bytePerPixel = GetBytesPerPixel(); + pixelMatrix[y][x * bytePerPixel + 0] = grayscale; + pixelMatrix[y][x * bytePerPixel + 1] = grayscale; + pixelMatrix[y][x * bytePerPixel + 2] = grayscale; + if (colorType == PNG_COLOR_TYPE_RGBA) + pixelMatrix[y][x * bytePerPixel + 3] = alpha; +} + +void ImageBackend::SetIndexedPixel(size_t y, size_t x, uint8_t index, uint8_t grayscale) +{ + assert(hasImageData); + assert(y < height); + assert(x < width); + + size_t bytePerPixel = GetBytesPerPixel(); + pixelMatrix[y][x * bytePerPixel + 0] = index; + + assert(index < paletteSize); + png_color* pal = static_cast(colorPalette); + pal[index].red = grayscale; + pal[index].green = grayscale; + pal[index].blue = grayscale; + alphaPalette[index] = 255; +} + +void ImageBackend::SetPaletteIndex(size_t index, uint8_t nR, uint8_t nG, uint8_t nB, uint8_t nA) +{ + assert(isColorIndexed); + assert(index < paletteSize); + + png_color* pal = static_cast(colorPalette); + pal[index].red = nR; + pal[index].green = nG; + pal[index].blue = nB; + alphaPalette[index] = nA; +} + +void ImageBackend::SetPalette(const ImageBackend& pal) +{ + assert(isColorIndexed); + size_t bytePerPixel = pal.GetBytesPerPixel(); + + for (size_t y = 0; y < pal.height; y++) + { + for (size_t x = 0; x < pal.width; x++) + { + uint8_t r = pal.pixelMatrix[y][x * bytePerPixel + 0]; + uint8_t g = pal.pixelMatrix[y][x * bytePerPixel + 1]; + uint8_t b = pal.pixelMatrix[y][x * bytePerPixel + 2]; + uint8_t a = pal.pixelMatrix[y][x * bytePerPixel + 3]; + SetPaletteIndex(y * pal.width + x, r, g, b, a); + } + } +} + +uint32_t ImageBackend::GetWidth() const +{ + return width; +} + +uint32_t ImageBackend::GetHeight() const +{ + return height; +} + +uint8_t ImageBackend::GetColorType() const +{ + return colorType; +} + +uint8_t ImageBackend::GetBitDepth() const +{ + return bitDepth; +} + +double ImageBackend::GetBytesPerPixel() const +{ + switch (colorType) + { + case PNG_COLOR_TYPE_RGBA: + return 4 * bitDepth / 8; + + case PNG_COLOR_TYPE_RGB: + return 3 * bitDepth / 8; + + case PNG_COLOR_TYPE_PALETTE: + return 1 * bitDepth / 8; + + default: + throw std::invalid_argument("ImageBackend::GetBytesPerPixel():\n\t Invalid color type."); + } +} + +void ImageBackend::FreeImageData() +{ + if (hasImageData) + { + for (size_t y = 0; y < height; y++) + free(pixelMatrix[y]); + free(pixelMatrix); + pixelMatrix = nullptr; + } + + if (isColorIndexed) + { + free(colorPalette); + free(alphaPalette); + colorPalette = nullptr; + alphaPalette = nullptr; + isColorIndexed = false; + } + + hasImageData = false; +} + +/* RGBAPixel */ + +void RGBAPixel::SetRGBA(uint8_t nR, uint8_t nG, uint8_t nB, uint8_t nA) +{ + r = nR; + g = nG; + b = nB; + a = nA; +} + +void RGBAPixel::SetGrayscale(uint8_t grayscale, uint8_t alpha) +{ + r = grayscale; + g = grayscale; + b = grayscale; + a = alpha; +} -- cgit v1.2.3