diff --git a/src/Umbraco.Core/IO/UmbracoMediaFile.cs b/src/Umbraco.Core/IO/UmbracoMediaFile.cs
index a8a55e5d8a..b8fe310f54 100644
--- a/src/Umbraco.Core/IO/UmbracoMediaFile.cs
+++ b/src/Umbraco.Core/IO/UmbracoMediaFile.cs
@@ -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
diff --git a/src/Umbraco.Core/Media/Exif/ExifIO.cs b/src/Umbraco.Core/Media/Exif/ExifIO.cs
new file mode 100644
index 0000000000..3818385955
--- /dev/null
+++ b/src/Umbraco.Core/Media/Exif/ExifIO.cs
@@ -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
+{
+ ///
+ /// 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
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Media/Exif/ExifIds.cs b/src/Umbraco.Core/Media/Exif/ExifIds.cs
new file mode 100644
index 0000000000..3c28892934
--- /dev/null
+++ b/src/Umbraco.Core/Media/Exif/ExifIds.cs
@@ -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
+ }
+
+ ///
+ /// As per http://www.exif.org/Exif2-2.PDF
+ ///
+ [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
+ }
+}
diff --git a/src/Umbraco.Core/Media/Exif/ExifReader.cs b/src/Umbraco.Core/Media/Exif/ExifReader.cs
new file mode 100644
index 0000000000..d98a0fb563
--- /dev/null
+++ b/src/Umbraco.Core/Media/Exif/ExifReader.cs
@@ -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
+{
+ ///
+ /// Based on http://www.media.mit.edu/pia/Research/deepview/exif.html
+ /// http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
+ ///
+ 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);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Media/Exif/ExifTag.cs b/src/Umbraco.Core/Media/Exif/ExifTag.cs
new file mode 100644
index 0000000000..7d9a56de46
--- /dev/null
+++ b/src/Umbraco.Core/Media/Exif/ExifTag.cs
@@ -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
+{
+ ///
+ /// As per: http://www.media.mit.edu/pia/Research/deepview/exif.html
+ ///
+ 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();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Media/Exif/JpegInfo.cs b/src/Umbraco.Core/Media/Exif/JpegInfo.cs
new file mode 100644
index 0000000000..1df90841b9
--- /dev/null
+++ b/src/Umbraco.Core/Media/Exif/JpegInfo.cs
@@ -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
+ {
+ ///
+ /// The Jpeg file name (excluding path).
+ ///
+ public string FileName
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The Jpeg file size, in bytes.
+ ///
+ public int FileSize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// True if the provided Stream was detected to be a Jpeg image, False otherwise.
+ ///
+ public bool IsValid
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Image height in pixels.
+ ///
+ public int Height
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Image width in pixels.
+ ///
+ public int Width
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// True if the image data is expressed in 3 components (RGB), False otherwise.
+ ///
+ public bool IsColor
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Orientation of the image.
+ ///
+ public ExifOrientation Orientation
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The X resolution of the image, expressed in ResolutionUnit.
+ ///
+ public double XResolution
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// The Y resolution of the image, expressed in ResolutionUnit.
+ ///
+ public double YResolution
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Resolution unit of the image.
+ ///
+ public ExifUnit ResolutionUnit
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Date at which the image was taken.
+ ///
+ public string DateTime
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Date at which the image was taken. Created by Lumia devices.
+ ///
+ public string DateTimeOriginal
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Description of the image.
+ ///
+ public string Description
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Camera manufacturer.
+ ///
+ public string Make
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Camera model.
+ ///
+ public string Model
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Software used to create the image.
+ ///
+ public string Software
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Image artist.
+ ///
+ public string Artist
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Image copyright.
+ ///
+ public string Copyright
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Image user comments.
+ ///
+ public string UserComment
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Exposure time, in seconds.
+ ///
+ public double ExposureTime
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// F-number (F-stop) of the camera lens when the image was taken.
+ ///
+ public double FNumber
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Flash settings of the camera when the image was taken.
+ ///
+ public ExifFlash Flash
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// GPS latitude reference (North, South).
+ ///
+ public ExifGpsLatitudeRef GpsLatitudeRef
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// GPS latitude (degrees, minutes, seconds).
+ ///
+ public double[] GpsLatitude = new double[3];
+
+ ///
+ /// GPS longitude reference (East, West).
+ ///
+ public ExifGpsLongitudeRef GpsLongitudeRef
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// GPS longitude (degrees, minutes, seconds).
+ ///
+ public double[] GpsLongitude = new double[3];
+
+ ///
+ /// Byte offset of the thumbnail data within the Exif section of the image file.
+ /// Used internally.
+ ///
+ public int ThumbnailOffset
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Byte size of the thumbnail data within the Exif section of the image file.
+ /// Used internally.
+ ///
+ public int ThumbnailSize
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Thumbnail data found in the Exif section.
+ ///
+ public byte[] ThumbnailData
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Time taken to load the image information.
+ ///
+ public TimeSpan LoadTime
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Media/ImageHelper.cs b/src/Umbraco.Core/Media/ImageHelper.cs
index a6cd19ddc0..3a2827f82d 100644
--- a/src/Umbraco.Core/Media/ImageHelper.cs
+++ b/src/Umbraco.Core/Media/ImageHelper.cs
@@ -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
///
internal static class ImageHelper
{
+ ///
+ /// Gets the dimensions of an image based on a stream
+ ///
+ ///
+ ///
+ ///
+ /// 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
+ ///
+ 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;
diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs
index fe18f018bb..778e40d613 100644
--- a/src/Umbraco.Core/StringExtensions.cs
+++ b/src/Umbraco.Core/StringExtensions.cs
@@ -482,17 +482,27 @@ namespace Umbraco.Core
///
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;
+ }
+
///
/// Encodes a string to a safe URL base64 string
///
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 0a61de7198..ca06404daf 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -314,6 +314,11 @@
+
+
+
+
+