GdipCreateHBITMAPFromBitmap + PixelFormat32bppARGB

G

Guest

The GDI+ function GdipCreateHBITMAPFromBitmap seems to be broken. When given
an image with an alpha channel, it produces an HBITMAP with its colours oddly
skewed. Only translucent pixels are skewed. This is evident, for instance,
when adding 32-bit RGBA System.Drawing.Bitmap objects to
System.Windows.Forms.ImageList controls in .NET. The
ControlPaint.CreateHBitmapColorMask function uses Bitmap.GetHBitmap which
calls the underlying GdipCreateHBITMAPFromBitmap function.

To test that it was in fact GdipCreateHBITMAPFromBitmap producing the blue
color skew clearly visible when ImageList is used with images with
translucency, I made a simple test case. It can be downloaded at:

http://israel.logiclrd.cx/GDIPlusBug.zip

The odd thing is, my test case produces images with a green tint instead of
blue. In both the case of ImageList and of my C++ test case which directly
calls into GDI+, the "background" parameter passed in is achromatic (all 3
channels have the same intensity -- gray), so I don't know where the colour
is coming from.

The only thing I can conclude based on the symptoms is that
GdipCreateHBITMAPFromBitmap is assuming that HBITMAPs can't properly handle
32-bit data and perhaps is trying to convert the data to an alpha-free form.
However, using a freeware DLL hooking library (madCHook), I was able to
attach my own implementation of GdipCreateHBITMAPFromBitmap:

GpStatus WINGDIPAPI
FixedGdipCreateHBITMAPFromBitmap(GpBitmap* bitmap,
HBITMAP* hbmReturn,
ARGB background)
{
GpStatus status;

UINT uWidth, uHeight;

status = GdipGetImageWidth(bitmap, &uWidth);

if (status != Ok)
return status;

status = GdipGetImageHeight(bitmap, &uHeight);

if (status != Ok)
return status;

GpRect rect;

rect.X = 0;
rect.Y = 0;
rect.Width = uWidth;
rect.Height = uHeight;

PixelFormat format;

status = GdipGetImagePixelFormat(bitmap, &format);

if (status != Ok)
return status;

int bits_per_pixel;

switch (format)
{
case PixelFormat1bppIndexed: bits_per_pixel = 1; break;
case PixelFormat4bppIndexed: bits_per_pixel = 4; break;
case PixelFormat8bppIndexed: bits_per_pixel = 8; break;
case PixelFormat16bppARGB1555:
case PixelFormat16bppGrayScale:
case PixelFormat16bppRGB555:
case PixelFormat16bppRGB565: bits_per_pixel = 16; break;
case PixelFormat24bppRGB: bits_per_pixel = 24; break;
case PixelFormat32bppARGB:
case PixelFormat32bppPARGB:
case PixelFormat32bppRGB: bits_per_pixel = 32; break;
case PixelFormat48bppRGB: bits_per_pixel = 48; break;
case PixelFormat64bppARGB:
case PixelFormat64bppPARGB: bits_per_pixel = 64; break;
default:
return InvalidParameter;
}

BitmapData data;

status = GdipBitmapLockBits(bitmap, &rect, ImageLockModeRead, format,
&data);

if (status != Ok)
return status;

HBITMAP ret = CreateBitmap(uWidth, uHeight, 1, bits_per_pixel, data.Scan0);

status = GdipBitmapUnlockBits(bitmap, &data);

if (ret == NULL)
return Win32Error;

if (status != Ok)
{
DeleteObject(ret);
return status;
}

*hbmReturn = ret;
return Ok;
}

As you can see, it simply dumps the bits it gets from locking the GDI+
Bitmap object into the GDI CreateBitmap function. In my limited testing, it
worked fine, and it eliminated the ImageList problem when used within a .NET
context :)

Any chance of getting this GDI+ bug fixed, perhaps through a Windows Update
patch?
 
M

Michael Phillips, Jr.

1) Since your bitmap has an alpha channel, the background color
that you specified in your test case will be blended with the
transparent areas of your bitmap. If you don't want this to
occur, specify Color.Black as your background and the colors
will not skew. You specified Color.LightGray so your background
blended with the transparent areas will give you a blue tint.

2) Your test case messed up on the bfsize of the BITMAPINFO header.
The bfSize = size of the image ( 500 * 500 * 3 ) + the size of all the
headers.
The size of the BITMAPINFOHEADER + size of the color table
= 40 + 4!
Your total size is 40 + 4 + 14 ( size of BITMAPINFO ) = 58. You have
54. That is why
your bitmap has a green tint!
 
G

Guest

I'll reply to your comments in the opposite order than that in which you
posted them, since it fits better.

Michael Phillips said:
2) Your test case messed up on the bfsize of the BITMAPINFO header.
The bfSize = size of the image ( 500 * 500 * 3 ) + the size of all the
headers.
The size of the BITMAPINFOHEADER + size of the color table
= 40 + 4!
Your total size is 40 + 4 + 14 ( size of BITMAPINFO ) = 58. You have
54. That is why your bitmap has a green tint!

Good catch on this one :) Of course, with the correct total header size,
the image shows the same blue tint as the .NET ImageList control.
1) Since your bitmap has an alpha channel, the background color
that you specified in your test case will be blended with the
transparent areas of your bitmap. If you don't want this to
occur, specify Color.Black as your background and the colors
will not skew. You specified Color.LightGray so your background
blended with the transparent areas will give you a blue tint.

What you say is true up to a point. Yes, the image has an alpha channel --
that is the whole point of the post, as the bug only appears on
partially-translucent pixels -- but Color.LightGray is not blue, it is gray,
so it should not impart a (very marked) blue tint to the image. In fact, it
should not affect the hue of any pixel in any way (quantization error aside).

I reworked my test case to go through all of the named Color values and try
each one as the background colour using a standard gradient image fading from
transparent to opaque with the same colour value (White { 255, 255, 255 })
for all pixels. The result is placed into a target image -- one row per
background colour -- with two columns. The expected output of the
GdipCreateHBITMAPFromBitmap function is placed in the left column, computed
by myself (feel free to find errors in my calculations :). The actual output
is put into the right-hand column. When the code finishes running, the
resulting 512x140 image (140 different colours, 2 columns of 256 pixels for
each) is saved to "bug-all_colours.png", using the much easier and less
error-prone Image::Save function. :)

The code also has, disabled by default, code to output the original sample's
test image once for each background colour. When the #define in the first
line of the file is uncommented, some 50 megabytes of PNG files will be
produced. This isn't really necessary for the demonstration, though :)

The test case can be downloaded at the following URL:

http://israel.logiclrd.cx/GDIPlusBug-2.zip

I have included the "bug-all_colours.png" output file for your convenience.
Upon viewing the file, it should be immediately evident that something is
indeed amiss inside GdipCreateHBITMAPFromBitmap. Everything on the right *is*
blue, despite the variety of colours present in the left column. If your
image viewer is capable of it, splitting the image into its component R, G, B
channels provides revealing information: The blue channel is being copied
properly, but the red and green channels are being set with no regard for the
background.

This leads to a much clearer conclusion about the nature of the bug: the
GdipCreateHBITMAPFromBitmap function is failing to extract the red & green
channels of the background colour. I highly suspect that this is bug can be
fixed by modifying exactly 2 lines of source code within GDI+ :) Do you
concur?

Jonathan Gilbert
 
M

Michael Phillips, Jr.

I don't believe there is a bug!

For background please see:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_3b3m.asp

When you call GetHBITMAP and the bitmap has an alpha channel,
you will get a bluish tint in the areas where the bitmap is transparent.
Your bitmap has a Format32bppARGB. It requires special handling.

This is by design. See the above link for an explanation.

If you first convert the bitmap to Format32bppPARGB and then call
GetHBITMAP, you image will display without a bluish tint.

Keep in mind that the original design of the GDI Bitmap did not include
an alpha channel. You can see this by examining the BITMAPINFOHEADER.

Microsoft later introduced the BITMAPV5HEADER which allows for
an alpha channel.

In Gdiplus, all bitmaps are 32bpp. If you don't do anything
but simply load and draw an image then everything comes out
Ok! Gdiplus handles all of the conversions to allow drawing
on a GDI context behind the scenes.

In my test case, I use the GDI functions to display the HBITMAPS.
You will get the same results if you simply use the Gdiplus Graphics
API.

Below is a test case that demonstrates the above:

Save as Form1.cs

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;




namespace hbitmap
{

/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
public const int transparent = 1;
public const int opaque = 2;
public const int AC_SRC_ALPHA = 1;
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.MenuItem menuItemDraw;
private System.Windows.Forms.MenuItem menuItemOpaque;
private System.Windows.Forms.MenuItem menuItemBitmap3;
private System.Windows.Forms.MenuItem menuItemFormat32bppARGB;
private System.Windows.Forms.MenuItem menuItemFormat32bppPARGB;
public const int AC_SRC_OVER = 0;

public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
};

public enum TernaryRasterOperations
{
SRCCOPY = 0x00CC0020, // dest = source
SRCPAINT = 0x00EE0086, // dest = source OR dest
SRCAND = 0x008800C6, // dest = source AND dest
SRCINVERT = 0x00660046, // dest = source XOR dest
SRCERASE = 0x00440328, // dest = source AND (NOT dest)
NOTSRCCOPY = 0x00330008, // dest = (NOT source)
NOTSRCERASE = 0x001100A6, // dest = (NOT src) AND (NOT dest)
MERGECOPY = 0x00C000CA, // dest = (source AND pattern)
MERGEPAINT = 0x00BB0226, // dest = (NOT source) OR dest
PATCOPY = 0x00F00021, // dest = pattern
PATPAINT = 0x00FB0A09, // dest = DPSnoo
PATINVERT = 0x005A0049, // dest = pattern XOR dest
DSTINVERT = 0x00550009, // dest = (NOT dest)
BLACKNESS = 0x00000042, // dest = BLACK
WHITENESS = 0x00FF0062, // dest = WHITE
};

[DllImport("Msimg32.dll")]
public static extern bool AlphaBlend(
IntPtr hdcDest, // handle to destination DC
int nXOriginDest, // x-coord of upper-left corner
int nYOriginDest, // y-coord of upper-left corner
int nWidthDest, // destination width
int nHeightDest, // destination height
IntPtr hdcSrc, // handle to source DC
int nXOriginSrc, // x-coord of upper-left corner
int nYOriginSrc, // y-coord of upper-left corner
int nWidthSrc, // source width
int nHeightSrc, // source height
BLENDFUNCTION blendFunction // alpha-blending function
);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hBitmap);

[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest,
int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc,
TernaryRasterOperations dwRop );

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC( IntPtr hdc );

[DllImport("gdi32.dll")]
public static extern bool DeleteDC( IntPtr hdc );

[DllImport("gdi32.dll")]
public static extern int SetBkMode( IntPtr hdc, int iBkMode );

[DllImport("gdi32.dll")]
public static extern int SetBkColor( IntPtr hdc, int crColor );

[DllImport("gdi32.dll")]
public static extern int SetTextColor( IntPtr hdc, int crColor );

[DllImport("user32.dll")]
public static extern IntPtr GetDC( IntPtr hWnd );

[DllImport("user32.dll")]
public static extern bool ReleaseDC( IntPtr hWnd, IntPtr hdc );

Bitmap bm;
IntPtr hBitmap;
IntPtr hMemDC;
IntPtr hOldBitmap;
bool bOpaque;

/// <summary>
///
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.menuItemDraw = new System.Windows.Forms.MenuItem();
this.menuItemOpaque = new System.Windows.Forms.MenuItem();
this.menuItemBitmap3 = new System.Windows.Forms.MenuItem();
this.menuItemFormat32bppARGB = new System.Windows.Forms.MenuItem();
this.menuItemFormat32bppPARGB = new System.Windows.Forms.MenuItem();
//
// mainMenu1
//
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItemBitmap3,
this.menuItemDraw});
//
// menuItemDraw
//
this.menuItemDraw.Index = 1;
this.menuItemDraw.MenuItems.AddRange(new System.Windows.Forms.MenuItem[]
{
this.menuItemOpaque});
this.menuItemDraw.Text = "Draw";
//
// menuItemOpaque
//
this.menuItemOpaque.Checked = true;
this.menuItemOpaque.Index = 0;
this.menuItemOpaque.Text = "Opaque";
this.menuItemOpaque.Click += new
System.EventHandler(this.menuItemOpaque_Click);
//
// menuItemBitmap3
//
this.menuItemBitmap3.Index = 0;
this.menuItemBitmap3.MenuItems.AddRange(new
System.Windows.Forms.MenuItem[] {
this.menuItemFormat32bppARGB,
this.menuItemFormat32bppPARGB});
this.menuItemBitmap3.Text = "Bitmap";
//
// menuItemFormat32bppARGB
//
this.menuItemFormat32bppARGB.Checked = true;
this.menuItemFormat32bppARGB.Index = 0;
this.menuItemFormat32bppARGB.Text = "Format32bppARGB";
this.menuItemFormat32bppARGB.Click += new
System.EventHandler(this.menuItemFormat32bppARGB_Click);
//
// menuItemFormat32bppPARGB
//
this.menuItemFormat32bppPARGB.Index = 1;
this.menuItemFormat32bppPARGB.Text = "Format32bppPARGB";
this.menuItemFormat32bppPARGB.Click += new
System.EventHandler(this.menuItemFormat32bppPARGB_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Menu = this.mainMenu1;
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.Paint += new
System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
this.Leave += new System.EventHandler(this.Form1_Leave);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void Form1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
// Do something with hBitmap.
IntPtr hdc = e.Graphics.GetHdc();

if ( this.bOpaque )
BitBlt(hdc, 0, 0, bm.Width, bm.Height, hMemDC, 0, 0,
TernaryRasterOperations.SRCCOPY);
else
{
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xFF;
bf.AlphaFormat = AC_SRC_ALPHA;

AlphaBlend(hdc, 0, 0, bm.Width, bm.Height, hMemDC, 0, 0, bm.Width,
bm.Height, bf );
}

e.Graphics.ReleaseHdc(hdc);
}

private void Form1_Load(object sender, System.EventArgs e)
{
this.bOpaque = true;
this.BackColor = Color.White;
this.bm = new Bitmap("c:\\temp\\PP0N6A08.png");

SelectObject(this.hMemDC, this.hOldBitmap );
DeleteObject(this.hBitmap);
DeleteDC(this.hMemDC);

this.hBitmap = bm.GetHbitmap(this.bm.GetPixel(0,0));
IntPtr hDC = GetDC( (IntPtr)null );
this.hMemDC = CreateCompatibleDC(hDC);
ReleaseDC( (IntPtr)null, hDC );
this.hOldBitmap = SelectObject(hMemDC, hBitmap );
}

private void Form1_Leave(object sender, System.EventArgs e)
{
this.bm.Dispose();
SelectObject(this.hMemDC, this.hOldBitmap );
DeleteDC(this.hMemDC);
DeleteObject(this.hBitmap);
}

private void menuItemOpaque_Click(object sender, System.EventArgs e)
{
bOpaque = !bOpaque;

if ( bOpaque )
{
menuItemOpaque.Checked = true;
Form1.ActiveForm.Invalidate();
}
else
{
menuItemOpaque.Checked = false;
Form1.ActiveForm.Invalidate();
}
}

private void menuItemFormat32bppARGB_Click(object sender, System.EventArgs
e)
{
menuItemFormat32bppPARGB.Checked = !menuItemFormat32bppPARGB.Checked;
menuItemFormat32bppARGB.Checked = true;
bm.Dispose();

this.bm = new Bitmap("c:\\temp\\PP0N6A08.png");

SelectObject(this.hMemDC, this.hOldBitmap );
DeleteObject(this.hBitmap);
DeleteDC(this.hMemDC);

this.hBitmap = bm.GetHbitmap(this.bm.GetPixel(0,0));
IntPtr hDC = GetDC( (IntPtr)null );
this.hMemDC = CreateCompatibleDC(hDC);
ReleaseDC( (IntPtr)null, hDC );
this.hOldBitmap = SelectObject(hMemDC, hBitmap );
Form1.ActiveForm.Invalidate();
}

private void menuItemFormat32bppPARGB_Click(object sender,
System.EventArgs e)
{
menuItemFormat32bppARGB.Checked = !menuItemFormat32bppARGB.Checked;
menuItemFormat32bppPARGB.Checked = true;

bm.Dispose();

Bitmap temp = new Bitmap("c:\\temp\\PP0N6A08.png");
this.bm = new Bitmap( temp.Width, temp.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb );
Graphics g = Graphics.FromImage( bm );
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
Rectangle rect = new Rectangle(0,0,temp.Width, temp.Height);
g.DrawImage( temp, rect );
g.Dispose();
temp.Dispose();

SelectObject(this.hMemDC, this.hOldBitmap );
DeleteObject(this.hBitmap);
DeleteDC(this.hMemDC);

this.hBitmap = bm.GetHbitmap(this.bm.GetPixel(0,0));
IntPtr hDC = GetDC( (IntPtr)null );
this.hMemDC = CreateCompatibleDC(hDC);
ReleaseDC( (IntPtr)null, hDC );
this.hOldBitmap = SelectObject(hMemDC, hBitmap );
Form1.ActiveForm.Invalidate();
}
}
}



I got the .png image from the PNG test image suite here:
http://www.schaik.com/pngsuite/pngsuite.html
 
G

Guest

Michael Phillips said:
I don't believe there is a bug!

For background please see:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_3b3m.asp

When you call GetHBITMAP and the bitmap has an alpha channel,
you will get a bluish tint in the areas where the bitmap is transparent.
Your bitmap has a Format32bppARGB. It requires special handling.

This is by design. See the above link for an explanation.

Are you suggesting that GDI+ uses the AlphaBlend GDI function in its
implementation of GdipCreateHBITMAPFromBitmap? I can find no evidence of
this. The GdiPlus.dll PECOFF image does not statically import the AlphaBlend
function; in fact, the string 'AlphaBlend' does not appear anywhere within
the file, neither as an ANSI or a Unicode string. The only function other
than AlphaBlend which uses the BLENDFUNCTION structure is
UpdateLayeredWindow, which hardly seems applicable in the implementation of
GdipCreateHBITMAPFromBitmap.

Even if this structure were being used by GdipCreateHBITMAPFromBitmap in
some way, this does not explain the behaviour. The expected result of not
premultiplying the alpha channel would be that the pixels would simply be too
bright. If the function failed to clamp the values for each colour component,
then they would wrap around, causing very noticeable artifacting wherever the
sum of the source and the destination exceeded 255.

The actual result that is being seen is that only the blue channel of the
specified background colour is being used. The red & green channels are being
treated as black. Did you check out the screenshot I showed?
If you first convert the bitmap to Format32bppPARGB and then call
GetHBITMAP, you image will display without a bluish tint.

My testing indicates otherwise. Even with the alpha channel premultiplied, I
still get a bluish tint. I tested this by altering my second test case,
starting at line 230, to read the following:

Bitmap test_source(256, 1, PixelFormat32bppPARGB);

for (int i=0; i < 256; i++)
test_source.SetPixel(i, 0, Color(/* a */ i, /* r, g, b */ i, i, i));

The output image is identical; it appears that GdipCreateHBITMAPFromBitmap
is aware of the difference between PixelFormat32bppARGB and
PixelFormat32bppPARGB and compensates correctly for whichever one it is not
natively processing (or has a separate code path for each).
Keep in mind that the original design of the GDI Bitmap did not include
an alpha channel. You can see this by examining the BITMAPINFOHEADER.

Microsoft later introduced the BITMAPV5HEADER which allows for
an alpha channel.

This shouldn't matter, though, because only the output is a GDI Bitmap. The
two inputs to the function are:

- a GDI+ Bitmap object, and
- a GDI+ Color value.

Both of these incorporate alpha channel support, and it is properly
implemented elsewhere in the library.
In my test case, I use the GDI functions to display the HBITMAPS.
You will get the same results if you simply use the Gdiplus Graphics
API.

Below is a test case that demonstrates the above:

I have not investigated your test case very closely, but it does not seem to
be applicable to the situation at hand. You also did not include the test
image you used, which is pertinent in this case because you use its first
pixel as the background colour.

However, there are some issues that were immediately obvious to me. Firstly:
private void Form1_Load(object sender, System.EventArgs e)
{
this.bOpaque = true;
this.BackColor = Color.White;
this.bm = new Bitmap("c:\\temp\\PP0N6A08.png");

SelectObject(this.hMemDC, this.hOldBitmap );
DeleteObject(this.hBitmap);
DeleteDC(this.hMemDC);

this.hBitmap = bm.GetHbitmap(this.bm.GetPixel(0,0));
IntPtr hDC = GetDC( (IntPtr)null );
this.hMemDC = CreateCompatibleDC(hDC);
ReleaseDC( (IntPtr)null, hDC );
this.hOldBitmap = SelectObject(hMemDC, hBitmap );
}

As far as I can tell, the middle calls to SelectObject, DeleteObject and
DeleteDC here occur before all other GDI-related initialization. The hMemDC
and hOldBitmap members will still be IntPtr.Zero at this point. This seems
rather odd, but the APIs are probably robust enough to handle this... :)

The other thing I noticed about it is that it seems to assume that the
problem with the blue tinge is related to how the bitmap is painted, and not
a deficiency of the bitmap data itself. This is a flawed assumption; when the
bug in question manifests, the resulting GDI HBITMAP contains the blue tinge
directly in the bitmap data, and nothing that is subsequently done can remove
it (since the pixels' alpha values are destroyed in the process).

This also causes problems with people who use Visual Studio's built-in
ImageList editor. The ImageList, as added to a Form & edited in the IDE, is
serialized and stored in the .resx file. Since the IDE uses a .NET ImageList
itself when editing, the serialized form is post-GdipCreateHBITMAPFromBitmap,
meaning that the blue tinge visible even in the IDE before compiling the code
is permanently stored in the resulting resources, which are subsequently
painted correctly. In other words, the problem is not the painting operation,
but the prior conversion which produced a broken bitmap.

Are you still convinced that there is no bug here? =/

Jonathan Gilbert
 
M

Michael Phillips, Jr.

The Windows ImageList control uses GDI for drawing.

The Windows bitmap specification was drawn up for Windows 3.1
a 16 bit operating system. Alpha blended bitmaps were not supported.

The documentation for AlphaBlend function implies that Windows GDI
uses premultiplied alpha channels for drawing bitmaps that contain
an alpha channel.

My test case uses GDI to draw alpha blended bitmaps.

The test image is a .png file from the PNG Test Image Suite.

I supplied a link in my post.

When you run the test case, clearly you get a bluish tint when you
use GDI to draw the bitmap converted from the .png image.

When you convert the bitmap to use premultiplied alpha, the bluish
tint is gone. The image displays correctly using GDI functions.
The same GDI functions used by the ImageList control.

Gdiplus correctly handles the drawing whether or not you
premultiply the alpha channel that is why I did not use it in
my test case.

If you use an alpha channel HBITMAP generated by gdiplus or one that
you create with CreateDibSection, it will not draw correctly without
premultiplying the alpha channel.

Not only does the above apply to HBITMAPs it also
applies to alpha channel HICONS. GDI will only
display the icons correctly when the alpha channel is
premultiplied.

I used your image file in my test case. Additionally, I created
a 32x32 alpha blended icon from your image.

When I premultiplied the alpha channel and use GDI
to draw the bitmap and icon, the image was drawn
correctly with no bluish tint. If I did not premultiply
the alpha channel, I got a bluish bitmap and a bluish
icon. Again GDI is used by the Microsoft's common
controls and GDI is used extensively to draw bitmaps
and icons for the Windows GUI ( buttons, windows, controls, etc.).

I have tested BitBlt, AlphaBlend, DrawIcon and TransBlt
on your image. The image was drawn without a bluish tint
once I premultiplied the alpha channel.

I am not convinced that you have discovered a bug!
 
G

Guest

Michael Phillips said:
The Windows ImageList control uses GDI for drawing.

Look closely at my last test case. It never draws the image! As soon as it
has the HBITMAP, it immediately calls GetDIBits and copies the raw bitmap
data into a GDI+ Bitmap manually, one pixel at a time. Nothing in GetDIBits
could possibly have the effect of eliminating two of the colour channels in
the background colour passed to GdipCreateHBITMAPFromBitmap; indeed, the
original bitmap data would be *required* in order to do this, because pixel
alpha information has already been lost, and the original pixels have been
blended with the background colour already. Even if GetDIBits did have such a
nefarious plan, there is no generic way (mathematically) whereby any
component of the background colour could be determined from the pixels
returned by GdipCreateHBITMAPFromBitmap.

All of your discussion about drawing bitmaps is thus completely irrelevant;
the bitmap is never drawn. Its bits are merely copied to a scan line in a
GDI+ Bitmap object, which is later saved as a PNG file.
The documentation for AlphaBlend function implies that Windows GDI
uses premultiplied alpha channels for drawing bitmaps that contain
an alpha channel.

You are fixated on this AlphaBlend function. GDI+ *never* uses it. The
GdiPlus.dll file never refers to that function in any way.
I supplied a link in my post.

My mistake, I see it now on closer inspection.

[snip]
I have tested BitBlt, AlphaBlend, DrawIcon and TransBlt
on your image. The image was drawn without a bluish tint
once I premultiplied the alpha channel.

Again, please look very closely at my test case. It directly inspects the
bits returned by GdipCreateHBITMAPFromBitmap. It does not draw them to the
screen (or to any DC for that matter) in any way.

In any event, GdipCreateHBITMAPFromBitmap is not directly documented, but
GDI+'s Bitmap::GetHBITMAP is defined as follows in the Platform SDK
GdiPlusBitmap.h header file:

inline Status
Bitmap::GetHBITMAP(
IN const Color& colorBackground,
OUT HBITMAP* hbmReturn
)
{
return SetStatus(DllExports::GdipCreateHBITMAPFromBitmap(
static_cast<GpBitmap*>(nativeImage),
hbmReturn,
colorBackground.GetValue()));
}

The documentation for this member function (which, as you can see, is
nothing but a thin wrapper around GdipCreateHBITMAPFromBitmap) mentions
absolutely nothing about it having to be premultiplied. Neither does the
documentation for .NET's System.Drawing.Bitmap::GetHbitmap() which is a
similarly thin wrapper. Why, then, do you purport that the function requires
this premultiplication as a precondition?

Just to further demonstrate that there IS in fact a bug here, I've recreated
my example in C# using System.Drawing instead of calling GDI+ directly. The
sample creates a perfectly transparent image (transparent black, which
happens to be identical in both ARGB and PARGB pixel formats) and then uses
solid white as the background colour for S.D.Bitmap::GetHbitmap(). It uses
both PixelFormat.Format32bppArgb and PixelFormat.Format32bppPArgb, and
converts the resulting HBITMAP back to a GDI+ Bitmap object using
S.D.Bitmap::FromHbitmap(). By all accounts, putting a perfectly transparent
image onto a white background should produce a perfectly white image, right?
Well, here is the code:

using System;
using System.Drawing;
using System.Drawing.Imaging;

class MainClass
{
static void Main(string[] args)
{
Color transparent = Color.FromArgb(0, Color.Black);

Bitmap argb = new Bitmap(50, 50, PixelFormat.Format32bppArgb);
Bitmap pargb = new Bitmap(50, 50, PixelFormat.Format32bppPArgb);

for (int x=0; x < 50; x++)
for (int y=0; y < 50; y++)
{
argb.SetPixel(x, y, transparent);
pargb.SetPixel(x, y, transparent);
}

IntPtr hArgbBitmap = argb.GetHbitmap(Color.White);
IntPtr hPArgbBitmap = pargb.GetHbitmap(Color.White);

Bitmap argb_after = Bitmap.FromHbitmap(hArgbBitmap);
Bitmap pargb_after = Bitmap.FromHbitmap(hPArgbBitmap);

argb_after.Save("argb_after.png", ImageFormat.Png);
pargb_after.Save("pargb_after.png", ImageFormat.Png);
}
}

I encourage you to run this sample. The resulting PNGs, being a solid
colour, compress very well; the 50x50 pixels turn into only 280 bytes.
Compare the two files; you will find them to be identical, byte for byte.
Upon opening them, you will discover that they are not white but blue. Can
you explain this by means of the AlphaBlend GDI function?

Jonathan Gilbert
 
M

Michael Phillips, Jr.

Again, no Bug!

Per the documentation, GetHBITMAP produces a GDI bitmap.

GDI bitmaps per the docs must consist of a BITMAPINFOHEADER
and a color table ( if any ) followed by the image bits.

By definition, a BITMAPINFOHEADER is not capable of representing
an alpha channel bitmap only a BITMAPV5HEADER can represent an
alpha channel bitmap.

Per the gdiplus documentation, the HBITMAP returned from GetHBITMAP
is a 32bpp DIBSection.

You can verify this fact by using the following code with the hBitmap
returned:

DIBSECTION ds;
::GetObject( hBitmap, sizeof(DIBSECTION), &ds );

It is extremely important to note that the DIBSECTION returned
is BI_RGB. This is a dead giveaway that this is a GDI bitmap
with no alpha channel.

The Gdiplus docs do not misrepresent the GetHBITMAP method.
It gives you exactly what the documentation states!

If you want to save a bitmap with an alpha channel, the docs
state that you must create the HBITMAP with CreateDIBSection
and use the BITMAPV5HEADER properly filled in.

You can use gdiplus to read and create the bitmap object and
use lockbits to fill the image bits of the DIBSection or
you can use GDI and GetDIBits to fill the DIBSection with
your alpha channel.

Jonathan Gilbert said:
Michael Phillips said:
The Windows ImageList control uses GDI for drawing.

Look closely at my last test case. It never draws the image! As soon as it
has the HBITMAP, it immediately calls GetDIBits and copies the raw bitmap
data into a GDI+ Bitmap manually, one pixel at a time. Nothing in
GetDIBits
could possibly have the effect of eliminating two of the colour channels
in
the background colour passed to GdipCreateHBITMAPFromBitmap; indeed, the
original bitmap data would be *required* in order to do this, because
pixel
alpha information has already been lost, and the original pixels have been
blended with the background colour already. Even if GetDIBits did have
such a
nefarious plan, there is no generic way (mathematically) whereby any
component of the background colour could be determined from the pixels
returned by GdipCreateHBITMAPFromBitmap.

All of your discussion about drawing bitmaps is thus completely
irrelevant;
the bitmap is never drawn. Its bits are merely copied to a scan line in a
GDI+ Bitmap object, which is later saved as a PNG file.
The documentation for AlphaBlend function implies that Windows GDI
uses premultiplied alpha channels for drawing bitmaps that contain
an alpha channel.

You are fixated on this AlphaBlend function. GDI+ *never* uses it. The
GdiPlus.dll file never refers to that function in any way.
I supplied a link in my post.

My mistake, I see it now on closer inspection.

[snip]
I have tested BitBlt, AlphaBlend, DrawIcon and TransBlt
on your image. The image was drawn without a bluish tint
once I premultiplied the alpha channel.

Again, please look very closely at my test case. It directly inspects the
bits returned by GdipCreateHBITMAPFromBitmap. It does not draw them to the
screen (or to any DC for that matter) in any way.

In any event, GdipCreateHBITMAPFromBitmap is not directly documented, but
GDI+'s Bitmap::GetHBITMAP is defined as follows in the Platform SDK
GdiPlusBitmap.h header file:

inline Status
Bitmap::GetHBITMAP(
IN const Color& colorBackground,
OUT HBITMAP* hbmReturn
)
{
return SetStatus(DllExports::GdipCreateHBITMAPFromBitmap(

static_cast<GpBitmap*>(nativeImage),
hbmReturn,
colorBackground.GetValue()));
}

The documentation for this member function (which, as you can see, is
nothing but a thin wrapper around GdipCreateHBITMAPFromBitmap) mentions
absolutely nothing about it having to be premultiplied. Neither does the
documentation for .NET's System.Drawing.Bitmap::GetHbitmap() which is a
similarly thin wrapper. Why, then, do you purport that the function
requires
this premultiplication as a precondition?

Just to further demonstrate that there IS in fact a bug here, I've
recreated
my example in C# using System.Drawing instead of calling GDI+ directly.
The
sample creates a perfectly transparent image (transparent black, which
happens to be identical in both ARGB and PARGB pixel formats) and then
uses
solid white as the background colour for S.D.Bitmap::GetHbitmap(). It uses
both PixelFormat.Format32bppArgb and PixelFormat.Format32bppPArgb, and
converts the resulting HBITMAP back to a GDI+ Bitmap object using
S.D.Bitmap::FromHbitmap(). By all accounts, putting a perfectly
transparent
image onto a white background should produce a perfectly white image,
right?
Well, here is the code:

using System;
using System.Drawing;
using System.Drawing.Imaging;

class MainClass
{
static void Main(string[] args)
{
Color transparent = Color.FromArgb(0, Color.Black);

Bitmap argb = new Bitmap(50, 50, PixelFormat.Format32bppArgb);
Bitmap pargb = new Bitmap(50, 50, PixelFormat.Format32bppPArgb);

for (int x=0; x < 50; x++)
for (int y=0; y < 50; y++)
{
argb.SetPixel(x, y, transparent);
pargb.SetPixel(x, y, transparent);
}

IntPtr hArgbBitmap = argb.GetHbitmap(Color.White);
IntPtr hPArgbBitmap = pargb.GetHbitmap(Color.White);

Bitmap argb_after = Bitmap.FromHbitmap(hArgbBitmap);
Bitmap pargb_after = Bitmap.FromHbitmap(hPArgbBitmap);

argb_after.Save("argb_after.png", ImageFormat.Png);
pargb_after.Save("pargb_after.png", ImageFormat.Png);
}
}

I encourage you to run this sample. The resulting PNGs, being a solid
colour, compress very well; the 50x50 pixels turn into only 280 bytes.
Compare the two files; you will find them to be identical, byte for byte.
Upon opening them, you will discover that they are not white but blue. Can
you explain this by means of the AlphaBlend GDI function?

Jonathan Gilbert
 
G

Guest

Michael Phillips said:
Again, no Bug!

Per the documentation, GetHBITMAP produces a GDI bitmap.

GDI bitmaps per the docs must consist of a BITMAPINFOHEADER
and a color table ( if any ) followed by the image bits.

By definition, a BITMAPINFOHEADER is not capable of representing
an alpha channel bitmap only a BITMAPV5HEADER can represent an
alpha channel bitmap.

Per the gdiplus documentation, the HBITMAP returned from GetHBITMAP
is a 32bpp DIBSection.

You can verify this fact by using the following code with the hBitmap
returned:

DIBSECTION ds;
::GetObject( hBitmap, sizeof(DIBSECTION), &ds );

It is extremely important to note that the DIBSECTION returned
is BI_RGB. This is a dead giveaway that this is a GDI bitmap
with no alpha channel.

The Gdiplus docs do not misrepresent the GetHBITMAP method.
It gives you exactly what the documentation states!

If you want to save a bitmap with an alpha channel, the docs
state that you must create the HBITMAP with CreateDIBSection
and use the BITMAPV5HEADER properly filled in.

You can use gdiplus to read and create the bitmap object and
use lockbits to fill the image bits of the DIBSection or
you can use GDI and GetDIBits to fill the DIBSection with
your alpha channel.

You *still* misunderstand! GdipCreateHBITMAPFromBitmap is intended to take a
GDI+ Bitmap object, optionally with an alpha channel, and overlay it onto a
background such that transparent & translucent pixels show the background.
The resulting *opaque* image is then to be returned as an HBITMAP for use in
plain old GDI calls. In short, GdipCreateHBITMAPFromBitmap, which is not the
same thing as AlphaBlend, DrawIcon, TransBlt, etc., is *specifically
designed* to handle source images (being GDI+ Bitmap objects) with alpha
channels, and to output old-fashioned HBITMAP GDI objects *without* alpha
channels.

Consider the fact that GdipCreateHBITMAPFromBitmap actually takes a
background parameter, which the documentaton specifically states is for
non-opaque pixels in the source image. Don't you think it would be odd for it
to do the background colour operation it mentions in the MSDN documentation
but still try to return a translucent HBITMAP? I certainly do not expect it
to attempt to do so! In any event, it is not any drawing operation which puts
a blue tint onto the HBITMAP, but the code within
GdipCreateHBITMAPFromBitmap. I have confirmed this by writing my own
GdipCreateHBITMAPFromBitmap function and plugging it into the test case code,
keeping the rest of the code unchanged. The blue tint goes away!

To summarize it:
- With the existing GdiPlus.dll's GdipCreateHBITMAPFromBitmap function, the
test case outputs have blue tints.
- With my own implementation of GdipCreateHBITMAPFromBitmap function, the
test case outputs do not have blue tints.

Nothing else in the test cases changes! So how can this not be a bug in
GdipCreateHBITMAPFromBitmap? Refer to the original post in this thread for my
implementation of GdipCreateHBITMAPFromBitmap.

Jonathan Gilbert
 
M

Michael Phillips, Jr.

I understand perfectly well! The gdiplus GdipCreateHBITMAPFromBitmap
is designed to return a BI_RGB GDI Bitmap with an optional color-key
of chroma-key for the transparency.

The background color that you specify in the GetHBITMAP
refers to the color-key or chroma-key. It is not the same
as the alpha channel!

The color-key operation is performed on rgb not argb.

Refer to what is returned by the DIBSECTION!
You get a BI_RGB bitmap. There is no alpha channel!
 
G

Guest

Michael Phillips said:
I understand perfectly well! The gdiplus GdipCreateHBITMAPFromBitmap
is designed to return a BI_RGB GDI Bitmap with an optional color-key
of chroma-key for the transparency.

The background color that you specify in the GetHBITMAP
refers to the color-key or chroma-key. It is not the same
as the alpha channel!

This is not true. Read the documentation more closely. It clearly states
that the background colour parameter is ignored when the source image has no
alpha channel, and that when it does, translucent & transparent pixels are
blended against the specified colour to produce an opaque image for the
HBITMAP.
The color-key operation is performed on rgb not argb.

There is no colour keying going on.
Refer to what is returned by the DIBSECTION!
You get a BI_RGB bitmap. There is no alpha channel!

What does this have to do with anything? What I am asserting is that the
BI_RGB bitmap contains broken data, and that that data is being broken inside
of GdipCreateHBITMAPFromBitmap.

Jonathan Gilbert
 
M

Michael Phillips, Jr.

As background, I am using the following:
http://msdn.microsoft.com/library/d...sses/BitmapClass/BitmapMethods/GetHBITMAP.asp

The docs state that the background color is ignored if totally opaque.

It does not refer to the alpha channel in any way. This is because the
background color refers to the color-key.

That is why gdiplus is able to load and draw .gif files with transparency.
It uses the transparent color as the color-key.

What does the BI_RGB returned from the DIBSECTION have to
do with anything. It is simple. Per the docs BI_RGB means there
is no alpha channel data preserved!!!!!!

See:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_1rw2.asp

The docs cleary state that the high order alpha bits are ignored!

If the DIBSECTION returned BI_BITFIELDS and the size of the
BITMAPINFOHEADER = sizeof(BITMAPV5HEADER),
then the alpha channel data would be preserved!

See:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_1rw2.asp
 
G

Guest

Michael Phillips said:
As background, I am using the following:
http://msdn.microsoft.com/library/d...sses/BitmapClass/BitmapMethods/GetHBITMAP.asp

The docs state that the background color is ignored if totally opaque.

It does not refer to the alpha channel in any way. This is because the
background color refers to the color-key.

If it is color keying, then why does the blue channel of the specified
background colour end up being blended into any translucent or transparent
pixels? Shouldn't it be either none of the background colour being blended in
(if it is colour-keying), or all 3 channels (if it is as I believe it should
be)?

Jonathan Gilbert
 
M

Michael Phillips, Jr.

I don't know what to say.

The docs don't lie. The API gives you what
you asked for!

Your test case was wrong.

I corrected it for you.

If you run it, you will get the results that you
originally expected:

using System;
using System.Drawing;
using System.Drawing.Imaging;

class MainClass
{
static void Main(string[] args)
{
Color transparent = Color.FromArgb(0x00, Color.Black);
Color opaque = Color.FromArgb(0xFF, Color.White);

Bitmap argb = new Bitmap(32, 32, PixelFormat.Format32bppRgb);
Bitmap pargb = new Bitmap(32, 32, PixelFormat.Format32bppPArgb);

for (int x=0; x < 32; x++)
for (int y=0; y < 32; y++)
{
argb.SetPixel(x, y, ((x%2 != 0) && (y%2 != 0) ? opaque :
transparent));

byte A, R, G, B;
if ( (x%2 != 0) && (y%2 != 0) )
{
A = opaque.A;
R = (byte)((float)opaque.R * opaque.A / 255.0);
G = (byte)((float)opaque.G * opaque.A / 255.0);
B = (byte)((float)opaque.B * opaque.A / 255.0);
}
else
{
A = transparent.A;
R = (byte)((float)transparent.R * transparent.A / 255.0);
G = (byte)((float)transparent.G * transparent.A / 255.0);
B = (byte)((float)transparent.B * transparent.A / 255.0);
}

Color preMult = Color.FromArgb(A,R,G,B);
pargb.SetPixel(x, y, preMult);
}

IntPtr hArgbBitmap = argb.GetHbitmap(Color.White);
IntPtr hPArgbBitmap = pargb.GetHbitmap(Color.White);

Bitmap argb_after = Bitmap.FromHbitmap(hArgbBitmap);
Bitmap pargb_after = Bitmap.FromHbitmap(hPArgbBitmap);

argb_after.Save("argb_after.png", ImageFormat.Png);
pargb.Save("pargb_after.png", ImageFormat.Png);
argb.Save("argb.png", ImageFormat.Png);
pargb.Save("pargb.png", ImageFormat.Png);

argb.Dispose();
pargb.Dispose();

argb_after.Dispose();
pargb_after.Dispose();
}
}

As you can see, premultiplying the alpha channel
allow you to get the desired result from the BI_RGB
DIBSection produced from the GetHbitmap method.
 
G

Guest

Michael Phillips said:
I don't know what to say.

The docs don't lie. The API gives you what
you asked for!

You are misinterpreting the documentation.
Your test case was wrong.

I corrected it for you.

Actually, you broke it. Let me show you the ways:
using System;
using System.Drawing;
using System.Drawing.Imaging;

class MainClass
{
static void Main(string[] args)
{
Color transparent = Color.FromArgb(0x00, Color.Black);
Color opaque = Color.FromArgb(0xFF, Color.White);

Color.White already has an alpha value of 0xFF, so this is superfluous.
Bitmap argb = new Bitmap(32, 32, PixelFormat.Format32bppRgb);

You have turned my ARGB bitmap into a plain RGB bitmap. It no longer has an
alpha channel, so all the playing around with alpha channels in Color values
is completely moot. Since the image is guaranteed to always be fully opaque
(by dint of its PixelFormat), GetHbitmap ignores the background colour, as
the documentation says it will.
Bitmap pargb = new Bitmap(32, 32, PixelFormat.Format32bppPArgb);

for (int x=0; x < 32; x++)
for (int y=0; y < 32; y++)
{
argb.SetPixel(x, y, ((x%2 != 0) && (y%2 != 0) ? opaque :
transparent));

The following block is completely redundant:
byte A, R, G, B;
if ( (x%2 != 0) && (y%2 != 0) )
{
A = opaque.A;
R = (byte)((float)opaque.R * opaque.A / 255.0);
G = (byte)((float)opaque.G * opaque.A / 255.0);
B = (byte)((float)opaque.B * opaque.A / 255.0);
}
else
{
A = transparent.A;
R = (byte)((float)transparent.R * transparent.A / 255.0);
G = (byte)((float)transparent.G * transparent.A / 255.0);
B = (byte)((float)transparent.B * transparent.A / 255.0);
}

In the case of the opaque colour, the alpha channel is guaranteed to be 255
(even if you don't trust Color.White to have that value, you explicitly set
it to be that way in the variable's initialization). Therefore, multiplying
R, G and B by the alpha and dividing by 255 does not change the R, G, B
values in any way.

In the case oft he transparent colour, the alpha channel is explicitly set
to 0 in initialization. Normally, this would have the effect of altering the
R, G, B values to be exactly 0, but no alteration takes place in this
instance because the base colour is *already* Color.Black.

So, you could just as well have used 'opaque' and 'transparent' for your
"fixed" version of my test case (I use "fixed" here in the same sense as
might be used to refer to a "fixed" race or a "fixed" contest).
Color preMult = Color.FromArgb(A,R,G,B);
pargb.SetPixel(x, y, preMult);
}

IntPtr hArgbBitmap = argb.GetHbitmap(Color.White);
IntPtr hPArgbBitmap = pargb.GetHbitmap(Color.White);

Bitmap argb_after = Bitmap.FromHbitmap(hArgbBitmap);
Bitmap pargb_after = Bitmap.FromHbitmap(hPArgbBitmap);

argb_after.Save("argb_after.png", ImageFormat.Png);

As described above, this file does not show the blue because even though the
variable says "argb" and the filename says "argb", you changed the pixel
format to Format32bppRgb, with no alpha channel.
pargb.Save("pargb_after.png", ImageFormat.Png);

And this line is just plain cheating. You don't actually save the "after"
image, you save the original but give in the filename "_after".
argb.Save("argb.png", ImageFormat.Png);
pargb.Save("pargb.png", ImageFormat.Png);

argb.Dispose();
pargb.Dispose();

argb_after.Dispose();
pargb_after.Dispose();
}
}

As you can see, premultiplying the alpha channel
allow you to get the desired result from the BI_RGB
DIBSection produced from the GetHbitmap method.

Premultiplying the alpha channel for these specific colours in the way you
did it does not alter them in any way whatsoever, and when the ARGB Bitmap
has the correct pixel format and the actual output of the PARGB Bitmap's
GetHbitmap is stored, the blue still appears.

Up until this last post, you did nothing but demonstrate extreme stubborness
and lack of intelligence, but you gave yourself away in this case. I will not
respond to any further posts by you, because you are clearly trolling. You
modified my test case with the intention of breaking its testing and
preventing the correct output from being saved, and you probably knew
fullwell that your "premultiplication" did absolutely nothing.

You have done nothing but waste small amounts of my time, of which I have
vast quantities for the time being, but I worry that perhaps other people
have been watching this thread and seeing your replies as authoritative. I
can only hope that they will also read through this last message in this
branch of the thread and realize, as I just did, that you have been stringing
everyone along with bogus arguments.

If anyone within Microsoft is reading, I would really appreciate it if you
might drop a post in here to acknowledge that my assessment of this bug is
correct, and I would also like this bug to be fixed, because there is no good
reason not to do so. I'm sure there are some concerns that I have not
addressed in my reimplementation, but it seemed very easy to me to write this
GdipCreateHBITMAPFromBitmap function in terms of the GDI and GDI+ API. See my
original post in this thread for that rewrite.

Jonathan Gilbert
 
T

TC

Seems foolish to abuse the folks who try to help.

Speaking as someone who has answered thousands of questions in other
groups, for no real benefit to myself, I notice people being aggressive
- to me, or to anyone else - and add them to my "don't reply" list.

Just my 2c ...

TC
 
Joined
Jan 3, 2007
Messages
1
Reaction score
0
I concur with Jonathan

There definitely seems to be a bug with the code that calculates the values of translucent pixels when combined with background color. .NET's Color.LightGray, when converted to a Win32 color with ColorTranslator.ToWin32 returns D3D3D3 (assumed to be FF for the alpha channel). When blended with a source pixel of ARGB 00000000 the result should be 7F696969, which is not blue but a darker shade of gray.

I agree with Jonathan that the code seems to be ignoring both the red and green channels in this calculation. Most of the rest of the discussion is unrelated to this particular issue, but I think this is the core issue that Jonathan was trying to get at. I would expect a blended pixel to look blue if I specified a color of Color.Blue.

Perhaps someone with inside knowledge of the GDI+ code can give the formula used to produce the blue color?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top