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:
Shannon
2015-02-10 16:33:59 +11:00
parent dd680b6fd1
commit 0efb9b72e7
9 changed files with 1076 additions and 11 deletions

View File

@@ -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

View 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);
}
}
}
}

View 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
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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" />