Thursday, August 18, 2011

Reading exif data on iOS (Adobe AIR)

When an AIR application gets an image from the CameraUI or CameraRoll class on iOS, the device returns a file that obeys the JPEG JFIF standard rather than the JPEG Exif defacto standard. While these standards are very similar, they each make one incompatible requirement -- that their format marker tag go first! In practice, at least for iOS images from the media library, this just means that you have to expect that the JFIF marker might be there when you are looking for the exif data.

At least one popular ActionScript exif reading library, jp.shichiseki.exif doesn't expect the JFIF marker and fails to find the exif data. The fix is easy, though.

To fix the problem, you have to check to see if the first marker after the initial JPEG marker is  the JFIF one. If it is, skip it. That's all there is too it. (Well, at least for the iOS images I tested it on.)

To fix the library, you just have to add the folowing to the ExifInfo class:

private const JFIF_MAKER:Array = [0xff, 0xe0]; //new marker type

//Updated to skip JFIF marker
private function validate(stream:ByteArray):Boolean {
        var app1DataSize:uint;
        // JPG format check
        if (!hasSoiMaker(stream) ) {
            return false;
        }
        if(hasJFIFMaker(stream)) //Skip the JFIF marker, if present. CWW
        {
            stream.position += 16;
        }
        else stream.position -=2; //Set position back to start of APP1 marker
           
        if ( !hasAPP1Maker(stream)) {
            return false;
        }
        // handle app1 data size
        app1DataSize = stream.readUnsignedShort();
        if (!hasExifHeader(stream)) {
            return false;
        }
        return true;
}

//New function to check for JFIF marker
private function hasJFIFMaker(stream:ByteArray):Boolean {
        return compareStreamBytes(stream, JFIF_MAKER);
}

 







3 comments:

  1. Hi Joe, great tutorial!
    ExifLoader.loadFilePromise() doesn't work. ExitInfo is null.

    override public function loadFilePromise(promise:IFilePromise, context:LoaderContext=null):void
    {
    _context = context;

    _mediaPromise = promise;

    contentLoaderInfo.addEventListener(Event.COMPLETE, onFilePromiseComplete, false, 0, true);
    super.loadFilePromise(promise);
    }

    // FilePromise loaded.
    private function onFilePromiseComplete(event:Event):void
    {
    contentLoaderInfo.removeEventListener(Event.COMPLETE, onFilePromiseComplete);

    // Read bytes to populate ExifInfo.
    _dataSource = _mediaPromise.open();

    if(_mediaPromise.isAsync) {
    //trace( "Asynchronous media promise." );
    var eventSource:IEventDispatcher = _dataSource as IEventDispatcher;
    eventSource.addEventListener(Event.COMPLETE, function():void{ readMediaData(event) }, false, 0, true );
    } else {
    //trace( "Synchronous media promise." );
    readMediaData(event);
    }
    }

    private function readMediaData(event:Event):void
    {
    var data:ByteArray = new ByteArray();
    _dataSource.readBytes( data );
    _exif = new ExifInfo(data);
    dispatchEvent(event);
    }

    ReplyDelete
  2. I suggest dumping data to a file and looking at it in a hex editor. If it is a valid jpeg file and has exif data, then you will have to debug into the exif reader code.

    I did notice that if anything goes wrong, the jp.shichiseki library returns null for the Exif data.

    ReplyDelete

Note: Only a member of this blog may post a comment.