How to draw text with a shadow like Windows XP does on Desktop icons?
-
31-05-2021 - |
Question
Windows XP draws icon text with a nice shadow, which helps to read the text on various backgrounds. The font color is white and the shadow is black (if desktop background is white) or there is no shadow at all (if desktop background is black).
So there are two sub-tasks:
How the shadow gets drawn? It is not a simple x,y offset of the text; the shadow looks more like a blur to me.
How to make shadow to behave the way it becomes more visible on white backgrounds and less visible on dark?
I need a solution for GDI (not GDI+).
Solution
Please read Glow and Shadow Effects using Windows GDI.
OTHER TIPS
Inspired by Chris Becke's answer in this thread
If you need quick and not-so dirty solution, you may also want to do the following:
Draw a text on a black bitmap then alpha blend it to the primary hdc but: shift the destination rectangle -1 .. 2 by x and -1 .. 3 by y (i.e. several blends in a loop). To achieve shadow fading effect modify SourceConstantAlpha accordingly for outer blends (|x| > 1 or |y| > 1).
This is a rough method, feel free to experiment with details.
I am not sure about performance and visual quality aspects of such solution but it may be sufficient in certain cases.
Also see the DrawShadowText function here: Writing Transparent Text on Image
The function prototype is this: procedure DrawShadowText(aCanvas: TCanvas; CONST Text: string; CONST X, Y, Opacity: Integer; TextColor, ShadowColor: TColor);
It is ready to be used.
Yet another DrawShadowText function, this time based on MS Win API. Needs Windows Vista and up.
Source: https://www.delphipraxis.net/66678-drawshadowtext-delphi-commctrl-h-comctl32-dll-3.html
Here is the english version:
{-------------------------------------------------------------------------------------------------------------
Unit ShadowText by Matthias G. aka turboPASCAL
Version 1.0 (VCL)
Draws text with shadow (This unit provides the function DrawShadowText from the ComCtl32.dll)
Minimum req:
Windows Vista
Comctl32.dll v6
If running under XP or without Windows XP Manifest, a substitute function is used to display the shadow.
From the user manual: To use DrawShadowText, specify Comctl32.dll version 6 in the manifest. For more information on manifests, see Enabling Visual Styles.
Src: https://www.delphipraxis.net/66678-drawshadowtext-delphi-commctrl-h-comctl32-dll-3.html
API docs: https://docs.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-drawshadowtext
Tester: c:\Myprojects\Project Testers\gr cGraphUtil.pas\
-------------------------------------------------------------------------------------------------------------}
UNIT ShadowText;
INTERFACE
USES
Windows, SysUtils, System.Classes, Graphics;
VAR
// determines whether the alternative shadow should be used if the function from ComCtl32.dll is not available
UseLQShadow : Boolean = True;
// Output of a text with shadow by specifying X and Y coordinates
function DrawShadowText(ACanvas: TCanvas; x, y: Integer; AText: string; TextColor, ShadowColor: TColor; ShadowSpaceX, ShadowSpaceY: Integer): Integer;
// Output of a text with shadow over a TRect structure with specification of the text formatting (text flags see Delphi help "DrawText")
function DrawShadowTextR(ACanvas: TCanvas; TextRect: TRect; x, y: Integer; AText: string; TextColor, ShadowColor: TColor; ShadowSpaceX, ShadowSpaceY: Integer; TextFlags: DWord): Integer;
IMPLEMENTATION
TYPE
TDrawShadowTextI = function(hdc: HDC;
pszText: LPCWSTR;
cch: UINT;
const pRect: PRect;
dwFlags: DWORD;
crText: COLORREF;
crShadow: COLORREF;
ixOffset: Integer;
iyOffset: Integer): Integer; stdcall;
VAR
hComCtl32DLL: THandle = 0;
DrawShadowTextI: TDrawShadowTextI;
OldCanvasColor: TColor;
OldBkModeColor: Integer;
// DrawShadowText as declared in ComCtl32.dll ( requires WindowsXP-Manifest! )
// function DrawShadowText_(hdc: HDC; pszText: LPCWSTR; cch: UINT; const pRect: PRect; dwFlags: DWORD; crText: COLORREF; crShadow: COLORREF; ixOffset: Integer; iyOffset: Integer): Integer; stdcall; external 'ComCtl32.dll' name 'DrawShadowText';
// Determines whether a Windows version from WinXP on is available.
function IsWindowsXPAndUp: Boolean;
begin
Result := ((Win32MajorVersion = 5) and (Win32MinorVersion >= 1)) or (Win32MajorVersion > 5);
end;
// DrawShadowTextL (L - Low Quality)
function DrawShadowTextL(ACanvas: TCanvas; TextRect: TRect; AText: string;
TextColor, ShadowColor: TColor; ShadowSpaceX,
ShadowSpaceY: Integer; TextFlags: DWORD): Integer;
begin
OldBkModeColor := SetBKMode(ACanvas.Handle, TRANSPARENT);
OldCanvasColor := ACanvas.Font.Color;
if UseLQShadow
then
begin
inc(TextRect.Left, ShadowSpaceX);
inc(TextRect.Top, ShadowSpaceY);
inc(TextRect.Right, ShadowSpaceX);
inc(TextRect.Bottom, ShadowSpaceY);
ACanvas.Font.Color := ShadowColor;
Result := DrawText(ACanvas.Handle, PChar(AText), length(AText), TextRect, TextFlags);
dec(TextRect.Left, ShadowSpaceX);
dec(TextRect.Top, ShadowSpaceY);
dec(TextRect.Right, ShadowSpaceX);
dec(TextRect.Bottom, ShadowSpaceY);
end
else
Result := 1;
ACanvas.Font.Color := TextColor;
Result := Result AND DrawText(ACanvas.Handle, PChar(AText), length(AText), TextRect, TextFlags);
ACanvas.Font.Color := OldCanvasColor;
SetBKMode(ACanvas.Handle, OldBkModeColor);
end;
// DrawShadowText: für einfache Ausgabe
function DrawShadowText(ACanvas: TCanvas; x, y: Integer; AText: string; TextColor, ShadowColor: TColor; ShadowSpaceX, ShadowSpaceY: Integer): Integer;
VAR TextRect: TRect;
begin
TextRect := RECT(x, y, x + ACanvas.TextWidth(AText), y + ACanvas.TextHeight(AText));
if @DrawShadowTextI <> nil
then Result := DrawShadowTextI(ACanvas.Handle, PWideChar(WideString(AText)), length(AText), @TextRect, 0, COLORREF(TextColor), COLORREF(ShadowColor), ShadowSpaceX, ShadowSpaceY)
else Result := DrawShadowTextL(ACanvas, TextRect, AText, TextColor, ShadowColor, ShadowSpaceX, ShadowSpaceY, 0);
end;
// DrawShadowTextR: for formatted output (R - Text[R]ect)
function DrawShadowTextR(ACanvas: TCanvas; TextRect: TRect; x, y: Integer; AText: string; TextColor, ShadowColor: TColor; ShadowSpaceX, ShadowSpaceY: Integer; TextFlags: DWord): Integer;
begin
if @DrawShadowTextI <> NIL
then Result := DrawShadowTextI(ACanvas.Handle, PWideChar(WideString(AText)), length(AText), @TextRect, TextFlags, COLORREF(TextColor), COLORREF(ShadowColor), ShadowSpaceX, ShadowSpaceY)
else Result := DrawShadowTextL(ACanvas, TextRect, AText, TextColor, ShadowColor, ShadowSpaceX, ShadowSpaceY, TextFlags);
end;
initialization
if IsWindowsXPAndUp then
begin
hComCtl32DLL := LoadLibrary('ComCtl32.dll');
@DrawShadowTextI := GetProcAddress(hComCtl32DLL, 'DrawShadowText');
end;
finalization
if hComCtl32DLL <> 0
then FreeLibrary(hComCtl32DLL);
end.