This is a sample C# program for reading EXIF orientation and then extracting the pages with the orientation applied.
This sample code is provided as-is, without expressed or implied warranty.
Notes
- This program has dependency on:
- .NET 4.5 or above,
- WindowsBase
- PresentationCore
- LaserficheImaging
- This program was tested on Windows 7. This program depends on components that are provided by Microsoft as part of the Windows platform. Earlier versions of Windows may have platform defects that affect this program's functionality.
- This program is intended to process images before they are uploaded to Laserfiche, or during Quick Fields custom processing.
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media.Imaging;
using Laserfiche.Imaging;
namespace ExifRotateDemo
{
public static class ExifRotateDemoUtility
{
public static void ExtractPageWithRotation(string filename, string outputFolder)
{
string inputFilenamePart = Path.GetFileNameWithoutExtension(filename);
Func<int, string> outputFilenameFunc = (int pageIndex) =>
{
return Path.Combine(outputFolder, inputFilenamePart + "_page" + pageIndex + "_converted.tif");
};
using (MemoryStream strm = new MemoryStream(File.ReadAllBytes(filename), false))
{
if (!IsTiffOrJpeg(strm))
{
string pageOutputFilename = outputFilenameFunc(1);
File.Copy(filename, pageOutputFilename);
}
else
{
var pageOrientations = GetRotationForImagePages(strm);
var pageCodecs = GetImagePageCodecs(strm);
var converter = new LfiImageConverter();
foreach (var pageIndexAndCodec in pageCodecs)
{
int pageIndex = pageIndexAndCodec.Key;
var codec = pageIndexAndCodec.Value;
if (codec == LfiBitmapCodec.Unknown)
{
codec = LfiBitmapCodec.TiffLzw;
}
if (codec == LfiBitmapCodec.FaxGroup3)
{
codec = LfiBitmapCodec.FaxGroup4;
}
int rotation;
if (!pageOrientations.TryGetValue(pageIndex, out rotation))
{
rotation = 0;
}
string pageOutputFilename = outputFilenameFunc(pageIndex + 1);
if (rotation == 0)
{
using (var outStream = File.OpenWrite(pageOutputFilename))
{
converter.ConvertFile(strm, pageIndex, outStream, LfiContainerFormat.Tiff, codec);
}
}
else
{
using (var bitmap = DecodeBitmapPage(strm, pageIndex))
{
using (var rotatedBitmap = new LfiRotatedBitmap(bitmap, rotation, LfiRotationOptions.AllowResize))
{
SaveBitmapPage(pageOutputFilename, rotatedBitmap, codec);
}
}
}
}
}
}
}
/// <summary>
/// Given the EXIF orientation flag value, return the degrees of rotation
/// required to normalize the image to the intended orientation.
/// </summary>
/// <param name="exifOrientationValue"></param>
/// <returns></returns>
static int ExifOrientationToRotationDegrees(int exifOrientationValue)
{
switch (exifOrientationValue)
{
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
default:
throw new ArgumentOutOfRangeException(
"Invalid EXIF orientation value, or an orientation that is flipped which cannot be corrected with rotation alone.");
}
}
static int? TryMetadataGetQueryConvertAsInt(BitmapMetadata metadata, string query)
{
var unknownResult = metadata.GetQuery(query);
if (unknownResult is IConvertible)
{
return (unknownResult as IConvertible).ToInt32(null);
}
return null;
}
static SortedDictionary<int, int> GetRotationForImagePages(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var result = new SortedDictionary<int, int>();
BitmapDecoder decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default);
string query;
if (decoder is TiffBitmapDecoder)
{
query = "/ifd/{ushort=274}";
}
else if (decoder is JpegBitmapDecoder)
{
query = "/app1/ifd/exif:{ushort=274}";
}
else
{
return result;
}
int nextFrame = 0;
foreach (var frame in decoder.Frames)
{
int frameIndex = nextFrame;
nextFrame++;
var unknownMetadata = frame.Metadata;
if (unknownMetadata is BitmapMetadata)
{
var metadata = (unknownMetadata as BitmapMetadata);
var orientation = TryMetadataGetQueryConvertAsInt(metadata, query);
if (orientation.HasValue)
{
int degrees;
try
{
degrees = ExifOrientationToRotationDegrees(orientation.Value);
}
catch
{
continue;
}
result.Add(frameIndex, degrees);
}
}
}
stream.Seek(0, SeekOrigin.Begin);
return result;
}
static bool IsTiffOrJpeg(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
LfiBitmapDecoder lfiDecoder = new LfiBitmapDecoder(stream, BitmapCreateOptions.None);
var container = lfiDecoder.ContainerFormat;
stream.Seek(0, SeekOrigin.Begin);
return (container == LfiContainerFormat.Tiff ||
container == LfiContainerFormat.Jpeg);
}
static SortedDictionary<int, LfiBitmapCodec> GetImagePageCodecs(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
LfiBitmapDecoder lfiDecoder = new LfiBitmapDecoder(stream, BitmapCreateOptions.None);
var result = new SortedDictionary<int, LfiBitmapCodec>();
int pageCount = lfiDecoder.Frames.Count;
for (int pageIndex = 0; pageIndex < pageCount; ++pageIndex)
{
var frame = lfiDecoder.Frames[pageIndex];
result.Add(pageIndex, frame.Codec);
}
stream.Seek(0, SeekOrigin.Begin);
return result;
}
static LfiBitmapSource DecodeBitmapPage(Stream stream, int pageIndex)
{
stream.Seek(0, SeekOrigin.Begin);
LfiBitmapDecoder lfiDecoder = new LfiBitmapDecoder(stream, BitmapCreateOptions.None);
return lfiDecoder.Frames[pageIndex].DecodeBitmap();
}
static void SaveBitmapPage(string outputFilename, LfiBitmapSource bitmap, LfiBitmapCodec codec)
{
LfiBitmapEncoder lfiEncoder = new LfiBitmapEncoder(LfiContainerFormat.Tiff);
lfiEncoder.Overwrite = false;
lfiEncoder.Append = false;
lfiEncoder.Frames.Add(new LfiBitmapFrame(bitmap, codec));
lfiEncoder.Save(outputFilename);
}
}
class Program
{
static void Main(string[] args)
{
ExifRotateDemoUtility.ExtractPageWithRotation(@"inputFilename", @"outputFolder");
}
}
}