76 using System.Collections.Generic;
78 using System.Runtime.InteropServices;
79 using SiliconStudio.Core;
80 using SiliconStudio.Core.Serialization.Contents;
82 namespace SiliconStudio.
Paradox.Graphics
87 [ContentSerializer(typeof(ImageSerializer))]
88 public sealed
class Image : IDisposable
90 public delegate
Image ImageLoadDelegate(IntPtr dataPointer,
int dataSize,
bool makeACopy, GCHandle? handle);
93 private const string MagicCodeTKTX =
"TKTX";
100 private List<int> mipMapToZIndex;
101 private int zBufferCountPerArraySlice;
103 private static List<LoadSaveDelegate> loadSaveDelegates =
new List<LoadSaveDelegate>();
117 private int totalSizeInBytes;
122 private IntPtr buffer;
127 private bool bufferIsDisposable;
132 private GCHandle? handle;
152 internal unsafe
Image(
ImageDescription description, IntPtr dataPointer,
int offset, GCHandle? handle,
bool bufferIsDisposable, PitchFlags pitchFlags = PitchFlags.None,
int rowStride = 0)
154 Initialize(description, dataPointer, offset, handle, bufferIsDisposable, pitchFlags, rowStride);
164 if (bufferIsDisposable)
166 Utilities.FreeMemory(buffer);
177 return mipmapDescriptions[mipmap];
190 if (mipmap > Description.MipLevels)
191 throw new ArgumentException(
"Invalid mipmap level",
"mipmap");
195 if (arrayOrZSliceIndex > Description.Depth)
196 throw new ArgumentException(
"Invalid z slice index",
"arrayOrZSliceIndex");
199 return GetPixelBufferUnsafe(0, arrayOrZSliceIndex, mipmap);
202 if (arrayOrZSliceIndex > Description.ArraySize)
204 throw new ArgumentException(
"Invalid array slice index",
"arrayOrZSliceIndex");
208 return GetPixelBufferUnsafe(arrayOrZSliceIndex, 0, mipmap);
222 if (mipmap > Description.MipLevels)
223 throw new ArgumentException(
"Invalid mipmap level",
"mipmap");
225 if (arrayIndex > Description.ArraySize)
226 throw new ArgumentException(
"Invalid array slice index",
"arrayIndex");
228 if (zIndex > Description.Depth)
229 throw new ArgumentException(
"Invalid z slice index",
"zIndex");
231 return this.GetPixelBufferUnsafe(arrayIndex, zIndex, mipmap);
245 if (ReferenceEquals(loader, saver))
246 throw new ArgumentNullException(
"Can set both loader and saver to null",
"loader/saver");
248 var newDelegate =
new LoadSaveDelegate(type, loader, saver);
249 for (
int i = 0; i < loadSaveDelegates.Count; i++)
251 var loadSaveDelegate = loadSaveDelegates[i];
252 if (loadSaveDelegate.FileType == type)
254 loadSaveDelegates[i] = newDelegate;
258 loadSaveDelegates.Add(newDelegate);
268 get {
return this.buffer; }
280 get {
return pixelBufferArray; }
286 public int TotalSizeInBytes
288 get {
return totalSizeInBytes; }
297 return (
DataBox[])dataBoxArray.Clone();
304 private DataBox[] ComputeDataBox()
306 dataBoxArray =
new DataBox[Description.ArraySize * Description.MipLevels];
308 for (
int arrayIndex = 0; arrayIndex < Description.ArraySize; arrayIndex++)
310 for (
int mipIndex = 0; mipIndex < Description.MipLevels; mipIndex++)
313 var pixelBuffer = this.GetPixelBufferUnsafe(arrayIndex, 0, mipIndex);
316 dataBoxArray[i].RowPitch = pixelBuffer.RowStride;
317 dataBoxArray[i].SlicePitch = pixelBuffer.BufferStride;
331 return New(description, IntPtr.Zero);
344 return New1D(width, mipMapCount, format, arraySize, IntPtr.Zero);
358 return New2D(width, height, mipMapCount, format, arraySize, IntPtr.Zero, rowStride);
370 return NewCube(width, mipMapCount, format, IntPtr.Zero);
384 return New3D(width, height, depth, mipMapCount, format, IntPtr.Zero);
395 return new Image(description, dataPointer, 0, null,
false);
409 return new Image(description, dataPointer, offset, handle, bufferIsDisposable);
423 return new Image(CreateDescription(
TextureDimension.Texture1D, width, 1, 1, mipMapCount, format, arraySize), dataPointer, 0, null,
false);
439 return new Image(CreateDescription(
TextureDimension.Texture2D, width, height, 1, mipMapCount, format, arraySize), dataPointer, 0, null,
false, PitchFlags.None, rowStride);
452 return new Image(CreateDescription(
TextureDimension.TextureCube, width, width, 1, mipMapCount, format, 6), dataPointer, 0, null,
false);
467 return new Image(CreateDescription(
TextureDimension.Texture3D, width, width, depth, mipMapCount, format, 1), dataPointer, 0, null,
false);
479 return Load(dataBuffer.
Pointer, dataBuffer.
Size, makeACopy);
490 public static Image Load(IntPtr dataPointer,
int dataSize,
bool makeACopy =
false)
492 return Load(dataPointer, dataSize, makeACopy, null);
504 throw new ArgumentNullException(
"buffer");
506 int size = buffer.Length;
509 if (size > (85 * 1024))
511 var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
512 return Load(handle.AddrOfPinnedObject(),
size,
false, handle);
515 fixed (
void* pbuffer = buffer)
517 return Load((IntPtr) pbuffer, size,
true);
529 if (imageStream == null)
throw new ArgumentNullException(
"imageStream");
542 if (imageStream == null)
throw new ArgumentNullException(
"imageStream");
543 Save(pixelBuffers, this.pixelBuffers.Length, Description, imageStream, fileType);
556 int maxMips = CountMips(width);
557 if (mipLevels > maxMips)
558 throw new InvalidOperationException(String.Format(
"MipLevels must be <= {0}", maxMips));
560 else if (mipLevels == 0)
562 mipLevels = CountMips(width);
582 int maxMips = CountMips(width, height);
583 if (mipLevels > maxMips)
584 throw new InvalidOperationException(String.Format(
"MipLevels must be <= {0}", maxMips));
586 else if (mipLevels == 0)
588 mipLevels = CountMips(width, height);
609 if (!IsPow2(width) || !IsPow2(height) || !IsPow2(depth))
610 throw new InvalidOperationException(
"Width/Height/Depth must be power of 2");
612 int maxMips = CountMips(width, height, depth);
613 if (mipLevels > maxMips)
614 throw new InvalidOperationException(String.Format(
"MipLevels must be <= {0}", maxMips));
616 else if (mipLevels == 0)
618 if (!IsPow2(width) || !IsPow2(height) || !IsPow2(depth))
619 throw new InvalidOperationException(
"Width/Height/Depth must be power of 2");
621 mipLevels = CountMips(width, height, depth);
632 mipLevel = Math.Min(mipLevel, CountMips(width));
633 width = width >> mipLevel;
634 return width > 0 ? width : 1;
646 private static Image Load(IntPtr dataPointer,
int dataSize,
bool makeACopy, GCHandle? handle)
648 foreach (var loadSaveDelegate
in loadSaveDelegates)
650 if (loadSaveDelegate.Load != null)
652 var image = loadSaveDelegate.Load(dataPointer, dataSize, makeACopy, handle);
659 throw new NotSupportedException(
"Image format not supported");
671 internal static void Save(PixelBuffer[] pixelBuffers,
int count, ImageDescription description,
Stream imageStream,
ImageFileType fileType)
673 foreach (var loadSaveDelegate
in loadSaveDelegates)
675 if (loadSaveDelegate.FileType == fileType)
677 loadSaveDelegate.Save(pixelBuffers,
count, description, imageStream);
682 throw new NotSupportedException(
"This file format is not yet implemented.");
687 Register(
ImageFileType.Paradox, ImageHelper.LoadFromMemory, ImageHelper.SaveFromMemory);
688 Register(
ImageFileType.Dds, DDSHelper.LoadFromDDSMemory, DDSHelper.SaveToDDSStream);
689 Register(
ImageFileType.Gif, StandardImageHelper.LoadFromMemory, StandardImageHelper.SaveGifFromMemory);
690 Register(
ImageFileType.Tiff,StandardImageHelper.LoadFromMemory, StandardImageHelper.SaveTiffFromMemory);
691 Register(
ImageFileType.Bmp, StandardImageHelper.LoadFromMemory, StandardImageHelper.SaveBmpFromMemory);
692 Register(
ImageFileType.Jpg, StandardImageHelper.LoadFromMemory, StandardImageHelper.SaveJpgFromMemory);
693 Register(
ImageFileType.Png, StandardImageHelper.LoadFromMemory, StandardImageHelper.SavePngFromMemory);
694 Register(
ImageFileType.Wmp, StandardImageHelper.LoadFromMemory, StandardImageHelper.SaveWmpFromMemory);
698 internal unsafe
void Initialize(ImageDescription description, IntPtr dataPointer,
int offset, GCHandle? handle,
bool bufferIsDisposable, PitchFlags pitchFlags = PitchFlags.None,
int rowStride = 0)
700 if (!description.Format.IsValid() || description.Format.IsVideo())
701 throw new InvalidOperationException(
"Unsupported DXGI Format");
703 if (rowStride > 0 && description.MipLevels != 1)
704 throw new InvalidOperationException(
"Cannot specify custom stride with mipmaps");
707 this.handle = handle;
709 switch (description.Dimension)
711 case TextureDimension.Texture1D:
712 if (description.Width <= 0 || description.Height != 1 || description.Depth != 1 || description.ArraySize == 0)
713 throw new InvalidOperationException(
"Invalid Width/Height/Depth/ArraySize for Image 1D");
716 description.MipLevels = CalculateMipLevels(description.Width, 1, description.MipLevels);
719 case TextureDimension.Texture2D:
720 case TextureDimension.TextureCube:
721 if (description.Width <= 0 || description.Height <= 0 || description.Depth != 1 || description.ArraySize == 0)
722 throw new InvalidOperationException(
"Invalid Width/Height/Depth/ArraySize for Image 2D");
726 if ((description.ArraySize % 6) != 0)
727 throw new InvalidOperationException(
"TextureCube must have an arraysize = 6");
731 description.MipLevels = CalculateMipLevels(description.Width, description.Height, description.MipLevels);
734 case TextureDimension.Texture3D:
735 if (description.Width <= 0 || description.Height <= 0 || description.Depth <= 0 || description.ArraySize != 1)
736 throw new InvalidOperationException(
"Invalid Width/Height/Depth/ArraySize for Image 3D");
739 description.MipLevels = CalculateMipLevels(description.Width, description.Height, description.Depth, description.MipLevels);
744 int pixelBufferCount;
745 this.mipMapToZIndex = CalculateImageArray(description, pitchFlags, rowStride, out pixelBufferCount, out totalSizeInBytes);
746 this.mipmapDescriptions = CalculateMipMapDescription(description, pitchFlags);
747 zBufferCountPerArraySlice = this.mipMapToZIndex[this.mipMapToZIndex.Count - 1];
750 pixelBuffers =
new PixelBuffer[pixelBufferCount];
751 pixelBufferArray =
new PixelBufferArray(
this);
755 this.bufferIsDisposable = !handle.HasValue && bufferIsDisposable;
756 this.buffer = dataPointer;
758 if (dataPointer == IntPtr.Zero)
760 buffer = Utilities.AllocateMemory(totalSizeInBytes);
762 this.bufferIsDisposable =
true;
765 SetupImageArray((IntPtr)((byte*)buffer + offset), totalSizeInBytes, rowStride, description, pitchFlags, pixelBuffers);
767 Description = description;
770 dataBoxArray = ComputeDataBox();
773 private PixelBuffer GetPixelBufferUnsafe(
int arrayIndex,
int zIndex,
int mipmap)
775 var depthIndex = this.mipMapToZIndex[mipmap];
776 var pixelBufferIndex = arrayIndex * this.zBufferCountPerArraySlice + depthIndex + zIndex;
777 return pixelBuffers[pixelBufferIndex];
780 private static ImageDescription CreateDescription(
TextureDimension dimension,
int width,
int height,
int depth, MipMapCount mipMapCount,
PixelFormat format,
int arraySize)
782 return new ImageDescription()
787 ArraySize = arraySize,
788 Dimension = dimension,
790 MipLevels = mipMapCount,
795 internal enum PitchFlags
804 internal static void ComputePitch(
PixelFormat fmt,
int width,
int height, out
int rowPitch, out
int slicePitch, out
int widthCount, out
int heightCount, PitchFlags
flags = PitchFlags.None)
807 heightCount = height;
809 if (fmt.IsCompressed())
817 case PixelFormat.BC1_Typeless:
818 case PixelFormat.BC1_UNorm:
819 case PixelFormat.BC1_UNorm_SRgb:
820 case PixelFormat.BC4_Typeless:
821 case PixelFormat.BC4_UNorm:
822 case PixelFormat.BC4_SNorm:
823 case PixelFormat.ETC1:
826 case PixelFormat.PVRTC_4bpp_RGB:
827 case PixelFormat.PVRTC_4bpp_RGBA:
828 case PixelFormat.PVRTC_II_4bpp:
832 case PixelFormat.PVRTC_2bpp_RGBA:
833 case PixelFormat.PVRTC_II_2bpp:
843 widthCount = Math.Max(1, (Math.Max(minWidth, width) + 3)) / 4;
844 heightCount = Math.Max(1, (Math.Max(minHeight, height) + 3)) / 4;
845 rowPitch = widthCount*bpb;
847 slicePitch = rowPitch*heightCount;
849 else if (fmt.IsPacked())
851 rowPitch = ((width + 1) >> 1) * 4;
853 slicePitch = rowPitch * height;
859 if ((
flags & PitchFlags.Bpp24) != 0)
861 else if ((
flags & PitchFlags.Bpp16) != 0)
863 else if ((
flags & PitchFlags.Bpp8) != 0)
866 bpp = fmt.SizeInBits();
868 if ((
flags & PitchFlags.LegacyDword) != 0)
872 rowPitch = ((width * bpp + 31) / 32) *
sizeof(int);
873 slicePitch = rowPitch * height;
877 rowPitch = (width * bpp + 7) / 8;
878 slicePitch = rowPitch * height;
883 internal static MipMapDescription[] CalculateMipMapDescription(ImageDescription
metadata, PitchFlags
cpFlags = PitchFlags.None)
887 return CalculateMipMapDescription(metadata,
cpFlags, out nImages, out pixelSize);
895 int w = metadata.Width;
896 int h = metadata.Height;
897 int d = metadata.Depth;
899 var mipmaps =
new MipMapDescription[metadata.MipLevels];
901 for (
int level = 0; level < metadata.MipLevels; ++level)
903 int rowPitch, slicePitch;
906 ComputePitch(metadata.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, PitchFlags.None);
908 mipmaps[level] =
new MipMapDescription(
918 pixelSize += d * slicePitch;
940 private static List<int> CalculateImageArray( ImageDescription imageDesc, PitchFlags pitchFlags,
int rowStride, out
int bufferCount, out
int pixelSizeInBytes)
942 pixelSizeInBytes = 0;
945 var mipmapToZIndex =
new List<int>();
947 for (
int j = 0; j < imageDesc.ArraySize; j++)
949 int w = imageDesc.Width;
950 int h = imageDesc.Height;
951 int d = imageDesc.Depth;
953 for (
int i = 0; i < imageDesc.MipLevels; i++)
955 int rowPitch, slicePitch;
958 ComputePitch(imageDesc.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, pitchFlags);
963 if (rowStride < rowPitch)
964 throw new InvalidOperationException(
string.Format(
"Invalid stride [{0}]. Value can't be lower than actual stride [{1}]", rowStride, rowPitch));
966 if (widthPacked != w || heightPacked != h)
967 throw new InvalidOperationException(
"Custom strides is not supported with packed PixelFormats");
970 rowPitch = rowStride;
973 slicePitch = rowStride * h;
978 mipmapToZIndex.Add(bufferCount);
981 pixelSizeInBytes += d * slicePitch;
996 mipmapToZIndex.Add(bufferCount);
998 return mipmapToZIndex;
1009 private static unsafe
void SetupImageArray(IntPtr buffer,
int pixelSize,
int rowStride, ImageDescription imageDesc, PitchFlags pitchFlags, PixelBuffer[] output)
1012 var pixels = (byte*)buffer;
1013 for (uint item = 0; item < imageDesc.ArraySize; ++item)
1015 int w = imageDesc.Width;
1016 int h = imageDesc.Height;
1017 int d = imageDesc.Depth;
1019 for (uint level = 0; level < imageDesc.MipLevels; ++level)
1021 int rowPitch, slicePitch;
1024 ComputePitch(imageDesc.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, pitchFlags);
1029 if (rowStride < rowPitch)
1030 throw new InvalidOperationException(
string.Format(
"Invalid stride [{0}]. Value can't be lower than actual stride [{1}]", rowStride, rowPitch));
1032 if (widthPacked != w || heightPacked != h)
1033 throw new InvalidOperationException(
"Custom strides is not supported with packed PixelFormats");
1036 rowPitch = rowStride;
1039 slicePitch = rowStride * h;
1042 for (uint zSlice = 0; zSlice < d; ++zSlice)
1046 output[index] =
new PixelBuffer(w, h, imageDesc.Format, rowPitch, slicePitch, (IntPtr)pixels);
1049 pixels += slicePitch;
1064 private static bool IsPow2(
int x)
1066 return ((x != 0) && (x & (x - 1)) == 0);
1088 while (height > 1 || width > 1)
1102 public static int CountMips(
int width,
int height,
int depth)
1106 while (height > 1 || width > 1 || depth > 1)
1123 private class LoadSaveDelegate
1125 public LoadSaveDelegate(
ImageFileType fileType, ImageLoadDelegate load, ImageSaveDelegate save)
1127 FileType = fileType;
1134 public ImageLoadDelegate Load;
1136 public ImageSaveDelegate Save;
static Image New3D(int width, int height, int depth, MipMapCount mipMapCount, PixelFormat format, IntPtr dataPointer)
Creates a new instance of a 3D Image.
static Image New2D(int width, int height, MipMapCount mipMapCount, PixelFormat format, int arraySize, IntPtr dataPointer, int rowStride=0)
Creates a new instance of a 2D Image.
_In_ size_t _In_ const TexMetadata _In_ DWORD cpFlags
void Save(Stream imageStream, ImageFileType fileType)
Saves this instance to a stream.
static int CalculateMipSize(int width, int mipLevel)
ImageFileType
Image file format used by Image.Save(string,SiliconStudio.Paradox.Graphics.ImageFileType) ...
A simple wrapper to specify number of mipmaps. Set to true to specify all mipmaps or sets an integer ...
static Image Load(DataPointer dataBuffer, bool makeACopy=false)
Loads an image from an unmanaged memory pointer.
_In_ size_t _In_ const TexMetadata _In_ DWORD _In_ size_t nImages
PixelBuffer GetPixelBuffer(int arrayOrZSliceIndex, int mipmap)
Gets the pixel buffer for the specified array/z slice and mipmap level.
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ DXGI_FORMAT _In_ DWORD flags
static int CountMips(int width)
static int CalculateMipLevels(int width, int height, MipMapCount mipLevels)
Calculates the number of miplevels for a Texture 2D.
Provides method to instantiate an image 1D/2D/3D supporting TextureArray and mipmaps on the CPU or to...
static Image Load(Stream imageStream)
Loads the specified image from a stream.
static Image New1D(int width, MipMapCount mipMapCount, PixelFormat format, int arraySize=1)
Creates a new instance of a 1D Image.
ImageDescription Description
Description of this image.
static Image New3D(int width, int height, int depth, MipMapCount mipMapCount, PixelFormat format)
Creates a new instance of a 3D Image.
TextureDimension
Defines the dimension of a texture.
Flags
Enumeration of the new Assimp's flags.
_In_ size_t _In_ const TexMetadata & metadata
void ComputePitch(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _Out_ size_t &rowPitch, _Out_ size_t &slicePitch, _In_ DWORD flags=CP_FLAGS_NONE)
static byte[] ReadStream(Stream stream)
Read stream to a byte[] buffer
static int CalculateMipLevels(int width, MipMapCount mipLevels)
Calculates the number of miplevels for a Texture 1D.
static Image Load(IntPtr dataPointer, int dataSize, bool makeACopy=false)
Loads an image from an unmanaged memory pointer.
static int CountMips(int width, int height)
static int CountMips(int width, int height, int depth)
Used by Image to provide a selector to a PixelBuffer.
An unmanaged buffer of pixels.
static Image New1D(int width, MipMapCount mipMapCount, PixelFormat format, int arraySize, IntPtr dataPointer)
Creates a new instance of a 1D Image.
DataBox[] ToDataBox()
Gets the databox from this image.
static Image New(ImageDescription description, IntPtr dataPointer)
Creates a new instance of Image from an image description.
static unsafe Image Load(byte[] buffer)
Loads an image from a managed buffer.
static int CalculateMipLevels(int width, int height, int depth, MipMapCount mipLevels)
Calculates the number of miplevels for a Texture 2D.
PixelBuffer GetPixelBuffer(int arrayIndex, int zIndex, int mipmap)
Gets the pixel buffer for the specified array/z slice and mipmap level.
MipMapDescription GetMipMapDescription(int mipmap)
Gets the mipmap description of this instance for the specified mipmap level.
Provides access to data organized in 3D.
static void Register(ImageFileType type, ImageLoadDelegate loader, ImageSaveDelegate saver)
Registers a loader/saver for a specified image file type.
_In_ size_t _In_ size_t _In_ DXGI_FORMAT format
static Image New2D(int width, int height, MipMapCount mipMapCount, PixelFormat format, int arraySize=1, int rowStride=0)
Creates a new instance of a 2D Image.
_In_ size_t _In_ size_t size
PixelFormat
Defines various types of pixel formats.
static Image New(ImageDescription description)
Creates a new instance of Image from an image description.
static Image New(ImageDescription description, IntPtr dataPointer, int offset, GCHandle?handle, bool bufferIsDisposable)
Initializes a new instance of the Image class.
static Image NewCube(int width, MipMapCount mipMapCount, PixelFormat format, IntPtr dataPointer)
Creates a new instance of a Cube Image.
static Image NewCube(int width, MipMapCount mipMapCount, PixelFormat format)
Creates a new instance of a Cube Image.