Showing posts with label alphablend. Show all posts
Showing posts with label alphablend. Show all posts

Wednesday, 24 July 2013

TTransparentCanvas: changing the background color of glowing text

You have probably already seen how to use DrawThemeTextEx on Vista and above to draw text with a white blurry 'glow' effect behind it. It's commonly used when drawing on glass, to ensure that text has enough background contrast to be easily readable. But the API only draws a white glow. What if you want another color?

The glow effect over a coloured background. What
if you want non-white glowing background?
I asked a question on Stack Overflow to see if it was possible to change the background glow colour - after all, the text colour and glow size are both options in the DTTOPTS structure; perhaps there was a way to make the border or shadow options affect the glow. But no-one answered, and from my own research / experiments it appears it's not possible through the API.

Instead, I've coded a method to do this into my TTransparentCanvas open-source library.

If you haven't seen the previous blog posts about it, TTransparentCanvas is a class (or set of classes) to draw transparent, blended shapes and text onto a bitmap or device context using TCanvas-like functions and normal VCL objects like TPen, TBrush, TFont etc. Shapes and text can be composed and blended over each other, and the final result then blended over an existing image.  The code aims to be easy to pick up and use if you are familiar with drawing with TCanvas, and is implemented with pure GDI - that is, no GDI+ or other external libraries are required.

How it's done

The following assumes you have read my previous articles about drawing transparent graphics with pure GDI (Part 1, Part 1½, and Part 2.) Specifically, you should know what premultiplied alpha is and be familiar with the record I use to represent a single transparent pixel, TQuadColor.
A quick explanatory note if you didn't go and meticulously pore over those links - which of course you did :) - is that premultiplied alpha changes the RGB representations of a pixel from 0-255 to that value scaled, or pre-multiplied, by the alpha channel, so with an alpha of 64, a full-white (normally (255, 255, 255)) pixel will have RGB values of (64, 64, 64). A grey pixel of (128, 128, 128) with an alpha value of 64 would have premultiplied RGB values of (32, 32, 32), which still represent the same colourIt's one of the steps done when blending transparent images together, and storing bitmaps in this format saves some calculations. TQuadColor is a simple record representing a 32-bit pixel as union or variant record of four bytes in ABGR order or a 32-bit Cardinal, along with some helpful methods.

TTransparentCanvas draws every shape or text onto an intermediate bitmap. This allows processing of the alpha of that shape before the result is blended onto the 'result' bitmap. This design allows every item drawn to have different transparencies, but also allows other intermediate processing before the drawn item is composed onto the final alpha-aware bitmap result.

It's this intermediate step we can take advantage of. If there's no way to change the background colour in the API, we can do it, effectively, as a post-processing stage by tinting the glowing pixels.

Examining the bitmap

Examining the glowing pixels in the debugger shows that the white transparent pixels are represented as pure white until they are completely transparent, but are of course in premultiplied alpha form. Thus, a pixel right at the edge of the glow may have the ABGR value (2, 2, 2, 2): that is, an alpha of 2, with a full-100%-white value (normally (255, 255, 255) premultiplied by the alpha to give each channel a value of 2.

What about the text? If drawn as black text, they will have full-alpha but black pixels, e.g. ABGR (255, 0, 0, 0.) However, text is anti-aliased: most parts of the text will not be fully black, but a colour between black and white, and it's not even guaranteed the text pixels will have full alpha.

Tinting these pixels

I initially considered tinting the pixels by drawing black text on the white glow, figuring out the relative "whiteness" and "blackness" of each pixel, and using that to interpolate between the user-specified glow and text colours. However, not only is this probably more processing than is required but sub-pixel antialiasing might result in non-uniformly-grey pixels.

A simpler solution is to tint all pixels, both glow and text, to the one colour, and then draw the text over again. Visually, this gives almost the same result with less per-pixel processing - a win.

(Note: the following quotes heavily from my Stack Overflow answer explaining the same material.)

To tint, one can ignore the existing colour of the pixel and instead set any non-zero-alpha pixels to a premultiplied alpha value using the user-specified background colour, based on the existing alpha of the pixel. Loop through your temporary bitmap and set the colour using the existing alpha as an intensity:

// PQuad is a pointer to the first pixel, a TQuadColor (see link, basically a packed struct of ABGR bytes)
for Loop := 0 to FWidth * FHeight - 1 do begin
  if PQuad.Alpha <> 0 then begin
    PQuad.SetFromColorMultAlpha(Color); // Sets the colour, and multiplies the alphas together
  end;
  Inc(PQuad);
end;

The key is PQuad.SetFromColorMultAlpha:

procedure TQuadColor.SetFromColorMultAlpha(const Color: TQuadColor);
var
  MultAlpha : Byte;
begin
  Red := Color.Red;
  Green := Color.Green;
  Blue := Color.Blue;
  MultAlpha := Round(Integer(Alpha) * Integer(Color.Alpha) / 255.0);
  SetAlpha(MultAlpha, MultAlpha / 255.0);
end;

This takes a quad colour (that is, alpha with RGB) and multiplies the two alphas together to get a resulting alpha. This lets you tint by a transparent colour to have the glow effect lessened by being partially transparent at its strongest point if you pass in a non-full-alpha color to tint with. The example application has a slider allowing you to see this in action.

SetAlpha is an existing method that converts to premultiplied alpha:

procedure TQuadColor.SetAlpha(const Transparency: Byte; const PreMult: Single);
begin
  Alpha := Transparency;
  Blue := Trunc(Blue * PreMult);
  Green := Trunc(Green * PreMult);
  Red := Trunc(Red * PreMult);
end;

Example of the result

What does this give you?

This image is the text 'Test glowing text with background color' tinted clLime:
A clLime-tinted glow
The final step is to draw the text over the top again, this time without any glow effect, giving this result:
...and with text drawn over the glow.
This means you can now draw text with any font color and any glow color:
clRed text with a clSkyBlue glow

But why stop there? Naturally, the next step is to experiment with custom-drawn title bars. The source demos include a small project heavily based on Chris Rolliston's excellent code to let you draw on a Vista+ title bar. (If you want to custom-draw on a title bar, please go read his article - it's very good - rather than basing your code on my quick-and-dirty hacked-in functionality, which was written only for the purpose of demonstrating using this code with the title bar.) That gives you results like this:

 

 

And of course, since TTransparentCanvas can draw to glass as easily as it can to a normal bitmap or DC, you can draw anything else on the title bar too.

Download

TTransparentCanvas is a MPL-licensed open source project hosted on Google Code. Get the source here. If you use it, have feature requests (such as drawing more shapes than are implemented so far) or if you have patches, please let me know. It has been tested with Delphi 2010, XE2 and XE4 and works when compiled to either 32 and 64-bit. Have fun!


Wednesday, 7 March 2012

Transparent graphics with pure GDI (Part 2) - and introducing the TTransparentCanvas class

This is part 2 of a short series that examines alpha-aware graphics using native GDI only - not GDI+, not DirectX, and not with any other custom non-GDI graphics implementation.

This post will cover:
  • Non-rectangular drawing
  • Handling GDI's clobbering of the alpha channel
  • Better use of per-pixel alpha (based on what's drawn, which is more useful than a horizontal gradient)
  • Introduction of the TTransparentCanvas class: a TCanvas-like class allowing you to draw rectangles, ellipses, text etc as you normally would, including using TPen, TBrush and TFont.  It composes the layers of drawing as you go, building a per-pixel alpha-aware image.  It also allows you to draw on glass - something VCL applications traditionally have trouble with.
Previous posts in the series:
If you're not very familiar with GDI and alpha transparency on Windows, including the intricacies of using AlphaBlend and its BLENDFUNCTION parameter, please (re-)read those before continuing.  Content in this post refers to classes and techniques introduced in the first two articles.

Tuesday, 8 November 2011

Transparent graphics with pure GDI (Part 1 1/2)

Last post, I said that the next part of my transparent graphics series would be Part 2, and would introduce a class I've written to simplify drawing to glass, drawing partially transparent shapes and text, etc.

I haven't posted this yet, simply because life has got in the way - since my last post, I spent a couple of weeks back home in Australia visiting friends and colleagues, and traveling a bit on the way back here.  (I went camel-riding.  They're surprisingly weird animals.)  I'm afraid I simply haven't had time to write the article and to polish the final bits of the library code.

Part 2 really is coming, and I'm sorry it's taking so long.  In the meantime, here is a quick post, Part 1 1/2, addressing a couple of questions asked in the comments for Part 1.

Thursday, 13 October 2011

Transparent graphics with pure GDI (Part 1)

Transparent graphics are used everywhere these days.  Half the UIs you will have used today will have made some use of composited graphics, overlays, or something similar.  You've probably seen:
  • Selections with transparent rectangles or other shapes
  • Text with a transparent shape behind it, so you can see the through to the background and still clearly read the text
  • Custom drawing on glass, such as text with the blurred white background and custom controls
How do you achieve all of these with Delphi or C++Builder?

This is part 1 of a short series that examines alpha-aware graphics using native GDI only - not GDI+, not DirectX, and not with any other custom non-GDI graphics implementation.  By the end of the series I will have introduced a small alpha-aware canvas class that you can use to draw standard shapes (rectangles, ellipses, etc) just as you can with the normal TCanvas (and using standard TFont, TPen and TBrush objects), to compose several layers of alpha blended graphics together, and to draw on glass on Vista and Windows 7.

This is a screenshot of the demo program which I used to test this class.  It's not the prettiest demo program ever, but you can see all of the above in action in this screenshot: