Return-Path: X-Original-To: apmail-incubator-callback-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-callback-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 2E2B9D238 for ; Wed, 20 Jun 2012 22:52:53 +0000 (UTC) Received: (qmail 50860 invoked by uid 500); 20 Jun 2012 22:52:52 -0000 Delivered-To: apmail-incubator-callback-commits-archive@incubator.apache.org Received: (qmail 50642 invoked by uid 500); 20 Jun 2012 22:52:51 -0000 Mailing-List: contact callback-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: callback-dev@incubator.apache.org Delivered-To: mailing list callback-commits@incubator.apache.org Received: (qmail 50524 invoked by uid 99); 20 Jun 2012 22:52:48 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 20 Jun 2012 22:52:48 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 823BD12D59; Wed, 20 Jun 2012 22:52:48 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: purplecabbage@apache.org To: callback-commits@incubator.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [5/5] wp7 commit: Capture parses EXIF data and rotates image based on EXIF_Orientation Message-Id: <20120620225248.823BD12D59@tyr.zones.apache.org> Date: Wed, 20 Jun 2012 22:52:48 +0000 (UTC) Capture parses EXIF data and rotates image based on EXIF_Orientation Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/commit/f2703aa4 Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/tree/f2703aa4 Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/diff/f2703aa4 Branch: refs/heads/master Commit: f2703aa49625867dd0f14c425d77666f8c7f779e Parents: c42bbd5 Author: Jesse MacFadyen Authored: Wed Jun 20 15:20:41 2012 -0700 Committer: Jesse MacFadyen Committed: Wed Jun 20 15:20:41 2012 -0700 ---------------------------------------------------------------------- framework/Cordova/Commands/Capture.cs | 33 +++- framework/Cordova/Commands/ImageExifHelper.cs | 194 ++++++++++++++++++++ framework/WP7CordovaClassLib.csproj | 10 +- 3 files changed, 231 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/f2703aa4/framework/Cordova/Commands/Capture.cs ---------------------------------------------------------------------- diff --git a/framework/Cordova/Commands/Capture.cs b/framework/Cordova/Commands/Capture.cs index 8207c68..25d22e3 100644 --- a/framework/Cordova/Commands/Capture.cs +++ b/framework/Cordova/Commands/Capture.cs @@ -25,6 +25,8 @@ using WP7CordovaClassLib.Cordova.UI; using AudioResult = WP7CordovaClassLib.Cordova.UI.AudioCaptureTask.AudioResult; using VideoResult = WP7CordovaClassLib.Cordova.UI.VideoCaptureTask.VideoResult; using System.Windows; +using System.Diagnostics; +using Microsoft.Phone.Controls; namespace WP7CordovaClassLib.Cordova.Commands { @@ -433,6 +435,7 @@ namespace WP7CordovaClassLib.Cordova.Commands } } + /// /// Handles result of capture to save image information /// @@ -458,14 +461,34 @@ namespace WP7CordovaClassLib.Cordova.Commands MediaLibrary library = new MediaLibrary(); Picture image = library.SavePicture(fileName, e.ChosenPhoto); + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 0; + switch (orient) + { + case ImageExifOrientation.LandscapeLeft : + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown : + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight : + newAngle = 270; + break; + case ImageExifOrientation.Portrait : default : break; // 0 default already set + } + + Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); + // Save image in isolated storage // we should return stream position back after saving stream to media library - e.ChosenPhoto.Seek(0, SeekOrigin.Begin); - byte[] imageBytes = new byte[e.ChosenPhoto.Length]; - e.ChosenPhoto.Read(imageBytes, 0, imageBytes.Length); + rotImageStream.Seek(0, SeekOrigin.Begin); + + byte[] imageBytes = new byte[rotImageStream.Length]; + rotImageStream.Read(imageBytes, 0, imageBytes.Length); + rotImageStream.Dispose(); string pathLocalStorage = this.SaveImageToLocalStorage(fileName, isoFolder, imageBytes); - + imageBytes = null; // Get image data MediaFile data = new MediaFile(pathLocalStorage, image); @@ -695,7 +718,7 @@ namespace WP7CordovaClassLib.Cordova.Commands } string filePath = System.IO.Path.Combine("/" + imageFolder + "/", imageFileName); - using (var stream = isoFile.CreateFile(filePath)) + using (IsolatedStorageFileStream stream = isoFile.CreateFile(filePath)) { stream.Write(imageBytes, 0, imageBytes.Length); } http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/f2703aa4/framework/Cordova/Commands/ImageExifHelper.cs ---------------------------------------------------------------------- diff --git a/framework/Cordova/Commands/ImageExifHelper.cs b/framework/Cordova/Commands/ImageExifHelper.cs new file mode 100644 index 0000000..2d7aab8 --- /dev/null +++ b/framework/Cordova/Commands/ImageExifHelper.cs @@ -0,0 +1,194 @@ +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.IO; +using System.Windows.Media.Imaging; +using System.Diagnostics; + +namespace WP7CordovaClassLib.Cordova.Commands +{ + public class ImageExifOrientation + { + public const int Portrait = 1; + public const int PortraitUpsideDown = 3; + public const int LandscapeLeft = 6; + public const int LandscapeRight = 8; + } + + public class ImageExifHelper + { + + public static Stream RotateStream(Stream stream, int angle) + { + stream.Position = 0; + if (angle % 90 != 0 || angle < 0) + { + throw new ArgumentException(); + } + if (angle % 360 == 0) + { + return stream; + } + + angle = angle % 360; + + BitmapImage bitmap = new BitmapImage(); + bitmap.SetSource(stream); + WriteableBitmap wbSource = new WriteableBitmap(bitmap); + + WriteableBitmap wbTarget = null; + + int srcPixelWidth = wbSource.PixelWidth; + int srcPixelHeight = wbSource.PixelHeight; + + if (angle % 180 == 0) + { + wbTarget = new WriteableBitmap(srcPixelWidth, srcPixelHeight); + } + else + { + wbTarget = new WriteableBitmap(srcPixelHeight, srcPixelWidth); + } + + int destPixelWidth = wbTarget.PixelWidth; + int[] srcPxls = wbSource.Pixels; + int[] destPxls = wbTarget.Pixels; + + // this ugly if/else is to avoid a conditional check for every pixel + if (angle == 90) { + for (int x = 0; x < srcPixelWidth; x++) { + for (int y = 0; y < srcPixelHeight; y++) { + destPxls[(srcPixelHeight - y - 1) + (x * destPixelWidth)] = srcPxls[x + y * srcPixelWidth]; + } + } + } + else if (angle == 180) { + for (int x = 0; x < srcPixelWidth; x++) { + for (int y = 0; y < srcPixelHeight; y++) { + destPxls[(srcPixelWidth - x - 1) + (srcPixelHeight - y - 1) * srcPixelWidth] = srcPxls[x + y * srcPixelWidth]; + } + } + } + else if (angle == 270) { + for (int x = 0; x < srcPixelWidth; x++) { + for (int y = 0; y < srcPixelHeight; y++) { + destPxls[y + (srcPixelWidth - x - 1) * destPixelWidth] = srcPxls[x + y * srcPixelWidth]; + } + } + } + + MemoryStream targetStream = new MemoryStream(); + wbTarget.SaveJpeg(targetStream, destPixelWidth, wbTarget.PixelHeight, 0, 100); + return targetStream; + } + + public static int getImageOrientationFromStream(Stream imgStream) + { + + // 0xFFD8 : jpgHeader + // 0xFFE1 : + // 0x???? : length of exif data + // 0x????, 0x???? : Chars 'E','x','i','f' + // 0x0000 : 2 empty bytes + // <== mark beginning of tags SIZE:ID:VALUE + // 0x???? : 'II' or 'MM' for Intel or Motorola ( always getting II on my WP7 devices ), determins littleEndian-ness + // 0x002A : marker value + // 0x???? : offset to the Image File Data + + // XXXX possible space before actual tag data ... we skip to mark + offset + + // 0x???? number of exif tags present + + // make sure we are at the begining + imgStream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(imgStream); + + byte[] jpgHdr = reader.ReadBytes(2); // always (0xFFD8) + + byte start = reader.ReadByte(); // 0xFF + byte index = reader.ReadByte(); // 0xE1 + + while (start == 0xFF && index != 0xE1) // This never seems to happen, todo: optimize + { + // Get the data length + ushort dLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + // skip along + reader.BaseStream.Seek(dLen - 2, SeekOrigin.Current); + start = reader.ReadByte(); + index = reader.ReadByte(); + } + + // It's only success if we found the 0xFFE1 marker + if (start != 0xFF || index != 0xE1) + { + // throw new Exception("Could not find Exif data block"); + Debug.WriteLine("Did not find EXIF data"); + return 0; + } + + // read 2 byte length of EXIF data + ushort exifLen = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + String exif = ""; // build the string + for (var n = 0; n < 4; n++) + { + exif += reader.ReadChar(); + } + if (exif != "Exif") + { + // did not find exif data ... + Debug.WriteLine("Did not find EXIF data"); + return 0; + } + + // read 2 empty bytes + //ushort emptyBytes = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + reader.ReadBytes(2); + + long headerMark = reader.BaseStream.Position; // where are we now <== + + //bool isLEndian = (reader.ReadChar() + "" + reader.ReadChar()) == "II"; + reader.ReadBytes(2); // 'II' or 'MM', but we don't care + + if (0x002A != BitConverter.ToUInt16(reader.ReadBytes(2), 0)) + { + Debug.WriteLine("Error in data != 0x002A"); + return 0; + } + + // Get the offset to the IFD (image file directory) + ushort imgOffset = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + + imgStream.Position = headerMark + imgOffset; + ushort tagCount = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + for (ushort x = 0; x < tagCount; x++) + { + // Orientation = 0x112, aka 274 + if (0x112 == BitConverter.ToUInt16(reader.ReadBytes(2), 0)) + { + ushort dType = BitConverter.ToUInt16(reader.ReadBytes(2), 0); + // don't care .. + uint comps = reader.ReadUInt32(); + byte[] tagData = reader.ReadBytes(4); + int orientation = (int)tagData[0]; + Debug.WriteLine("orientation = " + orientation.ToString()); + return orientation; + // 6 means rotate clockwise 90 deg + // 8 means rotate counter-clockwise 90 deg + // 1 means all is good + // 3 means flip vertical + } + // skip to the next item, 12 bytes each + reader.BaseStream.Seek(10, SeekOrigin.Current); + } + return 0; + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-cordova-wp7/blob/f2703aa4/framework/WP7CordovaClassLib.csproj ---------------------------------------------------------------------- diff --git a/framework/WP7CordovaClassLib.csproj b/framework/WP7CordovaClassLib.csproj index a84272e..b02eee4 100644 --- a/framework/WP7CordovaClassLib.csproj +++ b/framework/WP7CordovaClassLib.csproj @@ -96,6 +96,7 @@ Code + Code @@ -121,6 +122,9 @@ AudioRecorder.xaml + + ImageCapture.xaml + NotificationBox.xaml @@ -142,6 +146,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -196,4 +204,4 @@ --> - + \ No newline at end of file