using System.Drawing; namespace View_by_Distance.Metadata.Models.Stateless.Methods; internal static class Dimensions { const string _ErrorMessage = "Could not recognize image format."; #pragma warning disable IDE0230 private static readonly Dictionary> _ImageFormatDecoders = new() { { 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 }, { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP }, }; #pragma warning restore IDE0230 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; } private static short ReadLittleEndianInt16(BinaryReader binaryReader) { byte[] 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); } private static int ReadLittleEndianInt32(BinaryReader binaryReader) { byte[] 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); } private static Size DecodeBitmap(BinaryReader binaryReader) { _ = binaryReader.ReadBytes(16); int width = binaryReader.ReadInt32(); int height = binaryReader.ReadInt32(); return new Size(width, height); } private static Size DecodeGif(BinaryReader binaryReader) { int width = binaryReader.ReadInt16(); int height = binaryReader.ReadInt16(); return new Size(width, height); } private static Size DecodePng(BinaryReader binaryReader) { _ = binaryReader.ReadBytes(8); int width = ReadLittleEndianInt32(binaryReader); int height = ReadLittleEndianInt32(binaryReader); return new Size(width, height); } private static Size 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 Size(width, height); } if (chunkLength < 0) { ushort uChunkLength = (ushort)chunkLength; _ = binaryReader.ReadBytes(uChunkLength - 2); } else { _ = binaryReader.ReadBytes(chunkLength - 2); } } throw new ArgumentException(_ErrorMessage); } private static Size DecodeWebP(BinaryReader binaryReader) { _ = binaryReader.ReadUInt32(); // Size _ = binaryReader.ReadBytes(15); // WEBP, VP8 + more _ = binaryReader.ReadBytes(3); // SYNC int width = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits width int height = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits height return new Size(width, height); } /// /// Gets the dimensions of an image. /// /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. public static Size GetDimensions(BinaryReader binaryReader) { int maxMagicBytesLength = _ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; byte[] magicBytes = new byte[maxMagicBytesLength]; for (int i = 0; i < maxMagicBytesLength; i += 1) { magicBytes[i] = binaryReader.ReadByte(); foreach (KeyValuePair> kvPair in _ImageFormatDecoders) { if (StartsWith(magicBytes, kvPair.Key)) { return kvPair.Value(binaryReader); } } } throw new ArgumentException(_ErrorMessage, nameof(binaryReader)); } /// /// Gets the dimensions of an image. /// /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. public static Size GetDimensions(string path) { using BinaryReader binaryReader = new(File.OpenRead(path)); try { return GetDimensions(binaryReader); } catch (ArgumentException e) { if (e.Message.StartsWith(_ErrorMessage)) { throw new ArgumentException(_ErrorMessage, nameof(path), e); } else { throw; } } } }