using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using OfficeOpenXml.Drawing; using OfficeOpenXml.Packaging.Ionic.Zlib; namespace Masuit.Tools.Excel; public static class ImageDetector { private const float MToInch = 39.3700787F; private const float CmToInch = MToInch * 0.01F; public const float MmToInch = CmToInch * 0.1F; private const float HundredthThMmToInch = MmToInch * 0.01F; internal const float StandardDPI = 96f; internal struct TifIfd { public short Tag; public short Type; public int Count; public int ValueOffset; } /// /// 获取图像格式 /// /// /// public static ePictureType? GetPictureType(Stream ms) { var br = new BinaryReader(ms); if (IsJpg(br)) { return ePictureType.Jpg; } if (IsBmp(br, out _)) { return ePictureType.Bmp; } else if (IsGif(br)) { return ePictureType.Gif; } else if (IsPng(br)) { return ePictureType.Png; } else if (IsTif(br, out _)) { return ePictureType.Tif; } else if (IsIco(br)) { return ePictureType.Ico; } else if (IsWebP(br)) { return ePictureType.WebP; } else if (IsEmf(br)) { return ePictureType.Emf; } else if (IsWmf(br)) { return ePictureType.Wmf; } else if (IsSvg(ms)) { return ePictureType.Svg; } else if (IsGZip(br)) { _ = ExtractImage(ToArray(ms), out ePictureType? pt); return pt; } return null; } /// /// /// /// /// public static byte[] ToArray(Stream stream) { stream.Position = 0; byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, bytes.Length); // 设置当前流的位置为流的开始 stream.Seek(0, SeekOrigin.Begin); return bytes; } private static bool IsGZip(BinaryReader br) { br.BaseStream.Position = 0; var sign = br.ReadBytes(2); return IsGZip(sign); } private static bool IsGZip(byte[] sign) { return sign.Length >= 2 && sign[0] == 0x1F && sign[1] == 0x8B; } internal static bool TryGetImageBounds(ePictureType pictureType, MemoryStream ms, ref double width, ref double height, out double horizontalResolution, out double verticalResolution) { width = 0; height = 0; horizontalResolution = verticalResolution = StandardDPI; try { ms.Seek(0, SeekOrigin.Begin); if (pictureType == ePictureType.Bmp && IsBmp(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } if (pictureType == ePictureType.Jpg && IsJpg(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } if (pictureType == ePictureType.Gif && IsGif(ms, ref width, ref height)) { return true; } if (pictureType == ePictureType.Png && IsPng(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } if (pictureType == ePictureType.Emf && IsEmf(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } if (pictureType == ePictureType.Wmf && IsWmf(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } else if (pictureType == ePictureType.Svg && IsSvg(ms, ref width, ref height)) { return true; } else if (pictureType == ePictureType.Tif && IsTif(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } else if (pictureType == ePictureType.WebP && IsWebP(ms, ref width, ref height, ref horizontalResolution, ref verticalResolution)) { return true; } else if (pictureType == ePictureType.Ico && IsIcon(ms, ref width, ref height)) { return true; } return false; } catch { return false; } } internal static byte[] ExtractImage(byte[] img, out ePictureType? type) { if (IsGZip(img)) { try { var ms = new MemoryStream(img); var msOut = new MemoryStream(); const int bufferSize = 4096; var buffer = new byte[bufferSize]; using (var z = new GZipStream(ms, CompressionMode.Decompress)) { int size = 0; do { size = z.Read(buffer, 0, bufferSize); if (size > 0) { msOut.Write(buffer, 0, size); } } while (size == bufferSize); msOut.Position = 0; var br = new BinaryReader(msOut); if (IsEmf(br)) { type = ePictureType.Emf; } else if (IsWmf(br)) { type = ePictureType.Wmf; } else { type = null; } msOut.Position = 0; return msOut.ToArray(); } } catch { type = null; return img; } } type = null; return img; } private static bool IsJpg(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { if (IsJpg(br)) { float xDensity = 1, yDensity = 1; while (ms.Position < ms.Length) { var id = GetUInt16BigEndian(br); var length = (int)GetInt16BigEndian(br); switch (id) { case 0xFFE0: var identifier = br.ReadBytes(5); //JFIF\0 var version = br.ReadBytes(2); var unit = br.ReadByte(); xDensity = (int)GetInt16BigEndian(br); yDensity = (int)GetInt16BigEndian(br); if (unit == 1) { horizontalResolution = xDensity; verticalResolution = yDensity; } else if (unit == 2) { horizontalResolution = xDensity * CmToInch; verticalResolution = yDensity * CmToInch; } ms.Position += length - 14; break; case 0xFFE1: var pos = ms.Position; identifier = br.ReadBytes(6); //EXIF\0\0 or //EXIF\FF\FF double w = 0, h = 0; ReadTiffHeader(br, ref w, ref h, ref horizontalResolution, ref verticalResolution); ms.Position = pos + length - 2; break; case 0xFFC0: case 0xFFC1: case 0xFFC2: var precision = br.ReadByte(); //Bits height = GetUInt16BigEndian(br); width = GetUInt16BigEndian(br); br.Close(); return true; case 0xFFD9: return height != 0 && width != 0; default: ms.Position += length - 2; break; } } } return false; } } private static bool IsJpg(BinaryReader br) { br.BaseStream.Position = 0; var sign = br.ReadBytes(2); //FF D8 return sign.Length >= 2 && sign[0] == 0xFF && sign[1] == 0xD8; } private static bool IsGif(MemoryStream ms, ref double width, ref double height) { using (var br = new BinaryReader(ms)) { if (IsGif(br)) { width = br.ReadUInt16(); height = br.ReadUInt16(); br.Close(); return true; } } return false; } private static bool IsGif(BinaryReader br) { br.BaseStream.Seek(0, SeekOrigin.Begin); var b = br.ReadBytes(6); return b[0] == 0x47 && b[1] == 0x49 && b[2] == 0x46; //byte 4-6 contains the version, but we don't check them here. } private static bool IsBmp(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { if (IsBmp(br, out string sign)) { var size = br.ReadInt32(); var reserved = br.ReadBytes(4); var offsetData = br.ReadInt32(); //Info Header var ihSize = br.ReadInt32(); //Should be 40 width = br.ReadInt32(); height = br.ReadInt32(); if (sign == "BM") { br.ReadBytes(12); horizontalResolution = br.ReadInt32() / MToInch; verticalResolution = br.ReadInt32() / MToInch; } else { horizontalResolution = verticalResolution = 1; } return true; } } return false; } internal static bool IsBmp(BinaryReader br, out string sign) { try { br.BaseStream.Seek(0, SeekOrigin.Begin); sign = Encoding.ASCII.GetString(br.ReadBytes(2)); //BM for a Windows bitmap return (sign == "BM" || sign == "BA" || sign == "CI" || sign == "CP" || sign == "IC" || sign == "PT"); } catch { sign = null; return false; } } #region Ico private static bool IsIcon(MemoryStream ms, ref double width, ref double height) { using (var br = new BinaryReader(ms)) { if (IsIco(br)) { var imageCount = br.ReadInt16(); width = br.ReadByte(); if (width == 0) width = 256; height = br.ReadByte(); if (height == 0) height = 256; br.Close(); return true; } br.Close(); return false; } } internal static bool IsIco(BinaryReader br) { br.BaseStream.Seek(0, SeekOrigin.Begin); var type0 = br.ReadInt16(); var type1 = br.ReadInt16(); return type0 == 0 && type1 == 1; } #endregion Ico #region WebP private static bool IsWebP(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { width = height = 0; horizontalResolution = verticalResolution = StandardDPI * (1 + 1 / 3); //Excel seems to render webp at 1 1/3 size. using (var br = new BinaryReader(ms)) { if (IsWebP(br)) { var vp8 = Encoding.ASCII.GetString(br.ReadBytes(4)); switch (vp8) { case "VP8 ": var b = br.ReadBytes(10); var w = br.ReadInt16(); width = w & 0x3FFF; var hScale = w >> 14; var h = br.ReadInt16(); height = h & 0x3FFF; hScale = h >> 14; break; case "VP8X": br.ReadBytes(8); b = br.ReadBytes(6); width = BitConverter.ToInt32(new byte[] { b[0], b[1], b[2], 0 }, 0) + 1; height = BitConverter.ToInt32(new byte[] { b[3], b[4], b[5], 0 }, 0) + 1; break; case "VP8L": br.ReadBytes(5); b = br.ReadBytes(4); width = (b[0] | (b[1] & 0x3F) << 8) + 1; height = (b[1] >> 6 | b[2] << 2 | (b[3] & 0x0F) << 10) + 1; break; } } } return width != 0 && height != 0; } internal static bool IsWebP(BinaryReader br) { try { br.BaseStream.Seek(0, SeekOrigin.Begin); var riff = Encoding.ASCII.GetString(br.ReadBytes(4)); var length = GetInt32BigEndian(br); var webP = Encoding.ASCII.GetString(br.ReadBytes(4)); return riff == "RIFF" && webP == "WEBP"; } catch { return false; } } #endregion WebP #region Tiff private static bool IsTif(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { return ReadTiffHeader(br, ref width, ref height, ref horizontalResolution, ref verticalResolution); } } private static bool ReadTiffHeader(BinaryReader br, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { var ms = br.BaseStream; var pos = ms.Position; if (IsTif(br, out bool isBigEndian, false)) { var offset = GetTifInt32(br, isBigEndian); ms.Position = pos + offset; var numberOfIdf = GetTifInt16(br, isBigEndian); var ifds = new List(); for (int i = 0; i < numberOfIdf; i++) { var ifd = new TifIfd() { Tag = GetTifInt16(br, isBigEndian), Type = GetTifInt16(br, isBigEndian), Count = GetTifInt32(br, isBigEndian), }; if (ifd.Type == 1 || ifd.Type == 2 || ifd.Type == 6 || ifd.Type == 7) { ifd.ValueOffset = br.ReadByte(); br.ReadBytes(3); } else if (ifd.Type == 3 || ifd.Type == 8) { ifd.ValueOffset = GetTifInt16(br, isBigEndian); br.ReadBytes(2); } else { ifd.ValueOffset = GetTifInt32(br, isBigEndian); } ifds.Add(ifd); } int resolutionUnit = 2; foreach (var ifd in ifds) { switch (ifd.Tag) { case 0x100: width = ifd.ValueOffset; break; case 0x101: height = ifd.ValueOffset; break; case 0x11A: ms.Position = ifd.ValueOffset + pos; var l1 = GetTifInt32(br, isBigEndian); var l2 = GetTifInt32(br, isBigEndian); horizontalResolution = l1 / l2; break; case 0x11B: ms.Position = ifd.ValueOffset + pos; l1 = GetTifInt32(br, isBigEndian); l2 = GetTifInt32(br, isBigEndian); verticalResolution = l1 / l2; break; case 0x128: resolutionUnit = ifd.ValueOffset; break; } } if (resolutionUnit == 1) { horizontalResolution *= CmToInch; verticalResolution *= CmToInch; } } return width != 0 && height != 0; } private static bool IsTif(BinaryReader br, out bool isBigEndian, bool resetPos = false) { try { if (resetPos) { br.BaseStream.Position = 0; } var b = br.ReadBytes(2); isBigEndian = Encoding.ASCII.GetString(b) == "MM"; var identifier = GetTifInt16(br, isBigEndian); if (identifier == 42) { return true; } } catch { isBigEndian = false; return false; } return false; } private static short GetTifInt16(BinaryReader br, bool isBigEndian) { if (isBigEndian) { return GetInt16BigEndian(br); } else { return br.ReadInt16(); } } private static int GetTifInt32(BinaryReader br, bool isBigEndian) { if (isBigEndian) { return GetInt32BigEndian(br); } else { return br.ReadInt32(); } } #endregion Tiff #region Emf private static bool IsEmf(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { if (IsEmf(br)) { var length = br.ReadInt32(); var bounds = new int[4]; bounds[0] = br.ReadInt32(); bounds[1] = br.ReadInt32(); bounds[2] = br.ReadInt32(); bounds[3] = br.ReadInt32(); var frame = new int[4]; frame[0] = br.ReadInt32(); frame[1] = br.ReadInt32(); frame[2] = br.ReadInt32(); frame[3] = br.ReadInt32(); var signatureBytes = br.ReadBytes(4); var signature = Encoding.ASCII.GetString(signatureBytes); if (signature.Trim() == "EMF") { var version = br.ReadUInt32(); var size = br.ReadUInt32(); var records = br.ReadUInt32(); var handles = br.ReadUInt16(); var reserved = br.ReadUInt16(); var nDescription = br.ReadUInt32(); var offDescription = br.ReadUInt32(); var nPalEntries = br.ReadUInt32(); var device = new uint[2]; device[0] = br.ReadUInt32(); device[1] = br.ReadUInt32(); var mm = new uint[2]; mm[0] = br.ReadUInt32(); mm[1] = br.ReadUInt32(); //Extension 1 var cbPixelFormat = br.ReadUInt32(); var offPixelFormat = br.ReadUInt32(); var bOpenGL = br.ReadUInt32(); //Extension 2 var hr = br.ReadUInt32(); var vr = br.ReadUInt32(); var id = br.ReadInt32(); var size2 = br.ReadInt32(); width = (bounds[2] - bounds[0] + 1); height = (bounds[3] - bounds[1] + 1); horizontalResolution = width / ((frame[2] - frame[0]) * HundredthThMmToInch * StandardDPI) * StandardDPI; verticalResolution = height / ((frame[3] - frame[1]) * HundredthThMmToInch * StandardDPI) * StandardDPI; return true; } } } return false; } private static bool IsEmf(BinaryReader br) { br.BaseStream.Position = 0; var type = br.ReadInt32(); return type == 1; } #endregion Emf #region Wmf private const double PIXELS_PER_TWIPS = 1D / 15D; private const double DEFAULT_TWIPS = 1440D; private static bool IsWmf(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { if (IsWmf(br)) { var HWmf = br.ReadInt16(); var bounds = new ushort[4]; bounds[0] = br.ReadUInt16(); bounds[1] = br.ReadUInt16(); bounds[2] = br.ReadUInt16(); bounds[3] = br.ReadUInt16(); var inch = br.ReadInt16(); width = bounds[2] - bounds[0]; height = bounds[3] - bounds[1]; if (inch != 0) { width *= (DEFAULT_TWIPS / inch) * PIXELS_PER_TWIPS; height *= (DEFAULT_TWIPS / inch) * PIXELS_PER_TWIPS; } return width != 0 && height != 0; } } return false; } private static bool IsWmf(BinaryReader br) { br.BaseStream.Position = 0; var key = br.ReadUInt32(); return key == 0x9AC6CDD7; } #endregion Wmf #region Png private static bool IsPng(MemoryStream ms, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution) { using (var br = new BinaryReader(ms)) { return IsPng(br, ref width, ref height, ref horizontalResolution, ref verticalResolution); } } private static bool IsPng(BinaryReader br, ref double width, ref double height, ref double horizontalResolution, ref double verticalResolution, long fileEndPosition = long.MinValue) { if (IsPng(br)) { if (fileEndPosition == long.MinValue) { fileEndPosition = br.BaseStream.Length; } while (br.BaseStream.Position < fileEndPosition) { var chunkType = ReadPngChunkHeader(br, out int length); switch (chunkType) { case "IHDR": width = GetInt32BigEndian(br); height = GetInt32BigEndian(br); br.ReadBytes(5); //Ignored bytes, Depth compression etc. break; case "pHYs": horizontalResolution = GetInt32BigEndian(br); verticalResolution = GetInt32BigEndian(br); var unitSpecifier = br.ReadByte(); if (unitSpecifier == 1) { horizontalResolution /= MToInch; verticalResolution /= MToInch; } br.Close(); return true; default: br.ReadBytes(length); break; } var crc = br.ReadInt32(); } } br.Close(); return width != 0 && height != 0; } private static bool IsPng(BinaryReader br) { br.BaseStream.Position = 0; var signature = br.ReadBytes(8); return signature.SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }); } private static string ReadPngChunkHeader(BinaryReader br, out int length) { length = GetInt32BigEndian(br); var b = br.ReadBytes(4); var type = Encoding.ASCII.GetString(b); return type; } #endregion Png #region Svg private static bool IsSvg(MemoryStream ms, ref double width, ref double height) { try { var reader = new XmlTextReader(ms); while (reader.Read()) { if (reader.LocalName == "svg" && reader.NodeType == XmlNodeType.Element) { var w = reader.GetAttribute("width"); var h = reader.GetAttribute("height"); var vb = reader.GetAttribute("viewBox"); reader.Close(); if (w == null || h == null) { if (vb == null) { return false; } var bounds = vb.Split(new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); if (bounds.Length < 4) { return false; } if (string.IsNullOrEmpty(w)) { w = bounds[2]; } if (string.IsNullOrEmpty(h)) { h = bounds[3]; } } width = GetSvgUnit(w); if (double.IsNaN(width)) return false; height = GetSvgUnit(h); if (double.IsNaN(height)) return false; return true; } } return false; } catch { return false; } } private static bool IsSvg(Stream ms) { try { ms.Position = 0; var reader = new XmlTextReader(ms); while (reader.Read()) { if (reader.LocalName == "svg" && reader.NodeType == XmlNodeType.Element) { return true; } } return false; } catch { return false; } } private static double GetSvgUnit(string v) { var factor = 1D; if (v.EndsWith("px", StringComparison.OrdinalIgnoreCase)) { v = v.Substring(0, v.Length - 2); } else if (v.EndsWith("pt", StringComparison.OrdinalIgnoreCase)) { factor = 1.25; v = v.Substring(0, v.Length - 2); } else if (v.EndsWith("pc", StringComparison.OrdinalIgnoreCase)) { factor = 15; v = v.Substring(0, v.Length - 2); } else if (v.EndsWith("mm", StringComparison.OrdinalIgnoreCase)) { factor = 3.543307; v = v.Substring(0, v.Length - 2); } else if (v.EndsWith("cm", StringComparison.OrdinalIgnoreCase)) { factor = 35.43307; v = v.Substring(0, v.Length - 2); } else if (v.EndsWith("in", StringComparison.OrdinalIgnoreCase)) { factor = 90; v = v.Substring(0, v.Length - 2); } if (double.TryParse(v, out double value)) { return value * factor; } return double.NaN; } #endregion Svg private static ushort GetUInt16BigEndian(BinaryReader br) { var b = br.ReadBytes(2); return BitConverter.ToUInt16(new byte[] { b[1], b[0] }, 0); } private static short GetInt16BigEndian(BinaryReader br) { var b = br.ReadBytes(2); return BitConverter.ToInt16(new byte[] { b[1], b[0] }, 0); } private static int GetInt32BigEndian(BinaryReader br) { var b = br.ReadBytes(4); return BitConverter.ToInt32(new byte[] { b[3], b[2], b[1], b[0] }, 0); } }