[fixed] FileReference does not work with some images

Hi there,

I am using FileReference to load image from local disk to an HTML5 app.
Everything works fine exept on some images (in my case, it does not work with images taken from my Samsung phone). It’s not related to image size because if I import them in Photoshop and make a copy, it works again.

private var _fr:FileReference = new FileReference();

_fr.addEventListener(Event.SELECT, startLoad);
_fr.addEventListener(Event.COMPLETE, loadComplete);

function open_click(_)
{
_fr.browse([new FileFilter(“images”, “image/*”, “”)]);
}

function startLoad(_)
{
if (_opening)
return;

  _opening = true;
  _fr.load();

}

function loadComplete(_)
{
_fr.removeEventListener(Event.COMPLETE, loadComplete);

  _opening = false;
  
  BitmapData.loadFromBytes(_fr.data).onComplete(function(bdata) 
  {
  	try
  	{
  		_original_bitmap = bdata;
  		
  		//process();
  	}
  	
  	catch (e)
  	{
  		trace(e.message); // "bdata is null" with images taken with my phone
  	}
  	
  });

}

Any idea on another way to handle all types of JPG?
Thanks for youy help!

Yes, I have the same problem.

I’d recommend posting one of those naughty images. Maybe there’s something wrong with the fileheader or it doesn’t like the EXIF data.

Here is one: https://www.dropbox.com/s/qbi609eq2kua72z/example.jpg?dl=1

Does your browser show network activity as it loads your file? It might be possible that you have special characters in the file name which are interfering.

As for the JPG, I tested your example.jpg using OpenFL’s AssetLibrary (built in code) and the jpeg displays fine using HTML5. So there is nothing within my version of Haxe (4.1.5) or OpenFL which are not reading the data correctly. (using Lime 7.8.0 and OpenFL 8.9.5).

I recently required similar functions and overlooked the existence of FileReference. I am using js.html.FileReader instead with good results (for a text file anyway). It requires a DOM “input” element of type “fileInput”.

Let me know if you want example code for any of that.

As I’ve suspected it has to do with the image itself and due to the way openfl/lime handles JPEGs.

Let me try to explain: Usually files have a magic number - a sequence of bytes at the start of the file - so you can read it out and identify it as that particular filetype. In case of JPG it’s 0xffd8 starting at offset 0. Additionally a JPG also marks the end of a file with a magic number - 0xffd9.

If you call BitmapData.loadFromBytes() it will in-turn invoke a function of the lime.Graphics package: __isJPG() to verify the bytes passed in are indeed a JPG file. Below you can see that it checks two bytes at the start and the end of bytes.

Now if you go one step further and actually lookup your file inside a hexeditor, you’ll realize that your file doesn’t end with 0xffd9 because your camera seems to store some additional information at the end of the file. Because of that __isJPG() returns false and ultimately null gets returned.

Now there are two solutions to overcome that issue:

  1. Resave your jpg with e.g. Photoshop.
  2. Extract the bytes from the start of the image up to the end marker 0xffd9 and use it instead.

Something like:

@:keep class Helper {
    public static var jfifHeader:Array<Int>=[0xff,0xd8,0xff,0xe0];
    public static var exifHeader:Array<Int>=[0xff,0xd8,0xff,0xe1];
    public static var jpegEnd:Array<Int>=[0xff,0xd9];
  
    public static function isJfif(bytes:ByteArray):Bool 
    {
        for(a in 0...jfifHeader.length)
        {
            if(jfifHeader[a]!=bytes[a])
            {
                return false;
            }
        }
        return true;
    }
    
    public static function findEnd(bytes:ByteArray):Int
    {
        var position:Int=0;
        var last:Int=0;
        do
        {
            if(bytes[position]==jpegEnd[0])
            {
                last=position;
                if(bytes[position+1]==jpegEnd[1])
                {
                    last=position;
                }
                
            }
            position++;
        }
        while(position<bytes.length-2);
        
         last+=2;
        return last;
    }  
    
}

and

function loadComplete(_)
{
    _fr.removeEventListener(Event.COMPLETE, loadComplete);
    
    _opening = false;
    
    var bytes:ByteArray;
    if(!Helper.isJfif(_fr.data))
    {
        bytes=new ByteArray();
        bytes.writeBytes(_fr.data,0,Helper.findEnd(_fr.data));
    }
    else
    {
        bytes=_fr.data; 
    }
    
    BitmapData.loadFromBytes(bytes).onComplete(function(bdata) 
    {
        try
        {
            _original_bitmap = bdata;
            
            //process();
        }
        
        catch (e)
        {
            trace(e.message); // "bdata is null" with images taken with my phone
        }
        
    });
    
}
1 Like

Brilliant! Thanks you so much guy! I will try to implement your code. Thanks again!

:slight_smile:

It works!

I did just a little change because only weird images were working at first and not the others…so I implemented your code in the catch section, only if first try fails so…and now all the images are working.

Thanks!