-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added .ico file format reader (#105)
- Loading branch information
Showing
5 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package format.ico; | ||
|
||
import format.ico.Data; | ||
|
||
/** | ||
* Device-Independent Bitmap | ||
*/ | ||
class DIB { | ||
public var data(default, null) : haxe.io.Bytes; | ||
public var info(default, null) : BMPInfo; | ||
public var ixor(default, null) : Int; | ||
public var iand(default, null) : Int; | ||
public var width(default, null) : Int; | ||
public var height(default, null) : Int; | ||
public var colors(default, null) : Array<Int>; | ||
public function new( data, info ) { | ||
this.data = data; | ||
this.info = info; | ||
this.width = info.width; | ||
this.height = info.height >> 1; | ||
this.ixor = info.findDIBBits(); | ||
this.iand = this.ixor + this.height * info.bytesPerline(); | ||
this.colors = []; | ||
var ptr = info.sizeof; | ||
var max = ptr + info.numberColors() * 4; | ||
while(ptr < max) { | ||
this.colors.push(data.getInt32(ptr)); | ||
ptr += 4; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* For .ico file format, Only the following members are used: | ||
* | ||
* sizeof, width, height, planes, bitCount, sizeImage. All other members must be 0. | ||
*/ | ||
class BMPInfo { | ||
public var sizeof : Int; // DWORD, size of the structure = 40 | ||
public var width : Int; // DWORD, width of the image in pixels | ||
public var height : Int; // DWORD, height of the image in pixels, (negative: top-down, positive: bottom-up) | ||
public var planes : Int; // WORD, = 1 | ||
public var bitCount : Int; // WORD, bits per pixel (1, 4, 8, 16, 24, or 32) | ||
public var compression : Int; // DWORD, compression code | ||
public var sizeImage : Int; // DWORD, number of bytes in image | ||
public var xPelsPerMeter : Int; // DWORD, horizontal resolution | ||
public var yPelsPermeter : Int; // DWORD, vertical resolution | ||
public var clrUsed : Int; // DWORD, number of colors used | ||
public var clrImportant : Int; // DWORD, number of important colors | ||
|
||
public function new() { | ||
} | ||
|
||
// Calculates the number of entries in the color table. | ||
public function numberColors() : Int { | ||
if (clrUsed > 0) | ||
return clrUsed; | ||
return switch(bitCount) { // 1 << bitCount | ||
case 1: 2; | ||
case 4: 16; | ||
case 8: 256; | ||
default: 0; | ||
} | ||
} | ||
|
||
// Calculates the number of bytes in the color table. | ||
public inline function paletteSize() return numberColors() * 4; | ||
|
||
// Locate the image bits in a CF_DIB format DIB. | ||
public inline function findDIBBits() return sizeof + paletteSize(); | ||
|
||
// Calculates the number of bytes in one scan line from Indexes | ||
public inline function bytesPerline() return WIDTHBYTES(width * bitCount); | ||
|
||
// Multiple of 4 | ||
static public inline function WIDTHBYTES( bits ) return bits + 31 >> 5 << 2; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package format.ico; | ||
|
||
import format.ico.DIB; | ||
|
||
enum Data { | ||
DIB( d : DIB ); | ||
PNG( b : haxe.io.Bytes ); | ||
} | ||
|
||
class ICORoot { | ||
public var reserved : Int; // WORD, Reserved (must be 0) | ||
public var type : ICOType; // WORD | ||
public var count : Int; // WORD, length of entries | ||
public var entries : Array<ICOEntry>; | ||
public var datas : Array<Data>; | ||
public function new() { | ||
entries = []; | ||
datas = []; | ||
} | ||
} | ||
|
||
class ICOEntry { | ||
public var width : Int; // BYTE | ||
public var height : Int; // BYTE | ||
public var colorCount : Int; // BYTE | ||
public var reserved : Int; // BYTE | ||
public var planes : Int; // WORD, (x hotspot if CURSOR) | ||
public var bitCount : Int; // WORD, (y hotspot if CURSOR) | ||
public var size : Int; // DWORD | ||
public var offset : Int; // DWORD | ||
public function new() { | ||
} | ||
} | ||
|
||
extern enum abstract ICOType(Int) to Int { | ||
var ICON = 1; | ||
var CURSOR = 2; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package format.ico; | ||
|
||
import format.ico.Data; | ||
import format.ico.DIB; | ||
|
||
class Reader { | ||
|
||
var input : haxe.io.Input; | ||
|
||
inline function readUInt16() return input.readUInt16(); | ||
inline function readInt32() return input.readInt32(); | ||
inline function readByte() return input.readByte(); | ||
|
||
public function new(i) { | ||
input = i; | ||
input.bigEndian = false; | ||
} | ||
|
||
public function read() : ICORoot { | ||
var root = new ICORoot(); | ||
root.reserved = readUInt16(); | ||
root.type = cast readUInt16(); | ||
root.count = readUInt16(); | ||
|
||
if (root.reserved != 0) | ||
return root; // empty | ||
|
||
for (i in 0...root.count) { | ||
var entry = new ICOEntry(); | ||
entry.width = readByte(); | ||
entry.height = readByte(); | ||
entry.colorCount = readByte(); | ||
entry.reserved = readByte(); | ||
entry.planes = readUInt16(); | ||
entry.bitCount = readUInt16(); | ||
entry.size = readInt32(); | ||
entry.offset = readInt32(); | ||
root.entries.push(entry); | ||
} | ||
|
||
for (entry in root.entries) { | ||
var bmp = haxe.io.Bytes.alloc(entry.size); | ||
input.readBytes(bmp, 0, entry.size); | ||
|
||
if (bmp.getInt32(0) == PNG_SIGN) { | ||
root.datas.push( PNG(bmp) ); | ||
continue; | ||
} | ||
|
||
var info = new BMPInfo(); | ||
info.sizeof = bmp.getInt32(0); | ||
info.width = bmp.getInt32(1 * 4); | ||
info.height = bmp.getInt32(2 * 4); | ||
info.planes = bmp.getUInt16(3 * 4); | ||
info.bitCount = bmp.getUInt16(3 * 4 + 2); | ||
info.compression = bmp.getInt32(4 * 4); | ||
info.sizeImage = bmp.getInt32(5 * 4); | ||
info.xPelsPerMeter = bmp.getInt32(6 * 4); | ||
info.yPelsPermeter = bmp.getInt32(7 * 4); | ||
info.clrUsed = bmp.getInt32(8 * 4); | ||
info.clrImportant = bmp.getInt32(9 * 4); | ||
|
||
var image = new DIB(bmp, info); | ||
root.datas.push(DIB(image)); | ||
} | ||
input = null; | ||
return root; | ||
} | ||
|
||
static inline var PNG_SIGN = 0x89 | ("P".code << 8) | ("N".code << 16) | ("G".code << 24); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package format.ico; | ||
|
||
import format.ico.Data; | ||
import format.ico.DIB; | ||
|
||
class Tools { | ||
|
||
/** | ||
* extract DIB to [R, G, B, A, ...] format | ||
*/ | ||
static public function extract( bmp : DIB ) : Uint8Array { | ||
// fast reference | ||
var ixor = bmp.ixor; | ||
var source = bmp.data; | ||
var height = bmp.height; | ||
var colors = bmp.colors; | ||
|
||
var stride = bmp.info.bytesPerline(); | ||
var output = new Uint8Array(bmp.width * height * 4); | ||
var byteWidth = (bmp.width * bmp.info.bitCount) + 7 >> 3; | ||
var index = 0; | ||
var column : Int; | ||
var starts : Int; | ||
|
||
switch (bmp.info.bitCount >> 2) { // shrink switch table | ||
case 0: // 1bits | ||
var iand = bmp.iand; | ||
var rowlen = bmp.width * 4; | ||
var limits = rowlen; | ||
while (--height >= 0) { | ||
starts = height * stride; | ||
column = 0; | ||
while (column < byteWidth) { | ||
var pos = starts + column++; | ||
var icx = source.get(ixor + pos); | ||
var mak = source.get(iand + pos); | ||
var bits = 8; | ||
while (--bits >= 0 && index < limits) { | ||
var rgb = colors[icx >> bits & 1]; | ||
output[index++] = rgb >> 16 & 0xFF; | ||
output[index++] = rgb >> 8 & 0xFF; | ||
output[index++] = rgb & 0xFF; | ||
output[index++] = (mak >> bits & 1) - 1; | ||
} | ||
} | ||
limits += rowlen; | ||
} | ||
case 1: // 4bits | ||
copyAlphaChannel(output, bmp); | ||
while (--height >= 0) { | ||
starts = ixor + height * stride; | ||
column = 0; | ||
while (column < byteWidth) { | ||
var icx = source.get(starts + column++); | ||
writeRGB(output, index, colors[(icx >> 4) & 15]); | ||
index += 4; | ||
|
||
// if the iamge width is not multiple of 2 | ||
if (column == byteWidth && (byteWidth & 1) == 1) | ||
break; | ||
|
||
writeRGB(output, index, colors[(icx ) & 15]); | ||
index += 4; | ||
} | ||
} | ||
case 2: // 8bits | ||
copyAlphaChannel(output, bmp); | ||
while (--height >= 0) { | ||
starts = ixor + height * stride; | ||
column = 0; | ||
while (column < byteWidth) { | ||
var icx = source.get(starts + column++); | ||
writeRGB(output, index, colors[icx]); | ||
index += 4; | ||
} | ||
} | ||
case 6: // 24bits with 1 alpha bit | ||
copyAlphaChannel(output, bmp); | ||
while (--height >= 0) { | ||
starts = ixor + height * stride; | ||
column = 0; | ||
while (column < byteWidth) { | ||
output[index ] = source.get(starts + column + 2); | ||
output[index + 1] = source.get(starts + column + 1); | ||
output[index + 2] = source.get(starts + column ); | ||
index += 4; | ||
column += 3; | ||
} | ||
} | ||
case 8: // 32bits, NOTE: it still has the unused alpha table in "bmp.iand" | ||
while (--height >= 0) { | ||
starts = ixor + height * stride; | ||
column = 0; | ||
while (column < byteWidth) { | ||
var argb = source.getInt32(starts + column); | ||
output[index++] = (argb >> 16) & 0xFF; | ||
output[index++] = (argb >> 8) & 0xFF; | ||
output[index++] = (argb ) & 0xFF; | ||
output[index++] = (argb >> 24) & 0xFF; | ||
column += 4; | ||
} | ||
} | ||
default: | ||
} | ||
return output; | ||
} | ||
|
||
static function writeRGB( output : Uint8Array, i : Int, rgb : Int ) : Void { | ||
if (output[i + 3] == 0) | ||
return; | ||
output[i++] = (rgb >> 16) & 0xFF; | ||
output[i++] = (rgb >> 8) & 0xFF; | ||
output[i] = rgb & 0xFF; | ||
} | ||
|
||
static function copyAlphaChannel( output : Uint8Array, bmp : DIB ) : Void { | ||
var data = bmp.data; | ||
var iand = bmp.iand; | ||
var stride = BMPInfo.WIDTHBYTES(bmp.width); | ||
var rowlen = bmp.width * 4; | ||
var height = bmp.height; | ||
var limits = rowlen; | ||
var i = 3; | ||
while (--height >= 0) { | ||
var column = 0; | ||
var starts = iand + height * stride; | ||
while (column < stride) { | ||
var mark = data.get(starts + column++); | ||
var bits = 8; | ||
while (--bits >= 0 && i < limits) { | ||
output[i] = ((mark >> bits) & 1) - 1; // if (1) then 0 else -1 | ||
i += 4; | ||
} | ||
} | ||
limits += rowlen; | ||
} | ||
} | ||
} | ||
|
||
#if js | ||
typedef Uint8ArrayInner = js.lib.Uint8Array; | ||
#elseif hl | ||
typedef Uint8ArrayInner = hl.Bytes; | ||
#else | ||
typedef Uint8ArrayInner = haxe.io.Bytes; | ||
#end | ||
|
||
@:pure | ||
@:forward(length) | ||
extern abstract Uint8Array(Uint8ArrayInner) to Uint8ArrayInner { | ||
#if (js || hl) | ||
inline function new( size : Int ) this = new Uint8ArrayInner(size); | ||
|
||
@:arrayAccess inline private function get( pos : Int ) : Int return this[pos]; | ||
|
||
@:arrayAccess inline private function set( pos : Int, value : Int ) : Int return this[pos] = value; | ||
|
||
#else | ||
inline function new( size : Int ) this = haxe.io.Bytes.alloc(size); | ||
|
||
@:arrayAccess inline private function get( pos : Int ) : Int { | ||
return this.get(pos); | ||
} | ||
|
||
@:arrayAccess inline private function set( pos : Int, value : Int ) : Int { | ||
this.set(pos, value); | ||
return value; | ||
} | ||
#end | ||
} |