{ @abstract(This unit contains advanced graphic functions used by KControls suite.) @author(Tomas Krysl (tk@tkweb.eu)) @created(5 May 2004) @lastmod(20 Jun 2010) Copyright © 2004 Tomas Krysl (tk@@tkweb.eu)

License:
This code is distributed as a freeware. You are free to use it as part of your application for any purpose including freeware, commercial and shareware applications. The origin of this source code must not be misrepresented; you must not claim your authorship. You may modify this code solely for your own purpose. Please feel free to contact the author if you think your changes might be useful for other users. You may distribute only the original package. The author accepts no liability for any damage that may result from using this code. } unit KGraphics; {$include kcontrols.inc} {$WEAKPACKAGEUNIT ON} interface uses {$IFDEF FPC} // use the LCL interface support whenever possible {$IFDEF USE_WINAPI} Windows, {$ENDIF} GraphType, IntfGraphics, LCLType, LCLIntf, LMessages, LResources, {$ELSE} Windows, Messages, {$IFDEF USE_PNG_SUPPORT} PngImage, {$ENDIF} {$ENDIF} Classes, Forms, Graphics, Controls, KFunctions; resourcestring { @exclude } SGDIError = 'GDI object could not be created.'; const { PNG Support } PNGHeader = #137'PNG'#13#10#26#10; MNGHeader = #138'MNG'#13#10#26#10; type { Declares possible values for the Style parameter of the @link(BrightColor) function. } TKBrightMode = ( { The Color will be brightened with Percent of its entire luminosity range. } bsAbsolute, { The Color will be brightened with Percent of its current luminosity value. } bsOfBottom, { The Color will be brightened with Percent of the difference of its entire luminosity range and current luminosity value. } bsOfTop ); { Declares RGB + Alpha channel color description allowing both to access single channels and the whole color item. } TKColorRec = packed record case Integer of 0: (R, G, B, A: Byte); 1: (Value: Cardinal); end; { Pointer to TKColorRec. } PKColorRec = ^TKColorRec; { Dynamic array for TKColorRec. } TKColorRecs = array[0..MaxInt div SizeOf(TKColorRec) - 1] of TKColorRec; { Dynamic array for TKColorRecs. } PKColorRecs = ^TKColorRecs; { Dynamic array for TKColorRec. } TKDynColorRecs = array of TKColorRec; { String type for @link(ImageByType) function. } TKImageHeaderString = string[10]; {$IFDEF USE_PNG_SUPPORT} {$IFDEF FPC} { @exclude } TKPngImage = TPortableNetworkGraphic; {$ELSE} {$IFDEF COMPILER12_UP} { @exclude } TKPngImage = TPngImage; {$ELSE} { @exclude } TKPngImage = TPngObject; {$ENDIF} {$ENDIF} {$ENDIF} { Declares possible values for the Attributes parameter in the @link(DrawAlignedText) function. } TKTextAttribute = ( { Bounding rectangle is calculated. No text is drawn. } taCalcRect, { Text will be clipped within the given rectangle. } taClip, { Text will be drawn with end ellipsis if it does not fit within given width. } taEndEllipsis, { Given rectangle will be filled. } taFillRect, { Only yhe text within given rectangle will be filled. } taFillText, { Text will be drawn as multi-line text if it contains carriage returns and line feeds. } taLineBreak, { Text will be drawn with path ellipsis if it does not fit within given width. } taPathEllipsis, { Text line(s) will be broken between words if they don't fit within given width. } taWordBreak, { Text line(s) will be broken if they don't fit within col width. } taWrapText, //JR:20091229 { No white spaces will be trimmed at the beginning or end of text lines. } taTrimWhiteSpaces ); { Set type for @link(TKTextAttribute) enumeration. } TKTextAttributes = set of TKTextAttribute; { Declares possible values for the HAlign parameter in the @link(DrawAlignedText) function. } TKHAlign = ( { Text is aligned to the left border of a cell rectangle. } halLeft, { Text is horizontally centered within the cell rectangle. } halCenter, { Text is aligned to the right border of a cell rectangle. } halRight ); { Declares possible values for the StretchMode parameter in the @link(ExcludeShapeFromBaseRect) function. } TKStretchMode = ( { Shape is not stretched. } stmNone, { Shape is zoomed out. } stmZoomOutOnly, { Shape is zoomed in. } stmZoomInOnly, { Shape is zoomed arbitrary. } stmZoom ); { For backward compatibility. } TKTextHAlign = TKHAlign; { Declares possible values for the VAlign parameter in the @link(DrawAlignedText) function. } TKVAlign = ( { Text is aligned to the upper border of a cell rectangle. } valTop, { Text is vertically centered within the cell rectangle. } valCenter, { Text is aligned to the lower border of a cell rectangle. } valBottom ); { For backward compatibility. } TKTextVAlign = TKVAlign; { A simple platform independent encapsulation for a 32bpp bitmap with alpha channel with the ability to modify it's pixels directly. } TKAlphaBitmap = class(TGraphic) private FCanvas: TCanvas; FDirectCopy: Boolean; FHandle: HBITMAP; FHeight: Integer; {$IFNDEF USE_WINAPI} FImage: TLazIntfImage; // Lazarus only FMaskHandle: HBITMAP; {$ENDIF} FOldBitmap: HBITMAP; FPixels: PKColorRecs; FPixelsChanged: Boolean; FWidth: Integer; function GetScanLine(Index: Integer): PKColorRecs; function GetHandle: HBITMAP; function GetPixel(X, Y: Integer): TKColorRec; procedure SetPixel(X, Y: Integer; Value: TKColorRec); protected { Paints itself to ACanvas at location ARect. } procedure Draw(ACanvas: TCanvas; const ARect: TRect); override; { Returns True if bitmap is empty. } function GetEmpty: Boolean; override; { Returns the bitmap height. } function GetHeight: Integer; override; { Returns True. Treat alpha bitmap as transparent because of the possible alpha channel. } function GetTransparent: Boolean; override; { Returns the bitmap width. } function GetWidth: Integer; override; { Specifies new bitmap height. } procedure SetHeight(Value: Integer); override; { Specifies new bitmap width. } procedure SetWidth(Value: Integer); override; { Does nothing. Bitmap is never transparent. } procedure SetTransparent(Value: Boolean); override; { Updates the bitmap handle from bitmap pixels. } procedure UpdateHandle; dynamic; { Updates the pixels from bitmap handle. } procedure UpdatePixels; dynamic; public { Creates the instance. } constructor Create; override; { Creates the instance from application resources. For Lazarus 'BMP' type is taken, for Delphi RT_RCDATA is taken. } constructor CreateFromRes(const ResName: string); { Destroys the instance. } destructor Destroy; override; { Paints alpha bitmap onto Canvas at position given by X, Y. The alpha bitmap is combined with the background already drawn on Canvas using alpha channel stored in the alpha bitmap. } procedure AlphaDrawTo(ACanvas: TCanvas; X, Y: Integer); { Paints alpha bitmap onto Canvas at position given by ARect. The alpha bitmap is combined with the background already drawn on Canvas using alpha channel stored in the alpha bitmap. } procedure AlphaStretchDrawTo(ACanvas: TCanvas; const ARect: TRect); { Fills the alpha channel with Alpha. If the optional IfEmpty parameter is True, the alpha channel won't be modified unless it has zero value for all pixels. } procedure AlphaFill(Alpha: Byte; IfEmpty: Boolean = False); overload; { Fills the alpha channel according to given parameters. Currently it is used internally by @link(TKDragWindow). } procedure AlphaFill(Alpha: Byte; BlendColor: TColor; Gradient, Translucent: Boolean); overload; { Combines the pixel at given location with the given color. } procedure CombinePixel(X, Y: Integer; Color: TKColorRec); { Takes dimensions and pixels from ABitmap. } procedure CopyFrom(ABitmap: TKAlphaBitmap); { Takes 90°-rotated dimensions and pixels from ABitmap. } procedure CopyFromRotated(ABitmap: TKAlphaBitmap); { Copies a location specified by ARect from ACanvas to bitmap. } procedure DrawFrom(ACanvas: TCanvas; const ARect: TRect); { Calls @link(TKAlphaBitmap.Draw). } procedure DrawTo(ACanvas: TCanvas; const ARect: TRect); {$IFNDEF FPC} { Does nothing. } procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; APalette: HPALETTE); override; {$ENDIF} { Loads the bitmap from a stream. } procedure LoadFromStream(Stream: TStream); override; { Mirrors the bitmap pixels horizontally. } procedure MirrorHorz; { Mirrors the bitmap pixels vertically. } procedure MirrorVert; {$IFNDEF FPC} { Does nothing. } procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; var APalette: HPALETTE); override; {$ENDIF} { Saves the bitmap to a stream. } procedure SaveToStream(Stream: TStream); override; { Specifies the bitmap size. } procedure SetSize(AWidth, AHeight: Integer); {$IFNDEF FPC} reintroduce;{$ENDIF} { Returns the bitmap memory canvas. } property Canvas: TCanvas read FCanvas; { Temporary flag. Use when copying data directly from another TGraphic to TKAlphaBitmap. } property DirectCopy: Boolean read FDirectCopy write FDirectCopy; { Returns the bitmap handle. } property Handle: HBITMAP read GetHandle; { Specifies the pixel color. Does range checking. } property Pixel[X, Y: Integer]: TKColorRec read GetPixel write SetPixel; { Returns the pointer to bitmap pixels. } property Pixels: PKColorRecs read FPixels; { Set this property to True if you have modified the bitmap pixels. } property PixelsChanged: Boolean read FPixelsChanged write FPixelsChanged; { Returns the pointer to a bitmap scan line. } property ScanLine[Index: Integer]: PKColorRecs read GetScanLine; end; {$IFDEF USE_WINAPI} TUpdateLayeredWindowProc = function(Handle: THandle; hdcDest: HDC; pptDst: PPoint; _psize: PSize; hdcSrc: HDC; pptSrc: PPoint; crKey: COLORREF; pblend: PBLENDFUNCTION; dwFlags: DWORD): Boolean; stdcall; {$ENDIF} { @abstract(Encapsulates the drag window) Drag window is top level window used for dragging with mouse. It displays some portion of associated control. It can be translucent under Windows. } TKDragWindow = class(TObject) private FActive: Boolean; FAlphaEffects: Boolean; FBitmap: TKAlphaBitmap; FBitmapFilled: Boolean; FControl: TCustomControl; FGradient: Boolean; FInitialPos: TPoint; FLayered: Boolean; FMasterAlpha: Byte; {$IFDEF USE_WINAPI} FBlend: TBlendFunction; FUpdateLayeredWindow: TUpdateLayeredWindowProc; FWindow: HWND; {$ELSE} FDragForm: TCustomForm; {$ENDIF} public { Creates the instance. } constructor Create; { Destroys the instance. } destructor Destroy; override; { Shows the drag window on screen. Takes a rectangular part as set by ARect from IniCtrl's Canvas and displays it at position InitialPos. MasterAlpha and Gradient are used to premaster the copied image with a specific fading effect. } procedure Show(IniCtrl: TCustomControl; const ARect: TRect; const InitialPos, CurrentPos: TPoint; MasterAlpha: Byte; Gradient: Boolean); { Moves the drag window to a new location. } procedure Move(const NewPos: TPoint); { Hides the drag window. } procedure Hide; { Returns True if the drag window is shown. } property Active: Boolean read FActive; { Returns the pointer to the bitmap that holds the copied control image. } property Bitmap: TKAlphaBitmap read FBitmap; { Returns True if the control already copied itself to the bitmap. } property BitmapFilled: Boolean read FBitmapFilled; end; { @abstract(Base class for KControls hints) This class extends the standard THintWindow class. It adds functionality common to all hints used in KControls. } TKHintWindow = class(THintWindow) private FExtent: TPoint; procedure WMEraseBkGnd(var Msg: TLMessage); message LM_ERASEBKGND; public { Creates the instance. } constructor Create(AOwner: TComponent); override; { Shows the hint at given position. This is an IDE independent implementation. } procedure ShowAt(const Origin: TPoint); { Returns the extent of the hint. } property Extent: TPoint read FExtent; end; { @abstract(Hint window to display formatted text) This class implements the textual hint window. The text is displayed . } TKTextHint = class(TKHintWindow) private FText: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}; procedure SetText(const Value: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}); protected { Overriden method. Paints the hint. } procedure Paint; override; public { Creates the instance. } constructor Create(AOwner: TComponent); override; { } property Text: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF} read FText write SetText; end; TKGraphicHint = class(TKHintWindow) private FGraphic: TGraphic; procedure SetGraphic(const Value: TGraphic); protected { Overriden method. Paints the hint. } procedure Paint; override; public constructor Create(AOwner: TComponent); override; property Graphic: TGraphic read FGraphic write SetGraphic; end; { Draws Src to Dest with per pixel weighting by alpha channel saved in Src. } procedure BlendLine(Src, Dest: PKColorRecs; Count: Integer); { Calculates a brighter color of given color based on the HSL color space. } function BrightColor(Color: TColor; Percent: Single; Mode: TKBrightMode = bsAbsolute): TColor; { Returns current canvas window/wiewport scaling. } procedure CanvasGetScale(ACanvas: TCanvas; out MulX, MulY, DivX, DivY: Integer); { Selects the default window/wiewport scaling to given canvas for both axes. } procedure CanvasResetScale(ACanvas: TCanvas); { Returns True if the ACanvas's device context has been mapped to anything else than MM_TEXT. } function CanvasScaled(ACanvas: TCanvas): Boolean; { Selects the window/wiewport scaling to given canvas for both axes. } procedure CanvasSetScale(ACanvas: TCanvas; MulX, MulY, DivX, DivY: Integer); { Selects the wiewport offset to given canvas for both axes. } procedure CanvasSetOffset(ACanvas: TCanvas; OfsX, OfsY: Integer); { Makes a grayscale representation of the given color. } function ColorToGrayScale(Color: TColor): TColor; { Calls BitBlt. } procedure CopyBitmap(DestDC: HDC; DestRect: TRect; SrcDC: HDC; SrcX, SrcY: Integer); { Creates an empty rectangular region. } function CreateEmptyRgn: HRGN; { Draws Text to the Canvas at location given by ARect. HAlign and VAlign specify horizontal resp. vertical alignment of the text within ARect. HPadding and VPadding specify horizontal (both on left and right side) and vertical (both on top and bottom side) padding of the Text from ARect. BackColor specifies the fill color for brush gaps if a non solid Brush is defined in Canvas. Attributes specift various text output attributes. } procedure DrawAlignedText(Canvas: TCanvas; var ARect: TRect; HAlign: TKHAlign; VAlign: TKVAlign; HPadding, VPadding: Integer; const AText: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}; BackColor: TColor = clWhite; Attributes: TKTextAttributes = []); { Simulates WinAPI DrawEdge with customizable colors. } procedure DrawEdges(Canvas: TCanvas; const R: TRect; HighlightColor, ShadowColor: TColor; Flags: Cardinal); { Draws a rectangle to Canvas. The rectangle coordinates are given by Rect. The rectangle is filled by Brush. If Brush is not solid, its gaps are filled with BackColor. If BackColor is clNone these gaps are not filled and the Brush appears transparent. } procedure DrawFilledRectangle(Canvas: TCanvas; const ARect: TRect; BackColor: TColor); { This helper function excludes a rectangular area occupied by a shape from BaseRect and calculates the shape area rectangles Bounds and Interior. The shape area is specified by the shape extent (ShapeWidth and ShapeHeight), padding (HPadding and VPadding) and stretching mode (StretchMode). The returned Bounds includes (possibly stretched) shape + padding, and Interior includes only the (possibly stretched) shape. HAlign specifies the horizontal alignment of shape area within BaseRect. VAlign specifies the vertical alignment of shape area within BaseRect. The shape area is always excluded horizontally from BaseRect, as needed by cell data calculations in KGrid. } procedure ExcludeShapeFromBaseRect(var BaseRect: TRect; ShapeWidth, ShapeHeight: Integer; HAlign: TKHAlign; VAlign: TKVAlign; HPadding, VPadding: Integer; StretchMode: TKStretchMode; out Bounds, Interior: TRect); { Selects ARect into device context. Returns previous clipping region. } function ExtSelectClipRect(DC: HDC; ARect: TRect; Mode: Integer; out PrevRgn: HRGN): Boolean; { Selects ARect into device context. Combines with CurRgn and returns previous clipping region. Both regions have to be created first. } function ExtSelectClipRectEx(DC: HDC; ARect: TRect; Mode: Integer; CurRgn, PrevRgn: HRGN): Boolean; { Fills the area specified by the difference Boundary - Interior on ACanvas with current Brush. If Brush is not solid, its gaps are filled with BackColor. If BackColor is clNone these gaps are not filled and the Brush appears transparent. } procedure FillAroundRect(ACanvas: TCanvas; const Boundary, Interior: TRect; BackColor: TColor); { Selects the region into given device context and deletes the region. } procedure FinalizePrevRgn(DC: HDC; ARgn: HRGN); { Determine the height (ascent + descent) of the font currently selected into given DC. } function GetFontHeight(DC: HDC): Integer; { Raises an exception if GDI resource has not been created. } function GDICheck(Value: Integer): Integer; { Creates a TGraphic instance according to the image file header. Currently supported images are BMP, PNG, MNG, JPG, ICO. } function ImageByType(const Header: TKImageHeaderString): TGraphic; { Calls the IntersectClipRect function. } function IntersectClipRectIndirect(DC: HDC; ARect: TRect): Boolean; { Determines if given color has lightness > 0.5. } function IsBrightColor(Color: TColor): Boolean; { Loads a custom mouse cursor. } procedure LoadCustomCursor(Cursor: TCursor; const ResName: string); { Builds a TKColorRec structure. } function MakeColorRec(R, G, B, A: Byte): TKColorRec; { Returns a pixel format that matches Bpp. } function PixelFormatFromBpp(Bpp: Cardinal): TPixelFormat; { In Lazarus this WinAPI function is missing. } function RectInRegion(Rgn: HRGN; ARect: TRect): Boolean; { Paints an image so that it fits in ARect. Performs double buffering and fills the background with current brush for mapped device contexts. } procedure SafeStretchDraw(ACanvas: TCanvas; ARect: TRect; AGraphic: TGraphic; ABackColor: TColor = clWhite); { Selects ARect as new clipping region into the device context. } procedure SelectClipRect(DC: HDC; const ARect: TRect); { Calls StretchBlt. } procedure StretchBitmap(DestDC: HDC; DestRect: TRect; SrcDC: HDC; SrcRect: TRect); { Swaps the color format from RGB to BGR and vice versa. } function SwitchRGBToBGR(Value: TColor): TColor; { Subtracts the current device context offset to ARect. } procedure TranslateRectToDevice(DC: HDC; var ARect: TRect); implementation uses Math, SysUtils, Types, KControls {$IFDEF FPC} , FPImage {$ELSE} , JPeg {$ENDIF} ; procedure BlendLine(Src, Dest: PKColorRecs; Count: Integer); var I: Integer; R, G, B, A1, A2: Integer; begin // without assembler for I := 0 to Count - 1 do begin A1 := Src[I].A; A2 := 255 - A1; Inc(A1); Inc(A2); R := Src[I].R * A1 + Dest[I].R * A2; G := Src[I].G * A1 + Dest[I].G * A2; B := Src[I].B * A1 + Dest[I].B * A2; Dest[I].R := R shr 8; Dest[I].G := G shr 8; Dest[I].B := B shr 8; end; end; function CalcLightness(Color: TColor): Single; var X: TKColorRec; begin X.Value := ColorToRGB(Color); Result := (X.R + X.G + X.B) / (3 * 256); end; function BrightColor(Color: TColor; Percent: Single; Mode: TKBrightMode): TColor; var L, Tmp: Single; function Func1(Value: Single): Single; begin Result := Value * (L + Percent) / L; end; function Func2(Value: Single): Single; begin Result := 1 - (0.5 - Tmp) * (1 - Value) / (1 - L); { this is the shorter form of Value := 1 - 0.5 * (1 - Value) / (1 - L) ; // get color with L = 0.5 Result := 1 - (0.5 - Tmp) * (1 - Value) / 0.5; // get corresponding color } end; function Rd(Value: Single): Byte; begin Result := Min(Integer(Round(Value * 255)), 512); end; var R, G, B, Cmax, Cmin: Single; X: TKColorRec; begin X.Value := ColorToRGB(Color); R := X.R / 255; G := X.G / 255; B := X.B / 255; Cmax := Max(R, Max(G, B)); Cmin := Min(R, Min(G, B)); L := (Cmax + Cmin) / 2; if L < 1 then begin case Mode of bsOfBottom: Percent := L * Percent; bsOfTop: Percent := (1 - L) * Percent; end; Percent := Min(Percent, 1 - L); if L = 0 then begin // zero length singularity R := R + Percent; G := G + Percent; B := B + Percent; end else begin Tmp := L + Percent - 0.5; // lumination below 0.5 if L < 0.5 then begin // if L + Percent is >= 0.5, get color with L = 0.5 Percent := Min(Percent, 0.5 - L); R := Func1(R); G := Func1(G); B := Func1(B); L := 0.5; end; // lumination above 0.5 if Tmp > 0 then begin R := Func2(R); G := Func2(G); B := Func2(B); end; end; X.R := Rd(R); X.G := Rd(G); X.B := Rd(B); end; Result := X.Value; end; procedure CanvasGetScale(ACanvas: TCanvas; out MulX, MulY, DivX, DivY: Integer); {$IFDEF USE_DC_MAPPING} var WindowExt, ViewPortExt: TSize; {$ENDIF} begin {$IFDEF USE_DC_MAPPING} if Boolean(GetWindowExtEx(ACanvas.Handle, {$IFDEF FPC}@{$ENDIF}WindowExt)) and Boolean(GetViewPortExtEx(ACanvas.Handle, {$IFDEF FPC}@{$ENDIF}ViewPortExt)) then begin DivX := WindowExt.cx; DivY := WindowExt.cy; MulX := ViewPortExt.cx; MulY := ViewPortExt.cy; end else {$ENDIF} begin MulX := 1; DivX := 1; MulY := 1; DivY := 1; end; end; procedure CanvasResetScale(ACanvas: TCanvas); begin {$IFDEF USE_DC_MAPPING} SetMapMode(ACanvas.Handle, MM_TEXT); {$ENDIF} end; function CanvasScaled(ACanvas: TCanvas): Boolean; begin {$IFDEF USE_DC_MAPPING} Result := not (GetMapMode(ACanvas.Handle) in [0, MM_TEXT]); {$ELSE} Result := False; {$ENDIF} end; procedure CanvasSetScale(ACanvas: TCanvas; MulX, MulY, DivX, DivY: Integer); begin {$IFDEF USE_DC_MAPPING} SetMapMode(ACanvas.Handle, MM_ANISOTROPIC); SetWindowExtEx(ACanvas.Handle, DivX, DivY, nil); SetViewPortExtEx(ACanvas.Handle, MulX, MulY, nil); {$ELSE} {$WARNING 'Device context window/viewport transformations not working!'} {$ENDIF} end; procedure CanvasSetOffset(ACanvas: TCanvas; OfsX, OfsY: Integer); begin {$IFDEF USE_DC_MAPPING} SetMapMode(ACanvas.Handle, MM_ANISOTROPIC); SetViewPortOrgEx(ACanvas.Handle, OfsX, OfsY, nil); {$ENDIF} end; function ColorToGrayScale(Color: TColor): TColor; var GreyValue: Integer; X: TKColorRec; begin X.Value := ColorToRGB(Color); GreyValue := (X.R + X.G + X.B) div 3; X.R := GreyValue; X.G := GreyValue; X.B := GreyValue; Result := X.Value; end; procedure CopyBitmap(DestDC: HDC; DestRect: TRect; SrcDC: HDC; SrcX, SrcY: Integer); begin {$IFDEF USE_WINAPI}Windows.{$ENDIF}BitBlt(DestDC, DestRect.Left, DestRect.Top, DestRect.Right - DestRect.Left, DestRect.Bottom - DestRect.Top, SrcDC, 0, 0, SRCCOPY); end; function CreateEmptyRgn: HRGN; begin Result := CreateRectRgn(0,0,0,0); end; procedure DrawAlignedText(Canvas: TCanvas; var ARect: TRect; HAlign: TKHAlign; VAlign: TKVAlign; HPadding, VPadding: Integer; const AText: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}; BackColor: TColor; Attributes: TKTextAttributes); var DC: HDC; FontHeight: Integer; ClipRect: TRect; function MeasureOrOutput(Y: Integer; Output: Boolean): TSize; var EndEllipsis, PathEllipsis: Boolean; Width, EllipsisWidth: Integer; function TextExtent(AText: {$IFDEF STRING_IS_UNICODE}PChar{$ELSE}PWideChar{$ENDIF}; ALen: Integer; Trim: Boolean = False): TSize; begin if Trim then begin if taLineBreak in Attributes then TrimWhiteSpaces(AText, ALen, cLineBreaks); if taTrimWhiteSpaces in Attributes then TrimWhiteSpaces(AText, ALen, cWordBreaks); end; {$IFDEF STRING_IS_UNICODE} {$IFDEF FPC} {$IFDEF USE_CANVAS_METHODS} Result := Canvas.TextExtent(Copy(AText, 0, ALen)); // little slower but more secure in Lazarus {$ELSE} GetTextExtentPoint32(DC, AText, ALen, Result); {$ENDIF} {$ELSE} GetTextExtentPoint32(DC, AText, ALen, Result); {$ENDIF} {$ELSE} GetTextExtentPoint32W(DC, AText, ALen, Result); {$ENDIF} end; procedure FmtTextOut(Y: Integer; AText: {$IFDEF STRING_IS_UNICODE}PChar{$ELSE}PWideChar{$ENDIF}; ALen: Integer); var DrawEllipsis, DrawFileName: Boolean; AWidth, Index, NewIndex,SlashPos, FileNameLen, EllipsisMaxX, X: Integer; S: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}; begin DrawEllipsis := False; DrawFileName := False; SlashPos := 0; FileNameLen := 0; if taLineBreak in Attributes then TrimWhiteSpaces(AText, ALen, cLineBreaks); if taTrimWhiteSpaces in Attributes then TrimWhiteSpaces(AText, ALen, cWordBreaks); if (EndEllipsis or PathEllipsis) and (ALen > 1) then begin AWidth := TextExtent(AText, ALen).cx; if AWidth > Width then begin AWidth := 0; Index := 0; if EndEllipsis then begin EllipsisMaxX := Width - EllipsisWidth; while (Index < ALen) do begin NewIndex := StrNextCharIndex(AText, Index); Inc(AWidth, TextExtent(@AText[Index], NewIndex - Index).cx); if (AWidth > EllipsisMaxX) and (Index > 0) then Break else Index := NewIndex; end; ALen := Index; DrawEllipsis := True; end else if PathEllipsis then begin SlashPos := ALen; while (SlashPos > 0) and not CharInSetEx(AText[SlashPos], ['/', '\']) do Dec(SlashPos); if SlashPos > 0 then begin DrawEllipsis := True; DrawFileName := True; FileNameLen := ALen - SlashPos; EllipsisMaxX := Width - TextExtent(@AText[SlashPos], FileNameLen).cx - EllipsisWidth; while (Index < SlashPos) do begin NewIndex := StrNextCharIndex(AText, Index); Inc(AWidth, TextExtent(@AText[Index], NewIndex - Index).cx); if AWidth > EllipsisMaxX then Break else Index := NewIndex; end; ALen := Index; end; end; end; end; if DrawEllipsis then begin if DrawFileName then begin S := Copy(AText, 0, ALen) + cEllipsis + Copy(AText, SlashPos + 1, FileNameLen); end else S := Copy(AText, 0, ALen) + cEllipsis; AText := {$IFDEF STRING_IS_UNICODE}PChar{$ELSE}PWideChar{$ENDIF}(S); ALen := Length(S); end; case HAlign of halCenter: X := Max(ClipRect.Left, (ClipRect.Left + ClipRect.Right - TextExtent(AText, ALen).cx) div 2); halRight: X := ClipRect.Right - TextExtent(AText, ALen).cx; else X := ClipRect.Left; end; {$IFDEF STRING_IS_UNICODE} {$IFDEF FPC} {$IFDEF USE_CANVAS_METHODS} Canvas.TextOut(X, Y, Copy(AText, 0, ALen)); // little slower but more secure in Lazarus {$ELSE} TextOut(DC, X, Y, AText, ALen); {$ENDIF} {$ELSE} TextOut(DC, X, Y, AText, ALen); {$ENDIF} {$ELSE} TextOutW(DC, X, Y, AText, ALen); {$ENDIF} end; var I, Index, TextLen, LineBegin, LineBreaks, Vert: Integer; CalcRect, WordBreak, LineBreak, WhiteSpace, PrevWhiteSpace, FirstWord, WrapText: Boolean; Size: TSize; begin Result.cx := 0; Vert := Y; if AText <> '' then begin LineBegin := 1; LineBreaks := 0; TextLen := Length(AText); Width := ClipRect.Right - ClipRect.Left; CalcRect := taCalcRect in Attributes; WordBreak := taWordBreak in Attributes; LineBreak := taLineBreak in Attributes; WrapText := taWrapText in Attributes; //JR:20091229 if Output then begin EndEllipsis := taEndEllipsis in Attributes; PathEllipsis := taPathEllipsis in Attributes; EllipsisWidth := TextExtent(cEllipsis, Length(cEllipsis)).cx; end; if WordBreak or LineBreak then begin I := LineBegin; Index := LineBegin; WhiteSpace := True; FirstWord := True; while I <= TextLen + 1 do begin PrevWhiteSpace := WhiteSpace; WhiteSpace := CharInSetEx(AText[I], cWordBreaks + cLineBreaks); if (not PrevWhiteSpace and WhiteSpace and (I > LineBegin)) or (not PrevWhiteSpace and WrapText and (I > LineBegin)) then //JR:20091229 begin if (WordBreak or WrapText) and (LineBreaks = 0) and not FirstWord then begin Size := TextExtent(@AText[LineBegin], I - LineBegin, True); if Size.cx > Width then Inc(LineBreaks); end; if LineBreaks > 0 then begin if Index > LineBegin then begin if Output and (Vert >= ClipRect.Top - FontHeight) and (Vert <= ClipRect.Bottom) then FmtTextOut(Vert, @AText[LineBegin], Index - LineBegin) else if CalcRect then Result.cx := Max(Result.cx, TextExtent(@AText[LineBegin], Index - LineBegin, True).cx); LineBegin := Index; end; Inc(Vert, FontHeight * LineBreaks); LineBreaks := 0; end; Index := I; FirstWord := False; end; if LineBreak and (AText[I] = cCR) then Inc(LineBreaks); Inc(I); end; end; if LineBegin <= TextLen then begin if Output and (Vert >= ClipRect.Top - FontHeight) and (Vert <= ClipRect.Bottom) then FmtTextOut(Vert, @AText[LineBegin], TextLen - LineBegin + 1) else if CalcRect then Result.cx := Max(Result.cx, TextExtent(@AText[LineBegin], TextLen - LineBegin + 1, True).cx); Inc(Vert, FontHeight * (1 + LineBreaks)); end; end; Result.cy := Vert - Y; end; procedure Initialize; begin ClipRect := ARect; InflateRect(ClipRect, -HPadding, -VPadding); DC := Canvas.Handle; FontHeight := GetFontHeight(DC); end; var Y: Integer; TmpRect: TRect; Extent: TSize; PrevRgn: HRGN; begin if taCalcRect in Attributes then begin Initialize; Extent := MeasureOrOutput(0, False); ARect.Right := ARect.Left + Extent.cx; ARect.Bottom := ARect.Top + Extent.cy; end else if not IsRectEmpty(ARect) then begin if taFillRect in Attributes then DrawFilledRectangle(Canvas, ARect, BackColor); if AText <> '' then begin Initialize; if not IsRectEmpty(ClipRect) then begin case VAlign of valCenter: Y := Max(ClipRect.Top, (ClipRect.Bottom + ClipRect.Top - MeasureOrOutput(0, False).cy) div 2); valBottom: Y := ClipRect.Bottom - MeasureOrOutput(0, False).cy; else Y := ClipRect.Top; end; TmpRect := ClipRect; if taClip in Attributes then begin TranslateRectToDevice(DC, TmpRect); if ExtSelectClipRect(DC, TmpRect, RGN_AND, PrevRgn) then try if not (taFillText in Attributes) then SetBkMode(DC, TRANSPARENT); MeasureOrOutput(Y, True); finally FinalizePrevRgn(DC, PrevRgn); end; end else begin if not (taFillText in Attributes) then SetBkMode(DC, TRANSPARENT); MeasureOrOutput(Y, True); end; end; end; end; end; procedure DrawEdges(Canvas: TCanvas; const R: TRect; HighlightColor, ShadowColor: TColor; Flags: Cardinal); begin with Canvas do begin Brush.Style := bsSolid; Brush.Color := HighlightColor; if Flags and BF_LEFT <> 0 then FillRect(Rect(R.Left, R.Top + 1, R.Left + 1, R.Bottom)); if Flags and BF_TOP <> 0 then FillRect(Rect(R.Left, R.Top, R.Right, R.Top + 1)); Brush.Color := ShadowColor; if Flags and BF_RIGHT <> 0 then FillRect(Rect(R.Right - 1, R.Top + 1, R.Right, R.Bottom)); if Flags and BF_BOTTOM <> 0 then FillRect(Rect(R.Left + 1, R.Bottom - 1, R.Right - 1, R.Bottom)); end; end; procedure DrawFilledRectangle(Canvas: TCanvas; const ARect: TRect; BackColor: TColor); var DC: HDC; begin DC := Canvas.Handle; SetBkMode(DC, OPAQUE); SetBkColor(DC, ColorToRGB(BackColor)); FillRect(DC, ARect, Canvas.Brush.Handle); end; procedure ExcludeShapeFromBaseRect(var BaseRect: TRect; ShapeWidth, ShapeHeight: Integer; HAlign: TKHAlign; VAlign: TKVAlign; HPadding, VPadding: Integer; StretchMode: TKStretchMode; out Bounds, Interior: TRect); var MaxHeight, MaxWidth, StretchHeight, StretchWidth: Integer; RatioX, RatioY: Single; begin MaxHeight := BaseRect.Bottom - BaseRect.Top - 2 * VPadding; MaxWidth := BaseRect.Right - BaseRect.Left - HPadding; if ((MaxWidth <> ShapeWidth) or (MaxHeight <> ShapeHeight)) and ( (StretchMode = stmZoom) or (StretchMode = stmZoomInOnly) and (MaxWidth >= ShapeWidth) and (MaxHeight >= ShapeHeight) or (StretchMode = stmZoomOutOnly) and ((MaxWidth < ShapeWidth) or (MaxHeight < ShapeHeight)) ) then begin RatioX := MaxWidth / ShapeWidth; RatioY := MaxHeight / ShapeHeight; if RatioY >= RatioX then begin StretchWidth := MaxWidth; StretchHeight := ShapeHeight * StretchWidth div ShapeWidth; end else begin StretchHeight := MaxHeight; StretchWidth := ShapeWidth * StretchHeight div ShapeHeight; end; end else begin StretchHeight := ShapeHeight; StretchWidth := ShapeWidth; end; Bounds := BaseRect; Interior := BaseRect; case HAlign of halLeft: begin Inc(BaseRect.Left, StretchWidth + HPadding); // Bounds.Left remains unchanged Bounds.Right := BaseRect.Left; Inc(Interior.Left, HPadding); end; halCenter: begin BaseRect.Right := BaseRect.Left; // BaseRect empty, no space for next item! // Bounds remains unchanged Inc(Interior.Left, HPadding + (MaxWidth - StretchWidth) div 2); end; halRight: begin Dec(BaseRect.Right, StretchWidth + HPadding); Bounds.Left := BaseRect.Right; // Bounds.Right remains unchanged Interior.Left := BaseRect.Right; end; end; Interior.Right := Interior.Left + StretchWidth; case VAlign of valTop: Inc(Interior.Top, VPadding); valCenter: Inc(Interior.Top, VPadding + (MaxHeight - StretchHeight) div 2); valBottom: Interior.Top := BaseRect.Bottom - VPadding - StretchHeight; end; Interior.Bottom := Interior.Top + StretchHeight; end; function ExtSelectClipRect(DC: HDC; ARect: TRect; Mode: Integer; out PrevRgn: HRGN): Boolean; var TmpRgn: HRGN; begin PrevRgn := CreateEmptyRgn; GetClipRgn(DC, PrevRgn); TmpRgn := CreateEmptyRgn; try Result := ExtSelectClipRectEx(DC, ARect, Mode, TmpRgn, PrevRgn) finally DeleteObject(TmpRgn); end; end; function ExtSelectClipRectEx(DC: HDC; ARect: TRect; Mode: Integer; CurRgn, PrevRgn: HRGN): Boolean; var RectRgn: HRGN; begin RectRgn := CreateRectRgnIndirect(ARect); try Result := CombineRgn(CurRgn, PrevRgn, RectRgn, Mode) <> NULLREGION; if Result then SelectClipRgn(DC, CurRgn); finally DeleteObject(RectRgn); end; end; procedure FillAroundRect(ACanvas: TCanvas; const Boundary, Interior: TRect; BackColor: TColor); var R: TRect; begin R := Rect(Boundary.Left, Boundary.Top, Boundary.Right, Interior.Top); if not IsRectEmpty(R) then DrawFilledRectangle(ACanvas, R, BackColor); R := Rect(Boundary.Left, Interior.Top, Interior.Left, Interior.Bottom); if not IsRectEmpty(R) then DrawFilledRectangle(ACanvas, R, BackColor); R := Rect(Interior.Right, Interior.Top, Boundary.Right, Interior.Bottom); if not IsRectEmpty(R) then DrawFilledRectangle(ACanvas, R, BackColor); R := Rect(Boundary.Left, Interior.Bottom, Boundary.Right, Boundary.Bottom); if not IsRectEmpty(R) then DrawFilledRectangle(ACanvas, R, BackColor); end; procedure FinalizePrevRgn(DC: HDC; ARgn: HRGN); begin SelectClipRgn(DC, ARgn); DeleteObject(ARgn); end; function GetFontHeight(DC: HDC): Integer; var TM: TTextMetric; begin FillChar(TM, SizeOf(TTextMetric), 0); GetTextMetrics(DC, TM); Result := TM.tmHeight; end; function GDICheck(Value: Integer): Integer; begin if Value = 0 then raise EOutOfResources.Create(SGDIError); Result := Value; end; function ImageByType(const Header: TKImageHeaderString): TGraphic; begin if Pos('BM', {$IFDEF COMPILER12_UP}string{$ENDIF}(Header)) = 1 then Result := TBitmap.Create {$IFDEF USE_PNG_SUPPORT } else if (Pos(#137'PNG', {$IFDEF COMPILER12_UP}string{$ENDIF}(Header)) = 1) or (Pos(#138'MNG', {$IFDEF COMPILER12_UP}string{$ENDIF}(Header)) = 1) then Result := TKPngImage.Create {$ENDIF } else if (Pos(#$FF#$D8, {$IFDEF COMPILER12_UP}string{$ENDIF}(Header)) = 1) then Result := TJPegImage.Create else if (Pos(#$FF#$D8, {$IFDEF COMPILER12_UP}string{$ENDIF}(Header)) = 1) then Result := TIcon.Create else Result := nil; end; function IntersectClipRectIndirect(DC: HDC; ARect: TRect): Boolean; begin with ARect do Result := IntersectClipRect(DC, Left, Top, Right, Bottom) <> NULLREGION; end; function IsBrightColor(Color: TColor): Boolean; begin Result := CalcLightness(Color) > 0.5; end; function MakeColorRec(R, G, B, A: Byte): TKColorRec; begin Result.R := R; Result.G := G; Result.B := B; Result.A := A; end; procedure LoadCustomCursor(Cursor: TCursor; const ResName: string); begin Screen.Cursors[Cursor] := {$IFDEF FPC} LoadCursorFromLazarusResource(ResName); {$ELSE} LoadCursor(HInstance, PChar(ResName)); {$ENDIF} end; function PixelFormatFromBpp(Bpp: Cardinal): TPixelFormat; begin case Bpp of 1: Result := pf1bit; 2..4: Result := pf4bit; 5..8: Result := pf8bit; 9..16: Result := pf16bit; else Result := pf32bit; end; end; function RectInRegion(Rgn: HRGN; ARect: TRect): Boolean; {$IFDEF FPC} var RectRgn, TmpRgn: HRGN; {$ENDIF} begin {$IFDEF FPC} RectRgn := CreateRectRgnIndirect(ARect); try TmpRgn := CreateEmptyRgn; try Result := CombineRgn(TmpRgn, RectRgn, Rgn, RGN_AND) <> NULLREGION; finally DeleteObject(TmpRgn); end; finally DeleteObject(RectRgn); end; {$ELSE} Result := Windows.RectInRegion(Rgn, ARect); {$ENDIF} end; procedure SafeStretchDraw(ACanvas: TCanvas; ARect: TRect; AGraphic: TGraphic; ABackColor: TColor); {$IFDEF USE_WINAPI} var BM: TBitmap; W, H, MulX, MulY, DivX, DivY: Integer; R: TRect; {$ENDIF} begin {$IFDEF USE_WINAPI} if AGraphic.Transparent then begin // WinAPI StretchBlt function does not read properly from screen buffer // so we have to append double buffering CanvasGetScale(ACanvas, MulX, MulY, DivX, DivY); W := MulDiv(ARect.Right - ARect.Left, MulX, DivX); H := MulDiv(ARect.Bottom - ARect.Top, MulY, DivY); BM := TBitmap.Create; try BM.Width := W; BM.Height := H; BM.Canvas.Brush := ACanvas.Brush; R := Rect(0, 0, W, H); DrawFilledRectangle(BM.Canvas, R, ABackColor); BM.Canvas.StretchDraw(R, AGraphic); ACanvas.StretchDraw(ARect, BM); finally BM.Free; end; end else {$ENDIF} ACanvas.StretchDraw(ARect, AGraphic); end; procedure SelectClipRect(DC: HDC; const ARect: TRect); var Rgn: HRGN; begin Rgn := CreateRectRgnIndirect(ARect); try SelectClipRgn(DC, Rgn); finally DeleteObject(Rgn); end; end; procedure StretchBitmap(DestDC: HDC; DestRect: TRect; SrcDC: HDC; SrcRect: TRect); begin {$IFDEF USE_WINAPI}Windows.{$ENDIF}StretchBlt(DestDC, DestRect.Left, DestRect.Top, DestRect.Right - DestRect.Left, DestRect.Bottom - DestRect.Top, SrcDC, SrcRect.Left, SrcRect.Top, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top, SRCCOPY); end; procedure SwapBR(var ColorRec: TKColorRec); var Tmp: Byte; begin Tmp := ColorRec.R; ColorRec.R := ColorRec.B; ColorRec.B := Tmp; end; function SwitchRGBToBGR(Value: TColor): TColor; var B: Byte; begin Result := Value; B := PKColorRec(@Value).B; PKColorRec(@Result).B := PKColorRec(@Result).R; PKColorRec(@Result).R := B; end; procedure TranslateRectToDevice(DC: HDC; var ARect: TRect); var P: TPoint; {$IFDEF USE_DC_MAPPING} {$IFNDEF LCLQT} WindowExt, ViewportExt: TSize; {$ENDIF} {$ENDIF} begin {$IFDEF USE_DC_MAPPING} {$IFNDEF LCLQT} if not (GetMapMode(DC) in [0, MM_TEXT]) and Boolean(GetWindowExtEx(DC, {$IFDEF FPC}@{$ENDIF}WindowExt)) and Boolean(GetViewportExtEx(DC, {$IFDEF FPC}@{$ENDIF}ViewportExt)) then begin ARect.Left := MulDiv(ARect.Left, ViewportExt.cx, WindowExt.cx); ARect.Right := MulDiv(ARect.Right, ViewportExt.cx, WindowExt.cx); ARect.Top := MulDiv(ARect.Top, ViewportExt.cy, WindowExt.cy); ARect.Bottom := MulDiv(ARect.Bottom, ViewportExt.cy, WindowExt.cy); end; if Boolean(GetViewPortOrgEx(DC, {$IFDEF FPC}@{$ENDIF}P)) then OffsetRect(ARect, P.X, P.Y); {$ENDIF} {$ENDIF} if Boolean(GetWindowOrgEx(DC, {$IFDEF FPC}@{$ENDIF}P)) then OffsetRect(ARect, -P.X, -P.Y); end; { TKAlphaBitmap } constructor TKAlphaBitmap.Create; begin inherited; FCanvas := TCanvas.Create; FCanvas.Handle := CreateCompatibleDC(0); FDirectCopy := False; FHandle := 0; {$IFNDEF USE_WINAPI} FImage := TLazIntfImage.Create(0, 0); {$ENDIF} FHeight := 0; FOldBitmap := 0; FPixels := nil; FWidth := 0; end; constructor TKAlphaBitmap.CreateFromRes(const ResName: string); var Stream: {$IFDEF FPC}TLazarusResourceStream{$ELSE}TResourceStream{$ENDIF}; begin Create; try {$IFDEF FPC} Stream := TLazarusResourceStream.Create(LowerCase(ResName), 'BMP'); {$ELSE} Stream := TResourceStream.Create(HInstance, ResName, RT_RCDATA); {$ENDIF} try LoadFromStream(Stream); finally Stream.Free; end; except end; end; destructor TKAlphaBitmap.Destroy; var DC: HDC; begin inherited; SetSize(0, 0); {$IFNDEF USE_WINAPI} FImage.Free; {$ENDIF} DC := FCanvas.Handle; FCanvas.Handle := 0; DeleteDC(DC); FCanvas.Free; end; procedure TKAlphaBitmap.AlphaDrawTo(ACanvas: TCanvas; X, Y: Integer); begin AlphaStretchDrawTo(ACanvas, Rect(X, Y, X + FWidth, Y + FHeight)); end; procedure TKAlphaBitmap.AlphaFill(Alpha: Byte; IfEmpty: Boolean); var I: Integer; HasAlpha: Boolean; begin HasAlpha := False; if IfEmpty then for I := 0 to FWidth * FHeight - 1 do if FPixels[I].A <> 0 then begin HasAlpha := True; Break; end; if not HasAlpha then for I := 0 to FWidth * FHeight - 1 do FPixels[I].A := Alpha; end; procedure TKAlphaBitmap.AlphaFill(Alpha: Byte; BlendColor: TColor; Gradient, Translucent: Boolean); var I, J, A1, A2, AR, AG, AB, HAlpha: Integer; HStep, HSum, VStep, VSum: Single; Scan: PKColorRecs; CS: TKColorRec; begin VSum := 0; VStep := 0; HSum := 0; HStep := 0; if Gradient then begin VStep := Alpha / FHeight; VSum := Alpha; end; CS.Value := ColorToRGB(BlendColor); {$IFNDEF USE_WINAPI} for I := 0 to FHeight - 1 do {$ELSE} for I := FHeight - 1 downto 0 do {$ENDIF} begin Scan := ScanLine[I]; HAlpha := Alpha; if Gradient then begin HStep := HAlpha / FWidth; HSum := HAlpha; end; for J := 0 to FWidth - 1 do with Scan[J] do begin A1 := HAlpha; A2 := 255 - HAlpha; AR := R * A1 + CS.R * A2; AG := G * A1 + CS.G * A2; AB := B * A1 + CS.B * A2; R := AR shr 8; G := AG shr 8; B := AB shr 8; if Translucent then A := HAlpha else A := 255; if Gradient then begin HAlpha := Round(HSum); HSum := HSum - HStep; end; end; if Gradient then begin Alpha := Round(VSum); VSum := VSum - VStep; end; end; FPixelsChanged := True; end; procedure TKAlphaBitmap.AlphaStretchDrawTo(ACanvas: TCanvas; const ARect: TRect); {$IFDEF USE_WINAPI} var I: Integer; Tmp: TKAlphaBitmap; Ps, Pd: PKColorRecs; {$ENDIF} begin {$IFNDEF USE_WINAPI} DrawTo(ACanvas, ARect); {$ELSE} Tmp := TKAlphaBitmap.Create; try Tmp.SetSize(FWidth, FHeight); Tmp.DrawFrom(ACanvas, ARect); for I := 0 to FHeight - 1 do begin Ps := ScanLine[I]; Pd := Tmp.ScanLine[I]; BlendLine(Ps, Pd, FWidth); end; Tmp.PixelsChanged := True; Tmp.DrawTo(ACanvas, ARect); finally Tmp.Free; end; {$ENDIF} end; procedure TKAlphaBitmap.CombinePixel(X, Y: Integer; Color: TKColorRec); var Index, A1, A2, AR, AG, AB: Integer; begin if (X >= 0) and (X < FWidth) and (Y >= 0) and (Y < FHeight) then begin SwapBR(Color); {$IFDEF USE_WINAPI} Index := (FHeight - Y - 1) * FWidth + X; {$ELSE} Index := Y * FWidth + X; {$ENDIF} A2 := Color.A; if A2 = 255 then FPixels[Index] := Color else if A2 <> 0 then begin A1 := 255 - Color.A; AR := FPixels[Index].R * A1 + Color.R * A2; AG := FPixels[Index].G * A1 + Color.G * A2; AB := FPixels[Index].B * A1 + Color.B * A2; FPixels[Index].R := AR shr 8; FPixels[Index].G := AG shr 8; FPixels[Index].B := AB shr 8; FPixels[Index].A := 255; end; FPixelsChanged := True; end; end; procedure TKAlphaBitmap.CopyFrom(ABitmap: TKAlphaBitmap); var I, Size: Integer; begin SetSize(ABitmap.Width, ABitmap.Height); Size := FWidth * SizeOf(TKColorRec); for I := 0 to FHeight - 1 do Move(ABitmap.ScanLine[I]^, ScanLine[I]^, Size); FPixelsChanged := True; end; procedure TKAlphaBitmap.CopyFromRotated(ABitmap: TKAlphaBitmap); var I, J: Integer; SrcScan, DstScan: PKColorRecs; begin SetSize(ABitmap.Height, ABitmap.Width); for J := 0 to ABitmap.Height - 1 do begin SrcScan := ABitmap.ScanLine[J]; for I := 0 to ABitmap.Width - 1 do begin DstScan := ScanLine[ABitmap.Width - I - 1]; DstScan[J] := SrcScan[I]; end; end; FPixelsChanged := True; end; procedure TKAlphaBitmap.Draw(ACanvas: TCanvas; const ARect: TRect); begin if FDirectCopy then DrawTo(ACanvas, ARect) else AlphaStretchDrawTo(ACanvas, ARect); end; procedure TKAlphaBitmap.DrawFrom(ACanvas: TCanvas; const ARect: TRect); begin if not Empty then begin if not CanvasScaled(ACanvas) then StretchBitmap(FCanvas.Handle, Rect(0, 0, FWidth, FHeight), ACanvas.Handle, ARect) else begin FCanvas.Brush := ACanvas.Brush; DrawFilledRectangle(FCanvas, Rect(0, 0, FWidth, FHeight), {$IFDEF USE_WINAPI}GetBkColor(ACanvas.Handle){$ELSE}clWindow{$ENDIF}); end; UpdatePixels; end; end; procedure TKAlphaBitmap.DrawTo(ACanvas: TCanvas; const ARect: TRect); begin if not Empty then begin UpdateHandle; StretchBitmap(ACanvas.Handle, ARect, FCanvas.Handle, Rect(0, 0, FWidth, FHeight)) end; end; function TKAlphaBitmap.GetEmpty: Boolean; begin Result := (FWidth = 0) and (FHeight = 0); end; function TKAlphaBitmap.GetHeight: Integer; begin Result := FHeight; end; function TKAlphaBitmap.GetPixel(X, Y: Integer): TKColorRec; begin if (X >= 0) and (X < FWidth) and (Y >= 0) and (Y < FHeight) then begin {$IFDEF USE_WINAPI} Result := FPixels[(FHeight - Y - 1) * FWidth + X]; {$ELSE} Result := FPixels[Y * FWidth + X]; {$ENDIF} SwapBR(Result); end else Result := MakeColorRec(0,0,0,0); end; function TKAlphaBitmap.GetTransparent: Boolean; begin Result := True; end; function TKAlphaBitmap.GetScanLine(Index: Integer): PKColorRecs; begin // no checks here Result := @FPixels[Index * FWidth]; end; function TKAlphaBitmap.GetHandle: HBITMAP; begin Result := FHandle; end; function TKAlphaBitmap.GetWidth: Integer; begin Result := FWidth; end; {$IFNDEF FPC} procedure TKAlphaBitmap.LoadFromClipboardFormat(AFormat: Word; AData: THandle; APalette: HPALETTE); begin // does nothing end; {$ENDIF} procedure TKAlphaBitmap.LoadFromStream(Stream: TStream); var BF: TBitmapFileHeader; BI: TBitmapInfoHeader; begin SetSize(0, 0); Stream.Read(BF, SizeOf(TBitmapFileHeader)); Stream.Read(BI, SizeOf(TBitmapInfoHeader)); if BI.biBitCount = 32 then begin SetSize(BI.biWidth, BI.biHeight); Stream.Read(FPixels^, BI.biSizeImage); // if bitmap has no alpha channel, create full opacity AlphaFill($FF, True); end; FPixelsChanged := True; end; procedure TKAlphaBitmap.MirrorHorz; var I, J, Index: Integer; SrcScan: PKColorRecs; Buf: TKColorRec; begin for I := 0 to FHeight - 1 do begin SrcScan := ScanLine[I]; Index := FWidth - 1; for J := 0 to (FWidth shr 1) - 1 do begin Buf := SrcScan[Index]; SrcScan[Index] := SrcScan[J]; SrcScan[J] := Buf; Dec(Index); end; end; FPixelsChanged := True; end; procedure TKAlphaBitmap.MirrorVert; var I, Size, Index: Integer; SrcScan, DstScan: PKColorRecs; Buf: PKColorRec; begin Size:= FWidth * SizeOf(TKColorRec); Index := FHeight - 1; GetMem(Buf, Size); try for I := 0 to (FHeight shr 1) - 1 do begin SrcScan := ScanLine[I]; DstScan := ScanLine[Index]; Move(SrcScan^, Buf^, Size); Move(DstScan^, SrcScan^, Size); Move(Buf^, DstScan^, Size); Dec(Index); end; finally FreeMem(Buf); end; FPixelsChanged := True; end; {$IFNDEF FPC} procedure TKAlphaBitmap.SaveToClipboardFormat(var AFormat: Word; var AData: THandle; var APalette: HPALETTE); begin // does nothing end; {$ENDIF} procedure TKAlphaBitmap.SaveToStream(Stream: TStream); var Size: Integer; BF: TBitmapFileHeader; BI: TBitmapInfoHeader; begin Size := FWidth * FHeight * 4; FillChar(BF, SizeOf(TBitmapFileHeader), 0); BF.bfType := $4D42; BF.bfSize := SizeOf(TBitmapFileHeader) + SizeOf(TBitmapInfoHeader) + Size; BF.bfOffBits := SizeOf(TBitmapFileHeader) + SizeOf(TBitmapInfoHeader); Stream.Write(BF, SizeOf(TBitmapFileHeader)); FillChar(BI, SizeOf(TBitmapInfoHeader), 0); BI.biSize := SizeOf(TBitmapInfoHeader); BI.biWidth := FWidth; BI.biHeight := FHeight; BI.biPlanes := 1; BI.biBitCount := 32; BI.biCompression := BI_RGB; BI.biSizeImage := Size; Stream.Write(BI, SizeOf(TBitmapInfoHeader)); Stream.Write(FPixels^, Size); end; procedure TKAlphaBitmap.SetHeight(Value: Integer); begin SetSize(FWidth, Value); end; procedure TKAlphaBitmap.SetPixel(X, Y: Integer; Value: TKColorRec); begin if (X >= 0) and (X < FWidth) and (Y >= 0) and (Y < FHeight) then begin SwapBR(Value); {$IFDEF USE_WINAPI} FPixels[(FHeight - Y - 1) * FWidth + X] := Value; {$ELSE} FPixels[Y * FWidth + X] := Value; {$ENDIF} FPixelsChanged := True; end; end; procedure TKAlphaBitmap.SetSize(AWidth, AHeight: Integer); var {$IFNDEF USE_WINAPI} ImgFormatDescription: TRawImageDescription; {$ELSE} BI: TBitmapInfoHeader; {$ENDIF} begin AWidth := Max(AWidth, 0); AHeight := Max(AHeight, 0); if (AWidth <> FWidth) or (AHeight <> FHeight) then begin FWidth := AWidth; FHeight := AHeight; if FHandle <> 0 then begin SelectObject(FCanvas.Handle, FOldBitmap); DeleteObject(FHandle); FHandle := 0; {$IFNDEF USE_WINAPI} DeleteObject(FMaskHandle); FMaskHandle := 0; {$ENDIF} end; {$IFNDEF USE_WINAPI} FImage.SetSize(0, 0); {$ENDIF} FPixels := nil; if (FWidth <> 0) and (FHeight <> 0) then begin {$IFNDEF USE_WINAPI} ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FWidth,FHeight); FImage.DataDescription := ImgFormatDescription; FPixelsChanged := True; UpdateHandle; {$ELSE} FillChar(BI, SizeOf(TBitmapInfoHeader), 0); BI.biSize := SizeOf(TBitmapInfoHeader); BI.biWidth := FWidth; BI.biHeight := FHeight; BI.biPlanes := 1; BI.biBitCount := 32; BI.biCompression := BI_RGB; FHandle := GDICheck(CreateDIBSection(FCanvas.Handle, PBitmapInfo(@BI)^, DIB_RGB_COLORS, Pointer(FPixels), 0, 0)); FOldBitmap := SelectObject(FCanvas.Handle, FHandle); {$ENDIF} end; end; end; procedure TKAlphaBitmap.SetWidth(Value: Integer); begin SetSize(Value, FWidth); end; procedure TKAlphaBitmap.SetTransparent(Value: Boolean); begin // does nothing end; procedure TKAlphaBitmap.UpdateHandle; begin {$IFNDEF USE_WINAPI} if FPixelsChanged then begin PixelsChanged := False; if FHandle <> 0 then begin DeleteObject(FMaskHandle); DeleteObject(SelectObject(FCanvas.Handle, FOldBitmap)); end; FImage.CreateBitmaps(FHandle, FMaskHandle, False); FOldBitmap := SelectObject(FCanvas.Handle, FHandle); FPixels := PKColorRecs(FImage.PixelData); end; {$ENDIF} end; procedure TKAlphaBitmap.UpdatePixels; begin {$IFNDEF USE_WINAPI} FImage.LoadFromDevice(FCanvas.Handle); FPixelsChanged := True; UpdateHandle; {$ENDIF} end; {$IFDEF USE_WINAPI} const cLayeredWndClass = 'KControls drag window'; function DragWndProc(Window: HWnd; Msg, WParam, LParam: Longint): Longint; stdcall; var DC: HDC; PS: TPaintStruct; AWindow: TKDragWindow; begin case Msg of WM_PAINT: begin AWindow := TKDragWindow(GetWindowLong(Window, GWL_USERDATA)); if (AWindow <> nil) and AWindow.BitmapFilled then begin if wParam = 0 then DC := BeginPaint(Window, PS) else DC := wParam; try BitBlt(DC, 0, 0, AWindow.Bitmap.Width, AWindow.Bitmap.Height, AWindow.Bitmap.Canvas.Handle, 0, 0, SRCCOPY); finally if wParam = 0 then EndPaint(Window, PS); end; end; Result := 1; end; else Result := DefWindowProc(Window, Msg, WParam, LParam); end; end; {$ELSE} type { TKDragForm } TKDragForm = class(THintWindow) private FWindow: TKDragWindow; procedure WMEraseBkGnd(var Msg: TLMessage); message LM_ERASEBKGND; protected procedure Paint; override; public constructor CreateDragForm(AWindow: TKDragWindow); end; { TKDragForm } constructor TKDragForm.CreateDragForm(AWindow: TKDragWindow); begin inherited Create(nil); FWindow := AWindow; ShowInTaskBar := stNever; end; procedure TKDragForm.Paint; begin if FWindow.Active and FWindow.BitmapFilled then Canvas.Draw(0, 0, FWindow.FBitmap); end; procedure TKDragForm.WMEraseBkGnd(var Msg: TLMessage); begin Msg.Result := 1; end; {$ENDIF} constructor TKDragWindow.Create; {$IFDEF USE_WINAPI} var Cls: Windows.TWndClass; ExStyle: Cardinal; {$ENDIF} begin inherited; FActive := False; FBitmap := TKAlphaBitmap.Create; FInitialPos := Point(0, 0); {$IFDEF USE_WINAPI} FUpdateLayeredWindow := GetProcAddress(GetModuleHandle('user32.dll'), 'UpdateLayeredWindow'); FLayered := Assigned(FUpdateLayeredWindow); Cls.style := CS_SAVEBITS; Cls.lpfnWndProc := @DragWndProc; Cls.cbClsExtra := 0; Cls.cbWndExtra := 0; Cls.hInstance := HInstance; Cls.hIcon := 0; Cls.hCursor := 0; Cls.hbrBackground := 0; Cls.lpszMenuName := nil; Cls.lpszClassName := cLayeredWndClass; Windows.RegisterClass(Cls); ExStyle := WS_EX_TOOLWINDOW or WS_EX_TOPMOST; if FLayered then ExStyle := ExStyle or WS_EX_LAYERED or WS_EX_TRANSPARENT; FWindow := CreateWindowEx(ExStyle, cLayeredWndClass, '', WS_POPUP, Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT), 0, 0, HInstance, nil); Windows.SetWindowLong(FWindow, GWL_USERDATA, Integer(Self)); {$ELSE} FDragForm := TKDragForm.CreateDragForm(Self); FLayered := False; {$ENDIF} end; destructor TKDragWindow.Destroy; begin inherited; Hide; {$IFDEF USE_WINAPI} DestroyWindow(FWindow); Windows.UnregisterClass(cLayeredWndClass, HInstance); {$ELSE} FDragForm.Free; {$ENDIF} FBitmap.Free; end; procedure TKDragWindow.Hide; begin if FActive then begin {$IFDEF USE_WINAPI} ShowWindow(FWindow, SW_HIDE); {$ELSE} FDragForm.Hide; {$ENDIF} FActive := False; end; end; procedure TKDragWindow.Show(IniCtrl: TCustomControl; const ARect: TRect; const InitialPos, CurrentPos: TPoint; MasterAlpha: Byte; Gradient: Boolean); var Org: TPoint; W, H: Integer; ScreenDC: HDC; begin if not (IniCtrl is TKCustomControl) then Exit; if not FActive then begin FActive := True; FBitmapFilled := False; FControl := IniCtrl; FMasterAlpha := MasterAlpha; FGradient := Gradient; FInitialPos := InitialPos; W := ARect.Right - ARect.Left; H := ARect.Bottom - ARect.Top; FBitmap.SetSize(W, H); Org := IniCtrl.ClientToScreen(ARect.TopLeft); ScreenDC := GetDC(0); try FAlphaEffects := GetDeviceCaps(ScreenDC, BITSPIXEL) >= 15; // because alpha blending is not nice elsewhere finally ReleaseDC(0, ScreenDC); end; // to be compatible with all LCL widgetsets we must copy the control's part // while painting in TKCustomControl.Paint! TKCustomControl(FControl).MemoryCanvas := FBitmap.Canvas; TKCustomControl(FControl).MemoryCanvasRect := ARect; TKCustomControl(FControl).Repaint; {$IFDEF USE_WINAPI} if FLayered then with FBlend do begin BlendOp := AC_SRC_OVER; BlendFlags := 0; SourceConstantAlpha := 255; if FAlphaEffects then AlphaFormat := AC_SRC_ALPHA else AlphaFormat := 0; end; SetWindowPos(FWindow, 0, Org.X, Org.Y, W, H, SWP_NOACTIVATE or SWP_NOZORDER); {$ELSE} FDragForm.SetBounds(Org.X, Org.Y, W, H); {$ENDIF} Move(CurrentPos); end; end; procedure TKDragWindow.Move(const NewPos: TPoint); var R: TRect; DX, DY: Integer; BlendColor: TColor; {$IFDEF USE_WINAPI} ScreenDC: HDC; CanvasOrigin: TPoint; {$ENDIF} begin if FActive then begin if (TKCustomControl(FControl).MemoryCanvas = nil) and not FBitmapFilled then begin FBitmapFilled := True; FBitmap.UpdatePixels; if FAlphaEffects then begin if FLayered then BlendColor := clBlack else BlendColor := clWhite; FBitmap.AlphaFill(FMasterAlpha, BlendColor, FGradient, FLayered); FBitmap.UpdateHandle; end; end; DX := NewPos.X - FInitialPos.X; DY := NewPos.Y - FInitialPos.Y; if (DX <> 0) or (DY <> 0) then begin FInitialPos := NewPos; {$IFDEF USE_WINAPI} GetWindowRect(FWindow, R); OffsetRect(R, DX, DY); if FLayered then begin R.Right := FBitmap.Width; R.Bottom := FBitmap.Height; CanvasOrigin := Point(0, 0); ScreenDC := GetDC(0); try if FUpdateLayeredWindow(FWindow, ScreenDC, @R.TopLeft, PSize(@R.BottomRight), FBitmap.Canvas.Handle, @CanvasOrigin, clNone, @FBlend, ULW_ALPHA) then if FBitmapFilled then ShowWindow(FWindow, SW_SHOWNOACTIVATE); finally ReleaseDC(0, ScreenDC); end; end else if FBitmapFilled then SetWindowPos(FWindow, 0, R.Left, R.Top, 0, 0, SWP_NOACTIVATE or SWP_NOSIZE or SWP_NOZORDER or SWP_SHOWWINDOW); {$ELSE} R := FDragForm.BoundsRect; OffsetRect(R, DX, DY); FDragForm.BoundsRect := R; if FBitmapFilled then begin FDragForm.Visible := True; SetCaptureControl(FControl); end; {$ENDIF} end; end; end; { TKHintWindow } constructor TKHintWindow.Create(AOwner: TComponent); begin inherited; {$IFDEF FPC} ShowInTaskBar := stNever; {$ENDIF} DoubleBuffered := True; end; procedure TKHintWindow.ShowAt(const Origin: TPoint); begin ActivateHint(Rect(Origin.X, Origin.Y, Origin.X + FExtent.X + 10, Origin.Y + FExtent.Y + 10), ''); end; procedure TKHintWindow.WMEraseBkGnd(var Msg: TLMessage); begin Msg.Result := 1; end; { TKTextHint } constructor TKTextHint.Create(AOwner: TComponent); begin inherited; FText := ''; end; procedure TKTextHint.Paint; var R: TRect; begin Canvas.Brush.Style := bsSolid; Canvas.Brush.Color := clInfoBk; Canvas.FillRect(ClientRect); Canvas.Brush.Style := bsClear; R := Rect(0, 0, FExtent.X + 10, FExtent.Y + 10); DrawAlignedText(Canvas, R, halLeft, valCenter, 5, 5, FText, clInfoBk, [taEndEllipsis, taWordBreak, taLineBreak]) end; procedure TKTextHint.SetText(const Value: {$IFDEF STRING_IS_UNICODE}string{$ELSE}WideString{$ENDIF}); var R: TRect; begin if Value <> FText then begin FText := Value; R := Rect(0, 0, 300, 0); DrawAlignedText(Canvas, R, halLeft, valCenter, 0, 0, FText, clInfoBk, [taCalcRect, taWordBreak, taLineBreak]); FExtent.X := R.Right - R.Left; FExtent.Y := R.Bottom - R.Top; end; end; { TKGraphicHint } constructor TKGraphicHint.Create(AOwner: TComponent); begin inherited; FGraphic := nil; {$IFDEF FPC} ShowInTaskBar := stNever; {$ENDIF} DoubleBuffered := True; end; procedure TKGraphicHint.Paint; begin Canvas.Brush.Style := bsSolid; Canvas.Brush.Color := clInfoBk; Canvas.FillRect(ClientRect); if Assigned(FGraphic) then Canvas.Draw(5, 5, FGraphic) end; procedure TKGraphicHint.SetGraphic(const Value: TGraphic); begin if Value <> FGraphic then begin FGraphic := Value; FExtent.X := FGraphic.Width; FExtent.Y := FGraphic.Height; end; end; end.