Skip to content
This repository has been archived by the owner on Apr 18, 2019. It is now read-only.

Commit

Permalink
Capture parses EXIF data and rotates image based on EXIF_Orientation
Browse files Browse the repository at this point in the history
  • Loading branch information
purplecabbage committed Jun 20, 2012
1 parent c42bbd5 commit f2703aa
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 6 deletions.
33 changes: 28 additions & 5 deletions framework/Cordova/Commands/Capture.cs
Expand Up @@ -25,6 +25,8 @@
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
{
Expand Down Expand Up @@ -433,6 +435,7 @@ public void play(string options)
}
}


/// <summary>
/// Handles result of capture to save image information
/// </summary>
Expand All @@ -458,14 +461,34 @@ private void cameraTask_Completed(object sender, PhotoResult e)
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);

Expand Down Expand Up @@ -695,7 +718,7 @@ private string SaveImageToLocalStorage(string imageFileName, string imageFolder,
}
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);
}
Expand Down
194 changes: 194 additions & 0 deletions 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;
}

}
}
10 changes: 9 additions & 1 deletion framework/WP7CordovaClassLib.csproj
Expand Up @@ -96,6 +96,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Cordova\Commands\GeoLocation.cs" />
<Compile Include="Cordova\Commands\ImageExifHelper.cs" />
<Compile Include="Cordova\Commands\Media.cs">
<SubType>Code</SubType>
</Compile>
Expand All @@ -121,6 +122,9 @@
<Compile Include="Cordova\UI\AudioRecorder.xaml.cs">
<DependentUpon>AudioRecorder.xaml</DependentUpon>
</Compile>
<Compile Include="Cordova\UI\ImageCapture.xaml.cs">
<DependentUpon>ImageCapture.xaml</DependentUpon>
</Compile>
<Compile Include="Cordova\UI\NotificationBox.xaml.cs">
<DependentUpon>NotificationBox.xaml</DependentUpon>
</Compile>
Expand All @@ -142,6 +146,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Cordova\UI\ImageCapture.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Cordova\UI\NotificationBox.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -196,4 +204,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

0 comments on commit f2703aa

Please sign in to comment.