Refactor the JPG encoder to process in time slices rather than all at once.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@584 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Robin Barooah
2008-07-29 23:57:13 +00:00
parent 13f7a78932
commit 5dd60e0d45
+129 -51
View File
@@ -13,11 +13,14 @@ import flash.display.*;
import flash.utils.*; import flash.utils.*;
/** /**
* Class that converts BitmapData into a valid JPEG * Class for compressing bitmaps into jpegs. Each instance may only be used for a single image.
* When the encoder is constructed, the headers are generated, but no pixel encoding is done.
* Subsequent calls to 'process' will encode pixels for a specified length of time. Once the
* encoding is done (determined by process returning true, or a call to isComplete), the jpeg data
* can be accessed via getJpeg.
*/ */
public class JPGEncoder public class JPGEncoder
{ {
// Static table initialization // Static table initialization
private var ZigZag:Array = [ private var ZigZag:Array = [
@@ -207,39 +210,33 @@ public class JPGEncoder
} }
} }
// IO functions
private var byteout:ByteArray;
private var bytenew:int = 0;
private var bytepos:int = 7;
private function writeBits(bs:BitString):void private function writeBits(bs:BitString):void
{ {
var value:int = bs.val; var value:int = bs.val;
var posval:int = bs.len-1; var posval:int = bs.len-1;
while ( posval >= 0 ) { while ( posval >= 0 ) {
if (value & uint(1 << posval) ) { if (value & uint(1 << posval) ) {
bytenew |= uint(1 << bytepos); _bytenew |= uint(1 << _bytepos);
} }
posval--; posval--;
bytepos--; _bytepos--;
if (bytepos < 0) { if (_bytepos < 0) {
if (bytenew == 0xFF) { if (_bytenew == 0xFF) {
writeByte(0xFF); writeByte(0xFF);
writeByte(0); writeByte(0);
} }
else { else {
writeByte(bytenew); writeByte(_bytenew);
} }
bytepos=7; _bytepos=7;
bytenew=0; _bytenew=0;
} }
} }
} }
private function writeByte(value:int):void private function writeByte(value:int):void
{ {
byteout.writeByte(value); _byteout.writeByte(value);
} }
private function writeWord(value:int):void private function writeWord(value:int):void
@@ -542,16 +539,17 @@ public class JPGEncoder
} }
/** /**
* Constructor for JPEGEncoder class * Construct a new JPGEncoder object.
* *
* @param quality The quality level between 1 and 100 that detrmines the * @param image The image to encode
* level of compression used in the generated JPEG * @param quality Quality level between 1 and 100 determining the level of compression.s
* @langversion ActionScript 3.0 * @param pixelGranularity The minumum number of pixels to process at a time.
* @playerversion Flash 9.0
* @tiptext
*/ */
public function JPGEncoder(quality:Number = 50) public function JPGEncoder(image :BitmapData, quality :Number = 50, pixelGranularity :int = 100)
{ {
_image = image;
_pixelGranularity = pixelGranularity;
if (quality <= 0) { if (quality <= 0) {
quality = 1; quality = 1;
} }
@@ -568,58 +566,138 @@ public class JPGEncoder
initHuffmanTbl(); initHuffmanTbl();
initCategoryNumber(); initCategoryNumber();
initQuantTables(sf); initQuantTables(sf);
writeHeader();
initializeEncoding();
} }
/** /**
* Created a JPEG image from the specified BitmapData * Write the jpeg header into the byte array for output.
*
* @param image The BitmapData that will be converted into the JPEG format.
* @return a ByteArray representing the JPEG encoded image data.
* @langversion ActionScript 3.0
* @playerversion Flash 9.0
* @tiptext
*/ */
public function encode(image:BitmapData):ByteArray protected function writeHeader () :void
{ {
// Initialize bit writer // Initialize bit writer
byteout = new ByteArray(); _bytenew=0;
bytenew=0; _bytepos=7;
bytepos=7;
// Add JPEG headers // Add JPEG headers
writeWord(0xFFD8); // SOI writeWord(0xFFD8); // SOI
writeAPP0(); writeAPP0();
writeDQT(); writeDQT();
writeSOF0(image.width,image.height); writeSOF0(_image.width,_image.height);
writeDHT(); writeDHT();
writeSOS(); writeSOS();
}
protected function initializeEncoding () :void {
// Encode 8x8 macroblocks
_bytenew=0;
_bytepos=7;
}
// Encode 8x8 macroblocks /**
var DCY:Number=0; * Encode a specified number of pixels. Fewer pixels will be encoded if the end of the image
var DCU:Number=0; * is reached first.
var DCV:Number=0; */
bytenew=0; protected function encodePixels (unitSize: int) :void
bytepos=7; {
for (var ypos:int=0; ypos<image.height; ypos+=8) { for (var count :int = 0; !pixelsDone && count < unitSize; count++) {
for (var xpos:int=0; xpos<image.width; xpos+=8) {
RGB2YUV(image, xpos, ypos); RGB2YUV(_image, _xpos, _ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); _DCY = processDU(YDU, fdtbl_Y, _DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); _DCU = processDU(UDU, fdtbl_UV, _DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); _DCV = processDU(VDU, fdtbl_UV, _DCV, UVDC_HT, UVAC_HT);
_xpos += 8;
// If we have moved past the end of the line, move to the next one.
if (_xpos >= _image.width) {
_xpos = 0;
_ypos += 8;
// If we have moved past the last line, we are done.
if (_ypos >= _image.height) {
pixelsDone = true;
}
} }
} }
}
/**
* Add the EOI marker to the end of the buffer, and make the result available to the consumer
* of this class.
*/
protected function completeEncoding () :void
{
// Do the bit alignment of the EOI marker // Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) { if ( _bytepos >= 0 ) {
var fillbits:BitString = new BitString(); var fillbits:BitString = new BitString();
fillbits.len = bytepos+1; fillbits.len = _bytepos+1;
fillbits.val = (1<<(bytepos+1))-1; fillbits.val = (1<<(_bytepos+1))-1;
writeBits(fillbits); writeBits(fillbits);
} }
writeWord(0xFFD9); //EOI writeWord(0xFFD9); //EOI
return byteout; _encodedJpeg = _byteout;
} }
/**
* Return true if the encoding is complete.
*/
public function isComplete () :Boolean
{
return null != _encodedJpeg;
}
/**
* Work on encoding the image for a specified number of milliseconds. Return true if there
* is no more processing to do.
*/
public function process (timeSlice :int) :Boolean
{
const endTime :int = getTimer() + timeSlice;
while (! pixelsDone && getTimer() < endTime) {
encodePixels(_pixelGranularity);
}
if (pixelsDone) {
completeEncoding();
return true;
}
return false;
}
/**
* Return a byte array containing the encoded jpeg.
*/
public function getJpeg () :ByteArray
{
return _encodedJpeg;
}
/** The image to encode **/
protected var _image :BitmapData;
/** Byte array to contain the resuling jpg **/
protected var _encodedJpeg :ByteArray;
/** Byte array to contain the jpg while encoding is in progress. **/
protected var _byteout :ByteArray = new ByteArray();
// Used to keep track of the positon of the writer in the byte array
protected var _bytenew:int = 0;
protected var _bytepos:int = 7;
// State for the pixel block encoding process
protected var _DCY:Number=0;
protected var _DCU:Number=0;
protected var _DCV:Number=0;
protected var _ypos :int = 0;
protected var _xpos :int = 0;
protected var pixelsDone:Boolean = false;
/** The minumum number of pixels that will be processed at a time. **/
protected var _pixelGranularity :int;
} }
} }