Jump to content

Rendering a transparency checkerboard with Direct2D


null54

Recommended Posts

My current GDI+ code renders the checkerboard to its own Bitmap, and then performs a double-buffered copy to render it to the target Graphics object.

The double buffering is used to prevent flickering when the main image is rendered over the checkerboard.

 

Here is a link to the code for DrawCheckerboardBitmap: https://github.com/0xC0000054/PSFilterPdn/blob/61aef4befe814e8043e208334fb7bfc17aed98b6/src/PSApi/LoadPsFilter.cs#L2526

I did not include a copy of its code to reduce the length of this post.

 

int width = displaySurface.Width;
int height = displaySurface.Height;

if (checkerBoardBitmap == null ||
    checkerBoardBitmap.Width != width ||
    checkerBoardBitmap.Height != height)
{
    DrawCheckerBoardBitmap(width, height);
}

// Use a temporary bitmap to prevent flickering when the image is rendered over the checker board.
using (Bitmap temp = new(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
    Rectangle rect = new(0, 0, width, height);

    using (Graphics tempGr = Graphics.FromImage(temp))
    {
        tempGr.DrawImageUnscaled(checkerBoardBitmap, rect);
        tempGr.DrawImageUnscaled(aliasedDisplayPixelsBitmap, rect);
    }

    hdc.DrawImageUnscaled(temp, dstX, dstY);
}

 

Based on the existence of the BeginDraw and EndDraw methods, I am assuming that Direct2D does not require the manual double buffering.

 

Is there a more efficient method of rendering the checkerboard than creating a full-size image?

Looking at the ID2D1RenderTarget documentation, it appears that an ID2D1BitmapBrush could be used with the ID2DRenderTarget FillRectangle method.

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

3 hours ago, null54 said:

I am assuming that Direct2D does not require the manual double buffering

100% correct

 

3 hours ago, null54 said:

it appears that an ID2D1BitmapBrush could be used with the ID2DRenderTarget FillRectangle method.

That's exactly what I do for the canvas checkerboard.

 

I prepare a regular IBitmap<ColorPbgra32> with only the top-left 2x2 squares of the checkerboard. At 96 DPI this is only an 8x8 bitmap (I use the integer floor of the DPI/scaling factor in order to scale the 8x8 pixel size). Load it into an IDeviceBitmap (aka ID2D1Bitmap1). Then create an IBitmapBrush (aka ID2D1BitmapBrush) around that with InterpolationMode.NearestNeighbor.

 

I then do a using (dc.UseTransform(Matrix3x2Float.Identity, MatrixMultiplyOrder.Replace)) and then FillRectangle with the brush in the appropriate location.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

11 hours ago, Rick Brewster said:

I prepare a regular IBitmap<ColorPbgra32> with only the top-left 2x2 squares of the checkerboard. At 96 DPI this is only an 8x8 bitmap (I use the integer floor of the DPI/scaling factor in order to scale the 8x8 pixel size). Load it into an IDeviceBitmap (aka ID2D1Bitmap1). Then create an IBitmapBrush (aka ID2D1BitmapBrush) around that with InterpolationMode.NearestNeighbor.

 

Is it an 8x8 bitmap or 16x16?

I had to use 16x16 with the following code, otherwise it rendered as a solid color.

 

int dpiScaleFactor = (int)Math.Floor(UIScaleFactor.Current.Scale);
int size = 16 * dpiScaleFactor;

bitmap = new WICBitmapSurface<ColorPbgra32>(size, size, imagingFactory);

using (Surface checkerboardSurface = new(size, size))
{
    checkerboardSurface.ClearWithCheckerboardPattern();

    RegionPtr<ColorBgra> region = checkerboardSurface.AsRegionPtr();

    PixelKernels.SetAlphaChannel(region.Cast<ColorBgra32>(), ColorAlpha8.Opaque);

    RegionPtr<ColorPbgra32> premultiplied = region.Cast<ColorPbgra32>();

    PixelKernels.ConvertBgra32ToPbgra32(premultiplied);

    using (ISurfaceLock surfaceLock = bitmap.Lock(SurfaceLockMode.Write))
    {
        RegionPtr<ColorPbgra32> dst = new((ColorPbgra32*)surfaceLock.Buffer,
                                          surfaceLock.Width,
                                          surfaceLock.Height,
                                          surfaceLock.BufferStride);
        premultiplied.CopyTo(dst);
    }
}

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

4 hours ago, null54 said:

Is it an 8x8 bitmap or 16x16?

Right, it's 16x16 -- each square is 8x8. I misread my code.

 

You don't need to use ClearWithCheckerboardPattern(), which is a weird old method that doesn't give you, or allow you to provide, any layout or colorization information. Just clear with solid color (light or dark), then fill in the diagonal squares with the other color.

 

image.png

 

Also, there's no reason to use ConvertBgra32ToPbgra32() if the pixels are already opaque.

 

You can also use Direct2DPictureBox if you're working with a static image (or mostly static -- you can still update it with InvalidateBitmap(), but it will re-upload the whole thing to the GPU). It has an EnableAlphaCheckerboard property you can enable. You can derive from the class and override OnRenderBackground() and OnRenderForeground() to draw decorations below or above the bitmap.

 

You can also disassemble that class (e.g. ILSpy) and just recreate what it does. You won't be able to use AffineTransform2DEffect2, however, which means you'll need to limit your zooming/scaling so that you don't run into an IntermediateTooLargeException (AffineTransform2DEffect (non-2), and ScaleEffect, cannot scale below a certain size).

 

If necessary I can also look at providing some additional classes in PDN to help out with your scenario(s)

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

3 hours ago, Rick Brewster said:

You don't need to use ClearWithCheckerboardPattern(), which is a weird old method that doesn't give you, or allow you to provide, any layout or colorization information. Just clear with solid color (light or dark), then fill in the diagonal squares with the other color.

 

I used ClearWithCheckerboardPattern() because it was the only public API I could find that would allow a plugin get a checkerboard with the same colors as the native PDN one.

 

4 hours ago, Rick Brewster said:

You can also use Direct2DPictureBox if you're working with a static image (or mostly static -- you can still update it with InvalidateBitmap(), but it will re-upload the whole thing to the GPU). It has an EnableAlphaCheckerboard property you can enable. You can derive from the class and override OnRenderBackground() and OnRenderForeground() to draw decorations below or above the bitmap.

 

That would be the way to go for almost any other plugin, but in PSFilterPdn I am implementing a Photoshop plugin API that only provides the plugin host with the pixels to render and the destination HDC.

 

4 hours ago, Rick Brewster said:

If necessary I can also look at providing some additional classes in PDN to help out with your scenario(s)

 

As I mentioned above, PSFilterPdn only requires a checkerboard that matches the PDN canvas.

But an ICheckerboardRendererService would be useful for most of the tool plugins, perhaps something like:

public enum CheckerboardTileSize
{
    Small,
    Medium,
    Large
}

public interface ICheckerboardRendererService
{
    Render(RegionPtr<ColorBgra32> destination);
    // Other overloads as necessary
    Render(RegionPtr<ColorBgra32> destination, Point2Int32 srcOffset, ColorBgr32 light, ColorBgr32 dark, CheckerboardTileSize tileSize);
}

The single parameter overload would render the same checkerboard as the PDN canvas, with the other overloads allowing the plugin to customize the checkerboard.

The proposed API probably need some refinement, I was basing it off the methods in CheckerboardUtil.

 

Another API that would probably be useful for the various tool plugins that show their own dialogs (e.g. Shapemaker) is an ICanvasUIColorsService:

public interface ICanvasUIColorsService
{
    // The color surrounding the outside of the canvas
    ColorBgr32 Background { get; }
    
    ColorBgr32 CheckerboardDark { get; }
    
    ColorBgr32 CheckerboardLight { get; }
}

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...