However, if the Image has a transparent background, when I go to paste the
Image into a drawing app like Paint or Photoshop, the transparent area of
the Image has been replaced by a blue color. So it seems that the Image
isn't telling the clipboard what parts need to be transparent. This doesn't
make sense since it can draw the Image transparent areas fine.
Can anyone explain what is wrong here and if this is some sort of .NET
framework limitation, provide either C# or VB.NET code to work around it?
Thanks in advance!
Bill
This clipboard format requires that you create
a packed DIB from a DIBSECTION created with the BITMAPV5HEADER.
You can use P-INVOKE with CreateDIBSection for your bitmap
and then transfer the bits with LockBits, BitBlt or DrawImage.
You create a global memory object to represent the packed DIB.
This packed DIB will include the BITMAPV5HEADER and the bitmap's
bits.
Place the global memory handle on the clipboard.
Clipboard.SetDataObject(image) places a BI_RGB bitmap
on the clipboard. Per the documentation, the high byte in each
DWORD is not used.
There is no way for an aplication to natively know that the bitmap
that is retrieved from the clipboard with a CF_DIB or
CF_BITMAP clipboard format has an alpha channel.
An application could test but a CF_DIBV5 clipboard format
advertises that an alpha channel is present!
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:%23Qdy%23T0AG...@TK2MSFTNGP11.phx.gbl...
internal void CopyAlphaBitmapToClipboard(Bitmap image) {
BITMAPV4HEADER bih = new BITMAPV4HEADER();
bih.bV5Size =
System.Runtime.InteropServices.Marshal.SizeOf(typeof(BITMAPV4HEADER));
bih.bV5Width = image.Width;
bih.bV5Height = image.Height;
bih.bV5Planes = 1;
bih.bV5BitCount = 32;
bih.bV5Compression = 0;
bih.bV5RedMask = 0x00FF0000;
bih.bV5GreenMask = 0x0000FF00;
bih.bV5BlueMask = 0x000000FF;
bih.bV5AlphaMask = 0xFF000000;
BITMAPINFO bi = new BITMAPINFO();
bi.bmiHeader = bih;
Graphics g = Graphics.FromImage(image);
IntPtr imageHdc = g.GetHdc();
IntPtr hdc = NativeMethods.CreateCompatibleDC(imageHdc);
// Create the DIB section with an alpha channel
IntPtr ppvBits = IntPtr.Zero;
const int DIB_RGB_COLORS = 0;
IntPtr hBitmap = NativeMethods.CreateDIBSection(hdc, bi, DIB_RGB_COLORS,
out ppvBits, IntPtr.Zero, 0);
const int SRCCOPY = 0x00CC0020;
NativeMethods.BitBlt(hdc, 0, 0, image.Width, image.Height, imageHdc, 0, 0,
SRCCOPY); // SRCCOPY
NativeMethods.DeleteDC(hdc);
g.ReleaseHdc(imageHdc);
g.Dispose();
// TODO: Do more stuff here
}
Is that correct so far or do you see any bugs with bad initializations or
memory leaks? I used the V4 header since I think it has the alpha stuff I
need and was less to define. :)
What I think I have in the procedure above is a handle to a bitmap (hBitmap)
that is still open. Somehow I need to send that to a SetClipboardData API
call. But I think I need a global memory handle as you said. What do I
need to add to get that working?
Thanks so much for your help!
Bill
"Michael Phillips, Jr." <mphil...@nospam.jun0.c0m> wrote in message
news:uDNOSv0A...@TK2MSFTNGP14.phx.gbl...
public const uint BI_BITFIELDS = 3;
public const uint LCS_WINDOWS_COLOR_SPACE = 2;
public const uint LCS_GM_IMAGES = 4;
public const uint CF_DIBV5 = 17;
public const uint GMEM_MOVEABLE = 0x00000002;
public const uint GMEM_ZEROINIT = 0x00000040;
public const uint GMEM_DDESHARE = 0x00002000;
public const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT;
[StructLayout(LayoutKind.Sequential)]
public struct CIEXYZ
{
public uint ciexyzX; //FXPT2DOT30
public uint ciexyzY; //FXPT2DOT30
public uint ciexyzZ; //FXPT2DOT30
}
[StructLayout(LayoutKind.Sequential)]
public struct CIEXYZTRIPLE
{
public CIEXYZ ciexyzRed;
public CIEXYZ ciexyzGreen;
public CIEXYZ ciexyzBlue;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITFIELDS
{
public uint BlueMask;
public uint GreenMask;
public uint RedMask;
}
[StructLayout(LayoutKind.Explicit)]
public struct BITMAPV5HEADER
{
[FieldOffset(0)] public uint bV5Size;
[FieldOffset(4)] public int bV5Width;
[FieldOffset(8)] public int bV5Height;
[FieldOffset(12)]public ushort bV5Planes;
[FieldOffset(14)]public ushort bV5BitCount;
[FieldOffset(16)]public uint bV5Compression;
[FieldOffset(20)]public uint bV5SizeImage;
[FieldOffset(24)]public int bV5XPelsPerMeter;
[FieldOffset(28)]public int bV5YPelsPerMeter;
[FieldOffset(32)]public uint bV5ClrUsed;
[FieldOffset(36)]public uint bV5ClrImportant;
[FieldOffset(40)]public uint bV5RedMask;
[FieldOffset(44)]public uint bV5GreenMask;
[FieldOffset(48)]public uint bV5BlueMask;
[FieldOffset(52)]public uint bV5AlphaMask;
[FieldOffset(56)]public uint bV5CSType;
[FieldOffset(60)]public CIEXYZTRIPLE bV5Endpoints;
[FieldOffset(96)]public uint bV5GammaRed;
[FieldOffset(100)]public uint bV5GammaGreen;
[FieldOffset(104)]public uint bV5GammaBlue;
[FieldOffset(108)]public uint bV5Intent;
[FieldOffset(112)]public uint bV5ProfileData;
[FieldOffset(116)]public uint bV5ProfileSize;
[FieldOffset(120)]public uint bV5Reserved;
}
[DllImport("user32.dll")]
static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
static extern bool EmptyClipboard();
[DllImport("user32.dll")]
static extern bool CloseClipboard();
[DllImport("user32.dll")]
static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
[DllImport("kernel32.dll")]
static extern IntPtr GlobalAlloc(uint uFlags, uint dwBytes);
[DllImport("kernel32.dll")]
static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
static extern bool GlobalUnlock(IntPtr hMem);
public IntPtr CreatePackedDIBV5( Bitmap bm )
{
BitmapData bmData = bm.LockBits( new Rectangle(0,0,bm.Width, bm.Height),
ImageLockMode.ReadOnly, bm.PixelFormat);
uint bufferLen = (uint)(Marshal.SizeOf(typeof(BITMAPV5HEADER)) +
(Marshal.SizeOf(typeof(uint)) * 3) + bmData.Height * bmData.Stride);
IntPtr hMem = GlobalAlloc(GHND | GMEM_DDESHARE, bufferLen);
IntPtr packedDIBV5 = GlobalLock(hMem);
BITMAPV5HEADER bmi = (BITMAPV5HEADER)Marshal.PtrToStructure( packedDIBV5,
typeof(BITMAPV5HEADER));
bmi.bV5Size = (uint)Marshal.SizeOf(typeof(BITMAPV5HEADER));
bmi.bV5Width = bmData.Width;
bmi.bV5Height = bmData.Height;
bmi.bV5BitCount = 32;
bmi.bV5Planes = 1;
bmi.bV5Compression = BI_BITFIELDS;
bmi.bV5XPelsPerMeter = 0;
bmi.bV5YPelsPerMeter = 0;
bmi.bV5ClrUsed = 0;
bmi.bV5ClrImportant = 0;
bmi.bV5BlueMask = 0x000000FF;
bmi.bV5GreenMask = 0x0000FF00;
bmi.bV5RedMask = 0x00FF0000;
bmi.bV5AlphaMask = 0xFF000000;
bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmi.bV5GammaBlue = 0;
bmi.bV5GammaGreen = 0;
bmi.bV5GammaRed = 0;
bmi.bV5ProfileData = 0;
bmi.bV5ProfileSize = 0;
bmi.bV5Reserved = 0;
bmi.bV5Intent = LCS_GM_IMAGES;
bmi.bV5SizeImage = (uint)(bmData.Height * bmData.Stride);
bmi.bV5Endpoints.ciexyzBlue.ciexyzX =
bmi.bV5Endpoints.ciexyzBlue.ciexyzY =
bmi.bV5Endpoints.ciexyzBlue.ciexyzZ = 0;
bmi.bV5Endpoints.ciexyzGreen.ciexyzX =
bmi.bV5Endpoints.ciexyzGreen.ciexyzY =
bmi.bV5Endpoints.ciexyzGreen.ciexyzZ = 0;
bmi.bV5Endpoints.ciexyzRed.ciexyzX =
bmi.bV5Endpoints.ciexyzRed.ciexyzY =
bmi.bV5Endpoints.ciexyzRed.ciexyzZ = 0;
Marshal.StructureToPtr(bmi, packedDIBV5, false);
BITFIELDS Masks = (BITFIELDS)Marshal.PtrToStructure(
(IntPtr)(packedDIBV5.ToInt32() + bmi.bV5Size), typeof(BITFIELDS));
Masks.BlueMask = 0x000000FF;
Masks.GreenMask = 0x0000FF00;
Masks.RedMask = 0x00FF0000;
Marshal.StructureToPtr(Masks, (IntPtr)(packedDIBV5.ToInt32() +
bmi.bV5Size), false);
long offsetBits = bmi.bV5Size + Marshal.SizeOf(typeof(uint)) * 3;
IntPtr bits = (IntPtr)(packedDIBV5.ToInt32() + offsetBits);
for ( int y = 0; y < bmData.Height; y++ )
{
IntPtr DstDib = (IntPtr)(bits.ToInt32() + (y* bmData.Stride));
IntPtr SrcDib = (IntPtr)(bmData.Scan0.ToInt32() + ((bmData.Height-1-y)*
bmData.Stride));
for ( int x = 0; x < bmData.Width; x++ )
{
Marshal.WriteInt32(DstDib, Marshal.ReadInt32(SrcDib));
DstDib = (IntPtr)(DstDib.ToInt32() + 4);
SrcDib = (IntPtr)(SrcDib.ToInt32() + 4);
}
}
bm.UnlockBits(bmData);
GlobalUnlock(hMem);
return hMem;
}
An example on how to use this function follows:
IntPtr packedDIBV5 = CreatePackedDIBV5(AlphaBitmap);
OpenClipboard(this.Handle);
EmptyClipboard();
SetClipboardData(CF_DIBV5, packedDIBV5);
CloseClipboard();
I hope this helps!
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:OiZybeEB...@tk2msftngp13.phx.gbl...
I'm pasting using the .NET classes so is that perhaps the problem since
maybe Clipboard.GetDataObject messes up some of the data? I'm using
DataFormats.Bitmap as the format to retrieve.
Thanks again for your help. The code you posted is exactly what I want to
do to get the data to the clipboard. Now I just need to retrieve it again
into an Image class and have it render the same as when it was copied.
Bill
"Michael Phillips, Jr." <mphil...@nospam.jun0.c0m> wrote in message
news:%23zCmbdN...@TK2MSFTNGP09.phx.gbl...
The clipboard may not synthesize the other formats correctly.
A CF_DIBV5 containes a BITMAPV5HEADER + 3 DWORD bitfields +
the image's bits. The image compression is marked as BI_BITFIELDS
and therefore must contain a color table with 3 DWORDS for the mask.
This is redundant since the BITMAPV5HEADER contains the masks.
For 32bpp images, it is normally assumed that the image uses
the default masks with the compression set to BI_RGB.
You can try editing the code to use BI_RGB compression and get rid of the
bitfields. The result will be no offset of the image by 3 DWORDs.
The packed dib will look like the following:
BITMAPV5HEADER <---BI_RGB compression
bitmap bits
The MSDN documentation
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:O%23u%23JNOBG...@TK2MSFTNGP11.phx.gbl...
Bill
"Michael Phillips, Jr." <mphil...@nospam.jun0.c0m> wrote in message
news:%23SPp3fO...@TK2MSFTNGP10.phx.gbl...
If the application is GDI based, that application must create
a bitmap with CreateDIBSection and then pre-multiply the alpha
channel against the colors and use the GDI AlphaBlend function
for rendering.
If the application is Gdiplus based, that application must create
a PixelFormat32bppARGB bitmap object and use DrawImage
for rendering.
For a BI_RGB bitmap, the high order byte (i.e., Alpha ) is not
used. If that byte is 0x00 (i.e., transparent ), then it will be rendered
as opaque. The background for the transparent portion of the bitmap
will be rendered as black.
The original Windows Bitmap 3.1 specification did not include a
provision for an alpha channel. Any application that receives
a paste of an alpha channel bitmap must be aware that the alpha
channel exists and render it accordingly.
That application can either explicitly test for the presence of an
alpha channel or can use the BITMAPV5HEADER structure
to ascertain whether or not that bitmap was created with an
alpha channel.
If you look further in this newsgroup, you will see an
algorithm that I posted to test for an alpha channel
bitmap with only a handle (i.e., HBITMAP ) as the
source.
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:OqhuEuiB...@TK2MSFTNGP14.phx.gbl...
Note: Our DrawingHelper.DrawImage method allows us to do a flip.
internal static Bitmap PasteAlphaBitmapFromClipboard() {
NativeMethods.OpenClipboard(IntPtr.Zero);
IntPtr hMem = NativeMethods.GetClipboardData(CF_DIBV5);
if (hMem == IntPtr.Zero) {
NativeMethods.CloseClipboard();
return null;
}
IntPtr packedDIBV5 = NativeMethods.GlobalLock(hMem);
BITMAPV5HEADER bmi = (BITMAPV5HEADER)Marshal.PtrToStructure(packedDIBV5,
typeof(BITMAPV5HEADER));
Bitmap bitmap = new Bitmap(bmi.bV5Width, bmi.bV5Height,
(int)(bmi.bV5SizeImage / bmi.bV5Height), PixelFormat.Format32bppArgb,
new IntPtr(packedDIBV5.ToInt32() + bmi.bV5Size));
Bitmap outputBitmap = new Bitmap(bmi.bV5Width, bmi.bV5Height,
PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(outputBitmap);
DrawingHelper.DrawImage(g, bitmap, 0, 0, bmi.bV5Width, bmi.bV5Height, 1.0f,
RotateFlipType.RotateNoneFlipY);
g.Dispose();
bitmap.Dispose();
NativeMethods.GlobalUnlock(hMem);
NativeMethods.CloseClipboard();
return outputBitmap;
}
"Michael Phillips, Jr." <mphil...@nospam.jun0.c0m> wrote in message
news:%23wmw4Ak...@TK2MSFTNGP12.phx.gbl...
Instead of:
DrawingHelper.DrawImage(g, bitmap, 0, 0, bmi.bV5Width, bmi.bV5Height, 1.0f,
> RotateFlipType.RotateNoneFlipY);
Try this:
// note the negative stride
Bitmap bitmap = new Bitmap(bmi.bV5Width, bmi.bV5Height, -
(int)(bmi.bV5SizeImage / bmi.bV5Height), PixelFormat.Format32bppArgb,
new IntPtr(packedDIBV5.ToInt32() + bmi.bV5Size + (bmi.bV5Height-1)*
(int)(bmi.bV5SizeImage / bmi.bV5Height)));
Using a negative stride flips the orientation of the bitmap. Scan0 must
point to the
end of the bits.
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:O5ZemykB...@TK2MSFTNGP11.phx.gbl...
Bill
"Michael Phillips, Jr." <mphil...@nospam.jun0.c0m> wrote in message
news:uV2M1VlB...@TK2MSFTNGP12.phx.gbl...
"Bill Henning" <ju...@at-actipro.software.com> wrote in message news:OTfDymz...@TK2MSFTNGP15.phx.gbl...
I'm sorry to be such a pain on this issue, it's just no matter what I try,
it doesn't work right.
You're right that I should have had the BITFIELDS commented out. Also, I
made a mistake in my posting before so let me post all the scenarios again.
I'm starting over with a clean project. I created a tiny sample project
that maybe you could download and try:
http://www.actiprosoftware.com/ClipboardBitmap.zip
It's simply a Form with several buttons on them. It loads and shows a
transparent image on startup and has buttons for copying and pasting. I
have a button for copying using BI_RGB and one for using BI_BITFIELDS. I
have a paste button that uses the API code that we discussed too. Both the
copy and paste use CF_DIBV5. I also have copy/paste buttons for .NET
framework code, but ignore those.
So here's the scenarios using my test app:
1) Copy from my app using BI_RGB and paste in my app: the pasted image
looks perfect.
2) Copy from my app using BI_BITFIELDS and paste in my app: the pasted
image is offset to the right about two pixels and wraps to the left side.
But other than that, it looks good.
3) Copy from my app using BI_RGB and paste in Paint: the pasted image looks
good but has a black background where the transparent region should be.
Maybe Paint just isn't transparency aware, which is actually ok for my
purposes.
4) Copy from my app using BI_BITFIELDS and paste in Paint: the pasted image
is a combination of #2 and #3... it's offset to the right with a black
background. Also there's a little red, green, and then blue pixel at the
bottom. This makes me think it's pulling in the masks data as bitmap data.
5) Copy from Paint and past in my app: The image looks completely garbled,
like a fuzzy TV.
6) Copy from my app using BI_RGB and paste in Word customize mode for a
button: Same results as #2... image looks good but black background. I do
need transparency working in Word.
7) Copy from my app using BI_BITFIELDS and paste in Word customize mode for
a button: Same results as #4... image is offset with black background and
shows 3 RGB pixels at bottom.
8) Copy from Word customize mode for a button and paste in my app: The "new
Bitmap()" call in my paste method blows up with an "Invalid parameter used"
exception.
Anyhow, that's everything I need to get working. It seems like we're so
close. From the above scenarios, here's the remaining issues as I see them:
1) My BI_BITFIELDS copy code is slightly wrong somewhere. It seems like
it's reading the wrong area of the bitmap data (in the masks area) and maybe
is therefore off by a scanline or so.
2) Pasting a CF_DIBV5 image from my app into Word doesn't retain
transparency. I need that working one way or another.
3) Pasting from Paint into my app needs fixing since it's all garbled.
4) Pasting from Word into my app needs fixing since it throws an exception.
I'd really appreciate it if you could check out my sample project and modify
it however necessary to get those things functioning. That's probably
easier than you posting what I should change and then me still not getting
it right with my limited knowledge of this stuff. :)
I'll be happy to post the resulting code in the newsgroup for everyone
else's benefit as well.
Thanks and Happy Holidays,
Bill
Send me an email with your correct email address and I will send you the
zip.
Of course, you must make changes to my email address to resolve the obvious
spam prevention alterations that are a necessary evil!
Word will correctly identify an alpha channel bitmap and display it,
if you load or drag and drop an image with an alpha channel.
Word will not correctly identify an alpha channel bitmap if it receives
a paste from the clipboard. There are several clipboard formats
available on the clipboard for any application (i.e., CF_BITMAP, CF_DIB,
CF_DIBV5, etc.).
Which format an application requests and uses, is up to the application!
It is a good practice to place CF_BITMAP, CF_DIB and CF_DIBV5 versions of
your
images on the clipboard. If you don't, they will be synthesized by the
clipboard.
Since your application knows ahead of time that the image has an alpha
channel,
placing your version of these clipboard formats obviates the clipboard from
synthesizing the missing formats incorrectly.
It is up to the application to interpret what it receives from the
clipboard! Most applications
don't bother trying to ascertain whether or not an alpha channel bitmap
format is present
on the clipboard. That is why you see a black background in Paint and Word.
They render with an opaque background! Some applications are smart and test
for the presence of an alpha channel. The Microsoft ImageList used on
WindowsXP
is an example.
Happy Holidays
Michael
"Bill Henning" <ju...@at-actipro.software.com> wrote in message
news:eJE1JB9B...@tk2msftngp13.phx.gbl...
I'm facing the exact same problems as Bill (albeit for another purpose) and
am not getting anywhere.
I was wondering if you could send me your last changes or tips on this.
--
----
Ben Kooijman