User's collector

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

Проект SabreAMF

На OSFlash.org появился новый проект SabreAMF, представляющий собой сервер, написанный на PHP, 5 и AMF клиент к нему. Проект будет полезен для людей, которые желают работать с серверными приложениями, написанными на PHP, посредством AMF и Flash Remoting.

Сейчас проект находится в состоянии альфа-версии. Однако в будущем планируется реализовать полную поддержку протоколов AMF0 и AMF3. Поэтому предлагаю совместно следить за развитием проекта. SabreAMF разрабатывается под лицензией BSD. Ссылки на мейл-лист и SVN репозиторий прилагаются.

4 комментария

Теги:

Набрел на еще один интересный ресурс — сайт Компьютерной газеты. Честно говоря, я так и не понял, оффлайновое ли это издание, и можно ли номера газеты купить в России и в других странах СНГ (судя по доменной зоне, газета белорусская). Если это возможно, то я бы с удовольствием подписался на ее выпуски.

А все дело в том, что один из авторов газеты — black zorro — по всей видимости, является практикующим разработчиком на Flash/Flex/Ajax. И благодаря этому факту, из-под его пера (ну или клавиатуры) вышло несколько интересных статей на эту тему. Движок сайта газеты хромает, поэтому ссылки на архивные статьи 1997-2006 гг. (а так же статьи ранних выпусков 2007 года) являются битыми, и вот весь список статей black zorro по этой тематике, которые я смог найти (не гарантирую, что это все):

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

3 комментария

Теги:

До появления 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 комментария

Теги: