There is nothing wrong with your television set. Do not attempt to adjust the picture.
27 May 2008
Finally the part you’ve probably been waiting for: How do we replace the uncompressed bitmap with a nicely compressed PNG bitmap?
One alternative to GraphicEx is Gustavo Daud’s PNGDelphi library. Unfortunately this library was removed from SourceForge while I was writing this article, but according to rumors it will likely appear again soon.
Since Delphi doesn’t include native support for the PNG format (yet) we have to use a third-party library to load the PNG image. Luckily Mike Lischke’s open source GraphicsEx library does the job nicely so going back to PhotoShop, we can now save our bitmap as a PNG (make sure you save it with an alpha channel) and modify the application to load the PNG instead:
GraphicEx
unit:
··· implementation uses GraphicEx; ···
I have included a copy of GraphicEx in the .\GraphicEx
sub folder of the sample source, but you can also download the original yourself. Remember to add the GraphicEx folder to the project search path.
If you don’t plan to use any of the other graphic formats supported by GraphicEx, I suggest you modify the GraphicConfiguration.inc
file to disable everything but PNG support to save space. Basically only the PortableNetworkGraphic
conditional should be defined.
splash.rc
resource script to include the PNG bitmap:
1 2 | // Name Type Filename SPLASH RCDATA "splash.png" |
TPNGGraphic
object instead of a TBitmap
:
··· // Bitmap := TBitmap.Create; Bitmap := TPNGGraphic.Create; try ···
Because TPNGGraphic
descends from TBitmap
we can leave the rest of the code alone.
Using a compressed bitmap gives us a considerable smaller footprint and while some of this saving is offset by the added PNG support code, we still save enough space to make it worthwhile.
Using alpha transparent PNG bitmaps also has the benefit that they are much easier to create.
Another way to support PNG bitmaps is with the aid of GDI+. GDI+ is an object oriented library that enhances and encapsulates GDI with support for 2D vector graphics, imaging and typography. GDI+ is shipped with Windows XP and later but is also available as a separate redistributable for older systems. If you read through Microsoft’s description of GDI+ you might get the impression that GDI+ will replace GDI completely and that GDI will soon become obsolete:
As its name suggests, GDI+ is the successor to Windows Graphics Device Interface (GDI), the graphics device interface included with earlier versions of Windows. Windows XP or Windows Server 2003 supports GDI for compatibility with existing applications, but programmers of new applications should use GDI+ for all their graphics needs because GDI+ optimizes many of the capabilities of GDI and also provides additional features.
This nonsense resembles the FUD Microsoft spread to scare developers into abandoning native Win32 development and move to .NET. The truth is that GDI+ is just a framework layer on top of GDI and not a very nicely designed framework at that.
Anyway, back in the real world; GDI+ has support for the regular Windows graphic formats (BMP, ICO, WMF, EMF, EMF+) as well as the most widely used raster formats: GIF, JPEG, PNG, TIFF and Exif. For our purpose we only need GDI+ to read PNG files.
GDI+ provides its own set of image classes and while it would be perfectly feasible to use these directly, it is beyond the scope of this tutorial. Instead I use a GDI+ wrapper library. There are a few to chose from but only Prodigy’s GDI+ wrapper implement the GdipCreateHBITMAPFromBitmap
API function which we need.
In order to use GDI+ instead of GraphicEx we need to make a few changes:
GraphicEx
unit and include the GdipApi
, GdipObj
and ActiveX
units instead:
··· implementation uses GdipApi, GdipObj, ActiveX; ···
The GDI+ wrapper available from Prodigy’s site doesn’t support newer versions of Delphi, so I suggest you just use the version I have bundled with the sample source. The GDI+ library is in the .\GDI+
sub folder so go ahead and add that to the project search path.
TFormSplash.Execute
method to use GDI+:
procedure TFormSplash.Execute; var Stream: TStream; PNGBitmap: TGPBitmap; BitmapHandle: HBITMAP; StreamAdapter: IStream; Bitmap: TBitmap; ··· begin ··· Bitmap := TBitmap.Create; try // Load the PNG from a resource Stream := TResourceStream.Create(HInstance, 'SPLASH', RT_RCDATA); try // Wrap the VCL stream in a COM IStream StreamAdapter := TStreamAdapter.Create(Stream); try // Create and load a GDI+ bitmap from the stream PNGBitmap := TGPBitmap.Create(StreamAdapter); try // Convert the PNG to a 32 bit GDI bitmap PNGBitmap.GetHBITMAP(MakeColor(0,0,0,0), BitmapHandle); // Wrap the bitmap in a VCL TBitmap Bitmap.Handle := BitmapHandle; finally PNGBitmap.Free; end; finally StreamAdapter := nil; end; finally Stream.Free; end; ASSERT(Bitmap.PixelFormat = pf32bit, 'Wrong bitmap format - must be 32 bits/pixel'); // Perform run-time premultiplication PremultiplyBitmap(Bitmap); ···
That should be it, but if you run the above code you will probably end up in the debugger’s CPU view on an INT3
break point within ntdll.dll
. The reason is that there’s a small bug in Delphi’s TStreamAdapter
class.
It seems that the bug only manifests itself when run in the debugger, and only as a break point, but I recommend you fix it anyway.
The problem with TStreamAdapter
is in its implementation of the IStream.stat
method. The stat
method takes two parameters: A STATSTG
out parameter and a STATFLAG
value. The STATFLAG
value specifies if the stat
method should return a value in the STATSTG.pwcsName
member. If it does return a value, it is the responsibility of the called object (i.e. TStreamAdapter
) to allocate memory for the string value, and the responsibility of the caller (i.e. GDI+) to deallocate the string. Now TStreamAdapter.stat
completely ignores the STATFLAG
parameter, which is understandable because it doesn’t know anything about filenames, but unfortunately it also fails to zero the STATSTG.pwcsName
member. The result is that the caller (GDI+ in this case) receives an invalid string pointer. When GDI+ later dutifully calls coTaskMemFree
to deallocate the string, Windows objects and stops our application with a break point.
Luckily the bug is very easy to work around:
TFormSplash.Execute
:
type TFixedStreamAdapter = class(TStreamAdapter) public function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; override; stdcall; end; function TFixedStreamAdapter.Stat(out statstg: TStatStg; grfStatFlag: Integer): HResult; begin Result := inherited Stat(statstg, grfStatFlag); statstg.pwcsName := nil; end;
TFormSplash.Execute
to use the new class and Bob’s your uncle:
··· // Wrap the VCL stream in a COM IStream StreamAdapter := TFixedStreamAdapter.Create(Stream); ···
[Update 2008-05-28] It seems the problem is known:
QC 45528: Potential issue in TStreamAdapter.Stat implementation
Remember that if you use GDI+ and plan to support Windows 2000 or earlier, you should distribute
gdiplus.dll
together with your application. Also note that the correct location for your copy ofgdiplus.dll
is in the same folder as your application, not thesystem32
folder.
The choice between GraphicEx and GDI+ is up to you; One is not better than the other. GraphicEx has the advantage that you can compile it into your application so you don’t have to rely on a DLL which may or may not be present on the target system. GDI+ on the other hand has the advantage that it relieves your application of the PNG support code.
I suggest that you just choose which ever feels best to you or if you are already using one or the other, stick with that.
Please take note that both GraphicEx and Prodigy’s GDI+ wrapper are licensed under the Mozilla Public License (MPL).
For the final touch I will add a fade effect to our splash form. The UpdateLayeredWindow
API we use already provides the means to specify an alpha value to be applied on the entire bitmap; Namely the SourceConstantAlpha
member of the BLENDFUNCTION
parameter. This overriding alpha value is applied in addition to the per-pixel alpha values specified by the bitmap itself.
If we set SourceConstantAlpha
to 0 (zero), the form becomes completely transparent. If we set the value to 255 it becomes completely opaque, still with respect to the per-pixel alpha values. So in order to fade the form in or out we simply specify an increasing or decreasing sequence of SourceConstantAlpha
values:
procedure TFormSplash.Execute; var Ticks: DWORD; ··· begin ··· // Setup alpha blending parameters BlendFunction.BlendOp := AC_SRC_OVER; BlendFunction.BlendFlags := 0; BlendFunction.SourceConstantAlpha := 0; // Start completely transparent BlendFunction.AlphaFormat := AC_SRC_ALPHA; Show; // ... and action! Ticks := 0; while (BlendFunction.SourceConstantAlpha < 255) do begin while (Ticks = GetTickCount) do Sleep(10); // Don't fade too fast inc(BlendFunction.SourceConstantAlpha, (255-BlendFunction.SourceConstantAlpha) div 32+1); // Fade in UpdateLayeredWindow(Handle, 0, nil, @BitmapSize, Bitmap.Canvas.Handle, @BitmapPos, 0, @BlendFunction, ULW_ALPHA); end; ···
I’ll better explain what’s going on inside the loop.
The GetTickCount
stuff throttles the speed of the fade so it doesn’t progress too quickly on a fast machine. Since GetTickCount
has a resolution of approximately 16mS and the loop iterates 85 times [handwave], the whole fade takes at least 1.4 seconds (85*16mS) to complete. Use a multimedia timer (the timeGetTime
function in the mmSystem
unit) if you need better resolution.
Instead of just increasing the opacity in a linear fashion with a sequence of equal steps:
inc(BlendFunction.SourceConstantAlpha);
we start with a large step and continue with increasingly smaller steps using a simple algorithm that is known as exponential slide when applied to motion:
inc(BlendFunction.SourceConstantAlpha, (255-BlendFunction.SourceConstantAlpha) div 32+1);
The idea behind an exponential slide is that with each step, we halve the remaining distance. In this case we accelerate the slide by dividing the remaining distance by 32 instead of 2. The +1 is to avoid getting trapped in Zeno’s dichotomy paradox. In my opinion the exponential slide gives the fade a much more organic feel.
One bad side effect of the above implementation is that it uses busy waiting and in effect adds almost 2 useless seconds to your application startup time. If this is a problem you should move the whole loop into a low priority thread, but that, as they say, is left as an exercise to the reader.
That’s all for this time. I hope you enjoyed reading this and learned a bit in the process. I certainly did.
Please take a moment to rate the article and leave a comment to let me know what you think.
The source code that accompany this article are known to be compatible with the following versions of Delphi:
D1 | D2 | D3 | D4 | D5 | D6 | D7 | D2005 | D2006 | D2007 |
This work is licensed under a
Creative Commons Attribution-Share Alike 3.0 Unported License.
Download: | Alpha Blended Splash Form - Part 2 |
---|---|
Version: | 1.1 |
Updated: | 26 June, 2008 |
Size: | 477.05 KB |
Notes: | Incudes source and sample images. |
Downloads: | 14,739 |
thanks for this i have been wondering for a long time how to do it, but i think you are missing the
GraphicColor
files from your zip.great work tho thanks heaps
You're right.
The
GraphicEx\GraphicColor.pas
unit was missing. I've uploaded a new version which includes the missing file.Thanks.
Hi Anders
This is certainly one of the best tutorials I have ever seen regarding the idea, so thank you for that.
All my applications now use these alpha-blended splash windows, in PNG format (because it's so much easier). It really adds a great taste to the application. I have, however, stumbled accross a problem (my own problem, that is). I would like to start making widgets (similar to that of Yahoo!™ Widget Engine) in the near future, as well as glass toasters for my applications. Do you know how to add VCL controls to these alpha-blended windows?
Any help would really be appreciated. Thanks very much for putting time aside to show us this.
Michael
Hi, my apologies - you already answered my question in Part 1 of this article.
AlphaControls gives me the ability to have PNG based controls in my applications, but does not do the same to it's forms. If it does, however, my pocket does not match the dollar signs dancing around the Prices page of their website. I'm also based in South Africa, and don't have all these nausiating (spelling sucks, I think) accounts. Is there any way that you know of to get the effect and functionality of this widget?
Hi i've had the same problem but a programmer helped to me and i want to direct you to solution of this problem (using this way into real program that has child controls) please download this file, everything is clear but don't forget explanations are Turkish… And owner of sources said that, this is a simple generic example and it has no some other abilities (such as coloration)..
Link:
rapidshare.com/files/170827754/Generic_Clock.rar[edit: link dead]Hej Anders
I just tried this in Delphi 2009 and it cannot compile. I get several errors in MZLib and GraphicEx. Any chance you tried it?
Kind regards, Claus
Same for me here. Looks like GraphicEx doesn't work for D2009. I tried to fix the code but with no luck, so I switched to GDI+ and it works.
Delphi 2009 comes with PNG support.
Add
PNGImage
to your uses,Use
TPNGImage
instead ofTPNGGraphic
, although it does not descend fromTBitmap
, so you'll have to redo some of the code to support this.I tried modifying the code to use TPNGImage in Delphi 2009, but I guess I am missing something… The code compiles, but the image is never shown. Is there anyone out there that would post here the changes that have to be made in order to use TPNGImage in D2009? I thank you in advance!
Does anyone would know how to write with TLabel or TMemo on top of the transparant PNG image? My goal is to report loading progress during the display of the splash screen.
Haven't found a way to do that. We use the following code to draw licensing information on top of the form (you can't directly use
canvas.TextOut()
because it draws with the alpha channel set to 0, so you need to use GDI+ functions with the{$define PNG_GDIPLUS}
definition enabled). It's not completely self-explanatory, but hope it helps:This looks very helpful - thanks for the post. I will try it as soon as I have my new computer.
Been looking for this for ages. Now I'm assuming any image will do the trick? Another semi-transparent image over the alpha layer?
Hi All,
I also had compilation errors, however came up with my own solution. I decided to create two forms that become attached to each other. The first form is a frameless content holder. It holds all the data you need to display on a splash screen. The second uses the above-mentioned techniques to create a glassy border.
I will post the result soon.
I am also in the process of updating my website (inniosoft.netii.net), which is still new. Once done, the QuickShutdown program will be uploaded.
Regards
Michael Rockett
Thank you for this great tutorial, have been looking for something like this since the fail that was Winamp 3 came out, with it's alpha blended skins =)
Anyhow, I'm really looking forward to seeing how you will go about to attach a normal borderless form to an alpha blended one, in order to be able to use standard vcl components. I haven't managed to find a good enough solution for that.
Thanks
/Daniel
Thank you for this great tutorial, have been looking for something like this since the fail that was Winamp 3 came out, with it's alpha blended skins =)Anyhow, I'm really looking forward to seeing how you will go about to attach a normal borderless form to an alpha blended one, in order to be able to use standard vcl components. I haven't managed to find a good enough solution for that.
Thanks
/Daniel
Hi Daniel
I am in the process of completing my website, which is still static, but will contain everything soon. I am still in the process of developing my software, this of which - once completed - you’ll be able to download. The only programmes that use the method mention above are those listed in the Shareware section. It can still be downloaded for free, as the splash screen will be shown. If you haven’t purchased it, then that’s all you’ll see.
I’ll keep updates running.
Regards,
Michael
Hi Daniel (and others)
The website has been updated, however I have not uploaded any new software on to it. Daniel, I will create a full tutorial regarding the two forms that are used soon. The simple concept is this that the two forms do not collide. One uses the Alpha Blended Splash Screen tutorial (above), while the other does not. Once these forms are positioned correctly, the effect is provided. This method doesn't cater for what you may see with the Yahoo! Widget Engine - but if you wanted that particular effect, you would have to download a third-party component that generates such a form.
I'm sure there are methods that can be used within WinAPI that can call a procedure that creates the Alpha Blended form without layering it. From what I can tell, the software on Lee-Soft's website does not use layering as it is captured in a screen-shot. Layered windows are excluded from the screen-shot.
I find Delphi limited in this regard - and suggest using a third-party application for that reason.
I hope I have helped in some way
Regards,
Michael
Compiled under Delphi 7, running the demo app under Vista shows the window frame around the splash screen. Any thoughts on getting rid if this?
Thanks!
Setting the BorderStyle to bsNone resolved this problem.
Thanks Tim, I found the same problem (only shows up when Aero is enabled, which unfortunately it wasn't when testing under VMWare).
My website has been updated, and the download links for my applications that use the method from Part 1 are there to download, under the QuickDownloads section in the side bar.
To complete this great tutorial, it would be fine to upload the solution for windows-controls on Alpha-Blended Forms.
The download-Link, Alper Demir provided, is no longer working (RapidShare-Limit).
Is someone able to post his solution (or upload the file again) ?
Many Thanks!
Hi again Michael,
Have you had the time to compose the tutorial about placing a non-blended form ontop a blended one, making it possible to use standard vcl-components?
Really looking forward to that!
/Daniel
Hi Daniel
Unfortunately not - I recently had a PC crash and was not able to back up my data.
The purpose of that was to make splash screen dynamic. I had not found a way to make the forms movable at the same time.
There is, however, a handy set of utilities that you can use. I had the web bookmark for it, but I have lost it due to the crash. It's a set of flash components that enable you to have alpha blended forms. Problem with that is you wouldn't be able to fully utilise the Delphi language as you'd be using flash.
Furthermore, and unfortunately, I am not going to be continuing to use alpha forms as it is becoming highly tedious.
Kind Regards, and sorry I couldn't help.
Michael
Hi Daniel
I have found the link to the flash utilities:
http://www.almdev.com/
It's called SmartFlash. It isn't free - but it works well
Hello, I haven't work with PNG in Delphi, so I'm looking for advices about next problem: I have to show picture over picture, but I can't manage with the transparent spots, so the below one to be visible where the above should be transparent. I tried with Image component and pnglibrary, but still have no solution.
I don't know what that has to do with the article you're commenting on but
TImage
andTPngImage
support transparent PNGs just fine.Thanks, i really appreciate this tutorial.
Hi! I wonder if it is possible to use into a form with buttons? Thanks
Thanks for professional tutorial. It was really what I needed for my free desktop widgets engine.
You can download the Delphi TPNGImage from various sources. Like http://code.google.com/p/livreerp/downloads/list
Excellent article! Well written and to the point. It is a bit outdated though and I think more people could benefit if it was updated for Delphi 2010 etc. I Googled for quite some time before I found this article.
For example Embarcadero has now purchased to PNGImage source code and it is now included in the VCL from Delphi 2009 so there's no need for 3rd party libraries.
http://blogs.embarcadero.com/n...../13/39100/
For me, this solution worked - without GDI+:
I copy the ARGB image to a temporary bitmap and draw text on this bitmap using normal Canvas methods. These methods, however, always draw transparent objects, so I set the alpha channel for all areas where I drew on the image to 255 afterwards. This works for me because the areas of the image where I put my text on are not transparent (it is a splash screen with transparent borders):
… thans for the great article, anyway.