Adds small exif library to read meta data from jpg's so we can attempt to read width/height from exif instead of loading the file into mem with GDI, makes for much faster and far less memory processing.U4-6246 Uploading specific images causes GDI+ Errors
This commit is contained in:
@@ -154,12 +154,8 @@ namespace Umbraco.Core.IO
|
||||
EnsureFileSupportsResizing();
|
||||
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
using (var image = Image.FromStream(fs))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
_size = new Size(fileWidth, fileHeight);
|
||||
_size = ImageHelper.GetDimensions(fs);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
104
src/Umbraco.Core/Media/Exif/ExifIO.cs
Normal file
104
src/Umbraco.Core/Media/Exif/ExifIO.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Media.Exif
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility to handle multi-byte primitives in both big and little endian.
|
||||
/// http://msdn.microsoft.com/en-us/library/system.bitconverter.islittleendian.aspx
|
||||
/// http://en.wikipedia.org/wiki/Endianness
|
||||
/// </summary>
|
||||
internal static class ExifIO
|
||||
{
|
||||
public static short ReadShort(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToInt16(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] beBytes = new byte[2] { Data[offset + 1], Data[offset] };
|
||||
return BitConverter.ToInt16(beBytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static ushort ReadUShort(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToUInt16(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] beBytes = new byte[2] { Data[offset + 1], Data[offset] };
|
||||
return BitConverter.ToUInt16(beBytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static int ReadInt(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToInt32(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] beBytes = new byte[4] { Data[offset + 3], Data[offset + 2], Data[offset + 1], Data[offset] };
|
||||
return BitConverter.ToInt32(beBytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static uint ReadUInt(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToUInt32(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] beBytes = new byte[4] { Data[offset + 3], Data[offset + 2], Data[offset + 1], Data[offset] };
|
||||
return BitConverter.ToUInt32(beBytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static float ReadSingle(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToSingle(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// need to swap the data first
|
||||
byte[] beBytes = new byte[4] { Data[offset + 3], Data[offset + 2], Data[offset + 1], Data[offset] };
|
||||
return BitConverter.ToSingle(beBytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static double ReadDouble(byte[] Data, int offset, bool littleEndian)
|
||||
{
|
||||
if ((littleEndian && BitConverter.IsLittleEndian) ||
|
||||
(!littleEndian && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
return BitConverter.ToDouble(Data, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// need to swap the data first
|
||||
byte[] beBytes = new byte[8] {
|
||||
Data[offset + 7], Data[offset + 6], Data[offset + 5], Data[offset + 4],
|
||||
Data[offset + 3], Data[offset + 2], Data[offset + 1], Data[offset]};
|
||||
return BitConverter.ToDouble(beBytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/Umbraco.Core/Media/Exif/ExifIds.cs
Normal file
133
src/Umbraco.Core/Media/Exif/ExifIds.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Media.Exif
|
||||
{
|
||||
internal static class JpegId
|
||||
{
|
||||
public const int START = 0xFF;
|
||||
public const int SOI = 0xD8;
|
||||
public const int SOS = 0xDA;
|
||||
public const int EOI = 0xD9;
|
||||
public const int COM = 0xFE;
|
||||
public const int JFIF = 0xE0;
|
||||
public const int EXIF = 0xE1;
|
||||
public const int IPTC = 0xED;
|
||||
}
|
||||
|
||||
internal enum ExifIFD
|
||||
{
|
||||
Exif = 0x8769,
|
||||
Gps = 0x8825
|
||||
}
|
||||
|
||||
internal enum ExifId
|
||||
{
|
||||
Unknown = -1,
|
||||
|
||||
ImageWidth = 0x100,
|
||||
ImageHeight = 0x101,
|
||||
Orientation = 0x112,
|
||||
XResolution = 0x11A,
|
||||
YResolution = 0x11B,
|
||||
ResolutionUnit = 0x128,
|
||||
DateTime = 0x132,
|
||||
Description = 0x10E,
|
||||
Make = 0x10F,
|
||||
Model = 0x110,
|
||||
Software = 0x131,
|
||||
Artist = 0x13B,
|
||||
ThumbnailOffset = 0x201,
|
||||
ThumbnailLength = 0x202,
|
||||
ExposureTime = 0x829A,
|
||||
FNumber = 0x829D,
|
||||
Copyright = 0x8298,
|
||||
DateTimeOriginal = 0x9003,
|
||||
FlashUsed = 0x9209,
|
||||
UserComment = 0x9286
|
||||
}
|
||||
|
||||
internal enum ExifGps
|
||||
{
|
||||
Version = 0x0,
|
||||
LatitudeRef = 0x1,
|
||||
Latitude = 0x2,
|
||||
LongitudeRef = 0x3,
|
||||
Longitude = 0x4,
|
||||
AltitudeRef = 0x5,
|
||||
Altitude = 0x6,
|
||||
TimeStamp = 0x7,
|
||||
Satellites = 0x8,
|
||||
Status = 0x9,
|
||||
MeasureMode = 0xA,
|
||||
DOP = 0xB,
|
||||
SpeedRef = 0xC,
|
||||
Speed = 0xD,
|
||||
TrackRef = 0xE,
|
||||
Track = 0xF,
|
||||
ImgDirectionRef = 0x10,
|
||||
ImgDirection = 0x11,
|
||||
MapDatum = 0x12,
|
||||
DestLatitudeRef = 0x13,
|
||||
DestLatitude = 0x14,
|
||||
DestLongitudeRef = 0x15,
|
||||
DestLongitude = 0x16,
|
||||
DestBearingRef = 0x17,
|
||||
DestBearing = 0x18,
|
||||
DestDistanceRef = 0x19,
|
||||
DestDistance = 0x1A,
|
||||
ProcessingMethod = 0x1B,
|
||||
AreaInformation = 0x1C,
|
||||
DateStamp = 0x1D,
|
||||
Differential = 0x1E
|
||||
}
|
||||
|
||||
internal enum ExifOrientation
|
||||
{
|
||||
TopLeft = 1,
|
||||
BottomRight = 3,
|
||||
TopRight = 6,
|
||||
BottomLeft = 8,
|
||||
Undefined = 9
|
||||
}
|
||||
|
||||
internal enum ExifUnit
|
||||
{
|
||||
Undefined = 1,
|
||||
Inch = 2,
|
||||
Centimeter = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// As per http://www.exif.org/Exif2-2.PDF
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum ExifFlash
|
||||
{
|
||||
No = 0x0,
|
||||
Fired = 0x1,
|
||||
StrobeReturnLightDetected = 0x6,
|
||||
On = 0x8,
|
||||
Off = 0x10,
|
||||
Auto = 0x18,
|
||||
FlashFunctionPresent = 0x20,
|
||||
RedEyeReduction = 0x40
|
||||
}
|
||||
|
||||
internal enum ExifGpsLatitudeRef
|
||||
{
|
||||
Unknown = 0,
|
||||
North,
|
||||
South
|
||||
}
|
||||
|
||||
internal enum ExifGpsLongitudeRef
|
||||
{
|
||||
Unknown = 0,
|
||||
East,
|
||||
West
|
||||
}
|
||||
}
|
||||
255
src/Umbraco.Core/Media/Exif/ExifReader.cs
Normal file
255
src/Umbraco.Core/Media/Exif/ExifReader.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Media.Exif
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on http://www.media.mit.edu/pia/Research/deepview/exif.html
|
||||
/// http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
|
||||
/// </summary>
|
||||
internal class ExifReader
|
||||
{
|
||||
public JpegInfo info { get; private set; }
|
||||
private bool littleEndian = false;
|
||||
|
||||
public static JpegInfo ReadJpeg(Stream stream)
|
||||
{
|
||||
DateTime then = DateTime.Now;
|
||||
ExifReader reader = new ExifReader(stream);
|
||||
reader.info.LoadTime = (DateTime.Now - then);
|
||||
return reader.info;
|
||||
}
|
||||
|
||||
protected ExifReader(Stream stream)
|
||||
{
|
||||
info = new JpegInfo();
|
||||
int a = stream.ReadByte();
|
||||
|
||||
// ensure SOI marker
|
||||
if (a != JpegId.START || stream.ReadByte() != JpegId.SOI)
|
||||
return;
|
||||
|
||||
info.IsValid = true;
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
int marker = 0, prev = 0;
|
||||
|
||||
// find next marker
|
||||
for (a = 0; ; ++a)
|
||||
{
|
||||
marker = stream.ReadByte();
|
||||
if (marker != JpegId.START && prev == JpegId.START) break;
|
||||
prev = marker;
|
||||
}
|
||||
|
||||
// read section length
|
||||
int lenHigh = stream.ReadByte();
|
||||
int lenLow = stream.ReadByte();
|
||||
int itemlen = (lenHigh << 8) | lenLow;
|
||||
|
||||
// read the section
|
||||
byte[] section = new byte[itemlen];
|
||||
section[0] = (byte)lenHigh;
|
||||
section[1] = (byte)lenLow;
|
||||
int bytesRead = stream.Read(section, 2, itemlen - 2);
|
||||
if (bytesRead != itemlen - 2)
|
||||
return;
|
||||
|
||||
switch (marker)
|
||||
{
|
||||
case JpegId.SOS:
|
||||
// start of stream: and we're done
|
||||
return;
|
||||
case JpegId.EOI:
|
||||
// no data? no good.
|
||||
return;
|
||||
case JpegId.EXIF:
|
||||
{
|
||||
if (section[2] == 'E' &&
|
||||
section[3] == 'x' &&
|
||||
section[4] == 'i' &&
|
||||
section[5] == 'f')
|
||||
{
|
||||
ProcessExif(section);
|
||||
}
|
||||
} break;
|
||||
case JpegId.IPTC:
|
||||
{
|
||||
// don't care.
|
||||
} break;
|
||||
case 0xC0:
|
||||
case 0xC1:
|
||||
case 0xC2:
|
||||
case 0xC3:
|
||||
// case 0xC4: // not SOF
|
||||
case 0xC5:
|
||||
case 0xC6:
|
||||
case 0xC7:
|
||||
// case 0xC8: // not SOF
|
||||
case 0xC9:
|
||||
case 0xCA:
|
||||
case 0xCB:
|
||||
// case 0xCC: // not SOF
|
||||
case 0xCD:
|
||||
case 0xCE:
|
||||
case 0xCF:
|
||||
{
|
||||
ProcessSOF(section, marker);
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
// don't care.
|
||||
} break;
|
||||
}
|
||||
|
||||
section = null;
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessExif(byte[] section)
|
||||
{
|
||||
int idx = 6;
|
||||
if (section[idx++] != 0 ||
|
||||
section[idx++] != 0)
|
||||
{
|
||||
// "Exif" is not followed by 2 null bytes.
|
||||
return;
|
||||
}
|
||||
|
||||
if (section[idx] == 'I' && section[idx + 1] == 'I')
|
||||
{
|
||||
// intel order
|
||||
littleEndian = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (section[idx] == 'M' && section[idx + 1] == 'M')
|
||||
littleEndian = false;
|
||||
else
|
||||
{
|
||||
// unknown order...
|
||||
return;
|
||||
}
|
||||
}
|
||||
idx += 2;
|
||||
|
||||
int a = ExifIO.ReadUShort(section, idx, littleEndian);
|
||||
idx += 2;
|
||||
|
||||
if (a != 0x002A)
|
||||
{
|
||||
// bad start...
|
||||
return;
|
||||
}
|
||||
|
||||
a = ExifIO.ReadInt(section, idx, littleEndian);
|
||||
idx += 4;
|
||||
|
||||
if (a < 8 || a > 16)
|
||||
{
|
||||
if (a < 16 || a > section.Length - 16)
|
||||
{
|
||||
// invalid offset
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessExifDir(section, a + 8, 8, section.Length - 8, 0, ExifIFD.Exif);
|
||||
}
|
||||
|
||||
private int DirOffset(int start, int num)
|
||||
{
|
||||
return start + 2 + 12 * num;
|
||||
}
|
||||
|
||||
private void ProcessExifDir(byte[] section, int offsetDir, int offsetBase, int length, int depth, ExifIFD ifd)
|
||||
{
|
||||
if (depth > 4)
|
||||
{
|
||||
// corrupted Exif header...
|
||||
return;
|
||||
}
|
||||
|
||||
ushort numEntries = ExifIO.ReadUShort(section, offsetDir, littleEndian);
|
||||
if (offsetDir + 2 + 12 * numEntries >= offsetDir + length)
|
||||
{
|
||||
// too long
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
|
||||
for (int de = 0; de < numEntries; ++de)
|
||||
{
|
||||
offset = DirOffset(offsetDir, de);
|
||||
ExifTag exifTag = new ExifTag(section, offset, offsetBase, length, littleEndian);
|
||||
|
||||
if (!exifTag.IsValid)
|
||||
continue;
|
||||
|
||||
switch (exifTag.Tag)
|
||||
{
|
||||
case (int)ExifIFD.Exif:
|
||||
{
|
||||
int dirStart = offsetBase + exifTag.GetInt(0);
|
||||
if (dirStart <= offsetBase + length)
|
||||
{
|
||||
ProcessExifDir(section, dirStart, offsetBase, length, depth + 1, ExifIFD.Exif);
|
||||
}
|
||||
} break;
|
||||
case (int)ExifIFD.Gps:
|
||||
{
|
||||
int dirStart = offsetBase + exifTag.GetInt(0);
|
||||
if (dirStart <= offsetBase + length)
|
||||
{
|
||||
ProcessExifDir(section, dirStart, offsetBase, length, depth + 1, ExifIFD.Gps);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
exifTag.Populate(info, ifd);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// final link defined?
|
||||
offset = DirOffset(offsetDir, numEntries) + 4;
|
||||
if (offset <= offsetBase + length)
|
||||
{
|
||||
offset = ExifIO.ReadInt(section, offsetDir + 2 + 12 * numEntries, littleEndian);
|
||||
if (offset > 0)
|
||||
{
|
||||
int subDirStart = offsetBase + offset;
|
||||
if (subDirStart <= offsetBase + length && subDirStart >= offsetBase)
|
||||
{
|
||||
ProcessExifDir(section, subDirStart, offsetBase, length, depth + 1, ifd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.ThumbnailData == null && info.ThumbnailOffset > 0 && info.ThumbnailSize > 0)
|
||||
{
|
||||
// store it.
|
||||
info.ThumbnailData = new byte[info.ThumbnailSize];
|
||||
Array.Copy(section, offsetBase + info.ThumbnailOffset, info.ThumbnailData, 0, info.ThumbnailSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSOF(byte[] section, int marker)
|
||||
{
|
||||
// bytes 1,2 is section len
|
||||
// byte 3 is precision (bytes per sample)
|
||||
info.Height = ((int)section[3] << 8) | (int)section[4];
|
||||
info.Width = ((int)section[5] << 8) | (int)section[6];
|
||||
int components = (int)section[7];
|
||||
|
||||
info.IsColor = (components == 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
258
src/Umbraco.Core/Media/Exif/ExifTag.cs
Normal file
258
src/Umbraco.Core/Media/Exif/ExifTag.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Media.Exif
|
||||
{
|
||||
/// <summary>
|
||||
/// As per: http://www.media.mit.edu/pia/Research/deepview/exif.html
|
||||
/// </summary>
|
||||
internal enum ExifTagFormat
|
||||
{
|
||||
BYTE = 1,
|
||||
STRING = 2,
|
||||
USHORT = 3,
|
||||
ULONG = 4,
|
||||
URATIONAL = 5,
|
||||
SBYTE = 6,
|
||||
UNDEFINED = 7,
|
||||
SSHORT = 8,
|
||||
SLONG = 9,
|
||||
SRATIONAL = 10,
|
||||
SINGLE = 11,
|
||||
DOUBLE = 12,
|
||||
|
||||
NUM_FORMATS = 12
|
||||
}
|
||||
|
||||
internal class ExifTag
|
||||
{
|
||||
public int Tag { get; private set; }
|
||||
public ExifTagFormat Format { get; private set; }
|
||||
public int Components { get; private set; }
|
||||
public byte[] Data { get; private set; }
|
||||
public bool LittleEndian { get; private set; }
|
||||
|
||||
private static int[] BytesPerFormat = new int[] { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
|
||||
|
||||
public ExifTag(byte[] section, int sectionOffset, int offsetBase, int length, bool littleEndian)
|
||||
{
|
||||
this.IsValid = false;
|
||||
this.Tag = ExifIO.ReadUShort(section, sectionOffset, littleEndian);
|
||||
int format = ExifIO.ReadUShort(section, sectionOffset + 2, littleEndian);
|
||||
if (format < 1 || format > 12)
|
||||
return;
|
||||
this.Format = (ExifTagFormat)format;
|
||||
this.Components = ExifIO.ReadInt(section, sectionOffset + 4, littleEndian);
|
||||
if (this.Components > 0x10000)
|
||||
return;
|
||||
this.LittleEndian = littleEndian;
|
||||
|
||||
int byteCount = this.Components * BytesPerFormat[format];
|
||||
int valueOffset = 0;
|
||||
|
||||
if (byteCount > 4)
|
||||
{
|
||||
int offsetVal = ExifIO.ReadInt(section, sectionOffset + 8, littleEndian);
|
||||
if (offsetVal + byteCount > length)
|
||||
{
|
||||
// bad offset...
|
||||
return;
|
||||
}
|
||||
valueOffset = offsetBase + offsetVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueOffset = sectionOffset + 8;
|
||||
}
|
||||
this.Data = new byte[byteCount];
|
||||
Array.Copy(section, valueOffset, this.Data, 0, byteCount);
|
||||
this.IsValid = true;
|
||||
}
|
||||
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
private short ReadShort(int offset)
|
||||
{
|
||||
return ExifIO.ReadShort(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
private ushort ReadUShort(int offset)
|
||||
{
|
||||
return ExifIO.ReadUShort(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
private int ReadInt(int offset)
|
||||
{
|
||||
return ExifIO.ReadInt(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
private uint ReadUInt(int offset)
|
||||
{
|
||||
return ExifIO.ReadUInt(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
private float ReadSingle(int offset)
|
||||
{
|
||||
return ExifIO.ReadSingle(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
private double ReadDouble(int offset)
|
||||
{
|
||||
return ExifIO.ReadDouble(Data, offset, LittleEndian);
|
||||
}
|
||||
|
||||
public bool IsNumeric
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case ExifTagFormat.STRING:
|
||||
case ExifTagFormat.UNDEFINED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetInt(int componentIndex)
|
||||
{
|
||||
return (int)GetNumericValue(componentIndex);
|
||||
}
|
||||
|
||||
public double GetNumericValue(int componentIndex)
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case ExifTagFormat.BYTE: return (double)this.Data[componentIndex];
|
||||
case ExifTagFormat.USHORT: return (double)ReadUShort(componentIndex * 2);
|
||||
case ExifTagFormat.ULONG: return (double)ReadUInt(componentIndex * 4);
|
||||
case ExifTagFormat.URATIONAL: return (double)ReadUInt(componentIndex * 8) / (double)ReadUInt((componentIndex * 8) + 4);
|
||||
case ExifTagFormat.SBYTE:
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (double)(sbyte)this.Data[componentIndex];
|
||||
}
|
||||
}
|
||||
case ExifTagFormat.SSHORT: return (double)ReadShort(componentIndex * 2);
|
||||
case ExifTagFormat.SLONG: return (double)ReadInt(componentIndex * 4);
|
||||
case ExifTagFormat.SRATIONAL: return (double)ReadInt(componentIndex * 8) / (double)ReadInt((componentIndex * 8) + 4);
|
||||
case ExifTagFormat.SINGLE: return (double)ReadSingle(componentIndex * 4);
|
||||
case ExifTagFormat.DOUBLE: return ReadDouble(componentIndex * 8);
|
||||
default: return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetStringValue()
|
||||
{
|
||||
return GetStringValue(0);
|
||||
}
|
||||
|
||||
public string GetStringValue(int componentIndex)
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case ExifTagFormat.STRING:
|
||||
case ExifTagFormat.UNDEFINED:
|
||||
return Encoding.UTF8.GetString(this.Data, 0, this.Data.Length).Trim(' ', '\t', '\r', '\n', '\0');
|
||||
case ExifTagFormat.URATIONAL:
|
||||
return ReadUInt(componentIndex * 8).ToString() + "/" + ReadUInt((componentIndex * 8) + 4).ToString();
|
||||
case ExifTagFormat.SRATIONAL:
|
||||
return ReadInt(componentIndex * 8).ToString() + "/" + ReadInt((componentIndex * 8) + 4).ToString();
|
||||
default:
|
||||
return GetNumericValue(componentIndex).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Populate(JpegInfo info, ExifIFD ifd)
|
||||
{
|
||||
if (ifd == ExifIFD.Exif)
|
||||
{
|
||||
switch ((ExifId)this.Tag)
|
||||
{
|
||||
case ExifId.ImageWidth: info.Width = GetInt(0); break;
|
||||
case ExifId.ImageHeight: info.Height = GetInt(0); break;
|
||||
case ExifId.Orientation: info.Orientation = (ExifOrientation)GetInt(0); break;
|
||||
case ExifId.XResolution: info.XResolution = GetNumericValue(0); break;
|
||||
case ExifId.YResolution: info.YResolution = GetNumericValue(0); break;
|
||||
case ExifId.ResolutionUnit: info.ResolutionUnit = (ExifUnit)GetInt(0); break;
|
||||
case ExifId.DateTime: info.DateTime = GetStringValue(); break;
|
||||
case ExifId.DateTimeOriginal: info.DateTimeOriginal = GetStringValue(); break;
|
||||
case ExifId.Description: info.Description = GetStringValue(); break;
|
||||
case ExifId.Make: info.Make = GetStringValue(); break;
|
||||
case ExifId.Model: info.Model = GetStringValue(); break;
|
||||
case ExifId.Software: info.Software = GetStringValue(); break;
|
||||
case ExifId.Artist: info.Artist = GetStringValue(); break;
|
||||
case ExifId.ThumbnailOffset: info.ThumbnailOffset = GetInt(0); break;
|
||||
case ExifId.ThumbnailLength: info.ThumbnailSize = GetInt(0); break;
|
||||
case ExifId.Copyright: info.Copyright = GetStringValue(); break;
|
||||
case ExifId.UserComment: info.UserComment = GetStringValue(); break;
|
||||
case ExifId.ExposureTime: info.ExposureTime = GetNumericValue(0); break;
|
||||
case ExifId.FNumber: info.FNumber = GetNumericValue(0); break;
|
||||
case ExifId.FlashUsed: info.Flash = (ExifFlash)GetInt(0); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
else if (ifd == ExifIFD.Gps)
|
||||
{
|
||||
switch ((ExifGps)this.Tag)
|
||||
{
|
||||
case ExifGps.LatitudeRef:
|
||||
{
|
||||
if (GetStringValue() == "N") info.GpsLatitudeRef = ExifGpsLatitudeRef.North;
|
||||
else if (GetStringValue() == "S") info.GpsLatitudeRef = ExifGpsLatitudeRef.South;
|
||||
} break;
|
||||
case ExifGps.LongitudeRef:
|
||||
{
|
||||
if (GetStringValue() == "E") info.GpsLongitudeRef = ExifGpsLongitudeRef.East;
|
||||
else if (GetStringValue() == "W") info.GpsLongitudeRef = ExifGpsLongitudeRef.West;
|
||||
} break;
|
||||
case ExifGps.Latitude:
|
||||
{
|
||||
if (Components == 3)
|
||||
{
|
||||
info.GpsLatitude[0] = GetNumericValue(0);
|
||||
info.GpsLatitude[1] = GetNumericValue(1);
|
||||
info.GpsLatitude[2] = GetNumericValue(2);
|
||||
}
|
||||
} break;
|
||||
case ExifGps.Longitude:
|
||||
{
|
||||
if (Components == 3)
|
||||
{
|
||||
info.GpsLongitude[0] = GetNumericValue(0);
|
||||
info.GpsLongitude[1] = GetNumericValue(1);
|
||||
info.GpsLongitude[2] = GetNumericValue(2);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(64);
|
||||
sb.Append("0x");
|
||||
sb.Append(this.Tag.ToString("X4"));
|
||||
sb.Append("-");
|
||||
sb.Append(((ExifId)this.Tag).ToString());
|
||||
if (this.Components > 0)
|
||||
{
|
||||
sb.Append(": (");
|
||||
sb.Append(GetStringValue(0));
|
||||
if (Format != ExifTagFormat.UNDEFINED && Format != ExifTagFormat.STRING)
|
||||
{
|
||||
for (int i = 1; i < Components; ++i)
|
||||
sb.Append(", " + GetStringValue(i));
|
||||
}
|
||||
sb.Append(")");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
275
src/Umbraco.Core/Media/Exif/JpegInfo.cs
Normal file
275
src/Umbraco.Core/Media/Exif/JpegInfo.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Media.Exif
|
||||
{
|
||||
internal class JpegInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The Jpeg file name (excluding path).
|
||||
/// </summary>
|
||||
public string FileName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Jpeg file size, in bytes.
|
||||
/// </summary>
|
||||
public int FileSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the provided Stream was detected to be a Jpeg image, False otherwise.
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image height in pixels.
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image width in pixels.
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the image data is expressed in 3 components (RGB), False otherwise.
|
||||
/// </summary>
|
||||
public bool IsColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orientation of the image.
|
||||
/// </summary>
|
||||
public ExifOrientation Orientation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The X resolution of the image, expressed in ResolutionUnit.
|
||||
/// </summary>
|
||||
public double XResolution
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Y resolution of the image, expressed in ResolutionUnit.
|
||||
/// </summary>
|
||||
public double YResolution
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolution unit of the image.
|
||||
/// </summary>
|
||||
public ExifUnit ResolutionUnit
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Date at which the image was taken.
|
||||
/// </summary>
|
||||
public string DateTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Date at which the image was taken. Created by Lumia devices.
|
||||
/// </summary>
|
||||
public string DateTimeOriginal
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Description of the image.
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Camera manufacturer.
|
||||
/// </summary>
|
||||
public string Make
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Camera model.
|
||||
/// </summary>
|
||||
public string Model
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Software used to create the image.
|
||||
/// </summary>
|
||||
public string Software
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image artist.
|
||||
/// </summary>
|
||||
public string Artist
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image copyright.
|
||||
/// </summary>
|
||||
public string Copyright
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image user comments.
|
||||
/// </summary>
|
||||
public string UserComment
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposure time, in seconds.
|
||||
/// </summary>
|
||||
public double ExposureTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// F-number (F-stop) of the camera lens when the image was taken.
|
||||
/// </summary>
|
||||
public double FNumber
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flash settings of the camera when the image was taken.
|
||||
/// </summary>
|
||||
public ExifFlash Flash
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPS latitude reference (North, South).
|
||||
/// </summary>
|
||||
public ExifGpsLatitudeRef GpsLatitudeRef
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPS latitude (degrees, minutes, seconds).
|
||||
/// </summary>
|
||||
public double[] GpsLatitude = new double[3];
|
||||
|
||||
/// <summary>
|
||||
/// GPS longitude reference (East, West).
|
||||
/// </summary>
|
||||
public ExifGpsLongitudeRef GpsLongitudeRef
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPS longitude (degrees, minutes, seconds).
|
||||
/// </summary>
|
||||
public double[] GpsLongitude = new double[3];
|
||||
|
||||
/// <summary>
|
||||
/// Byte offset of the thumbnail data within the Exif section of the image file.
|
||||
/// Used internally.
|
||||
/// </summary>
|
||||
public int ThumbnailOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte size of the thumbnail data within the Exif section of the image file.
|
||||
/// Used internally.
|
||||
/// </summary>
|
||||
public int ThumbnailSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thumbnail data found in the Exif section.
|
||||
/// </summary>
|
||||
public byte[] ThumbnailData
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time taken to load the image information.
|
||||
/// </summary>
|
||||
public TimeSpan LoadTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,8 @@ using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
using Umbraco.Core.Media.Exif;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
@@ -18,6 +16,37 @@ namespace Umbraco.Core.Media
|
||||
/// </summary>
|
||||
internal static class ImageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dimensions of an image based on a stream
|
||||
/// </summary>
|
||||
/// <param name="imageStream"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// First try with EXIF, this is because it is insanely faster and doesn't use any memory to read exif data than to load in the entire
|
||||
/// image via GDI. Otherwise loading an image into GDI consumes a crazy amount of memory on large images.
|
||||
///
|
||||
/// Of course EXIF data might not exist in every file and can only exist in JPGs
|
||||
/// </remarks>
|
||||
public static Size GetDimensions(Stream imageStream)
|
||||
{
|
||||
//Try to load with exif
|
||||
var jpgInfo = ExifReader.ReadJpeg(imageStream);
|
||||
if (jpgInfo.IsValid && jpgInfo.Width > 0 && jpgInfo.Height > 0)
|
||||
{
|
||||
return new Size(jpgInfo.Width, jpgInfo.Height);
|
||||
}
|
||||
|
||||
//we have no choice but to try to read in via GDI
|
||||
using (var image = Image.FromStream(imageStream))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
return new Size(fileWidth, fileHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static string GetMimeType(this Image image)
|
||||
{
|
||||
var format = image.RawFormat;
|
||||
|
||||
@@ -482,17 +482,27 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static string ConvertToHex(this string input)
|
||||
{
|
||||
if (String.IsNullOrEmpty(input)) return String.Empty;
|
||||
if (string.IsNullOrEmpty(input)) return string.Empty;
|
||||
|
||||
var sb = new StringBuilder(input.Length);
|
||||
foreach (char c in input)
|
||||
foreach (var c in input)
|
||||
{
|
||||
int tmp = c;
|
||||
sb.AppendFormat("{0:x2}", Convert.ToUInt32(c));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string DecodeFromHex(this string hexValue)
|
||||
{
|
||||
var strValue = "";
|
||||
while (hexValue.Length > 0)
|
||||
{
|
||||
strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString();
|
||||
hexValue = hexValue.Substring(2, hexValue.Length - 2);
|
||||
}
|
||||
return strValue;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
/// Encodes a string to a safe URL base64 string
|
||||
///</summary>
|
||||
|
||||
@@ -314,6 +314,11 @@
|
||||
<Compile Include="HideFromTypeFinderAttribute.cs" />
|
||||
<Compile Include="IApplicationEventHandler.cs" />
|
||||
<Compile Include="IDisposeOnRequestEnd.cs" />
|
||||
<Compile Include="Media\Exif\ExifIds.cs" />
|
||||
<Compile Include="Media\Exif\ExifIO.cs" />
|
||||
<Compile Include="Media\Exif\ExifReader.cs" />
|
||||
<Compile Include="Media\Exif\ExifTag.cs" />
|
||||
<Compile Include="Media\Exif\JpegInfo.cs" />
|
||||
<Compile Include="Persistence\RecordPersistenceType.cs" />
|
||||
<Compile Include="IO\ResizedImage.cs" />
|
||||
<Compile Include="IO\UmbracoMediaFile.cs" />
|
||||
|
||||
Reference in New Issue
Block a user