using MediaBrowser.Model.Drawing ;
using MediaBrowser.Model.Logging ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using MediaBrowser.Common.IO ;
using MediaBrowser.Controller.IO ;
using MediaBrowser.Model.IO ;
namespace Emby.Drawing.Common
{
/// <summary>
/// Taken from http://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/111349
/// http://www.codeproject.com/Articles/35978/Reading-Image-Headers-to-Get-Width-and-Height
/// Minor improvements including supporting unsigned 16-bit integers when decoding Jfif and added logic
/// to load the image using new Bitmap if reading the headers fails
/// </summary>
public static class ImageHeader
{
/// <summary>
/// The error message
/// </summary>
const string ErrorMessage = "Could not recognize image format." ;
/// <summary>
/// The image format decoders
/// </summary>
private static readonly KeyValuePair < byte [ ] , Func < BinaryReader , ImageSize > > [ ] ImageFormatDecoders = new Dictionary < byte [ ] , Func < BinaryReader , ImageSize > >
{
{ new byte [ ] { 0x42 , 0x4D } , DecodeBitmap } ,
{ new byte [ ] { 0x47 , 0x49 , 0x46 , 0x38 , 0x37 , 0x61 } , DecodeGif } ,
{ new byte [ ] { 0x47 , 0x49 , 0x46 , 0x38 , 0x39 , 0x61 } , DecodeGif } ,
{ new byte [ ] { 0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A } , DecodePng } ,
{ new byte [ ] { 0xff , 0xd8 } , DecodeJfif }
} . ToArray ( ) ;
private static readonly int MaxMagicBytesLength = ImageFormatDecoders . Select ( i = > i . Key . Length ) . OrderByDescending ( i = > i ) . First ( ) ;
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
/// <param name="path">The path of the image to get the dimensions of.</param>
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param>
/// <returns>The dimensions of the specified image.</returns>
/// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>
public static ImageSize GetDimensions ( string path , ILogger logger , IFileSystem fileSystem )
{
using ( var fs = fileSystem . OpenRead ( path ) )
{
using ( var binaryReader = new BinaryReader ( fs ) )
{
return GetDimensions ( binaryReader ) ;
}
}
}
/// <summary>
/// Gets the dimensions of an image.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>Size.</returns>
/// <exception cref="System.ArgumentException">binaryReader</exception>
/// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>
private static ImageSize GetDimensions ( BinaryReader binaryReader )
{
var magicBytes = new byte [ MaxMagicBytesLength ] ;
for ( var i = 0 ; i < MaxMagicBytesLength ; i + = 1 )
{
magicBytes [ i ] = binaryReader . ReadByte ( ) ;
foreach ( var kvPair in ImageFormatDecoders )
{
if ( StartsWith ( magicBytes , kvPair . Key ) )
{
return kvPair . Value ( binaryReader ) ;
}
}
}
throw new ArgumentException ( ErrorMessage , "binaryReader" ) ;
}
/// <summary>
/// Startses the with.
/// </summary>
/// <param name="thisBytes">The this bytes.</param>
/// <param name="thatBytes">The that bytes.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private static bool StartsWith ( byte [ ] thisBytes , byte [ ] thatBytes )
{
for ( int i = 0 ; i < thatBytes . Length ; i + = 1 )
{
if ( thisBytes [ i ] ! = thatBytes [ i ] )
{
return false ;
}
}
return true ;
}
/// <summary>
/// Reads the little endian int16.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>System.Int16.</returns>
private static short ReadLittleEndianInt16 ( BinaryReader binaryReader )
{
var bytes = new byte [ sizeof ( short ) ] ;
for ( int i = 0 ; i < sizeof ( short ) ; i + = 1 )
{
bytes [ sizeof ( short ) - 1 - i ] = binaryReader . ReadByte ( ) ;
}
return BitConverter . ToInt16 ( bytes , 0 ) ;
}
/// <summary>
/// Reads the little endian int32.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>System.Int32.</returns>
private static int ReadLittleEndianInt32 ( BinaryReader binaryReader )
{
var bytes = new byte [ sizeof ( int ) ] ;
for ( int i = 0 ; i < sizeof ( int ) ; i + = 1 )
{
bytes [ sizeof ( int ) - 1 - i ] = binaryReader . ReadByte ( ) ;
}
return BitConverter . ToInt32 ( bytes , 0 ) ;
}
/// <summary>
/// Decodes the bitmap.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>Size.</returns>
private static ImageSize DecodeBitmap ( BinaryReader binaryReader )
{
binaryReader . ReadBytes ( 16 ) ;
int width = binaryReader . ReadInt32 ( ) ;
int height = binaryReader . ReadInt32 ( ) ;
return new ImageSize
{
Width = width ,
Height = height
} ;
}
/// <summary>
/// Decodes the GIF.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>Size.</returns>
private static ImageSize DecodeGif ( BinaryReader binaryReader )
{
int width = binaryReader . ReadInt16 ( ) ;
int height = binaryReader . ReadInt16 ( ) ;
return new ImageSize
{
Width = width ,
Height = height
} ;
}
/// <summary>
/// Decodes the PNG.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>Size.</returns>
private static ImageSize DecodePng ( BinaryReader binaryReader )
{
binaryReader . ReadBytes ( 8 ) ;
int width = ReadLittleEndianInt32 ( binaryReader ) ;
int height = ReadLittleEndianInt32 ( binaryReader ) ;
return new ImageSize
{
Width = width ,
Height = height
} ;
}
/// <summary>
/// Decodes the jfif.
/// </summary>
/// <param name="binaryReader">The binary reader.</param>
/// <returns>Size.</returns>
/// <exception cref="System.ArgumentException"></exception>
private static ImageSize DecodeJfif ( BinaryReader binaryReader )
{
while ( binaryReader . ReadByte ( ) = = 0xff )
{
byte marker = binaryReader . ReadByte ( ) ;
short chunkLength = ReadLittleEndianInt16 ( binaryReader ) ;
if ( marker = = 0xc0 )
{
binaryReader . ReadByte ( ) ;
int height = ReadLittleEndianInt16 ( binaryReader ) ;
int width = ReadLittleEndianInt16 ( binaryReader ) ;
return new ImageSize
{
Width = width ,
Height = height
} ;
}
if ( chunkLength < 0 )
{
var uchunkLength = ( ushort ) chunkLength ;
binaryReader . ReadBytes ( uchunkLength - 2 ) ;
}
else
{
binaryReader . ReadBytes ( chunkLength - 2 ) ;
}
}
throw new ArgumentException ( ErrorMessage ) ;
}
}
}