|
using System;
using System.Text;
using System.Drawing;
using System.IO;
namespace ExifWorks
{
//
// Utility class for working with EXIF data in images. Provides abstraction
// for most common data and generic utilities for work with all other.
//
//
// Copyright (c) Michal A. Valášek - Altair Communications, 2003-2005
// Copmany: http://software.altaircom.net, E-mail: support@altaircom.net
// Private: http://www.rider.cz, E-mail: rider@rider.cz
// This is free software licensed under GNU Lesser General Public License
//
//
// [altair] 10.09.2003 Created
// [altair] 12.06.2004 Added capability to write EXIF data
// [altair] 11.07.2004 Added option to change encoding
// [altair] 04.09.2005 Changed source of Width and Height properties from EXIF to image
// [altair] 05.09.2005 Code clean-up and minor changes
// [marco.ridoni@virgilio.it] 02-11-2006 C# translation
//
public class ExifManager : IDisposable
{
private FileStream _Stream;
private Bitmap _Image;
private Encoding _Encoding = Encoding.UTF8;
#region Type declarations
//
// Contains possible values of EXIF tag names (ID)
//
// See GdiPlusImaging.h
//
// [altair] 10.09.2003 Created
//
public enum TagNames
{
ExifIFD = 0x8769,
GpsIFD = 0x8825,
NewSubfileType = 0xFE,
SubfileType = 0xFF,
ImageWidth = 0x100,
ImageHeight = 0x101,
BitsPerSample = 0x102,
Compression = 0x103,
PhotometricInterp = 0x106,
ThreshHolding = 0x107,
CellWidth = 0x108,
CellHeight = 0x109,
FillOrder = 0x10A,
DocumentName = 0x10D,
ImageDescription = 0x10E,
EquipMake = 0x10F,
EquipModel = 0x110,
StripOffsets = 0x111,
Orientation = 0x112,
SamplesPerPixel = 0x115,
RowsPerStrip = 0x116,
StripBytesCount = 0x117,
MinSampleValue = 0x118,
MaxSampleValue = 0x119,
XResolution = 0x11A,
YResolution = 0x11B,
PlanarConfig = 0x11C,
PageName = 0x11D,
XPosition = 0x11E,
YPosition = 0x11F,
FreeOffset = 0x120,
FreeByteCounts = 0x121,
GrayResponseUnit = 0x122,
GrayResponseCurve = 0x123,
T4Option = 0x124,
T6Option = 0x125,
ResolutionUnit = 0x128,
PageNumber = 0x129,
TransferFuncition = 0x12D,
SoftwareUsed = 0x131,
DateTime = 0x132,
Artist = 0x13B,
HostComputer = 0x13C,
Predictor = 0x13D,
WhitePoint = 0x13E,
PrimaryChromaticities = 0x13F,
ColorMap = 0x140,
HalftoneHints = 0x141,
TileWidth = 0x142,
TileLength = 0x143,
TileOffset = 0x144,
TileByteCounts = 0x145,
InkSet = 0x14C,
InkNames = 0x14D,
NumberOfInks = 0x14E,
DotRange = 0x150,
TargetPrinter = 0x151,
ExtraSamples = 0x152,
SampleFormat = 0x153,
SMinSampleValue = 0x154,
SMaxSampleValue = 0x155,
TransferRange = 0x156,
JPEGProc = 0x200,
JPEGInterFormat = 0x201,
JPEGInterLength = 0x202,
JPEGRestartInterval = 0x203,
JPEGLosslessPredictors = 0x205,
JPEGPointTransforms = 0x206,
JPEGQTables = 0x207,
JPEGDCTables = 0x208,
JPEGACTables = 0x209,
YCbCrCoefficients = 0x211,
YCbCrSubsampling = 0x212,
YCbCrPositioning = 0x213,
REFBlackWhite = 0x214,
ICCProfile = 0x8773,
Gamma = 0x301,
ICCProfileDescriptor = 0x302,
SRGBRenderingIntent = 0x303,
ImageTitle = 0x320,
Copyright = 0x8298,
ResolutionXUnit = 0x5001,
ResolutionYUnit = 0x5002,
ResolutionXLengthUnit = 0x5003,
ResolutionYLengthUnit = 0x5004,
PrintFlags = 0x5005,
PrintFlagsVersion = 0x5006,
PrintFlagsCrop = 0x5007,
PrintFlagsBleedWidth = 0x5008,
PrintFlagsBleedWidthScale = 0x5009,
HalftoneLPI = 0x500A,
HalftoneLPIUnit = 0x500B,
HalftoneDegree = 0x500C,
HalftoneShape = 0x500D,
HalftoneMisc = 0x500E,
HalftoneScreen = 0x500F,
JPEGQuality = 0x5010,
GridSize = 0x5011,
ThumbnailFormat = 0x5012,
ThumbnailWidth = 0x5013,
ThumbnailHeight = 0x5014,
ThumbnailColorDepth = 0x5015,
ThumbnailPlanes = 0x5016,
ThumbnailRawBytes = 0x5017,
ThumbnailSize = 0x5018,
ThumbnailCompressedSize = 0x5019,
ColorTransferFunction = 0x501A,
ThumbnailData = 0x501B,
ThumbnailImageWidth = 0x5020,
ThumbnailImageHeight = 0x502,
ThumbnailBitsPerSample = 0x5022,
ThumbnailCompression = 0x5023,
ThumbnailPhotometricInterp = 0x5024,
ThumbnailImageDescription = 0x5025,
ThumbnailEquipMake = 0x5026,
ThumbnailEquipModel = 0x5027,
ThumbnailStripOffsets = 0x5028,
ThumbnailOrientation = 0x5029,
ThumbnailSamplesPerPixel = 0x502A,
ThumbnailRowsPerStrip = 0x502B,
ThumbnailStripBytesCount = 0x502C,
ThumbnailResolutionX = 0x502D,
ThumbnailResolutionY = 0x502E,
ThumbnailPlanarConfig = 0x502F,
ThumbnailResolutionUnit = 0x5030,
ThumbnailTransferFunction = 0x5031,
ThumbnailSoftwareUsed = 0x5032,
ThumbnailDateTime = 0x5033,
ThumbnailArtist = 0x5034,
ThumbnailWhitePoint = 0x5035,
ThumbnailPrimaryChromaticities = 0x5036,
ThumbnailYCbCrCoefficients = 0x5037,
ThumbnailYCbCrSubsampling = 0x5038,
ThumbnailYCbCrPositioning = 0x5039,
ThumbnailRefBlackWhite = 0x503A,
ThumbnailCopyRight = 0x503B,
LuminanceTable = 0x5090,
ChrominanceTable = 0x5091,
FrameDelay = 0x5100,
LoopCount = 0x5101,
PixelUnit = 0x5110,
PixelPerUnitX = 0x5111,
PixelPerUnitY = 0x5112,
PaletteHistogram = 0x5113,
ExifExposureTime = 0x829A,
ExifFNumber = 0x829D,
ExifExposureProg = 0x8822,
ExifSpectralSense = 0x8824,
ExifISOSpeed = 0x8827,
ExifOECF = 0x8828,
ExifVer = 0x9000,
ExifDTOrig = 0x9003,
ExifDTDigitized = 0x9004,
ExifCompConfig = 0x9101,
ExifCompBPP = 0x9102,
ExifShutterSpeed = 0x9201,
ExifAperture = 0x9202,
ExifBrightness = 0x9203,
ExifExposureBias = 0x9204,
ExifMaxAperture = 0x9205,
ExifSubjectDist = 0x9206,
ExifMeteringMode = 0x9207,
ExifLightSource = 0x9208,
ExifFlash = 0x9209,
ExifFocalLength = 0x920A,
ExifMakerNote = 0x927C,
ExifUserComment = 0x9286,
ExifDTSubsec = 0x9290,
ExifDTOrigSS = 0x9291,
ExifDTDigSS = 0x9292,
ExifFPXVer = 0xA000,
ExifColorSpace = 0xA001,
ExifPixXDim = 0xA002,
ExifPixYDim = 0xA003,
ExifRelatedWav = 0xA004,
ExifInterop = 0xA005,
ExifFlashEnergy = 0xA20B,
ExifSpatialFR = 0xA20C,
ExifFocalXRes = 0xA20E,
ExifFocalYRes = 0xA20F,
ExifFocalResUnit = 0xA210,
ExifSubjectLoc = 0xA214,
ExifExposureIndex = 0xA215,
ExifSensingMethod = 0xA217,
ExifFileSource = 0xA300,
ExifSceneType = 0xA301,
ExifCfaPattern = 0xA302,
GpsVer = 0x0,
GpsLatitudeRef = 0x1,
GpsLatitude = 0x2,
GpsLongitudeRef = 0x3,
GpsLongitude = 0x4,
GpsAltitudeRef = 0x5,
GpsAltitude = 0x6,
GpsGpsTime = 0x7,
GpsGpsSatellites = 0x8,
GpsGpsStatus = 0x9,
GpsGpsMeasureMode = 0xA,
GpsGpsDop = 0xB,
GpsSpeedRef = 0xC,
GpsSpeed = 0xD,
GpsTrackRef = 0xE,
GpsTrack = 0xF,
GpsImgDirRef = 0x10,
GpsImgDir = 0x11,
GpsMapDatum = 0x12,
GpsDestLatRef = 0x13,
GpsDestLat = 0x14,
GpsDestLongRef = 0x15,
GpsDestLong = 0x16,
GpsDestBearRef = 0x17,
GpsDestBear = 0x18,
GpsDestDistRef = 0x19,
GpsDestDist = 0x1A
}
//
// Real position of 0th row and column of picture
//
//
//
// [altair] 10.09.2003 Created
//
public enum Orientations
{
TopLeft = 1,
TopRight = 2,
BottomRight = 3,
BottomLeft = 4,
LeftTop = 5,
RightTop = 6,
RightBottom = 7,
LftBottom = 8
}
//
// Exposure programs
//
//
//
// [altair] 10.09.2003 Created
//
public enum ExposurePrograms
{
Manual = 1,
Normal = 2,
AperturePriority = 3,
ShutterPriority = 4,
Creative = 5,
Action = 6,
Portrait = 7,
Landscape = 8,
}
//
// Exposure metering modes
//
//
//
// [altair] 10.09.2003 Created
//
public enum ExposureMeteringModes
{
Unknown = 0,
Average = 1,
CenterWeightedAverage = 2,
Spot = 3,
MultiSpot = 4,
MultiSegment = 5,
Partial = 6,
Other = 255
}
//
// Flash activity modes
//
//
//
// [altair] 10.09.2003 Created
//
public enum FlashModes
{
NotFired = 0,
Fired = 1,
FiredButNoStrobeReturned = 5,
FiredAndStrobeReturned = 7,
}
//
// Possible light sources (white balance)
//
//
//
// [altair] 10.09.2003 Created
//
public enum LightSources
{
Unknown = 0,
Daylight = 1,
Fluorescent = 2,
Tungsten = 3,
Flash = 10,
StandardLightA = 17,
StandardLightB = 18,
StandardLightC = 19,
D55 = 20,
D65 = 21,
D75 = 22,
Other = 255
}
//
// EXIF data types
//
//
//
// [altair] 12.6.2004 Created
//
public enum ExifDataTypes : short
{
UnsignedByte = 1,
AsciiString = 2,
UnsignedShort = 3,
UnsignedLong = 4,
UnsignedRational = 5,
SignedByte = 6,
Undefined = 7,
SignedShort = 8,
SignedLong = 9,
SignedRational = 10,
SingleFloat = 11,
DoubleFloat = 12
}
//
// Represents rational which is type of some Exif properties
//
//
//
// [altair] 10.09.2003 Created
//
public struct Rational
{
public Int32 Numerator;
public Int32 Denominator;
//
// Converts rational to string representation
//
// Optional, default "/". String to be used as delimiter of components.
// String representation of the rational.
//
//
// [altair] 10.09.2003 Created
//
public override string ToString()
{
return ToString("/");
}
public string ToString(string Delimiter)
{
return Numerator + "/" + Denominator;
}
//
// Converts rational to double precision real number
//
// The rational as double precision real number.
//
//
// [altair] 10.09.2003 Created
//
public double ToDouble()
{
return (double)Numerator / Denominator;
}
}
#endregion
//
// Initializes new instance of this class.
//
// Bitmap to read exif information from
//
//
// [altair] 10.09.2003 Created
//
public ExifManager(Bitmap Bitmap)
{
if (Bitmap == null)
throw new ArgumentNullException("Bitmap");
_Image = Bitmap;
}
//
// Initializes new instance of this class.
//
// Name of file to be loaded
//
//
// [altair] 13.06.2004 Created
//
public ExifManager(string FileName)
{
if (FileName == null)
throw new ArgumentNullException("FileName");
_Stream = new FileStream(FileName, FileMode.Open, FileAccess.Read);
_Image = (Bitmap)Image.FromStream(_Stream, true, /*avoid loading here!*/ false);
// _Image = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(FileName);
}
//
// Get or set encoding used for string metadata
//
// Encoding used for string metadata
// Default encoding is UTF-8
//
// [altair] 11.07.2004 Created
// [altair] 05.09.2005 Changed from shared to instance member
//
public Encoding Encoding
{
get
{
return _Encoding;
}
set
{
if (value == null)
throw new ArgumentNullException();
_Encoding = value;
}
}
//
// Returns copy of bitmap this instance is working on
//
//
//
//
// [altair] 13.06.2004 Created
//
public Bitmap GetBitmap()
{
return (Bitmap)_Image.Clone();
}
//
// Returns all available data in formatted string form
//
//
//
//
// [altair] 10.09.2003 Created
//
public override string ToString()
{
StringBuilder SB = new StringBuilder();
SB.Append("Image:");
SB.Append("\n\tDimensions: " + Width + " x " + Height + " px");
SB.Append("\n\tResolution: " + ResolutionX + " x " + ResolutionY + " dpi");
SB.Append("\n\tOrientation: " + Enum.GetName(typeof(Orientations), Orientation));
SB.Append("\n\tTitle: " + Title);
SB.Append("\n\tDescription: " + Description);
SB.Append("\n\tCopyright: " + Copyright);
SB.Append("\nEquipment:");
SB.Append("\n\tMaker: " + EquipmentMaker);
SB.Append("\n\tModel: " + EquipmentModel);
SB.Append("\n\tSoftware: " + Software);
SB.Append("\nDate and time:");
SB.Append("\n\tGeneral: " + DateTimeLastModified.ToString());
SB.Append("\n\tOriginal: " + DateTimeOriginal.ToString());
SB.Append("\n\tDigitized: " + DateTimeDigitized.ToString());
SB.Append("\nShooting conditions:");
SB.Append("\n\tExposure time: " + ExposureTime.ToString("N4") + " s");
SB.Append("\n\tExposure program: " + Enum.GetName(typeof(ExposurePrograms), ExposureProgram));
SB.Append("\n\tExposure mode: " + Enum.GetName(typeof(ExposureMeteringModes), ExposureMeteringMode));
SB.Append("\n\tAperture: F" + Aperture.ToString("N2"));
SB.Append("\n\tISO sensitivity: " + ISO);
SB.Append("\n\tSubject distance: " + SubjectDistance.ToString("N2") + " m");
SB.Append("\n\tFocal length: " + FocalLength);
SB.Append("\n\tFlash: " + Enum.GetName(typeof(FlashModes), FlashMode));
SB.Append("\n\tLight source (WB): " + Enum.GetName(typeof(LightSources), LightSource));
//SB.Replace("\n", vbCrLf);
//SB.Replace("\t", vbTab);
return SB.ToString();
}
#region Nicely formatted well-known properties
//
// Brand of equipment (EXIF EquipMake)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string EquipmentMaker
{
get
{
return GetPropertyString(TagNames.EquipMake);
}
}
//
// Model of equipment (EXIF EquipModel)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string EquipmentModel
{
get
{
return GetPropertyString(TagNames.EquipModel);
}
}
//
// Software used for processing (EXIF Software)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string Software
{
get
{
return GetPropertyString(TagNames.SoftwareUsed);
}
}
//
// Orientation of image (position of row 0, column 0) (EXIF Orientation)
//
//
//
//
// [altair] 10.09.2003 Created
//
public Orientations Orientation
{
get
{
Int32 X = GetPropertyInt16(TagNames.Orientation);
if (!Enum.IsDefined(typeof(Orientations), X))
return Orientations.TopLeft;
else
return (Orientations)Enum.Parse(typeof(Orientations), Enum.GetName(typeof(Orientations), X));
}
}
//
// Time when image was last modified (EXIF DateTime).
//
//
//
//
// [altair] 10.09.2003 Created
//
public DateTime DateTimeLastModified
{
get
{
try
{
return DateTime.ParseExact(GetPropertyString(TagNames.DateTime), @"yyyy\:MM\:dd HH\:mm\:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
SetPropertyString((int)TagNames.DateTime, value.ToString(@"yyyy\:MM\:dd HH\:mm\:ss"));
}
catch
{ }
}
}
//
// Time when image was taken (EXIF DateTimeOriginal).
//
//
//
//
// [altair] 10.09.2003 Created
//
public DateTime DateTimeOriginal
{
get
{
try
{
return DateTime.ParseExact(GetPropertyString(TagNames.ExifDTOrig), @"yyyy\:MM\:dd HH\:mm\:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
SetPropertyString((int)TagNames.ExifDTOrig, value.ToString(@"yyyy\:MM\:dd HH\:mm\:ss"));
}
catch
{ }
}
}
//
// Time when image was digitized (EXIF DateTimeDigitized).
//
//
//
//
// [altair] 10.09.2003 Created
//
public DateTime DateTimeDigitized
{
get
{
try
{
return DateTime.ParseExact(GetPropertyString(TagNames.ExifDTDigitized), @"yyyy\:MM\:dd HH\:mm\:ss", null);
}
catch
{
return DateTime.MinValue;
}
}
set
{
try
{
SetPropertyString((int)TagNames.ExifDTDigitized, value.ToString(@"yyyy\:MM\:dd HH\:mm\:ss"));
}
catch
{ }
}
}
//
// Image width
//
//
//
//
// [altair] 10.09.2003 Created
// [altair] 04.09.2005 Changed output to Int32, load from image instead of EXIF
//
public Int32 Width
{
get { return _Image.Width; }
}
//
// Image height
//
//
//
//
// [altair] 10.09.2003 Created
// [altair] 04.09.2005 Changed output to Int32, load from image instead of EXIF
//
public Int32 Height
{
get { return _Image.Height; }
}
//
// X resolution in dpi (EXIF XResolution/ResolutionUnit)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double ResolutionX
{
get
{
double R = GetPropertyRational(TagNames.XResolution).ToDouble();
if (GetPropertyInt16(TagNames.ResolutionUnit) == 3)
{
// -- resolution is in points/cm
return R * 2.54;
}
else
{
// -- resolution is in points/inch
return R;
}
}
}
//
// Y resolution in dpi (EXIF YResolution/ResolutionUnit)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double ResolutionY
{
get
{
double R = GetPropertyRational(TagNames.YResolution).ToDouble();
if (GetPropertyInt16(TagNames.ResolutionUnit) == 3)
{
// -- resolution is in points/cm
return R * 2.54;
}
else
{
// -- resolution is in points/inch
return R;
}
}
}
//
// Image title (EXIF ImageTitle)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string Title
{
get
{
return GetPropertyString(TagNames.ImageTitle);
}
set
{
try
{
SetPropertyString((int)TagNames.ImageTitle, value);
}
catch { }
}
}
//
// User comment (EXIF UserComment)
//
//
//
//
// [altair] 13.06.2004 Created
//
public string UserComment
{
get
{
return GetPropertyString(TagNames.ExifUserComment);
}
set
{
try
{
SetPropertyString((int)TagNames.ExifUserComment, value);
}
catch { }
}
}
//
// Artist name (EXIF Artist)
//
//
//
//
// [altair] 13.06.2004 Created
//
public string Artist
{
get
{
return GetPropertyString(TagNames.Artist);
}
set
{
try
{
SetPropertyString((int)TagNames.Artist, value);
}
catch { }
}
}
//
// Image description (EXIF ImageDescription)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string Description
{
get
{
return GetPropertyString(TagNames.ImageDescription);
}
set
{
try
{
SetPropertyString((int)TagNames.ImageDescription, value);
}
catch { }
}
}
//
// Image copyright (EXIF Copyright)
//
//
//
//
// [altair] 10.09.2003 Created
//
public string Copyright
{
get
{
return GetPropertyString(TagNames.Copyright);
}
set
{
try
{
SetPropertyString((int)TagNames.Copyright, value);
}
catch { }
}
}
//
// Exposure time in seconds (EXIF ExifExposureTime/ExifShutterSpeed)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double ExposureTimeAbs
{
get
{
if (IsPropertyDefined(TagNames.ExifExposureTime))
// -- Exposure time is explicitly specified
return GetPropertyRational(TagNames.ExifExposureTime).ToDouble();
else
if (IsPropertyDefined(TagNames.ExifShutterSpeed))
//'-- Compute exposure time from shutter spee
return (1 / Math.Pow(2, GetPropertyRational(TagNames.ExifShutterSpeed).ToDouble()));
else
// -- Can't figure out
return 0;
}
}
public Rational ExposureTime
{
get
{
if (IsPropertyDefined(TagNames.ExifExposureTime))
// -- Exposure time is explicitly specified
return GetPropertyRational(TagNames.ExifExposureTime);
else
return new Rational();
}
}
//
// Aperture value as F number (EXIF ExifFNumber/ExifApertureValue)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double Aperture
{
get
{
if (IsPropertyDefined(TagNames.ExifFNumber))
return GetPropertyRational(TagNames.ExifFNumber).ToDouble();
else
if (IsPropertyDefined(TagNames.ExifAperture))
return Math.Pow(System.Math.Sqrt(2), GetPropertyRational(TagNames.ExifAperture).ToDouble());
else
return 0;
}
}
//
// Exposure program used (EXIF ExifExposureProg)
//
//
// If not specified, returns Normal (2)
//
// [altair] 10.09.2003 Created
//
public ExposurePrograms ExposureProgram
{
get
{
Int32 X = GetPropertyInt16(TagNames.ExifExposureProg);
if (Enum.IsDefined(typeof(ExposurePrograms), X))
return (ExposurePrograms)Enum.Parse(typeof(ExposurePrograms), Enum.GetName(typeof(ExposurePrograms), X));
else
return ExposurePrograms.Normal;
}
}
//
// ISO sensitivity
//
//
//
//
// [altair] 10.09.2003 Created
//
public Int16 ISO
{
get { return GetPropertyInt16(TagNames.ExifISOSpeed); }
}
//
// Subject distance in meters (EXIF SubjectDistance)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double SubjectDistance
{
get { return GetPropertyRational(TagNames.ExifSubjectDist).ToDouble(); }
}
//
// Exposure method metering mode used (EXIF MeteringMode)
//
//
// If not specified, returns Unknown (0)
//
// [altair] 10.09.2003 Created
//
public ExposureMeteringModes ExposureMeteringMode
{
get
{
Int32 X = GetPropertyInt16(TagNames.ExifMeteringMode);
if (Enum.IsDefined(typeof(ExposureMeteringModes), X))
return (ExposureMeteringModes)Enum.Parse(typeof(ExposureMeteringModes), Enum.GetName(typeof(ExposureMeteringModes), X));
else
return ExposureMeteringModes.Unknown;
}
}
//
// Focal length of lenses in mm (EXIF FocalLength)
//
//
//
//
// [altair] 10.09.2003 Created
//
public double FocalLength
{
get { return GetPropertyRational(TagNames.ExifFocalLength).ToDouble(); }
}
//
// Flash mode (EXIF Flash)
//
//
// If not present, value NotFired (0) is returned
//
// [altair] 10.09.2003 Created
//
public FlashModes FlashMode
{
get
{
Int32 X = GetPropertyInt16(TagNames.ExifFlash);
if (Enum.IsDefined(typeof(FlashModes), X))
return (FlashModes)Enum.Parse(typeof(FlashModes), Enum.GetName(typeof(FlashModes), X));
else
return FlashModes.NotFired;
}
}
//
// Light source / white balance (EXIF LightSource)
//
//
// If not specified, returns Unknown (0).
//
// [altair] 10.09.2003 Created
//
public LightSources LightSource
{
get
{
Int32 X = GetPropertyInt16(TagNames.ExifLightSource);
if (Enum.IsDefined(typeof(LightSources), X))
return (LightSources)Enum.Parse(typeof(LightSources), Enum.GetName(typeof(LightSources), X));
else
return LightSources.Unknown;
}
}
#endregion
#region Support methods for working with EXIF properties
//
// Checks if current image has specified certain property
//
//
// True if image has specified property, False otherwise.
//
//
// [altair] 10.09.2003 Created
//
public bool IsPropertyDefined(TagNames PID)
{
return (Array.IndexOf(_Image.PropertyIdList, (int)PID) > -1);
}
//
// Gets specified Int32 property
//
// Property ID
// Optional, default 0. Default value returned if property is not present.
// Value of property or DefaultValue if property is not present.
//
//
// [altair] 10.09.2003 Created
//
public Int32 GetPropertyInt32(TagNames PID)
{
return GetPropertyInt32(PID, 0);
}
public Int32 GetPropertyInt32(TagNames PID, Int32 DefaultValue)
{
if (IsPropertyDefined(PID))
return GetInt32(_Image.GetPropertyItem((int)PID).Value);
else
return DefaultValue;
}
//
// Gets specified Int16 property
//
// Property ID
// Optional, default 0. Default value returned if property is not present.
// Value of property or DefaultValue if property is not present.
//
//
// [altair] 10.09.2003 Created
//
public Int16 GetPropertyInt16(TagNames PID)
{
return GetPropertyInt16(PID, 0);
}
public Int16 GetPropertyInt16(TagNames PID, Int16 DefaultValue)
{
if (IsPropertyDefined(PID))
return GetInt16(_Image.GetPropertyItem((int)PID).Value);
else
return DefaultValue;
}
//
// Gets specified string property
//
// Property ID
// Optional, default String.Empty. Default value returned if property is not present.
//
// Value of property or DefaultValue if property is not present.
//
// [altair] 10.09.2003 Created
//
public string GetPropertyString(TagNames PID)
{
return GetPropertyString(PID, "");
}
public string GetPropertyString(TagNames PID, string DefaultValue)
{
if (IsPropertyDefined(PID))
return GetString(_Image.GetPropertyItem((int)PID).Value);
else
return DefaultValue;
}
//
// Gets specified property in raw form
//
// Property ID
// Optional, default Nothing. Default value returned if property is not present.
//
// Is recommended to use typed methods (like etc.) instead, when possible.
//
// [altair] 05.09.2005 Created
//
public byte[] GetProperty(TagNames PID, byte[] DefaultValue)
{
if (IsPropertyDefined(PID))
return _Image.GetPropertyItem((int)PID).Value;
else
return DefaultValue;
}
public byte[] GetProperty(TagNames PID)
{
return GetProperty(PID, null);
}
//
// Gets specified rational property
//
// Property ID
//
// Value of property or 0/1 if not present.
//
// [altair] 10.09.2003 Created
//
public Rational GetPropertyRational(TagNames PID)
{
if (IsPropertyDefined(PID))
return GetRational(_Image.GetPropertyItem((int)PID).Value);
else
{
Rational R;
R.Numerator = 0;
R.Denominator = 1;
return R;
}
}
//
// Sets specified string property
//
// Property ID
// Value to be set
//
//
// [altair] 12.6.2004 Created
//
public void SetPropertyString(Int32 PID, string Value)
{
byte[] Data = _Encoding.GetBytes(Value + '\0');
SetProperty(PID, Data, ExifDataTypes.AsciiString);
}
//
// Sets specified Int16 property
//
// Property ID
// Value to be set
//
//
// [altair] 12.6.2004 Created
//
public void SetPropertyInt16(Int32 PID, Int16 Value)
{
byte[] Data = new byte[2];
Data[0] = (byte)(Value & 0xFF);
Data[1] = (byte)((Value & 0xFF00) >> 8);
SetProperty(PID, Data, ExifDataTypes.SignedShort);
}
//
// Sets specified Int32 property
//
// Property ID
// Value to be set
//
//
// [altair] 13.06.2004 Created
//
public void SetPropertyInt32(Int32 PID, Int32 Value)
{
byte[] Data = new byte[4];
for (int I = 0; I < 4; I++)
{
Data[I] = (byte)(Value & 0xFF);
Value >>= 8;
}
SetProperty(PID, Data, ExifDataTypes.SignedLong);
}
//
// Sets specified property in raw form
//
// Property ID
// Raw data
// EXIF data type
// Is recommended to use typed methods (like etc.) instead, when possible.
//
// [altair] 12.6.2004 Created
//
public void SetProperty(Int32 PID, byte[] Data, ExifDataTypes Type)
{
System.Drawing.Imaging.PropertyItem P = _Image.PropertyItems[0];
P.Id = PID;
P.Value = Data;
P.Type = (Int16)Type;
P.Len = Data.Length;
_Image.SetPropertyItem(P);
}
//
// Reads Int32 from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// [altair] 10.09.2003 Created
// [altair] 05.09.2005 Changed from public shared to private instance method
//
private static Int32 GetInt32(byte[] B)
{
if (B.Length < 4)
throw new ArgumentException("Data too short (4 bytes expected)", "B");
return B[3] << 24 | B[2] << 16 | B[1] << 8 | B[0];
}
//
// Reads Int16 from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// [altair] 10.09.2003 Created
// [altair] 05.09.2005 Changed from public shared to private instance method
//
private static Int16 GetInt16(byte[] B)
{
if (B.Length < 2)
throw new ArgumentException("Data too short (2 bytes expected)", "B");
return (short)(B[1] << 8 | B[0]);
}
//
// Reads string from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// [altair] 10.09.2003 Created
// [altair] 05.09.2005 Changed from public shared to private instance method
//
private string GetString(byte[] B)
{
string R = _Encoding.GetString(B);
if (R.EndsWith("\0"))
R = R.Substring(0, R.Length - 1);
return R;
}
//
// Reads rational from EXIF bytearray.
//
// EXIF bytearray to process
//
//
//
// [altair] 10.09.2003 Created
// [altair] 05.09.2005 Changed from public shared to private instance method
//
private static Rational GetRational(byte[] B)
{
Rational R = new Rational();
byte[] N = new byte[4];
byte[] D = new byte[4];
Array.Copy(B, 0, N, 0, 4);
Array.Copy(B, 4, D, 0, 4);
R.Denominator = GetInt32(D);
R.Numerator = GetInt32(N);
return R;
}
//
// Gets specified rational property
//
// Property ID
//
// Value of property or 0/1 if not present.
//
// [altair] 10.09.2003 Created
//
public Rational GetPropertyRational(TagNames PID, int rank)
{
if (IsPropertyDefined(PID))
{
byte[] N = new byte[8];
Array.Copy(_Image.GetPropertyItem((int)PID).Value, rank * 8, N, 0, 8);
return GetRational(N);
}
else
{
Rational R = new Rational { Numerator = 0, Denominator = 1 };
return R;
}
}
static double DMStoDD(double vDeg, double vMin, double vSec)
{
return vDeg + (vMin / 60) + (vSec / 3600);
}
public double Latitude
{
get
{
if (IsPropertyDefined(TagNames.GpsLatitude))
{
Rational deg = GetPropertyRational(TagNames.GpsLatitude, 0);
Rational min = GetPropertyRational(TagNames.GpsLatitude, 1);
Rational sec = GetPropertyRational(TagNames.GpsLatitude, 2);
Double res = DMStoDD(deg.ToDouble(), min.ToDouble(), sec.ToDouble());
String Ref = GetPropertyString(TagNames.GpsLatitudeRef);
if (Ref != "N")
res = -res;
return res;
}
else
return Double.NaN;
}
}
public double Longitude
{
get
{
if (IsPropertyDefined(TagNames.GpsLongitude))
{
Rational deg = GetPropertyRational(TagNames.GpsLongitude, 0);
Rational min = GetPropertyRational(TagNames.GpsLongitude, 1);
Rational sec = GetPropertyRational(TagNames.GpsLongitude, 2);
Double res = DMStoDD(deg.ToDouble(), min.ToDouble(), sec.ToDouble());
String Ref = GetPropertyString(TagNames.GpsLongitudeRef);
if (Ref != "E")
res = -res;
return res;
}
else
return Double.NaN;
}
}
#endregion
#region " IDisposable implementation "
//
// Disposes unmanaged resources of this class
//
//
//
// [altair] 10.09.2003 Created
//
public void Dispose()
{
_Image.Dispose();
_Stream.Dispose();
}
#endregion
}
}
|
|
|
|
|
I want to open an image, change some properties and then save it again as the same file. How to do it?
Currently I try to create a temp file of the source file, delete the original, open the temp file with ExifWorks and save it with the original filename. All this works OK, but I then want to delete the temp file. Even though I use .Dispose on the ExifWorks object I get an error message saying that the file is opened by another process.
Is this the smoothest way to do this? Or are there an other, smoother way?
|
|
|
|
|
I used the EXIFWorks class and followed the style of extacting EXIF data to pull back latitude & longitude. However, I can only seem to pull back the first degrees part eg. 51 instead of 51, 27.86
Here is how I've done it:
'In ExifWorks Class
Public ReadOnly Property GPSLatitude() As Integer
Get
Return Me.GetPropertyInt32(TagNames.GpsLatitude)
End Get
End Property
'In my project
Dim GPSLat As Long = EW.GPSLatitude()
labelLat.Text = GPSLat
Am I not using the correct data type (Integer/PropertyInt32)?
|
|
|
|
|
I've stumbled upon the same problem. There must be something wrong with the data type. The data type "Integer" only supports real integers. Did you get any answer to this question or did you manage to solve it yourself? I'm curious since I can't solve it...
|
|
|
|
|
I just published the answer below.
|
|
|
|
|
The posting you did above is the converted version of vb.net, right? As I converted it back to vb.net (I am programming in that language) I noted that there were no references at all to any GPS-information. I'm back to square one. Could you please show me how to write the coding that gets that information out of the exif?
// Kristofer
|
|
|
|
|
Latitude and Longitude are new properties near the end of the C# version above.
I have converted them back to VB.Net and included the fixed Get* routines below:
''' <summary>
''' Read Int32 from EXIF bytearray.
''' </summary>
''' <param name="B">EXIF bytearray to process</param>
''' <returns></returns>
''' <remarks></remarks>
''' <history>
''' [altair] 10.09.2003 Created
''' [altair] 05.09.2005 Changed from public shared to private instance method
''' </history>
Private Shared Function GetInt32(ByVal B As Byte()) As Int32
If B.Length < 4 Then
Throw New ArgumentException("Data too short (4 bytes expected)", "B")
End If
Return System.BitConverter.ToInt32(B, 0)
End Function
''' <summary>
''' Read Int16 from EXIF bytearray.
''' </summary>
''' <param name="B">EXIF bytearray to process</param>
''' <returns></returns>
''' <remarks></remarks>
''' <history>
''' [altair] 10.09.2003 Created
''' [altair] 05.09.2005 Changed from public shared to private instance method
''' </history>
Private Shared Function GetInt16(ByVal B As Byte()) As Int16
If B.Length < 2 Then
Throw New ArgumentException("Data too short (2 bytes expected)", "B")
End If
Return System.BitConverter.ToInt16(B, 0)
End Function
''' <summary>
''' Read string from EXIF bytearray.
''' </summary>
''' <param name="B">EXIF bytearray to process</param>
''' <returns></returns>
''' <remarks></remarks>
''' <history>
''' [altair] 10.09.2003 Created
''' [altair] 05.09.2005 Changed from public shared to private instance method
''' </history>
Private Function GetString(ByVal B As Byte()) As String
Dim R As String = _Encoding.GetString(B)
If R.EndsWith(vbNullChar) Then
R = R.Substring(0, R.Length - 1)
End If
Return R
End Function
''' <summary>
''' Read rational from EXIF bytearray.
''' </summary>
''' <param name="B">EXIF bytearray to process</param>
''' <returns></returns>
''' <remarks></remarks>
''' <history>
''' [altair] 10.09.2003 Created
''' [altair] 05.09.2005 Changed from public shared to private instance method
''' </history>
Private Shared Function GetRational(ByVal B As Byte()) As Rational
Dim R As New Rational
R.Denominator = System.BitConverter.ToInt32(B, 4)
R.Numerator = System.BitConverter.ToInt32(B, 0)
Return R
End Function
''' <summary>
''' Get specified rational property
''' </summary>
''' <param name="PID">Property ID</param>
''' <param name="rank"></param>
''' <returns>Value of property or 0/1 if not present</returns>
''' <history>
''' [altair] 10.09.2003 Created
''' </history>
Public Function GetPropertyRational(ByVal PID As TagNames, ByVal rank As Integer) As Rational
If IsPropertyDefined(PID) Then
Dim N As Byte() = New Byte(7) {}
Array.Copy(_Image.GetPropertyItem(PID).Value, rank * 8, N, 0, 8)
Return GetRational(N)
Else
Dim R As New Rational()
Return R
End If
End Function
''' <summary>
''' Degrees, Minutes, Seconds to Decimal Degrees
''' </summary>
''' <param name="Deg">Degrees</param>
''' <param name="Min">Minutes</param>
''' <param name="Sec">Seconds</param>
''' <returns>Decimal Degrees</returns>
''' <remarks>in EXIF, Deg is always positive, but added case for negative degrees in case this routine is reused</remarks>
Private Shared Function DMStoDD(ByVal Deg As Double, ByVal Min As Double, ByVal Sec As Double) As Double
Dim lFraction As Double = (Min / 60) + (Sec / 3600)
If Deg > 0 Then
Return Deg + lFraction
Else
Return Deg - lFraction
End If
End Function
''' <summary>
''' GPS Latitude
''' </summary>
''' <returns>Decimal degrees of latitude</returns>
Public ReadOnly Property Latitude() As Double
Get
If IsPropertyDefined(TagNames.GpsLatitude) Then
Dim deg As Rational = GetPropertyRational(TagNames.GpsLatitude, 0)
Dim min As Rational = GetPropertyRational(TagNames.GpsLatitude, 1)
Dim sec As Rational = GetPropertyRational(TagNames.GpsLatitude, 2)
Dim res As [Double] = DMStoDD(deg.ToDouble(), min.ToDouble(), sec.ToDouble())
Dim Ref As [String] = GetPropertyString(TagNames.GpsLatitudeRef)
If Ref <> "N" Then
res = -res
End If
Return res
Else
Return [Double].NaN
End If
End Get
End Property
''' <summary>
''' GPS Longitude
''' </summary>
''' <returns>Decimal degrees of longitude</returns>
Public ReadOnly Property Longitude() As Double
Get
If IsPropertyDefined(TagNames.GpsLongitude) Then
Dim deg As Rational = GetPropertyRational(TagNames.GpsLongitude, 0)
Dim min As Rational = GetPropertyRational(TagNames.GpsLongitude, 1)
Dim sec As Rational = GetPropertyRational(TagNames.GpsLongitude, 2)
Dim res As [Double] = DMStoDD(deg.ToDouble(), min.ToDouble(), sec.ToDouble())
Dim Ref As [String] = GetPropertyString(TagNames.GpsLongitudeRef)
If Ref <> "E" Then
res = -res
End If
Return res
Else
Return [Double].NaN
End If
End Get
End Property
|
|
|
|
|
You are only reading the 1st value in GpsLatitude, there are 3, Degrees, Minutes and Seconds so you need to write code to obtain all three. Happily someone has already done this
http://www.vbforums.com/showthread.php?t=647226
|
|
|
|
|
Hi,
There may be other bugs other than what i found but at first sight, it gives wrong info about Flash Mode. Though other programs say "Flash is Fired" and i'm sure i fired the Flash (my own photo), your class returns NotFired value from FlashModes enum.
Some further optimizations might be needed for the issues like that one.
Thanks.
|
|
|
|
|
Flash info is returned wrong. Any others having it?
|
|
|
|
|
Any code will he helpful.
Pendin Approval
|
|
|
|
|
I have written a test program to update and save the Title and User Comments tags with limited success...meaning that I can re-read the saved image and I can see the changes that I made to the Title and User Comments ...if I read the image using EXIFWorks....however if I use any of the Windows picture viewing products (either Vista Windows Photo Gallery or Microsoft Office 2007 Picture Manager) the updated Title or User Comments are NOT visible when the picture properties is displayed.
Any idea why ?
|
|
|
|
|
Hello,
just a remark: your ExifWorks class implements the IDisposable interface which cleans up the image passed on to it. This is bad, as that image (of class Bitmap) is a MANAGED resource which will be cleared automatically by the CLR during garbage collection if appropriate. As it stands now code like that below will fail further down the line because:
Public Shared Sub AddCalibratedMetadata(ByVal img As bitmap)
Dim exif As New CIPF.ExifWorks(img)
exif.Description = "Calibrated"
exif.Dispose()
End Sub
From here on the img reference is invalid ...
So there is no need for the IDIsposable interface, this is for cleaning up stuff like COM objects.
Keep up the good work
Yves
|
|
|
|
|
Hi,
thankyou for your work.
I found this problem in your Class.
In this routine:
<br />
Public Sub New(ByVal FileName As String)<br />
Me._Image = DirectCast(System.Drawing.Bitmap.FromFile(FileName), System.Drawing.Bitmap)<br />
Me._Image.Tag = FileName 'safe path and filename for later use<br />
End Sub<br />
when image was loaded, the file will be locked and this lock never be released.
I tryed this code:
<br />
Private _reader As System.IO.StreamReader<br />
<br />
Public Sub New(ByVal FileName As String)<br />
If FileName = "" Then Throw New ArgumentNullException("File Name")<br />
<br />
Me._reader = New System.IO.StreamReader(FileName)<br />
Me._Image = DirectCast(System.Drawing.Bitmap.FromStream(_reader.BaseStream), System.Drawing.Bitmap)<br />
Me._reader.Close()<br />
Me._Image.Tag = FileName 'safe path and filename for later use<br />
<br />
End Sub<br />
in this case the file will be unlocked, but when i'll try to read any property I'll get an error.
Can you help me?
Many Thanks
Marco
|
|
|
|
|
Hi,
seem i found a solution.
I need to modify all Get Property like this:
<br />
Public Function GetPropertyString(ByVal PID As Int32, Optional ByVal DefaultValue As String = "") As String<br />
If Me.IsPropertyDefined(PID) Then<br />
Dim a As String = GetString(Me._Image.GetPropertyItem(PID).Value)<br />
Me._Image.Dispose()<br />
Return a<br />
Else<br />
Me._Image.Dispose()<br />
Return DefaultValue<br />
End If<br />
End Function<br />
Seem it works fine.
|
|
|
|
|
Great job really, the class works well (I use the C# translation from above).
You can improve a lot performance by avoiding the whole image file to be loaded. Here is the way to do it, in the constructor :
<br />
public ExifManager(string FileName)<br />
{<br />
if (FileName == null)<br />
throw new ArgumentNullException("FileName");<br />
this._Stream = new FileStream(FileName, FileMode.Open, FileAccess.Read);<br />
this._Image = (System.Drawing.Bitmap)System.Drawing.Image.FromStream(this._Stream, true, false);<br />
<br />
}<br />
of course you need to add a private member like that :
<br />
private System.IO.FileStream _Stream;<br />
and you need to get reed of it in the IDisposable implementation :
<br />
public void Dispose()<br />
{<br />
this._Image.Dispose();<br />
this._Stream.Dispose();<br />
}<br />
I got an improvement of factor 20, measured on 2200 photos. Quite interesting isn't it ?
Hope that helps.
Greg
|
|
|
|
|
Great, thanks for sharing this with us!
|
|
|
|
|
Dim propItems As PropertyItem()
Dim propItem As PropertyItem
Dim fStream = New FileStream(FileDir + FileName, FileMode.Open, FileAccess.Read)
propItems = Image.FromStream(fStream, True, False).PropertyItems
fStream.close()
These few VB.Net lines have the same impact. Parameter three (validate the image data) of Image.FromStream() must be set to False to achieve the performance.
Rob
|
|
|
|
|
How to use it with asp.net and vb? Do You know any asp.net commponent written in vb for reading of exif image data. I have all image info in mssql database.
Thanks!
|
|
|
|
|
I can imagine that this improves performance on reading, but it also breaks the SetProperty side of things.
If you never read the whole image, you can't save it back with new properties.
|
|
|
|
|
UTF8 encoding not work.
Latin character not write correctly.
|
|
|
|
|
Using Exifworks can you give me an example for editing a tag ?
|
|
|
|
|
Public Class EXIFtest
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim text2 As String = TextBox2.Text
Dim text3 As String = Replace(text2, ".jpg", "")
Dim ef As New ExifWorks(text2)
Dim imgWithExif As Image = ef._Image
ef.UserComment = TextBox1.Text
imgWithExif.Save(text3 + "TAGGED.jpg")
End Sub
Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
TextBox2.Text = ("C:\Users\Simon\Music\Pictures\Exif Tests\DSC_1847.jpg")
PictureBox2.ImageLocation = TextBox2.Text
End Sub
Private Sub EXIFtest_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
TextBox2.Text = PictureBox2.ImageLocation
End Sub
End Class
|
|
|
|
|
This class is almost perfect!
However, it appears that for a lot of cameras like the canon eos range, details such as the ISO is stored in the manufacturer specific MakerNotes section. When i look at my images under ExifClass it is not one of the available properties even though Im sure its there!
Has anybody managed to extract the makernotes correctly and if so how do i extract it (I assume I should be able to get it as a byte array but the property says nothing?
Thanks
Andy
|
|
|
|
|
I have been trying to write a VB .Net 2005 application to manage my photographs. One of the things I want this app to do is imbed some configurable information (i.e. Copyright, contact information, etc.) into the metadata of the raw files during the transfer from the CF card to my hard drive. I was able to use your Exif Works class to modify data as long as the file I was working with was a jpeg or a tiff. When I tried to use it on a Canon raw file (*.cr2) or a Photoshop Document (*.psd) I got an error at the point the DirectCast instruction was executed (these file types apparently can't be converted to a bitmapped file). Do you have any suggestions on changing your class to handle these file types? Do you know of any online resources that might give me more information about manipulating the metadata in these file types? Any help you can give would be greatly appreciated as I have not been able to find anything that addresses this problem.
Thanks,
Mike Allgood
mallgood@adveng.com
|
|
|
|
|