/// <summary> /// Function to create an array of System.Drawing.Images from a given texture. /// </summary> /// <param name="texture">Texture to evaluate.</param> /// <returns>A list of GDI+ images.</returns> public static Image[] CreateGDIImagesFromTexture(GorgonTexture texture) { using (var data = GorgonImageData.CreateFromTexture(texture)) { return(CreateGDIImagesFromImageData(data)); } }
/// <summary> /// Function to initialize the texture with optional initial data. /// </summary> /// <param name="initialData">Data used to populate the image.</param> protected override void OnInitialize(GorgonImageData initialData) { var shaderBind = Settings.AllowShaderView ? BindFlags.ShaderResource : BindFlags.None; var desc = new Texture2DDescription { ArraySize = Settings.ArrayCount, BindFlags = GetBindFlags(true, false) | shaderBind, CpuAccessFlags = CpuAccessFlags.None, Format = Settings.AllowShaderView ? (Format)Settings.TextureFormat : (Format)Settings.Format, Height = Settings.Height, Width = Settings.Width, MipLevels = Settings.MipCount, OptionFlags = ResourceOptionFlags.None, SampleDescription = GorgonMultisampling.Convert(Settings.Multisampling) }; Gorgon.Log.Print("{0} {1}: Creating 2D depth/stencil texture...", LoggingLevel.Verbose, GetType().Name, Name); // Create the texture. D3DResource = initialData != null ? new Texture2D(Graphics.D3DDevice, desc, initialData.Buffers.DataBoxes) : new Texture2D(Graphics.D3DDevice, desc); GorgonRenderStatistics.DepthBufferCount++; GorgonRenderStatistics.DepthBufferSize += SizeInBytes; InitializeResourceViews(); _defaultView = GetDepthStencilView(Settings.Format, 0, 0, 1, Settings.DefaultDepthStencilViewFlags); }
/// <summary> /// Function to initialize the texture with optional initial data. /// </summary> /// <param name="initialData">Data used to populate the image.</param> protected override void OnInitialize(GorgonImageData initialData) { var desc = new D3D.Texture2DDescription { ArraySize = Settings.ArrayCount, Format = (GI.Format)Settings.Format, Width = Settings.Width, Height = Settings.Height, MipLevels = Settings.MipCount, BindFlags = GetBindFlags(false, false), Usage = (D3D.ResourceUsage)Settings.Usage, OptionFlags = Settings.IsTextureCube ? D3D.ResourceOptionFlags.TextureCube : D3D.ResourceOptionFlags.None, SampleDescription = GorgonMultisampling.Convert(Settings.Multisampling) }; switch (Settings.Usage) { case BufferUsage.Staging: desc.CpuAccessFlags = D3D.CpuAccessFlags.Read | D3D.CpuAccessFlags.Write; break; case BufferUsage.Dynamic: desc.CpuAccessFlags = D3D.CpuAccessFlags.Write; break; default: desc.CpuAccessFlags = D3D.CpuAccessFlags.None; break; } D3DResource = initialData != null ? new D3D.Texture2D(Graphics.D3DDevice, desc, initialData.Buffers.DataBoxes) : new D3D.Texture2D(Graphics.D3DDevice, desc); }
/// <summary> /// Funciton to generate random noise for the effect. /// </summary> private void GenerateRandomNoise() { int textureSize = 128; object parameter; if ((Parameters.TryGetValue("TextureSize", out parameter)) && (parameter != null)) { var size = (int)parameter; if (size > 16) { textureSize = size; } } using (var image = new GorgonImageData(new GorgonTexture2DSettings { Width = textureSize, Height = textureSize, Format = BufferFormat.R8_UIntNormal, Usage = BufferUsage.Default })) { unsafe { var dataPtr = (byte *)image.Buffers[0].Data.UnsafePointer; // Write perlin noise to the texture. for (int y = 0; y < textureSize; ++y) { for (int x = 0; x < textureSize; ++x) { float simplexNoise = GorgonRandom.SimplexNoise(new Vector2(x * (1.0f / _noiseFrequency), y * (1.0f / _noiseFrequency))); if (simplexNoise < -0.75f) { simplexNoise *= -1; } else { simplexNoise *= 0.95f; } if (simplexNoise < 0.125f) { simplexNoise = 0.0f; } *(dataPtr++) = (byte)(simplexNoise * 255.0f); } } } _randomTexture = Graphics.Textures.CreateTexture <GorgonTexture2D>("Effect.OldFilm.RandomTexture", image); } }
/// <summary> /// Function to create a list of buffers to use. /// </summary> /// <param name="data">Data to copy/reference.</param> internal unsafe void CreateBuffers(byte *data) { int bufferIndex = 0; var formatInfo = GorgonBufferFormatInfo.GetInfo(_image.Settings.Format); // Format information. // Allocate enough room for the array and mip levels. _buffers = new GorgonImageBuffer[GorgonImageData.GetDepthSliceCount(_image.Settings.Depth, _image.Settings.MipCount) * _image.Settings.ArrayCount]; MipOffsetSize = new Tuple <int, int> [_image.Settings.MipCount * _image.Settings.ArrayCount]; // Offsets for the mip maps. DataBoxes = new DX.DataBox[_image.Settings.ArrayCount * _image.Settings.MipCount]; // Create the data boxes for textures. // Enumerate array indices. (For 1D and 2D only, 3D will always be 1) for (int array = 0; array < _image.Settings.ArrayCount; array++) { int mipWidth = _image.Settings.Width; int mipHeight = _image.Settings.Height; int mipDepth = _image.Settings.Depth; // Enumerate mip map levels. for (int mip = 0; mip < _image.Settings.MipCount; mip++) { int arrayIndex = mip + (array * _image.Settings.MipCount); var pitchInformation = formatInfo.GetPitch(mipWidth, mipHeight, PitchFlags.None); // Get data box for texture upload. DataBoxes[arrayIndex] = new DX.DataBox(new IntPtr(data), pitchInformation.RowPitch, pitchInformation.SlicePitch); // Calculate buffer offset by mip. MipOffsetSize[arrayIndex] = new Tuple <int, int>(bufferIndex, mipDepth); // Enumerate depth slices. for (int depth = 0; depth < mipDepth; depth++) { // Get mip information. _buffers[bufferIndex] = new GorgonImageBuffer(data, pitchInformation, mip, array, depth, mipWidth, mipHeight, mipDepth, _image.Settings.Format); data += pitchInformation.SlicePitch; bufferIndex++; } if (mipWidth > 1) { mipWidth >>= 1; } if (mipHeight > 1) { mipHeight >>= 1; } if (mipDepth > 1) { mipDepth >>= 1; } } } }
/// <summary> /// Function to create 2D Gorgon image data from a single System.Drawing.Image. /// </summary> /// <param name="wic">Windows Imaging Component interface to use.</param> /// <param name="image">An image to convert.</param> /// <param name="options">Options for conversion.</param> /// <returns>The converted image data.</returns> public static GorgonImageData Create2DImageDataFromImage(GorgonWICImage wic, Image image, GorgonGDIOptions options) { if (options.Format == BufferFormat.Unknown) { options.Format = GetBufferFormat(image.PixelFormat); } if (options.Format == BufferFormat.Unknown) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, image.PixelFormat)); } if (options.Width < 1) { options.Width = image.Width; } if (options.Height < 1) { options.Height = image.Height; } // Specify 0 to generate a full mip chain. options.MipCount = options.MipCount < 1 ? GorgonImageData.GetMaxMipCount(options.Width, options.Height) : options.MipCount.Min(GorgonImageData.GetMaxMipCount(options.Width, options.Height)); // Create our settings. var settings = new GorgonTexture2DSettings { Width = options.Width, Height = options.Height, MipCount = options.MipCount, ArrayCount = 1, Format = options.Format, AllowUnorderedAccessViews = options.AllowUnorderedAccess, ShaderViewFormat = options.ViewFormat, Usage = options.Usage }; // Create our image data. var data = new GorgonImageData(settings); // Using the image, convert to a WIC bitmap object. using (Bitmap bitmap = wic.CreateWICImageFromImage(image)) { for (int mipLevel = 0; mipLevel < options.MipCount; mipLevel++) { var buffer = data.Buffers[mipLevel]; wic.AddWICBitmapToImageData(bitmap, options.Filter, options.Dither, buffer, options.UseClipping); } } return(data); }
/// <summary> /// Function to read the data from a frame. /// </summary> /// <param name="wic">WIC interface.</param> /// <param name="data">Image data to populate.</param> /// <param name="srcFormat">Source image format.</param> /// <param name="convertFormat">Conversion format.</param> /// <param name="frame">Frame containing the image data.</param> private void ReadFrame(GorgonWICImage wic, GorgonImageData data, Guid srcFormat, Guid convertFormat, BitmapFrameDecode frame) { var buffer = data.Buffers[0]; // We don't need to convert, so just leave. if ((convertFormat == Guid.Empty) || (srcFormat == convertFormat)) { frame.CopyPixels(buffer.PitchInformation.RowPitch, buffer.Data.BasePointer, buffer.PitchInformation.SlicePitch); return; } // Perform conversion. using (var converter = new FormatConverter(wic.Factory)) { bool isIndexed = ((frame.PixelFormat == PixelFormat.Format8bppIndexed) || (frame.PixelFormat == PixelFormat.Format4bppIndexed) || (frame.PixelFormat == PixelFormat.Format2bppIndexed) || (frame.PixelFormat == PixelFormat.Format1bppIndexed)); Tuple <Palette, double, BitmapPaletteType> paletteInfo = null; try { // If the pixel format is indexed, then retrieve a palette. if (isIndexed) { paletteInfo = GetPaletteInfo(wic, null); } // If we've defined a palette for an indexed image, then copy it to a bitmap and set its palette. if ((paletteInfo != null) && (paletteInfo.Item1 != null)) { using (var tempBitmap = new Bitmap(wic.Factory, frame, BitmapCreateCacheOption.CacheOnDemand)) { tempBitmap.Palette = paletteInfo.Item1; converter.Initialize(tempBitmap, convertFormat, (BitmapDitherType)Dithering, paletteInfo.Item1, paletteInfo.Item2, paletteInfo.Item3); converter.CopyPixels(buffer.PitchInformation.RowPitch, buffer.Data.BasePointer, buffer.PitchInformation.SlicePitch); } return; } // Only apply palettes to indexed image data. converter.Initialize(frame, convertFormat, (BitmapDitherType)Dithering, null, 0.0, BitmapPaletteType.Custom); converter.CopyPixels(buffer.PitchInformation.RowPitch, buffer.Data.BasePointer, buffer.PitchInformation.SlicePitch); } finally { if ((paletteInfo != null) && (paletteInfo.Item1 != null)) { paletteInfo.Item1.Dispose(); } } } }
/// <summary> /// Function to create an array of System.Drawing.Images from an image data object. /// </summary> /// <param name="imageData">Image data to process.</param> /// <returns>A list of GDI+ images.</returns> public static Image[] CreateGDIImagesFromImageData(GorgonImageData imageData) { PixelFormat?format = GetPixelFormat(imageData.Settings.Format); Bitmap[] bitmaps = null; Image[] images; if (format == null) { format = GetPixelFormat(imageData.Settings.Format); if (format == null) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, imageData.Settings.Format)); } } using (var wic = new GorgonWICImage()) { try { bitmaps = wic.CreateWICBitmapsFromImageData(imageData); images = new Image[bitmaps.Length]; for (int i = 0; i < bitmaps.Length; i++) { images[i] = wic.CreateGDIImageFromWICBitmap(bitmaps[i], format.Value); } } finally { // Clean up. if (bitmaps != null) { foreach (var bitmap in bitmaps) { bitmap.Dispose(); } } } } return(images); }
public void Test2ViewsSameShaderStage() { GorgonTexture2D texture = null; _framework.CreateTestScene(Shaders, Shaders, true); try { using (var data = new GorgonImageData(new GorgonTexture2DSettings { Width = 256, Height = 256, ArrayCount = 1, Format = BufferFormat.R8G8B8A8, MipCount = 1, ShaderViewFormat = BufferFormat.R8G8B8A8_Int, AllowUnorderedAccessViews = false, Usage = BufferUsage.Default })) { for (int i = 0; i < 5000; i++) { data.Buffers[0].Data.Position = ((GorgonRandom.RandomInt32(0, 256) * data.Buffers[0].PitchInformation.RowPitch) + GorgonRandom.RandomInt32(0, 256) * 4); data.Buffers[0].Data.Write((int)((GorgonRandom.RandomSingle() * 2.0f - 1.0f) * (Int32.MaxValue - 2))); } texture = _framework.Graphics.Textures.CreateTexture <GorgonTexture2D>("Test2D", data); } GorgonTextureShaderView view = texture.GetShaderView(BufferFormat.R8G8B8A8_UIntNormal); _framework.Graphics.Shaders.PixelShader.Resources[0] = texture; _framework.Graphics.Shaders.PixelShader.Resources[1] = view; Assert.IsTrue(_framework.Run() == DialogResult.Yes); } finally { if (texture != null) { texture.Dispose(); } } }
/// <summary> /// Function to load an image from a stream. /// </summary> /// <param name="stream">Stream containing the data to load.</param> /// <param name="size">Size of the data to read, in bytes.</param> /// <returns> /// The image data that was in the stream. /// </returns> protected internal override GorgonImageData LoadFromStream(GorgonDataStream stream, int size) { GorgonImageData imageData = null; TGAConversionFlags flags; if (DirectAccess.SizeOf <TGAHeader>() > size) { throw new EndOfStreamException(Resources.GORGFX_STREAM_EOF); } // Read the header information. IImageSettings settings = ReadHeader(stream, out flags); if (ArrayCount > 1) { settings.ArrayCount = ArrayCount; } try { // Create our image data structure. imageData = new GorgonImageData(settings); // Copy the data from the stream to the buffer. CopyImageData(stream, imageData, flags); } catch { // Clean up any memory allocated if we can't copy the image. if (imageData != null) { imageData.Dispose(); } throw; } return(imageData); }
/// <summary> /// Function to initialize the texture with optional initial data. /// </summary> /// <param name="initialData">Data used to populate the image.</param> protected override void OnInitialize(GorgonImageData initialData) { if ((Settings.Format != BufferFormat.Unknown) && (Settings.TextureFormat == BufferFormat.Unknown)) { Settings.TextureFormat = Settings.Format; } var desc = new D3D.Texture2DDescription { ArraySize = Settings.ArrayCount, Format = (GI.Format)Settings.TextureFormat, Width = Settings.Width, Height = Settings.Height, MipLevels = Settings.MipCount, BindFlags = GetBindFlags(false, true), Usage = D3D.ResourceUsage.Default, CpuAccessFlags = D3D.CpuAccessFlags.None, OptionFlags = D3D.ResourceOptionFlags.None, SampleDescription = GorgonMultisampling.Convert(Settings.Multisampling) }; Gorgon.Log.Print("{0} {1}: Creating 2D render target texture...", LoggingLevel.Verbose, GetType().Name, Name); // Create the texture. D3DResource = initialData != null ? new D3D.Texture2D(Graphics.D3DDevice, desc, initialData.Buffers.DataBoxes) : new D3D.Texture2D(Graphics.D3DDevice, desc); // Create the default render target view. _defaultRenderTargetView = GetRenderTargetView(Settings.Format, 0, 0, 1); GorgonRenderStatistics.RenderTargetCount++; GorgonRenderStatistics.RenderTargetSize += SizeInBytes; CreateDepthStencilBuffer(); // Set default viewport. Viewport = new GorgonViewport(0, 0, Settings.Width, Settings.Height, 0.0f, 1.0f); }
/// <summary> /// Function to create an image with initial data. /// </summary> /// <param name="initialData">Data to use when creating the image.</param> /// <remarks> /// The <paramref name="initialData" /> can be NULL (Nothing in VB.Net) IF the texture is not created with an Immutable usage flag. /// <para>To initialize the texture, create a new <see cref="GorgonLibrary.Graphics.GorgonImageData">GorgonImageData</see> object and fill it with image information.</para> /// </remarks> protected override void OnInitialize(GorgonImageData initialData) { var desc = new D3D.Texture3DDescription { Format = (Format)Settings.Format, Width = Settings.Width, Height = Settings.Height, Depth = Settings.Depth, MipLevels = Settings.MipCount, BindFlags = GetBindFlags(false, false), Usage = (D3D.ResourceUsage)Settings.Usage, OptionFlags = D3D.ResourceOptionFlags.None }; switch (Settings.Usage) { case BufferUsage.Staging: desc.CpuAccessFlags = D3D.CpuAccessFlags.Read | D3D.CpuAccessFlags.Write; break; case BufferUsage.Dynamic: desc.CpuAccessFlags = D3D.CpuAccessFlags.Write; break; default: desc.CpuAccessFlags = D3D.CpuAccessFlags.None; break; } if ((initialData != null) && (initialData.Buffers.Count > 0)) { D3DResource = new D3D.Texture3D(Graphics.D3DDevice, desc, initialData.Buffers.DataBoxes); } else { D3DResource = new D3D.Texture3D(Graphics.D3DDevice, desc); } }
/// <summary> /// Returns a collection of standard values for the data type this type converter is designed for when provided with a format context. /// </summary> /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext" /> that provides a format context that can be used to extract additional information about the environment from which this converter is invoked. This parameter or properties of this parameter can be null.</param> /// <returns> /// A <see cref="T:System.ComponentModel.TypeConverter.StandardValuesCollection" /> that holds a standard set of valid values, or null if the data type does not support a standard set of values. /// </returns> public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { var content = (GorgonImageContent)((ContentTypeDescriptor)context.Instance).Content; var formatList = (BufferFormat[])Enum.GetValues(typeof(BufferFormat)); BufferFormat currentFormat = content.ImageFormat; var formats = (from format in formatList let formatInfo = GorgonBufferFormatInfo.GetInfo(format) where (!formatInfo.HasDepth) && (!formatInfo.HasStencil) && (!formatInfo.IsTypeless) && (!formatInfo.IsCompressed) && (((content.ImageType == ImageType.Image1D) && (ContentObject.Graphics.VideoDevice.Supports1DTextureFormat(format))) || ((content.ImageType == ImageType.Image2D || content.ImageType == ImageType.ImageCube) && (ContentObject.Graphics.VideoDevice.Supports2DTextureFormat(format))) || ((content.ImageType == ImageType.Image3D) && (ContentObject.Graphics.VideoDevice.Supports3DTextureFormat(format)))) && (content.Codec.SupportedFormats.Any(item => item == format)) && ((content.MipCount == 1) || ((content.MipCount > 1) && (ContentObject.Graphics.VideoDevice.SupportsMipMaps(format)))) select format); return(new StandardValuesCollection(GorgonImageData.CanConvertToAny(currentFormat, formats))); }
/// <summary> /// Function to create a list of WIC bitmaps from Gorgon image data. /// </summary> /// <param name="data">Data to convert to the list of WIC bitmaps.</param> /// <returns>The list of WIC bitmaps.</returns> public WIC.Bitmap[] CreateWICBitmapsFromImageData(GorgonImageData data) { int bitmapIndex = 0; Guid bitmapFormat = GetGUID(data.Settings.Format); if (bitmapFormat == Guid.Empty) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, data.Settings.Format)); } // Make room for all the buffers. var bitmaps = new WIC.Bitmap[data.Buffers.Count]; // Copy to the bitmap. foreach (var buffer in data.Buffers) { var pointer = new DX.DataRectangle(buffer.Data.BasePointer, buffer.PitchInformation.RowPitch); bitmaps[bitmapIndex] = new WIC.Bitmap(Factory, buffer.Width, buffer.Height, bitmapFormat, pointer, pointer.Pitch * buffer.Height); bitmapIndex++; } return(bitmaps); }
/// <summary> /// Paints a representation of the value of an object using the specified <see cref="T:System.Drawing.Design.PaintValueEventArgs" />. /// </summary> /// <param name="e">A <see cref="T:System.Drawing.Design.PaintValueEventArgs" /> that indicates what to paint and where to paint it.</param> public override void PaintValue(PaintValueEventArgs e) { var sprite = (GorgonSpriteContent)((ContentTypeDescriptor)e.Context.Instance).Content; if (sprite.Texture == null) { e.Graphics.DrawImage(Resources.image_missing_16x16, e.Bounds, new Rectangle(0, 0, Resources.image_missing_16x16.Width, Resources.image_missing_16x16.Height), GraphicsUnit.Pixel); return; } // We cannot convert block compressed images to GDI+ image types, so just put a generic icon up. if ((sprite.Texture.FormatInformation.IsCompressed) || (!GorgonImageData.CanConvert(sprite.Texture.Settings.Format, BufferFormat.R8G8B8A8_UIntNormal))) { e.Graphics.DrawImage(Resources.open_image_16x16, e.Bounds, new Rectangle(0, 0, Resources.open_image_16x16.Width, Resources.open_image_16x16.Height), GraphicsUnit.Pixel); return; } // Copy the image into system memory, convert it and display it. using (GorgonImageData imageData = GorgonImageData.CreateFromTexture(sprite.Texture)) { imageData.Resize(e.Bounds.Width, e.Bounds.Height, false, ImageFilter.Fant); imageData.ConvertFormat(BufferFormat.R8G8B8A8_UIntNormal); using (Image gdiImage = imageData.Buffers[0].ToGDIImage()) { e.Graphics.DrawImage(gdiImage, e.Bounds); } } }
/// <summary> /// Function to perform the copying of image data into the buffer. /// </summary> /// <param name="stream">Stream containing the image data.</param> /// <param name="image">Image data.</param> /// <param name="conversionFlags">Flags used to convert the image.</param> private void CopyImageData(GorgonDataStream stream, GorgonImageData image, TGAConversionFlags conversionFlags) { var buffer = image.Buffers[0]; // Get the first buffer only. var formatInfo = GorgonBufferFormatInfo.GetInfo(image.Settings.Format); GorgonFormatPitch srcPitch = (conversionFlags & TGAConversionFlags.Expand) == TGAConversionFlags.Expand ? new GorgonFormatPitch(image.Settings.Width * 3, image.Settings.Width * 3 * image.Settings.Height) : formatInfo.GetPitch(image.Settings.Width, image.Settings.Height, PitchFlags.None); // Otherwise, allocate a buffer for conversion. var srcPtr = (byte *)stream.PositionPointerUnsafe; var destPtr = (byte *)buffer.Data.UnsafePointer; // Adjust destination for inverted axes. if ((conversionFlags & TGAConversionFlags.InvertX) == TGAConversionFlags.InvertX) { destPtr += buffer.PitchInformation.RowPitch - formatInfo.SizeInBytes; } if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY) { destPtr += (image.Settings.Height - 1) * buffer.PitchInformation.RowPitch; } // Get bounds of image memory. var scanSize = (int)(stream.Length - stream.Position); byte *endScan = (byte *)stream.PositionPointerUnsafe + scanSize; int opaqueLineCount = 0; for (int y = 0; y < image.Settings.Height; y++) { bool setOpaque; if ((conversionFlags & TGAConversionFlags.RLE) == TGAConversionFlags.RLE) { setOpaque = ReadCompressed(ref srcPtr, image.Settings.Width, destPtr, endScan, image.Settings.Format, conversionFlags); } else { setOpaque = ReadUncompressed(srcPtr, srcPitch.RowPitch, destPtr, image.Settings.Format, conversionFlags); srcPtr += srcPitch.RowPitch; } if ((setOpaque) && (SetOpaqueIfZeroAlpha)) { opaqueLineCount++; } if ((conversionFlags & TGAConversionFlags.InvertY) != TGAConversionFlags.InvertY) { destPtr -= buffer.PitchInformation.RowPitch; } else { destPtr += buffer.PitchInformation.RowPitch; } } if (opaqueLineCount != image.Settings.Height) { return; } // Set the alpha to opaque if we don't have any alpha values (i.e. alpha = 0 for all pixels). destPtr = (byte *)buffer.Data.UnsafePointer; for (int y = 0; y < image.Settings.Height; y++) { CopyScanline(destPtr, buffer.PitchInformation.RowPitch, destPtr, buffer.PitchInformation.RowPitch, image.Settings.Format, ImageBitFlags.OpaqueAlpha); destPtr += buffer.PitchInformation.RowPitch; } }
/// <summary> /// Function to persist image data to a stream. /// </summary> /// <param name="imageData"><see cref="GorgonLibrary.Graphics.GorgonImageData">Gorgon image data</see> to persist.</param> /// <param name="stream">Stream that will contain the data.</param> protected override void SaveToStream(GorgonImageData imageData, Stream stream) { if (imageData.Settings.Format != BufferFormat.R8G8B8A8_UIntNormal) { throw new ArgumentException(@"The image format must be R8G8B8A8_UIntNormal", "imageData"); } // First, we'll need to set up our header metadata. var header = new TvHeader { MagicValueData = MagicValue, Width = imageData.Settings.Width, Height = imageData.Settings.Height }; // Write the metadata to the stream. using (var writer = new GorgonBinaryWriter(stream, true)) { writer.WriteValue(header); // Now, we need to encode the image data as 1 byte for every other color component per pixel. // In essence, we'll be writing one channel as a byte and moving to the next pixel. unsafe { // We're in unsafe land now. Again, if you're uncomfortable with pointers, the GorgonDataStream object // provides safe methods to read image data. // Get the pointer to our image buffer. var imagePtr = (byte *)imageData.UnsafePointer; // Allocate a buffer to store our scanline before dumping to the file. var scanLineBuffer = new byte[imageData.Settings.Width * 2]; // For each scan line in the image we'll encode the data as described above. for (int y = 0; y < imageData.Settings.Height; ++y) { // Read 4 bytes at a time. var colorPtr = (uint *)imagePtr; // Reset to the beginning of the scanline buffer. fixed(byte *scanLinePtr = scanLineBuffer) { // Need to alias the pointer because the result value in the fixed // block can't be changed. var scanLine = (ushort *)scanLinePtr; // Loop through the scan line until we're at its end. for (int x = 0; x < imageData.Settings.Width; ++x) { // We're assuming our image data is 4 bytes/pixel, but in real world scenarios this is dependent upon // the format of the data. var pixel = *(colorPtr++); // Get the alpha channel for this pixel. var alpha = (byte)((pixel >> 24) & 0xff); // Since we encode 1 byte per color component for each pixel, we need to bump up the bit shift // by 8 bits. Once we get above 24 bits we'll start over since we're only working with 4 bytes // per pixel in the destination. // We determine how many bits to shift the pixel based on horizontal positioning. // We assume that the image is based on 4 bytes/pixel. In most cases this value should be // determined by dividing the row pitch by the image width. // Get the color component for the pixel. var color = (byte)((pixel >> (8 * (x % 3))) & 0xff); // Write it to the scanline. // We're encoding a pixel as a single color component with its alpha channel // value into an unsigned 16 bit number. *(scanLine++) = (ushort)((color << 8) | alpha); } } // Ensure that we move to the next line by the row pitch and not the amount of pixels. // Some images put padding in for alignment reasons which can throw off the data offsets. // Also, the width is not suitable as a pixel is often more than 1 byte. imagePtr += imageData.Buffers[0].PitchInformation.RowPitch; // Send the scanline to the file. writer.Write(scanLineBuffer); } } } }
/// <summary> /// Function to load an image from a stream. /// </summary> /// <param name="stream">Stream containing the data to load.</param> /// <param name="size">Size of the data to read, in bytes.</param> /// <returns> /// The image data that was in the stream. /// </returns> protected internal override GorgonImageData LoadFromStream(GorgonDataStream stream, int size) { GorgonImageData result = null; // Get our WIC interface. var wrapperStream = new GorgonStreamWrapper(stream); using (var wic = new GorgonWICImage()) { using (var decoder = new BitmapDecoder(wic.Factory, SupportedFormat)) { using (var wicStream = new WICStream(wic.Factory, wrapperStream)) { try { decoder.Initialize(wicStream, DecodeOptions.CacheOnDemand); } catch (SharpDXException) { // Repackage this exception to keep in line with our API. throw new IOException(string.Format(Resources.GORGFX_IMAGE_FILE_INCORRECT_DECODER, Codec)); } using (var frame = decoder.GetFrame(0)) { Guid bestFormat; var settings = ReadMetaData(wic, decoder, frame, out bestFormat); if (settings.Format == BufferFormat.Unknown) { throw new IOException(string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, settings.Format)); } // Create our image data. try { _actualArrayCount = settings.ArrayCount; if (ArrayCount > 0) { settings.ArrayCount = ArrayCount; } result = new GorgonImageData(settings); if ((settings.ArrayCount > 1) && (_actualArrayCount > 1)) { ReadFrames(wic, result, decoder); } else { ReadFrame(wic, result, frame.PixelFormat, bestFormat, frame); } // If we've not read the full length of the data (WIC seems to ignore the CRC on the IEND chunk for PNG files for example), // then we need to move the pointer up by however many bytes we've missed. if (wrapperStream.Position < size) { wrapperStream.Position = size; } } catch { // If we run into a problem, dump the memory buffer. if (result != null) { result.Dispose(); } throw; } } } } } return(result); }
/// <summary> /// Function to persist image data to a stream. /// </summary> /// <param name="imageData"><see cref="GorgonLibrary.Graphics.GorgonImageData">Gorgon image data</see> to persist.</param> /// <param name="stream">Stream that will contain the data.</param> protected internal abstract void SaveToStream(GorgonImageData imageData, Stream stream);
/// <summary> /// Function to perform any post processing on image data. /// </summary> internal void PostProcess(GorgonImageData data) { IImageSettings destSettings = data.Settings.Clone(); int width = Width > 0 ? Width : data.Settings.Width; int height = Height > 0 ? Height : data.Settings.Height; int mipCount = MipCount > 0 ? MipCount : data.Settings.MipCount; BufferFormat format = (Format != BufferFormat.Unknown) ? Format : data.Settings.Format; Rectangle newSize = Rectangle.Empty; int mipStart = 0; var sourceInfo = GorgonBufferFormatInfo.GetInfo(data.Settings.Format); var destInfo = GorgonBufferFormatInfo.GetInfo(format); // First, confirm whether we can perform format conversions. using (var wic = new GorgonWICImage()) { Guid srcPixelFormat = wic.GetGUID(data.Settings.Format); Guid destPixelFormat = wic.GetGUID(format); // Do nothing if we can't do anything with the source format. if (srcPixelFormat == Guid.Empty) { return; } // Cancel conversion if we're using the same format. if (srcPixelFormat == destPixelFormat) { destPixelFormat = Guid.Empty; } // Get the new size. if ((width != data.Settings.Width) || (height != data.Settings.Height)) { newSize = new Rectangle(0, 0, width, height); } // Set up destination buffer settings. destSettings.Format = format; destSettings.Width = width; destSettings.Height = height; destSettings.MipCount = mipCount; // Ensure we don't go over the maximum. int maxMips = GorgonImageData.GetMaxMipCount(destSettings); if (mipCount > maxMips) { mipCount = maxMips; } // Nothing's going to happen here, so leave. if ((destPixelFormat == Guid.Empty) && (newSize == Rectangle.Empty) && (mipCount == 1)) { return; } // Create our worker buffer. GorgonImageData destData; using (destData = new GorgonImageData(destSettings)) { // The first step is to convert and resize our images: if ((destPixelFormat != Guid.Empty) || (newSize != Rectangle.Empty)) { for (int array = 0; array < destSettings.ArrayCount; array++) { // We're not going to copy mip levels at this point, that will come in the next step. for (int depth = 0; depth < destSettings.Depth; depth++) { // Get our source/destination buffers. var sourceBuffer = data.Buffers[0, data.Settings.ImageType == ImageType.Image3D ? depth : array]; var destBuffer = destData.Buffers[0, data.Settings.ImageType == ImageType.Image3D ? depth : array]; var dataRect = new DataRectangle(sourceBuffer.Data.BasePointer, sourceBuffer.PitchInformation.RowPitch); // Create a temporary WIC bitmap to work with. using (var bitmap = new Bitmap(wic.Factory, sourceBuffer.Width, sourceBuffer.Height, srcPixelFormat, dataRect, sourceBuffer.PitchInformation.SlicePitch)) { wic.TransformImageData(bitmap, destBuffer.Data.BasePointer, destBuffer.PitchInformation.RowPitch, destBuffer.PitchInformation.SlicePitch, destPixelFormat, sourceInfo.IssRGB, destInfo.IssRGB, Dithering, newSize, Clip, Filter); } } } // Adjust the mip map starting point. mipStart = 1; } // Next, we need to build mip maps. if (destSettings.MipCount > 1) { // The first step is to convert and resize our images: for (int array = 0; array < destSettings.ArrayCount; array++) { int mipDepth = destSettings.Depth; for (int mip = mipStart; mip < destSettings.MipCount; mip++) { // We're not going to copy mip levels at this point, that will come in the next step. for (int depth = 0; depth < mipDepth; depth++) { // Get our source/destination buffers. var sourceBuffer = destData.Buffers[0, data.Settings.ImageType == ImageType.Image3D ? (destSettings.Depth / mipDepth) * depth : array]; var destBuffer = destData.Buffers[mip, data.Settings.ImageType == ImageType.Image3D ? depth : array]; var dataRect = new DataRectangle(sourceBuffer.Data.BasePointer, sourceBuffer.PitchInformation.RowPitch); // Create a temporary WIC bitmap to work with. using (var bitmap = new Bitmap(wic.Factory, sourceBuffer.Width, sourceBuffer.Height, srcPixelFormat, dataRect, sourceBuffer.PitchInformation.SlicePitch)) { wic.TransformImageData(bitmap, destBuffer.Data.BasePointer, destBuffer.PitchInformation.RowPitch, destBuffer.PitchInformation.SlicePitch, Guid.Empty, false, false, ImageDithering.None, new Rectangle(0, 0, destBuffer.Width, destBuffer.Height), false, Filter); } } if (mipDepth > 1) { mipDepth >>= 1; } } } } // Update our data. data.TakeOwnership(destData); } } }
/// <summary> /// Function to create 2D Gorgon image data from multiple single System.Drawing.Images. /// </summary> /// <param name="wic">Windows Imaging Component interface to use.</param> /// <param name="images">Images to convert.</param> /// <param name="options">Options for conversion.</param> /// <returns>The converted image data.</returns> public static GorgonImageData Create2DImageDataFromImages(GorgonWICImage wic, IList <Image> images, GorgonGDIOptions options) { if (options.Format == BufferFormat.Unknown) { options.Format = GetBufferFormat(images[0].PixelFormat); } if (options.Format == BufferFormat.Unknown) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, images[0].PixelFormat)); } if (images.Any(item => item.PixelFormat != images[0].PixelFormat)) { throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORGFX_IMAGE_MUST_BE_SAME_FORMAT, images[0].PixelFormat)); } if (options.Width < 1) { options.Width = images[0].Width; } if (options.Height < 1) { options.Height = images[0].Height; } if (options.ArrayCount < 1) { options.ArrayCount = 1; } options.MipCount = options.MipCount < 1 ? 1 : options.MipCount.Min(GorgonImageData.GetMaxMipCount(options.Width, options.Height)); // Create our settings. var settings = new GorgonTexture2DSettings { Width = options.Width, Height = options.Height, MipCount = options.MipCount, ArrayCount = options.ArrayCount, Format = options.Format, AllowUnorderedAccessViews = options.AllowUnorderedAccess, ShaderViewFormat = options.ViewFormat, Usage = options.Usage }; if ((options.ArrayCount * options.MipCount) > images.Count) { throw new GorgonException(GorgonResult.CannotCreate, Resources.GORGFX_IMAGE_MIPCOUNT_ARRAYCOUNT_TOO_LARGE); } // Create our image data. var data = new GorgonImageData(settings); for (int array = 0; array < data.Settings.ArrayCount; array++) { for (int mipLevel = 0; mipLevel < data.Settings.MipCount; mipLevel++) { var image = images[array * data.Settings.MipCount + mipLevel]; if (image == null) { continue; } // Using the image, convert to a WIC bitmap object. using (var bitmap = wic.CreateWICImageFromImage(image)) { var buffer = data.Buffers[mipLevel, array]; wic.AddWICBitmapToImageData(bitmap, options.Filter, options.Dither, buffer, options.UseClipping); } } } return(data); }
/// <summary> /// Function to persist image data to a stream. /// </summary> /// <param name="imageData"><see cref="GorgonLibrary.Graphics.GorgonImageData">Gorgon image data</see> to persist.</param> /// <param name="stream">Stream that will contain the data.</param> protected internal override void SaveToStream(GorgonImageData imageData, Stream stream) { int frameCount = 1; // Wrap the stream so WIC doesn't mess up the position. using (var wrapperStream = new GorgonStreamWrapper(stream)) { using (var wic = new GorgonWICImage()) { // Find a compatible format. Guid targetFormat = wic.GetGUID(imageData.Settings.Format); if (targetFormat == Guid.Empty) { throw new IOException(string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, imageData.Settings.Format)); } Guid actualFormat = targetFormat; using (var encoder = new BitmapEncoder(wic.Factory, SupportedFormat)) { try { encoder.Initialize(wrapperStream); AddCustomMetaData(encoder, null, 0, imageData.Settings, null); } catch (SharpDXException) { // Repackage this exception to keep in line with our API. throw new IOException(string.Format(Resources.GORGFX_IMAGE_FILE_INCORRECT_ENCODER, Codec)); } using (var encoderInfo = encoder.EncoderInfo) { if ((imageData.Settings.ArrayCount > 1) && (CodecUseAllFrames) && (encoderInfo.IsMultiframeSupported)) { frameCount = imageData.Settings.ArrayCount; } for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { using (var frame = new BitmapFrameEncode(encoder)) { var buffer = imageData.Buffers[0, frameIndex]; frame.Initialize(); frame.SetSize(buffer.Width, buffer.Height); frame.SetResolution(72, 72); frame.SetPixelFormat(ref actualFormat); SetFrameOptions(frame); // If the image encoder doesn't like the format we've chosen, then we'll need to convert to // the best format for the codec. if (targetFormat != actualFormat) { var rect = new DataRectangle(buffer.Data.BasePointer, buffer.PitchInformation.RowPitch); using (var bitmap = new Bitmap(wic.Factory, buffer.Width, buffer.Height, targetFormat, rect)) { // If we're using a codec that supports 8 bit indexed data, then get the palette info. var paletteInfo = GetPaletteInfo(wic, bitmap); if (paletteInfo == null) { throw new IOException(string.Format(Resources.GORGFX_IMAGE_FILE_INCORRECT_ENCODER, Codec)); } try { using (var converter = new FormatConverter(wic.Factory)) { converter.Initialize(bitmap, actualFormat, (BitmapDitherType)Dithering, paletteInfo.Item1, paletteInfo.Item2, paletteInfo.Item3); if (paletteInfo.Item1 != null) { frame.Palette = paletteInfo.Item1; } AddCustomMetaData(encoder, frame, frameIndex, imageData.Settings, (paletteInfo.Item1 != null) ? paletteInfo.Item1.Colors : null); frame.WriteSource(converter); } } finally { if (paletteInfo.Item1 != null) { paletteInfo.Item1.Dispose(); } } } } else { // No conversion was needed, just dump as-is. AddCustomMetaData(encoder, frame, frameIndex, imageData.Settings, null); frame.WritePixels(buffer.Height, buffer.Data.BasePointer, buffer.PitchInformation.RowPitch, buffer.PitchInformation.SlicePitch); } frame.Commit(); } } } encoder.Commit(); } } } }
/// <summary> /// Initializes a new instance of the <see cref="GorgonImageBufferList"/> class. /// </summary> /// <param name="image">The image that owns this list.</param> internal GorgonImageBufferList(GorgonImageData image) { _image = image; _buffers = new GorgonImageBuffer[0]; }
public void TestTextureUpdate() { float diver = 1.0f; var shader = _framework.Graphics.Shaders.FromMemory <GorgonPixelShader>("PS", "TestPSUpdateSub", Resources.TextureShaders); var texture = _framework.Graphics.Textures.CreateTexture("Texture", new GorgonTexture2DSettings { ArrayCount = 1, Format = BufferFormat.R8G8B8A8_UIntNormal, MipCount = 4, Height = 256, Width = 256, Usage = BufferUsage.Default }); var dynTexture = _framework.Graphics.Textures.CreateTexture("DynTexture", new GorgonTexture2DSettings { ArrayCount = 1, Format = BufferFormat.R8G8B8A8_UIntNormal, MipCount = 1, Height = 256, Width = 256, Usage = BufferUsage.Dynamic }); var imageData = GorgonImageData.CreateFromGDIImage(Resources.Glass, ImageType.Image2D, new GorgonGDIOptions { MipCount = 4 }); _framework.CreateTestScene(Shaders, Shaders, true); _framework.Graphics.Shaders.PixelShader.Current = shader; _framework.Graphics.Shaders.PixelShader.Resources[1] = texture; texture.UpdateSubResource(imageData.Buffers[0], new Rectangle { Width = 128, Height = 128, X = 0, Y = 0 }); texture.UpdateSubResource(imageData.Buffers[1], new Rectangle { Width = 64, Height = 64, X = 128, Y = 0 }); GorgonTexture2D testIntFormat = _framework.Graphics.Textures.CreateTexture("asas", new GorgonTexture2DSettings { Format = BufferFormat.R8G8B8A8_Int, Usage = BufferUsage.Dynamic, Width = 64, Height = 64 }); _framework.IdleFunc = () => { _framework.Graphics.Shaders.PixelShader.Resources[2] = null; using (var lockData = dynTexture.Lock(BufferLockFlags.Write | BufferLockFlags.Discard)) { for (int i = 0; i < 4096; ++i) { int y = GorgonRandom.RandomInt32(0, imageData.Buffers[0].Height); int x = GorgonRandom.RandomInt32(0, imageData.Buffers[0].Width); // 95417E imageData.Buffers[0].Data.Position = (y * imageData.Buffers[0].PitchInformation.RowPitch) + (x * 4); lockData.Data.Position = (y * lockData.PitchInformation.RowPitch) + (x * dynTexture.FormatInformation.SizeInBytes); var color = new GorgonColor(imageData.Buffers[0].Data.ReadInt32()); color = new GorgonColor(color.Red / diver, color.Green / diver, color.Blue / diver); lockData.Data.Write(color.ToARGB()); //lockData.Data.Write(0xFF00FF00); /*lockData.Data.Write(Color.FromArgb(color.ToARGB()).R); * lockData.Data.Write(Color.FromArgb(color.ToARGB()).G); * lockData.Data.Write(Color.FromArgb(color.ToARGB()).B); * lockData.Data.Write(Color.FromArgb(color.ToARGB()).A);*/ } } diver += 0.5f * GorgonTiming.Delta; if (diver > 32.0f) { diver = 1.0f; } _framework.Graphics.Shaders.PixelShader.Resources[2] = dynTexture; return(false); }; Assert.IsTrue(_framework.Run() == DialogResult.Yes); }
/// <summary> /// Function to create a 3D Gorgon image data from multiple System.Drawing.Images. /// </summary> /// <param name="wic">Windows Imaging Component interface to use.</param> /// <param name="images">Images to convert.</param> /// <param name="options">Conversion options.</param> /// <returns>The converted image data.</returns> public static GorgonImageData Create3DImageDataFromImages(GorgonWICImage wic, IList <Image> images, GorgonGDIOptions options) { if (options.Format == BufferFormat.Unknown) { options.Format = GetBufferFormat(images[0].PixelFormat); } if (options.Format == BufferFormat.Unknown) { throw new GorgonException(GorgonResult.FormatNotSupported, string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, images[0].PixelFormat)); } if (images.Any(item => item.PixelFormat != images[0].PixelFormat)) { throw new GorgonException(GorgonResult.CannotCreate, string.Format(Resources.GORGFX_IMAGE_MUST_BE_SAME_FORMAT, images[0].PixelFormat)); } if (options.Width <= 0) { options.Width = images[0].Width; } if (options.Height <= 0) { options.Height = images[0].Height; } if (options.Depth < 1) { options.Depth = 1; } options.MipCount = options.MipCount < 1 ? 1 : options.MipCount.Min(GorgonImageData.GetMaxMipCount(options.Width, options.Height, options.Depth)); // Set the depth to the number of images if there are no mip-maps. if ((images.Count > 1) && (options.MipCount == 1)) { options.Depth = images.Count; } // Create our settings. var settings = new GorgonTexture3DSettings { Width = options.Width, Height = options.Height, Depth = options.Depth, MipCount = options.MipCount, Format = options.Format, AllowUnorderedAccessViews = options.AllowUnorderedAccess, ShaderViewFormat = options.ViewFormat, Usage = options.Usage }; // Only volume textures that are size to a power of 2 can have mip maps. if ((!settings.IsPowerOfTwo) && (options.MipCount > 1)) { throw new GorgonException(GorgonResult.CannotCreate, Resources.GORGFX_IMAGE_VOLUME_NOT_POWER_OF_TWO); } // Create our image data. var data = new GorgonImageData(settings); int depthMip = options.Depth; int imageIndex = 0; for (int mipLevel = 0; mipLevel < data.Settings.MipCount; mipLevel++) { if (imageIndex >= images.Count) { data.Dispose(); throw new GorgonException(GorgonResult.CannotCreate, Resources.GORGFX_IMAGE_VOLUME_MIPCOUNT_DEPTHCOUNT_TOO_LARGE); } for (int depth = 0; depth < depthMip; depth++) { var image = images[imageIndex + depth]; // Skip NULL images. if (image == null) { continue; } // Using the image, convert to a WIC bitmap object. using (var bitmap = wic.CreateWICImageFromImage(image)) { var buffer = data.Buffers[mipLevel, depth]; wic.AddWICBitmapToImageData(bitmap, options.Filter, options.Dither, buffer, options.UseClipping); } } imageIndex += depthMip; // Decrease depth based on mip level. if (depthMip > 1) { depthMip >>= 1; } } return(data); }
/// <summary> /// Function to persist image data to a stream. /// </summary> /// <param name="imageData"><see cref="GorgonLibrary.Graphics.GorgonImageData">Gorgon image data</see> to persist.</param> /// <param name="stream">Stream that will contain the data.</param> protected internal override void SaveToStream(GorgonImageData imageData, Stream stream) { // Use a binary writer. using (var writer = new GorgonBinaryWriter(stream, true)) { // Write the header for the file. TGAConversionFlags conversionFlags; WriteHeader(imageData.Settings, writer, out conversionFlags); GorgonFormatPitch pitch; if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { pitch = new GorgonFormatPitch(imageData.Settings.Width * 3, imageData.Settings.Width * 3 * imageData.Settings.Height); } else { var formatInfo = GorgonBufferFormatInfo.GetInfo(imageData.Settings.Format); pitch = formatInfo.GetPitch(imageData.Settings.Width, imageData.Settings.Height, PitchFlags.None); } // Get the pointer to the first mip/array/depth level. var srcPointer = (byte *)imageData.Buffers[0].Data.UnsafePointer; var srcPitch = imageData.Buffers[0].PitchInformation; // If the two pitches are equal, then just write out the buffer. if ((pitch == srcPitch) && (conversionFlags == TGAConversionFlags.None)) { writer.Write(srcPointer, srcPitch.SlicePitch); return; } // If we have to do a conversion, create a worker buffer. using (var convertBuffer = new GorgonDataStream(pitch.SlicePitch)) { var destPtr = (byte *)convertBuffer.UnsafePointer; // Write out each scan line. for (int y = 0; y < imageData.Settings.Height; y++) { if ((conversionFlags & TGAConversionFlags.RGB888) == TGAConversionFlags.RGB888) { Compress24BPPScanLine(srcPointer, srcPitch.RowPitch, destPtr, pitch.RowPitch); } else if ((conversionFlags & TGAConversionFlags.Swizzle) == TGAConversionFlags.Swizzle) { SwizzleScanline(srcPointer, srcPitch.RowPitch, destPtr, pitch.RowPitch, imageData.Settings.Format, ImageBitFlags.None); } else { CopyScanline(srcPointer, srcPitch.RowPitch, destPtr, pitch.RowPitch, imageData.Settings.Format, ImageBitFlags.None); } destPtr += pitch.RowPitch; srcPointer += srcPitch.RowPitch; } // Persist to the stream. writer.Write(convertBuffer.UnsafePointer, pitch.SlicePitch); } } }
/// <summary> /// Function to clip the sprite region from the texture. /// </summary> /// <param name="texture">Texture containing the image data to clip.</param> /// <returns>A rectangle for the sprite. Or an empty rectangle if no sprite was selected.</returns> public unsafe Rectangle Clip(GorgonTexture2D texture) { GorgonImageData imageData = null; try { // Constrain our mouse to the texture. _startPoint.X = _startPoint.X.Max(0).Min(texture.Settings.Width - 1); _startPoint.Y = _startPoint.Y.Max(0).Min(texture.Settings.Height - 1); imageData = GorgonImageData.CreateFromTexture(texture); _stride = imageData.Buffers[0].PitchInformation.RowPitch; _format = imageData.Settings.Format; _bytesPerPixel = GorgonBufferFormatInfo.GetInfo(_format).SizeInBytes; _textureWidth = imageData.Settings.Width; // Get a pointer to the buffer. byte *bytePtr = (byte *)imageData.UnsafePointer; if (IsMaskValue(bytePtr, _startPoint.X, _startPoint.Y)) { return(Rectangle.Empty); } Queue <ClipSpan> clipSpans = new Queue <ClipSpan>(); _pixels = new bool[imageData.Settings.Width * imageData.Settings.Height]; int left = _startPoint.X; int top = _startPoint.Y; int right = left; int bottom = top; // Get the initial span from our starting point and add it to our queue. ClipSpan span = GetSpan(bytePtr, left, top); clipSpans.Enqueue(span); while (clipSpans.Count > 0) { // Take the span off the queue. span = clipSpans.Dequeue(); // Find the next vertical span above and below the current span. int west = span.Start; int east = span.End; int north = span.Y - 1; int south = span.Y + 1; // Check each pixel between the start and end of the upper and lower spans. for (int x = west; x <= east; ++x) { int pixelindex = _textureWidth * north + x; if ((span.Y > 0) && (!IsMaskValue(bytePtr, x, north)) && (!_pixels[pixelindex])) { clipSpans.Enqueue(GetSpan(bytePtr, x, north)); } pixelindex = _textureWidth * south + x; if ((span.Y >= imageData.Settings.Height - 1) || (IsMaskValue(bytePtr, x, south)) || (_pixels[pixelindex])) { continue; } clipSpans.Enqueue(GetSpan(bytePtr, x, south)); } // Update the boundaries. left = west.Min(left); right = (east + 1).Max(right); top = (north + 1).Min(top); bottom = south.Max(bottom); } return(Rectangle.FromLTRB(left, top, right, bottom)); } finally { if (imageData != null) { imageData.Dispose(); } } }
/// <summary> /// Function to read multiple frames from a decoder that supports multiple frames. /// </summary> /// <param name="wic">WIC interface.</param> /// <param name="data">Image data to populate.</param> /// <param name="decoder">Decoder for the image.</param> private void ReadFrames(GorgonWICImage wic, GorgonImageData data, BitmapDecoder decoder) { Guid bestPixelFormat = wic.GetGUID(data.Settings.Format); // Find the best fit pixel format. if (bestPixelFormat == Guid.Empty) { throw new IOException(string.Format(Resources.GORGFX_FORMAT_NOT_SUPPORTED, data.Settings.Format)); } // Use the image array as the frames. int arrayCount = _actualArrayCount.Min(data.Settings.ArrayCount); for (int array = 0; array < arrayCount; array++) { var buffer = data.Buffers[0, array]; // Get the frame data. using (var frame = decoder.GetFrame(array)) { IntPtr bufferPointer = buffer.Data.BasePointer; Guid frameFormat = frame.PixelFormat; int frameWidth = frame.Size.Width; int frameHeight = frame.Size.Height; var frameOffset = GetFrameOffset(frame); // Calculate the pointer offset if we have an offset from the frame. Only offset if we're clipping the image though. if (((frameOffset.Y != 0) || (frameOffset.X != 0)) && (Clip)) { bufferPointer = buffer.Data.BasePointer + (frameOffset.Y * buffer.PitchInformation.RowPitch) + (frameOffset.X * (buffer.PitchInformation.RowPitch / buffer.Width)); } // Confirm that we actually need to perform clipping. bool needsSizeAdjust = (frameWidth + frameOffset.X > data.Settings.Width) || (frameHeight + frameOffset.Y > data.Settings.Height); // If the formats match, then we don't need to do conversion. if (bestPixelFormat == frameFormat) { // If the width and height are the same then we can just do a straight copy into the buffer. if (((frameWidth == data.Settings.Width) && (frameHeight == data.Settings.Height)) || ((!needsSizeAdjust) && (Clip))) { frame.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); continue; } // We need to scale the image up/down to the size of our image data. if (!Clip) { using (var scaler = new BitmapScaler(wic.Factory)) { scaler.Initialize(frame, data.Settings.Width, data.Settings.Height, (BitmapInterpolationMode)Filter); scaler.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); } continue; } using (var clipper = new BitmapClipper(wic.Factory)) { clipper.Initialize(frame, new Rectangle(0, 0, data.Settings.Width, data.Settings.Height)); clipper.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); } continue; } // Poop. We need to convert this image. using (var converter = new FormatConverter(wic.Factory)) { converter.Initialize(frame, bestPixelFormat, (BitmapDitherType)Dithering, null, 0.0, BitmapPaletteType.Custom); if (((frameWidth == data.Settings.Width) && (frameHeight == data.Settings.Height)) || ((!needsSizeAdjust) && (Clip))) { converter.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); continue; } // And we need to scale the image. if (!Clip) { using (var scaler = new BitmapScaler(wic.Factory)) { scaler.Initialize(converter, data.Settings.Width, data.Settings.Height, (BitmapInterpolationMode)Filter); scaler.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); } continue; } using (var clipper = new BitmapClipper(wic.Factory)) { clipper.Initialize(frame, new Rectangle(0, 0, data.Settings.Width, data.Settings.Height)); clipper.CopyPixels(buffer.PitchInformation.RowPitch, bufferPointer, buffer.PitchInformation.SlicePitch); } } } } }
/// <summary> /// Function to load an image from a stream. /// </summary> /// <param name="stream">Stream containing the data to load.</param> /// <param name="size">Size of the data to read, in bytes.</param> /// <returns> /// The image data that was in the stream. /// </returns> protected override GorgonImageData LoadFromStream(GorgonDataStream stream, int size) { // Read the image meta data so we'll know how large the data should be. IImageSettings settings = ReadMetaData(stream); // Calculate the expected size of the image. int dataSize = settings.Width * settings.Height * 2; if ((size - TvHeader.SizeInBytes) != dataSize) { throw new ArgumentException("The data in the stream is not the same size as the proposed image size."); } // We'll be getting into unsafe territory here for performance reasons. // If you're not comfortable with pointers, the stream object provides other ways of retrieving // the data and copying it. unsafe { // Create our resulting image buffer. var result = new GorgonImageData(settings); // Get pointers to our data buffers. var imagePtr = (byte *)result.UnsafePointer; var srcPtr = (ushort *)stream.UnsafePointer; // Write each scanline. for (int y = 0; y < settings.Height; ++y) { var destPtr = (uint *)imagePtr; // Decode the pixels in the scan line for our resulting image. for (int x = 0; x < settings.Width; ++x) { // Get our current pixel. ushort pixel = *(srcPtr++); // Since we encode 1 byte per color component for each pixel, we need to bump up the bit shift // by 8 bits. Once we get above 24 bits we'll start over since we're only working with 4 bytes // per pixel in the destination. // We determine how many bits to shift the pixel based on horizontal positioning. // We assume that the image is based on 4 bytes/pixel. In most cases this value should be // determined by dividing the row pitch by the image width. // Write the color by shifting the byte in the source data to the appropriate byte position. var color = (uint)(((pixel >> 8) & 0xff) << (8 * (x % 3))); var alpha = (uint)((pixel & 0xff) << 24); *(destPtr++) = color | alpha; } // Ensure that we move to the next line by the row pitch and not the amount of pixels. // Some images put padding in for alignment reasons which can throw off the data offsets. imagePtr += result.Buffers[0].PitchInformation.RowPitch; } // Move to the end of the stream. stream.Position += dataSize; return(result); } }