/**************************************************************************** ** ** Copyright (C) 2008-2012 NVIDIA Corporation. ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ // ========================================================== // BMP Loader and Writer // // Design and implementation by // - Floris van den Berg (flvdberg@wxs.nl) // - Markus Loibl (markus.loibl@epost.de) // - Martin Weber (martweb@gmx.net) // - Herve Drolon (drolon@infonie.fr) // - Michal Novotny (michal@etc.cz) // // This file is part of FreeImage 3 // // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER // THIS DISCLAIMER. // // Use at your own risk! // ========================================================== #include "Qt3DSRenderLoadedTextureFreeImageCompat.h" // ---------------------------------------------------------- // Constants + headers // ---------------------------------------------------------- static const BYTE RLE_COMMAND = 0; static const BYTE RLE_ENDOFLINE = 0; static const BYTE RLE_ENDOFBITMAP = 1; static const BYTE RLE_DELTA = 2; static const BYTE BI_RGB = 0; static const BYTE BI_RLE8 = 1; static const BYTE BI_RLE4 = 2; static const BYTE BI_BITFIELDS = 3; // ---------------------------------------------------------- #ifdef _WIN32 #pragma pack(push, 1) #else #pragma pack(1) #endif typedef struct tagBITMAPCOREHEADER { DWORD bcSize; WORD bcWidth; WORD bcHeight; WORD bcPlanes; WORD bcBitCnt; } BITMAPCOREHEADER, *PBITMAPCOREHEADER; typedef struct tagBITMAPINFOOS2_1X_HEADER { DWORD biSize; WORD biWidth; WORD biHeight; WORD biPlanes; WORD biBitCount; } BITMAPINFOOS2_1X_HEADER, *PBITMAPINFOOS2_1X_HEADER; typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER, *PBITMAPFILEHEADER; #ifdef _WIN32 #pragma pack(pop) #else #pragma pack() #endif // ========================================================== // Plugin Interface // ========================================================== static int s_format_id; // ========================================================== // Internal functions // ========================================================== #ifdef FREEIMAGE_BIGENDIAN static void SwapInfoHeader(BITMAPINFOHEADER *header) { SwapLong(&header->biSize); SwapLong((DWORD *)&header->biWidth); SwapLong((DWORD *)&header->biHeight); SwapShort(&header->biPlanes); SwapShort(&header->biBitCount); SwapLong(&header->biCompression); SwapLong(&header->biSizeImage); SwapLong((DWORD *)&header->biXPelsPerMeter); SwapLong((DWORD *)&header->biYPelsPerMeter); SwapLong(&header->biClrUsed); SwapLong(&header->biClrImportant); } static void SwapCoreHeader(BITMAPCOREHEADER *header) { SwapLong(&header->bcSize); SwapShort(&header->bcWidth); SwapShort(&header->bcHeight); SwapShort(&header->bcPlanes); SwapShort(&header->bcBitCnt); } static void SwapOS21XHeader(BITMAPINFOOS2_1X_HEADER *header) { SwapLong(&header->biSize); SwapShort(&header->biWidth); SwapShort(&header->biHeight); SwapShort(&header->biPlanes); SwapShort(&header->biBitCount); } static void SwapFileHeader(BITMAPFILEHEADER *header) { SwapShort(&header->bfType); SwapLong(&header->bfSize); SwapShort(&header->bfReserved1); SwapShort(&header->bfReserved2); SwapLong(&header->bfOffBits); } #endif // -------------------------------------------------------------------------- /** Load uncompressed image pixels for 1-, 4-, 8-, 16-, 24- and 32-bit dib @param io FreeImage IO @param handle FreeImage IO handle @param dib Image to be loaded @param height Image height @param pitch Image pitch @param bit_count Image bit-depth (1-, 4-, 8-, 16-, 24- or 32-bit) */ static void LoadPixelData(FreeImageIO *io, fi_handle handle, FIBITMAP *dib, int height, int pitch, int bit_count) { (void)bit_count; // Load pixel data // NB: height can be < 0 for BMP data if (height > 0) { io->read_proc((void *)FreeImage_GetBits(dib), height * pitch, 1, handle); } else { int positiveHeight = abs(height); for (int c = 0; c < positiveHeight; ++c) { io->read_proc((void *)FreeImage_GetScanLine(dib, positiveHeight - c - 1), pitch, 1, handle); } } // swap as needed #ifdef FREEIMAGE_BIGENDIAN if (bit_count == 16) { for (unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { WORD *pixel = (WORD *)FreeImage_GetScanLine(dib, y); for (unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { SwapShort(pixel); pixel++; } } } #endif #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB if (bit_count == 24 || bit_count == 32) { for (unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { BYTE *pixel = FreeImage_GetScanLine(dib, y); for (unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { INPLACESWAP(pixel[0], pixel[2]); pixel += (bit_count >> 3); } } } #endif } /** Load image pixels for 4-bit RLE compressed dib @param io FreeImage IO @param handle FreeImage IO handle @param width Image width @param height Image height @param dib Image to be loaded @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL LoadPixelDataRLE4(FreeImageIO *io, fi_handle handle, int width, int height, FIBITMAP *dib) { int status_byte = 0; BYTE second_byte = 0; int bits = 0; BYTE *pixels = NULL; // temporary 8-bit buffer try { height = abs(height); pixels = (BYTE *)malloc(width * height * sizeof(BYTE)); if (!pixels) throw(1); memset(pixels, 0, width * height * sizeof(BYTE)); BYTE *q = pixels; BYTE *end = pixels + height * width; for (int scanline = 0; scanline < height;) { if (q < pixels || q >= end) { break; } if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { throw(1); } if (status_byte != 0) { status_byte = (int)MIN((size_t)status_byte, (size_t)(end - q)); // Encoded mode if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { throw(1); } for (int i = 0; i < status_byte; i++) { *q++ = (BYTE)((i & 0x01) ? (second_byte & 0x0f) : ((second_byte >> 4) & 0x0f)); } bits += status_byte; } else { // Escape mode if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { throw(1); } switch (status_byte) { case RLE_ENDOFLINE: { // End of line bits = 0; scanline++; q = pixels + scanline * width; } break; case RLE_ENDOFBITMAP: // End of bitmap q = end; break; case RLE_DELTA: { // read the delta values BYTE delta_x = 0; BYTE delta_y = 0; if (io->read_proc(&delta_x, sizeof(BYTE), 1, handle) != 1) { throw(1); } if (io->read_proc(&delta_y, sizeof(BYTE), 1, handle) != 1) { throw(1); } // apply them bits += delta_x; scanline += delta_y; q = pixels + scanline * width + bits; } break; default: { // Absolute mode status_byte = (int)MIN((size_t)status_byte, (size_t)(end - q)); for (int i = 0; i < status_byte; i++) { if ((i & 0x01) == 0) { if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { throw(1); } } *q++ = (BYTE)((i & 0x01) ? (second_byte & 0x0f) : ((second_byte >> 4) & 0x0f)); } bits += status_byte; // Read pad byte if (((status_byte & 0x03) == 1) || ((status_byte & 0x03) == 2)) { BYTE padding = 0; if (io->read_proc(&padding, sizeof(BYTE), 1, handle) != 1) { throw(1); } } } break; } } } { // Convert to 4-bit for (int y = 0; y < height; y++) { const BYTE *src = (BYTE *)pixels + y * width; BYTE *dst = FreeImage_GetScanLine(dib, y); BOOL hinibble = TRUE; for (int cols = 0; cols < width; cols++) { if (hinibble) { dst[cols >> 1] = (src[cols] << 4); } else { dst[cols >> 1] |= src[cols]; } hinibble = !hinibble; } } } free(pixels); return TRUE; } catch (int) { if (pixels) free(pixels); return FALSE; } } /** Load image pixels for 8-bit RLE compressed dib @param io FreeImage IO @param handle FreeImage IO handle @param width Image width @param height Image height @param dib Image to be loaded @return Returns TRUE if successful, returns FALSE otherwise */ static BOOL LoadPixelDataRLE8(FreeImageIO *io, fi_handle handle, int width, int height, FIBITMAP *dib) { BYTE status_byte = 0; BYTE second_byte = 0; int scanline = 0; int bits = 0; for (;;) { if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { return FALSE; } switch (status_byte) { case RLE_COMMAND: if (io->read_proc(&status_byte, sizeof(BYTE), 1, handle) != 1) { return FALSE; } switch (status_byte) { case RLE_ENDOFLINE: bits = 0; scanline++; break; case RLE_ENDOFBITMAP: return TRUE; case RLE_DELTA: { // read the delta values BYTE delta_x = 0; BYTE delta_y = 0; if (io->read_proc(&delta_x, sizeof(BYTE), 1, handle) != 1) { return FALSE; } if (io->read_proc(&delta_y, sizeof(BYTE), 1, handle) != 1) { return FALSE; } // apply them bits += delta_x; scanline += delta_y; break; } default: { if (scanline >= abs(height)) { return TRUE; } int count = MIN((int)status_byte, width - bits); BYTE *sline = FreeImage_GetScanLine(dib, scanline); if (io->read_proc((void *)(sline + bits), sizeof(BYTE) * count, 1, handle) != 1) { return FALSE; } // align run length to even number of bytes if ((status_byte & 1) == 1) { if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { return FALSE; } } bits += status_byte; break; } } break; default: { if (scanline >= abs(height)) { return TRUE; } int count = MIN((int)status_byte, width - bits); BYTE *sline = FreeImage_GetScanLine(dib, scanline); if (io->read_proc(&second_byte, sizeof(BYTE), 1, handle) != 1) { return FALSE; } for (int i = 0; i < count; i++) { *(sline + bits) = second_byte; bits++; } break; } } } } // -------------------------------------------------------------------------- static FIBITMAP *LoadWindowsBMP(FreeImageIO *io, fi_handle handle, int flags, unsigned bitmap_bits_offset) { FIBITMAP *dib = NULL; (void)flags; try { // load the info header BITMAPINFOHEADER bih; io->read_proc(&bih, sizeof(BITMAPINFOHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapInfoHeader(&bih); #endif // keep some general information about the bitmap int used_colors = bih.biClrUsed; int width = bih.biWidth; int height = bih.biHeight; // WARNING: height can be < 0 => check each call using 'height' // as a parameter int alloc_height = abs(height); int bit_count = bih.biBitCount; int compression = bih.biCompression; int pitch = CalculatePitch(CalculateLine(width, bit_count)); switch (bit_count) { case 1: case 4: case 8: { if ((used_colors <= 0) || (used_colors > CalculateUsedPaletteEntries(bit_count))) used_colors = CalculateUsedPaletteEntries(bit_count); // allocate enough memory to hold the bitmap (header, palette, pixels) and read the // palette dib = FreeImage_Allocate(width, alloc_height, bit_count, io); if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); // load the palette io->read_proc(FreeImage_GetPalette(dib), used_colors * sizeof(RGBQUAD), 1, handle); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB RGBQUAD *pal = FreeImage_GetPalette(dib); for (int i = 0; i < used_colors; i++) { INPLACESWAP(pal[i].rgbRed, pal[i].rgbBlue); } #endif // seek to the actual pixel data. // this is needed because sometimes the palette is larger than the entries it contains // predicts if (bitmap_bits_offset > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * sizeof(RGBQUAD)))) io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); // read the pixel data switch (compression) { case BI_RGB: LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; case BI_RLE4: if (LoadPixelDataRLE4(io, handle, width, height, dib)) { return dib; } else { throw "Error encountered while decoding RLE4 BMP data"; } break; case BI_RLE8: if (LoadPixelDataRLE8(io, handle, width, height, dib)) { return dib; } else { throw "Error encountered while decoding RLE8 BMP data"; } break; default: throw "compression type not supported"; } } break; // 1-, 4-, 8-bit case 16: { if (bih.biCompression == BI_BITFIELDS) { DWORD bitfields[3]; io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], bitfields[2], io); } else { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); } if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; } break; // 16-bit case 24: case 32: { if (bih.biCompression == BI_BITFIELDS) { DWORD bitfields[3]; io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], bitfields[2], io); } else { if (bit_count == 32) { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } else { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } } if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); // Skip over the optional palette // A 24 or 32 bit DIB may contain a palette for faster color reduction if (used_colors > 0) { io->seek_proc(handle, used_colors * sizeof(RGBQUAD), SEEK_CUR); } else if ((bih.biCompression != BI_BITFIELDS) && (bitmap_bits_offset > sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) { io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); } // read in the bitmap bits // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); // check if the bitmap contains transparency, if so enable it in the header return dib; } break; // 24-, 32-bit } } catch (const char *message) { if (dib) { FreeImage_Unload(dib); } if (message) { FreeImage_OutputMessageProc(s_format_id, message, io); } } return NULL; } // -------------------------------------------------------------------------- static FIBITMAP *LoadOS22XBMP(FreeImageIO *io, fi_handle handle, int flags, unsigned bitmap_bits_offset) { FIBITMAP *dib = NULL; (void)flags; try { // load the info header BITMAPINFOHEADER bih; io->read_proc(&bih, sizeof(BITMAPINFOHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapInfoHeader(&bih); #endif // keep some general information about the bitmap int used_colors = bih.biClrUsed; int width = bih.biWidth; int height = bih.biHeight; // WARNING: height can be < 0 => check each read_proc using // 'height' as a parameter int alloc_height = abs(height); int bit_count = bih.biBitCount; int compression = bih.biCompression; int pitch = CalculatePitch(CalculateLine(width, bit_count)); switch (bit_count) { case 1: case 4: case 8: { if ((used_colors <= 0) || (used_colors > CalculateUsedPaletteEntries(bit_count))) used_colors = CalculateUsedPaletteEntries(bit_count); // allocate enough memory to hold the bitmap (header, palette, pixels) and read the // palette dib = FreeImage_Allocate(width, alloc_height, bit_count, io); if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); // load the palette io->seek_proc(handle, sizeof(BITMAPFILEHEADER) + bih.biSize, SEEK_SET); RGBQUAD *pal = FreeImage_GetPalette(dib); for (int count = 0; count < used_colors; count++) { FILE_BGR bgr; io->read_proc(&bgr, sizeof(FILE_BGR), 1, handle); pal[count].rgbRed = bgr.r; pal[count].rgbGreen = bgr.g; pal[count].rgbBlue = bgr.b; } // seek to the actual pixel data. // this is needed because sometimes the palette is larger than the entries it contains // predicts if (bitmap_bits_offset > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); // read the pixel data switch (compression) { case BI_RGB: // load pixel data LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; case BI_RLE4: if (LoadPixelDataRLE4(io, handle, width, height, dib)) { return dib; } else { throw "Error encountered while decoding RLE4 BMP data"; } break; case BI_RLE8: if (LoadPixelDataRLE8(io, handle, width, height, dib)) { return dib; } else { throw "Error encountered while decoding RLE8 BMP data"; } break; default: throw "compression type not supported"; } } case 16: { if (bih.biCompression == 3) { DWORD bitfields[3]; io->read_proc(bitfields, 3 * sizeof(DWORD), 1, handle); dib = FreeImage_Allocate(width, alloc_height, bit_count, bitfields[0], bitfields[1], bitfields[2], io); } else { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); } if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); if (bitmap_bits_offset > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) { io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); } // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; } case 24: case 32: { if (bit_count == 32) { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } else { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } if (dib == NULL) throw "DIB allocation failed"; // set resolution information FreeImage_SetDotsPerMeterX(dib, bih.biXPelsPerMeter); FreeImage_SetDotsPerMeterY(dib, bih.biYPelsPerMeter); // Skip over the optional palette // A 24 or 32 bit DIB may contain a palette for faster color reduction if (bitmap_bits_offset > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (used_colors * 3))) io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); // read in the bitmap bits // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; } } } catch (const char *message) { if (dib) FreeImage_Unload(dib); FreeImage_OutputMessageProc(s_format_id, message, io); } return NULL; } // -------------------------------------------------------------------------- static FIBITMAP *LoadOS21XBMP(FreeImageIO *io, fi_handle handle, int flags, unsigned bitmap_bits_offset) { FIBITMAP *dib = NULL; (void)flags; try { BITMAPINFOOS2_1X_HEADER bios2_1x; io->read_proc(&bios2_1x, sizeof(BITMAPINFOOS2_1X_HEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapOS21XHeader(&bios2_1x); #endif // keep some general information about the bitmap int used_colors = 0; int width = bios2_1x.biWidth; int height = bios2_1x.biHeight; // WARNING: height can be < 0 => check each read_proc using // 'height' as a parameter int alloc_height = abs(height); int bit_count = bios2_1x.biBitCount; int pitch = CalculatePitch(CalculateLine(width, bit_count)); switch (bit_count) { case 1: case 4: case 8: { used_colors = CalculateUsedPaletteEntries(bit_count); // allocate enough memory to hold the bitmap (header, palette, pixels) and read the // palette dib = FreeImage_Allocate(width, alloc_height, bit_count, io); if (dib == NULL) throw "DIB allocation failed"; // set resolution information to default values (72 dpi in english units) FreeImage_SetDotsPerMeterX(dib, 2835); FreeImage_SetDotsPerMeterY(dib, 2835); // load the palette RGBQUAD *pal = FreeImage_GetPalette(dib); for (int count = 0; count < used_colors; count++) { FILE_BGR bgr; io->read_proc(&bgr, sizeof(FILE_BGR), 1, handle); pal[count].rgbRed = bgr.r; pal[count].rgbGreen = bgr.g; pal[count].rgbBlue = bgr.b; } // Skip over the optional palette // A 24 or 32 bit DIB may contain a palette for faster color reduction io->seek_proc(handle, bitmap_bits_offset, SEEK_SET); // read the pixel data // load pixel data LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; } case 16: { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK, io); if (dib == NULL) throw "DIB allocation failed"; // set resolution information to default values (72 dpi in english units) FreeImage_SetDotsPerMeterX(dib, 2835); FreeImage_SetDotsPerMeterY(dib, 2835); // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); return dib; } case 24: case 32: { if (bit_count == 32) { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } else { dib = FreeImage_Allocate(width, alloc_height, bit_count, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, io); } if (dib == NULL) throw "DIB allocation failed"; // set resolution information to default values (72 dpi in english units) FreeImage_SetDotsPerMeterX(dib, 2835); FreeImage_SetDotsPerMeterY(dib, 2835); // Skip over the optional palette // A 24 or 32 bit DIB may contain a palette for faster color reduction // load pixel data and swap as needed if OS is Big Endian LoadPixelData(io, handle, dib, height, pitch, bit_count); // check if the bitmap contains transparency, if so enable it in the header return dib; } } } catch (const char *message) { if (dib) FreeImage_Unload(dib); FreeImage_OutputMessageProc(s_format_id, message, io); } return NULL; } // ========================================================== // Plugin Implementation // ========================================================== // ---------------------------------------------------------- static FIBITMAP *DoLoadBMP(FreeImageIO *io, fi_handle handle, int flags) { if (handle != NULL) { BITMAPFILEHEADER bitmapfileheader; DWORD type = 0; BYTE magic[2]; // we use this offset value to make seemingly absolute seeks relative in the file long offset_in_file = io->tell_proc(handle); // read the magic io->read_proc(&magic, sizeof(magic), 1, handle); // compare the magic with the number we know // somebody put a comment here explaining the purpose of this loop while (memcmp(&magic, "BA", 2) == 0) { io->read_proc(&bitmapfileheader.bfSize, sizeof(DWORD), 1, handle); io->read_proc(&bitmapfileheader.bfReserved1, sizeof(WORD), 1, handle); io->read_proc(&bitmapfileheader.bfReserved2, sizeof(WORD), 1, handle); io->read_proc(&bitmapfileheader.bfOffBits, sizeof(DWORD), 1, handle); io->read_proc(&magic, sizeof(magic), 1, handle); } // read the fileheader io->seek_proc(handle, (0 - (int)sizeof(magic)), SEEK_CUR); io->read_proc(&bitmapfileheader, (int)sizeof(BITMAPFILEHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapFileHeader(&bitmapfileheader); #endif // read the first byte of the infoheader io->read_proc(&type, sizeof(DWORD), 1, handle); io->seek_proc(handle, 0 - (int)sizeof(DWORD), SEEK_CUR); #ifdef FREEIMAGE_BIGENDIAN SwapLong(&type); #endif // call the appropriate load function for the found bitmap type if (type == 40) return LoadWindowsBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); if (type == 12) return LoadOS21XBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); if (type <= 64) return LoadOS22XBMP(io, handle, flags, offset_in_file + bitmapfileheader.bfOffBits); char buf[256]; sprintf(buf, "unknown bmp subtype with id %d", type); FreeImage_OutputMessageProc(s_format_id, buf, io); } return NULL; } template struct SPaletteIndexer { static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) { uint32_t divisor = 8 / TBitWidth; uint32_t byte = inPos / divisor; uint32_t modulus = inPos % divisor; uint32_t shift = TBitWidth * modulus; uint32_t mask = (1 << TBitWidth) - 1; mask = mask << shift; uint32_t byteData = inData[byte]; return (byteData & mask) >> shift; } }; template <> struct SPaletteIndexer<1> { static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) { uint32_t byte = (inPos / 8); uint32_t bit = 1 << (7 - (inPos % 8)); uint32_t byteData = inData[byte]; return (byteData & bit) ? 1 : 0; } }; template <> struct SPaletteIndexer<8> { static inline uint32_t IndexOf(const uint8_t *inData, uint32_t inPos) { uint32_t byte = inPos; uint32_t bit = 0xFF; uint32_t byteData = inData[byte]; return byteData & bit; } }; static inline void assignQuad(uint8_t *dest, const RGBQUAD &quad) { dest[0] = quad.rgbRed; dest[1] = quad.rgbGreen; dest[2] = quad.rgbBlue; } template inline void LoadPalettized(bool inFlipY, const RGBQUAD *palette, void *data, uint8_t *newData, int width, int height, int components, int transparentIndex) { const uint8_t *oldData = (const uint8_t *)data; int pitch = CalculatePitch(CalculateLine(width, bitCount)); for (uint32_t h = 0; h < (uint32_t)height; ++h) { uint32_t relHeight = h; if (inFlipY) relHeight = ((uint32_t)height) - h - 1; for (uint32_t w = 0; w < (uint32_t)width; ++w) { const uint8_t *dataLine = oldData + pitch * h; uint32_t pos = width * relHeight + w; uint32_t paletteIndex = SPaletteIndexer::IndexOf(dataLine, w); const RGBQUAD &theQuad = palette[paletteIndex]; uint8_t *writePtr = newData + (pos * components); assignQuad(writePtr, theQuad); if (paletteIndex == (uint32_t)transparentIndex && components == 4) { writePtr[3] = 0; } } } } inline int firstHighBit(int data) { if (data == 0) return 0; int idx = 0; while ((data % 2) == 0) { data = data >> 1; ++idx; } return idx; } struct SMaskData { uint32_t mask; uint32_t shift; uint32_t max; SMaskData(int inMask) { mask = inMask; shift = firstHighBit(mask); max = mask >> shift; } inline uint8_t MapColor(uint32_t color) const { uint32_t intermediateValue = (color & mask) >> shift; return (uint8_t)((intermediateValue * 255) / max); } }; template struct ColorAccess { }; template <> struct ColorAccess<16> { static uint32_t GetPixelWidth() { return 2; } static uint32_t GetColor(const char8_t *src) { return (uint32_t) * reinterpret_cast(src); } }; template <> struct ColorAccess<24> { static uint32_t GetPixelWidth() { return 3; } static uint32_t GetColor(const char8_t *src) { return (uint32_t)(*reinterpret_cast(src) & 0xFFFFFF); } }; template <> struct ColorAccess<32> { static uint32_t GetPixelWidth() { return 4; } static uint32_t GetColor(const char8_t *src) { return *reinterpret_cast(src); } }; template inline void LoadMasked(bool inFlipY, QT3DSI32 *inMasks, void *data, uint8_t *newData, int width, int height) { const char8_t *oldData = (const char8_t *)data; SMaskData rMask(inMasks[0]); SMaskData gMask(inMasks[1]); SMaskData bMask(inMasks[2]); for (int h = 0; h < height; ++h) { int relHeight = h; if (inFlipY) relHeight = height - h - 1; for (int w = 0; w < width; ++w) { int pos = width * relHeight + w; const char8_t *readPtr = oldData + (pos * ColorAccess::GetPixelWidth()); uint8_t *writePtr = newData + (pos * 3); uint32_t colorVal = ColorAccess::GetColor(readPtr); writePtr[0] = rMask.MapColor(colorVal); writePtr[1] = gMask.MapColor(colorVal); writePtr[2] = bMask.MapColor(colorVal); } } } void SLoadedTexture::FreeImagePostProcess(bool inFlipY) { // We always convert 32 bit RGBA if (m_ExtendedFormat != ExtendedTextureFormats::NoExtendedFormat) { QT3DSU32 stride = 3 * width; format = NVRenderTextureFormats::RGB8; components = 3; if (m_ExtendedFormat == ExtendedTextureFormats::Palettized && m_TransparentPaletteIndex > -1 && m_TransparentPaletteIndex < 256) { stride = 4 * width; components = 4; format = NVRenderTextureFormats::RGBA8; } QT3DSU32 byteSize = height * stride; uint8_t *newData = (uint8_t *)m_Allocator.allocate(byteSize, "texture data", __FILE__, __LINE__); if (format == NVRenderTextureFormats::RGBA8) memSet(newData, 255, byteSize); switch (m_ExtendedFormat) { case ExtendedTextureFormats::Palettized: { RGBQUAD *palette = (RGBQUAD *)m_Palette; switch (m_BitCount) { case 1: LoadPalettized<1>(inFlipY, palette, data, newData, width, height, components, m_TransparentPaletteIndex); break; case 2: LoadPalettized<2>(inFlipY, palette, data, newData, width, height, components, m_TransparentPaletteIndex); break; case 4: LoadPalettized<4>(inFlipY, palette, data, newData, width, height, components, m_TransparentPaletteIndex); break; case 8: LoadPalettized<8>(inFlipY, palette, data, newData, width, height, components, m_TransparentPaletteIndex); break; default: QT3DS_ASSERT(false); memSet(newData, 0, byteSize); break; } } break; case ExtendedTextureFormats::CustomRGB: { switch (m_BitCount) { case 16: LoadMasked<16>(inFlipY, m_CustomMasks, data, newData, width, height); break; case 24: LoadMasked<24>(inFlipY, m_CustomMasks, data, newData, width, height); break; case 32: LoadMasked<32>(inFlipY, m_CustomMasks, data, newData, width, height); break; default: QT3DS_ASSERT(false); memSet(newData, 0, byteSize); break; } } break; default: QT3DS_ASSERT(false); memSet(newData, 0, byteSize); break; } m_Allocator.deallocate(data); if (m_Palette) m_Allocator.deallocate(m_Palette); data = newData; m_Palette = NULL; m_BitCount = 0; this->dataSizeInBytes = byteSize; m_ExtendedFormat = ExtendedTextureFormats::NoExtendedFormat; } } SLoadedTexture *SLoadedTexture::LoadBMP(ISeekableIOStream &inStream, bool inFlipY, NVFoundationBase &inFnd, qt3ds::render::NVRenderContextType renderContextType) { Q_UNUSED(renderContextType) FreeImageIO theIO(inFnd.getAllocator(), inFnd); SLoadedTexture *retval = DoLoadBMP(&theIO, &inStream, 0); if (retval) retval->FreeImagePostProcess(inFlipY); return retval; }