22 Replies Latest reply: Jul 7, 2013 12:42 AM by aboutveniceY RSS

    Camera image orientation on iOS

    nirnirnir Community Member

      Hi,

       

      Taking a picture using CamerUI on iOS (and on Android) may bring the image oriented in the wrong angle.

      Reading the Exif on Android is fine and allows reorientation to the correct position, however this is not working on iOS.

      Since I assume taking a picture using the camera is a core feature for many applications, I’d appreciate if someone who managed to solve it can post a solution here?

      Thanks!

        • 1. Re: Camera image orientation on iOS
          WeLoveKimchi

          Up !

           

          Same problem for me.

          How a basic fonction like this is so difficult to find !?

          Did you find anything yet ?

           

          Cheers.

          • 2. Re: Camera image orientation on iOS
            Joe ... Ward Community Member

            This information comes from one of Adobe's quality engineers:

             

            So I found that the image that we get after using CameraRoll/CameraUI  does contain EXIF information but not in the format expected by the  ExifInfo AS3 library (http://code.shichiseki.jp/as3/ExifInfo/).

            Firstly, it does contain the orientation information which I am able to  verify using the Jpeg decoding tool :  http://www.impulseadventure.com/photo/jpeg-snoop-source.html.

            The Exif data is stored in one of JPEG’s defined utility Application  Segments APPn. The AS3 library - ExifInfo that you are using for reading  Exif header expects APP1 marker whereas the actual image has APP0 at  that place followed by APP1 segment which contains the orientation data.  Images that comply to JFIF standards have APP0 marker just after SOI  (Start of Image) and images that comply to EXIF standards should have  APP1 marker after SOI.

            As per wiki http://en.wikipedia.org/wiki/JPEG_File_Interchange_Format :  "many programs and digital cameras produce files with both application  segments included" which is what exactly happens in our case.

            So I think you may try switching to a library which is able to parse an  image that has multiple APPn markers and read only the one required. You  can even modify the ExifInfo library.

            Hope this helps.

            • 3. Re: Camera image orientation on iOS
              WeLoveKimchi Community Member

              Thanks a lot for your answer.

              I understand the concept of switching the APP marker to get data, but i already have trouble about accessing picture.

               

              For exemple if i take the sample from ExifInfo AS3 Library (just the part about accessing the image), here is my test

               

              private function mediaSelectHandler( event:MediaEvent ):void
              {
                      mediaPromise = event.data;
                      trace(mediaPromise);
                      trace(mediaPromise.file.url);

                      /*
                      this.exifLoader = new ExifLoader();
                      this.exifLoader.addEventListener(Event.COMPLETE, completeHandler );
                      this.exifLoader.load( new URLRequest( mediaPromise.file.url ) );

                      */

              }

               

              When media is selected, when i trace mediaPromise, i get a media [mediaPromise object], but when i trace mediaPromise.file.url its null. In that way how can i get access to the file because since on iOS its null.

               

              Is there any example/sample about this, because i really get the concept and the logic, but i have no idea how to get it.

               

              Thank you again

              • 4. Re: Camera image orientation on iOS
                Joe ... Ward Community Member

                On iOS, the datasource for the MediaPromise is not file-based, so the file URL is null. Instead, you have to read the image data into a ByteArray in order to access the raw image data. (To just display the image, you could use the Loader.loadMediaPromise() method, but that doesn't give you the EXIF data.)

                 

                For an example of how to get the raw image data, please see my code example in this thread: http://forums.adobe.com/message/3667058#3667058.

                • 5. Re: Camera image orientation on iOS
                  WeLoveKimchi Community Member

                  Thanks a lot for all of these informations.

                  You are provided me a really big solution. But unfortunately i didnt arrive yet to get the orientation.

                   

                  As a test i used the sample you gave me, that get the picture as a ByteArray.

                  I edited and checked how the Exif library was working, and indeed after getting the URL of the image, it is loading its ByteArray

                   

                  This is from exifLoader.as (from the library http://code.shichiseki.jp/as3/ExifInfo/)

                   

                          private function onComplete(e:Event):void {
                              var data:ByteArray = _urlLoader.data as ByteArray;
                              _exif = new ExifInfo(data);
                              super.loadBytes(data, _context);
                              _urlLoader = null;
                              dispatchEvent(e);
                          }

                   

                  So my idea was to simply add this in your script, like this, since we dont need Loader anymore because we have already the ByteArray :

                   

                          private function readMediaData():void
                          {
                              trace("Getting byteArray");
                              var imageBytes:ByteArray = new ByteArray();
                              dataSource.readBytes( imageBytes );
                             
                              trace("Data Trace");
                              trace(imageBytes.length);
                              imageBytes.position = 0;
                              var string:String = imageBytes.readUTFBytes(300);
                              trace( string );

                   

                              // ------------------------------------------------------------------- trying to get exif data

                   

                              trace("Exif info");           
                              var exif:ExifInfo = new ExifInfo(imageBytes);
                              ldr = new Loader();
                              ldr.loadBytes(imageBytes);
                             
                              trace("exif =");
                              trace(exif);
                              trace("exif trace");
                              trace(exif.ifds);
                              trace(exif.ifds.primary);
                              trace(exif.ifds.exif);
                              trace(exif.ifds.gps);
                              trace(exif.ifds.thumbnail);
                             
                          }

                   

                  So when i trace this i get :

                   

                  Getting byteArray
                  Data Trace
                  274667
                  ÿØÿà

                   

                  So no problem until here, it is working, now here is the second part

                   

                  Exif info
                  exif =
                  [object ExifInfo]
                  exif trace
                  null
                  TypeError: Error #1009: Cannot access a property or method of a null object reference.

                   

                  So where im blocked is to convert this ByteArray into an ExifInfo object, and then geting the different properties such as orientation, gps etc.

                  But it is null ~

                   

                  I know you told be before that data should be in APP0 and not APP1. But since this library is working perfectly on android and also on local files, i wanted to give it a try.

                   

                   

                  I think solution is really close, its just a question of syntax  ! ^^

                  • 6. Re: Camera image orientation on iOS
                    Joe ... Ward Community Member

                    I also got errors with every Exif library I tried (posibly because iOS puts in the JFIF APP0 marker and both JFIF and EXIF specs state that their marker goes first). However, this one was able to get the orientation: http://devstage.blogspot.com/2011/02/extract-jpg-exif-metadata-in.html

                     

                    I used the following code to get the orientation from the exif reader class:

                            private function onFileSelect( event:Event ):void
                            {
                                var stream:FileStream = new FileStream();
                                var buffer:ByteArray = new ByteArray();
                               
                                stream.open( file, FileMode.READ );
                                stream.readBytes(buffer,0,8)
                                stream.position = 0;
                                trace( file.name + " is " + checkMagicNumber( buffer ) );
                                stream.close();           
                               
                                var reader:ExifReader = new ExifReader();
                                reader.addEventListener(Event.COMPLETE, function (e:Event):void{
                                    // Prints all of the metadata keys parsed.
                                    if( reader.hasKey( "Orientation" ) )
                                    {
                                        var orientation:uint = uint(reader.getValue("Orientation"));
                                        trace(orientation.toString(16));
                                    }

                     

                                });
                                reader.load(new URLRequest(file.url), true);
                            }

                     

                    1 = normal

                    3 = rotated 180 degrees (upside down)

                    6 = rotated 90 degrees CW

                    8 = rotated 90 degrees CCW

                    9 = unknown

                     

                    Note that I was running this in a desktop AIR app, not on my phone (it was quicker). That's why I'm using the File API here. I tested images uploaded from the phone that had passed through the CameraRoll. So adapting the Exif reader to use Loader.loadBytes() instead of Loader.load() remains to be done for your use case. Still, this should get you started.

                     

                    (Please note the developer's terms of use if you use the exif reader code.)

                    • 7. Re: Camera image orientation on iOS
                      Joe ... Ward Community Member

                      It also wasn't hard to fix the jp.shichiseki library to ignore the JFIF marker. 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);
                              }

                       

                      Note that the library fails silently if it doesn't recognize the file format. That's why you get a null reference error. The library wasn't creating any of its usual objects. Another thing to be aware of is that not all devices on Android record the orientation. In my collection, only one of three did so.

                      • 8. Re: Camera image orientation on iOS
                        ErichCervantez

                        Joe, I'm interested in taking a look at the ExifReader from http://devstage.blogspot.com/2011/02/extract-jpg-exif-metadata-in.html but that URL now seems invalid.  Any chance you have the source code laying around somewhere??

                         

                        Thanks!

                         

                        • 9. Re: Camera image orientation on iOS
                          Joe ... Ward Community Member

                          I think this is it:

                           

                          // ExifReader.as

                          // Reads and obtains JPG EXIF data.

                          // Project site: http://devstage.blogspot.com/2011/02/extract-jpg-exif-metadata-in.html

                          //

                          // SAMPLE USAGE:

                          // var reader:ExifReader = new ExifReader();

                          // reader.addEventListener(Event.COMPLETE, function (e:Event):void{

                          //     trace(reader.getKeys());

                          //     trace(reader.getValue("DateTime"));

                          //     trace(reader.getThumbnailBitmapData().width +'x' + reader.getThumbnailBitmapData().height);

                          //    });

                          // reader.load(new URLRequest('myimage.jpg'), true);

                           

                          package

                          {

                              import flash.display.Bitmap;

                              import flash.display.BitmapData;

                              import flash.display.Loader;

                              import flash.display.LoaderInfo;

                              import flash.events.Event;

                              import flash.events.EventDispatcher;

                              import flash.events.ProgressEvent;

                              import flash.net.URLRequest;

                              import flash.net.URLStream;

                              import flash.utils.ByteArray;

                           

                              public class ExifReader extends EventDispatcher

                              { 

                                  private var m_loadThumbnail:Boolean = false;

                                  private var m_urlStream:URLStream = null;

                                  private var m_data:ByteArray = new ByteArray();

                                  private var m_exif:Object = new Object;

                                  private var m_exifKeys:Array = new Array();

                           

                                  private var m_intel:Boolean=true;

                                  private var m_loc:uint=0; 

                                  private var m_thumbnailData:ByteArray = null;

                                  private var m_thumbnailBitmapData:BitmapData=null;

                           

                                  private var DATASIZES:Object = new Object;

                                  private var TAGS:Object = new Object;

                           

                                  public function load(urlReq:URLRequest, loadThumbnail:Boolean = false):void{

                                      m_loadThumbnail = loadThumbnail;  

                                      m_urlStream = new URLStream();

                                      m_urlStream.addEventListener( ProgressEvent.PROGRESS, dataHandler);

                                      m_urlStream.load(urlReq);

                                  }

                                  public function getKeys():Array{

                                      return m_exifKeys;

                                  }

                                  public function hasKey(key:String):Boolean{

                                      return m_exif[key] != undefined;

                                  }

                                  public function getValue(key:String):Object{

                                      if(m_exif[key] == undefined) return null;

                                      return m_exif[key];

                                  }

                                  public function getThumbnailBitmapData():BitmapData{

                                      return m_thumbnailBitmapData;

                                  }

                           

                                  public function ExifReader(){

                                      DATASIZES[1] = 1;

                                      DATASIZES[2] = 1;

                                      DATASIZES[3] = 2;

                                      DATASIZES[4] = 4;

                                      DATASIZES[5] = 8;

                                      DATASIZES[6] = 1;  

                                      DATASIZES[7] = 1;

                                      DATASIZES[8] = 2;

                                      DATASIZES[9] = 4;

                                      DATASIZES[10] = 8;

                                      DATASIZES[11] = 4;

                                      DATASIZES[12] = 8;

                           

                                      TAGS[0x010e] = 'ImageDescription';

                                      TAGS[0x010f] = 'Make';

                                      TAGS[0X0110] = 'Model';

                                      TAGS[0x0112] = 'Orientation';

                                      TAGS[0x011a] = 'XResolution';

                                      TAGS[0x011b] = 'YResolution';

                                      TAGS[0x0128] = 'ResolutionUnit';

                                      TAGS[0x0131] = 'Software';

                                      TAGS[0x0132] = 'DateTime';

                                      TAGS[0x013e] = 'WhitePoint';

                                      TAGS[0x013f] = 'PrimaryChromaticities';

                                      TAGS[0x0221] = 'YCbCrCoefficients';

                                      TAGS[0x0213] = 'YCbCrPositioning';

                                      TAGS[0x0214] = 'ReferenceBlackWhite';

                                      TAGS[0x8298] = 'Copyright';

                           

                                      TAGS[0x829a] = 'ExposureTime';

                                      TAGS[0x829d] = 'FNumber';

                                      TAGS[0x8822] = 'ExposureProgram';

                                      TAGS[0x8827] = 'IsoSpeedRatings';

                                      TAGS[0x9000] = 'ExifVersion';

                                      TAGS[0x9003] = 'DateTimeOriginal';

                                      TAGS[0x9004] = 'DateTimeDigitized';

                                      TAGS[0x9101] = 'ComponentsConfiguration';

                                      TAGS[0x9102] = 'CompressedBitsPerPixel';

                                      TAGS[0x9201] = 'ShutterSpeedValue';

                                      TAGS[0x9202] = 'ApertureValue';

                                      TAGS[0x9203] = 'BrightnessValue';

                                      TAGS[0x9204] = 'ExposureBiasValue';

                                      TAGS[0x9205] = 'MaxApertureValue';

                                      TAGS[0x9206] = 'SubjectDistance';

                                      TAGS[0x9207] = 'MeteringMode';

                                      TAGS[0x9208] = 'LightSource';

                                      TAGS[0x9209] = 'Flash';

                                      TAGS[0x920a] = 'FocalLength';

                                      TAGS[0x927c] = 'MakerNote';

                                      TAGS[0x9286] = 'UserComment';

                                      TAGS[0x9290] = 'SubsecTime';

                                      TAGS[0x9291] = 'SubsecTimeOriginal';

                                      TAGS[0x9292] = 'SubsecTimeDigitized';

                                      TAGS[0xa000] = 'FlashPixVersion';

                                      TAGS[0xa001] = 'ColorSpace';

                                      TAGS[0xa002] = 'ExifImageWidth';

                                      TAGS[0xa003] = 'ExifImageHeight';

                                      TAGS[0xa004] = 'RelatedSoundFile';

                                      TAGS[0xa005] = 'ExifInteroperabilityOffset';

                                      TAGS[0xa20e] = 'FocalPlaneXResolution';

                                      TAGS[0xa20f] = 'FocalPlaneYResolution';

                                      TAGS[0xa210] = 'FocalPlaneResolutionUnit';

                                      TAGS[0xa215] = 'ExposureIndex';

                                      TAGS[0xa217] = 'SensingMethod';

                                      TAGS[0xa300] = 'FileSource';

                                      TAGS[0xa301] = 'SceneType';

                                      TAGS[0xa302] = 'CFAPattern';

                           

                                      //... add more if you like.

                                      //See http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html

                                  }

                           

                                  private function dataHandler(e:ProgressEvent):void{

                                      //EXIF data in top 64kb of data

                                      if(m_urlStream.bytesAvailable >= 64*1024){

                                          m_urlStream.readBytes(m_data, 0, m_urlStream.bytesAvailable);

                                          m_urlStream.close();

                                          processData();

                                      }

                                  }

                                  private function processData():void{

                                      var iter:int=0;

                           

                                      //confirm JPG type

                                      if(!(m_data.readUnsignedByte()==0xff && m_data.readUnsignedByte()==0xd8))

                                          return stop();

                           

                                      //Locate APP1 MARKER

                                      var ff:uint=0;

                                      var marker:uint=0;

                                      for(iter=0;iter<5;++iter){ //cap iterations

                                          ff = m_data.readUnsignedByte();

                                          marker = m_data.readUnsignedByte();

                                          var size:uint = (m_data.readUnsignedByte()<<8) + m_data.readUnsignedByte();

                                          if(marker == 0x00e1) break;

                                          else{

                                              for(var x:int=0;x<size-2;++x) m_data.readUnsignedByte();

                                          }

                                      }

                                      //Confirm APP1 MARKER

                                      if(!(ff == 0x00ff && marker==0x00e1)) return stop();

                           

                                      //Confirm EXIF header

                                      var i:uint;

                                      var exifHeader:Array = [0x45,0x78,0x69,0x66,0x0,0x0];

                                      for(i=0; i<6;i++) {if(exifHeader[i] != m_data.readByte()) return stop();}

                           

                                      //Read past TIFF header

                                      m_intel = (m_data.readByte()!=0x4d);

                                      m_data.readByte(); //redundant

                                      for(i=0; i<6;i++) {m_data.readByte();} //read rest of TIFF header

                           

                                      //Read IFD data

                                      m_loc = 8;

                                      readIFD(0);

                           

                                      //Read thumbnail

                                      if(m_thumbnailData){

                                          var loader:Loader = new Loader();

                                          loader.contentLoaderInfo.addEventListener(Event.COMPLETE, thumbnailLoaded);

                                          loader.loadBytes(m_thumbnailData);

                                      }

                                      else stop();

                                  }

                           

                                  //EXIF data is composed of 'IFD' fields.  You have IFD0, which is the main picture data.

                                  //IFD1 contains thumbnail data.  There are also sub-IFDs inside IFDs, notably inside IFD0.

                                  //The sub-IFDs will contain a lot of additional EXIF metadata.

                                  //readIFD(int) will help read all of these such fields.

                                  private function readIFD(ifd:int):void{

                                      var iter:int=0;

                                      var jumps:Array = new Array();

                                      var subexifJump:uint=0;

                                      var thumbnailAddress:uint=0;

                                      var thumbnailSize:int=0;

                           

                                      // Read number of entries

                                      var numEntries:uint;

                                      if(m_intel) numEntries = m_data.readUnsignedByte() + (m_data.readUnsignedByte()<<8);

                                      else numEntries = (m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte());

                                      if(numEntries>100) numEntries=100; //cap entries

                           

                                      m_loc+=2;

                                      for(iter=0; iter<numEntries;++iter){

                                          //Read tag

                                          var tag:uint;

                                          if(m_intel) tag = (m_data.readUnsignedByte()) + (m_data.readUnsignedByte()<<8);

                                          else tag = (m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte());

                           

                                          //read type

                                          var type:uint;

                                          if(m_intel) type = (m_data.readUnsignedByte()+(m_data.readUnsignedByte()<<8));

                                          else type = (m_data.readUnsignedByte()<<8)+(m_data.readUnsignedByte()<<0);

                           

                                          //Read # of components

                                          var comp:uint;

                                          if(m_intel) comp = (m_data.readUnsignedByte()+(m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<24));

                                          else comp = (m_data.readUnsignedByte()<<24)+(m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<0);

                           

                                          //Read data

                                          var data:uint;

                                          if(m_intel) data= m_data.readUnsignedByte()+(m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<24);

                                          else data = (m_data.readUnsignedByte()<<24)+(m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<0);

                                          m_loc+=12;

                           

                                          if(tag==0x0201) thumbnailAddress = data; //thumbnail address

                                          if(tag==0x0202) thumbnailSize = data;  //thumbnail size (in bytes)

                           

                                          if(TAGS[tag] != undefined){

                                              //Determine data size

                                              if(DATASIZES[type] * comp <= 4){

                                                  //data is contained within field

                                                  m_exif[TAGS[tag]] = data;

                                                  m_exifKeys.push(TAGS[tag]);

                                              }

                                              else{

                                                  //data is at an offset

                                                  var jumpT:Object = new Object();

                                                  jumpT.name = TAGS[tag];

                                                  jumpT.address=data;

                                                  jumpT.components = comp;

                                                  jumpT.type = type;

                                                  jumps.push(jumpT);

                                              }

                                          }   

                           

                                          if(tag==0x8769){ // subexif tag

                                              subexifJump = data;

                                          }

                                      }

                           

                                      var nextIFD:uint;

                                      if(m_intel) {

                                          nextIFD= m_data.readUnsignedByte()+(m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<24);

                                      }

                                      else {

                                          nextIFD = (m_data.readUnsignedByte()<<24)+(m_data.readUnsignedByte()<<16) + (m_data.readUnsignedByte()<<8) + (m_data.readUnsignedByte()<<0);

                                      }

                                      m_loc+=4;

                           

                                      //commenting this out, as suggested in the comments.

                                      //if(ifd==0) jumps = new Array();

                                      for each(var jumpTarget:Object in jumps){

                                          var jumpData:Object = null;

                                          for(;m_loc<jumpTarget.address;++m_loc)m_data.readByte();

                           

                                          if(jumpTarget.type==5){

                                              //unsigned rational

                                              var numerator:uint = m_data.readInt();

                                              var denominator:uint = m_data.readUnsignedInt();

                                              m_loc+=8;

                                              jumpData = numerator / denominator;

                                          }

                                          if(jumpTarget.type==2){

                                              //string

                                              var field:String='';

                                              for(var compGather:int=0;compGather<jumpTarget.components;++compGather){

                                                  field += String.fromCharCode(m_data.readByte());

                                                  ++m_loc;

                                              }

                           

                                              if(jumpTarget.name=='DateTime' ||

                                                  jumpTarget.name=='DateTimeOriginal' ||

                                                  jumpTarget.name=='DateTimeDigitized'){

                                                  var array:Array = field.split(/[: ]/);

                                                  if(parseInt(array[0]) > 1990){

                                                      jumpData = new Date(parseInt(array[0]), parseInt(array[1])-1,

                                                          parseInt(array[2]), parseInt(array[3]),

                                                          parseInt(array[4]), parseInt(array[5]));

                                                  }

                                              }

                                              else jumpData = field;

                                          }

                                          m_exif[jumpTarget.name] = jumpData;

                                          m_exifKeys.push(jumpTarget.name);

                                      }

                           

                                      if(ifd==0 && subexifJump){

                                          //jump to subexif area to obtain meta information

                                          for(;m_loc<data;++m_loc) m_data.readByte();

                                          readIFD(ifd);

                                      }

                           

                                      if(ifd==1 && thumbnailAddress && m_loadThumbnail){

                                          //jump to obtain thumbnail

                                          for(;m_loc<thumbnailAddress;++m_loc) m_data.readByte();

                                          m_thumbnailData = new ByteArray();

                                          m_data.readBytes(m_thumbnailData, 0, thumbnailSize);

                                          return;

                                      }

                           

                                      // End-of-IFD

                           

                                      // read the next IFD

                                      if(nextIFD){

                                          for(;m_loc<nextIFD;++m_loc) m_data.readUnsignedByte();               

                                      }

                                      if(ifd==0 && nextIFD)

                                      {

                                          readIFD(1);

                                      }

                                  }

                           

                                  private function thumbnailLoaded(e:Event):void{

                                      m_thumbnailData.clear();

                                      var loader:LoaderInfo = e.target as LoaderInfo;

                                      var bitmap:Bitmap = loader.content as Bitmap;

                                      if(bitmap != null){

                                          m_thumbnailBitmapData = bitmap.bitmapData;

                                      }

                                      stop();

                                  }

                           

                                  // Releases m_data and dispatches COMPLETE signal.

                                  private function stop():void{

                                      m_data=null;

                                      dispatchEvent(new Event(Event.COMPLETE));

                                      return;

                                  }

                           

                              }

                          }

                          • 10. Re: Camera image orientation on iOS
                            JuniorTrials

                            Hi Joe, I tested the class ExifReader on Android and IOS did not work and has someexample application with the mediaPromisse?

                             

                            thank you

                            • 11. Re: Camera image orientation on iOS
                              GoonNguyen

                              Well, is there any solutions? I still can't fix the camera orientation. I already unchecked Auto orientation in publish settings and tried StageOrientationChange, but no helps.

                               

                              That's weird when loadFilePromise doesn't get the correct orrientation of the captured photo.

                              • 12. Re: Camera image orientation on iOS
                                Joe ... Ward Community Member

                                JuniorTrial,

                                 

                                ExifReader isn't something I wrote, so I can't vouch for how robust it is. I know it worked in my initial tests.

                                 

                                Here's something I wrote up about using another library: http://recycledinformation.blogspot.com/2011/08/reading-exif-data-on-ios.html (but again, most of it isn't my code. You might need to do some additional debugging/fixing to get it to work with all possible jpg images).

                                • 13. Re: Camera image orientation on iOS
                                  Joe ... Ward Community Member

                                  GoonNguyen,

                                   

                                  The root of the trouble is that there are two ways to store image orientation information in the JPEG file. In my (admittedly less than comprehensive) experience, most cameras and applications rotate the image data within the file so that the top-left-hand corner of the stored image array is the top-left-hand corner of the picture. In other words, if you took two pictures with the phone camera in opposite orientations, the top left of the picture is stored in the same place in the JPEG file for both.

                                   

                                  On iOS they chose a different way. The image data is stored as it comes out of the sensor and an additional exif data item is added to indicate the orientation. Thus if you took the same two pictures as above, you would find that one of them is inverted in the JPEG file compared to the other. So on iOS, you have to read the EXIF data to tell which way is up. (And reading the EXIF data is problematic since the file isn't really a proper JPEG format, it is a JFIF or some combination of the two. JFIF and JPEG are technically incompatible, though the practical differences are minor.)

                                  • 14. Re: Camera image orientation on iOS
                                    GoonNguyen Community Member

                                    Thank you a lot for you quick reply. After few hours trying and looking around, finnally I can read EXIF of a photo on iOS, actually it's JFIF as you said.

                                     

                                    Allow me to write something here, hope it helps people who still can't get the orientation of the photos on iOS...

                                     

                                    As first, you guys have to take a look at this: http://code.shichiseki.jp/as3/ExifInfo

                                     

                                    And as Joe said above, On iOS, the datasource for the MediaPromise is not file-based, so the file URL is null. Instead, you have to read the image data into a ByteArray in order to access the raw image data. (To just display the image, you could use the Loader.loadMediaPromise() method, but that doesn't give you the EXIF data.)

                                     

                                    Also:

                                    It also wasn't hard to fix the jp.shichiseki library to ignore the JFIF marker. 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);
                                            }

                                     

                                    Note that the library fails silently if it doesn't recognize the file format. That's why you get a null reference error. The library wasn't creating any of its usual objects. Another thing to be aware of is that not all devices on Android record the orientation. In my collection, only one of three did so.

                                     

                                     

                                    Finally, below is my few lines of code:

                                     

                                    var mediaPromise:MediaPromise;

                                    var dataSource:IDataInput;

                                     

                                    var loader:Loader = new Loader();

                                    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);

                                    addChild(loader);

                                     

                                    captureBtn.addEventListener(MouseEvent.CLICK, function(){

                                              trace(CameraRoll.supportsBrowseForImage)

                                              if(CameraRoll.supportsBrowseForImage){

                                                        camRoll.browseForImage()

                                              }

                                    })

                                     

                                    function onSelected(e:MediaEvent){

                                              mediaPromise = e.data;

                                     

                                              dataSource = mediaPromise.open();

                                     

                                              if( mediaPromise.isAsync )

                                        {

                                            trace( "Asynchronous media promise." );

                                            var eventSource:IEventDispatcher = dataSource as IEventDispatcher;           

                                            eventSource.addEventListener( Event.COMPLETE, onMediaLoaded );        

                                        }

                                        else

                                        {

                                            trace( "Synchronous media promise." );

                                            readMediaData();

                                        }

                                              //output.appendText("\n"+mediaPromise)

                                              //output.appendText("\n"+mediaPromise.file)

                                              //output.appendText("\n"+mediaPromise.file.url)

                                              //trace(mediaPromise.file.url);

                                    }

                                     

                                    function onMediaLoaded( event:Event ):void

                                    {

                                        trace("Media load complete");

                                        readMediaData();

                                    }

                                     

                                    function readMediaData():void

                                    {

                                     

                                              // THIS PART IS READING JFIF DATA:

                                     

                                              var data:ByteArray = new ByteArray();

                                              dataSource.readBytes( data );

                                              exif = new ExifInfo(data);

                                              //output.text = displayIFD(exif.ifds.exif); // This stores some properties like resolutionX, resolutionY,.etc.

                                              //output.text = displayIFD(exif.ifds.primary); // This one stores the orientation data.

                                              output.text = getOrientation(exif.ifds.primary)

                                     

                                        //do something with the data

                                     

                                              loader.loadFilePromise(mediaPromise);

                                     

                                    }

                                     

                                     

                                    function displayIFD(ifd:IFD):String {

                                              //trace(" --- " + ifd.level + " --- ");

                                              var str:String = "";

                                              for (var entry:String in ifd) {

                                                        trace(entry + ": " + ifd[entry]);

                                                        str += (entry + ": " + ifd[entry] + "\n")

                                              }

                                     

                                              return str;

                                    }

                                     

                                     

                                    function getOrientation(ifd:IFD):String{

                                              var str:String = "";

                                              for (var entry:String in ifd) {

                                                        if(entry == "Orientation"){

                                                                  str = ifd[entry];

                                                        }

                                              }

                                     

                                              switch(str){

                                                        case "1": //normal

                                                                  str = "NORMAL";

                                                        break;

                                                        case "3": //rotated 180 degrees (upside down)

                                                                  str = "UPSIDE_DOWN";

                                                        break;

                                                        case "6": //rotated 90 degrees CW

                                                                  str = "ROTATED_LEFT"

                                                        break;

                                                        case "8": //rotated 90 degrees CCW

                                                                  str = "ROTATED_RIGHT"

                                                        break;

                                                        case "9": //unknown

                                                                  str = "UNKNOWN"

                                                        break;

                                              }

                                     

                                              return str;

                                    }

                                     

                                    function onCancelled(e:Event){

                                     

                                    }

                                    Thank you a bunch, again, Joe!

                                    Have a good day!

                                    • 15. Re: Camera image orientation on iOS
                                      rogierdoekes Community Member

                                      Hi Joe,

                                       

                                      Great stuff. Just what I was looking for, as I tried to do a similar scenario: Press a button, snap a picture and upload to a vault. It works great accept for the rotated image on iOs. I understand how to use the ExifInfo to find out if a picture is rotated. But I have a hard time understanding how I actually rotate a picture before uploading to the fault. Could you help me out with that?

                                       

                                      Thanks a lot.

                                       

                                      -Rogier

                                      • 16. Re: Camera image orientation on iOS
                                        bonsai123

                                        i get to here

                                         

                                        var data:ByteArray = new ByteArray();

                                                  dataSource.readBytes( data );

                                                  exif = new ExifInfo(data);

                                         

                                        and when i try to instantiate the ExifInfo i get this error..

                                         

                                        exception fault: Error: Error #2030: End of file was encountered.

                                            at flash.utils::ByteArray/readUnsignedByte()

                                        • 17. Re: Camera image orientation on iOS
                                          Joe ... Ward Community Member

                                          @rogierdoekes,

                                           

                                          (I can't claim to be an expert on this topic, but...) I think there are two ways to rotate the image before uploading it.

                                           

                                          One is to rewrite the file data transposing the pixel rows and columns and setting other values in the jpeg format as needed. This is probably the fastest computationally, but would require a strong understanding of the jpeg file format on your part.

                                           

                                          The other way I can think of is to render the image to a Flash bitmap in the desired orientation, reencode as a jpeg and upload that. Easier to program, but slower computationally, and you add a generation of quality loss to the jpeg by reencoding.

                                           

                                          @bonsai123,

                                          When reading binary data it is really easy to make mistakes and go past the end. Perhaps the code is looking for a particular byte sequence and not finding it or the data itself is malformed. I can't debug it from here. You'll need to understand what the code is trying to do and trace the execution to see where it is going wrong.

                                           

                                          -Joe

                                          • 18. Re: Camera image orientation on iOS
                                            bonsai123 Community Member

                                            thanks for the reply Joe.. here's my code using Air 3.4 compiling .ipa file using ipa-debug-interpreter

                                             

                                            public function CameraView() : void

                                                    {

                                                        trace("CameraView.as -> CameraView");

                                             

                                                        //

                                                        // init camera

                                                        _deviceCam = new CameraUI();

                                             

                                                        if (CameraUI.isSupported)

                                                        {

                                                            _deviceCam.addEventListener(MediaEvent.COMPLETE, imageCaptured);

                                                            _deviceCam.addEventListener(flash.events.Event.CANCEL, captureCanceled);

                                                            _deviceCam.addEventListener(ErrorEvent.ERROR, errorEvent);

                                                            _deviceCam.launch(MediaType.IMAGE);

                                                        }

                                                        else

                                                            trace("CameraUI not supported");

                                                    }

                                             

                                                    private function imageCaptured(event : MediaEvent) : void

                                                    {

                                                        trace("CameraView.as -> imageCaptured");

                                             

                                                        _mediaPromise = event.data;

                                             

                                                        _dataSource = _mediaPromise.open();

                                             

                                                        if (_mediaPromise.isAsync)

                                                        {

                                                            trace("Asynchronous media promise.");

                                             

                                                            var eventSource : IEventDispatcher = _dataSource as IEventDispatcher;

                                             

                                                            eventSource.addEventListener(flash.events.Event.COMPLETE, onMediaLoaded);

                                                        }

                                                        else

                                                        {

                                                            trace("Synchronous media promise.");

                                             

                                                            readMediaData();

                                                        }

                                                    }

                                             

                                                    private function onMediaLoaded(event : flash.events.Event) : void

                                                    {

                                                        trace("Media load complete");

                                             

                                                        readMediaData();

                                                    }

                                             

                                                    private function readMediaData() : void

                                                    {

                                                        // THIS PART IS READING JFIF DATA:

                                             

                                                        var data : ByteArray = new ByteArray();

                                             

                                                        _dataSource.readBytes(data);

                                             

                                                        _exif = new ExifInfo(data);

                                             

                                                        trace("CameraView.as -> readMediaData " + _exif);

                                             

                                                        // exception fault: Error: Error #2030: End of file was encountered.

                                                       // at flash.utils::ByteArray/readUnsignedByte()

                                                    }

                                             

                                             

                                            UPDATE :

                                             

                                            actually now for some reason i'm not getting that error anymore but am getting a null object reference by replacing the last trace above with this..

                                             

                                            trace("CameraView.as -> readMediaData " + _exif.ifds.primary);

                                             

                                            another update :

                                             

                                            i hadn't correctly updated the ExifInfo.as file

                                             

                                              if (!hasSoiMaker(stream) ) {

                                                            return false;

                                                        }

                                             

                                            was still..

                                             

                                                        if (!hasSoiMaker(stream) || !hasAPP1Maker(stream)) {

                                                            return false;

                                                        }

                                             

                                             

                                            it's working now!

                                             

                                            thanks again for the reply Joe

                                            • 19. Re: Camera image orientation on iOS
                                              rogierdoekes Community Member

                                              Hi Joe,

                                               

                                              Do you have an example how to render the image to a Flash bitmap in the desired orientation? I have a hard time any scripts that does that. Pardon me my ignorance it is indeed trivial.

                                               

                                              Thanks much for your anwer,

                                               

                                              -Rogier

                                              • 20. Re: Camera image orientation on iOS
                                                bonsai123 Community Member

                                                actually.. one last thing. now i know the orientation of the image i need to display it

                                                 

                                                _imageLoader = new Loader();

                                                                _imageLoader.loadBytes(data);

                                                 

                                                                trace("loader width = " + _imageLoader.width); // this traces 0

                                                 

                                                and so..

                                                 

                                                 

                                                var bmd : BitmapData = new BitmapData(_imageLoader.width, _imageLoader.height);

                                                            bmd.draw(_imageLoader);

                                                 

                                                gives an invalid bitmapdata error

                                                 

                                                any ideas?

                                                 

                                                cheers

                                                 

                                                UPDATE :

                                                 

                                                found something.. needs a slight delay for some reason

                                                 

                                                _imageLoader.loadBytes(data);

                                                setTimeout(function(){

                                                   trace("loader width = " + _imageLoader.width); // 960

                                                },500)

                                                • 21. Re: Camera image orientation on iOS
                                                  GoonNguyen Community Member

                                                  You should add this:

                                                   

                                                  _imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);

                                                   

                                                  function onLoadComplete(e:Event)
                                                  {

                                                        trace("loader width = " + _imageLoader.width);

                                                  }

                                                   

                                                  Please, don't use setTimeout for this situation, very dangerous if you're using the low performance device to test.

                                                  • 22. Re: Camera image orientation on iOS
                                                    aboutveniceY

                                                    hi GoonNguyen

                                                     

                                                    I tried your code and modify the jp.shichiseki librariby but got error like this:

                                                     

                                                    TypeError: Error #1034: Type Coercion failed: cannot convert "<tags level="0th IFD TIFF Tags">

                                                      <tag tag_name="Image width" field_name="ImageWidth" id="0x0100" compressed="J">

                                                        <uncompressed chunky="M" planar="M" ycc="M" />

                                                      </tag>

                                                      <tag tag_name="Image height" field_name="ImageLength" id="0x0101" compressed="J">

                                                        <uncompressed chunky="M" planar="M" ycc="M" />

                                                      </tag>

                                                    ......

                                                     


                                                    any sugesstion for this?  I think that is caused by jp.shichiseki librariby that I modified.

                                                     

                                                    Thank you.

                                                     

                                                     

                                                    <*Update*>

                                                     

                                                    sorry I just quick searching and find the answer of this:

                                                     

                                                    http://forums.adobe.com/thread/1170567

                                                     

                                                    "Open jp.shichiseki.exif.Tags.as, and replace line 52 by this :

                                                     

                                                    return XML( levels[level].data );

                                                     

                                                    It forces getSet() function to return a full typed XML, and not just a String formatted as an XML."

                                                     

                                                     

                                                    It's works now !

                                                    I still put here in case someone make the same mistake like me.