I've been trying to copy a transparent PNG image to clipboard and preserve its transparency to paste it into a specific program that supports it.
I tried many solutions already but the background always ended up gray in one way or another.
So I tried copying the same image using Chrome and pasting it into the program and it worked. It preserved transparency. So then I tried Getting the image from the Clipboard that I had copied using Chrome and Set the image again, expecting the transparency to still be there - but no, transparency was not preserved even though I just took the image from the clipboard and set it again.
var img = Clipboard.GetImage(); // copied using Chrome and transparency is preserved
Clipboard.SetImage(img); // transparency lost
Same issue even if I use the System.Windows.Forms.Clipboard or try getting and setting the DataObject instead of the Image.
The Windows clipboard, by default, does not support transparency, but you can put content on the clipboard in many types together to make sure most applications find some type in it that they can use. Sadly, the most common type, DeviceIndependentBitmap (which Windows itself seems to use) is a really dirty and unreliable one. I wrote a big rant explanation about that here.
I'll assume you have read through that before continuing with my answer here, because it contains the background information required for the next part.
Now, the cleanest way of putting an image on the clipboard with transparency support is a PNG stream, but it won't guarantee that all applications can paste it. Gimp supports PNG paste, and apparently so do the newer MS Office programs, but Google Chrome, for example, doesn't, and will only accept the messy DIB type detailed in the answer I linked to. On the other hand, Gimp will not accept DIB as having transparency, because its creators actually followed the format's specifications, and realized that the format was unreliable (as clearly demonstrated by that question I linked).
Because of the DIB mess, sadly, the best thing to do is simply to put it in there in as many generally-supported types as you can, including PNG, DIB and the normal Image.
PNG and DIB are both put on the clipboard in the same way: by putting them in the DataObject as MemoryStream, and then giving the clipboard the "copy" instruction when actually putting it on.
Most of this is straightforward, but the DIB one is a bit more complex. Note that the following part contains a couple of references to my own toolsets. The GetImageData one can be found in this answer, the BuildImage one can be found here, and the ArrayUtils ones are given below.
These toolsets all use System.Drawing, though. You'll have to figure out for yourself exactly how to do the same things in WPF.
/// <summary>
/// Copies the given image to the clipboard as PNG, DIB and standard Bitmap format.
/// </summary>
/// <param name="image">Image to put on the clipboard.</param>
/// <param name="imageNoTr">Optional specifically nontransparent version of the image to put on the clipboard.</param>
/// <param name="data">Clipboard data object to put the image into. Might already contain other stuff. Leave null to create a new one.</param>
public static void SetClipboardImage(Bitmap image, Bitmap imageNoTr, DataObject data)
{
Clipboard.Clear();
if (data == null)
data = new DataObject();
if (imageNoTr == null)
imageNoTr = image;
using (MemoryStream pngMemStream = new MemoryStream())
using (MemoryStream dibMemStream = new MemoryStream())
{
// As standard bitmap, without transparency support
data.SetData(DataFormats.Bitmap, true, imageNoTr);
// As PNG. Gimp will prefer this over the other two.
image.Save(pngMemStream, ImageFormat.Png);
data.SetData("PNG", false, pngMemStream);
// As DIB. This is (wrongly) accepted as ARGB by many applications.
Byte[] dibData = ConvertToDib(image);
dibMemStream.Write(dibData, 0, dibData.Length);
data.SetData(DataFormats.Dib, false, dibMemStream);
// The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation.
Clipboard.SetDataObject(data, true);
}
}
/// <summary>
/// Converts the image to Device Independent Bitmap format of type BITFIELDS.
/// This is (wrongly) accepted by many applications as containing transparency,
/// so I'm abusing it for that.
/// </summary>
/// <param name="image">Image to convert to DIB</param>
/// <returns>The image converted to DIB, in bytes.</returns>
public static Byte[] ConvertToDib(Image image)
{
Byte[] bm32bData;
Int32 width = image.Width;
Int32 height = image.Height;
// Ensure image is 32bppARGB by painting it on a new 32bppARGB image.
using (Bitmap bm32b = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
{
using (Graphics gr = Graphics.FromImage(bm32b))
gr.DrawImage(image, new Rectangle(0, 0, bm32b.Width, bm32b.Height));
// Bitmap format has its lines reversed.
bm32b.RotateFlip(RotateFlipType.Rotate180FlipX);
Int32 stride;
bm32bData = ImageUtils.GetImageData(bm32b, out stride);
}
// BITMAPINFOHEADER struct for DIB.
Int32 hdrSize = 0x28;
Byte[] fullImage = new Byte[hdrSize + 12 + bm32bData.Length];
//Int32 biSize;
ArrayUtils.WriteIntToByteArray(fullImage, 0x00, 4, true, (UInt32)hdrSize);
//Int32 biWidth;
ArrayUtils.WriteIntToByteArray(fullImage, 0x04, 4, true, (UInt32)width);
//Int32 biHeight;
ArrayUtils.WriteIntToByteArray(fullImage, 0x08, 4, true, (UInt32)height);
//Int16 biPlanes;
ArrayUtils.WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
//Int16 biBitCount;
ArrayUtils.WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
//BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
ArrayUtils.WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
//Int32 biSizeImage;
ArrayUtils.WriteIntToByteArray(fullImage, 0x14, 4, true, (UInt32)bm32bData.Length);
// These are all 0. Since .net clears new arrays, don't bother writing them.
//Int32 biXPelsPerMeter = 0;
//Int32 biYPelsPerMeter = 0;
//Int32 biClrUsed = 0;
//Int32 biClrImportant = 0;
// The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values.
ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length);
return fullImage;
}
Now, as for getting an image off the clipboard, I noticed there is apparently a difference in behaviour between .Net 3.5 and the later ones, which seem to actually use that DIB. Given that difference, and given how unreliable the DIB format is, you'll want to actually check manually for all types, preferably starting with the completely reliable PNG format.
You can get the DataObject from the clipboard with this code:
DataObject retrievedData = Clipboard.GetDataObject() as DataObject;
The CloneImage function used here is basically just the combination of my GetImageData and BuildImage toolsets, ensuring that a new image is created without any backing resources that might mess up; image objects are known to cause crashes when they're based on a Stream that then gets disposed. A compacted and optimised version of it was posted here, in a question well worth reading on the subject of why this cloning is so important.
/// <summary>
/// Retrieves an image from the given clipboard data object, in the order PNG, DIB, Bitmap, Image object.
/// </summary>
/// <param name="retrievedData">The clipboard data.</param>
/// <returns>The extracted image, or null if no supported image type was found.</returns>
public static Bitmap GetClipboardImage(DataObject retrievedData)
{
Bitmap clipboardimage = null;
// Order: try PNG, move on to try 32-bit ARGB DIB, then try the normal Bitmap and Image types.
if (retrievedData.GetDataPresent("PNG", false))
{
MemoryStream png_stream = retrievedData.GetData("PNG", false) as MemoryStream;
if (png_stream != null)
using (Bitmap bm = new Bitmap(png_stream))
clipboardimage = ImageUtils.CloneImage(bm);
}
if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Dib, false))
{
MemoryStream dib = retrievedData.GetData(DataFormats.Dib, false) as MemoryStream;
if (dib != null)
clipboardimage = ImageFromClipboardDib(dib.ToArray());
}
if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Bitmap))
clipboardimage = new Bitmap(retrievedData.GetData(DataFormats.Bitmap) as Image);
if (clipboardimage == null && retrievedData.GetDataPresent(typeof(Image)))
clipboardimage = new Bitmap(retrievedData.GetData(typeof(Image)) as Image);
return clipboardimage;
}
public static Bitmap ImageFromClipboardDib(Byte[] dibBytes)
{
if (dibBytes == null || dibBytes.Length < 4)
return null;
try
{
Int32 headerSize = (Int32)ArrayUtils.ReadIntFromByteArray(dibBytes, 0, 4, true);
// Only supporting 40-byte DIB from clipboard
if (headerSize != 40)
return null;
Byte[] header = new Byte[40];
Array.Copy(dibBytes, header, 40);
Int32 imageIndex = headerSize;
Int32 width = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x04, 4, true);
Int32 height = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x08, 4, true);
Int16 planes = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0C, 2, true);
Int16 bitCount = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0E, 2, true);
//Compression: 0 = RGB; 3 = BITFIELDS.
Int32 compression = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x10, 4, true);
// Not dealing with non-standard formats.
if (planes != 1 || (compression != 0 && compression != 3))
return null;
PixelFormat fmt;
switch (bitCount)
{
case 32:
fmt = PixelFormat.Format32bppRgb;
break;
case 24:
fmt = PixelFormat.Format24bppRgb;
break;
case 16:
fmt = PixelFormat.Format16bppRgb555;
break;
default:
return null;
}
if (compression == 3)
imageIndex += 12;
if (dibBytes.Length < imageIndex)
return null;
Byte[] image = new Byte[dibBytes.Length - imageIndex];
Array.Copy(dibBytes, imageIndex, image, 0, image.Length);
// Classic stride: fit within blocks of 4 bytes.
Int32 stride = (((((bitCount * width) + 7) / 8) + 3) / 4) * 4;
if (compression == 3)
{
UInt32 redMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 0, 4, true);
UInt32 greenMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 4, 4, true);
UInt32 blueMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 8, 4, true);
// Fix for the undocumented use of 32bppARGB disguised as BITFIELDS. Despite lacking an alpha bit field,
// the alpha bytes are still filled in, without any header indication of alpha usage.
// Pure 32-bit RGB: check if a switch to ARGB can be made by checking for non-zero alpha.
// Admitted, this may give a mess if the alpha bits simply aren't cleared, but why the hell wouldn't it use 24bpp then?
if (bitCount == 32 && redMask == 0xFF0000 && greenMask == 0x00FF00 && blueMask == 0x0000FF)
{
// Stride is always a multiple of 4; no need to take it into account for 32bpp.
for (Int32 pix = 3; pix < image.Length; pix += 4)
{
// 0 can mean transparent, but can also mean the alpha isn't filled in, so only check for non-zero alpha,
// which would indicate there is actual data in the alpha bytes.
if (image[pix] == 0)
continue;
fmt = PixelFormat.Format32bppPArgb;
break;
}
}
else
// Could be supported with a system that parses the colour masks,
// but I don't think the clipboard ever uses these anyway.
return null;
}
Bitmap bitmap = ImageUtils.BuildImage(image, width, height, stride, fmt, null, null);
// This is bmp; reverse image lines.
bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
return bitmap;
}
catch
{
return null;
}
}
Because BitConverter always requires that dumb check on system endianness, I got my own ReadIntFromByteArray and WriteIntToByteArray in an ArrayUtils class:
public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt32 value)
{
Int32 lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + ".");
for (Int32 index = 0; index < bytes; index++)
{
Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (Byte)(value >> (8 * index) & 0xFF);
}
}
public static UInt32 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
{
Int32 lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + ".");
UInt32 value = 0;
for (Int32 index = 0; index < bytes; index++)
{
Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
value += (UInt32)(data[offs] << (8 * index));
}
return value;
}
Related
I'm trying to get cursor data (mask and color, if available) as a byte array using GetDIBits(), with the correct size and correct animation frame but it's proving to be unreliable.
Also, I would like to get or be able to simulate the multiple frames available in a cursor, such as what is possible when using DrawIconEx(), passing the cursorStep parameter.
Basically what I'm trying to emulate is part of the behavior of IDXGIOutputDuplication::GetFramePointerShape(), to get a buffer (byte[]) with the contents of the current cursor, just without having to rely on the DirectX library.
So, I want:
Get the cursor data as a byte[], even with the mask working (SOLVED).
Get the cursor data with the correct size, not a fixed 32x32px cursor.
Get the actual animation frame of the cursor.
Just as the DXGI method provides if I'm using OutputDuplication, but in my case, I'm not.
1) Get the cursor mask correctly (solved):
TL;DR: Wrong data interpretation on my part.
I'm calling GetDIBits() once, passing null as the buffer parameter to get the details of the color/mask images and a second time to get the image.
It works all right with the color image but returns a wrong data size and wrong image content for the mask image.
Here's a comparison and the code sample.
Files ending with "2" were created by using System.Drawing.Bitmap.FromHBitmap() while the others were obtained from GetDiBits().
For some reason, GetDIBits() returns this data (BitmapInfoHeader) for the mask. Also, only returns 128 bytes (256 if the mask has two frames, such as for the I-Beam/Text cursor), which is way too little for 32x32px or 32x64px masks.
Here's my code so far (it's C#):
WindowDeviceContext = User32.GetWindowDC(IntPtr.Zero);
//The parameter passed in the structs is just to be able to calculate the size of it.
var cursorInfo = new CursorInfo(false);
if (!User32.GetCursorInfo(out cursorInfo))
return;
if (cursorInfo.Flags != Native.Constants.CursorShowing)
{
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
var iconHandle = User32.CopyIcon(cursorInfo.CursorHandle);
if (iconHandle == IntPtr.Zero)
{
User32.DestroyIcon(iconHandle);
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
if (!User32.GetIconInfo(iconHandle, out var iconInfo))
{
Gdi32.DeleteObject(iconInfo.Color);
Gdi32.DeleteObject(iconInfo.Mask);
User32.DestroyIcon(iconHandle);
Gdi32.DeleteObject(cursorInfo.CursorHandle);
return;
}
//Color.
var colorHeader = new BitmapInfoHeader(false);
//Gets the color image details.
Gdi32.GetDIBits(WindowDeviceContext, iconInfo.Color, 0, 0, null, ref colorHeader, DibColorModes.RgbColors);
if (colorHeader.Height != 0)
{
colorHeader.Height *= -1;
var colorBuffer = new byte[colorHeader.SizeImage];
Gdi32.GetDIBits(WindowDeviceContext, iconInfo.Color, 0, (uint)(colorHeader.Height * -1), colorBuffer, ref colorHeader, DibColorModes.RgbColors);
//Converts a byte array to image (System.Windows.Media.Imaging.BitmapSource) just for testing.
var image = ImageUtil.ImageMethods.FromArray(colorBuffer, colorHeader.Width, colorHeader.Height * -1, 4); //4 channels
using var fileStream = new FileStream(#"Color.png", FileMode.Create);
var bmpEncoder = new PngBitmapEncoder();
bmpEncoder.Frames.Add(BitmapFrame.Create(image));
bmpEncoder.Save(fileStream);
//Converts the HBitmap to Bitmap, just for testing too.
var image2 = System.Drawing.Image.FromHbitmap(iconInfo.Color);
image2.Save(#"Color2.png");
}
//Mask.
var maskHeader = new BitmapInfoHeader(false);
//Gets the mask image details.
Gdi32.GetDIBits(WindowDeviceContext, iconInfo.Mask, 0, 0, null, ref maskHeader, DibColorModes.RgbColors);
if (maskHeader.Height != 0)
{
maskHeader.Height *= -1;
var maskBuffer = new byte[maskHeader.SizeImage];
Gdi32.GetDIBits(WindowDeviceContext, iconInfo.Mask, 0, (uint) maskHeader.Height, maskBuffer, ref maskHeader, DibColorModes.RgbColors);
//Converts a byte array to image (System.Windows.Media.Imaging.BitmapSource) just for testing.
var image = ImageUtil.ImageMethods.FromArray(maskBuffer.ToList(), maskHeader.Width, maskHeader.Height * -1, 1, 1);
using var fileStream = new FileStream(#"Mask.png", FileMode.Create);
var bmpEncoder = new PngBitmapEncoder();
bmpEncoder.Frames.Add(BitmapFrame.Create(image));
bmpEncoder.Save(fileStream);
//Converts the HBitmap to Bitmap, just for testing too.
var image2 = System.Drawing.Image.FromHbitmap(iconInfo.Mask);
image2.Save(#"Mask2.png");
}
Gdi32.DeleteObject(iconInfo.Color);
Gdi32.DeleteObject(iconInfo.Mask);
User32.DestroyIcon(iconHandle);
Gdi32.DeleteObject(cursorInfo.CursorHandle);
Solved:
With the proper interpretation of the bits per pixel of the mask image, this issue was solved.
public static BitmapSource FromArray(byte[] data, int width, int height, int channels, int bitsPerPixel = 32)
{
var format = PixelFormats.Default;
var stride = channels * ((bitsPerPixel * width + 31) / 32);
//Abridged channel and bits by pixel to format converter.
if (channels == 1)
{
format = PixelFormats.BlackWhite;
stride = width / 8;
}
else if (channels == 3)
format = PixelFormats.Bgr24; //RGB.
else if (channels == 4)
format = PixelFormats.Bgr32; //RGB + alpha.
var wbm = new WriteableBitmap(width, height, 96, 96, format, null);
wbm.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);
return wbm;
}
2) Get the correct size of the cursor:
As noted above, I'm only getting cursor images of size 32x32 or 32x64 (for masks).
I've tried to increase the size being passed to GetDIBits(), but the image being returned is always the same size.
Even if the size in Windows 10/11 settings is set to a bigger cursor size.
The IDXGIOutputDuplication::GetFramePointerShape() method returns a correct size, so it looks like it's possible. What does this method do in order to get the correct image size?
3) Get the actual cursor frame being displayed (for animated cursors):
As for the animation frame (steps) of the cursor, I don't see a way of getting multiple frames based on an index the same way that I would be able to draw with DrawIconEx():
//If the cursor rate needs to be precisely captured, I could use this undocumented API:
//https://source.winehq.org/source/dlls/user32/cursoricon.c#2325
//int rate = 0, num = 0;
//var ok1 = User32.GetCursorFrameInfo(cursorInfo.hCursor, IntPtr.Zero, 17, ref rate, ref num);
//CursorStep
var ok = User32.DrawIconEx(CompatibleDeviceContext, frame.CursorX - iconInfo.XHotspot, frame.CursorY - iconInfo.YHotspot, cursorInfo.CursorHandle, 0, 0, CursorStep, IntPtr.Zero, 0x0003);
if (!ok)
{
CursorStep = 0;
User32.DrawIconEx(CompatibleDeviceContext, frame.CursorX - iconInfo.XHotspot, frame.CursorY - iconInfo.YHotspot, cursorInfo.CursorHandle, 0, 0, CursorStep, IntPtr.Zero, 0x0003);
}
else
CursorStep++;
Maybe I could draw the cursor color and masks separately using DrawIconEx() into two bitmaps (using the flags DI_IMAGE and DI_MASK in each call, passing the cursor step), and then getting the pixels using GetDIBits()?
But how to do that?
I have recently started using openCL.Net and have run into an issue with some image processing code. What I am trying to do is target specific colors in an image and change them to a different RGB values, in order to reduce the total number of colors in the image. The workflow for this process goes as follows:
Identify all colors in an image.
Identify a color to remove.
Round that color to the next nearest color in the image.
Find all pixels in image that match that color and change them to the rounded color.
Repeat above steps until number of colors threshold is met.
I have all of this code functioning in C#, but now I want the pixel changing done on the gpu since running through 250000 pixels multiple times in a row can take a couple of minutes.
using OpenCL.Net;
private Context _context;
private Device _device;
public void SetupOpenCL()
{
ErrorCode error;
Platform[] platforms = Cl.GetPlatformIDs(out error);
List<Device> devicesList = new List<Device>();
CheckErr(error, "Cl.GetPlatformIDs");
foreach (Platform platform in platforms)
{
string platformName = Cl.GetPlatformInfo(platform, PlatformInfo.Name, out error).ToString();
Console.WriteLine("Platform: " + platformName);
CheckErr(error, "Cl.GetPlatformInfo");
//We will be looking only for GPU devices
foreach (Device device in Cl.GetDeviceIDs(platform, DeviceType.Gpu, out error))
{
CheckErr(error, "Cl.GetDeviceIDs");
//Console.WriteLine("Device: " + device.ToString());
devicesList.Add(device);
}
}
if (devicesList.Count <= 0)
{
//Console.WriteLine("No devices found.");
//Set bool used to signify that code processed on CPU only
return;
}
_device = devicesList[0];
if (Cl.GetDeviceInfo(_device, DeviceInfo.ImageSupport, out error).CastTo<Bool>() == Bool.False)
{
//Console.WriteLine("No image support.");
//Set bool used to signify that code processed on CPU only
return;
}
_context = Cl.CreateContext(null, 1, new[] { _device }, ContextNotify, IntPtr.Zero, out error); //Second parameter is amount of devices
CheckErr(error, "Cl.CreateContext");
}
Through Visual Studio, I can see that my computer has a Intel(R) Core(TM) i7-4500U CPU # 1.80GHz and a Intel(R) HD Graphics 4400 (GPU). The code that sets up my kernel is as follows:
public Bitmap ImagingTest2(Bitmap inputBM, int[] oldRGB, int[] newRGB)
{
ErrorCode error;
//Load and compile kernel source code.
string programPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\ImagingTest2.cl";
//The path to the source file may vary
if (!System.IO.File.Exists(programPath))
{
Console.WriteLine("Program doesn't exist at path " + programPath);
return inputBM;
}
string programSource = System.IO.File.ReadAllText(programPath);
using (OpenCL.Net.Program program = Cl.CreateProgramWithSource(_context, 1, new[] { programSource }, null, out error))
{
CheckErr(error, "Cl.CreateProgramWithSource");
//Compile kernel source
error = Cl.BuildProgram(program, 1, new[] { _device }, string.Empty, null, IntPtr.Zero);
CheckErr(error, "Cl.BuildProgram");
//Check for any compilation errors
if (Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Status, out error).CastTo<BuildStatus>()
!= BuildStatus.Success)
{
CheckErr(error, "Cl.GetProgramBuildInfo");
Console.WriteLine("Cl.GetProgramBuildInfo != Success");
Console.WriteLine(Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Log, out error));
return inputBM;
}
//Create the required kernel (entry function)
Kernel kernel = Cl.CreateKernel(program, "imagingTest", out error);
CheckErr(error, "Cl.CreateKernel");
int intPtrSize = 0;
intPtrSize = Marshal.SizeOf(typeof(IntPtr));
//Image's RGBA data converted to an unmanaged[] array
byte[] inputByteArray;
//OpenCL memory buffer that will keep our image's byte[] data.
Mem inputImage2DBuffer;
OpenCL.Net.ImageFormat clImageFormat = new OpenCL.Net.ImageFormat(ChannelOrder.RGBA, ChannelType.Unsigned_Int8);
int inputImgWidth, inputImgHeight;
int inputImgBytesSize;
int inputImgStride;
inputImgWidth = inputBM.Width;
inputImgHeight = inputBM.Height;
//Get raw pixel data of the bitmap
//The format should match the format of clImageFormat
BitmapData bitmapData = inputBM.LockBits(new Rectangle(0, 0, inputBM.Width, inputBM.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);//inputImage.PixelFormat);
inputImgStride = bitmapData.Stride;
inputImgBytesSize = bitmapData.Stride * bitmapData.Height;
//Copy the raw bitmap data to an unmanaged byte[] array
inputByteArray = new byte[inputImgBytesSize];
Marshal.Copy(bitmapData.Scan0, inputByteArray, 0, inputImgBytesSize);
//Allocate OpenCL image memory buffer
inputImage2DBuffer = (Mem)Cl.CreateImage2D(_context, MemFlags.CopyHostPtr | MemFlags.ReadOnly, clImageFormat,
(IntPtr)bitmapData.Width, (IntPtr)bitmapData.Height,
(IntPtr)0, inputByteArray, out error);
CheckErr(error, "Cl.CreateImage2D input");
//Unmanaged output image's raw RGBA byte[] array
byte[] outputByteArray = new byte[inputImgBytesSize];
//Allocate OpenCL image memory buffer
Mem outputImage2DBuffer = (Mem)Cl.CreateImage2D(_context, MemFlags.CopyHostPtr |
MemFlags.WriteOnly, clImageFormat, (IntPtr)inputImgWidth,
(IntPtr)inputImgHeight, (IntPtr)0, outputByteArray, out error);
CheckErr(error, "Cl.CreateImage2D output");
//Pass the memory buffers to our kernel function
error = Cl.SetKernelArg(kernel, 0, (IntPtr)intPtrSize, inputImage2DBuffer);
error |= Cl.SetKernelArg(kernel, 1, (IntPtr)intPtrSize, outputImage2DBuffer);
error = Cl.SetKernelArg(kernel, 2, (IntPtr)(sizeof(int) * 4), oldRGB);
error = Cl.SetKernelArg(kernel, 3, (IntPtr)(sizeof(int) * 4), newRGB);
CheckErr(error, "Cl.SetKernelArg");
//Create a command queue, where all of the commands for execution will be added
CommandQueue cmdQueue = Cl.CreateCommandQueue(_context, _device, (CommandQueueProperties)0, out error);
CheckErr(error, "Cl.CreateCommandQueue");
Event clevent;
//Copy input image from the host to the GPU.
IntPtr[] originPtr = new IntPtr[] { (IntPtr)0, (IntPtr)0, (IntPtr)0 }; //x, y, z
IntPtr[] regionPtr = new IntPtr[] { (IntPtr)inputImgWidth, (IntPtr)inputImgHeight, (IntPtr)1 }; //x, y, z
IntPtr[] workGroupSizePtr = new IntPtr[] { (IntPtr)inputImgWidth, (IntPtr)inputImgHeight, (IntPtr)1 };
error = Cl.EnqueueWriteImage(cmdQueue, inputImage2DBuffer, Bool.True,
originPtr, regionPtr, (IntPtr)0, (IntPtr)0, inputByteArray, 0, null, out clevent);
CheckErr(error, "Cl.EnqueueWriteImage");
//Execute our kernel (OpenCL code)
error = Cl.EnqueueNDRangeKernel(cmdQueue, kernel, 2, null, workGroupSizePtr, null, 0, null, out clevent);
CheckErr(error, "Cl.EnqueueNDRangeKernel");
//Wait for completion of all calculations on the GPU.
error = Cl.Finish(cmdQueue);
CheckErr(error, "Cl.Finish");
//Read the processed image from GPU to raw RGBA data byte[] array
error = Cl.EnqueueReadImage(cmdQueue, outputImage2DBuffer, Bool.True, originPtr, regionPtr,
(IntPtr)0, (IntPtr)0, outputByteArray, 0, null, out clevent);
CheckErr(error, "Cl.clEnqueueReadImage");
//Clean up memory
Cl.ReleaseKernel(kernel);
Cl.ReleaseCommandQueue(cmdQueue);
Cl.ReleaseMemObject(inputImage2DBuffer);
Cl.ReleaseMemObject(outputImage2DBuffer);
//Get a pointer to our unmanaged output byte[] array
GCHandle pinnedOutputArray = GCHandle.Alloc(outputByteArray, GCHandleType.Pinned);
IntPtr outputBmpPointer = pinnedOutputArray.AddrOfPinnedObject();
//Create a new bitmap with processed data and save it to a file.
Bitmap outputBitmap = new Bitmap(inputImgWidth, inputImgHeight,
inputImgStride, PixelFormat.Format32bppArgb, outputBmpPointer);
inputBM.UnlockBits(bitmapData);
pinnedOutputArray.Free();
return outputBitmap;
}
}
Where inputBM is a known Bitmap that I have the RGB data for every pixel. oldRGB is the RGB values of the pixels that I want to change. newRGB is the RGB values that I want to change the pixels to.
Finally, here is the code for the kernel itself:
__kernel void imagingTest(__read_only image2d_t srcImg,__write_only image2d_t dstImg, int4 oldRGB, int4 newRGB)
{
const sampler_t smp = CLK_NORMALIZED_COORDS_FALSE | //Natural coordinates
CLK_ADDRESS_CLAMP_TO_EDGE |
CLK_FILTER_NEAREST;
int2 coord = (int2)(get_global_id(0), get_global_id(1));
int4 bgra = read_imagei(srcImg, smp, coord); //The byte order is BGRA
//printf("%v4i\n", bgra); //For Debug only
if(oldRGB.x == bgra.z && oldRGB.y == bgra.y && oldRGB.z == bgra.x)
{
bgra.x = (int) newRGB.z;
bgra.y = (int) newRGB.y;
bgra.z = (int) newRGB.x;
bgra.w = 255;
}
write_imagei(dstImg, coord, bgra);
}
I can compile and execute this code successfully. The problem is that the image returned from the gpu is very different colorwise. Using the printf command, I found that the RGB values I get for my input image are different in C# when I use Bitmap.GetPixel from when I use read_imagei within the kernel. He is what I got during one of my tests.
RGB values from Bitmap containing 6 colors using Bitmap.GetPixel:
255,52,12
226,105,123
255,206,206
246,167,180
250,250,250
255,213,213
Same Bitmap passed to the gpu returns the following RGB values with read_imagei:
255,0,0
255,192,192
255,120,120
255,160,160
255,255,255
255,220,220
I'm not sure if this difference is being cause by a image formatting issue or possibly by some sort of rounding that the gpu is performing during the read_imagei command. I'm a self taught programmer and I have hit the limits of my googling capabilities. Any explanation for or help with this problem would be appreciated.
Thanks,
******** Update ********
So I ran another test with an image that had a few more colors. When I read the pixels in C# using Bitmap.GetPixel, I found 10 unique colors. When I read the image on the gpu using read_imagei, it returned 17 unique colors. I have no idea why there is this difference in the number of colors.
RGB values from Bitmap containing 10 colors using Bitmap.GetPixel:
217,0,40
250,133,0
255,157,37
254,231,61
7,113,26
28,136,23
1,110,165
121,54,102
96,78,135
247,111,1
Same Bitmap passed to the gpu returns the following RGB values with read_imagei:
228,3,3
255,140,0
247,111,1
255,237,0
255,137,0
255,173,0
255,241,0
255,240,0
0,118,40
0,128,38
0,77,255
0,75,255
0,130,0
0,94,205
121,0,127
117,7,135
56,61,222
I have a 8 bit bitmap color image. when i do a
Color pixelcolor = b.GetPixel(j,i);
Console.Write(pixelcolor.ToString() + " " );
I get
Color [A=255, R=255, G=255, B=255]
I need to get only the 8 bit value. not 24 bit seperate values for R,G,B ,A.
There is no way to do this using the Bitmap class directly. However, you can use the LockBits method to access the pixels directly.
Using unsafe code: (remember to enable unsafe code in your project first)
public static unsafe Byte GetIndexedPixel(Bitmap b, Int32 x, Int32 y)
{
if (b.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentException("Image is not in 8 bit per pixel indexed format!");
if (x < 0 || x >= b.Width) throw new ArgumentOutOfRangeException("x", string.Format("x should be in 0-{0}", b.Width));
if (y < 0 || y >= b.Height) throw new ArgumentOutOfRangeException("y", string.Format("y should be in 0-{0}", b.Height));
BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
try
{
Byte* scan0 = (Byte*)data.Scan0;
return scan0[x + y * data.Stride];
}
finally
{
if (data != null) b.UnlockBits(data);
}
}
The safe alternative, using Marshal.Copy:
public static Byte GetIndexedPixel(Bitmap b, Int32 x, Int32 y)
{
if (b.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentException("Image is not in 8 bit per pixel indexed format!");
if (x < 0 || x >= b.Width) throw new ArgumentOutOfRangeException("x", string.Format("x should be in 0-{0}", b.Width));
if (y < 0 || y >= b.Height) throw new ArgumentOutOfRangeException("y", string.Format("y should be in 0-{0}", b.Height));
BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
try
{
Byte[] pixel = new Byte[1];
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + x + y * data.Stride), pixel, 0, 1);
return pixel[0];
}
finally
{
if (data != null) b.UnlockBits(data);
}
}
The methods in the Bitmap class doesn't let you get the palette index directly.
You can get the palette for the image using the Palette property, and look for the color there, but that's a bit of a workaround.
To get the palette index directly, you would use the LockBits method to get access to the image data directly. You would either have to use marshalling to copy the data into an array, or use pointers in unsafe mode to access it.
The A property in a Color value is the Alpha component. It can have the value 0 to 255, where 0 is fully transparent and 255 is fully solid.
The values you want are actually R, G and B, which are 8bit bitmap values of corresponding Red, Green and Blue components of the color.
A is a Alfa coponent, the transparency value of the color. If you don't care about it, just don't show it in string output.
If you don't want to use LockBits, you can do this:
Warning: This method only works if the palette does not have duplicated values and if it is not changed by another thread after pixelRGB is set.
/// <summary>
/// Gets the pixel value in bytes. Uses Bitmap GetPixel method.
/// </summary>
/// <param name="bmp">Bitmap</param>
/// <param name="location">Pixel location</param>
/// <returns>Pixel value</returns>
public static byte Get8bppImagePixel(Bitmap bmp, Point location)
{
Color pixelRGB = bmp.GetPixel(location.X, location.Y);
int pixel8bpp = Array.IndexOf(bmp.Palette.Entries, pixelRGB);
return (byte)pixel8bpp;
}
I am in the process of creating a TCP remote desktop application
i want to send only the difference of the previously sent frame.
When I compare the original image and the second image I put information of pixels that have changed in ArrayList
five Item in the ArrayList containing information one pixel
The first Item contains a Height point
The second Item contains a Width point
The third Item contains RGB.red
The fourth Item contains RGB.Green
The Fifth Item contains RGB.Blue
This is the code
private void button1_Click(object sender, EventArgs e)
{
ArrayList new_pixel = Unsafe_diff_array(pictureBox2.Image, pictureBox1.Image);
Bitmap new_bit_map = (Bitmap)pictureBox3.Image;
for (int i = 0; i < new_pixel.Count; i+=5)
{
int x = (int)new_pixel[i +1];
int y=(int)new_pixel[i];
int red= Convert.ToInt16(new_pixel[i + 4]) ;
int green= Convert.ToInt16(new_pixel[i + 3]) ;
int blue=Convert.ToInt16(new_pixel[i + 2]);
new_bit_map.SetPixel(x, y, Color.FromArgb(red , green, blue));
}
pictureBox3.Image = new_bit_map;
}
public ArrayList Unsafe_diff_array(Image OrginalImage, Image SecondImage)
{
Bitmap BOrginalImage = new Bitmap(OrginalImage);
Bitmap BSecondImage = new Bitmap(SecondImage);
BitmapData bitmapData1 = BOrginalImage.LockBits(new Rectangle(0, 0,
OrginalImage.Width, OrginalImage.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
BitmapData bitmapData2 = BSecondImage.LockBits(new Rectangle(0, 0,
SecondImage.Width, SecondImage.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
ArrayList siblings = new ArrayList();
unsafe
{
byte* imagePointer1 = (byte*)bitmapData1.Scan0;
byte* imagePointer2 = (byte*)bitmapData2.Scan0;
for (int i = 0; i < bitmapData1.Height; i++)
{
for (int j = 0; j < bitmapData1.Width; j++)
{
// write the logic implementation here
if ((imagePointer1[0] != imagePointer2[0]) || (imagePointer1[1] != imagePointer2[1]) || (imagePointer1[2] != imagePointer2[2]))
{
imagePointer2[0] = imagePointer1[0];
imagePointer2[1] = imagePointer1[1];
imagePointer2[2] = imagePointer1[2];
siblings.Add(i);
siblings.Add(j);
siblings.Add(imagePointer2[0]);
siblings.Add(imagePointer2[1]);
siblings.Add(imagePointer2[2]);
}
imagePointer2[3] = imagePointer1[3];
imagePointer1 += 4;
imagePointer2 += 4;
}//end for j
imagePointer1 += bitmapData1.Stride -
(bitmapData1.Width * 4);
imagePointer2 += bitmapData1.Stride -
(bitmapData1.Width * 4);
}//end for i
}//end unsafe
BOrginalImage.UnlockBits(bitmapData1);
BSecondImage.UnlockBits(bitmapData2);
return siblings ;
// return BSecondImage.GetThumbnailImage(SecondImage.Width, SecondImage.Height, null, new IntPtr()); ;
}
the problem is when i Serialize ArrayList to MemoryStream
I find that the size larger than the images
I have also tried to put information of pixels that changed in Short Array, but also found a size larger than the images !!!!!?
How do I make this process so that I can Thumbnail size to the smallest possible size ?
the code which i used to Serialize ArrayList
private System.IO.MemoryStream SerializeBinary(object obj){
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
System.IO.MemoryStream memStrm = new System.IO.MemoryStream();
serializer.Serialize(memStrm, obj);
return memStrm;
}
A typical 32-bit WxH image uses WxHx4 bytes of memory (that's 4MB for a 1024x1024 bitmap).
Assuming that you're sending the list of different pixels in [x][y][color] format with 16-bit [x] and [y] and a 32-bit [color] value, and there are D different pixels, the difference will use Dx8 bytes of memory. Thus, the difference will be larger than the image if more than half the pixels are different (D > WxH/2).
In your case, you're using an array, which means that [color] is actually represented as three [r][g][b] 16-bit values. The memory usage becomes Dx10 and the threshold can be found if 40% pixels have changed (D > WxH/2.5 ).
Consider using a bit to tell whether you're sending a list of differences or a full image, and send the smallest of the two. Also consider things like RLE encoding, using a single [offset] 16-bit value instead of [x] and [y], or just making any unchanged values transparent and saving the result in PNG format.
This, of course, is compounded by any additional serialization data is inserted by your runtime, and the fact that images can be compressed (PNG is lossless, for instance).
Of course it will be larger!
Bitmap has an in-memory structure consisting of header, pixel data and sometimes palette while binary formatter serialization result of an ArrayList has a totally different format consisting of assembly metadata, type metatdata, etc.
Also it is a pity all your performance gain of doing unsafe coding on pixels will be wasted on boxing happening of using ArrayList.
I have an application which I have made a 256 x 256 Windows Vista icon for.
I was wondering how I would be able to use a 256x256 PNG file in the ico file used as the application icon and show it in a picture box on a form.
I am using VB.NET, but answers in C# are fine. I'm thinking I may have to use reflection.
I am not sure if this is even possible in Windows XP and may need Windows Vista APIs
Today, I made a very nice function for extracting the 256x256 Bitmaps from Vista icons.
Like you, Nathan W, I use it to display the large icon as a Bitmap in "About" box. For example, this code gets Vista icon as PNG image, and displays it in a 256x256 PictureBox:
picboxAppLogo.Image = ExtractVistaIcon(myIcon);
This function takes Icon object as a parameter. So, you can use it with any icons - from resources, from files, from streams, and so on. (Read below about extracting EXE icon).
It runs on any OS, because it does not use any Win32 API, it is 100% managed code :-)
// Based on: http://www.codeproject.com/KB/cs/IconExtractor.aspx
// And a hint from: http://www.codeproject.com/KB/cs/IconLib.aspx
Bitmap ExtractVistaIcon(Icon icoIcon)
{
Bitmap bmpPngExtracted = null;
try
{
byte[] srcBuf = null;
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{ icoIcon.Save(stream); srcBuf = stream.ToArray(); }
const int SizeICONDIR = 6;
const int SizeICONDIRENTRY = 16;
int iCount = BitConverter.ToInt16(srcBuf, 4);
for (int iIndex=0; iIndex<iCount; iIndex++)
{
int iWidth = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex];
int iHeight = srcBuf[SizeICONDIR + SizeICONDIRENTRY * iIndex + 1];
int iBitCount = BitConverter.ToInt16(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 6);
if (iWidth == 0 && iHeight == 0 && iBitCount == 32)
{
int iImageSize = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 8);
int iImageOffset = BitConverter.ToInt32(srcBuf, SizeICONDIR + SizeICONDIRENTRY * iIndex + 12);
System.IO.MemoryStream destStream = new System.IO.MemoryStream();
System.IO.BinaryWriter writer = new System.IO.BinaryWriter(destStream);
writer.Write(srcBuf, iImageOffset, iImageSize);
destStream.Seek(0, System.IO.SeekOrigin.Begin);
bmpPngExtracted = new Bitmap(destStream); // This is PNG! :)
break;
}
}
}
catch { return null; }
return bmpPngExtracted;
}
IMPORTANT! If you want to load this icon directly from EXE file, then you CAN'T use Icon.ExtractAssociatedIcon(Application.ExecutablePath) as a parameter, because .NET function ExtractAssociatedIcon() is so stupid, it extracts ONLY 32x32 icon!
Instead, you better use the whole IconExtractor class, created by Tsuda Kageyu (http://www.codeproject.com/KB/cs/IconExtractor.aspx). You can slightly simplify this class, to make it smaller. Use IconExtractor this way:
// Getting FILL icon set from EXE, and extracting 256x256 version for logo...
using (TKageyu.Utils.IconExtractor IconEx = new TKageyu.Utils.IconExtractor(Application.ExecutablePath))
{
Icon icoAppIcon = IconEx.GetIcon(0); // Because standard System.Drawing.Icon.ExtractAssociatedIcon() returns ONLY 32x32.
picboxAppLogo.Image = ExtractVistaIcon(icoAppIcon);
}
Note: I'm still using my ExtractVistaIcon() function here, because I don't like how IconExtractor handles this job - first, it extracts all icon formats by using IconExtractor.SplitIcon(icoAppIcon), and then you have to know the exact 256x256 icon index to get the desired vista-icon. So, using my ExtractVistaIcon() here is much faster and simplier way :)
Found info here. To get the large Vista icon, you need to use Shell32's SHGetFileInfo method. I've copied the relevant text below, of course you'll want to replace the filename variable with "Assembly.GetExecutingAssembly().Location".
using System.Runtime.InteropServices;
A bunch of constants we will use in the call to SHGetFileInfo() to specify the size of the icon we wish to retrieve:
// Constants that we need in the function call
private const int SHGFI_ICON = 0x100;
private const int SHGFI_SMALLICON = 0x1;
private const int SHGFI_LARGEICON = 0x0;
The SHFILEINFO structure is very important as it will be our handle to various file information, among which is the graphic icon.
// This structure will contain information about the file
public struct SHFILEINFO
{
// Handle to the icon representing the file
public IntPtr hIcon;
// Index of the icon within the image list
public int iIcon;
// Various attributes of the file
public uint dwAttributes;
// Path to the file
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szDisplayName;
// File type
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
The final preparation for the unmanaged code is to define the signature of SHGetFileInfo, which is located inside the popular Shell32.dll:
// The signature of SHGetFileInfo (located in Shell32.dll)
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, uint uFlags);
Now that we have everything prepared, it's time to make the call to the function and display the icon that we retrieved. The object that will be retrieved is an Icon type (System.Drawing.Icon) but we want to display it in a PictureBox so we'll convert the Icon to a Bitmap using the ToBitmap() method.
But first of all there are 3 controls you need to add to the form, a Button btnExtract that has "Extract Icon" for its Text property, picIconSmall which is a PictureBox and a picIconLarge which is also a PictureBox. That's because we will get two icons sizes. Now double click btnExtract in Visual Studio's Design view and you'll get to its Click event. Inside it is the rest of the code:
private void btnExtract_Click(object sender, EventArgs e)
{
// Will store a handle to the small icon
IntPtr hImgSmall;
// Will store a handle to the large icon
IntPtr hImgLarge;
SHFILEINFO shinfo = new SHFILEINFO();
// Open the file that we wish to extract the icon from
if(openFile.ShowDialog() == DialogResult.OK)
{
// Store the file name
string FileName = openFile.FileName;
// Sore the icon in this myIcon object
System.Drawing.Icon myIcon;
// Get a handle to the small icon
hImgSmall = SHGetFileInfo(FileName, 0, ref shinfo, Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SMALLICON);
// Get the small icon from the handle
myIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon);
// Display the small icon
picIconSmall.Image = myIcon.ToBitmap();
// Get a handle to the large icon
hImgLarge = SHGetFileInfo(FileName, 0, ref shinfo, Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_LARGEICON);
// Get the large icon from the handle
myIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon);
// Display the large icon
picIconLarge.Image = myIcon.ToBitmap();
}
}
UPDATE: found even more info here.
None of the above answers handle Vista Icons - only small (32x32) and large (48x48)
There is a library that handles Vista Icons here
...it looks quite complicated due to the dual-png alpha channel format.
I will try to make a concise answer in vb .net but it may take some time.
Having the same problem of displaying the 256*256*32 image from an ICO file in a picture box, I found the solution from SAL80 the most efficient one (and almost working). However, the original code doesn't support images stored as BMP (the large icon is usually PNG, but not always...).
Here is my version for future references. The code to create the bitmap is also slightly simpler :
/// <summary>
/// Extracts the large Vista icon from a ICO file
/// </summary>
/// <param name="srcBuf">Bytes of the ICO file</param>
/// <returns>The large icon or null if not found</returns>
private static Bitmap ExtractVistaIcon(byte[] srcBuf)
{
const int SizeIcondir = 6;
const int SizeIcondirentry = 16;
// Read image count from ICO header
int iCount = BitConverter.ToInt16(srcBuf, 4);
// Search for a large icon
for (int iIndex = 0; iIndex < iCount; iIndex++)
{
// Read image information from image directory entry
int iWidth = srcBuf[SizeIcondir + SizeIcondirentry * iIndex];
int iHeight = srcBuf[SizeIcondir + SizeIcondirentry * iIndex + 1];
int iBitCount = BitConverter.ToInt16(srcBuf, SizeIcondir + SizeIcondirentry * iIndex + 6);
// If Vista icon
if (iWidth == 0 && iHeight == 0 && iBitCount == 32)
{
// Get image data position and length from directory
int iImageSize = BitConverter.ToInt32(srcBuf, SizeIcondir + SizeIcondirentry * iIndex + 8);
int iImageOffset = BitConverter.ToInt32(srcBuf, SizeIcondir + SizeIcondirentry * iIndex + 12);
// Check if the image has a PNG signature
if (srcBuf[iImageOffset] == 0x89 && srcBuf[iImageOffset+1] == 0x50 && srcBuf[iImageOffset+2] == 0x4E && srcBuf[iImageOffset+3] == 0x47)
{
// the PNG data is stored directly in the file
var x = new MemoryStream(srcBuf, iImageOffset, iImageSize, false, false);
return new Bitmap(x);
}
// Else it's bitmap data with a partial bitmap header
// Read size from partial header
int w = BitConverter.ToInt32(srcBuf, iImageOffset + 4);
// Create a full header
var b = new Bitmap(w, w, PixelFormat.Format32bppArgb);
// Copy bits into bitmap
BitmapData bmpData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.WriteOnly, b.PixelFormat);
Marshal.Copy(srcBuf, iImageOffset + Marshal.SizeOf(typeof(Bitmapinfoheader)), bmpData.Scan0, b.Width*b.Height*4);
b.UnlockBits(bmpData);
return b;
}
}
return null;
}
Have a look at the Windows icon functions that are available. There's also an overview that mentions querying for different icon sizes. There's a Dream.In.Code forum thread for using the APIs in C# as well as a Pinvoke.net reference.