How do you have nice-looking icons on the Delphi / C++Builder IDE splash screen? As regular readers of my blog or users of my plugins know, I care about details and appearance a lot. This is visible in the visual style of my plugins, but also carries through to the source code or design. The visual appearance of a product is the main or often only way we can judge the internal quality, and certainly the way we form our first impressions, and that matters.

The Delphi splash screen is visible to users for perhaps half a minute, but images are limited to bitmaps with one solid transparent colour, which means making splash images that look good on any background can be difficult. They often end up looking like they were made in 1995 or 2000: few colours and pixelated. That’s for a good reason! With a single transparent colour, it’s hard to make an image that renders by 2016 standards against any background; edges will always be hard and aliased.

Compare the Parnassus images to the JCL’s in Seattle (left) and Berlin (right):

In XE8 and below, the splash screen background was black, so you could create a bitmap antialiased against a black background in a paint program, but in Seattle and Berlin the splash screens have changed colour with both each version, and with each SKU: Delphi’s screen is a different colour to C++Builder’s, which is different to RAD Studio’s, and each is different in Seattle compared to Berlin. So how do you make images that look good?

When Seattle was released I didn’t realise each SKU had a different splash screen and so I made a nice bitmap for each plugin, antialiased against orange, which was the splash screen background I saw (I had both Delphi and C++Builder installed.) This looked terrible for those who had only Delphi installed because they had a blue splashscreen, and images antialiased against orange looked even worse than if I’d just left it against black. Oops.

This is the approach I currently take for Parnassus plugins.

PNGs, icons, or bitmaps?

The obvious approach is to use a PNG image. You can have a pre-rendered, antialiased, semitransparent image. Perfect, right?

There are two problems:

First, I don’t know of a way to use a PNG image directly as the splash screen image. There is no ToolsAPI method to add a splash screen icon using a TPngImage, only a TBitmap. (It might be easier if it had been a TGraphic.) This is the IOTASplashScreenServices declaration:

{ Any IDE plugin may provide an image to be displayed on the splash screen as the product is initializing. If AddPluginBitmap is called, AddProductBitmap should *NOT* be called or a duplicate entry will be displayed. The bitmap should be 24x24 pixels with the lower-left pixel indicating the transparent color. If IsUnRegistered is true, the caption will be painted red. LicenseStatus will be shown in parentheses after the caption. SKUName will be appended to the caption } procedure AddPluginBitmap(const ACaption: string; ABitmap: HBITMAP; AIsUnRegistered: Boolean = False; const ALicenseStatus: string = ''; const ASKUName: string = ''); 1 2 3 4 5 6 7 8 9 10 { Any IDE plugin may provide an image to be displayed on the splash screen as the product is initializing . If AddPluginBitmap is called , AddProductBitmap should * NOT * be called or a duplicate entry will be displayed . The bitmap should be 24x24 pixels with the lower - left pixel indicating the transparent color . If IsUnRegistered is true , the caption will be painted red . LicenseStatus will be shown in parentheses after the caption . SKUName will be appended to the caption } procedure AddPluginBitmap ( const ACaption : string ; ABitmap : HBITMAP ; AIsUnRegistered : Boolean = False ; const ALicenseStatus : string = '' ; const ASKUName : string = '' ) ;

That seems both very clear (one corner with a transparent pixel) and very much not a PNG image.

Second, using PNG images in a plugin, including DLL plugin, can cause incompatibilities for users who use PngComponents, preventing the PngComponents PNG library from registering in (from memory) XE2 or XE3. Because of this, I don’t use any PNG images – all of mine are either icons, bitmaps, or drawn in code at runtime – and I’d have to ifdef in or out the PNG code to make sure it wasn’t built in by accident in the plugin build for an older IDE version. Too much hassle. Better to do something else.

Updated 2016-05-11: Let’s try bitmaps. Stefan Glienke pointed out that a 32-bit bitmap works fine. Windows bitmaps can have an alpha channel, although they can be hard to create – neither Paint.Net nor Pixelmator generate them, for example. You can build one easily in code using my TTransparentCanvas library.

There are some problems, however:

My tests show that while fully transparent pixels are transparent, semitransparent ones don’t seem to blend correctly. Not sure what’s happening there; perhaps the wrong BLENDFUNC. I created a bitmap with a red rounded rectangle with 128 alpha, and on the splash screen, the area around it was transparent but the red roundrect drew as red – not as red blended over the blue splash screen background.

You can’t use a 32-bit bitmap as a resource, using the Delphi resource compiler (BRCC32). You get an error message like, “[BRCC32 Error] Images.rc(11): Invalid bitmap format”. Baoquan Zuo kindly told me via chat that this works if you replace the Borland resource compiler with the Microsoft one – but doing this seems a bit of a hack and not reliable (ie you can no longer guarantee you can build on any Delphi machine.) You could also include it as a DATA resource, but then using TBitmap.LoadFromResourceName won’t find it, so you would have to load it into a stream and load from there. Lots of fuss, and this is assuming you have access to a bitmap editor that writes out 32-bit bitmaps you can use in the first place.

So let’s strike PNG and BMP32 images off the list, and go back to using a plain TBitmap with a transparent corner pixel.

If you are happy building your bitmap in code, instead of loading from resource, using a 32-bit bitmap is completely okay. You could perhaps combine this with some of the techniques used below, such as using an icon as a reliable alpha-aware bitmap format, or assembling an image with TTransparentCanvas or Graphics32.

Blending against the splash screen colour

Let’s revisit the problem: the whole problem is that the background colour of the splash screen can change version to version and SKU to SKU. Why don’t we create an image at runtime, which is the plugin’s icon blended against that background colour?

The colour is not in the registry. However, at the time your plugin is loaded and you call the AddPluginBitmap method, the splash screen window exists. In all versions of the IDE so far, the area of the splash screen listing plugins is a solid colour. What about finding this colour?

This is in fact rather easy.

Finding the splash screen

The approach is simple: find the splash screen window. Get its image (as it’s rendered onscreen.) Look up the colour of a pixel in an area that is known to be in the plugin list area.

The splash screen happens to be a form with the caption ‘SplashScreen’ (convenient.) Since the application exists, you don’t even need to do any WinAPI window enumeration work to find a window with a specific caption or class – you can just iterate Screen.Forms. That makes both finding it and rendering it to a bitmap very easy:

function FindSplashScreenAsImage(IntoBMP : TBitmap) : Boolean; var I : Integer; begin Result := false; for I := 0 to Pred(Screen.FormCount) do if Screen.Forms[i].Caption = 'SplashScreen' then begin IntoBMP.Width := Screen.Forms[i].ClientWidth; IntoBMP.Height := Screen.Forms[i].ClientHeight; Screen.Forms[i].PaintTo(IntoBMP.Canvas.Handle, 0, 0); Exit(true); end; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 function FindSplashScreenAsImage ( IntoBMP : TBitmap ) : Boolean ; var I : Integer ; begin Result : = false ; for I : = 0 to Pred ( Screen . FormCount ) do if Screen . Forms [ i ] . Caption = 'SplashScreen' then begin IntoBMP . Width : = Screen . Forms [ i ] . ClientWidth ; IntoBMP . Height : = Screen . Forms [ i ] . ClientHeight ; Screen . Forms [ i ] . PaintTo ( IntoBMP . Canvas . Handle , 0 , 0 ) ; Exit ( true ) ; end ; end ;

Finding the background colour

The splash screen has several areas: the top area, which is where it says it’s Delphi / C++Builder, or RAD Studio; the bottom area, with the copyright and info about which package is currently being loaded; and in between, the area with the loaded product and plugin information.

The splash screen expands as more plugins are loaded, but the top and bottom areas stay the same size. That makes it quite easy to find a pixel that is in the plugin area, no matter how many plugins have currently been loaded. In this code, I use a percentage of the form height rather than a hardcoded pixel value to look up from the bottom of the form, in case the form is scaled (maybe because of high DPI.) At minimum, when the form is at its smallest size, the black area at the bottom takes up about 20% of the form height. So look above that plus a bit – this code looks 25% up from the bottom, and no matter how much the plugin area expands, that will always be in the plugin area. It also looks 4 pixels in from the left, the idea being that will be between the edge and any icons or text.

function GetSplashScreenBaseColor : TColor; var SplashBMP : TBitmap; begin Result := clBlack; // Reasonable default SplashBMP := TBitmap.Create; try // Find the colour a quarter of the way up - skips the black area (about 20%) // but is well into the area where icons are displayed if FindSplashScreenAsImage(SplashBMP) then Result := SplashBMP.Canvas.Pixels[4, SplashBMP.Height - (SplashBMP.Height div 4)]; finally SplashBMP.Free; end; end; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function GetSplashScreenBaseColor : TColor ; var SplashBMP : TBitmap ; begin Result : = clBlack ; // Reasonable default SplashBMP : = TBitmap . Create ; try // Find the colour a quarter of the way up - skips the black area (about 20%) // but is well into the area where icons are displayed if FindSplashScreenAsImage ( SplashBMP ) then Result : = SplashBMP . Canvas . Pixels [ 4 , SplashBMP . Height - ( SplashBMP . Height div 4 ) ] ; finally SplashBMP . Free ; end ; end ;

So, now we have the background colour of the splash screen, which gives us something to antialias or blend against. You can create a ‘blank’ splash screen image like so:

function CreateBlankSplashBitmap(const W, H : Integer) : TBitmap; var SplashColor, Fill, Edge : TColor; begin Result := TBitmap.Create; Result.PixelFormat := pf24bit; Result.Width := W; Result.Height := H; SplashColor := GetSplashScreenBaseColor; // Fill with the same colour as the splash screen - will be made transparent when // it's exactly this, and so anything antialiased to it will draw nicely Result.Canvas.Brush.Color := SplashColor; Result.Canvas.Brush.Style := bsSolid; Result.Canvas.FillRect(Rect(0, 0, Result.Width, Result.Height)); end; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function CreateBlankSplashBitmap ( const W , H : Integer ) : TBitmap ; var SplashColor , Fill , Edge : TColor ; begin Result : = TBitmap . Create ; Result . PixelFormat : = pf24bit ; Result . Width : = W ; Result . Height : = H ; SplashColor : = GetSplashScreenBaseColor ; // Fill with the same colour as the splash screen - will be made transparent when // it's exactly this, and so anything antialiased to it will draw nicely Result . Canvas . Brush . Color : = SplashColor ; Result . Canvas . Brush . Style : = bsSolid ; Result . Canvas . FillRect ( Rect ( 0 , 0 , Result . Width , Result . Height ) ) ; end ;

Adding the logo

The final step is adding the plugin or product logo. If we could use PNG images – and you can certainly do this if you wish – just draw the PNG to the ‘blank’ bitmap created above.

However, I avoid PNG images in my plugins for the reasons detailed above. What else is semitransparent and can be drawn by Delphi without any external libraries? An icon.

Thus, I load an icon from a resource, and draw it centered on the blank bitmap like so:

function CreateSplashBitmap(const Logo : TIcon) : TBitmap; begin Result := CreateBlankSplashBitmap(SplashSize, SplashSize); Result.Canvas.Draw((SplashSize div 2) - (Logo.Width div 2), (SplashSize div 2) - (Logo.Height div 2), Logo); end; 1 2 3 4 5 6 7 8 function CreateSplashBitmap ( const Logo : TIcon ) : TBitmap ; begin Result : = CreateBlankSplashBitmap ( SplashSize , SplashSize ) ; Result . Canvas . Draw ( ( SplashSize div 2 ) - ( Logo . Width div 2 ) , ( SplashSize div 2 ) - ( Logo . Height div 2 ) , Logo ) ; end ;

SplashSize is a constant that equals 24, because that’s what the ToolsAPI documentation says. Someday they might support a varying size bitmap because of high DPI and I want to be able to change the size of the bitmap I give the IDE easily.

And there you go! An antialiased, semitransparent logo on an IDE splash screen of any colour.

Other details

There are a few miscellaneous notes:

The Parnassus logos all have a circular background. That’s not in the icon or bitmap; I draw that using Graphics32. I like the look it gives.

When you load an icon from a resource, do not use LoadFromResource because of RSP-13912, which uses the wrong (a very old) Windows API to load the icon and only ever loads the 32×32 version, or scales any other size version to 32×32. Since 32×32 is bigger than the splash bitmap size of 24×24 you clearly don’t want this. Because of the circular background on my plugin bitmaps, I load a 16×16 icon, but you may want to load a 24×24 one – either way, avoid this method. Instead, use LoadImage manually. For example, if your icon has only one size like 16×16:

SplashIcon := TIcon.Create; SplashIcon.Handle := LoadImage(HInstance, 'ICONRESOURCENAME', IMAGE_ICON, 0, 0, 0); 1 2 SplashIcon : = TIcon . Create ; SplashIcon . Handle : = LoadImage ( HInstance , 'ICONRESOURCENAME' , IMAGE_ICON , 0 , 0 , 0 ) ;

The first two 0s are the size; 0 for the default, so I use this to load an icon that has only a 16×16 resource. If you have several sizes in the one icon you’ll want to specify the desired size manually, like so:

SplashIcon := TIcon.Create; SplashIcon.Handle := LoadImage(HInstance, 'ICONRESOURCENAME', IMAGE_ICON, 24, 24, 0); 1 2 SplashIcon : = TIcon . Create ; SplashIcon . Handle : = LoadImage ( HInstance , 'ICONRESOURCENAME' , IMAGE_ICON , 24 , 24 , 0 ) ;

Finally, remember that the bottom left pixel is the transparent colour. Nothing we’ve done has avoided that. Since the Parnassus plugins draw only in the center of the bitmap it’s fine, but if you load or blend a large image, make sure you do not draw over that pixel, or that the icon you draw over it is transparent on the bottom left, or manually paint the bottom left pixel to be the background colour at the end.

I kept second-guessing myself both when writing this article and when writing the original code for the plugins back in the Seattle era, thinking that there must already be a way to use a PNG or icon, and so is a pointless or silly approach! But so far as I know there is not. Please vote for RSP-14842, which is to do this, to expand the ToolsAPI to use a transparent PNG or icon on the splash screen.

I hope this helps you add nicer-looking images to the splash screen. Have fun!

Share this: LinkedIn

Facebook

Twitter

Reddit

Print



Discussions about this page on Google+