blob: 62b6462ac8f2ddbe21b18cdd42d89435683a238c [file] [log] [blame]
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Imaging;
namespace WPCordovaClassLib.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 ), determines 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 beginning
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.ReadBytes(dLen - 2);
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;
}
}
}