فهرست منبع

Centraliced image file management to a new InMemoryImage object.

Vicente Penades 5 سال پیش
والد
کامیت
759df59e46

+ 0 - 74
src/Shared/_Extensions.cs

@@ -345,80 +345,6 @@ namespace SharpGLTF
 
         #endregion
 
-        #region images
-
-        internal static bool _IsPngImage(this IReadOnlyList<Byte> data)
-        {
-            if (data[0] != 0x89) return false;
-            if (data[1] != 0x50) return false;
-            if (data[2] != 0x4e) return false;
-            if (data[3] != 0x47) return false;
-
-            return true;
-        }
-
-        internal static bool _IsJpgImage(this IReadOnlyList<Byte> data)
-        {
-            if (data[0] != 0xff) return false;
-            if (data[1] != 0xd8) return false;
-
-            return true;
-        }
-
-        internal static bool _IsDdsImage(this IReadOnlyList<Byte> data)
-        {
-            if (data[0] != 0x44) return false;
-            if (data[1] != 0x44) return false;
-            if (data[2] != 0x53) return false;
-            if (data[3] != 0x20) return false;
-            return true;
-        }
-
-        internal static bool _IsWebpImage(this IReadOnlyList<Byte> data)
-        {
-            // RIFF
-            if (data[0] != 0x52) return false;
-            if (data[1] != 0x49) return false;
-            if (data[2] != 0x46) return false;
-            if (data[3] != 0x46) return false;
-
-            // WEBP
-            if (data[8] != 0x57) return false;
-            if (data[9] != 0x45) return false;
-            if (data[10] != 0x42) return false;
-            if (data[11] != 0x50) return false;
-
-            return true;
-        }
-
-        internal static bool _IsImage(this IReadOnlyList<Byte> data)
-        {
-            if (data == null) return false;
-            if (data.Count < 12) return false;
-
-            if (data._IsDdsImage()) return true;
-            if (data._IsJpgImage()) return true;
-            if (data._IsPngImage()) return true;
-            if (data._IsWebpImage()) return true;
-
-            return false;
-        }
-
-        internal static bool _IsImage(this IReadOnlyList<Byte> image, string format)
-        {
-            if (string.IsNullOrWhiteSpace(format)) return image._IsImage();
-
-            if (format.EndsWith("png", StringComparison.OrdinalIgnoreCase)) return image._IsPngImage();
-            if (format.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)) return image._IsJpgImage();
-            if (format.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase)) return image._IsJpgImage();
-            if (format.EndsWith("dds", StringComparison.OrdinalIgnoreCase)) return image._IsDdsImage();
-            if (format.EndsWith("webp", StringComparison.OrdinalIgnoreCase)) return image._IsWebpImage();
-
-            return false;
-        }
-
-        #endregion
-
         #region vertex & index accessors
 
         public static String ToDebugString(this EncodingType encoding, DimensionType dimensions, bool normalized)

+ 259 - 0
src/SharpGLTF.Core/Memory/InMemoryImage.cs

@@ -0,0 +1,259 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Memory
+{
+    /// <summary>
+    /// Represents an image file stored as an in-memory byte array
+    /// </summary>
+    public readonly struct InMemoryImage
+    {
+        #region constants
+
+        const string EMBEDDED_OCTET_STREAM = "data:application/octet-stream;base64,";
+        const string EMBEDDED_GLTF_BUFFER = "data:application/gltf-buffer;base64,";
+        const string EMBEDDED_JPEG_BUFFER = "data:image/jpeg;base64,";
+        const string EMBEDDED_PNG_BUFFER = "data:image/png;base64,";
+        const string EMBEDDED_DDS_BUFFER = "data:image/vnd-ms.dds;base64,";
+        const string EMBEDDED_WEBP_BUFFER = "data:image/webp;base64,";
+
+        const string MIME_PNG = "image/png";
+        const string MIME_JPG = "image/jpeg";
+        const string MIME_DDS = "image/vnd-ms.dds";
+        const string MIME_WEBP = "image/webp";
+
+        private const string DEFAULT_PNG_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==";
+
+        internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
+
+        public static InMemoryImage Empty => default;
+
+        #endregion
+
+        #region constructor
+
+        public static implicit operator InMemoryImage(ArraySegment<Byte> image) { return new InMemoryImage(image); }
+
+        public static implicit operator InMemoryImage(Byte[] image) { return new InMemoryImage(image); }
+
+        public InMemoryImage(ArraySegment<Byte> image) { _Image = image; }
+
+        public InMemoryImage(Byte[] image) { _Image = image == null ? default : new ArraySegment<byte>(image); }
+
+        #endregion
+
+        #region data
+
+        private readonly ArraySegment<Byte> _Image;
+
+        #endregion
+
+        #region properties
+
+        public bool IsEmpty => _Image.Count == 0;
+
+        /// <summary>
+        /// Gets a value indicating whether this object represents a valid PNG image.
+        /// </summary>
+        public bool IsPng => _IsPngImage(_Image);
+
+        /// <summary>
+        /// Gets a value indicating whether this object represents a valid JPG image.
+        /// </summary>
+        public bool IsJpg => _IsJpgImage(_Image);
+
+        /// <summary>
+        /// Gets a value indicating whether this object represents a valid DDS image.
+        /// </summary>
+        public bool IsDds => _IsDdsImage(_Image);
+
+        /// <summary>
+        /// Gets a value indicating whether this object represents a valid WEBP image.
+        /// </summary>
+        public bool IsWebp => _IsWebpImage(_Image);
+
+        /// <summary>
+        /// Gets a value indicating whether this object represents a valid image.
+        /// </summary>
+        public bool IsValid => _IsImage(_Image);
+
+        /// <summary>
+        /// Gets the most appropriate extension string for this image.
+        /// </summary>
+        public string FileExtension
+        {
+            get
+            {
+                if (IsPng) return "png";
+                if (IsJpg) return "jpg";
+                if (IsDds) return "dds";
+                if (IsWebp) return "webp";
+                throw new NotImplementedException();
+            }
+        }
+
+        /// <summary>
+        /// Gets the most appropriate Mime type string for this image.
+        /// </summary>
+        public string MimeType
+        {
+            get
+            {
+                if (IsPng) return MIME_PNG;
+                if (IsJpg) return MIME_JPG;
+                if (IsDds) return MIME_DDS;
+                if (IsWebp) return MIME_WEBP;
+                return "raw";
+            }
+        }
+
+        #endregion
+
+        #region API
+
+        /// <summary>
+        /// Opens the image file for reading its contents
+        /// </summary>
+        /// <returns>A read only <see cref="System.IO.Stream"/>.</returns>
+        public System.IO.Stream Open()
+        {
+            if (_Image.Count == 0) return null;
+            return new System.IO.MemoryStream(_Image.Array, _Image.Offset, _Image.Count, false);
+        }
+
+        /// <summary>
+        /// Returns this image file, enconded as a Mime64 string.
+        /// </summary>
+        /// <param name="withPrefix">true to prefix the string with a header.</param>
+        /// <returns>A mime64 string.</returns>
+        public string ToMime64(bool withPrefix = true)
+        {
+            if (!this.IsValid) return null;
+
+            var mimeContent = string.Empty;
+            if (withPrefix)
+            {
+                if (this.IsPng) mimeContent = EMBEDDED_PNG_BUFFER;
+                if (this.IsJpg) mimeContent = EMBEDDED_JPEG_BUFFER;
+                if (this.IsDds) mimeContent = EMBEDDED_DDS_BUFFER;
+                if (this.IsWebp) mimeContent = EMBEDDED_WEBP_BUFFER;
+            }
+
+            return mimeContent + Convert.ToBase64String(_Image.Array, _Image.Offset, _Image.Count, Base64FormattingOptions.None);
+        }
+
+        /// <summary>
+        /// Gets the internal buffer.
+        /// </summary>
+        /// <returns>An array buffer.</returns>
+        public ArraySegment<Byte> GetBuffer() { return _Image; }
+
+        /// <summary>
+        /// Tries to parse a Mime64 string to a Byte array.
+        /// </summary>
+        /// <param name="mime64content">The Mime64 string source.</param>
+        /// <returns>A byte array representing an image file, or null if the image was not identified.</returns>
+        public static Byte[] TryParseBytes(string mime64content)
+        {
+            return _TryParseBase64Unchecked(mime64content, EMBEDDED_GLTF_BUFFER)
+                ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_OCTET_STREAM)
+                ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_JPEG_BUFFER)
+                ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_PNG_BUFFER)
+                ?? _TryParseBase64Unchecked(mime64content, EMBEDDED_DDS_BUFFER)
+                ?? null;
+        }
+
+        /// <summary>
+        /// identifies an image of a specific type.
+        /// </summary>
+        /// <param name="format">A string representing the format: png, jpg, dds...</param>
+        /// <returns>True if this image is of the given type.</returns>
+        public bool IsImageOfType(string format)
+        {
+            Guard.NotNullOrEmpty(format, nameof(format));
+
+            if (!IsValid) return false;
+
+            if (format.EndsWith("png", StringComparison.OrdinalIgnoreCase)) return IsPng;
+            if (format.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)) return IsJpg;
+            if (format.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase)) return IsJpg;
+            if (format.EndsWith("dds", StringComparison.OrdinalIgnoreCase)) return IsDds;
+            if (format.EndsWith("webp", StringComparison.OrdinalIgnoreCase)) return IsWebp;
+
+            return false;
+        }
+
+        #endregion
+
+        #region internals
+
+        private static Byte[] _TryParseBase64Unchecked(string uri, string prefix)
+        {
+            if (uri == null) return null;
+            if (!uri.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;
+
+            var content = uri.Substring(prefix.Length);
+            return Convert.FromBase64String(content);
+        }
+
+        private static bool _IsPngImage(IReadOnlyList<Byte> data)
+        {
+            if (data[0] != 0x89) return false;
+            if (data[1] != 0x50) return false;
+            if (data[2] != 0x4e) return false;
+            if (data[3] != 0x47) return false;
+
+            return true;
+        }
+
+        private static bool _IsJpgImage(IReadOnlyList<Byte> data)
+        {
+            if (data[0] != 0xff) return false;
+            if (data[1] != 0xd8) return false;
+
+            return true;
+        }
+
+        private static bool _IsDdsImage(IReadOnlyList<Byte> data)
+        {
+            if (data[0] != 0x44) return false;
+            if (data[1] != 0x44) return false;
+            if (data[2] != 0x53) return false;
+            if (data[3] != 0x20) return false;
+            return true;
+        }
+
+        private static bool _IsWebpImage(IReadOnlyList<Byte> data)
+        {
+            // RIFF
+            if (data[0] != 0x52) return false;
+            if (data[1] != 0x49) return false;
+            if (data[2] != 0x46) return false;
+            if (data[3] != 0x46) return false;
+
+            // WEBP
+            if (data[8] != 0x57) return false;
+            if (data[9] != 0x45) return false;
+            if (data[10] != 0x42) return false;
+            if (data[11] != 0x50) return false;
+
+            return true;
+        }
+
+        private static bool _IsImage(IReadOnlyList<Byte> data)
+        {
+            if (data == null) return false;
+            if (data.Count < 12) return false;
+
+            if (_IsDdsImage(data)) return true;
+            if (_IsJpgImage(data)) return true;
+            if (_IsPngImage(data)) return true;
+            if (_IsWebpImage(data)) return true;
+
+            return false;
+        }
+
+        #endregion
+    }
+}

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.Accessors.cs

@@ -493,7 +493,7 @@ namespace SharpGLTF.Schema2
 
             // when Dimensions == VEC3, its morph target tangent deltas
 
-            if (Dimensions == DimensionType.VEC4) 
+            if (Dimensions == DimensionType.VEC4)
             {
                 var tangents = this.AsVector4Array();
 

+ 44 - 130
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -10,26 +10,6 @@ namespace SharpGLTF.Schema2
     [System.Diagnostics.DebuggerDisplay("Image[{LogicalIndex}] {Name}")]
     public sealed partial class Image
     {
-        #region Base64 constants
-
-        const string EMBEDDED_OCTET_STREAM = "data:application/octet-stream;base64,";
-        const string EMBEDDED_GLTF_BUFFER = "data:application/gltf-buffer;base64,";
-        const string EMBEDDED_JPEG_BUFFER = "data:image/jpeg;base64,";
-        const string EMBEDDED_PNG_BUFFER = "data:image/png;base64,";
-        const string EMBEDDED_DDS_BUFFER = "data:image/vnd-ms.dds;base64,";
-        const string EMBEDDED_WEBP_BUFFER = "data:image/webp;base64,";
-
-        const string MIME_PNG = "image/png";
-        const string MIME_JPG = "image/jpeg";
-        const string MIME_DDS = "image/vnd-ms.dds";
-        const string MIME_WEBP = "image/webp";
-
-        const string DEFAULT_PNG_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==";
-
-        internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
-
-        #endregion
-
         #region lifecycle
 
         internal Image() { }
@@ -64,40 +44,40 @@ namespace SharpGLTF.Schema2
         /// </summary>
         public bool IsSatelliteFile => _SatelliteImageContent != null;
 
+        /// <summary>
+        /// Returns the in-memory representation of the image file.
+        /// </summary>
+        public Memory.InMemoryImage MemoryImage => new Memory.InMemoryImage(GetImageContent());
+
         /// <summary>
         /// Gets a value indicating whether the contained image is a PNG image.
         /// </summary>
-        public bool IsPng => GetImageContent()._IsPngImage();
+        [Obsolete("Use MemoryImage property")]
+        public bool IsPng => this.MemoryImage.IsPng;
 
         /// <summary>
         /// Gets a value indicating whether the contained image is a JPEG image.
         /// </summary>
-        public bool IsJpeg => GetImageContent()._IsJpgImage();
+        [Obsolete("Use MemoryImage property")]
+        public bool IsJpeg => this.MemoryImage.IsJpg;
 
         /// <summary>
         /// Gets a value indicating whether the contained image is a DDS image.
         /// </summary>
-        public bool IsDds => GetImageContent()._IsDdsImage();
+        [Obsolete("Use MemoryImage property")]
+        public bool IsDds => this.MemoryImage.IsDds;
 
         /// <summary>
         /// Gets a value indicating whether the contained image is a WEBP image.
         /// </summary>
-        public bool IsWebp => GetImageContent()._IsWebpImage();
+        [Obsolete("Use MemoryImage property")]
+        public bool IsWebp => this.MemoryImage.IsWebp;
 
         /// <summary>
         /// Gets the filename extension of the image that can be retrieved with <see cref="GetImageContent"/>
         /// </summary>
-        public string FileExtension
-        {
-            get
-            {
-                if (IsPng) return "png";
-                if (IsJpeg) return "jpg";
-                if (IsDds) return "dds";
-                if (IsWebp) return "webp";
-                return "raw";
-            }
-        }
+        [Obsolete("Use MemoryImage property")]
+        public string FileExtension => this.MemoryImage.FileExtension;
 
         internal int _SourceBufferViewIndex => _bufferView.AsValue(-1);
 
@@ -119,12 +99,10 @@ namespace SharpGLTF.Schema2
         /// Opens the image file.
         /// </summary>
         /// <returns>A <see cref="System.IO.Stream"/> containing the image file.</returns>
+        [Obsolete("Use MemoryImage property")]
         public System.IO.Stream OpenImageFile()
         {
-            var content = GetImageContent();
-            if (content.Count == 0) return null;
-
-            return new System.IO.MemoryStream(content.Array, content.Offset, content.Count, false);
+            return this.MemoryImage.Open();
         }
 
         /// <summary>
@@ -169,14 +147,10 @@ namespace SharpGLTF.Schema2
         {
             Guard.NotNull(content, nameof(content));
 
-            string imageType = null;
+            var imimg = new Memory.InMemoryImage(content);
+            if (!imimg.IsValid) throw new ArgumentException($"{nameof(content)} must be a PNG, JPG, DDS or WEBP image", nameof(content));
 
-            if (content._IsPngImage()) imageType = MIME_PNG;
-            if (content._IsJpgImage()) imageType = MIME_JPG;
-            if (content._IsDdsImage()) imageType = MIME_DDS;
-            if (content._IsWebpImage()) imageType = MIME_WEBP;
-
-            if (imageType == null) throw new ArgumentException($"{nameof(content)} must be a PNG, JPG, DDS or WEBP image", nameof(content));
+            string imageType = imimg.MimeType;
 
             _DiscardContent();
 
@@ -207,24 +181,22 @@ namespace SharpGLTF.Schema2
 
         internal void _ResolveUri(IO.ReadContext context)
         {
-            if (!String.IsNullOrWhiteSpace(_uri))
+            if (String.IsNullOrWhiteSpace(_uri)) return;
+
+            var data = Memory.InMemoryImage.TryParseBytes(_uri);
+
+            if (data == null)
             {
-                var data = _LoadImageUnchecked(context, _uri);
+                var bytes = context.ReadAllBytesToEnd(_uri);
 
-                _SatelliteImageContent = data;
-                _uri = null;
-                _mimeType = null;
+                // let's try to avoid making a copy if it's not neccesary.
+                if (bytes.Offset == 0 && bytes.Array.Length == bytes.Count) data = bytes.Array;
+                else data = bytes.ToArray();
             }
-        }
 
-        private static Byte[] _LoadImageUnchecked(IO.ReadContext context, string uri)
-        {
-            return uri._TryParseBase64Unchecked(EMBEDDED_GLTF_BUFFER)
-                ?? uri._TryParseBase64Unchecked(EMBEDDED_OCTET_STREAM)
-                ?? uri._TryParseBase64Unchecked(EMBEDDED_JPEG_BUFFER)
-                ?? uri._TryParseBase64Unchecked(EMBEDDED_PNG_BUFFER)
-                ?? uri._TryParseBase64Unchecked(EMBEDDED_DDS_BUFFER)
-                ?? context.ReadAllBytesToEnd(uri).ToArray();
+            _SatelliteImageContent = data;
+            _uri = null;
+            _mimeType = null;
         }
 
         internal void _DiscardContent()
@@ -246,37 +218,9 @@ namespace SharpGLTF.Schema2
         {
             if (_SatelliteImageContent == null) { _WriteAsBufferView(); return; }
 
-            var mimeContent = Convert.ToBase64String(_SatelliteImageContent, Base64FormattingOptions.None);
-
-            if (_SatelliteImageContent._IsPngImage())
-            {
-                _mimeType = MIME_PNG;
-                _uri = EMBEDDED_PNG_BUFFER + mimeContent;
-                return;
-            }
-
-            if (_SatelliteImageContent._IsJpgImage())
-            {
-                _mimeType = MIME_JPG;
-                _uri = EMBEDDED_JPEG_BUFFER + mimeContent;
-                return;
-            }
-
-            if (_SatelliteImageContent._IsDdsImage())
-            {
-                _mimeType = MIME_DDS;
-                _uri = EMBEDDED_DDS_BUFFER + mimeContent;
-                return;
-            }
-
-            if (_SatelliteImageContent._IsWebpImage())
-            {
-                _mimeType = MIME_WEBP;
-                _uri = EMBEDDED_WEBP_BUFFER + mimeContent;
-                return;
-            }
-
-            throw new NotImplementedException();
+            var imimg = new Memory.InMemoryImage(_SatelliteImageContent);
+            _mimeType = imimg.MimeType;
+            _uri = imimg.ToMime64();
         }
 
         /// <summary>
@@ -292,53 +236,23 @@ namespace SharpGLTF.Schema2
                 return;
             }
 
-            if (_SatelliteImageContent._IsPngImage())
-            {
-                _mimeType = null;
-                _uri = satelliteUri += ".png";
-                writer.WriteAllBytesToEnd(_uri, _SatelliteImageContent.Slice(0) );
-                return;
-            }
-
-            if (_SatelliteImageContent._IsJpgImage())
-            {
-                _mimeType = null;
-                _uri = satelliteUri += ".jpg";
-                writer.WriteAllBytesToEnd(_uri, _SatelliteImageContent.Slice(0) );
-                return;
-            }
-
-            if (_SatelliteImageContent._IsDdsImage())
-            {
-                _mimeType = null;
-                _uri = satelliteUri += ".dds";
-                writer.WriteAllBytesToEnd(_uri, _SatelliteImageContent.Slice(0) );
-                return;
-            }
+            _mimeType = null;
 
-            if (_SatelliteImageContent._IsWebpImage())
-            {
-                _mimeType = null;
-                _uri = satelliteUri += ".webp";
-                writer.WriteAllBytesToEnd(_uri, _SatelliteImageContent.Slice(0));
-                return;
-            }
+            var imimg = new Memory.InMemoryImage(_SatelliteImageContent);
+            if (!imimg.IsValid) throw new InvalidOperationException();
 
-            throw new NotImplementedException();
+            _uri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
+            writer.WriteAllBytesToEnd(_uri, imimg.GetBuffer());
         }
 
         private void _WriteAsBufferView()
         {
             Guard.IsTrue(_bufferView.HasValue, nameof(_bufferView));
 
-            var imageContent = GetImageContent();
-
-            if (imageContent._IsPngImage()) { _mimeType = MIME_PNG; return; }
-            if (imageContent._IsJpgImage()) { _mimeType = MIME_JPG; return; }
-            if (imageContent._IsDdsImage()) { _mimeType = MIME_DDS; return; }
-            if (imageContent._IsWebpImage()) { _mimeType = MIME_WEBP; return; }
+            var imimg = this.MemoryImage;
+            if (!imimg.IsValid) throw new InvalidOperationException();
 
-            throw new NotImplementedException();
+            _mimeType = imimg.MimeType;
         }
 
         /// <summary>
@@ -405,7 +319,7 @@ namespace SharpGLTF.Schema2
         public Image UseImage(BYTES imageContent)
         {
             Guard.NotNullOrEmpty(imageContent, nameof(imageContent));
-            Guard.IsTrue(imageContent._IsImage(), nameof(imageContent), $"{nameof(imageContent)} must be a valid image byte stream.");
+            Guard.IsTrue(new Memory.InMemoryImage(imageContent).IsValid, nameof(imageContent), $"{nameof(imageContent)} must be a valid image byte stream.");
 
             foreach (var img in this.LogicalImages)
             {

+ 6 - 6
src/SharpGLTF.Core/Schema2/gltf.Textures.cs

@@ -89,7 +89,7 @@ namespace SharpGLTF.Schema2
 
             if (primaryImage.IsDds || primaryImage.IsWebp)
             {
-                var fallback = LogicalParent.UseImage(Image.DefaultPngImage.Slice(0));
+                var fallback = LogicalParent.UseImage(Memory.InMemoryImage.DefaultPngImage.Slice(0));
                 SetImages(primaryImage, fallback);
             }
             else
@@ -105,17 +105,17 @@ namespace SharpGLTF.Schema2
             Guard.NotNull(fallbackImage, nameof(fallbackImage));
             Guard.MustShareLogicalParent(this, primaryImage, nameof(primaryImage));
             Guard.MustShareLogicalParent(this, fallbackImage, nameof(fallbackImage));
-            Guard.IsTrue(primaryImage.IsDds || primaryImage.IsWebp, "Primary image must be DDS or WEBP");
-            Guard.IsTrue(fallbackImage.IsJpeg || fallbackImage.IsPng, nameof(fallbackImage), "Fallback image must be PNG or JPEG");
+            Guard.IsTrue(primaryImage.MemoryImage.IsDds || primaryImage.MemoryImage.IsWebp, "Primary image must be DDS or WEBP");
+            Guard.IsTrue(fallbackImage.MemoryImage.IsJpg || fallbackImage.MemoryImage.IsPng, nameof(fallbackImage), "Fallback image must be PNG or JPEG");
 
             ClearImages();
 
-            if (primaryImage.IsDds)
+            if (primaryImage.MemoryImage.IsDds)
             {
                 _UseDDSTexture().Image = primaryImage;
             }
 
-            if (primaryImage.IsWebp)
+            if (primaryImage.MemoryImage.IsWebp)
             {
                 _UseWEBPTexture().Image = primaryImage;
             }
@@ -180,7 +180,7 @@ namespace SharpGLTF.Schema2
                 if (value != null)
                 {
                     Guard.MustShareLogicalParent(_Parent, value, nameof(value));
-                    Guard.IsTrue(value.IsDds, nameof(value));
+                    Guard.IsTrue(value.MemoryImage.IsDds, nameof(value));
                 }
 
                 _source = value?.LogicalIndex;

+ 4 - 5
src/SharpGLTF.Toolkit/IO/WavefrontWriter.cs

@@ -105,12 +105,11 @@ namespace SharpGLTF.IO
 
             foreach (var img in images)
             {
-                var imgName = firstImg ? baseName : $"{baseName}_{files.Count}";
+                var imimg = new Memory.InMemoryImage(img);
 
-                if (img._IsPngImage()) files[imgName + ".png"] = img;
-                if (img._IsJpgImage()) files[imgName + ".jpg"] = img;
-                if (img._IsDdsImage()) files[imgName + ".dds"] = img;
-                if (img._IsWebpImage()) files[imgName + ".webp"] = img;
+                var imgName = firstImg ? baseName : $"{baseName}_{files.Count}.{imimg.FileExtension}";
+
+                files[imgName] = img;
 
                 firstImg = false;
             }

+ 31 - 30
src/SharpGLTF.Toolkit/Materials/TextureBuilder.cs

@@ -101,6 +101,7 @@ namespace SharpGLTF.Materials
         /// Gets or sets the default image bytes to use by this <see cref="TextureBuilder"/>,
         /// Supported formats are: PNG, JPG, DDS and WEBP
         /// </summary>
+        [Obsolete("Use PrimaryImage property.")]
         public BYTES PrimaryImageContent
         {
             get => _PrimaryImageContent;
@@ -111,12 +112,33 @@ namespace SharpGLTF.Materials
         /// Gets or sets the fallback image bytes to use by this <see cref="TextureBuilder"/>,
         /// Supported formats are: PNG, JPG.
         /// </summary>
+        [Obsolete("Use FallbackImage property.")]
         public BYTES FallbackImageContent
         {
             get => _FallbackImageContent;
             set => WithFallbackImage(value);
         }
 
+        /// <summary>
+        /// Gets or sets the default image bytes to use by this <see cref="TextureBuilder"/>,
+        /// Supported formats are: PNG, JPG, DDS and WEBP
+        /// </summary>
+        public Memory.InMemoryImage PrimaryImage
+        {
+            get => new Memory.InMemoryImage(_PrimaryImageContent);
+            set => WithPrimaryImage(value.GetBuffer());
+        }
+
+        /// <summary>
+        /// Gets or sets the fallback image bytes to use by this <see cref="TextureBuilder"/>,
+        /// Supported formats are: PNG, JPG.
+        /// </summary>
+        public Memory.InMemoryImage FallbackImage
+        {
+            get => new Memory.InMemoryImage(_FallbackImageContent);
+            set => WithFallbackImage(value.GetBuffer());
+        }
+
         public TextureTransformBuilder Transform => _Transform;
 
         public static IEqualityComparer<TextureBuilder> ContentComparer => _ContentComparer.Default;
@@ -147,7 +169,7 @@ namespace SharpGLTF.Materials
         public TextureBuilder WithImage(string imagePath) { return WithPrimaryImage(imagePath); }
 
         [Obsolete("Use WithPrimaryImage instead,")]
-        public TextureBuilder WithImage(BYTES image) { return WithPrimaryImage(image); }
+        public TextureBuilder WithImage(Memory.InMemoryImage image) { return WithPrimaryImage(image); }
 
         public TextureBuilder WithPrimaryImage(string imagePath)
         {
@@ -158,18 +180,18 @@ namespace SharpGLTF.Materials
             return WithPrimaryImage(primary);
         }
 
-        public TextureBuilder WithPrimaryImage(BYTES image)
+        public TextureBuilder WithPrimaryImage(Memory.InMemoryImage image)
         {
-            if (image.Count > 0)
+            if (!image.IsEmpty)
             {
-                Guard.IsTrue(image._IsImage(), nameof(image), "Must be JPG, PNG, DDS or WEBP");
+                Guard.IsTrue(image.IsValid, nameof(image), "Must be JPG, PNG, DDS or WEBP");
             }
             else
             {
                 image = default;
             }
 
-            _PrimaryImageContent = image;
+            _PrimaryImageContent = image.GetBuffer();
             return this;
         }
 
@@ -182,18 +204,18 @@ namespace SharpGLTF.Materials
             return WithFallbackImage(primary);
         }
 
-        public TextureBuilder WithFallbackImage(BYTES image)
+        public TextureBuilder WithFallbackImage(Memory.InMemoryImage image)
         {
-            if (image.Count > 0)
+            if (!image.IsEmpty)
             {
-                Guard.IsTrue(image._IsJpgImage() || image._IsPngImage(), nameof(image), "Must be JPG or PNG");
+                Guard.IsTrue(image.IsJpg || image.IsPng, nameof(image), "Must be JPG or PNG");
             }
             else
             {
                 image = default;
             }
 
-            _FallbackImageContent = image;
+            _FallbackImageContent = image.GetBuffer();
             return this;
         }
 
@@ -242,27 +264,6 @@ namespace SharpGLTF.Materials
         }
 
         #endregion
-
-        #region image utilities
-
-        /// <summary>
-        /// Checks if <paramref name="data"/> represents a stream of an encoded image.
-        /// </summary>
-        /// <param name="data">A stream of bytes.</param>
-        /// <param name="extension">
-        /// An image format, valid values are:
-        /// - PNG
-        /// - JPG
-        /// - DDS
-        /// - WEBP
-        /// </param>
-        /// <returns>True if <paramref name="data"/> is an image.</returns>
-        public static bool IsImage(ArraySegment<Byte> data, string extension)
-        {
-            return data._IsImage(extension);
-        }
-
-        #endregion
     }
 
     public class TextureTransformBuilder

+ 12 - 12
src/SharpGLTF.Toolkit/Schema2/MaterialExtensions.cs

@@ -298,12 +298,8 @@ namespace SharpGLTF.Schema2
                 dstChannel.Texture.WithTransform(srcXform.Offset, srcXform.Scale, srcXform.Rotation, srcXform.TextureCoordinateOverride);
             }
 
-            dstChannel.Texture.PrimaryImageContent = srcChannel.Texture.PrimaryImage.GetImageContent();
-
-            if (srcChannel.Texture.FallbackImage != null)
-            {
-                dstChannel.Texture.FallbackImageContent = srcChannel.Texture.FallbackImage.GetImageContent();
-            }
+            dstChannel.Texture.PrimaryImage = srcChannel.Texture.PrimaryImage?.MemoryImage ?? Memory.InMemoryImage.Empty;
+            dstChannel.Texture.FallbackImage = srcChannel.Texture.FallbackImage?.MemoryImage ?? Memory.InMemoryImage.Empty;
         }
 
         public static void CopyTo(this Materials.MaterialBuilder srcMaterial, Material dstMaterial)
@@ -375,19 +371,23 @@ namespace SharpGLTF.Schema2
             var srcTex = srcChannel.Texture;
             if (srcTex == null) return;
 
-            var primary = dstChannel
+            Image primary = null;
+            Image fallback = null;
+
+            if (srcTex.PrimaryImage.IsValid)
+            {
+                primary = dstChannel
                 .LogicalParent
                 .LogicalParent
-                .UseImageWithContent(srcTex.PrimaryImageContent.ToArray());
-
-            Image fallback = null;
+                .UseImageWithContent(srcTex.PrimaryImage.GetBuffer().ToArray());
+            }
 
-            if (srcTex.FallbackImageContent.Count > 0)
+            if (srcTex.FallbackImage.IsValid)
             {
                 fallback = dstChannel
                 .LogicalParent
                 .LogicalParent
-                .UseImageWithContent(srcTex.FallbackImageContent.ToArray());
+                .UseImageWithContent(srcTex.FallbackImage.GetBuffer().ToArray());
             }
 
             dstChannel.SetTexture(srcTex.CoordinateSet, primary, fallback, srcTex.WrapS, srcTex.WrapT, srcTex.MinFilter, srcTex.MagFilter);