поиск

ByteArray в AMFPHP и обратно

Намусорил: Алексей «Vooparker» Аникутин
В категории: ActionScript 3, Client/Server, Flex 2, PHP

До появления ActionScript 3 единственным вариантом сохранить изображение на сервере была передача значения каждого пикселя объекта BitmapData серверному скрипту. Теперь с появлением объекта ByteArray и с помощью последнего релиза AMFPHP мы можем с легкостью передать объект BitmapData в виде последовательности байтов.

Оригинал статьи: Send and Receive ByteArray to AMFPHP
Автор: Alessandro Crugnola
Перевод: Алексей «Vooparker» Аникутин

Перед прочтением этой статьи автор советует ознакомиться с этими материалами:

  1. Export JPEG with Flash/PHP.
  2. Flex RemoteObject and AMFPHP 1.9 (ru).

1. Экипировка

Чтобы успешно справиться c этим уроком нам потребуются:

2. Настройка аmfphp-проекта

Сначала настроим серверную часть нашего проекта. Для это в папке amfphp/services создайте директорию: “tutorials/amfphp_bytearray“. И в только что созданной директории создайте файл SaveJPEG.php со следующим содержанием:

<?php
class SaveJPEG
{
    var $output_dir = "temp";
    var $server_url = "http://www.sephiroth.it/amfphp2/services/tutorials/amfphp_bytearray/";

    /**
     * Save image from the given bytearray
     * and return the path of the saved image
     */
    function SaveAsJPEG($ba, $compressed = false)
    {
        if(!file_exists($this->output_dir) || !is_writeable($this->output_dir))
            trigger_error ("please create a 'temp' directory first with write access", E_USER_ERROR);

        $data = $ba->data;
        if($compressed)
        {
            if(function_exists(gzuncompress))
            {
                $data = gzuncompress($data);
            } else {
                trigger_error ("gzuncompress method does not exists, please send uncompressed data", E_USER_ERROR);
            }
        }
        file_put_contents($this->output_dir . "/rawdata.jpeg", $data);
        return $this->server_url . $this->output_dir . "/rawdata.jpeg";
    }

    /**
     * Save file from a given bytearray
     * and return a ByteArray from the saved file
     */
    function SaveAsByteArray($ba, $compresses = false)
    {
        if(!file_exists($this->output_dir) || !is_writeable($this->output_dir))
            trigger_error ("please create a 'temp' directory first with write access", E_USER_ERROR);

        $data = $ba->data;
        if($compressed)
        {
            if(function_exists(gzuncompress))
            {
                $data = gzuncompress($data);
            } else {
                trigger_error ("gzuncompress method does not exists, please send uncompressed data", E_USER_ERROR);
            }
        }
        file_put_contents($this->output_dir . "/rawdata.rgb", $data);
        return new ByteArray(file_get_contents($this->output_dir . "/rawdata.rgb"));
    }
}
?>

В той же директории amfphp_bytearray создайте пустую директорию temp c маской доступа 755. Мы будем использовать эту директорию для хранения временных файлов, которые были получены из flash.

В нашем классе всего два метода: SaveAsJPEG и SaveAsByteArray. Оба принимают два параметра:

  • экземпляр ByteArray
  • и флаг указывающий сжата ли передаваемая последовательность байтов

Так как метод compress класса ByteArray использует алгоритм сжатия zlib, необходимо чтобы в вашей установке PHP была доступна функция gzuncompress.

2.1 SaveAsJPEG

Этот метод получает из flash последовательность байтов, которая представляет собой jpeg файл. По этой причине, мы просто сохраняем файл как ‘rawdata.jpg‘, а так как это изображение будет валидным jpeg файлом, и мы просто скажем flash загрузить его как обычное изображение.

2.2 SaveAsByteArray

В этом случае мы сохраним последовательность байтов так как есть, это означает, что изображение не может быть загружено как обычный файл изображения (вы также не сможете просмотреть его в браузере). Так что, во втором примере мы вернем flash экземпляр php ByteArray, содержащий данные сохраненного файла (используя для этого file_get_content).
Flash получит его как последовательность байтов и, таким образом, мы сможем с легкостью использовать его для создания экземпляра BitmapData.

3. Flex

Давайте посмотрим на наш .mxml файл:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    xmlns:display="flash.display.*"
    xmlns:ns1="http://www.sephiroth.it/2006/mxml">
    <mx:RemoteObject id="service" showBusyCursor="true" destination="amfphp" fault="faultHandler(event)" source="tutorials.amfphp_bytearray.SaveJPEG">
        <mx:method name="SaveAsJPEG" result="savejpeg_resultHandler(event)" />
        <mx:method name="SaveAsByteArray" result="savebyte_resultHandler(event)" />
    </mx:RemoteObject>
    <mx:Style>
        NumericPopUp {
            slider-border-style:solid;
            slider-border-color:#999999;
            slider-background-color:#EBEBEB;
            direction:horizontal;
        }

        Application {
            background-color: #FFFFFF;
        }
    </mx:Style>
    <mx:Script>
        <![CDATA[
            import mx.core.UIComponent;
            import mx.controls.Alert;
            import mx.controls.SWFLoader;
            import com.adobe.images.JPGEncoder;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;

            private var send_compressed:Boolean = false;    // send always uncompressed bytearrays (i.e. your server doesn not support "gzuncompress")
            private var encoder:JPGEncoder;

            /**
             * Default handler for the remote SaveAsJPEG function
             */
            private function savejpeg_resultHandler(event:ResultEvent):void
            {
                if(event.result || event.result is String)
                {
                    var path:String = event.result as String;
                    trace(path);

                    var swf_loader:SWFLoader = preview_box.getChildByName("preview") as SWFLoader;
                    swf_loader.showBusyCursor = true;
                    swf_loader.load(path + "?rand=" + new Date().getTime());

                    image_size.text = "Loading...";
                }
            }

            /**
             * Default handler for the remote SaveAsByteArray function
             */
            private function savebyte_resultHandler(event:ResultEvent):void
            {
                var ba:ByteArray = event.result as ByteArray;
                var ui_loader:UIComponent = preview_box.getChildByName("preview") as UIComponent;
                image_size.text = 'ByteArray size: ' + Math.round(((ba.length/4)/1024)*100)/100 + ' Kb'

                try
                {
                    ba.uncompress();
                } catch(err:Error)
                {
                }

                var data:BitmapData = new BitmapData(original_image.width, original_image.height, false, 0);
                var bmp:Bitmap = new Bitmap(data);
                bmp.name = "image";
                data.setPixels(data.rect, ba);

                ui_loader.addChild(new Bitmap(data));
                ui_loader.width = data.width;
                ui_loader.height = data.height;

            }

            /**
             * Default fault handler
             */
            private function faultHandler(event:FaultEvent):void
            {
                Alert.show(event.fault.faultString, "Error: " + event.fault.faultCode);
                trace(event.fault.message);
            }

            /**
             * Save the image using the JPEGEncoder class
             */
            private function saveJpegHandler(event:MouseEvent):void
            {
                removeImages();

                var swf_loader:SWFLoader = new SWFLoader();
                swf_loader.autoLoad = true;
                swf_loader.name = "preview";
                swf_loader.addEventListener(Event.COMPLETE, function(event:Event):void { image_size.text = 'Image size: ' + (SWFLoader(event.target).bytesTotal/1024).toPrecision(3) + ' Kb' });
                preview_box.addChildAt(swf_loader, 0);

                var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
                var ba:ByteArray;

                encoder = new JPGEncoder(jpeg_quality.value);
                ba = encoder.encode( bmpdata );

                if(send_compressed)
                    ba.compress();

                service.getOperation("SaveAsJPEG").send(ba, send_compressed);

                image_size.text = "Sending..."
            }

            /**
             * Save the image using only ByteArray derived from
             * BitmapData.getPixels
             */
            private function saveByteHandler(event:MouseEvent):void
            {
                removeImages();

                var ui_loader:UIComponent = new UIComponent();
                ui_loader.name = "preview";
                preview_box.addChildAt(ui_loader, 0);

                var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
                var arr:ByteArray = Bitmap(original_image.content).bitmapData.getPixels(new Rectangle(0,0,bmpdata.width, bmpdata.height));

                if(send_compressed)
                    arr.compress();

                arr.position = 0;
                service.getOperation("SaveAsByteArray").send(arr, send_compressed);

                image_size.text = "Sending..."
            }

            private function removeImages():void
            {
                if(preview_box.getChildByName("preview"))
                    preview_box.removeChild(preview_box.getChildByName("preview"));

                image_size.text = "";
            }

        ]]>
    </mx:Script>
    <mx:VBox left="5" top="5" right="5" bottom="5">
        <ns1:LabelledBox title="Original Image" direction="horizontal" paddingBottom="20" paddingLeft="20" paddingRight="20" paddingTop="20" labelPlacement="left" labelPadding="10" cornerRadius="5" id="original_box">
            <mx:Image x="10" y="33" source="@Embed('images/284541843_b77b917989[1].jpg')"  id="original_image"/>
            <mx:Grid x="269" y="33">
                <mx:GridRow width="100%" height="100%">
                    <mx:GridItem width="100%" height="100%" verticalAlign="middle">
                        <mx:Label text="Jpeg quality"/>
                    </mx:GridItem>
                    <mx:GridItem width="100%" height="100%" verticalAlign="middle">
                        <ns1:NumericPopUp id="jpeg_quality" minimum="1" maximum="100" stepSize="1" value="50" direction="horizontal">
                        </ns1:NumericPopUp>
                    </mx:GridItem>
                </mx:GridRow>
                <mx:GridRow width="100%" height="100%">
                    <mx:GridItem width="100%" height="100%">
                    </mx:GridItem>
                    <mx:GridItem width="100%" height="100%" verticalAlign="middle">
                        <mx:Button label="Save as Jpeg" click="saveJpegHandler(event)" width="100%"/>
                    </mx:GridItem>
                </mx:GridRow>
                <mx:GridRow width="100%" height="100%">
                    <mx:GridItem width="100%" height="100%">
                    </mx:GridItem>
                    <mx:GridItem width="100%" height="100%" verticalAlign="middle">
                        <mx:Button label="Save as ByteArray" click="saveByteHandler(event)" width="100%"/>
                    </mx:GridItem>
                </mx:GridRow>
                <mx:GridRow width="100%" height="100%">
                    <mx:GridItem width="100%" height="100%" colSpan="2">
                    </mx:GridItem>
                </mx:GridRow>
            </mx:Grid>
        </ns1:LabelledBox>
        <ns1:LabelledBox id="preview_box" paddingBottom="20" paddingLeft="20" paddingRight="20" paddingTop="20" labelPlacement="left" labelPadding="10" cornerRadius="5" title="Saved Image" clipContent="false" minWidth="300" minHeight="100" direction="horizontal" width="{original_box.width}">
            <mx:Grid>
                <mx:GridRow width="100%" height="100%">
                    <mx:GridItem width="100%" height="100%" verticalAlign="middle">
                        <mx:Label text="" id="image_size" />
                    </mx:GridItem>
                </mx:GridRow>
                <mx:GridRow width="100%" height="100%">
                </mx:GridRow>
            </mx:Grid>
        </ns1:LabelledBox>
    </mx:VBox>
</mx:Application>

Для отправки данных на сервер у нас есть два метода: saveJpegHandler и saveByteHandler.

3.1 saveJpegHandler

Этот метод получает экземпляр BitmapData из оригинального изображения, и преобразует его в jpeg, используя JPEGEncoder, передавая в конструктор степень оптимизации jpeg изображения.

var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
var ba:ByteArray;
encoder = new JPGEncoder(jpeg_quality.value);
ba = encoder.encode( bmpdata );

Сервер вернет строку, содержащую путь к сохраненному изображению, так что все что нам остается, так это загрузить файл используя SWFLoader.

swf_loader.load(path + "?rand=" + new Date().getTime());

Добавление параметра “?rand” позволяет обойти проблему с кэшированием браузером изображения.

3.2 saveByteHandler

Второй метод отправляет последовательность байтов amfphp, и получает ту же последовательность в качестве результата:

// get the original bitmapdata
var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
// transform the bitmapdata into a bytearray
var arr:ByteArray = Bitmap(original_image.content).bitmapData.getPixels(new Rectangle(0,0,bmpdata.width, bmpdata.height));

Метод преобразует BitmapData в ByteArray с помощью метода класса BitmapData getPixels и отправляет на сервер.
Метод AMFPHP возвращает последовательность байтов, так что мы можем использовать ее, чтобы создать BitmapData, используя метод setPixel:

// server returns a bytearray
var ba:ByteArray = event.result as ByteArray;
// create a new bitmapdata to store into the resulting bytearray pixels
var data:BitmapData = new BitmapData(original_image.width, original_image.height, false, 0);
var bmp:Bitmap = new Bitmap(data);
// draw the bytearray into just created bitmapdata
data.setPixels(data.rect, ba);

// then add the bitmap to a new UIComponent
ui_loader.addChild(new Bitmap(data));
ui_loader.width = data.width;
ui_loader.height = data.height;

4. Загрузки

Вы также можете скачать исходные файлы к статье.



Kомментариев - 3 к «ByteArray в AMFPHP и обратно»

baron27 [4 июня, 2007 в 22:15]

Известно ли что-нибудь о релизе 1.9 версии? Ведь чувак, который ее разрабатывал ушел из флеша…

Vooparker [4 июня, 2007 в 23:56]

К сожалению, пока ничего не известно… по крайней мере мне.
С другой стороны, проект вроде бы не бросили на произвол судьбы, последняя версия датирована маем это года, когда были пофиксены ошибки совместимости с php 5.2.2. Так что, я думаю и очень надеюсь, что amfphp не погибнет и найдет своего разработчика.

kernel [22 апреля, 2008 в 13:10]

спасибо за доступное разъяснение!!!!
я в пхп ноль - а тут всё понятно расписано до мелочей!

Написать комментарий:

Bы можете использовать следующие теги для форматирования: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



User's collector

Внимание!
Эта опция станет доступной только после того как вы авторизуетесь.


 запомнить меня 
Я новый пользователь

На правах рекламы