/** * @file LLImageBMP.cpp * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "llimagebmp.h" #include "llendianswizzle.h" /** * @struct LLBMPHeader * * This struct helps deal with bmp files. */ struct LLBMPHeader { S32 mSize; S32 mWidth; S32 mHeight; S16 mPlanes; S16 mBitsPerPixel; S16 mCompression; S16 mAlignmentPadding; // pads out to next word boundary S32 mImageSize; S32 mHorzPelsPerMeter; S32 mVertPelsPerMeter; S32 mNumColors; S32 mNumColorsImportant; }; /** * @struct Win95BmpHeaderExtension */ struct Win95BmpHeaderExtension { U32 mReadMask; U32 mGreenMask; U32 mBlueMask; U32 mAlphaMask; U32 mColorSpaceType; U16 mRed[3]; // Red CIE endpoint U16 mGreen[3]; // Green CIE endpoint U16 mBlue[3]; // Blue CIE endpoint U32 mGamma[3]; // Gamma scale for r g and b }; /** * LLImageBMP */ LLImageBMP::LLImageBMP() : LLImageFormatted(IMG_CODEC_BMP), mColorPaletteColors(0), mColorPalette(NULL), mBitmapOffset(0), mBitsPerPixel(0), mOriginAtTop(false) { mBitfieldMask[0] = 0; mBitfieldMask[1] = 0; mBitfieldMask[2] = 0; mBitfieldMask[3] = 0; } LLImageBMP::~LLImageBMP() { if (mColorPalette) { delete[] mColorPalette; mColorPalette = NULL; } } bool LLImageBMP::updateData() { resetLastError(); // Check to make sure that this instance has been initialized with data U8* mdata = getData(); if (!mdata || getDataSize() == 0) { setLastError("Uninitialized instance of LLImageBMP"); return false; } // Read the bitmap headers in order to get all the useful info // about this image //////////////////////////////////////////////////////////////////// // Part 1: "File Header" // 14 bytes consisting of // 2 bytes: either BM or BA // 4 bytes: file size in bytes // 4 bytes: reserved (always 0) // 4 bytes: bitmap offset (starting position of image data in bytes) constexpr S32 FILE_HEADER_SIZE = 14; if (mdata[0] != 'B' || mdata[1] != 'M') { if (mdata[0] != 'B' || mdata[1] != 'A') { setLastError("OS/2 bitmap array BMP files are not supported"); return false; } else { setLastError("Does not appear to be a bitmap file"); return false; } } mBitmapOffset = mdata[13]; mBitmapOffset <<= 8; mBitmapOffset += mdata[12]; mBitmapOffset <<= 8; mBitmapOffset += mdata[11]; mBitmapOffset <<= 8; mBitmapOffset += mdata[10]; //////////////////////////////////////////////////////////////////// // Part 2: "Bitmap Header" constexpr S32 BITMAP_HEADER_SIZE = 40; LLBMPHeader header; llassert(sizeof(header) == BITMAP_HEADER_SIZE); memcpy((void*)&header, mdata + FILE_HEADER_SIZE, BITMAP_HEADER_SIZE); // Convert BMP header from little endian (no-op on little endian builds) llendianswizzleone(header.mSize); llendianswizzleone(header.mWidth); llendianswizzleone(header.mHeight); llendianswizzleone(header.mPlanes); llendianswizzleone(header.mBitsPerPixel); llendianswizzleone(header.mCompression); llendianswizzleone(header.mAlignmentPadding); llendianswizzleone(header.mImageSize); llendianswizzleone(header.mHorzPelsPerMeter); llendianswizzleone(header.mVertPelsPerMeter); llendianswizzleone(header.mNumColors); llendianswizzleone(header.mNumColorsImportant); bool windows_nt_version = false; bool windows_95_version = false; if (12 == header.mSize) { setLastError("Windows 2.x and OS/2 1.x BMP files are not supported"); return false; } else if (40 == header.mSize) { if (3 == header.mCompression) { // Windows NT windows_nt_version = true; } } else if (12 <= header.mSize && header.mSize <= 64) { setLastError("OS/2 2.x BMP files are not supported"); return false; } else if (108 == header.mSize) { // BITMAPV4HEADER windows_95_version = true; } else if (108 < header.mSize) { // BITMAPV5HEADER or greater // Should work as long at Microsoft maintained backwards compatibility // (which they did in V4 and V5) windows_95_version = true; } S32 width = header.mWidth; S32 height = header.mHeight; if (height < 0) { mOriginAtTop = true; height = -height; } else { mOriginAtTop = false; } mBitsPerPixel = header.mBitsPerPixel; S32 components; switch (mBitsPerPixel) { case 8: components = 1; break; case 24: case 32: components = 3; break; case 1: case 4: case 16: // Started work on 16, but doesn't work yet // These are legal, but we don't support them yet. setLastError("Unsupported bit depth"); return false; default: setLastError("Unrecognized bit depth"); return false; } setSize(width, height, components); switch (header.mCompression) { case 0: // Uncompressed break; case 1: setLastError("8 bit RLE compression not supported."); return false; case 2: setLastError("4 bit RLE compression not supported."); return false; case 3: // Windows NT or Windows 95 break; default: setLastError("Unsupported compression format."); return false; } //////////////////////////////////////////////////////////////////// // Part 3: Bitfield Masks and other color data S32 extension_size = 0; if (windows_nt_version) { if (header.mBitsPerPixel != 16 && header.mBitsPerPixel != 32) { setLastError("Bitfield encoding requires 16 or 32 bits per pixel."); return false; } if (header.mNumColors != 0) { setLastError("Bitfield encoding is not compatible with a color table."); return false; } extension_size = 4 * 3; memcpy(mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, extension_size); } else if (windows_95_version) { Win95BmpHeaderExtension win_95_extension; extension_size = sizeof(win_95_extension); llassert(sizeof(win_95_extension) + BITMAP_HEADER_SIZE == 108); memcpy(&win_95_extension, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, sizeof(win_95_extension)); if (3 == header.mCompression) { memcpy(mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, 4 * 4); } // Color correction ignored for now } //////////////////////////////////////////////////////////////////// // Part 4: Color Palette (optional) // Note: There's no color palette if there are 16 or more bits per pixel S32 color_palette_size = 0; mColorPaletteColors = 0; if (header.mBitsPerPixel < 16) { if (header.mNumColors == 0) { mColorPaletteColors = 1 << header.mBitsPerPixel; } else { mColorPaletteColors = header.mNumColors; } } color_palette_size = mColorPaletteColors * 4; if (mColorPaletteColors > 0) { mColorPalette = new (std::nothrow) U8[color_palette_size]; if (!mColorPalette) { LLMemory::allocationFailed(color_palette_size); return false; } memcpy(mColorPalette, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE + extension_size, color_palette_size); } return true; } bool LLImageBMP::decode(LLImageRaw* raw_image) { if (!raw_image) { llwarns << "Attempted to decode a NULL raw image" << llendl; llassert(false); return false; } resetLastError(); // Check to make sure that this instance has been initialized with data U8* mdata = getData(); if (!mdata || getDataSize() == 0) { setLastError("LLImageBMP trying to decode an image with no data"); return false; } if (!raw_image->resize(getWidth(), getHeight(), 3)) { setLastError("LLImageBMP failed to resize image"); return false; } U8* src = mdata + mBitmapOffset; U8* dst = raw_image->getData(); bool success = false; switch (mBitsPerPixel) { case 8: if (mColorPaletteColors >= 256) { success = decodeColorTable8(dst, src); } break; case 16: success = decodeColorMask16(dst, src); break; case 24: success = decodeTruecolor24(dst, src); break; case 32: success = decodeColorMask32(dst, src); break; } if (success && mOriginAtTop) { raw_image->verticalFlip(); } return success; } U32 LLImageBMP::countTrailingZeros(U32 m) { U32 shift_count = 0; while (!(m & 1)) { ++shift_count; m >>= 1; } return shift_count; } bool LLImageBMP::decodeColorMask16(U8* dst, U8* src) { if (mBitsPerPixel != 16) { llassert(false); return false; } if (!mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2]) { // Use default values mBitfieldMask[0] = 0x00007C00; mBitfieldMask[1] = 0x000003E0; mBitfieldMask[2] = 0x0000001F; } S32 src_row_span = getWidth() * 2; // Round up to nearest multiple of 4: S32 alignment_bytes = (3 * src_row_span) % 4; U32 r_shift = countTrailingZeros(mBitfieldMask[2]); U32 g_shift = countTrailingZeros(mBitfieldMask[1]); U32 b_shift = countTrailingZeros(mBitfieldMask[0]); for (S32 row = 0, height = getHeight(); row < height; ++row) { for (S32 col = 0, width = getWidth(); col < width; ++col) { U32 value = *((U16*)src); dst[0] = U8((value & mBitfieldMask[2]) >> r_shift); // Red dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green dst[2] = U8((value & mBitfieldMask[0]) >> b_shift); // Blue src += 2; dst += 3; } src += alignment_bytes; } return true; } bool LLImageBMP::decodeColorMask32(U8* dst, U8* src) { if (mBitsPerPixel != 32) { // Alpha is not supported llassert(false); return false; } if (!mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2]) { // Use default values mBitfieldMask[0] = 0x00FF0000; mBitfieldMask[1] = 0x0000FF00; mBitfieldMask[2] = 0x000000FF; } if (4 * getWidth() * getHeight() > getDataSize() - mBitmapOffset) { // here we have situation when data size in src less than actually // needed return false; } S32 src_row_span = getWidth() * 4; // Round up to nearest multiple of 4: S32 alignment_bytes = (3 * src_row_span) % 4; U32 r_shift = countTrailingZeros(mBitfieldMask[0]); U32 g_shift = countTrailingZeros(mBitfieldMask[1]); U32 b_shift = countTrailingZeros(mBitfieldMask[2]); for (S32 row = 0, height = getHeight(); row < height; ++row) { for (S32 col = 0, width = getWidth(); col < width; ++col) { U32 value = *((U32*)src); dst[0] = U8((value & mBitfieldMask[0]) >> r_shift); // Red dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green dst[2] = U8((value & mBitfieldMask[2]) >> b_shift); // Blue src += 4; dst += 3; } src += alignment_bytes; } return true; } bool LLImageBMP::decodeColorTable8(U8* dst, U8* src) { if (mBitsPerPixel != 8 || mColorPaletteColors < 256) { llassert(false); return false; } S32 src_row_span = getWidth() * 1; // Round up to nearest multiple of 4: S32 alignment_bytes = (3 * src_row_span) % 4; if (getWidth() * getHeight() + getHeight() * alignment_bytes > getDataSize() - mBitmapOffset) { // here we have situation when data size in src less than actually // needed return false; } for (S32 row = 0, height = getHeight(); row < height; ++row) { for (S32 col = 0, width = getWidth(); col < width; ++col) { S32 index = 4 * src[0]; dst[0] = mColorPalette[index + 2]; // Red dst[1] = mColorPalette[index + 1]; // Green dst[2] = mColorPalette[index + 0]; // Blue src++; dst += 3; } src += alignment_bytes; } return true; } bool LLImageBMP::decodeTruecolor24(U8* dst, U8* src) { if (mBitsPerPixel != 24 || getComponents() != 3) { llassert(false); return false; } S32 src_row_span = getWidth() * 3; // Round up to nearest multiple of 4: S32 alignment_bytes = (3 * src_row_span) % 4; if (3 * getWidth() * getHeight() + getHeight() * alignment_bytes > getDataSize() - mBitmapOffset) { // here we have situation when data size in src less than actually // needed return false; } for (S32 row = 0, height = getHeight(); row < height; ++row) { for (S32 col = 0, width = getWidth(); col < width; ++col) { dst[0] = src[2]; // Red dst[1] = src[1]; // Green dst[2] = src[0]; // Blue src += 3; dst += 3; } src += alignment_bytes; } return true; } bool LLImageBMP::encode(const LLImageRaw* raw_image) { if (!raw_image) { llwarns << "Attempted to decode a NULL raw image" << llendl; llassert(false); return false; } resetLastError(); S32 src_components = raw_image->getComponents(); S32 dst_components = (src_components < 3) ? 1 : 3; if (src_components == 2 || src_components == 4) { llinfos << "Dropping alpha information during BMP encoding" << llendl; } setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components); U8 magic[14]; LLBMPHeader header; int header_bytes = 14 + sizeof(header); llassert(header_bytes == 54); if (getComponents() == 1) { header_bytes += 1024; // Need colour LUT. } int line_bytes = getComponents() * getWidth(); int alignment_bytes = (3 * line_bytes) % 4; line_bytes += alignment_bytes; int file_bytes = line_bytes*getHeight() + header_bytes; // Allocate the new buffer for the data. if (!allocateData(file_bytes)) // memory allocation failed { return false; } magic[0] = 'B'; magic[1] = 'M'; magic[2] = (U8) file_bytes; magic[3] = (U8)(file_bytes>>8); magic[4] = (U8)(file_bytes>>16); magic[5] = (U8)(file_bytes>>24); magic[6] = magic[7] = magic[8] = magic[9] = 0; magic[10] = (U8) header_bytes; magic[11] = (U8)(header_bytes>>8); magic[12] = (U8)(header_bytes>>16); magic[13] = (U8)(header_bytes>>24); header.mSize = 40; header.mWidth = getWidth(); header.mHeight = getHeight(); header.mPlanes = 1; header.mBitsPerPixel = (getComponents()==1)?8:24; header.mCompression = 0; header.mAlignmentPadding = 0; header.mImageSize = 0; #if LL_DARWIN header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 2834; // 72dpi #else header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 0; #endif header.mNumColors = header.mNumColorsImportant = 0; // convert BMP header to little endian (no-op on little endian builds) llendianswizzleone(header.mSize); llendianswizzleone(header.mWidth); llendianswizzleone(header.mHeight); llendianswizzleone(header.mPlanes); llendianswizzleone(header.mBitsPerPixel); llendianswizzleone(header.mCompression); llendianswizzleone(header.mAlignmentPadding); llendianswizzleone(header.mImageSize); llendianswizzleone(header.mHorzPelsPerMeter); llendianswizzleone(header.mVertPelsPerMeter); llendianswizzleone(header.mNumColors); llendianswizzleone(header.mNumColorsImportant); U8* mdata = getData(); // Output magic, then header, then the palette table, then the data. U32 cur_pos = 0; memcpy(mdata, magic, 14); cur_pos += 14; memcpy(mdata + cur_pos, &header, 40); cur_pos += 40; if (getComponents() == 1) { for (S32 n = 0; n < 256; ++n) { mdata[cur_pos++] = (U8)n; mdata[cur_pos++] = (U8)n; mdata[cur_pos++] = (U8)n; mdata[cur_pos++] = 0; } } // Need to iterate through, because we need to flip the RGB. const U8* src = raw_image->getData(); U8* dst = mdata + cur_pos; for (S32 row = 0, height = getHeight(); row < height; ++row) { for (S32 col = 0, width = getWidth(); col < width; ++col) { switch (src_components) { case 1: { *dst++ = *src++; break; } case 2: { U32 lum = src[0]; U32 alpha = src[1]; *dst++ = (U8)(lum * alpha / 255); src += 2; break; } case 3: case 4: { dst[0] = src[2]; dst[1] = src[1]; dst[2] = src[0]; src += src_components; dst += 3; break; } } } for (S32 i = 0; i < alignment_bytes; ++i) { *dst++ = 0; } } return true; }