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!


Sunday, 21 July 2013

TFireMonkeyContainer update: bugs fixed, features added

The example application showing a 3D FireMonkey form.
On the other tab is a standard 2D form.
On Wednesday I announced TFireMonkeyContainer, a VCL control that can host a FireMonkey form, allowing you to mix FireMonkey elements into your VCL app. It was (and is) a new project, and the announcement page listed several known bugs and possible future design changes.

Bugs: gone! Design changes: made! The current state is not quite perfect but is bug-free enough I feel happy about you using it in real applications, or at least trying it out. (With the caveat that I'm the only one who has tested it so far, and my entire QA department is me :) If you try it, please let me know how it goes.

 Bugs fixed

  • Focus now correctly follows the host VCL and hosted FMX forms
  • Window activation now correctly follows the host VCL and hosted FMX forms, when switching by clicking on the host VCL form, the hosted FMX form, via alt-tab, via the taskbar, and when there are several VCL forms and several hosted FMX forms in the one application.
  • The title bar of host VCL forms correctly changes when the form is active and inactive.
  • An exception is now thrown when two containers both try to host the same form.

Design changes

    This FireMonkey form embedded in the VCL form can't be
    edited in the VCL designer - it's a static preview. Switch
    to the FMX form tab to edit it.
  • Design-time (in the IDE): When a FireMonkey form is hosted in a VCL form in designtime, it shows an uneditable view of the hosted form. (This used to be an editable FireMonkey form, ie you could invoke the FireMonkey designer. This only partially worked and caused a lot of problems.) Switch tabs to the FireMonkey form's IDE design tab to edit the FireMonkey form. Changes will be reflected immediately when you switch back to the VCL form's design tab.
  • There are two new events you can use to control hosting the FireMonkey form. This means there are three ways to embed a FireMonkey form:
    1. Set the FireMonkeyForm property at design-time. (Not recommended, see 'Known problems' below.)
    2. Set the FireMonkeyForm property at runtime.
    3. Use the OnCreateFMXForm and OnDestroyFMXForm events.
      • OnCreateFMXForm fires when the component is first created. It has a var Form parameter which you can use to set the hosted FireMonkey form. If a form was specified at designtime, Form refers to it. If you change it, OnDestroyFMXForm will be called for the old value.
      • OnDestroyFMXForm fires when the component is destroyed. Much like a TForm's OnClose event, you can choose an action to apply to the hosted FMX form, eg to free it. The default is to do nothing, since it was probably created with an owner. The Destroy event also occurs when you change the Form parameter in the Create event (eg, when there was a FMX form set at design-time, but in the Create event you change it to another form.)

Known problems

  • When you set the FireMonkeyForm property at designtime, you can only set it to a FMX form that is open in a tab in the IDE - otherwise the IDE doesn't see it and won't show it in the dropdown in the Object Inspector. When the IDE tab is closed, the reference is invalid - and this means the FireMonkeyForm property is cleared. This might be due to problems mixing VCL and FMX forms in the one app, or (more likely) it might be due to a mistake I've made in how to correctly reference another form in a component. Suggested workaround: set the property at runtime, or use the OnCreateFMXForm and OnDestroyFMXForm events instead.

Notes, and getting the code

The component subclasses both the VCL form and the FMX form in order to catch the WM_NCACTIVATE (VCL form) and WM_ACTIVATE and WM_MOUSEACTIVATE (FMX form) messages. I'm not an expert in window activation and there may be bugs (or I might have just plain written bad code) in the  handlers. I'm also not certain I have hit on completely the right design for the events. I'd quite appreciate feedback if you use the component and feel that the events that occur are a bad design or force you to write ugly code. The same goes for any aspects of the component, really.

You can download the latest version from the Google code homepage and Subversion source checkout.

Wednesday, 17 July 2013

TFireMonkeyContainer - a VCL control for mixing VCL and FMX

I've created a small MPL-licensed component called TFireMonkeyContainer.  It's a VCL control that can host a FireMonkey form - 2D or 3D, it doesn't matter.

This will let you use FireMonkey's swishy graphics, animations, etc in an existing VCL application, either in a form among other controls, or if the host container is client-aligned then as though the whole window is a FireMonkey form (it just happens that the window chrome is a VCL form.)  As far as I know, while lots of people ask about mixing FireMonkey in a VCL app, there are no easy-to-use components which let you do so.  I hope, with some further development and bugfixes, this will do so.

Here's a screenshot from the example app:



How to use

  1. Drop a TFireMonkeyContainer control on a VCL form.
  2. Add a FMX form to your project if you haven't got one already (the IDE makes this difficult; you might need to create it in a separate FMX project and then add the unit to the VCL project.)
  3. At designtime, you can set the FireMonkeyForm property to an existing, auto-created FMX form. Note that for the IDE to see it, the form has to be open in a tab.
  4. At runtime, you can set the FireMonkeyForm property to a new instance of a FMX form.  Note that it doesn't take ownership of the form, so you will need to free the form yourself.  This is something that might change in future.

Known bugs

Yes, there definitely are known bugs.  This version is the result of only a couple of hours' work :)
  • The host form's title bar changes to paint as inactive when you click inside the hosted FMX form.  I plan to fix this by handling WM_NCACTIVATE and related messages; if you can think of a better way, by all means please let me know.
  • At designtime, you can't set the FireMonkeyForm property unless the FMX form is open in a tab in the IDE.
  • At designtime, the embedded FMX form draws a few pixels to the right and below where it should be.  You will see a border on the top and left sides of the controls.  I don't know why this is, and at runtime it is in the right place.
  • Designtime is fairly strange all around.  You can edit the hosted FMX control, such as by dragging a button around, but trying to do something like open a style editor causes lots of problems.  I'd recommend you regard it as view-only.

Possible future changes

This code is the result of one or two hours' work - it's early.  Please take that into account.  I thought it might be worth getting feedback on the component and see if it's useful before doing much more work.
That said, here are some possible future changes:
  • Make designtime view-only - don't let you edit the FMX form when it's displayed at designtime in the VCL editor, and just draw it there instead so you can see what it will look like.  This would probably make it a lot more stable.
  • Take ownership of the FMX form - set and forget.  (Currently, you have to delete the FMX form instance if you don't want a memory leak.)
  • Add two events for runtime form creation and deletion, so that when the component is created, you can handle an event and create a new FMX form, and when it is destroyed you can optionally do something with the FMX form before it is freed, or stop it being freed entirely.
I'm interested in other ideas too!

Credits

I saw some example code linked from this Delphi Sorcery post which inspired this component.  The code was DSharp.Windows.FMXAdapter.pas by Stefan Glienke.  My code is considerably expanded so I view it as a separate work - it solves some bugs (eg, removing flicker when the host VCL form is resized, hiding the phantom FMX application window, etc) and of course is a component.  Thanks go to those two pages for showing the basic technique!

Getting the code

You can grab the code via Subversion from Google Code here:
http://firemonkey-container.googlecode.com/svn/trunk/
or via the command line:
svn checkout http://firemonkey-container.googlecode.com/svn/trunk/ firemonkey-container-read-only

You will find two packages (one designtime, one runtime - both 32 and 64-bit) and one small example project.  It's only been tested with XE4 so far!

Have fun, and please contribute back any bugfixes you find.