// Project64 - A Nintendo 64 emulator // https://www.pj64-emu.com/ // Copyright(C) 2001-2021 Project64 // Copyright(C) 2007 Hiroshi Morii // Copyright(C) 2003 Rice1964 // GNU/GPLv2 licensed: https://gnu.org/licenses/gpl-2.0.html // use power of 2 texture size // (0:disable, 1:enable, 2:3dfx) #define POW2_TEXTURES 0 // Check 8 bytes. Use a larger value if needed. #define PNG_CHK_BYTES 8 #include "TxImage.h" #include "TxReSample.h" #include "TxDbg.h" #include #include bool TxImage::getPNGInfo(FILE *fp, png_structp *png_ptr, png_infop *info_ptr) { unsigned char sig[PNG_CHK_BYTES]; // Check for valid file pointer if (!fp) return 0; // Check if file is PNG if (fread(sig, 1, PNG_CHK_BYTES, fp) != PNG_CHK_BYTES) return 0; if (png_sig_cmp(sig, 0, PNG_CHK_BYTES) != 0) return 0; // Get PNG file info *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!*png_ptr) return 0; *info_ptr = png_create_info_struct(*png_ptr); if (!*info_ptr) { png_destroy_read_struct(png_ptr, nullptr, nullptr); return 0; } if (setjmp(png_jmpbuf(*png_ptr))) { DBG_INFO(80, "Error reading PNG!\n"); png_destroy_read_struct(png_ptr, info_ptr, nullptr); return 0; } png_init_io(*png_ptr, fp); png_set_sig_bytes(*png_ptr, PNG_CHK_BYTES); png_read_info(*png_ptr, *info_ptr); return 1; } uint8* TxImage::readPNG(FILE* fp, int* width, int* height, uint16* format) { // NOTE: Returned image format is GFX_TEXFMT_ARGB_8888 png_structp png_ptr; png_infop info_ptr; uint8 *image = nullptr; int bit_depth, color_type, interlace_type, compression_type, filter_type, row_bytes, o_width, o_height, num_pas; // Initialize *width = 0; *height = 0; *format = 0; // Check if we have a valid PNG file if (!fp) return nullptr; if (!getPNGInfo(fp, &png_ptr, &info_ptr)) { INFO(80, "Error reading PNG file! PNG image is corrupt.\n"); return nullptr; } png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)&o_width, (png_uint_32*)&o_height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_type); DBG_INFO(80, "PNG format %d x %d bitdepth:%d color:%x interlace:%x compression:%x filter:%x\n", o_width, o_height, bit_depth, color_type, interlace_type, compression_type, filter_type); // Transformations // Rice high resolution textures // _all.png // _rgb.png, _a.png // _ciByRGBA.png // _allciByRGBA.png // Strip if color channel is larger than 8-bit if (bit_depth > 8) { png_set_strip_16(png_ptr); bit_depth = 8; } #if 1 // These are not really required as per the Rice format specification, // but is done just in case someone uses them. // Convert palette color to RGB color if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; } // Expand 1,2, and 4-bit grayscale to 8-bit grayscale if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(png_ptr); // Convert grayscale or grayscale + alpha to RGB color if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; } #endif // Add alpha channel if any if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); color_type = PNG_COLOR_TYPE_RGB_ALPHA; } // Convert RGB to RGBA if (color_type == PNG_COLOR_TYPE_RGB) { png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); color_type = PNG_COLOR_TYPE_RGB_ALPHA; } // Check for invalid formatting if (color_type != PNG_COLOR_TYPE_RGB_ALPHA) { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); DBG_INFO(80, "Error: not PNG_COLOR_TYPE_RGB_ALPHA format!\n"); return nullptr; } /*png_color_8p sig_bit; if (png_get_sBIT(png_ptr, info_ptr, &sig_bit)) png_set_shift(png_ptr, sig_bit);*/ // Convert RGBA to BGRA png_set_bgr(png_ptr); // Turn on interlace handling to cope with the weirdness // of texture authors using interlaced format num_pas = png_set_interlace_handling(png_ptr); // Update info structure png_read_update_info(png_ptr, info_ptr); // We only get here if ARGB8888 row_bytes = png_get_rowbytes(png_ptr, info_ptr); // Allocate memory to read in image image = (uint8*)malloc(row_bytes * o_height); // Read in image if (image) { int pas, i; uint8* tmpimage; for (pas = 0; pas < num_pas; pas++) { // Deal with interlacing tmpimage = image; for (i = 0; i < o_height; i++) { // Copy row png_read_rows(png_ptr, &tmpimage, nullptr, 1); tmpimage += row_bytes; } } // Read rest of the info structure png_read_end(png_ptr, info_ptr); *width = (row_bytes >> 2); *height = o_height; *format = GFX_TEXFMT_ARGB_8888; #if POW2_TEXTURES // Next power of 2 size conversions // NOTE: I can do this in the above loop for faster operations, but some // texture packs require a workaround. See HACKALERT in nextPow2(). TxReSample txReSample = new TxReSample; // Temporary, move to a better place. #if (POW2_TEXTURES == 2) if (!txReSample->nextPow2(&image, width, height, 32, 1)) { #else if (!txReSample->nextPow2(&image, width, height, 32, 0)) { #endif if (image) { free(image); image = nullptr; } *width = 0; *height = 0; *format = 0; } delete txReSample; #endif // POW2_TEXTURES } // Clean up png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); #ifdef DEBUG if (!image) { DBG_INFO(80, "Error: Failed to load PNG image!\n"); } #endif return image; } bool TxImage::writePNG(uint8* src, FILE* fp, int width, int height, int rowStride, uint16 format, uint8 *palette) { png_structp png_ptr; png_infop info_ptr; png_color_8 sig_bit; png_colorp palette_ptr; png_bytep trans_ptr;//, tex_ptr; int bit_depth, color_type, row_bytes, num_palette; int i; //uint16 srcfmt, destfmt; if (!src || !fp) return 0; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (png_ptr == nullptr) return 0; info_ptr = png_create_info_struct(png_ptr); if (info_ptr == nullptr) { png_destroy_write_struct(&png_ptr, nullptr); return 0; } /*if (setjmp(png_ptr->jmpbuf)) { png_destroy_write_struct(&png_ptr, &info_ptr); return 0; }*/ png_init_io(png_ptr, fp); /* TODO: Images must be converted to RGBA8888 or CI8, palettes need to be separated to A and RGB. N64 formats Format: 0 - RGBA, 1 - YUV, 2 - CI, 3 - IA, 4 - I Size: 0 - 4bit, 1 - 8bit, 2 - 16bit, 3 - 32 bit format = (Format << 8 | Size); Each channel is saved in 8-bit for consistency */ switch (format) { case 0x0002:// RGBA5551 bit_depth = 8; sig_bit.red = 5; sig_bit.green = 5; sig_bit.blue = 5; sig_bit.alpha = 1; color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; case 0x0003:// RGBA8888 case 0x0302:// IA88 bit_depth = 8; sig_bit.red = 8; sig_bit.green = 8; sig_bit.blue = 8; sig_bit.alpha = 8; color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; case 0x0300:// IA31 bit_depth = 8; sig_bit.red = 3; sig_bit.green = 3; sig_bit.blue = 3; sig_bit.alpha = 1; color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; case 0x0301:// IA44 bit_depth = 8; sig_bit.red = 4; sig_bit.green = 4; sig_bit.blue = 4; sig_bit.alpha = 4; color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; case 0x0400:// I4 bit_depth = 8; sig_bit.red = 4; sig_bit.green = 4; sig_bit.blue = 4; color_type = PNG_COLOR_TYPE_RGB; break; case 0x0401:// I8 case 0x0402:// I16 bit_depth = 8; sig_bit.red = 8; sig_bit.green = 8; sig_bit.blue = 8; color_type = PNG_COLOR_TYPE_RGB; break; case 0x0200:// CI4 bit_depth = 8; num_palette = 16; color_type = PNG_COLOR_TYPE_PALETTE; break; case 0x0201:// CI8 bit_depth = 8; num_palette = 256; color_type = PNG_COLOR_TYPE_PALETTE; break; case 0x0102:// YUV? case 0x0103: default: // Unsupported format png_destroy_write_struct(&png_ptr, &info_ptr); return 0; } switch (color_type) { case PNG_COLOR_TYPE_RGB_ALPHA: case PNG_COLOR_TYPE_RGB: //row_bytes = (bit_depth * width) >> 1; row_bytes = rowStride; png_set_bgr(png_ptr); png_set_sBIT(png_ptr, info_ptr, &sig_bit); break; case PNG_COLOR_TYPE_PALETTE: //row_bytes = (bit_depth * width) >> 3; row_bytes = rowStride; png_set_PLTE(png_ptr, info_ptr, palette_ptr, num_palette); png_set_tRNS(png_ptr, info_ptr, trans_ptr, num_palette, 0); } //png_set_filter(png_ptr, 0, PNG_ALL_FILTERS); //if (bit_depth == 16) // png_set_swap(png_ptr); //if (bit_depth < 8) // png_set_packswap(png_ptr); png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); //png_set_gAMA(png_ptr, info_ptr, 1.0); png_write_info(png_ptr, info_ptr); for (i = 0; i < height; i++) { png_write_row(png_ptr, (png_bytep)src); src += row_bytes; } png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); //if (tex_ptr) delete [] tex_ptr; return 1; } bool TxImage::getBMPInfo(FILE* fp, BITMAPFILEHEADER* bmp_fhdr, BITMAPINFOHEADER* bmp_ihdr) { // Read in BITMAPFILEHEADER // Is this a BMP file? if (fread(&bmp_fhdr->bfType, 2, 1, fp) != 1) return 0; if (memcmp(&bmp_fhdr->bfType, "BM", 2) != 0) return 0; // Get file size if (fread(&bmp_fhdr->bfSize, 4, 1, fp) != 1) return 0; // Reserved 1 if (fread(&bmp_fhdr->bfReserved1, 2, 1, fp) != 1) return 0; // Reserved 2 if (fread(&bmp_fhdr->bfReserved2, 2, 1, fp) != 1) return 0; // Offset to the image data if (fread(&bmp_fhdr->bfOffBits, 4, 1, fp) != 1) return 0; // Read in BITMAPINFOHEADER // Size of BITMAPINFOHEADER if (fread(&bmp_ihdr->biSize, 4, 1, fp) != 1) return 0; // Is this a Windows BMP? if (bmp_ihdr->biSize != 40) return 0; // Width of the bitmap in pixels if (fread(&bmp_ihdr->biWidth, 4, 1, fp) != 1) return 0; // Height of the bitmap in pixels if (fread(&bmp_ihdr->biHeight, 4, 1, fp) != 1) return 0; // Number of planes (always 1) if (fread(&bmp_ihdr->biPlanes, 2, 1, fp) != 1) return 0; // Number of bits-per-pixel (1, 4, 8, 16, 24, 32) if (fread(&bmp_ihdr->biBitCount, 2, 1, fp) != 1) return 0; /* Compression for a compressed bottom-up bitmap * 0: Uncompressed format * 1: Run-length encoded 4 bpp format * 2: Run-length encoded 8 bpp format * 3: Bitfield */ if (fread(&bmp_ihdr->biCompression, 4, 1, fp) != 1) return 0; // Size of the image in bytes if (fread(&bmp_ihdr->biSizeImage, 4, 1, fp) != 1) return 0; // Horizontal resolution in pixels-per-meter if (fread(&bmp_ihdr->biXPelsPerMeter, 4, 1, fp) != 1) return 0; // Vertical resolution in pixels-per-meter if (fread(&bmp_ihdr->biYPelsPerMeter, 4, 1, fp) != 1) return 0; // Number of color indexes in the color table that are actually used if (fread(&bmp_ihdr->biClrUsed, 4, 1, fp) != 1) return 0; // The number of color indexes that are required for displaying if (fread(&bmp_ihdr->biClrImportant, 4, 1, fp) != 1) return 0; return 1; } uint8* TxImage::readBMP(FILE* fp, int* width, int* height, uint16* format) { /* NOTE: Returned image format; * 4, 8-bit palette bmp -> GFX_TEXFMT_P_8 * 24, 32-bit bmp -> GFX_TEXFMT_ARGB_8888 */ uint8 *image = nullptr; uint8 *image_row = nullptr; uint8 *tmpimage = nullptr; unsigned int row_bytes, pos; int i, j; /* Windows bitmap */ BITMAPFILEHEADER bmp_fhdr; BITMAPINFOHEADER bmp_ihdr; // Initialize *width = 0; *height = 0; *format = 0; // Check if we have a valid BMP file if (!fp) return nullptr; if (!getBMPInfo(fp, &bmp_fhdr, &bmp_ihdr)) { INFO(80, "Error reading bitmap file! Bitmap image is corrupt.\n"); return nullptr; } DBG_INFO(80, "BMP format %d x %d bitdepth:%d compression:%x offset:%d\n", bmp_ihdr.biWidth, bmp_ihdr.biHeight, bmp_ihdr.biBitCount, bmp_ihdr.biCompression, bmp_fhdr.bfOffBits); // rowStride in bytes row_bytes = (bmp_ihdr.biWidth * bmp_ihdr.biBitCount) >> 3; // Align to 4 bytes boundary row_bytes = (row_bytes + 3) & ~3; // Rice high resolution textures if (!(bmp_ihdr.biBitCount == 8 || bmp_ihdr.biBitCount == 4 || bmp_ihdr.biBitCount == 32 || bmp_ihdr.biBitCount == 24) || bmp_ihdr.biCompression != 0) { DBG_INFO(80, "Error: Incompatible bitmap format!\n"); return nullptr; } switch (bmp_ihdr.biBitCount) { case 8: case 32: // 8-bit, 32-bit bitmap image = (uint8*)malloc(row_bytes * bmp_ihdr.biHeight); if (image) { tmpimage = image; pos = bmp_fhdr.bfOffBits + row_bytes * (bmp_ihdr.biHeight - 1); for (i = 0; i < bmp_ihdr.biHeight; i++) { // Read in image fseek(fp, pos, SEEK_SET); fread(tmpimage, row_bytes, 1, fp); tmpimage += row_bytes; pos -= row_bytes; } } break; case 4: // 4-bit bitmap image = (uint8*)malloc((row_bytes * bmp_ihdr.biHeight) << 1); image_row = (uint8*)malloc(row_bytes); if (image && image_row) { tmpimage = image; pos = bmp_fhdr.bfOffBits + row_bytes * (bmp_ihdr.biHeight - 1); for (i = 0; i < bmp_ihdr.biHeight; i++) { // Read in image fseek(fp, pos, SEEK_SET); fread(image_row, row_bytes, 1, fp); // Expand 4bpp to 8bpp, stuff 4-bit values into 8-bit comps for (j = 0; j < (int)row_bytes; j++) { tmpimage[j << 1] = image_row[j] & 0x0f; tmpimage[(j << 1) + 1] = (image_row[j] & 0xf0) >> 4; } tmpimage += (row_bytes << 1); pos -= row_bytes; } free(image_row); } else { if (image_row) free(image_row); if (image) free(image); image = nullptr; } break; case 24: // 24-bit bitmap image = (uint8*)malloc((bmp_ihdr.biWidth * bmp_ihdr.biHeight) << 2); image_row = (uint8*)malloc(row_bytes); if (image && image_row) { tmpimage = image; pos = bmp_fhdr.bfOffBits + row_bytes * (bmp_ihdr.biHeight - 1); for (i = 0; i < bmp_ihdr.biHeight; i++) { // Read in image fseek(fp, pos, SEEK_SET); fread(image_row, row_bytes, 1, fp); // Convert 24bpp to 32bpp for (j = 0; j < bmp_ihdr.biWidth; j++) { tmpimage[(j << 2)] = image_row[j * 3]; tmpimage[(j << 2) + 1] = image_row[j * 3 + 1]; tmpimage[(j << 2) + 2] = image_row[j * 3 + 2]; tmpimage[(j << 2) + 3] = 0xFF; } tmpimage += (bmp_ihdr.biWidth << 2); pos -= row_bytes; } free(image_row); } else { if (image_row) free(image_row); if (image) free(image); image = nullptr; } } if (image) { *width = (row_bytes << 3) / bmp_ihdr.biBitCount; *height = bmp_ihdr.biHeight; switch (bmp_ihdr.biBitCount) { case 8: case 4: *format = GFX_TEXFMT_P_8; break; case 32: case 24: *format = GFX_TEXFMT_ARGB_8888; } #if POW2_TEXTURES // Next power of 2 size conversions // NOTE: I can do this in the above loop for faster operations, but some // texture packs require a workaround. See HACKALERT in nextPow2(). TxReSample txReSample = new TxReSample; // Temporary, move to a better place. #if (POW2_TEXTURES == 2) if (!txReSample->nextPow2(&image, width, height, 8, 1)) { #else if (!txReSample->nextPow2(&image, width, height, 8, 0)) { #endif if (image) { free(image); image = nullptr; } *width = 0; *height = 0; *format = 0; } delete txReSample; #endif // POW2_TEXTURES } #ifdef DEBUG if (!image) { DBG_INFO(80, "Error: Failed to load BMP image!\n"); } #endif return image; } bool TxImage::getDDSInfo(FILE *fp, DDSFILEHEADER *dds_fhdr) { // Read in DDSFILEHEADER // Is this a DDS file? if (fread(&dds_fhdr->dwMagic, 4, 1, fp) != 1) return 0; if (memcmp(&dds_fhdr->dwMagic, "DDS ", 4) != 0) return 0; if (fread(&dds_fhdr->dwSize, 4, 1, fp) != 1) return 0; // Get file flags if (fread(&dds_fhdr->dwFlags, 4, 1, fp) != 1) return 0; // Height of DDS in pixels if (fread(&dds_fhdr->dwHeight, 4, 1, fp) != 1) return 0; // Width of DDS in pixels if (fread(&dds_fhdr->dwWidth, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwLinearSize, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwDepth, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwMipMapCount, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwReserved1, 4 * 11, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwSize, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwFlags, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwFourCC, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwRGBBitCount, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwRBitMask, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwGBitMask, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwBBitMask, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->ddpf.dwRGBAlphaBitMask, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwCaps1, 4, 1, fp) != 1) return 0; if (fread(&dds_fhdr->dwCaps2, 4, 1, fp) != 1) return 0; return 1; } uint8* TxImage::readDDS(FILE* fp, int* width, int* height, uint16* format) { uint8 *image = nullptr; DDSFILEHEADER dds_fhdr; uint16 tmpformat = 0; // Initialize *width = 0; *height = 0; *format = 0; // Check if we have a valid DDS file if (!fp) return nullptr; if (!getDDSInfo(fp, &dds_fhdr)) { INFO(80, "Error reading DDS file! DDS image is corrupt.\n"); return nullptr; } DBG_INFO(80, "DDS format %d x %d HeaderSize %d LinearSize %d\n", dds_fhdr.dwWidth, dds_fhdr.dwHeight, dds_fhdr.dwSize, dds_fhdr.dwLinearSize); if (!(dds_fhdr.dwFlags & (DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_LINEARSIZE))) { DBG_INFO(80, "Error: Incompatible DDS format!\n"); return nullptr; } if ((dds_fhdr.dwFlags & DDSD_MIPMAPCOUNT) && dds_fhdr.dwMipMapCount != 1) { DBG_INFO(80, "Error: Mipmapped DDS not supported!\n"); return nullptr; } if (!((dds_fhdr.ddpf.dwFlags & DDPF_FOURCC) && dds_fhdr.dwCaps2 == 0)) { DBG_INFO(80, "Error: Not fourcc standard texture!\n"); return nullptr; } if (memcmp(&dds_fhdr.ddpf.dwFourCC, "DXT1", 4) == 0) { DBG_INFO(80, "DXT1 format\n"); // Compensate for missing LinearSize dds_fhdr.dwLinearSize = (dds_fhdr.dwWidth * dds_fhdr.dwHeight) >> 1; tmpformat = GFX_TEXFMT_ARGB_CMP_DXT1; } else if (memcmp(&dds_fhdr.ddpf.dwFourCC, "DXT3", 4) == 0) { DBG_INFO(80, "DXT3 format\n"); dds_fhdr.dwLinearSize = dds_fhdr.dwWidth * dds_fhdr.dwHeight; tmpformat = GFX_TEXFMT_ARGB_CMP_DXT3; } else if (memcmp(&dds_fhdr.ddpf.dwFourCC, "DXT5", 4) == 0) { DBG_INFO(80, "DXT5 format\n"); dds_fhdr.dwLinearSize = dds_fhdr.dwWidth * dds_fhdr.dwHeight; tmpformat = GFX_TEXFMT_ARGB_CMP_DXT5; } else { DBG_INFO(80, "Error: Not DXT1 or DXT3 or DXT5 format!\n"); return nullptr; } // Read in image image = (uint8*)malloc(dds_fhdr.dwLinearSize); if (image) { *width = dds_fhdr.dwWidth; *height = dds_fhdr.dwHeight; *format = tmpformat; fseek(fp, 128, SEEK_SET); // Size of header is 128 bytes fread(image, dds_fhdr.dwLinearSize, 1, fp); } return image; }