User's collector

Внимание!   Данная опция будет доступна только после того, как вы авторизуетесь.
   запомнить меня 
4 июня 2007

ByteArray в AMFPHP и обратно

До появления 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:
  1. <?php
  2. class SaveJPEG
  3. {
  4.     var $output_dir = "temp";
  5.     var $server_url = "http://www.sephiroth.it/amfphp2/services/tutorials/amfphp_bytearray/";
  6.  
  7.     /**
  8.      * Save image from the given bytearray
  9.      * and return the path of the saved image
  10.      */
  11.     function SaveAsJPEG($ba, $compressed = false)
  12.     {
  13.         if(!file_exists($this->output_dir) || !is_writeable($this->output_dir))
  14.             trigger_error ("please create a 'temp' directory first with write access", E_USER_ERROR);
  15.  
  16.         $data = $ba->data;
  17.         if($compressed)
  18.         {
  19.             if(function_exists(gzuncompress))
  20.             {
  21.                 $data = gzuncompress($data);
  22.             } else {
  23.                 trigger_error ("gzuncompress method does not exists, please send uncompressed data", E_USER_ERROR);
  24.             }
  25.         }
  26.         file_put_contents($this->output_dir . "/rawdata.jpeg", $data);
  27.         return $this->server_url . $this->output_dir . "/rawdata.jpeg";
  28.     }
  29.  
  30.     /**
  31.      * Save file from a given bytearray
  32.      * and return a ByteArray from the saved file
  33.      */
  34.     function SaveAsByteArray($ba, $compresses = false)
  35.     {
  36.         if(!file_exists($this->output_dir) || !is_writeable($this->output_dir))
  37.             trigger_error ("please create a 'temp' directory first with write access", E_USER_ERROR);
  38.  
  39.         $data = $ba->data;
  40.         if($compressed)
  41.         {
  42.             if(function_exists(gzuncompress))
  43.             {
  44.                 $data = gzuncompress($data);
  45.             } else {
  46.                 trigger_error ("gzuncompress method does not exists, please send uncompressed data", E_USER_ERROR);
  47.             }
  48.         }
  49.         file_put_contents($this->output_dir . "/rawdata.rgb", $data);
  50.         return new ByteArray(file_get_contents($this->output_dir . "/rawdata.rgb"));
  51.     }
  52. }
  53. ?>

В той же директории 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:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application
  3.     xmlns:mx="http://www.adobe.com/2006/mxml"
  4.     layout="absolute"
  5.     xmlns:display="flash.display.*"
  6.     xmlns:ns1="http://www.sephiroth.it/2006/mxml">
  7.     <mx:RemoteObject id="service" showBusyCursor="true" destination="amfphp" fault="faultHandler(event)" source="tutorials.amfphp_bytearray.SaveJPEG">
  8.         <mx:method name="SaveAsJPEG" result="savejpeg_resultHandler(event)" />
  9.         <mx:method name="SaveAsByteArray" result="savebyte_resultHandler(event)" />
  10.     </mx:RemoteObject>
  11.     <mx:Style>
  12.         NumericPopUp {
  13.             slider-border-style:solid;
  14.             slider-border-color:#999999;
  15.             slider-background-color:#EBEBEB;
  16.             direction:horizontal;
  17.         }
  18.  
  19.         Application {
  20.             background-color: #FFFFFF;
  21.         }
  22.     </mx:Style>
  23.     <mx:Script>
  24.         <![CDATA[
  25.             import mx.core.UIComponent;
  26.             import mx.controls.Alert;
  27.             import mx.controls.SWFLoader;
  28.             import com.adobe.images.JPGEncoder;
  29.             import mx.rpc.events.FaultEvent;
  30.             import mx.rpc.events.ResultEvent;
  31.  
  32.             private var send_compressed:Boolean = false;    // send always uncompressed bytearrays (i.e. your server doesn not support "gzuncompress")
  33.             private var encoder:JPGEncoder;
  34.  
  35.             /**
  36.              * Default handler for the remote SaveAsJPEG function
  37.              */
  38.             private function savejpeg_resultHandler(event:ResultEvent):void
  39.             {
  40.                 if(event.result || event.result is String)
  41.                 {
  42.                     var path:String = event.result as String;
  43.                     trace(path);
  44.  
  45.                     var swf_loader:SWFLoader = preview_box.getChildByName("preview") as SWFLoader;
  46.                     swf_loader.showBusyCursor = true;
  47.                     swf_loader.load(path + "?rand=" + new Date().getTime());
  48.  
  49.                     image_size.text = "Loading...";
  50.                 }
  51.             }
  52.  
  53.             /**
  54.              * Default handler for the remote SaveAsByteArray function
  55.              */
  56.             private function savebyte_resultHandler(event:ResultEvent):void
  57.             {
  58.                 var ba:ByteArray = event.result as ByteArray;
  59.                 var ui_loader:UIComponent = preview_box.getChildByName("preview") as UIComponent;
  60.                 image_size.text = 'ByteArray size: ' + Math.round(((ba.length/4)/1024)*100)/100 + ' Kb'
  61.  
  62.                 try
  63.                 {
  64.                     ba.uncompress();
  65.                 } catch(err:Error)
  66.                 {
  67.                 }
  68.  
  69.                 var data:BitmapData = new BitmapData(original_image.width, original_image.height, false, 0);
  70.                 var bmp:Bitmap = new Bitmap(data);
  71.                 bmp.name = "image";
  72.                 data.setPixels(data.rect, ba);
  73.  
  74.                 ui_loader.addChild(new Bitmap(data));
  75.                 ui_loader.width = data.width;
  76.                 ui_loader.height = data.height;
  77.  
  78.             }
  79.  
  80.             /**
  81.              * Default fault handler
  82.              */
  83.             private function faultHandler(event:FaultEvent):void
  84.             {
  85.                 Alert.show(event.fault.faultString, "Error: " + event.fault.faultCode);
  86.                 trace(event.fault.message);
  87.             }
  88.  
  89.             /**
  90.              * Save the image using the JPEGEncoder class
  91.              */
  92.             private function saveJpegHandler(event:MouseEvent):void
  93.             {
  94.                 removeImages();
  95.  
  96.                 var swf_loader:SWFLoader = new SWFLoader();
  97.                 swf_loader.autoLoad = true;
  98.                 swf_loader.name = "preview";
  99.                 swf_loader.addEventListener(Event.COMPLETE, function(event:Event):void { image_size.text = 'Image size: ' + (SWFLoader(event.target).bytesTotal/1024).toPrecision(3) + ' Kb' });
  100.                 preview_box.addChildAt(swf_loader, 0);
  101.  
  102.                 var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
  103.                 var ba:ByteArray;
  104.  
  105.                 encoder = new JPGEncoder(jpeg_quality.value);
  106.                 ba = encoder.encode( bmpdata );
  107.  
  108.                 if(send_compressed)
  109.                     ba.compress();
  110.  
  111.                 service.getOperation("SaveAsJPEG").send(ba, send_compressed);
  112.  
  113.                 image_size.text = "Sending..."
  114.             }
  115.  
  116.             /**
  117.              * Save the image using only ByteArray derived from
  118.              * BitmapData.getPixels
  119.              */
  120.             private function saveByteHandler(event:MouseEvent):void
  121.             {
  122.                 removeImages();
  123.  
  124.                 var ui_loader:UIComponent = new UIComponent();
  125.                 ui_loader.name = "preview";
  126.                 preview_box.addChildAt(ui_loader, 0);
  127.  
  128.                 var bmpdata:BitmapData = Bitmap(original_image.content).bitmapData;
  129.                 var arr:ByteArray = Bitmap(original_image.content).bitmapData.getPixels(new Rectangle(0,0,bmpdata.width, bmpdata.height));
  130.  
  131.                 if(send_compressed)
  132.                     arr.compress();
  133.  
  134.                 arr.position = 0;
  135.                 service.getOperation("SaveAsByteArray").send(arr, send_compressed);
  136.  
  137.                 image_size.text = "Sending..."
  138.             }
  139.  
  140.             private function removeImages():void
  141.             {
  142.                 if(preview_box.getChildByName("preview"))
  143.                     preview_box.removeChild(preview_box.getChildByName("preview"));
  144.  
  145.                 image_size.text = "";
  146.             }
  147.  
  148.         ]]>
  149.     </mx:Script>
  150.     <mx:VBox left="5" top="5" right="5" bottom="5">
  151.         <ns1:LabelledBox title="Original Image" direction="horizontal" paddingBottom="20" paddingLeft="20" paddingRight="20" paddingTop="20" labelPlacement="left" labelPadding="10" cornerRadius="5" id="original_box">
  152.             <mx:Image x="10" y="33" source="@Embed('images/284541843_b77b917989[1].jpg')"  id="original_image"/>
  153.             <mx:Grid x="269" y="33">
  154.                 <mx:GridRow width="100%" height="100%">
  155.                     <mx:GridItem width="100%" height="100%" verticalAlign="middle">
  156.                         <mx:Label text="Jpeg quality"/>
  157.                     </mx:GridItem>
  158.                     <mx:GridItem width="100%" height="100%" verticalAlign="middle">
  159.                         <ns1:NumericPopUp id="jpeg_quality" minimum="1" maximum="100" stepSize="1" value="50" direction="horizontal">
  160.                         </ns1:NumericPopUp>
  161.                     </mx:GridItem>
  162.                 </mx:GridRow>
  163.                 <mx:GridRow width="100%" height="100%">
  164.                     <mx:GridItem width="100%" height="100%">
  165.                     </mx:GridItem>
  166.                     <mx:GridItem width="100%" height="100%" verticalAlign="middle">
  167.                         <mx:Button label="Save as Jpeg" click="saveJpegHandler(event)" width="100%"/>
  168.                     </mx:GridItem>
  169.                 </mx:GridRow>
  170.                 <mx:GridRow width="100%" height="100%">
  171.                     <mx:GridItem width="100%" height="100%">
  172.                     </mx:GridItem>
  173.                     <mx:GridItem width="100%" height="100%" verticalAlign="middle">
  174.                         <mx:Button label="Save as ByteArray" click="saveByteHandler(event)" width="100%"/>
  175.                     </mx:GridItem>
  176.                 </mx:GridRow>
  177.                 <mx:GridRow width="100%" height="100%">
  178.                     <mx:GridItem width="100%" height="100%" colSpan="2">
  179.                     </mx:GridItem>
  180.                 </mx:GridRow>
  181.             </mx:Grid>
  182.         </ns1:LabelledBox>
  183.         <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}">
  184.             <mx:Grid>
  185.                 <mx:GridRow width="100%" height="100%">
  186.                     <mx:GridItem width="100%" height="100%" verticalAlign="middle">
  187.                         <mx:Label text="" id="image_size" />
  188.                     </mx:GridItem>
  189.                 </mx:GridRow>
  190.                 <mx:GridRow width="100%" height="100%">
  191.                 </mx:GridRow>
  192.             </mx:Grid>
  193.         </ns1:LabelledBox>
  194.     </mx:VBox>
  195. </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. Загрузки

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

Теги:


3 комментария к записи:

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

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

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

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

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

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

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

Имя: *
* — обязательно для заполнения
Электропочта: *
Сайт:
Сообщение *
Коментировать
Коментировать