Downloadable Image in Flex–is it possible that images contained in Flex applications are downloadable as they rendered in HTML?

Reading-The-Book-128x128 Problem Summery: Continue the sample posted before A Trick of Using PNGs to Mask Images, When you browse a web page rendered in HTML with fire fox, IE, etc., click the right button of mouse above an image, you will find these commands as below:

clip_image002

As you can see, the browser provides a command named “Save Image as” or some words similar to allow you save this image into the local disk. However, is it possible that images contained in Flex applications are downloadable as they rendered in HTML?

Solution Summery: I make a monkey-patched Image class derived from SWFLoader class of Flex SDK and added the custom context menu “save as” to make the image source downloadable. Most of the codes of Image class are copied from Image class of Flex SDK.

*A monkey patch (also spelled monkey-patch, MonkeyPatch) is a way to extend or modify the runtime code of dynamic languages (e.g. Smalltalk, JavaScript, Objective-C, Ruby, Perl, Python, Groovy etc.) without altering the original source code.

Due to the different features between flash player 9 and flash player 10, this feature is not fully supported with flash player 9.

Search-256x256 Demo | DownloadDownload Full Project

Following is the source code of Image.as:

  1. package mx.controls
  2. {
  3.  
  4. import flash.display.Bitmap;
  5. import flash.display.BitmapData;
  6. import flash.display.DisplayObject;
  7. import flash.events.ContextMenuEvent;
  8. import flash.events.Event;
  9. import flash.net.FileReference;
  10. import flash.net.URLRequest;
  11. import flash.ui.ContextMenu;
  12. import flash.ui.ContextMenuItem;
  13. import flash.utils.ByteArray;
  14.  
  15. import mx.controls.listClasses.BaseListData;
  16. import mx.controls.listClasses.IDropInListItemRenderer;
  17. import mx.controls.listClasses.IListItemRenderer;
  18. import mx.core.IDataRenderer;
  19. import mx.core.mx_internal;
  20. import mx.events.FlexEvent;
  21. import mx.graphics.codec.PNGEncoder;
  22. import mx.managers.SystemManagerGlobals;
  23. import mx.utils.LoaderUtil;
  24. use namespace mx_internal;
  25.  
  26. [Event(name="dataChange", type="mx.events.FlexEvent")]
  27.  
  28. [DefaultBindingProperty(source="progress", destination="source")]
  29.  
  30. [DefaultTriggerEvent("complete")]
  31.  
  32. [IconFile("Image.png")]
  33.  
  34. public dynamic class Image extends SWFLoader
  35.                    implements IDataRenderer, IDropInListItemRenderer,
  36.                    IListItemRenderer
  37. {   
  38.     //--------------------------------------------------------------------------
  39.     //
  40.     //  Constructor
  41.     //
  42.     //--------------------------------------------------------------------------
  43.  
  44.     /**
  45.      *  Constructor.
  46.      */
  47.     public function Image()
  48.     {
  49.         super();
  50.  
  51.         // images are generally not interactive
  52.         tabChildren = false;
  53.         tabEnabled = true;
  54.        
  55.         showInAutomationHierarchy = true;      
  56.     }
  57.  
  58.     private var makeContentVisible:Boolean = false;
  59.    
  60.     private var sourceSet:Boolean;
  61.  
  62.     private var settingBrokenImage:Boolean;
  63.  
  64.     [Bindable("sourceChanged")]
  65.     [Inspectable(category="General", defaultValue="", format="File")]
  66.  
  67.     override public function set source(value:Object):void
  68.     {
  69.         settingBrokenImage = (value == getStyle("brokenImageSkin"));
  70.         sourceSet = !settingBrokenImage;
  71.         super.source = value;    
  72.         invalidateProperties()
  73.     }
  74.  
  75.     private var _data:Object;
  76.  
  77.     [Bindable("dataChange")]
  78.     [Inspectable(environment="none")]
  79.  
  80.     public function get data():Object
  81.     {
  82.         return _data;
  83.     }
  84.  
  85.     public function set data(value:Object):void
  86.     {
  87.         _data = value;
  88.        
  89.         if (!sourceSet)
  90.         {
  91.             source = listData ? listData.label : data;
  92.             sourceSet = false;
  93.         }
  94.  
  95.         dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
  96.     }
  97.  
  98.     private var _listData:BaseListData;
  99.  
  100.     [Bindable("dataChange")]
  101.     [Inspectable(environment="none")]
  102.  
  103.     public function get listData():BaseListData
  104.     {
  105.         return _listData;
  106.     }
  107.  
  108.     public function set listData(value:BaseListData):void
  109.     {
  110.         _listData = value;
  111.     }
  112.    
  113.     private var downloadMenu:ContextMenuItem;
  114.     private var fr:FileReference;
  115.    
  116.     // check if the run-time support the features of 
  117.     // flash player 10
  118.     private function get usingPlayerGlobal10():Boolean
  119.     { 
  120.         if(!fr) fr = new FileReference();
  121.         var result:Boolean = false;
  122.         try
  123.         {
  124.             result = fr.load is Function;
  125.         }
  126.         catch(e:Error)
  127.         {
  128.             result = false;
  129.         }
  130.         return result;
  131.     }
  132.    
  133.     private var _downloadable:Boolean = false;
  134.    
  135.     [Inspectable(category="General", defaultValue="false", format="Boolean")]
  136.     public function get downloadable():Boolean
  137.     {
  138.         return _downloadable;
  139.     }
  140.    
  141.     public function set downloadable(value:Boolean):void
  142.     {
  143.         _downloadable = value;
  144.         invalidateProperties();
  145.     }
  146.  
  147.     override public function invalidateSize():void
  148.     {
  149.         if (data && settingBrokenImage)
  150.         {
  151.             // don't invalidate otherwise we'll reload and loop forever
  152.             return;
  153.         }
  154.  
  155.         super.invalidateSize();
  156.     }
  157.    
  158.     override protected function commitProperties():void
  159.     {
  160.         super.commitProperties();
  161.        
  162.         if(!downloadable)
  163.         {
  164.             removeContextMenu();
  165.          }
  166.          else
  167.          {
  168.              if(usingPlayerGlobal10)
  169.             {
  170.                 if(!source || source == "")
  171.                 {
  172.                     removeContextMenu();
  173.                 }
  174.                 else
  175.                 {
  176.                     addContextMenu();
  177.                 }
  178.             }
  179.             else
  180.             {
  181.                 if(source && source is String && source != "")
  182.                 {
  183.                     var url:String = getAbsoluteURL(String(source));
  184.                     if(url.indexOf("http") == 0)
  185.                         addContextMenu();
  186.                 }
  187.                 else
  188.                 {
  189.                     removeContextMenu()
  190.                 }
  191.             }
  192.          }      
  193.     }
  194.  
  195.     override protected function updateDisplayList(unscaledWidth:Number,
  196.                                                   unscaledHeight:Number):void
  197.     {
  198.         super.updateDisplayList(unscaledWidth, unscaledHeight);
  199.         if (makeContentVisible && contentHolder)
  200.         {
  201.             contentHolder.visible = true;
  202.             makeContentVisible = false;
  203.         }     
  204.     }
  205.    
  206.     override mx_internal function contentLoaderInfo_completeEventHandler(
  207.                                         event:Event):void
  208.     {
  209.         var obj:DisplayObject = DisplayObject(event.target.loader);
  210.  
  211.         super.contentLoaderInfo_completeEventHandler(event);
  212.        
  213.         // Hide the object until draw
  214.         obj.visible = false;
  215.         makeContentVisible = true;
  216.         invalidateDisplayList();
  217.     }
  218.    
  219.     private function addContextMenu():void
  220.     {
  221.         if(!fr) 
  222.             fr = new FileReference();
  223.         if(!contextMenu)
  224.         {
  225.             contextMenu = new ContextMenu();
  226.             contextMenu.hideBuiltInItems();
  227.         }
  228.         if(!downloadMenu)
  229.             downloadMenu = new ContextMenuItem("Save Image...");
  230.        
  231.         downloadMenu.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuItemSelectHandler);
  232.         contextMenu.customItems.push(downloadMenu);       
  233.     }
  234.    
  235.     private function removeContextMenu():void
  236.     {
  237.         if(downloadMenu && contextMenu)
  238.         {
  239.             var index:int = contextMenu.customItems.indexOf(downloadMenu);
  240.             if(index > -1)
  241.             {
  242.                 contextMenu.customItems[index] = null;
  243.                 downloadMenu.removeEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuItemSelectHandler);
  244.             }         
  245.         }
  246.     }
  247.    
  248.     private function menuItemSelectHandler(event:ContextMenuEvent):void
  249.     {
  250.         if(event.currentTarget == downloadMenu)
  251.         {          
  252.             var cls:Class;
  253.             var url:String;
  254.            
  255.             if (!(source is Class) && !(source is ByteArray) && !(source is DisplayObject))
  256.             {
  257.                 try
  258.                 {
  259.                     cls = Class(systemManager.getDefinitionByName(String(source)));
  260.                 }
  261.                 catch(e:Error)
  262.                 { // ignore
  263.                 }
  264.                 url = source.toString();                   
  265.             }
  266.                            
  267.             if(!cls && url)
  268.             {              
  269.                 url = getAbsoluteURL(url);
  270.                
  271.                 if(url.indexOf("http") == 0)
  272.                 {
  273.                        fr.download(new URLRequest(url));                      
  274.                        return;
  275.                    }
  276.             }
  277.            
  278.             if(usingPlayerGlobal10)
  279.             {
  280.                 if(content && content is Bitmap)
  281.                 {
  282.                     var bitmapData:BitmapData = Bitmap(content).bitmapData;
  283.                     var encoder:PNGEncoder = new PNGEncoder();
  284.                     var encodeResult:ByteArray = encoder.encode(bitmapData);
  285.                     fr.save(encodeResult,name + ".png");
  286.                 }
  287.             }
  288.         }
  289.     }
  290.    
  291.     private function getAbsoluteURL(rawURL:String):String
  292.     {
  293.         var url:String = rawURL;
  294.         if (!(url.indexOf(":") > -1 || url.indexOf("/") == 0 || url.indexOf("\\") == 0))
  295.         {
  296.             var rootURL:String;
  297.             if (SystemManagerGlobals.bootstrapLoaderInfoURL != null && SystemManagerGlobals.bootstrapLoaderInfoURL != "")
  298.                 rootURL = SystemManagerGlobals.bootstrapLoaderInfoURL;
  299.             else if (root)
  300.                 rootURL = LoaderUtil.normalizeURL(root.loaderInfo);
  301.             else if (systemManager)
  302.                 rootURL = LoaderUtil.normalizeURL(DisplayObject(systemManager).loaderInfo);
  303.  
  304.             if (rootURL)
  305.             {
  306.                 var lastIndex:int = Math.max(rootURL.lastIndexOf("\\"), rootURL.lastIndexOf("/"));
  307.                 if (lastIndex != -1)
  308.                     url = rootURL.substr(0, lastIndex + 1) + url;
  309.             }
  310.         }
  311.         return url;
  312.     }
  313.       
  314. }
  315.  
  316. }

Screenshot

clip_image004

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • Reddit
  • Technorati
  • StumbleUpon
  • Twitter
RSS Enjoy this Post? Subscribe to Ntt.cc

RSS Feed   RSS Feed     Email Feed  Email Feed
You can leave a response, or trackback from your own site.

6 Responses to “Downloadable Image in Flex–is it possible that images contained in Flex applications are downloadable as they rendered in HTML?”

  1. DavidD says:

    Very nice ! it sometimes frustrating not to have the same old known behaviours you are used to with html when you are in a Flex app.

    But the real question stays …

    … who’s the girl ? :-)

  2. T_Champak says:

    HI

    I am getting the following errors while builing the application

    1061: Call to a possibly undefined method save through a reference with static type flash.net:FileReference.

    1119: Access of possibly undefined property load through a reference with static type flash.net:FileReference.

    I am using flex 3

    please tell me what is to be done

  3. neil says:

    Hi,

    Same error as T_Champak, did u find a solution?

    Thanks in advance

    Neil

  4. Mitesh panchal says:

    hi It is good.I have problem in image control.i want to remove white color and replays with its background color so any idea of this problem please send me code or some solution on send me mail Thank you

  5. xhalox says:

    Verry good ! Thanks for share….

Leave a Reply