четверг, 17 марта 2011 г.

Байты каждый день

Представляю вашему вниманию перевод главы «Chapter 2 - Everyday bytes» (PDF) из книги «What can you do with bytes ?»

В предыдущей главе мы рассмотрели важные концепции управления двоичными данными в Flash Player. Настало время использовать эти концепции на практике в реальных проектах. Мы увидим как с помощью нашего лучшего друга - API класса ByteArray можно осуществлять внедрение двоичных ресурсов, управление звуком, парсинг SWF и т.д. - все то, что было недоступно прежде в ActionScript.

Мы продолжим наше путешествие в ByteArray, рассматривая различные примеры его применения, с которыми вы можете сталкиваться каждый день во время работы с Flash.

Содержание:
- Копирование объектов
- Сериализация/десериализация пользовательских объектов
- Внедрение ресурсов
- Внедрение байтов
- Прогрессивная загрузка изображений
- Компрессия/декомпрессия данных
- Генерация файлов изображений (PNG или JPEG)
- Сохранение двоичного потока с помощью удаленного сервера
- Сохранение двоичного потока локально
- Генерация PDF
- От байтов к звуку
- Парсинг двоичных данных (на примере SWF)


Копирование объектов



Одно из наиболее частых применений класса ByteArray – дублирование объектов. Вспомним, что AMF сериализация и десериализация доступна посредством интерфейса класса ByteArray. Метод writeObject это то, что нам нужно:

// creates an empty ByteArray
var stream:ByteArray = new ByteArray();

// creates an object
var parameters:Object = { age : 25, name : "Bob" };

// serializes the object as amf and stores it into the ByteArray
stream.writeObject( parameters );


Для возвращения сериализованного объекта мы используем метод readObject:

// creates an empty ByteArray
var stream:ByteArray = new ByteArray();

// creates an object
var parameters:Object = { age : 25, name : "Bob" };

// serializes the object as amf and stores it into the ByteArray
stream.writeObject( parameters );

// resets the position
stream.position = 0;

// reads the object copy
var objectCopy:Object = stream.readObject();


Мы можем включить эту логику в пользовательский метод:

function copyObject ( objectToCopy:* ):*
{
var stream:ByteArray = new ByteArray();
stream.writeObject( objectToCopy );
stream.position = 0;
return stream.readObject();
}


Теперь всегда, когда нам нужно создать копию объекта, мы вызываем метод copyObject:

// creates an object
var parameters:Object = { age : 25, name : "Bob" };
var parametersCopy:Object = copyObject ( parameters );

/* outputs :
name : Bob
age : 25
*/

for ( var p:String in parametersCopy )
trace( p, " : ", parametersCopy[p] );

function copyObject ( objectToCopy:* ):*
{
var stream:ByteArray = new ByteArray();
stream.writeObject( objectToCopy );
stream.position = 0;
return stream.readObject();
}


Изменяя свойства оригинального объекта, мы видим, что они не изменяются в его копии:

// creates an object
var parameters:Object = { age : 25, name : "Bob" };
var parametersCopy:Object = copyObject ( parameters );
parameters.nom = "Stevie";

/* outputs :
name : Bob
age : 25
*/
for ( var p:String in parametersCopy )
trace( p, " : ", parametersCopy[p] );

function copyObject ( objectToCopy:* ):*
{
var stream:ByteArray = new ByteArray();
stream.writeObject( objectToCopy );
stream.position = 0;
return stream.readObject();
}


Двинемся дальше и посмотрим, как сохранять и восстанавливать более сложные типы данных с помощью интерфейса класса ByteArray.


Сериализация/десериализация пользовательских объектов



Замечу, что код, представленный выше, не работает для пользовательских типов данных, которые вы можете объявить в своем приложении. Давайте рассмотрим простейший сценарий, в котором нужно будет использовать объект User:

package
{
public class User
{
private var _firstName:String;
private var _lastName:String;

public function set firstName (firstName:String):void
{
_firstName = firstName;
}

public function set lastName (lastName:String):void
{
_lastName = lastName;
}

public function get firstName ():String
{
return _firstName;
}

public function get lastName ():String
{
return _lastName;
}
}
}


Вам нужно хранить ваш экземпляр класса User на сервере в двоичном файле или локально в SharedObject. Если вы попробуете сохранить экземпляр класса User внутри ByteArray и вызвать его позже, то Flash Player сначала будет выяснять, был ли зарегистрирован любой объект типа User прежде, если нет, то он вернет просто экземпляр класса Object:

// creates an instance of User
var myUser:User = new User ();

// sets the members
myUser.firstName = "Stevie";
myUser.lastName = "Wonder";

// outputs :[object User]
trace ( myUser );

// create a ByteArray to store the instance
var bytes:ByteArray = new ByteArray();

// stores the instance
bytes.writeObject ( myUser );

// resets the position
bytes.position = 0;

// outputs : false
trace ( bytes.readObject() is User );


Теперь используем метод registerClassAlias для того, чтобы сообщить Flash Player, что мы хотим зарегистрировать тип User для автоматической десериализации:

// creates an instance of User
var myUser:User = new User ();

// sets the members
myUser.firstName = "Stevie";
myUser.lastName = "Wonder";

// outputs :[object User]
trace ( myUser );

// registers the type User for deserialization
registerClassAlias ( "userTypeAlias", User );

// create a ByteArray to store the instance
var bytes:ByteArray = new ByteArray();

// stores the instance
bytes.writeObject ( myUser );

// resets the position
bytes.position = 0;

// reads the stored instance and automatically deserializes it to the User type
var storedUser:User = bytes.readObject() as User;

// outputs : true
trace ( storedUser is User );

// outputs : Stevie Wonder
trace ( storedUser.firstName, storedUser.lastName );


Используя эту технику, мы можем сохранять состояние любого пользовательского объекта и восстанавливать его в любой момент времени. Как видите, теперь мы можем работать с копией пользовательского объекта:

storedUser.firstName = "Bobby";
storedUser.lastName = "Womack";

// outputs : Stevie Wonder
trace ( myUser.firstName, myUser.lastName );

// outputs : Bobby Womack
trace ( storedUser.firstName, storedUser.lastName );


Нужно помнить, что некоторые нативные типы не могут быть сериализованы/десериализованы с помощью AMF. Это все отображаемые объекты, наследующие от DisplayObject. Если мы попробуем сериализовать экземпляр класса MovieClip, то получим следующую неявную ошибку:

// creates a ByteArray
var bytes:ByteArray = new ByteArray();

// try storing a DisplayObject type
bytes.writeObject ( new MovieClip() );

// resets the position
bytes.position = 0;

// outputs : undefined
trace ( bytes.readObject() );



Внедрение ресурсов



Другое классическое применение, которое вы, возможно, найдете полезным это внедрение внешних XML-образных файлов, таких как фильтры Pixel Bender Kernel.
В коде ниже мы используем тег Embed для внедрения внешнего фильтра Pixel Bender.

[Embed(source="myFilter.pbj", mimeType="application/octet-stream")]
var myShaderKernel:Class;


Во время компиляции фильтр будет внедрен как ByteArray, т.к. используется mimeType="application/octet-stream" (в этом случае любые внедряемые ресурсы будут представлены в виде ByteArray).

Мы можем также просто внедрить XML без изменения исходного кода во время компиляции:

import flash.utils.ByteArray;

[Embed(source="test.xml", mimeType="application/octet-stream")]
var xmlStream:Class;

// instanciate the stream as a ByteArray
var xmlBytes:ByteArray = new xmlStream();

// read the XML String from the byte stream
var xmlString:String = xmlBytes.readUTFBytes( xmlBytes.bytesAvailable );

// instanciate a XML object by passing the content
var myXML:XML = new XML(xmlString);

/*
outputs :





*/
trace ( myXML );
// outputs : 3
trace ( myXML.item.length() );


Таким образом, можно внедрить любые данные, т.е. здесь мы внедряем двоичные данные, которыми сможем управлять с помощью интерфейса ByteArray.
Но предыдущий код несколько избыточен, т.к. если использовать определенный mimeType, то можно заставить транскодер отобразить определенный тип двоичных данных и сделать все преобразования за вас. В следующем коде мы внедряем XML тем же путем, но используя корректный mimeType, что делает код более простым:

import flash.utils.ByteArray;
[Embed(source="test.xml", mimeType="text/xml")]
var xmlStream:Class;

// instanciate a XML object by passing the content
var myXML:XML = new XML (xmlStream.data);

/*
outputs :





*/
trace ( myXML );

// outputs : 3
trace ( myXML.item.length() );


Очень удобно. Теперь давайте посмотрим, как можно делать байтовые инъекции и создавать из них контент. Adobe Flash Player предлагает для этого очень мощное API и мы его сейчас посмотрим.


Внедрение байтов



Наиболее часто это делают с изображениями, шрифтами или файлами SWF. Flash Player не имеет нативного типа данных SWF, но распознать его в таком виде можно с помощью метода loadBytes класса Loader.

Это позволяет нам вставить SWF внутрь объекта Loader в виде ByteArray и запустить его:

import flash.utils.ByteArray;
import flash.display.Loader;

[Embed(source="test-loading.swf", mimeType="application/octet-stream")]
var swfStream:Class;

// instanciate the stream as a ByteArray
var swfBytes:ByteArray = new swfStream();

// instanciate a Loader
var myLoader:Loader = new Loader();

// inject the embedded stream inside the Loader
// the SWF is executed automatically
myLoader.loadBytes(swfBytes);


Чтобы отобразить SWF мы добавляем объект Loader в список отображения:

import flash.utils.ByteArray;
import flash.display.Loader;

[Embed(source="test-loading.swf", mimeType="application/octet-stream")]
var swfStream:Class;

// instanciate the stream as a ByteArray
var swfBytes:ByteArray = new swfStream();

// instanciate a Loader
var myLoader:Loader = new Loader();

// show the Loader object
addChild ( myLoader );

// inject the embedded stream inside the Loader
// the SWF is executed automatically
myLoader.loadBytes(swfBytes);


Для изображений и музыки вам не нужно использовать определенные mimeType, транскодер автоматически корректно отобразит поток байтов без любых подсказок:

import flash.utils.ByteArray;
import flash.display.Bitmap;

[Embed(source="ferrari-demo.png")]
var myImageStream:Class;

// instanciate the image as a classic Bitmap instance
var myImage:Bitmap = new myImageStream();

// show it!
addChild ( myImage );


Давайте немного поиграем с изображениями, в следующем разделе мы сделаем динамическую загрузку изображения, но другим способом.


Прогрессивная загрузка изображений



Другой отличный интерфейс для работы с байтами – URLStream, он позволяем нам загружать контент в Flash Player и дает доступ к загруженным байтам.
Что еще здорово, так это то, что большая часть интерфейса класса ByteArray доступна также в URLStream. В каком же случае вам понадобится этот интерфейс? Во-первых, попробуйте следующий код, возможно, он сразу даст вам несколько хороших идей:

// creates a URLStream object
var stream:URLStream = new URLStream();

// listen to the ProgressEvent.PROGRESS event to grab the incoming bytes
stream.addEventListener ( ProgressEvent.PROGRESS, onProgress );
stream.addEventListener ( Event.COMPLETE, onComplete );

// download the google.com index page
stream.load ( new URLRequest ("http://www.google.com" ) );

function onProgress ( e:ProgressEvent ):void
{
trace ("progress");
}

function onComplete ( e:Event ):void
{
trace ("complete");
}


Мы видим, что событие ProgressEvent.PROGRESS рассылается многократно, а событие Event.COMPLETE только раз в конце загрузки. Ничего сложного, правда?
Но теперь вы можете получить доступ к байтам, как только они станут поступать, в отличие от Loader, который дает доступ, когда загружен весь файл. Все что вы можете сделать во время загрузки это получить информацию об общем количестве байтов и о количестве загруженных байтов.

Давайте изменим наш предыдущий код следующим образом:

function onProgress ( e:ProgressEvent ):void
{
trace ( stream.readUTFBytes ( stream.bytesAvailable ) );
}


Теперь мы распознаем загруженные байты как строку. Вы должны получить в окне вывода чистый HTML код странички Google. Должно получиться что-то подобное:

Это значит, что у нас есть доступ к байтам во время их загрузки. Это интересная возможность, которая позволит нам, например, получить разрешение изображения перед полной его загрузкой путем парсинга заголовка файла, если он доступен.

Мы также можем сделать прогрессивный загрузчик, который будет отображать изображение по мере его загрузки. Чтобы воспроизвести этот эффект напишем следующий код:

// creates a URLStream object
var stream:URLStream = new URLStream();

// listen to the ProgressEvent.PROGRESS event to grab the incoming bytes
stream.addEventListener ( ProgressEvent.PROGRESS, onProgress );
stream.addEventListener ( Event.COMPLETE, onComplete );

// download the remote image
stream.load ( new URLRequest ("http://dl.dropbox.com/u/7009356/IMG_4958.jpg" ) );

// store the incoming bytes
var buffer:ByteArray = new ByteArray();

// Loader to display the picture
var loader:Loader = new Loader();

// show it
addChild ( loader );

function onProgress ( e:ProgressEvent ):void
{
// we keep writing the bytes coming in
stream.readBytes ( buffer, buffer.length );
// we clear the previously loaded content
loader.unload();
// we inject the bytes to display the image
loader.loadBytes ( buffer );
}

function onComplete ( e:Event ):void
{
trace ("complete");
}


По мере загрузки байтов мы показываем их посредством метода loadBytes. Следующее изображение показывает частичную загрузку картинки:

Картинка загружена частично


Рисунок 1. – Картинка загружена частично

Теперь, шаг за шагом, картинка загружается полностью и отображается на экране:

Картинка загружена полностью


Рисунок 2. – Картинка загружена полностью

Возможно, вы усомнитесь в правильности такого подхода к загрузке, но возможно он даст лучшие идеи насчет того, что можно делать с байтами и URLStream. Как мы увидели loadBytes очен

Комментариев нет:

Отправить комментарий