MemoryImage.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using BYTES = System.ArraySegment<System.Byte>;
  5. namespace SharpGLTF.Memory
  6. {
  7. /// <summary>
  8. /// Represents an image file stored as an in-memory byte array
  9. /// </summary>
  10. public readonly struct MemoryImage
  11. {
  12. #region constants
  13. const string EMBEDDED_OCTET_STREAM = "data:application/octet-stream;base64,";
  14. const string EMBEDDED_GLTF_BUFFER = "data:application/gltf-buffer;base64,";
  15. const string EMBEDDED_JPEG_BUFFER = "data:image/jpeg;base64,";
  16. const string EMBEDDED_PNG_BUFFER = "data:image/png;base64,";
  17. const string EMBEDDED_DDS_BUFFER = "data:image/vnd-ms.dds;base64,";
  18. const string EMBEDDED_WEBP_BUFFER = "data:image/webp;base64,";
  19. const string MIME_PNG = "image/png";
  20. const string MIME_JPG = "image/jpeg";
  21. const string MIME_DDS = "image/vnd-ms.dds";
  22. const string MIME_WEBP = "image/webp";
  23. private const string DEFAULT_PNG_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==";
  24. internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
  25. public static MemoryImage Empty => default;
  26. #endregion
  27. #region constructor
  28. public static implicit operator MemoryImage(BYTES image) { return new MemoryImage(image); }
  29. public static implicit operator MemoryImage(Byte[] image) { return new MemoryImage(image); }
  30. public MemoryImage(BYTES image) { _Image = image; }
  31. public MemoryImage(Byte[] image) { _Image = image == null ? default : new BYTES(image); }
  32. public MemoryImage(string filePath)
  33. {
  34. var data = System.IO.File.ReadAllBytes(filePath);
  35. _Image = new BYTES(data);
  36. }
  37. #endregion
  38. #region data
  39. private readonly BYTES _Image;
  40. #endregion
  41. #region properties
  42. public bool IsEmpty => _Image.Count == 0;
  43. /// <summary>
  44. /// Gets a value indicating whether this object represents a valid PNG image.
  45. /// </summary>
  46. public bool IsPng => _IsPngImage(_Image);
  47. /// <summary>
  48. /// Gets a value indicating whether this object represents a valid JPG image.
  49. /// </summary>
  50. public bool IsJpg => _IsJpgImage(_Image);
  51. /// <summary>
  52. /// Gets a value indicating whether this object represents a valid DDS image.
  53. /// </summary>
  54. public bool IsDds => _IsDdsImage(_Image);
  55. /// <summary>
  56. /// Gets a value indicating whether this object represents a valid WEBP image.
  57. /// </summary>
  58. public bool IsWebp => _IsWebpImage(_Image);
  59. /// <summary>
  60. /// Gets a value indicating whether this object represents a valid image.
  61. /// </summary>
  62. public bool IsValid => _IsImage(_Image);
  63. /// <summary>
  64. /// Gets the most appropriate extension string for this image.
  65. /// </summary>
  66. public string FileExtension
  67. {
  68. get
  69. {
  70. if (IsPng) return "png";
  71. if (IsJpg) return "jpg";
  72. if (IsDds) return "dds";
  73. if (IsWebp) return "webp";
  74. throw new NotImplementedException();
  75. }
  76. }
  77. /// <summary>
  78. /// Gets the most appropriate Mime type string for this image.
  79. /// </summary>
  80. public string MimeType
  81. {
  82. get
  83. {
  84. if (IsPng) return MIME_PNG;
  85. if (IsJpg) return MIME_JPG;
  86. if (IsDds) return MIME_DDS;
  87. if (IsWebp) return MIME_WEBP;
  88. return "raw";
  89. }
  90. }
  91. #endregion
  92. #region API
  93. /// <summary>
  94. /// Opens the image file for reading its contents
  95. /// </summary>
  96. /// <returns>A read only <see cref="System.IO.Stream"/>.</returns>
  97. public System.IO.Stream Open()
  98. {
  99. if (_Image.Count == 0) return null;
  100. return new System.IO.MemoryStream(_Image.Array, _Image.Offset, _Image.Count, false);
  101. }
  102. /// <summary>
  103. /// Returns this image file, enconded as a Mime64 string.
  104. /// </summary>
  105. /// <param name="withPrefix">true to prefix the string with a header.</param>
  106. /// <returns>A mime64 string.</returns>
  107. public string ToMime64(bool withPrefix = true)
  108. {
  109. if (!this.IsValid) return null;
  110. var mimeContent = string.Empty;
  111. if (withPrefix)
  112. {
  113. if (this.IsPng) mimeContent = EMBEDDED_PNG_BUFFER;
  114. if (this.IsJpg) mimeContent = EMBEDDED_JPEG_BUFFER;
  115. if (this.IsDds) mimeContent = EMBEDDED_DDS_BUFFER;
  116. if (this.IsWebp) mimeContent = EMBEDDED_WEBP_BUFFER;
  117. }
  118. return mimeContent + Convert.ToBase64String(_Image.Array, _Image.Offset, _Image.Count, Base64FormattingOptions.None);
  119. }
  120. /// <summary>
  121. /// Gets the internal buffer.
  122. /// </summary>
  123. /// <returns>An array buffer.</returns>
  124. public BYTES GetBuffer() { return _Image; }
  125. /// <summary>
  126. /// Tries to parse a Mime64 string to a Byte array.
  127. /// </summary>
  128. /// <param name="mime64content">The Mime64 string source.</param>
  129. /// <returns>A byte array representing an image file, or null if the image was not identified.</returns>
  130. public static Byte[] TryParseBytes(string mime64content)
  131. {
  132. return _TryParseBase64Unchecked(mime64content, EMBEDDED_GLTF_BUFFER)
  133. ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_OCTET_STREAM)
  134. ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_JPEG_BUFFER)
  135. ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_PNG_BUFFER)
  136. ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_DDS_BUFFER)
  137. ?? null;
  138. }
  139. /// <summary>
  140. /// identifies an image of a specific type.
  141. /// </summary>
  142. /// <param name="format">A string representing the format: png, jpg, dds...</param>
  143. /// <returns>True if this image is of the given type.</returns>
  144. public bool IsImageOfType(string format)
  145. {
  146. Guard.NotNullOrEmpty(format, nameof(format));
  147. if (!IsValid) return false;
  148. if (format.EndsWith("png", StringComparison.OrdinalIgnoreCase)) return IsPng;
  149. if (format.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)) return IsJpg;
  150. if (format.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase)) return IsJpg;
  151. if (format.EndsWith("dds", StringComparison.OrdinalIgnoreCase)) return IsDds;
  152. if (format.EndsWith("webp", StringComparison.OrdinalIgnoreCase)) return IsWebp;
  153. return false;
  154. }
  155. #endregion
  156. #region internals
  157. private static Byte[] _TryParseBase64Unchecked(string uri, string prefix)
  158. {
  159. if (uri == null) return null;
  160. if (!uri.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;
  161. var content = uri.Substring(prefix.Length);
  162. return Convert.FromBase64String(content);
  163. }
  164. private static bool _IsPngImage(IReadOnlyList<Byte> data)
  165. {
  166. if (data[0] != 0x89) return false;
  167. if (data[1] != 0x50) return false;
  168. if (data[2] != 0x4e) return false;
  169. if (data[3] != 0x47) return false;
  170. return true;
  171. }
  172. private static bool _IsJpgImage(IReadOnlyList<Byte> data)
  173. {
  174. if (data[0] != 0xff) return false;
  175. if (data[1] != 0xd8) return false;
  176. return true;
  177. }
  178. private static bool _IsDdsImage(IReadOnlyList<Byte> data)
  179. {
  180. if (data[0] != 0x44) return false;
  181. if (data[1] != 0x44) return false;
  182. if (data[2] != 0x53) return false;
  183. if (data[3] != 0x20) return false;
  184. return true;
  185. }
  186. private static bool _IsWebpImage(IReadOnlyList<Byte> data)
  187. {
  188. // RIFF
  189. if (data[0] != 0x52) return false;
  190. if (data[1] != 0x49) return false;
  191. if (data[2] != 0x46) return false;
  192. if (data[3] != 0x46) return false;
  193. // WEBP
  194. if (data[8] != 0x57) return false;
  195. if (data[9] != 0x45) return false;
  196. if (data[10] != 0x42) return false;
  197. if (data[11] != 0x50) return false;
  198. return true;
  199. }
  200. private static bool _IsImage(IReadOnlyList<Byte> data)
  201. {
  202. if (data == null) return false;
  203. if (data.Count < 12) return false;
  204. if (_IsDdsImage(data)) return true;
  205. if (_IsJpgImage(data)) return true;
  206. if (_IsPngImage(data)) return true;
  207. if (_IsWebpImage(data)) return true;
  208. return false;
  209. }
  210. #endregion
  211. }
  212. }