diff --git a/leenkx/Sources/iron/format/bmp/Tools.hx b/leenkx/Sources/iron/format/bmp/Tools.hx new file mode 100644 index 0000000..04051e6 --- /dev/null +++ b/leenkx/Sources/iron/format/bmp/Tools.hx @@ -0,0 +1,256 @@ +/* + * 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 + } + } +} \ No newline at end of file