Fast or asynchronous AS3 JPEG encoding - actionscript-3

I'm currently using the JPGEncoder from the AS3 core lib to encode a bitmap to JPEG
var enc:JPGEncoder = new JPGEncoder(90);
var jpg:ByteArray = enc.encode(bitmap);
Because the bitmap is rather large (3000 x 2000) the encoding takes a long while (about 20 seconds), causing the application to seemingly freeze while encoding. To solve this, I need either:
An asynchronous encoder so I can keep updating the screen (with a progress bar or something) while encoding
An alternative encoder which is simply faster
Is either possible, and how can I do it?

I found an asynchronous encoder:
http://www.switchonthecode.com/tutorials/flex-tutorial-an-asynchronous-jpeg-encoder

Setting up the encoder to be asynchronous would likely be your best bet.
Here are two examples from Adobe
This example is with actionscript/flex, but its the same idea.

You can do it much faster with Alchemy:
http://www.websector.de/blog/2009/06/21/speed-up-jpeg-encoding-using-alchemy/
http://segfaultlabs.com/devlogs/alchemy-asynchronous-jpeg-encoding-2

You can use the alchemy encoder. It is really fast and you can encode images asynchronously. You can use this class to abstract it.
public class JPGAlchemyEncoder {
private static var alchemyWrapper:Object;
private var quality:Number;
public function JPGAlchemyEncoder(quality:Number) {
this.quality = quality;
if (!alchemyWrapper){
var loader:CLibInit = new CLibInit;
alchemyWrapper = loader.init();
}
}
public function encode(bitmapData:BitmapData):ByteArray{
var data: ByteArray = bitmapData.clone().getPixels( bitmapData.rect );
data.position = 0;
return alchemyWrapper.write_jpeg_file(data, bitmapData.width, bitmapData.height, 3, 2, quality);
}
public function encodeAsync(bitmapData:BitmapData, completeHandler:Function):void{
var encodedData:ByteArray = new ByteArray();
var data: ByteArray = bitmapData.clone().getPixels(bitmapData.rect);
data.position = 0;
var encodeComplete:Function = function():void{
completeHandler(encodedData);
};
alchemyWrapper.encodeAsync(encodeComplete, data, encodedData, bitmapData.width, bitmapData.height, quality);
}
}
}

asynchronous decode the png picture in separate thread ,supported by new version ...
var loaderContext:LoaderContext = new LoaderContext();
loaderContext.imageDecodingPolicy = ImageDecodingPolicy.ON_LOAD;
var loader:Loader = new Loader();
loader.load(new URLRequest("...png"),loaderContext);
addChild(loader);
that's official.

Related

AS3 Serialization

What I'm trying to do:
-Have objects in a toolbar, drag and dropable onto a movieclip (they then become a child of the movieclip). Once this is done, I want to be able serialize this object, so I can save it to a file. Then, I can reload this file, and continue draging/dropping things onto/off of this movieclip.
How I'm doing it:
public class Serialization {
public static function serializeToString(value:Object):String{
if(value==null){
trace("null isn't a legal serialization candidate");
}
var bytes:ByteArray = new ByteArray();
bytes.writeObject(value);
bytes.position = 0;
var be:Base64Encoder = new Base64Encoder();
be.encode(bytes.readUTFBytes(bytes.length));
return be.drain();
}
public static function readObjectFromStringBytes(value:String):Object{
var dec:Base64Decoder=new Base64Decoder();
dec.decode(value);
var result:ByteArray=dec.drain();
result.position=0;
return result.readObject();
}
}
This is where call the function/write it to the file:
var fr:FileReference = new FileReference;
fr.addEventListener(Event.COMPLETE, success);
var txtString:String = new String();
txtString = save.Serialization.serializeToString(pagePic);
trace(txtString);
fr.save(txtString, "test.txt");
Unfortunately, txtString appears to be blank. Am I approaching this wrong?
Side notes:
This is being developed for a mobile platform.
Unfortunately MovieClips, Sounds, and other resources cannot be serialized. My solution is to create a custom class that will store all my properties and reassign them upon loading, or just write to/parse a text file when saving/loading.

Game session Sound Playback / Recording

So I'm making a simple flash game. Basically I have a turntable that goes on for 30 seconds with a couple of sample music that adds up together in multiple layers of sound to form a final song.
Now I would like to be able to record and play the sounds at the end of the game.
I've created a SoundFx class that takes mp3 audio and turns it into byteArrays with the hope to mix the audios in to the same Sound channel.
Now I've reached a certain impass since I cannot properly mix the bytearrays. I'm starting to think it's not possible to encode the byte Arrays as you add the channels to the mix.
I'd love to be guided in the right direction. I'm not sure if the best way to proceed from here, even just the playback would be nice. Creating a button log would probably fix the playback and mixing the audio in a second run to go straight to the file. but it sure seems like a long path to achieve this.
Many thanks and apologies for my crappy english in advance
David R.
some code on the matter:
private var srcSound:Sound;
private var sound1:Sound;
private var sound2:Sound;
private var soundChannel:SoundChannel;
private var bytes:ByteArray;
private var incbytes:ByteArray;
private var mixedBytes:ByteArray;
public var pitchShiftFactor:Number;
public var position:Number;
public var AddSound:Boolean = false;
public var incremental:Number;
public var left1:Number;
public var left2:Number;
public var right1:Number;
public var right2:Number;
public var mixedBytes1:Number;
public var mixedBytes2:Number;
public function SoundFx() {
}
public function playFx(srcSound:Sound):void{
this.srcSound = srcSound;
position = 0;
var morphedSound:Sound = new Sound();
morphedSound.addEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);
soundChannel = morphedSound.play();
}
public function addSound(sound1:Sound , sound2:Sound):void{
this.sound1 = sound1;
this.sound2 = sound2;
sound1.addEventListener(SampleDataEvent.SAMPLE_DATA, addSampleData);
position = 0;
soundChannel = sound1.play();
soundChannel = sound2.play();
AddSound = true;
}
private function addSampleData(event:SampleDataEvent):void{
position = 0;
var incbytes:ByteArray = new ByteArray();
position += sound1.extract(incbytes, 4096, position);
var bytes:ByteArray = new ByteArray();
position += sound2.extract(bytes, 4096, position);
event.data.writeBytes(mixBytes(bytes, incbytes));
}
private function sampleDataHandler(event:SampleDataEvent):void
{
var bytes:ByteArray = new ByteArray();
position += srcSound.extract(bytes, 4096, position);
event.data.writeBytes(editBytes(bytes));
}
private function mixBytes(bytes1:ByteArray , bytes2:ByteArray ):ByteArray{
bytes.position = 0;
incbytes.position = 0;
var returnBytes:ByteArray = new ByteArray();
while(bytes1.bytesAvailable > 0)
{
left1 = bytes1.readFloat();
left2 = bytes2.readFloat();
right1 = bytes1.readFloat();
right2 = bytes2.readFloat();
mixedBytes1 = left1 + left2;
mixedBytes2 = right1 + right1;
mixedBytes.writeFloat(mixedBytes1);
mixedBytes.writeFloat(mixedBytes2);
}
return mixedBytes;
}
private function editBytes(bytes:ByteArray):ByteArray{
//var skipCount:Number = 0;
var returnBytes:ByteArray = new ByteArray();
bytes.position = 0;
while(bytes.bytesAvailable > 0)
{
//skipCount++;
returnBytes.writeFloat(bytes.readFloat());
returnBytes.writeFloat(bytes.readFloat());
}
return returnBytes;
}
}
i think i get your idea,hope i did.
You should try to pass all of your SoundChannel classes (sound layers being added) to a SoundMixer class (there is plenty of info on this in adobe site),like a funnel,and then add a SAMPLEDATA event listener to that soundmixer, and capture the byte array of that sound mixer,it is like getting the byte array of the final mixdown...then encode those bytes to wav or mp3 .
Sorry i can not write that code down for you,i am not very good with the code yet, but im good at mixing sounds.
Here is an example form adobe site itself:
function loaded(event:Event):void
{
outputSnd.addEventListener(SampleDataEvent.SAMPLE_DATA, processSound);
outputSnd.play();
}
Useful link:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html
soundmixer class link:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/SoundMixer.html
experimenting with stratus (cirrus) internet radio i used SoundMixer.computeSpectrum() on the server side and passed that data to the client side with NetStream.send() for drawing and playback.
It worked, but was very ram hungry with client and server on one computer (and with several songs loaded and converted to raw audio data :)
so, try computeSpectrum(), it seems perfect for creating an "audio log"

AS3 ByteArray " The supplied index is out of bounds" Error

I'm working on an application and I need to save an AS3 object to a db.
Here's what I'm doing:
private function getComplete(e:Event)
{
var getVars:URLVariables = new URLVariables(unescape(e.target.data));
var _saveData_obj = readObjectFromStringBytes( getVars.saveData);
// do something with the save data....
}
public function SaveGame() {
var _save_data:Object = _puzzle.dataForSaving;
var _serialized_string = escape(serializeToString(_save_data));
var _round_time = Math.round( _elapsed_time);
var _token = MD5.hash( _id +
_difficulty +
"mysomewhatsecretstringhere" +
_round_time );
var request:URLRequest =
new URLRequest( _home + 'savegame.php' );
request.method = URLRequestMethod.POST;
var variables:URLVariables = new URLVariables();
variables.saveData = _serialized_string;
variables.time = _round_time;
variables.id = _id;
variables.dif = _difficulty;
variables.token = _token;
request.data = variables;
var loader:URLLoader = new URLLoader (request);
loader.addEventListener(Event.COMPLETE, postComplete);
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.load(request);
}
public function LoadGame() {
var request:URLRequest =
new URLRequest( _home + 'loadgame.php?id='+_id+"&dif="+_difficulty);
request.method = URLRequestMethod.GET;
var loader:URLLoader = new URLLoader (request);
loader.addEventListener(Event.COMPLETE, getComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, netError);
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.load(request);
}
public static function serializeToString(value:Object):String{
if(value==null){
throw new Error("null isn't a legal serialization candidate");
}
var bytes:ByteArray = new ByteArray();
bytes.writeObject(value);
bytes.position = 0;
trace ("Saved: "+bytes.length);
var be:String = Base64.encodeByteArray(bytes);
return be;
}
public static function readObjectFromStringBytes(value:String):Object {
var result:ByteArray=Base64.decodeToByteArray( value) as ByteArray;
result.position = 0;
var the_obj:Object = result.readObject();
return the_obj
}
The problem is that I keep getting a "The supplied index is out of bounds" error when I try to read the object from the Base64 string.... I checked if the saved string and the loaded string are the same. Tried to save the Base64 string to a shared object and retrieve it - that works fine ... the only problem is when I save and load to/from the server.
Can you guys help me? What am I doing wrong?
Thanks.
try using ba.writeMultiByte(string, 'utf-8') and ba.readMultiByte(ba.bytesAvailable, 'utf-8') without converting to object
Ok, so it looks like we've come to a series of steps you can take to debug what's going on.
(In save game) make sure that unescape( variables ).saveData == _puzzle.dataForSaving. If this does not work, then your issue is with either escape or unescape. I have to admit
I am suspicious here, you should never need to escape data for a POST once it has been Base64 encoded (as3crypto's Base 64 encoding will return only one of these characters: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=. Those are all legit in a POST) and your URLLoader should take care of making it a legit request, so I have difficulty seeing the need.
It is more consistent (and more expected) to have escape and unescape both accept strings as parameters and return strings.
Instead of storing to the database and then retrieving, store to $_SESSION (or, if possible, just echo the value back). This will eliminate PHP as a possible culprit.
In PHP, make sure that $_POST['saveData'] == /* whatever you echo */. If it isn't, then you have a DB encoding issue. You need to make sure that whatever encoding is used in MySQL is the same as the one used in AS.
As an aside, when working with AS, you'll often find it easier to use $_REQUEST instead of $_POST or $_GET. Since the requests are more or less abstracted, the URLRequests of AS don't really need to worry about being RESTful, and it is a heck of a lot easier to work with than exclusively using POSTs.

How do you read and write http headers in ActionScript?

Is it even possible to both read and write http headers in actionscript?
Hmm... ok setting them doesn't seem to be a problem.
Pretty straight forward, you just add and array of URLRequestHeader objects to the URLRequest object.
var req:URLRequest = new URLRequest();
var someheader:URLRequestHeader = new URLRequestHeader( "Connection", "OK" );
req.requestHeaders = [someheader];
However, you can't read them in the regular FlashPlayer. This can be done in AIR and Flash Lite on using the requestHeader property on the HTTPStatusEvent.
var loader:URLLoader = new URLLoader();
loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
private function onStatus( e:HTTPStatusEvent ) : void
{
var headers:Array = e.requestHeaders; // only available in FlashLite and AIR
}
This is kinda weird.

Setting default values for object properties in AS3

I'm an actionscript newbie so please bear with me. Below is a function, and I am curious how to set default property values for objects that are being created in a loop.
In the example below, these propeties are the same for each object created in the loop: titleTextField.selectable, titleTextField.wordWrap, titleTextField.x
If you pull these properties out of the loop, they are null because the TextField objects have not been created, but it seems silly to have to set them each time. What is the correct way to do this. Thanks!
var titleTextFormat:TextFormat = new TextFormat();
titleTextFormat.size = 10;
titleTextFormat.font = "Arial";
titleTextFormat.color = 0xfff200;
for (var i=0; i<arrThumbPicList.length; i++) {
var yPos = 55 * i
var titleTextField:TextField = new TextField();
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.text = arrThumbTitles[i];
titleTextField.x = 106;
titleTextField.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
}
There are basically three options here.
You could create a custom class that
acts as a proxy to a TextFormat.
TextFormatProxy, for instance, which
could create a TextFormat in it's
constructor and set each of your
default values. See this link
or google "AS3 proxy pattern."
You could write a custom class that
extends TextFormat and likewise, set
these default values in the
constructor, or in a function of
your choosing.
You could use a little helper
function like this
package {
import flash.display.Sprite;
public class DefaultProperties extends Sprite{
public var s:Sprite;
public function DefaultProperties() {
s = new Sprite();
s.graphics.beginFill(0x00ff00, 1);
s.graphics.drawRect(0, 0, 100, 100);
s.graphics.endFill();
this.setDefaults(s, {x:100, y:200, scaleX:.5});
this.addChild(s);
}
function setDefaults($obj:*, $properties:Object):void {
for (var i in $properties) {
$obj[i] = $properties[i];
}
}
}
}
Without knowing the surrounding code, I'd suggest putting any construction logic in a factory object. If that's overkill, then I'd say your code is fine. Pulling it out of the loop and into a helper method is really just adding another layer of indirection which is not only harder to read, but it's also less efficient since you're introducing another method call on the stack.
Using a factory object is good if your class is really not interested in how things are created, but rather just needs one or more instance of something. That way, the factory is responsible for the creation of the object and you get a nice separation of concerns.
I wouldn't recommend creating a proxy structure that only has the purpose of setting the properties of your instance. I think it's an anti pattern because it favors inheritance over composition and it does so solely for the purpose of code re-use. But doing this means you're locking yourself to that specific implementation, which is likely not what you want. Just because your textfield has certain value, doesn't mean that it is a different type.
This is how your code could look when using the factory pattern, first out, the format factory:
public interface FormatFactory
{
function getInstance():TextFormat;
}
public class TitleFormatFactory implements FormatFactory
{
public function getInstance():TextFormat
{
var format:TextFormat = new TextFormat();
format.size = 10;
format.font = "Arial";
format.color = 0xfff200;
return format;
}
}
Factories may or may not be parameterized:
public interface TextFieldFactory
{
function getInstance(text:String, position:Point, format:TextFormat):TextField;
}
public class TitleFactory implements TextFieldFactory
{
public function getInstance(text:String, position:Point, format:TextFormat):TextField
{
var title:TextField = new TextField();
title.selectable = false;
title.wordWrap = true;
title.text = text;
title.x = position.x;
title.y = position.y;
title.setTextFormat(format);
return title;
}
}
Lastly, this is how you'd use the code:
var formatFactory:FormatFactory = new TitleFormatFactory();
var titleFactory:TextFieldFactory = new TitleFactory();
var format:TextFormat = formatFactory.getInstance();
for (var i = 0; i < arrThumbPicList.length; i++)
{
var position:Point = new Point(106, 331 + 55 * i);
var title:TextField = titleFactory.getInstance(text, position, format);
container.addChild(title);
}
Besides being readable, a huge benefit is that you can now swap the implementations of the factories and thus changing what kind of components you're using, without having to change your actual logic. All you have to do is change the references to the factories.
Also, by separating the concerns you make it easier to focus on one aspect of your code and thus run less of a risk of introducing errors and if you still do, those are often easier to spot and fix. More-over, it's much easier to unit test code when you separate concerns and more importantly, creation logic from business logic.
If I were to approach the same problem I'd use the same as you posted, just because making the new data structure would be unnecessary. An alternative would be to create a function that accepts the textbox as a parameter:
for (var i=0; i<arrThumbPicList.length; i++) {
var titleTextField:TextField = new TextField();
setProperties(titleTextField,i);
}
function setProperties(txt:TextField,offset:int){
var yPos = 55 * offset;
txt.selectable = false;
txt.wordWrap = true;
txt.text = arrThumbTitles[offset];
txt.x = 106;
txt.y = 331 + yPos;
container.addChild(titleTextField);
titleTextField.setTextFormat(titleTextFormat);
i++;
}
Although I'd probably go with the way you posted unless you're adding things from multiple for loops.
Your code is ok for me. Simple and effective. You only have 3 fixed parameters, for me it's not necessary to do more :
titleTextField.selectable = false;
titleTextField.wordWrap = true;
titleTextField.x = 106;
Unlike the other answer, I would do a helper function to get the textfield with all fixed parameters (I would do that ONLY if there was a LOT of stuff to do).
function createMyContextTextField():TextField{
textfield:TextField = new textField();
textfield.selectable = false;
textfield.wordWrap = true;
textfield.x = 106;
return textfield;
}