/* * format - Haxe File Formats * * BMP File Format * Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion) * * Copyright (c) 2009, The Haxe Project Contributors * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package iron.format.bmp; class Tools { // a r g b static var ARGB_MAP(default, never):Array = [0, 1, 2, 3]; static var BGRA_MAP(default, never):Array = [3, 2, 1, 0]; static var COLOR_SIZE(default, never):Int = 4; /** Extract BMP pixel data (24bpp in BGR format) and expands it to BGRA, removing any padding in the process. **/ inline static public function extractBGRA( bmp : iron.format.bmp.Data ) : haxe.io.Bytes { return _extract32(bmp, BGRA_MAP, 0xFF); } /** Extract BMP pixel data (24bpp in BGR format) and converts it to ARGB. **/ inline static public function extractARGB( bmp : iron.format.bmp.Data ) : haxe.io.Bytes { return _extract32(bmp, ARGB_MAP, 0xFF); } /** Creates BMP data from bytes in BGRA format for each pixel. **/ inline static public function buildFromBGRA( width : Int, height : Int, srcBytes : haxe.io.Bytes, topToBottom : Bool = false ) : Data { return _buildFrom32(width, height, srcBytes, BGRA_MAP, topToBottom); } /** Creates BMP data from bytes in ARGB format for each pixel. **/ inline static public function buildFromARGB( width : Int, height : Int, srcBytes : haxe.io.Bytes, topToBottom : Bool = false ) : Data { return _buildFrom32(width, height, srcBytes, ARGB_MAP, topToBottom); } inline static public function computePaddedStride(width:Int, bpp:Int):Int { return ((((width * bpp) + 31) & ~31) >> 3); } /** * Gets number of colors for indexed palettes */ inline static public function getNumColorsForBitDepth(bpp:Int):Int { return switch (bpp) { case 1: 2; case 4: 16; case 8: 256; case 16: 65536; default: throw 'Unsupported bpp $bpp'; } } // `channelMap` contains indices to map into ARGB (f.e. the mapping for ARGB is [0,1,2,3], while for BGRA is [3,2,1,0]) static function _extract32( bmp : iron.format.bmp.Data, channelMap : Array, alpha : Int = 0xFF) : haxe.io.Bytes { var srcBytes = bmp.pixels; var dstLen = bmp.header.width * bmp.header.height * 4; var dstBytes = haxe.io.Bytes.alloc( dstLen ); var srcPaddedStride = bmp.header.paddedStride; var yDir = -1; var dstPos = 0; var srcPos = srcPaddedStride * (bmp.header.height - 1); if ( bmp.header.topToBottom ) { yDir = 1; srcPos = 0; } if ( bmp.header.bpp < 8 || bmp.header.bpp == 16 ) { throw 'bpp ${bmp.header.bpp} not supported'; } var colorTable:haxe.io.Bytes = null; if ( bmp.header.bpp <= 8 ) { var colorTableLength = getNumColorsForBitDepth(bmp.header.bpp); colorTable = haxe.io.Bytes.alloc(colorTableLength * COLOR_SIZE); var definedColorTableLength = Std.int( bmp.colorTable.length / COLOR_SIZE ); for( i in 0...definedColorTableLength ) { var b = bmp.colorTable.get( i * COLOR_SIZE); var g = bmp.colorTable.get( i * COLOR_SIZE + 1); var r = bmp.colorTable.get( i * COLOR_SIZE + 2); colorTable.set(i * COLOR_SIZE + channelMap[0], alpha); colorTable.set(i * COLOR_SIZE + channelMap[1], r); colorTable.set(i * COLOR_SIZE + channelMap[2], g); colorTable.set(i * COLOR_SIZE + channelMap[3], b); } // We want to have the table the full length in case indices outside the range are present colorTable.fill(definedColorTableLength, colorTableLength - definedColorTableLength, 0); for( i in definedColorTableLength...colorTableLength ) { colorTable.set(i * COLOR_SIZE + channelMap[0], alpha); } } switch bmp.header.compression { case 0: while( dstPos < dstLen ) { for( i in 0...bmp.header.width ) { if (bmp.header.bpp == 8) { var currentSrcPos = srcPos + i; var index = srcBytes.get(currentSrcPos); dstBytes.blit( dstPos, colorTable, index * COLOR_SIZE, COLOR_SIZE ); } else if (bmp.header.bpp == 24) { var currentSrcPos = srcPos + i * 3; var b = srcBytes.get(currentSrcPos); var g = srcBytes.get(currentSrcPos + 1); var r = srcBytes.get(currentSrcPos + 2); dstBytes.set(dstPos + channelMap[0], alpha); dstBytes.set(dstPos + channelMap[1], r); dstBytes.set(dstPos + channelMap[2], g); dstBytes.set(dstPos + channelMap[3], b); } else if (bmp.header.bpp == 32) { var currentSrcPos = srcPos + i * 4; var b = srcBytes.get(currentSrcPos); var g = srcBytes.get(currentSrcPos + 1); var r = srcBytes.get(currentSrcPos + 2); dstBytes.set(dstPos + channelMap[0], alpha); dstBytes.set(dstPos + channelMap[1], r); dstBytes.set(dstPos + channelMap[2], g); dstBytes.set(dstPos + channelMap[3], b); } dstPos += 4; } srcPos += yDir * srcPaddedStride; } case 1: srcPos = 0; var x = 0; var y = bmp.header.topToBottom ? 0 : bmp.header.height - 1; while( srcPos < bmp.header.dataLength ) { var count = srcBytes.get(srcPos++); var index = srcBytes.get(srcPos++); if ( count == 0 ) { if ( index == 0 ) { x = 0; y += yDir; } else if ( index == 1 ) { break; } else if ( index == 2 ) { x += srcBytes.get(srcPos++); y += srcBytes.get(srcPos++); } else { count = index; for( i in 0...count ) { index = srcBytes.get(srcPos++); dstBytes.blit( COLOR_SIZE * ((x+i) + y * bmp.header.width), colorTable, index * COLOR_SIZE, COLOR_SIZE ); } if (srcPos % 2 != 0) srcPos++; x += count; } } else { for( i in 0...count ) { dstBytes.blit( COLOR_SIZE * ((x+i) + y * bmp.header.width), colorTable, index * COLOR_SIZE, COLOR_SIZE ); } x += count; } } default: throw 'compression ${bmp.header.compression} not supported'; } return dstBytes; } // `channelMap` contains indices to map into ARGB (f.e. the mapping for ARGB is [0,1,2,3], while for BGRA is [3,2,1,0]) static function _buildFrom32( width : Int, height : Int, srcBytes : haxe.io.Bytes, channelMap : Array, topToBottom : Bool = false ) : Data { var bpp = 24; var paddedStride = computePaddedStride(width, bpp); var bytesBGR = haxe.io.Bytes.alloc(paddedStride * height); var topToBottom = topToBottom; var dataLength = bytesBGR.length; var dstStride = width * 3; var srcLen = width * height * 4; var yDir = -1; var dstPos = dataLength - paddedStride; var srcPos = 0; if ( topToBottom ) { yDir = 1; dstPos = 0; } while( srcPos < srcLen ) { var i = dstPos; while( i < dstPos + dstStride ) { var r = srcBytes.get(srcPos + channelMap[1]); var g = srcBytes.get(srcPos + channelMap[2]); var b = srcBytes.get(srcPos + channelMap[3]); bytesBGR.set(i++, b); bytesBGR.set(i++, g); bytesBGR.set(i++, r); srcPos += 4; } dstPos += yDir * paddedStride; } return { header: { width: width, height: height, paddedStride: paddedStride, topToBottom: topToBottom, bpp: bpp, dataLength: dataLength, compression: 0 }, pixels: bytesBGR, colorTable: null } } }