/*=============================================================================
Project: SharpImage
Module: siTransferFunction.cs
Language: C#
Author: Dan Mueller
Date: $Date: 2008-01-09 16:23:10 +1000 (Wed, 09 Jan 2008) $
Revision: $Revision: 29 $
Copyright (c) Queensland University of Technology (QUT) 2007.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=============================================================================*/
using System;
using System.IO;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace SharpImage.Rendering
{
#region TransferFunctionPaintMode Enumeration
//=========================================================================
///
/// Specifies the painting mode for the transfer function.
///
public enum siTransferFunctionPaintMode
{
///
/// Paint the transfer function for use on the screen.
///
Screen,
///
/// Paint the transfer function for use in a publication.
///
Publish,
///
/// Paint the transfer function for use as a LUT function.
///
Function
}
//=========================================================================
#endregion
#region siTransferFunctionPaintEventArgs Class
//=========================================================================
public class siTransferFunctionPaintEventArgs : PaintEventArgs
{
private siTransferFunctionPaintMode m_PaintMode;
private String m_Layer;
///
/// Default constructor.
///
/// The graphics handle to paint to.
///
/// The mode of the painting: eg. Screen or Function.
/// The key of the layer to paint. Null or empty to paint all layers.
public siTransferFunctionPaintEventArgs(Graphics g, Rectangle clipRect, siTransferFunctionPaintMode mode, String layer)
: base(g, clipRect)
{
this.m_PaintMode = mode;
this.m_Layer = layer;
}
///
/// Constructor taking PaintEventArgs.
///
///
///
///
public siTransferFunctionPaintEventArgs(PaintEventArgs e, siTransferFunctionPaintMode mode, String layer)
: this(e.Graphics, e.ClipRectangle, mode, layer)
{
// Nothing to do
}
///
/// Constructor taking PaintEventArgs and drawing all layers.
///
///
///
public siTransferFunctionPaintEventArgs(PaintEventArgs e, siTransferFunctionPaintMode mode)
: this(e.Graphics, e.ClipRectangle, mode, null)
{
// Nothing to do
}
///
/// Get the paint mode ie. Screen for edit mode OR Fuction for actual usage.
///
public siTransferFunctionPaintMode PaintMode
{
get { return this.m_PaintMode; }
}
///
/// Get the name/key of the layer to draw.
/// If null or empty, all layers are drawn.
///
public String Layer
{
get { return this.m_Layer; }
}
}
//=========================================================================
#endregion
///
/// Represents a two dimensional transfer function (usually Value-GradMag)
/// for direct volume rendering. The transfer function comprises of a user
/// specified number of layers. As such the TF is represented as a 3D image,
/// with each slice depicting each layer. Each layer is actually duplicated
/// and allow correct interpolation to occur between the duplicated slices.
/// Transfer functions are always represented with RGBA pixel type.
///
public class siTransferFunction : IDisposable
{
#region Constants
//=====================================================================
public const string UNUSED_LAYER_KEY = "Unused";
private int RGBA_CHANNELS = 4;
private PixelFormat RGBA_FORMAT = PixelFormat.Format32bppArgb;
//=====================================================================
#endregion
#region Instance Variables
//=====================================================================
private bool m_Disposed = false;
//=====================================================================
#endregion
#region Construction and Disposal
//=====================================================================
///
/// Protected parameterless constructor for serialization.
///
protected siTransferFunction() { }
///
/// Default constructor for a single layer transfer function.
///
/// The size of the 2D transfer functions.
public siTransferFunction(itk.itkSize size)
{
this.m_Size0 = size[0];
this.m_Size1 = size[1];
List layers = new List(1);
layers.Add("Main");
this.Initialise(size, layers);
}
///
/// Constructor.
///
/// The size of the 2D transfer functions.
/// A list of names or keys for the layers of the transfer function.
public siTransferFunction(itk.itkSize size, params String[] layers)
: this(size, new List(layers))
{ }
///
/// Constructor.
///
/// The size of the 2D transfer functions.
/// A list of names or keys for the layers of the transfer function.
public siTransferFunction(itk.itkSize size, List layers)
{
this.m_Size0 = size[0];
this.m_Size1 = size[1];
this.Initialise(size, layers);
}
///
/// Initialise the transfer function.
///
protected void Initialise(itk.itkSize size, List layers)
{
// Check size is 2D
if (size.Dimension != 2)
throw new ArgumentOutOfRangeException("size", size, "Invalid size: the TransferFuction size must have two dimensions.");
// Check that size is a power of two
for (int i = 0; i < size.Dimension; i++)
{
int roundedSize = siMathHelper.RoundToNextHighestPowerOfTwo(size[i]);
if (roundedSize != size[i])
throw new ArgumentOutOfRangeException("size", size, "Invalid size: the TransferFuction size must be a power of two (eg. 256 x 256).");
}
// Expand the layers to a power of two
int roundedLayers = siMathHelper.RoundToNextHighestPowerOfTwo(layers.Count);
int diffLayers = roundedLayers - layers.Count;
for (int i = 0; i < diffLayers; i++)
layers.Add(UNUSED_LAYER_KEY);
// Expand the size to include the layers
itk.itkSize sizeWithLayers = new itk.itkSize(size[0], size[1], layers.Count);
// Set the layers
this.m_Layers = layers;
// Create the underlying image
itk.itkIndex index = new itk.itkIndex(3U);
itk.itkImageRegion region = new itk.itkImageRegion(sizeWithLayers, index);
this.m_Image = itk.itkImage_RGBAUC3.New();
this.m_Image.SetRegions(region);
this.m_Image.Allocate();
itk.itkPixel_RGBAUC zero = new itk.itkPixel_RGBAUC(new itk.itkArray((uint)RGBA_CHANNELS));
this.m_Image.FillBuffer(zero);
this.m_Image.Spacing = new itk.itkSpacing(1.0, 1.0, 1.0);
this.m_Image.Name = null;
// Create the bitmaps to access the functions
this.m_Bitmaps = new Bitmap[sizeWithLayers[2]];
for (int k = 0; k < sizeWithLayers[2]; k++)
{
int iOffset = k * sizeWithLayers[1] * sizeWithLayers[0] * RGBA_CHANNELS;
int iBufferWithOffset = this.m_Image.Buffer.ToInt32() + iOffset;
IntPtr bufferWithOffset = new IntPtr(iBufferWithOffset);
this.m_Bitmaps[k] = new Bitmap(sizeWithLayers[0],
sizeWithLayers[1],
sizeWithLayers[0] * RGBA_CHANNELS,
RGBA_FORMAT,
bufferWithOffset);
}
// Setup default Metadata
this.Metadata["Cursor"] = Cursors.Default;
}
///
/// Dispose of the siTransferFunction resources.
///
public void Dispose()
{
// Only dispose once
if (this.m_Disposed)
return;
else
this.m_Disposed = true;
// Dispose of bitmaps
if (this.m_Bitmaps != null && this.m_Bitmaps.Length > 0)
{
foreach (Bitmap bitmap in this.m_Bitmaps)
bitmap.Dispose();
this.m_Bitmaps = null;
}
// Dispose of the image
if (this.m_Image != null)
this.m_Image.Dispose();
// Clear the parts list
if (this.Parts != null)
this.Parts.Clear();
// Tell the GC that the Finalize process no longer needs
// to be run for this object
GC.SuppressFinalize(this);
}
//=====================================================================
#endregion
#region Properties
//=====================================================================
#region Metadata
//=====================================================================
private Dictionary m_Metadata = new Dictionary();
///
/// Gets the Metadata variable list.
///
[System.Xml.Serialization.XmlIgnore]
public Dictionary Metadata
{
get { return this.m_Metadata; }
}
//=====================================================================
#endregion
#region Layers
//=====================================================================
private List m_Layers = new List();
///
/// Gets the list of layers within the transfer function.
///
public List Layers
{
get { return this.m_Layers; }
}
//=====================================================================
#endregion
#region Size
//=====================================================================
private int m_Size0 = 0;
private int m_Size1 = 0;
///
/// This method is needed for serialization.
/// It is not intended for general use.
///
public int Size0
{
get { return this.m_Size0; }
set { this.m_Size0 = value; }
}
///
/// This method is needed for serialization.
/// It is not intended for general use.
///
public int Size1
{
get { return this.m_Size1; }
set { this.m_Size1 = value; }
}
//=====================================================================
#endregion
#region Image
//=====================================================================
private itk.itkImageBase m_Image = null;
///
/// Get the 3D image representing the layers of 2D transfer functions.
///
[System.Xml.Serialization.XmlIgnore]
public itk.itkImageBase Image
{
get { return this.m_Image; }
}
//=====================================================================
#endregion
#region Bitmaps
//=====================================================================
private Bitmap[] m_Bitmaps = null;
///
/// Get the 3D image representing the layers of 2D transfer functions.
///
[System.Xml.Serialization.XmlIgnore]
protected Bitmap[] Bitmaps
{
get { return this.m_Bitmaps; }
}
//=====================================================================
#endregion
#region Parts
//=====================================================================
private List m_Parts = new List();
///
/// Gets the list of parts comprising the transfer function.
///
[XmlElement(Type = typeof(siTransferFunctionPartRectangle)),
XmlElement(Type = typeof(siTransferFunctionPartOval)),
XmlElement(Type = typeof(siTransferFunctionPartGradient)),
XmlElement(Type = typeof(siTransferFunctionPartLevoy)),
XmlElement(Type = typeof(siTransferFunctionPartTrapezoid))]
public List Parts
{
get { return this.m_Parts; }
}
//=====================================================================
#endregion
#region Modified
//=====================================================================
private bool m_IsModified = false;
private bool m_ModifiedEventEnabled = true;
///
/// Gets/sets if the Renderer has been modified and needs repainting.
///
[System.Xml.Serialization.XmlIgnore]
public bool IsModified
{
get { return this.m_IsModified; }
set { this.m_IsModified = value; }
}
///
/// Gets/sets if the transfer function raises modified events when
/// the function is changed.
///
[System.Xml.Serialization.XmlIgnore]
public bool ModifiedEventEnabled
{
get { return this.m_ModifiedEventEnabled; }
set
{
this.m_ModifiedEventEnabled = value;
if (value) this.RaiseModified();
}
}
//=====================================================================
#endregion
#region Background
//=====================================================================
private itk.itkImageBase m_BackgroundImage = null;
private Bitmap m_BackgroundBitmap = null;
///
/// Gets/sets the background image as an itkImageBase.
///
[System.Xml.Serialization.XmlIgnore]
public itk.itkImageBase BackgroundImage
{
get { return this.m_BackgroundImage; }
set { this.m_BackgroundImage = value; }
}
//=====================================================================
#endregion
//=====================================================================
#endregion
#region Events and Delegates
//=====================================================================
public delegate void siTransferFunctionHandler(siTransferFunction sender);
public delegate void siTransferFunctionModifiedHandler(siTransferFunction sender, bool partial);
#region Modified
//=====================================================================
private siTransferFunctionModifiedHandler m_EventStorage_Modified;
///
/// An event raised when the Transfer Function is modified.
///
public event siTransferFunctionModifiedHandler Modified
{
add { this.m_EventStorage_Modified += value; }
remove { this.m_EventStorage_Modified -= value; }
}
///
/// Raises the Modified event. Signals a full modification (ie. partial = false).
///
protected void RaiseModified()
{
if (this.m_EventStorage_Modified != null && this.ModifiedEventEnabled)
this.m_EventStorage_Modified(this, false);
}
///
/// Raises the Modified event. Signals a full or partial modification.
///
protected void RaiseModified(bool partial)
{
if (this.m_EventStorage_Modified != null && this.ModifiedEventEnabled)
this.m_EventStorage_Modified(this, partial);
}
//=====================================================================
#endregion
#region EditStarted
//=====================================================================
private siTransferFunctionHandler m_EventStorage_EditStarted;
///
/// An event raised when the Transfer Function is starting being edited.
///
public event siTransferFunctionHandler EditStarted
{
add { this.m_EventStorage_EditStarted += value; }
remove { this.m_EventStorage_EditStarted -= value; }
}
///
/// Raises the EditStarted event.
///
protected void RaiseEditStarted()
{
if (this.m_EventStorage_EditStarted != null)
this.m_EventStorage_EditStarted(this);
}
//=====================================================================
#endregion
#region EditFinished
//=====================================================================
private siTransferFunctionHandler m_EventStorage_EditFinished;
///
/// An event raised when the Transfer Function is finished being edited.
///
public event siTransferFunctionHandler EditFinished
{
add { this.m_EventStorage_EditFinished += value; }
remove { this.m_EventStorage_EditFinished -= value; }
}
///
/// Raises the EditFinished event.
///
protected void RaiseEditFinished()
{
if (this.m_EventStorage_EditFinished != null)
this.m_EventStorage_EditFinished(this);
}
//=====================================================================
#endregion
#region PartSelected
//=====================================================================
private siActor.siActorHandler m_EventStorage_PartSelected;
///
/// An event raised when a transfer function part is selected (clicked).
///
public event siActor.siActorHandler PartSelected
{
add { this.m_EventStorage_PartSelected += value; }
remove { this.m_EventStorage_PartSelected -= value; }
}
///
/// Raises the PartSelected event.
///
protected void RaisePartSelected(siTransferFunctionPart part)
{
if (this.m_EventStorage_PartSelected != null)
this.m_EventStorage_PartSelected(part as siActor);
}
//=====================================================================
#endregion
//=====================================================================
#endregion
#region Serialization Methods
//=====================================================================
///
/// Serializes this object to the given path.
///
/// The absolute path and name of the file to create with the serialized object.
public void ToXmlFile(string filePath)
{
// Ensure the filePath is valid
if (filePath == null || filePath.Length == 0)
throw new ArgumentNullException("filePath", "The file path to serialize the Transfer Function can not be null or empty.");
// Ensure the directory exists
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
// Serialize
XmlSerializer serializer = new XmlSerializer(typeof(siTransferFunction));
FileStream stream = File.Create(filePath);
serializer.Serialize(stream, this);
stream.Flush();
stream.Close();
stream.Dispose();
}
///
/// Deserializes this object from the given path.
///
/// The absolute path and name of the file to deserialize the object from.
///
public static siTransferFunction FromXmlFile(string filePath)
{
// Ensure the filePath is valid
if (filePath == null || filePath.Length == 0)
throw new ArgumentNullException("filePath", "The file path to serialize the Transfer Function can not be null or empty.");
if (!File.Exists(filePath))
throw new FileNotFoundException("The file path to deserialize the Transfer Function was not found.", filePath);
// Deserialize
XmlSerializer serializer = new XmlSerializer(typeof(siTransferFunction));
FileStream stream = File.OpenRead(filePath);
siTransferFunction result = (siTransferFunction)serializer.Deserialize(stream);
// Finish setting up result
result.Initialise(new itk.itkSize(result.Size0, result.Size1), result.Layers);
siTransferFunctionPart[] parts = result.Parts.ToArray();
result.Parts.Clear();
foreach (siTransferFunctionPart part in parts)
result.AddPartToFront(part);
// Clean up
stream.Flush();
stream.Close();
stream.Dispose();
// Return
return result;
}
//=====================================================================
#endregion
#region Paint Methods
//=====================================================================
///
/// Paint the transfer function layer in the given mode to the graphics handle.
/// If e.Layer is null or empty, all the layers are painted.
///
///
public void Paint(siTransferFunctionPaintEventArgs e)
{
// Set to draw smooth
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Paint the background
this.PaintBackground(e);
// Paint each part in reverse order
for (int i = this.Parts.Count - 1; i >= 0; i--)
this.Parts[i].Paint(this, e);
// Clear the modified flag
this.IsModified = false;
}
///
/// Paint the transfer function layers to the internal 3D image.
///
public void PaintToImage()
{
for (int i = 0; i < this.Bitmaps.Length; i++)
{
// Setup for painting
Graphics g = Graphics.FromImage(this.Bitmaps[i]);
Rectangle clipRect = new Rectangle(0, 0, this.Image.Size[0], this.Image.Size[1]);
g.Clip = new Region(clipRect);
siTransferFunctionPaintMode mode = siTransferFunctionPaintMode.Function;
siTransferFunctionPaintEventArgs e = new siTransferFunctionPaintEventArgs(g, clipRect, mode, this.Layers[i]);
// Paint background
this.PaintBackground(e);
// Paint each part in reverse order
for (int j = this.Parts.Count - 1; j >= 0; j--)
this.Parts[j].Paint(this, e);
// Clean up
g.Dispose();
e.Dispose();
}
}
///
/// Returns a bitmap which represents a "screen capture".
///
///
public virtual Bitmap GetScreenCapture()
{
// Create and setup the output bitmap for offscreen rendering
Bitmap result = new Bitmap(this.Image.Size[0]+1, this.Image.Size[1]+1);
Graphics g = Graphics.FromImage(result);
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle clipRect = new Rectangle(0, 0, this.Image.Size[0]+1, this.Image.Size[1]+1);
g.Clip = new Region(clipRect);
siTransferFunctionPaintMode mode = siTransferFunctionPaintMode.Publish;
siTransferFunctionPaintEventArgs e = new siTransferFunctionPaintEventArgs(g, clipRect, mode, null);
// Paint background
this.PaintBackground(e);
// Paint each part in reverse order
for (int j = this.Parts.Count - 1; j >= 0; j--)
this.Parts[j].Paint(this, e);
// Clean up
g.Dispose();
e.Dispose();
// Return
return result;
}
//=====================================================================
#endregion
#region Mouse Methods
//=====================================================================
public void MouseDown(MouseEventArgs e)
{
// Allow the Parts to consume the event
bool eventWasConsumed = false;
foreach (siTransferFunctionPart part in this.Parts.ToArray())
{
eventWasConsumed = part.MouseDown(this, e);
if (eventWasConsumed)
{
this.SendPartToFront(part);
this.RaisePartSelected(part);
this.RaiseEditStarted();
return;
}
}
}
public void MouseUp(MouseEventArgs e)
{
// Allow the Parts to consume the event
bool eventWasConsumed = false;
foreach (siTransferFunctionPart part in this.Parts.ToArray())
{
eventWasConsumed = part.MouseUp(this, e);
if (eventWasConsumed)
{
this.RaiseEditFinished();
return;
}
}
}
public void MouseMove(MouseEventArgs e)
{
// Allow the Parts to consume the event
bool eventWasConsumed = false;
foreach (siTransferFunctionPart part in this.Parts.ToArray())
{
eventWasConsumed = part.MouseMove(this, e);
if (eventWasConsumed) return;
}
if (!eventWasConsumed)
{
if (this.Metadata["Cursor"] as Cursor != Cursors.Default)
{
this.Metadata["Cursor"] = Cursors.Default;
this.RaiseModified();
}
}
}
//=====================================================================
#endregion
#region Part Methods
//=====================================================================
///
/// Adds the given part to the function at the back of the list.
///
///
public void AddPart(siTransferFunctionPart part)
{
if (part != null)
this.AddPartToBack(part);
}
///
/// Adds the given part to the front of the list.
///
///
public void AddPartToFront(siTransferFunctionPart part)
{
if (part != null)
{
this.Parts.Insert(0, part);
part.Modified += new siActor.siActorModifiedHandler(Part_Modified);
part.RaiseModified();
this.RaisePartSelected(part);
}
}
///
/// Adds the given part to the back of the list.
///
///
public void AddPartToBack(siTransferFunctionPart part)
{
if (part != null)
{
this.Parts.Add(part);
part.Modified += new siActor.siActorModifiedHandler(Part_Modified);
part.RaiseModified();
this.RaisePartSelected(part);
}
}
///
/// Removes the given part from the list.
///
///
public void RemovePart(siTransferFunctionPart part)
{
if (part != null && this.Parts.Contains(part))
{
this.Parts.Remove(part);
part.Modified -= new siActor.siActorModifiedHandler(Part_Modified);
this.Metadata["Cursor"] = Cursors.Default;
this.IsModified = true;
this.RaiseModified(false);
}
}
///
/// Remove the part at the front of the list (the currently selected part).
///
public void RemovePartAtFront()
{
if (this.Parts != null && this.Parts.Count > 0)
this.RemovePart(this.Parts[0]);
}
///
/// Remove all the parts from the list.
///
public void ClearAllParts()
{
siTransferFunctionPart[] parts = this.Parts.ToArray();
foreach (siTransferFunctionPart part in parts)
this.RemovePart(part);
}
///
/// Sends the given part to the back of the list.
///
///
public void SendPartToBack(siTransferFunctionPart part)
{
if (part != null && this.Parts.Contains(part))
{
this.Parts.Remove(part);
this.Parts.Add(part);
part.RaiseModified();
}
}
///
/// Sends the given part the front of the list.
///
///
public void SendPartToFront(siTransferFunctionPart part)
{
if (part != null && this.Parts.Contains(part))
{
this.Parts.Remove(part);
this.Parts.Insert(0, part);
part.RaiseModified();
this.RaisePartSelected(part);
}
}
///
/// An event handler for whenever a part is modified.
///
///
private void Part_Modified(siActor sender, bool partial)
{
// Indicate the function has been modified
this.IsModified = true;
this.RaiseModified(partial);
}
//=====================================================================
#endregion
#region Private Methods
//=====================================================================
///
/// Paint the background of the transfer function to g depending on
/// the mode: for Screen mode paint a checker board effect to indicate
/// transparency, for Function mode clear the background with a
/// transparent color.
///
///
///
private void PaintBackground(siTransferFunctionPaintEventArgs e)
{
switch (e.PaintMode)
{
case siTransferFunctionPaintMode.Screen:
// Fall through
case siTransferFunctionPaintMode.Publish:
// Get background bitmap
Bitmap background = this.GetBackgroundBitmap();
e.Graphics.DrawImageUnscaled(background, 0, 0);
break;
case siTransferFunctionPaintMode.Function:
// Clear background with transparent color
e.Graphics.Clear(Color.FromArgb(0, 0, 0, 0));
break;
}
}
///
/// Construct the background bitmap (or return an already constructed bitmap).
///
private Bitmap GetBackgroundBitmap()
{
if (this.m_BackgroundBitmap != null)
{
// Do nothing, we have already constructed the bitmap
}
else if (this.m_BackgroundImage != null)
{
// The user has specified an image, convert to a Bitmap
if (this.m_BackgroundImage.Dimension != 2 ||
this.m_BackgroundImage.PixelType.IsArray ||
this.m_BackgroundImage.PixelType.IsSigned ||
!this.m_BackgroundImage.PixelType.IsChar)
{
// The given background image is not valid, use the default
}
else
{
// Convert from itk to bitmap
// NOTE: On failure, null is returned and the default used.
this.m_BackgroundBitmap = this.ConvertItkImageToBitmap(this.m_BackgroundImage);
}
}
// Check that a valid bitmap was created
if (this.m_BackgroundBitmap == null)
{
// Use the checker board default
this.m_BackgroundBitmap = siColorHelper.CreateTransparentBackground(this.Image.Size[0] + 1, this.Image.Size[1] + 1);
}
// Return
return this.m_BackgroundBitmap;
}
///
/// Converts a UC2 itkImage to a bitmap.
/// This method does not check that the given image is of type UC2.
/// On failure of any kind, null is returned.
///
///
///
private Bitmap ConvertItkImageToBitmap(itk.itkImageBase image)
{
Bitmap result = null;
try
{
// Create bitmap palette and format
siGdiLookupTable lut = new siGdiLookupTable();
lut.SetTableRange(image.PixelType);
lut.SetTableToGreyscale();
ColorPalette palette = palette = lut.GetGdiPalette();
PixelFormat format = lut.GetGdiPixelFormat();
// Check if the stride is the same as the width
if (image.Size[0] % 4 == 0)
{
// Width = Stride: simply use the Bitmap constructor
result = new Bitmap(image.Size[0], // Width
image.Size[1], // Height
image.Size[0], // Stride
format, // PixelFormat
image.Buffer // Buffer
);
}
else
{
unsafe
{
// Width != stride: copy data from buffer to bitmap
int width = image.Size[0];
int height = image.Size[1];
byte* buffer = (byte*)image.Buffer.ToPointer();
// Compute the stride
int stride = width;
if (image.Size[0] % 4 != 0)
stride = ((image.Size[0] / 4) * 4 + 4);
result = new Bitmap(width, height, format);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bitmapData = result.LockBits(rect, ImageLockMode.WriteOnly, format);
for (int k = 0; k < height; k++) // Row
{
byte* row = (byte*)bitmapData.Scan0 + (k * stride);
for (int j = 0; j < width; j++) // Column
{
row[j] = buffer[(k * width) + j];
}
}
result.UnlockBits(bitmapData);
} // end unsafe
}
// Set the palette
result.Palette = palette;
}
catch
{
// Null the result
result = null;
}
// Return
return result;
}
//=====================================================================
#endregion
}
}