| 1 |
//---------------------------------------------------------------------------------------------------- |
|---|
| 2 |
// Extended ByteArray |
|---|
| 3 |
// Copyright (c) 2008 keim All rights reserved. |
|---|
| 4 |
// Distributed under BSD-style license (see org.si.license.txt). |
|---|
| 5 |
//---------------------------------------------------------------------------------------------------- |
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
package org.si.utils { |
|---|
| 11 |
import flash.net.FileFilter; |
|---|
| 12 |
import flash.net.FileReference; |
|---|
| 13 |
import flash.net.URLRequest; |
|---|
| 14 |
import flash.net.URLLoader; |
|---|
| 15 |
import flash.utils.ByteArray; |
|---|
| 16 |
import flash.events.Event; |
|---|
| 17 |
import flash.display.BitmapData; |
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
/** Extended ByteArray, png image serialize, IFF chunk structure, FileReference operations. */ |
|---|
| 21 |
public class ByteArrayExt extends ByteArray { |
|---|
| 22 |
// variables |
|---|
| 23 |
//-------------------------------------------------- |
|---|
| 24 |
static private var crc32:Vector.<uint> = null; |
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
// constructor |
|---|
| 30 |
//-------------------------------------------------- |
|---|
| 31 |
/** constructor */ |
|---|
| 32 |
function ByteArrayExt(copyFrom:ByteArray = null) |
|---|
| 33 |
{ |
|---|
| 34 |
super(); |
|---|
| 35 |
if (copyFrom) { |
|---|
| 36 |
this.writeBytes(copyFrom); |
|---|
| 37 |
this.endian = copyFrom.endian; |
|---|
| 38 |
this.position = 0; |
|---|
| 39 |
} |
|---|
| 40 |
} |
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
// bitmap data operations |
|---|
| 46 |
//-------------------------------------------------- |
|---|
| 47 |
/** translate from BitmapData |
|---|
| 48 |
* @param bmd BitmapData translating from. |
|---|
| 49 |
* @return this instance |
|---|
| 50 |
*/ |
|---|
| 51 |
public function fromBitmapData(bmd:BitmapData) : ByteArrayExt |
|---|
| 52 |
{ |
|---|
| 53 |
var x:int, y:int, i:int, w:int=bmd.width, h:int=bmd.height, len:int, p:int; |
|---|
| 54 |
this.clear(); |
|---|
| 55 |
len = bmd.getPixel(w-1, h-1); |
|---|
| 56 |
for (y=0, i=0; y<h && i<len; y++) for (x=0; x<w && i<len; x++, i++) { |
|---|
| 57 |
p = bmd.getPixel(x, y); |
|---|
| 58 |
this.writeByte(p>>>16); |
|---|
| 59 |
if (++i >= len) break; |
|---|
| 60 |
this.writeByte(p>>>8) |
|---|
| 61 |
if (++i >= len) break; |
|---|
| 62 |
this.writeByte(p); |
|---|
| 63 |
} |
|---|
| 64 |
this.position = 0; |
|---|
| 65 |
return this; |
|---|
| 66 |
} |
|---|
| 67 |
|
|---|
| 68 |
|
|---|
| 69 |
/** translate to BitmapData |
|---|
| 70 |
* @param width same as BitmapData's constructor, set 0 to calculate automatically. |
|---|
| 71 |
* @param height same as BitmapData's constructor, set 0 to calculate automatically. |
|---|
| 72 |
* @param transparent same as BitmapData's constructor. |
|---|
| 73 |
* @param fillColor same as BitmapData's constructor. |
|---|
| 74 |
* @return translated BitmapData |
|---|
| 75 |
*/ |
|---|
| 76 |
public function toBitmapData(width:int=0, height:int=0, transparent:Boolean = true, fillColor:uint = 0xFFFFFFFF) : BitmapData |
|---|
| 77 |
{ |
|---|
| 78 |
var x:int, y:int, reqh:int, bmd:BitmapData, len:int = this.length, p:uint; |
|---|
| 79 |
if (width == 0) width = ((int(Math.sqrt(len)+65535/65536))+15)&(~15); |
|---|
| 80 |
reqh = ((int(len/width+65535/65536))+15)&(~15); |
|---|
| 81 |
if (height == 0 || reqh > height) height = reqh; |
|---|
| 82 |
bmd = new BitmapData(width, height, transparent, fillColor); |
|---|
| 83 |
this.position = 0; |
|---|
| 84 |
for (y=0; y<height; y++) for (x=0; x<width; x++) { |
|---|
| 85 |
if (this.bytesAvailable < 3) break; |
|---|
| 86 |
bmd.setPixel32(x, y, 0xff000000|((this.readUnsignedShort()<<8)|this.readUnsignedByte())); |
|---|
| 87 |
} |
|---|
| 88 |
p = 0xff000000; |
|---|
| 89 |
if (this.bytesAvailable > 0) p |= this.readUnsignedByte() << 16; |
|---|
| 90 |
if (this.bytesAvailable > 0) p |= this.readUnsignedByte() << 8; |
|---|
| 91 |
if (this.bytesAvailable > 0) p |= this.readUnsignedByte(); |
|---|
| 92 |
bmd.setPixel32(x, y, p); |
|---|
| 93 |
this.position = 0; |
|---|
| 94 |
bmd.setPixel32(x, y, 0xff000000|(uint(this.length))); |
|---|
| 95 |
return bmd; |
|---|
| 96 |
} |
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 |
/** translate to 24bit png data |
|---|
| 100 |
* @param width png file width, set 0 to calculate automatically. |
|---|
| 101 |
* @param height png file height, set 0 to calculate automatically. |
|---|
| 102 |
* @return ByteArrayExt of PNG data |
|---|
| 103 |
*/ |
|---|
| 104 |
public function toPNGData(width:int=0, height:int=0) : ByteArrayExt |
|---|
| 105 |
{ |
|---|
| 106 |
var i:int, imax:int, reqh:int, pixels:int = (this.length+2)/3, y:int, |
|---|
| 107 |
png:ByteArrayExt = new ByteArrayExt(), |
|---|
| 108 |
header:ByteArray = new ByteArray(), |
|---|
| 109 |
content:ByteArray = new ByteArray(); |
|---|
| 110 |
//----- settings |
|---|
| 111 |
if (width == 0) width = ((int(Math.sqrt(pixels)+65535/65536))+15)&(~15); |
|---|
| 112 |
reqh = ((int(pixels/width+65535/65536))+15)&(~15); |
|---|
| 113 |
if (height == 0 || reqh > height) height = reqh; |
|---|
| 114 |
header.writeInt(width); // width |
|---|
| 115 |
header.writeInt(height); // height |
|---|
| 116 |
header.writeUnsignedInt(0x08020000); // 24bit RGB |
|---|
| 117 |
header.writeByte(0); |
|---|
| 118 |
imax = pixels - width; |
|---|
| 119 |
for (y=0, i=0; i<imax; i+=width, y++) { |
|---|
| 120 |
content.writeByte(0); |
|---|
| 121 |
content.writeBytes(this, i*3, width*3); |
|---|
| 122 |
} |
|---|
| 123 |
content.writeByte(0); |
|---|
| 124 |
content.writeBytes(this, i*3, this.length-i*3); |
|---|
| 125 |
imax = (i + width) * 3; |
|---|
| 126 |
for (i=this.length; i<imax; i++) content.writeByte(0); |
|---|
| 127 |
imax = width * 3 + 1; |
|---|
| 128 |
for (y++; y<height; y++) for (i=0; i<imax; i++) content.writeByte(0); |
|---|
| 129 |
i = this.length; |
|---|
| 130 |
content.position -= 3; |
|---|
| 131 |
content.writeByte(i>>>16); |
|---|
| 132 |
content.writeByte(i>>>8); |
|---|
| 133 |
content.writeByte(i); |
|---|
| 134 |
content.compress(); |
|---|
| 135 |
|
|---|
| 136 |
//----- write png data |
|---|
| 137 |
png.writeUnsignedInt(0x89504e47); |
|---|
| 138 |
png.writeUnsignedInt(0x0D0A1A0A); |
|---|
| 139 |
png_writeChunk(0x49484452, header); |
|---|
| 140 |
png_writeChunk(0x49444154, content); |
|---|
| 141 |
png.writeUnsignedInt(0); |
|---|
| 142 |
png.writeUnsignedInt(0x49454E44); |
|---|
| 143 |
png.position = 0; |
|---|
| 144 |
|
|---|
| 145 |
return png; |
|---|
| 146 |
|
|---|
| 147 |
//----- write png chunk |
|---|
| 148 |
function png_writeChunk(type:uint, data:ByteArray) : void { |
|---|
| 149 |
png.writeUnsignedInt(data.length); |
|---|
| 150 |
var crcStartAt:uint = png.position; |
|---|
| 151 |
png.writeUnsignedInt(type); |
|---|
| 152 |
png.writeBytes(data); |
|---|
| 153 |
png.writeUnsignedInt(calculateCRC32(png, crcStartAt, png.position - crcStartAt)); |
|---|
| 154 |
} |
|---|
| 155 |
} |
|---|
| 156 |
|
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 |
|
|---|
| 160 |
// IFF chunk operations |
|---|
| 161 |
//-------------------------------------------------- |
|---|
| 162 |
/** write IFF chunk */ |
|---|
| 163 |
public function writeChunk(chunkID:String, data:ByteArray, listType:String=null) : void |
|---|
| 164 |
{ |
|---|
| 165 |
var isList:Boolean = (chunkID == "RIFF" || chunkID == "LIST"), |
|---|
| 166 |
len:int = ((data) ? data.length : 0) + ((isList) ? 4 : 0); |
|---|
| 167 |
this.writeMultiByte((chunkID+" ").substr(0,4), "us-ascii"); |
|---|
| 168 |
this.writeInt(len); |
|---|
| 169 |
if (isList) { |
|---|
| 170 |
if (listType) this.writeMultiByte((listType+" ").substr(0,4), "us-ascii"); |
|---|
| 171 |
else this.writeMultiByte(" ", "us-ascii"); |
|---|
| 172 |
} |
|---|
| 173 |
if (data) { |
|---|
| 174 |
this.writeBytes(data); |
|---|
| 175 |
if (len & 1) this.writeByte(0); |
|---|
| 176 |
} |
|---|
| 177 |
} |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
/** read (or search) IFF chunk from current position. */ |
|---|
| 181 |
public function readChunk(bytes:ByteArray, offset:int=0, searchChunkID:String=null) : * |
|---|
| 182 |
{ |
|---|
| 183 |
var id:String, len:int, type:String=null; |
|---|
| 184 |
while (this.bytesAvailable > 0) { |
|---|
| 185 |
id = this.readMultiByte(4, "us-ascii"); |
|---|
| 186 |
len = this.readInt(); |
|---|
| 187 |
if (searchChunkID == null || searchChunkID == id) { |
|---|
| 188 |
if (id == "RIFF" || id == "LIST") { |
|---|
| 189 |
type = this.readMultiByte(4, "us-ascii"); |
|---|
| 190 |
this.readBytes(bytes, offset, len-4); |
|---|
| 191 |
} else { |
|---|
| 192 |
this.readBytes(bytes, offset, len); |
|---|
| 193 |
} |
|---|
| 194 |
if (len & 1) this.readByte(); |
|---|
| 195 |
bytes.endian = this.endian; |
|---|
| 196 |
return {"chunkID":id, "length":len, "listType":type}; |
|---|
| 197 |
} |
|---|
| 198 |
this.position += len + (len & 1); |
|---|
| 199 |
} |
|---|
| 200 |
return null; |
|---|
| 201 |
} |
|---|
| 202 |
|
|---|
| 203 |
|
|---|
| 204 |
/** read all IFF chunks from current position. */ |
|---|
| 205 |
public function readAllChunks() : * |
|---|
| 206 |
{ |
|---|
| 207 |
var header:*, ret:* = {}, pickup:ByteArrayExt; |
|---|
| 208 |
while (header = readChunk(pickup = new ByteArrayExt())) { |
|---|
| 209 |
if (header.chunkID in ret) { |
|---|
| 210 |
if (ret[header.chunkID] is Array) ret[header.chunkID].push(pickup); |
|---|
| 211 |
else ret[header.chunkID] = [ret[header.chunkID]]; |
|---|
| 212 |
} else { |
|---|
| 213 |
ret[header.chunkID] = pickup; |
|---|
| 214 |
} |
|---|
| 215 |
} |
|---|
| 216 |
return ret; |
|---|
| 217 |
} |
|---|
| 218 |
|
|---|
| 219 |
|
|---|
| 220 |
|
|---|
| 221 |
|
|---|
| 222 |
// URL operations |
|---|
| 223 |
//-------------------------------------------------- |
|---|
| 224 |
/** load from URL |
|---|
| 225 |
* @param url URL string to load swf file. |
|---|
| 226 |
* @param onComplete handler for Event.COMPLETE. The format is function(bae:ByteArrayExt) : void. |
|---|
| 227 |
* @param onCancel handler for Event.CANCEL. The format is function(e:Event) : void. |
|---|
| 228 |
* @param onError handler for Event.IO_ERROR. The format is function(e:IOErrorEvent) : void. |
|---|
| 229 |
*/ |
|---|
| 230 |
public function load(url:String, onComplete:Function=null, onCancel:Function=null, onError:Function=null) : void |
|---|
| 231 |
{ |
|---|
| 232 |
var loader:URLLoader = new URLLoader(), bae:ByteArrayExt = this; |
|---|
| 233 |
loader.dataFormat = "binary"; |
|---|
| 234 |
loader.addEventListener("complete", _onLoadComplete); |
|---|
| 235 |
loader.addEventListener("cancel", _onLoadCancel); |
|---|
| 236 |
loader.addEventListener("ioError", _onLoadError); |
|---|
| 237 |
loader.load(new URLRequest(url)); |
|---|
| 238 |
|
|---|
| 239 |
function _removeAllEventListeners(e:Event, callback:Function) : void { |
|---|
| 240 |
loader.removeEventListener("complete", _onLoadComplete); |
|---|
| 241 |
loader.removeEventListener("cancel", _onLoadCancel); |
|---|
| 242 |
loader.removeEventListener("ioError", _onLoadError); |
|---|
| 243 |
if (callback != null) callback(e); |
|---|
| 244 |
} |
|---|
| 245 |
function _onLoadComplete(e:Event) : void { |
|---|
| 246 |
bae.clear(); |
|---|
| 247 |
bae.writeBytes(e.target.data); |
|---|
| 248 |
_removeAllEventListeners(e, null); |
|---|
| 249 |
bae.position = 0; |
|---|
| 250 |
if (onComplete != null) onComplete(bae); |
|---|
| 251 |
} |
|---|
| 252 |
function _onLoadCancel(e:Event) : void { _removeAllEventListeners(e, onCancel); } |
|---|
| 253 |
function _onLoadError(e:Event) : void { _removeAllEventListeners(e, onError); } |
|---|
| 254 |
} |
|---|
| 255 |
|
|---|
| 256 |
|
|---|
| 257 |
|
|---|
| 258 |
|
|---|
| 259 |
// FileReference operations |
|---|
| 260 |
//-------------------------------------------------- |
|---|
| 261 |
/** Call FileReference::browse(). |
|---|
| 262 |
* @param onComplete handler for Event.COMPLETE. The format is function(bae:ByteArrayExt) : void. |
|---|
| 263 |
* @param onCancel handler for Event.CANCEL. The format is function(e:Event) : void. |
|---|
| 264 |
* @param onError handler for Event.IO_ERROR. The format is function(e:IOErrorEvent) : void. |
|---|
| 265 |
* @param fileFilterName name of file filter. |
|---|
| 266 |
* @param extensions extensions of file filter (like "*.jpg;*.png;*.gif"). |
|---|
| 267 |
*/ |
|---|
| 268 |
public function browse(onComplete:Function=null, onCancel:Function=null, onError:Function=null, fileFilterName:String=null, extensions:String=null) : void |
|---|
| 269 |
{ |
|---|
| 270 |
var fr:FileReference = new FileReference(), bae:ByteArrayExt = this; |
|---|
| 271 |
fr.addEventListener("select", function(e:Event) : void { |
|---|
| 272 |
e.target.removeEventListener(e.type, arguments.callee); |
|---|
| 273 |
fr.addEventListener("complete", _onBrowseComplete); |
|---|
| 274 |
fr.addEventListener("cancel", _onBrowseCancel); |
|---|
| 275 |
fr.addEventListener("ioError", _onBrowseError); |
|---|
| 276 |
fr.load(); |
|---|
| 277 |
}); |
|---|
| 278 |
fr.browse((fileFilterName) ? [new FileFilter(fileFilterName, extensions)] : null); |
|---|
| 279 |
|
|---|
| 280 |
function _removeAllEventListeners(e:Event, callback:Function) : void { |
|---|
| 281 |
fr.removeEventListener("complete", _onBrowseComplete); |
|---|
| 282 |
fr.removeEventListener("cancel", _onBrowseCancel); |
|---|
| 283 |
fr.removeEventListener("ioError", _onBrowseError); |
|---|
| 284 |
if (callback != null) callback(e); |
|---|
| 285 |
} |
|---|
| 286 |
function _onBrowseComplete(e:Event) : void { |
|---|
| 287 |
bae.clear(); |
|---|
| 288 |
bae.writeBytes(e.target.data); |
|---|
| 289 |
_removeAllEventListeners(e, null); |
|---|
| 290 |
bae.position = 0; |
|---|
| 291 |
if (onComplete != null) onComplete(bae); |
|---|
| 292 |
} |
|---|
| 293 |
function _onBrowseCancel(e:Event) : void { _removeAllEventListeners(e, onCancel); } |
|---|
| 294 |
function _onBrowseError(e:Event) : void { _removeAllEventListeners(e, onError); } |
|---|
| 295 |
} |
|---|
| 296 |
|
|---|
| 297 |
|
|---|
| 298 |
/** Call FileReference::save(). |
|---|
| 299 |
* @param defaultFileName default file name. |
|---|
| 300 |
* @param onComplete handler for Event.COMPLETE. The format is function(e:Event) : void. |
|---|
| 301 |
* @param onCancel handler for Event.CANCEL. The format is function(e:Event) : void. |
|---|
| 302 |
* @param onError handler for Event.IO_ERROR. The format is function(e:IOErrorEvent) : void. |
|---|
| 303 |
*/ |
|---|
| 304 |
public function save(defaultFileName:String=null, onComplete:Function=null, onCancel:Function=null, onError:Function=null) : void |
|---|
| 305 |
{ |
|---|
| 306 |
var fr:FileReference = new FileReference(); |
|---|
| 307 |
fr.addEventListener("complete", _onSaveComplete); |
|---|
| 308 |
fr.addEventListener("cancel", _onSaveCancel); |
|---|
| 309 |
fr.addEventListener("ioError", _onSaveError); |
|---|
| 310 |
fr.save(this, defaultFileName); |
|---|
| 311 |
|
|---|
| 312 |
function _removeAllEventListeners(e:Event, callback:Function) : void { |
|---|
| 313 |
fr.removeEventListener("complete", _onSaveComplete); |
|---|
| 314 |
fr.removeEventListener("cancel", _onSaveCancel); |
|---|
| 315 |
fr.removeEventListener("ioError", _onSaveError); |
|---|
| 316 |
if (callback != null) callback(e); |
|---|
| 317 |
} |
|---|
| 318 |
function _onSaveComplete(e:Event) : void { _removeAllEventListeners(e, onComplete); } |
|---|
| 319 |
function _onSaveCancel(e:Event) : void { _removeAllEventListeners(e, onCancel); } |
|---|
| 320 |
function _onSaveError(e:Event) : void { _removeAllEventListeners(e, onError); } |
|---|
| 321 |
} |
|---|
| 322 |
|
|---|
| 323 |
|
|---|
| 324 |
|
|---|
| 325 |
|
|---|
| 326 |
// utilities |
|---|
| 327 |
//-------------------------------------------------- |
|---|
| 328 |
/** calculate crc32 chuck sum */ |
|---|
| 329 |
static public function calculateCRC32(byteArray:ByteArray, offset:int=0, length:int=0) : uint |
|---|
| 330 |
{ |
|---|
| 331 |
var i:int, j:int, c:uint, currentPosition:int; |
|---|
| 332 |
if (!crc32) { |
|---|
| 333 |
crc32 = new Vector.<uint>(256, false); |
|---|
| 334 |
for (i=0; i<256; i++) { |
|---|
| 335 |
for (c=i, j=0; j<8; j++) c = uint(((c&1)?0xedb88320:0)^(c>>>1)); |
|---|
| 336 |
crc32[i] = c; |
|---|
| 337 |
} |
|---|
| 338 |
} |
|---|
| 339 |
|
|---|
| 340 |
if (length==0) length = byteArray.length; |
|---|
| 341 |
currentPosition = byteArray.position; |
|---|
| 342 |
byteArray.position = offset; |
|---|
| 343 |
for (c=0xffffffff, i=0; i<length; i++) { |
|---|
| 344 |
j = (c ^ byteArray.readUnsignedByte()) & 255; |
|---|
| 345 |
c >>>= 8; |
|---|
| 346 |
c ^= crc32[j]; |
|---|
| 347 |
} |
|---|
| 348 |
byteArray.position = currentPosition; |
|---|
| 349 |
|
|---|
| 350 |
return c ^ 0xffffffff; |
|---|
| 351 |
} |
|---|
| 352 |
} |
|---|
| 353 |
} |
|---|
| 354 |
|
|---|