Direct2D With GDI+ Render Target Blending Transparent as Black - winforms

Reproduce the problem in this project:
https://github.com/adrotter/GDIDirect2DDualRenderer
I am trying to make Direct2D work with our current rendering engine, which is GDI+. I did this by abstracting all the calls to GDI+, and making a concrete class for GDI+ and for Direct2D. One issue that a lot of people seem to have is when you call Clear on the render target with a transparent color, you get completely opaque black background.
How do I clear a Direct2D render target to fully transparent
How to create a transparent bitmap in Direct2D
The answers from the above stack overflow posts suggest I need a WS_EX_LAYERED window, or using DirectComposition, but I am not sure if doing that would work well in WinForms. Besides, they are only talking about making the window transparent and not pixels on a bitmap. The solution I came up with that partially works is if there is a call to Graphics.Clear, and I have yet to start drawing using the render target, then I call Clear on the Drawing.Graphics object, and then start drawing. This will make the background completely transparent if I use Graphics.Clear(Color.Transparent) (fixes the problem people had with the black background).
Here is how that looks like with 100% opacity:
The color ramp on the left is Direct2D rendering, and the color ramp on the right is GDI+. They look similar, but the Direct2D looks darker. It gets worse.
Here's 50% opacity
1% opacity
Now this is why my solution partially works: 0% opacity works great. See here.
Now I don't actually think that the target blending black with transparent is a result of me calling Clear first with the System.Drawing.Graphics object first, because I tested the partial transparency without using my trick (I made no calls to GDI+ graphics at all). I would still get the same results as above for opacities 50 and 1. It seems like calling DrawBitmap using a bitmap with partial transparencies will always blend the partial transparent color with black.
For reference here is how my render target is being made:
Dim targetProperties As New RenderTargetProperties
targetProperties.Type = RenderTargetType.Default
targetProperties.PixelFormat = New Direct2D1.PixelFormat(Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied)
targetProperties.Usage = RenderTargetUsage.GdiCompatible
Dim factory = New Direct2D1.Factory()
_target = New DeviceContextRenderTarget(factory, targetProperties)
_target.BindDeviceContext(_graphics.GetHdc(), rec)
Also for reference here is how I turn a System.Drawing.Bitmap into a Direct2D1Bitmap:
Private Function GetDirect2DBitmap(bmp As System.Drawing.Bitmap) As SharpDX.Direct2D1.Bitmap
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(
New System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
Dim stream As New SharpDX.DataStream(bmpData.Scan0, bmpData.Stride * bmpData.Height, True, False)
Dim pFormat As New SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, Direct2D1.AlphaMode.Premultiplied)
Dim bmpProps As New SharpDX.Direct2D1.BitmapProperties(pFormat)
Dim result As New SharpDX.Direct2D1.Bitmap(
_targets(_selectedTargetIdx),
New SharpDX.Size2(bmp.Width, bmp.Height),
stream,
bmpData.Stride,
bmpProps)
bmp.UnlockBits(bmpData)
stream.Dispose()
Return result
End Function
And here is a simplification of getting my semi transparent bitmaps:
//using C# style comments so this looks prettier
//Make empty bitmap to draw on
dim canvasBMP = new System.Drawing.Bitmap(100,100)
//Make the bitmap that has partial transparent pixels
dim partialTransparentBMP as System.Drawing.Bitmap = GetSemiTransparentBMP(100,100)
Using g as System.Drawing.Graphics = Graphics.FromImage(canvasBMP)
g.Clear(Color.White)
dim d2dTarget = MakeRenderTarget(g, g.VisibleClipBounds)
d2dTarget.BeginDraw()
d2dTarget.DrawBitmap(GetDirect2DBitmap(partialTransparentBMP), GetDirect2DRect(...), 1.0F, BitmapInterpolationMode.Linear)
d2dTarget.EndDraw()
End Using
How do I get a white blended partial transparent pixel and not a black one using a GDI+ render target?

Related

when to use WriteableBitmap and BitmapImage in silverlight

I am trying to display image using BitmapImage for some time and it worked.I have changed the image and it stopped working.
For Bitmapimage I was using this code:
`ms.Seek(0, SeekOrigin.Begin); // ms is memory stream
BitmapImage b = new BitmapImage();
b.SetSource(ms);
image.ImageSource = b;`
I have ran into piece of code where it was checking if the length of the bytes[] ==14400
if(bytes.length == 14400)
{
var bmp = new WriteableBitmap(width, height);
Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
}
I want to know when to use WriteableBitmap and BitmapImage .
From iProgrammer:
Bitmaps are generally implemented as immutable objects. What this means is that once you create a bitmap you can't make any changes to it. You can manipulate bitmaps by creating new versions, which then immediately become immutable....
The WriteableBitmap, as its name suggests, isn't immutable and you can get at its individual pixels and manipulate them as much as you want. This is the ideal way to work when you need dynamic bitmaps.
iProgrammer - WriteableBitmap
From MSDN:
"Use the WriteableBitmap class to update and render a bitmap on a per-frame basis..." MSDN - WriteableBitmap Class
The Examples section of the MSDN article also shows how to update a WritableBitmap image when responding to mouse events. The code in the example erases pixels of the image by setting the pixel's ARGB values to 0 when the mouse's right button is down. The code also shows how to update individual pixels in the image where the mouse's left button is down. Essentially the code shows a rudimentary pixel image editor.
The point, however, is that you can't change image data when using regular bitmap - you have to use WritableBitmap instead. You can, however, render both if you wish.

How to set Transparency color for the output of RenderTargetBitmap?

I'm trying to save a Visual object, which has a transparent background, to a bitmap using RenderTargetBitmap...
public static RenderTargetBitmap RenderToBitmap(this Visual Source, int Width, int Height)
{
var Result = new RenderTargetBitmap(Width, Height, 96, 96, PixelFormats.Default);
Result.Render(Source);
return Result;
}
It works, but the transparent pixels are rendered with black color.
What the simplest way to change these transparent pixels to another color?
If you are saving the image as a JPG, transparent will appear black since JPG does not support transparent channel AFAIK. Possible solution: save as a PNG, or paint with a reasonable background color.
I haven't tested this, but in theory it should work.
Use the CopyPixels() method to extract all the pixel data from your RenderTargetBitmap to an array.
Then query the Alpha channel of all those pixels and where they are equal to 0x00 (fully transparent), set the color to whatever you'd like in the background. If you want to be more elegant, you'd have to do the "color math" to properly set the colors in semi-transparent pixels.
Finally, after you have an adjusted array of pixels, create a new BitmapSource from them.
To save that to disk, you'll probably have to create an Image in memory and set its Source to your new Bitmapsource and run your RenderToBitmap again.
Hope it helps.
EDIT:
After posting this, I had
another thought that may be easier.
If you take a clone or snapshot of the
visual element you're trying to save
and put it into a new in-memory panel
(such as a grid or a canvas), you
could simply change the background of
the panel to be the color you want.
Then you'd use the panel as your
source for RenderTargetBitmap.

Draw custom pushpin in code without using an image

I need to draw a pushpin for the Bing Silverlight control that can have the head be a variable color. I can draw a nice dot like this:
Dim marker As Ellipse = New Ellipse
marker.Fill = New SolidColorBrush(Color.FromArgb(255, 11, 255, 0))
marker.Stroke = New SolidColorBrush(Colors.Gray)
marker.Width = 10
marker.Height = 10
I'll be making dozens of pushpins, each with a slightly different color for the Fill. How can I add the pointy part? I would like to have some amount of flaring out at the top so that it looks more like a pushpin and less like a lollipop.
Examples in C# are welcome as well.
Maybe there's a reason you need it to be a custom one rather than using the built-in pushpin objects, but if not, you can set the background color on those pushpins like so:
myPushpin.Background = new SolidColorBrush(Colors.Gray);
As far as actually drawing your own, I'm not as sure. Could you draw some sort of a triangle?

Create and resize an image in WPF from System.Drawing.Bitmap areas

I'm trying to implement a function that takes a System.Drawing.Bitmap object and renders it on a WPF Canvas. The bitmap has to be cropped and joined a few times before rendering.
Environment: WPF application running on .NET 3.5 SP1
Input: System.Drawing.Bitmap object, of size 800x600 and pixel format RGB24
Goal: to display an image which is composed of two stripes of the input bitmap (on one line). The stripes are two bitmap halves - (0,0,800,300) and (0,300,800,600). Later on I want to be able to scale the image up or down.
I've already implemented a solution with GDI and Graphics.DrawImage (that renders into a Bitmap object), but I want to improve performance (this function could be called 30 times per second).
Is there a faster way to implement this with WPF, assuming I want to render the image on a WPF window?
The best solution I found so far is using WriteableBitmap, something like this:
void Init()
{
m_writeableBitmap = new WriteableBitmap(DesiredWidth, DesiredHeight, DesiredDpi, DesiredDpi, PixelFormats.Pbgra32, null);
{
void CopyPixels(System.Drawing.Bitmap frame, Rectangle source, Point destBegin)
{
var bmpData = frame.LockBits(source, ImageLockMode.ReadOnly, frame.PixelFormat);
m_writeableBitmap.Lock();
var dest = new Int32Rect(destBegin.X, destBegin.Y, bmpData.Width, bmpData.Height);
m_writeableBitmap.WritePixels(dest, bmpData.Scan0, bmpData.Stride * bmpData.Height, bmpData.Stride);
m_writeableBitmap.Unlock();
frame.UnlockBits(bmpData);
}
CopyPixels would be called twice for the use case I described in my question (two stripes).

Rotating a .NET panel in Windows Forms

We use Windows Forms and custom user controls, and I would like to be able to rotate the panel hosting the userControl in a particular form. I have seen similar functionnalities with WPF, but I can't use it for the moment. Is it possible to achieve the rotation of a panel and its children using possibly built-in .NET methods or GDI+?
I have seen some pretty cool visual effect with menus that are displayed in game development, so I was wondering if it would be possible to create similar effects using Windows Forms.
Rotating a panel and its children in Windows Forms is not something directly supported, and I think it will end up being a buggy headache that could easily suck up lots of time. It's especially painful to think about when you could do this in WPF with zero lines of C# code and only a tiny bit of XAML.
You can use rotations in GDI+ by calling the RotateTransform method on a Graphics object.
However, rotating an entire control is not so simple, and will depend heavily on how the control is implemented.
If it's a composite UserControl that has other controls inside of it, you're out of luck.
If it's a sinlge control that paints itself, try inheriting the control, overriding the OnPaint method, and calling RotateTransform on the Graphics object. However, you will probably have trouble with it. In particular, you will probably need to override all of the mouse events and call the base control's events with rotated coordinates.
You can get halfway there by calling the DrawToBitmap method on your panel, then rotating the bitmap and displaying it e.g. in a PictureBox:
var bitmap = new Bitmap(panel.Width, panel.Height);
panel.DrawToBitmap(bitmap, new Rectangle(Point.Empty, panel.Size));
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
var pictureBox = new PictureBox();
pictureBox.Location = panel.Location;
pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;
pictureBox.Image = bitmap;
Controls.Remove(panel);
Controls.Add(pictureBox);
Rotation angles other than 90-degree increments are also possible, if you draw the bitmap into another bitmap using GDI:
var bitmap2 = new Bitmap(bmp.Width + 75, bmp.Height + 100);
var graphics = Graphics.FromImage(bmp2);
graphics.TranslateTransform(bitmap2.Width / 2, bitmap2.Height / 2);
graphics.RotateTransform(-15f);
graphics.TranslateTransform(-bitmap.Width / 2, -bitmap.Height / 2);
graphics.DrawImageUnscaled(bitmap, Point.Empty);
graphics.Dispose();
The problem of course is that you're only displaying an image of your panel, and not the panel itself, so it's no longer possible to interact with the controls inside.
That could probably be done as well, but you would have to mess with window messages, which gets quite a bit more complicated. Depending on your needs you might also be able to get away with handling click and key events on the PictureBox, manipulating the controls in the panel, and then updating the image.

Resources