Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
d49ffeb42This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpGLTF.Validation;
using BYTES = System.ArraySegment<byte>;
namespace SharpGLTF.Schema2
{
[System.Diagnostics.DebuggerDisplay("{_DebuggerDisplay(),nq}")]
public sealed partial class Image
{
#region debug
internal string _DebuggerDisplay()
{
return $"Image[{LogicalIndex}] {Name} = {Content.DisplayText}";
}
#endregion
#region lifecycle
internal Image() { }
#endregion
#region data
/// <summary>
/// this is the not a raw bitmap, but tha actual compressed image in PNG or JPEG.
/// </summary>
/// <remarks>
/// When a model is loaded, the image file is loaded into memory and assigned to this
/// field, and the <see cref="Image._uri"/> and <see cref="Image._mimeType"/> fields are nullified.
/// When writing a gltf file with external images, the <see cref="Image._uri"/> and <see cref="Image._mimeType"/>
/// fields are briefly reassigned so the JSON can be serialized correctly.
/// After serialization <see cref="Image._uri"/> and <see cref="Image._mimeType"/> fields are set back to null.
/// </remarks>
private Memory.MemoryImage? _SatelliteContent;
#endregion
#region properties
/// <summary>
/// Gets or sets the in-memory representation of the image file.
/// </summary>
[Obsolete("Use Content property instead.")]
public Memory.MemoryImage MemoryImage
{
get => Content;
set => Content = value;
}
/// <summary>
/// Gets or sets the in-memory representation of the image file.
/// </summary>
public Memory.MemoryImage Content
{
get => GetSatelliteContent();
set => SetSatelliteContent(value);
}
internal int _SourceBufferViewIndex => _bufferView.AsValue(-1);
internal bool _HasContent
{
get
{
if (_bufferView != null) return true;
return _SatelliteContent?.IsValid ?? false;
}
}
#endregion
#region API
/// <summary>
/// Retrieves the image file as a segment of bytes.
/// </summary>
/// <returns>A <see cref="BYTES"/> segment containing the image file, which can be a PNG, JPG, DDS, WEBP or KTX2 format.</returns>
private Memory.MemoryImage GetSatelliteContent()
{
// the image is stored locally in a temporary buffer
if (_SatelliteContent.HasValue) return _SatelliteContent.Value;
// the image is stored in a BufferView
if (this._bufferView.HasValue)
{
var bv = this.LogicalParent.LogicalBufferViews[this._bufferView.Value];
return bv.Content;
}
// TODO: if external images have not been loaded into _ExternalImageContent
// and ModelRoot was loaded from file and stored the load path, use the _uri
// to load the model.
throw new InvalidOperationException();
}
/// <summary>
/// Initializes this <see cref="Image"/> with an image stored in a <see cref="Byte"/> array.
/// </summary>
/// <param name="content">A <see cref="Byte"/> array containing a PNG or JPEG image.</param>
private void SetSatelliteContent(Memory.MemoryImage content)
{
Memory.MemoryImage.Verify(content, nameof(content));
_DiscardContent();
this._SatelliteContent = content;
}
/// <summary>
/// If the image is stored externaly as an image file,
/// it creates a new BufferView and stores the image in the BufferView.
/// </summary>
internal void TransferToInternalBuffer()
{
if (!this._SatelliteContent.HasValue) return;
// transfer the external image content to a buffer.
this._bufferView = this.LogicalParent
.UseBufferView(this._SatelliteContent.Value._GetBuffer())
.LogicalIndex;
this._uri = null;
this._mimeType = null;
this._SatelliteContent = default;
}
#endregion
#region binary read
internal void _ResolveUri(IO.ReadContext context)
{
// No uri to decode.
if (String.IsNullOrWhiteSpace(_uri)) return;
// Try decode Base64 embedded image.
if (Memory.MemoryImage.TryParseMime64(_uri, out byte[] data))
{
_SatelliteContent = data;
}
// Then it's a regular URI
else
{
// try resolve the full path
if (context.TryGetFullPath(_uri, out string fullPath))
{
_SatelliteContent = fullPath;
}
// full path could not be resolved, use direct load instead.
else
{
_SatelliteContent = context.ReadAllBytesToEnd(_uri);
}
}
_uri = null;
_mimeType = null;
}
internal void _DiscardContent()
{
this._uri = null;
this._mimeType = null;
this._bufferView = null;
this._SatelliteContent = null;
}
#endregion
#region binary write
/// <summary>
/// Called internally by the serializer when the image content is to be embedded into the JSON document.
/// </summary>
internal void _WriteToInternal()
{
if (!_SatelliteContent.HasValue) { _WriteAsBufferView(); return; }
var imimg = _SatelliteContent.Value;
Memory.MemoryImage.Verify(imimg, nameof(imimg));
_uri = imimg.ToMime64();
_mimeType = imimg.MimeType;
}
/// <summary>
/// Called internally by the serializer when the image content is to be written as an external file
/// </summary>
/// <param name="writer">The satellite asset writer</param>
/// <param name="satelliteUri">A local satellite URI</param>
internal void _WriteToSatellite(IO.WriteContext writer, string satelliteUri)
{
if (!_SatelliteContent.HasValue)
{
_WriteAsBufferView();
return;
}
var imimg = _SatelliteContent.Value;
Memory.MemoryImage.Verify(imimg, nameof(imimg));
satelliteUri = System.IO.Path.ChangeExtension(satelliteUri, imimg.FileExtension);
satelliteUri = writer.WriteImage(satelliteUri, imimg);
satelliteUri = satelliteUri.Replace("\\", "/");
_uri = Uri.EscapeUriString(satelliteUri);
_mimeType = null;
}
private void _WriteAsBufferView()
{
Guard.IsTrue(_bufferView.HasValue, nameof(_bufferView));
var imimg = this.Content;
Memory.MemoryImage.Verify(imimg, nameof(imimg));
_uri = null;
_mimeType = imimg.MimeType;
}
/// <summary>
/// Called by the serializer immediatelly after
/// calling <see cref="_WriteToSatellite(IO.WriteContext, string)"/>
/// or <see cref="_WriteToInternal"/>
/// </summary>
internal void _ClearAfterWrite()
{
_uri = null;
_mimeType = null;
}
#endregion
#region Validation
protected override void OnValidateReferences(Validation.ValidationContext validate)
{
base.OnValidateReferences(validate);
validate
.IsNullOrValidURI(nameof(_uri), this._uri, Memory.MemoryImage._EmbeddedHeaders)
.IsNullOrIndex("BufferView", _bufferView, validate.Root.LogicalBufferViews);
}
protected override void OnValidateContent(ValidationContext validate)
{
if (this._bufferView.HasValue)
{
var bv = validate.Root.LogicalBufferViews[this._bufferView ?? 0];
validate.IsTrue("BufferView", bv.IsDataBuffer, "is a GPU target.");
}
Memory.MemoryImage.Verify(Content, nameof(Content));
}
#endregion
}
public partial class ModelRoot
{
/// <summary>
/// Creates a new <see cref="Image"/> instance.
/// and appends it to <see cref="ModelRoot.LogicalImages"/>.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <returns>A <see cref="Image"/> instance.</returns>
public Image CreateImage(string name = null)
{
var image = new Image();
image.Name = name;
this._images.Add(image);
return image;
}
/// <summary>
/// Creates or reuses a <see cref="Image"/> instance.
/// </summary>
/// <param name="imageContent">An image encoded in PNG, JPEG or DDS</param>
/// <returns>A <see cref="Image"/> instance.</returns>
public Image UseImage(Memory.MemoryImage imageContent)
{
Memory.MemoryImage.Verify(imageContent, nameof(imageContent));
// If we find an image with the same content, let's reuse it.
foreach (var img in this.LogicalImages)
{
if (img.Content.Equals(imageContent)) return img;
}
var image = this.CreateImage();
image.Content = imageContent;
return image;
}
/// <summary>
/// Transfers all the <see cref="ModelRoot.LogicalImages"/> content into <see cref="BufferView"/> instances
/// </summary>
/// <remarks>
/// Images can be stored in three different ways:
/// - As satellite files.
/// - Embedded as MIME64 into the JSON document
/// - Referenced with <see cref="BufferView"/>
///
/// This call ensures all images will be internalized as <see cref="BufferView"/> instances.
///
/// This action cannot be reversed.
/// </remarks>
public void MergeImages()
{
foreach (var img in this._images) img.TransferToInternalBuffer();
}
}
}