// SPDX-License-Identifier: LGPL-3.0-linking-exception { General framework methods for rendering background, borders, text, etc. originally written in 2012 by Krzysztof Dibowski dibowski at interia.pl } {******************************* CONTRIBUTOR(S) ****************************** - Edivando S. Santos Brasil | mailedivando@gmail.com (Compatibility with delphi VCL 11/2018) ***************************** END CONTRIBUTOR(S) *****************************} unit BCTools; {$I bgracontrols.inc} interface uses Classes, SysUtils, Types, Graphics, {$IFDEF FPC}LCLType, LCLIntf,{$ENDIF} {$IFNDEF FPC}BGRAGraphics, GraphType, FPImage, {$ENDIF} BGRABitmap, BGRABitmapTypes, bctypes, Controls, BGRAGradientScanner; function ScaleRect(ARect: TRect; AScale: Single): TRect; // This method prepare BGRABitmap for rendering BCFont type procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap); // Calculate text height and width (doesn't include wordwrap - just single line) procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer; AShadowMargin: boolean = true); // Calculate text height and width (handles wordwrap and end ellipsis) procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer; AAvailableWidth: integer; AShadowMargin: boolean = false); // Determines the layout of the glyph procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment; out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment; out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer; out AGlyphVertMargin: integer); // Computes the position the glyph and update rAvail with the space dedicated to text. // Specify the flag AOldPlacement to have the old (buggy) version function ComputeGlyphPosition(var rAvail: TRect; AGlyph: TBitmap; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false; AGlyphScale: Single = 1): TRect; overload; function ComputeGlyphPosition(var rAvail: TRect; gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false): TRect; overload; // This method correct TRect to border width. As far as border width is bigger, // BGRA drawing rectangle with offset (half border width) procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect); // This returns a rectangle that is inside the border outline procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect); // Create BGRA Gradient Scanner based on BCGradient properties function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner; // Render arrow (used by BCButton with DropDownMenu style) procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect; ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor = clBlack; AOpacity: Byte = 255); // Render customizable backgroud (used e.g. by TBCButton, TBCPanel, TBCLabel) procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false); procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); procedure RenderBackgroundAndBorder(const ARect: TRect; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single = 0); // Render customizable border (used e.g. by TBCButton, TBCPanel, TBCLabel) procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); // Render BCFont (used e.g. by TBCButton, TBCPanel, TBCLabel) procedure RenderText(const ARect: TRect; AFont: TBCFont; const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean); // Return LCL horizontal equivalent for BCAlignment function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment; // Return LCL vertical equivalent for BCAlignment function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout; implementation uses BGRAPolygon, BGRAFillInfo, BGRAText, math, BGRAUTF8, LazUTF8; function ComputeGlyphPosition(var rAvail: TRect; AGlyph: TBitmap; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string; AFont: TBCFont; AOldPlacement: boolean; AGlyphScale: Single): TRect; var gw, gh: integer; begin if Assigned(AGlyph) and not AGlyph.Empty then begin gw := round(AGlyph.Width * AGlyphScale); gh := round(AGlyph.Height * AGlyphScale); end else begin gw := 0; gh := 0; end; result := ComputeGlyphPosition(rAvail, gw, gh, AGlyphAlignment, AGlyphMargin, ACaption, AFont, AOldPlacement); end; procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect); var w: integer; begin if ABorder = nil then Exit; w := ABorder.Width div 2; Inc(ARect.Left, w); Inc(ARect.Top, w); Dec(ARect.Right, w); Dec(ARect.Bottom, w); end; procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect); var w: integer; begin if (ABorder = nil) or (ABorder.Style = bboNone) then Exit; w := ABorder.Width; Inc(ARect.Left, w); Inc(ARect.Top, w); Dec(ARect.Right, w); Dec(ARect.Bottom, w); end; function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner; begin Result := TBGRAGradientScanner.Create( ColorToBGRA(ColorToRGB(AGradient.StartColor), AGradient.StartColorOpacity), ColorToBGRA(ColorToRGB(AGradient.EndColor), AGradient.EndColorOpacity), AGradient.GradientType, PointF(ARect.Left + Round( ((ARect.Right - ARect.Left) / 100) * AGradient.Point1XPercent), ARect.Top + Round(((ARect.Bottom - ARect.Top) / 100) * AGradient.Point1YPercent)), PointF(ARect.Left + Round(((ARect.Right - ARect.Left) / 100) * AGradient.Point2XPercent), ARect.Top + Round( ((ARect.Bottom - ARect.Top) / 100) * AGradient.Point2YPercent)), AGradient.ColorCorrection, AGradient.Sinus); end; procedure RenderBackgroundAndBorder(const ARect: TRect; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single); var w: single; begin if ABorder.Style = bboNone then begin w := AInnerMargin-0.5; RenderBackgroundF(ARect.Left+w, ARect.Top+w, ARect.Right-1-w, ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding); end else begin w := (ABorder.Width-1)/2+AInnerMargin; RenderBackgroundF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding); RenderBorderF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABorder,ATargetBGRA,ARounding); end; end; procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); begin RenderBorderF(ARect.Left,ARect.Top,ARect.Right-1,ARect.Bottom-1,ABorder, ATargetBGRA,ARounding); end; procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); var fiLight: TFillBorderRoundRectInfo; rx,ry: Byte; ropt: TRoundRectangleOptions; begin if (x1>x2) or (y1>y2) then exit; if ABorder.Style=bboNone then Exit; if ARounding = nil then begin rx := 0; ry := 0; ropt := []; end else begin rx := ARounding.RoundX; ry := ARounding.RoundY; ropt := ARounding.RoundOptions; end; ATargetBGRA.RoundRectAntialias(x1,y1,x2,y2, rx, ry, ColorToBGRA(ColorToRGB(ABorder.Color),ABorder.ColorOpacity), ABorder.Width, ropt); if ABorder.LightWidth > 0 then begin //compute light position fiLight := TFillBorderRoundRectInfo.Create( x1,y1,x2,y2, rx, ry, ABorder.Width + ABorder.LightWidth, ropt); //check if there is an inner position if fiLight.InnerBorder <> nil then with fiLight.InnerBorder do //fill with light ATargetBGRA.RoundRectAntialias(topleft.x, topleft.y, bottomright.x, bottomright.y, radiusx, radiusY, ColorToBGRA(ColorToRGB(ABorder.LightColor), ABorder.LightOpacity), ABorder.LightWidth, ropt); fiLight.Free; end; end; procedure RenderText(const ARect: TRect; AFont: TBCFont; const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean); var shd: TBGRABitmap; hal: TAlignment; val: TTextLayout; st: TTextStyle; r: TRect; c: TColor; begin if AText = '' then exit; AssignBCFont(AFont,ATargetBGRA); hal := BCAlign2HAlign(AFont.TextAlignment); val := BCAlign2VAlign(AFont.TextAlignment); FillChar({%H-}st, SizeOf({%H-}st),0); st.Wordbreak := AFont.WordBreak; st.Alignment := hal; st.Layout := val; st.SingleLine := AFont.SingleLine; st.EndEllipsis := AFont.EndEllipsis; r := ARect; r.Left += AFont.PaddingLeft; r.Right -= AFont.PaddingRight; r.Top += AFont.PaddingTop; r.Bottom -= AFont.PaddingBottom; if AFont.Shadow then begin shd := TBGRABitmap.Create(ATargetBGRA.Width,ATargetBGRA.Height,BGRAPixelTransparent); shd.FontName := ATargetBGRA.FontName; shd.FontStyle := ATargetBGRA.FontStyle; shd.FontQuality := ATargetBGRA.FontQuality; shd.FontHeight := ATargetBGRA.FontHeight; shd.TextRect(r, r.Left, r.Top, AText, st, ColorToBGRA(ColorToRGB(AFont.ShadowColor), AFont.ShadowColorOpacity)); BGRAReplace(shd, shd.FilterBlurRadial(AFont.ShadowRadius, rbFast)); ATargetBGRA.BlendImage(AFont.ShadowOffsetX, AFont.ShadowOffsetY, shd, boLinearBlend); shd.Free; end; if AEnabled or (AFont.DisabledColor = clNone) then c := AFont.Color else c := AFont.DisabledColor; ATargetBGRA.TextRect(r,r.Left,r.Top,AText,st,c); end; function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment; begin if AAlign in [bcaCenter, bcaCenterTop, bcaCenterBottom] then Result := taCenter else if AAlign in [bcaRightCenter, bcaRightTop, bcaRightBottom] then Result := taRightJustify else Result := taLeftJustify; end; function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout; begin if AAlign in [bcaCenter, bcaLeftCenter, bcaRightCenter] then Result := tlCenter else if AAlign in [bcaCenterBottom, bcaLeftBottom, bcaRightBottom] then Result := tlBottom else Result := tlTop; end; function ScaleRect(ARect: TRect; AScale: Single): TRect; begin with ARect do result := rect(round(Left*AScale), round(Top*AScale), round(Right*AScale), round(Bottom*AScale)); end; procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap); var c: TBitmap; begin // Canvas is need for calculate font height c := TBitmap.Create; c.Canvas.Font.Name := AFont.Name; c.Canvas.Font.Style := AFont.Style; case AFont.FontQuality of fqSystem: c.Canvas.Font.Quality := fqNonAntialiased; fqFineAntialiasing: c.Canvas.Font.Quality := fqAntialiased; fqFineClearTypeRGB: c.Canvas.Font.Quality := fqProof; fqSystemClearType: c.Canvas.Font.Quality := fqCleartype; end; // FontAntialias is only backward compability for FontQuality property. // FontQuality is published in TBCFont so we don't need FontAntialias anymore. //ATargetBGRA.FontAntialias := AFont.FontAntialias; {%H-}ATargetBGRA.FontStyle := AFont.Style; // If font quality is system, then we can leave default values. LCL will // handle everything (when name is "default" or height 0) if AFont.FontQuality in [fqSystem,fqSystemClearType] then begin ATargetBGRA.FontName := AFont.Name; ATargetBGRA.FontHeight := AFont.Height; end else begin // Getting real font name if SameText(AFont.Name,'default') then ATargetBGRA.FontName := string(GetFontData(c.Canvas.Font.Handle).Name) else ATargetBGRA.FontName := AFont.Name; // Calculate default height, because when font quality is <> fqSystemXXX // then if height is 0 then it is 0 for real if (AFont.Height=0) then ATargetBGRA.FontHeight := -c.Canvas.TextHeight('Bgra') else ATargetBGRA.FontHeight := AFont.Height; end; ATargetBGRA.FontQuality := AFont.FontQuality; c.Free; end; procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer; AShadowMargin: boolean); var s: TSize; tmp: TBGRABitmap; begin if (AText = '') or (AFont = nil) then begin ANewWidth := 0; ANewHeight := 0; Exit; end; tmp := TBGRABitmap.Create(0,0); AssignBCFont(AFont, tmp); s := tmp.TextSize(AText); tmp.Free; { shadow offset } if AShadowMargin and AFont.Shadow then begin Inc(s.cx, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius); Inc(s.cy, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius); end; inc(s.cx, AFont.PaddingLeft+Afont.PaddingRight); inc(s.cy, AFont.PaddingTop+Afont.PaddingBottom); ANewWidth := s.cx; ANewHeight := s.cy; end; procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer; AAvailableWidth: integer; AShadowMargin: boolean); var s: TSize; tmp: TBGRABitmap; extraX,extraY, fitCount: integer; dotSize: LongInt; begin if (AText = '') or (AFont = nil) then begin ANewWidth := 0; ANewHeight := 0; Exit; end; extraX := 0; extraY := 0; { shadow offset } if AShadowMargin and AFont.Shadow then begin Inc(extraX, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius); Inc(extraY, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius); end; inc(extraX, AFont.PaddingLeft+Afont.PaddingRight); inc(extraY, AFont.PaddingTop+Afont.PaddingBottom); dec(AAvailableWidth, extraX); tmp := TBGRABitmap.Create(0,0); AssignBCFont(AFont, tmp); if AFont.WordBreak then s := tmp.TextSize(AText, AAvailableWidth) else begin s := tmp.TextSize(AText); if AFont.EndEllipsis and (s.cx > AAvailableWidth) then begin dotSize := tmp.TextSize('...').cx; fitCount := tmp.TextFitInfo(AText, AAvailableWidth-dotSize); s.cx := tmp.TextSize(UTF8Copy(AText, 1, fitCount)).cx + dotSize; end; end; tmp.Free; ANewWidth := s.cx+extraX; ANewHeight := s.cy+extraY; end; procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment; out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment; out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer; out AGlyphVertMargin: integer); begin if AGlyphAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify else if AGlyphAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify else AHorizAlign:= taCenter; if AGlyphAlignment in [bcaCenter,bcaLeftCenter,bcaRightCenter] then AVertAlign := tlCenter else if AGlyphAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign := tlBottom else AVertAlign := tlTop; if ACaption<>'' then begin AGlyphRelativeVertAlign:= AVertAlign; if AVertAlign <> tlCenter then AGlyphRelativeHorizAlign:= AHorizAlign else begin if AHorizAlign = taCenter then begin if IsRightToLeftUTF8(ACaption) then AGlyphRelativeHorizAlign := taRightJustify else AGlyphRelativeHorizAlign := taLeftJustify; end else AGlyphRelativeHorizAlign:= AHorizAlign; end; if AFont.TextAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify else if AFont.TextAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify else AHorizAlign := taCenter; if AFont.TextAlignment in [bcaLeftTop,bcaCenterTop,bcaRightTop] then AVertAlign := tlTop else if AFont.TextAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign:= tlBottom else AVertAlign:= tlCenter; if AGlyphRelativeVertAlign in[tlTop,tlBottom] then begin if AGlyphRelativeHorizAlign <> taCenter then AGlyphHorizMargin:= AGlyphMargin else AGlyphHorizMargin:= 0; if AGlyphRelativeVertAlign = AVertAlign then AGlyphVertMargin:= AGlyphMargin else AGlyphVertMargin:= 0; end else begin AGlyphHorizMargin:= AGlyphMargin; AGlyphVertMargin:= 0; end; end else begin case AHorizAlign of taCenter: AGlyphRelativeHorizAlign:= taCenter; taRightJustify: AGlyphRelativeHorizAlign:= taLeftJustify; else AGlyphRelativeHorizAlign:= taRightJustify; end; if AHorizAlign <> taCenter then AGlyphHorizMargin := AGlyphMargin else AGlyphHorizMargin := 0; case AVertAlign of tlCenter: AGlyphRelativeVertAlign:= tlCenter; tlBottom: AGlyphRelativeVertAlign:= tlTop; else AGlyphRelativeVertAlign:= tlBottom; end; if AVertAlign <> tlCenter then AGlyphVertMargin := AGlyphMargin else AGlyphVertMargin := 0; end; end; function ComputeGlyphPosition(var rAvail: TRect; gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string; AFont: TBCFont; AOldPlacement: boolean): TRect; var w, h, w2,h2, glyphHorzMargin, glyphVertMargin: integer; horizAlign, relHorizAlign: TAlignment; vertAlign, relVertAlign: TTextLayout; rText, rAll, rGlyph: TRect; l,t: integer; procedure AlignRect(var ARect: TRect; const ABounds: TRect; AHorizAlign: TAlignment; AVertAlign: TTextLayout; AHorizMargin: integer = 0; AVertMargin: integer = 0); begin case AHorizAlign of taCenter: ARect.Offset((ABounds.Left+ABounds.Right - (ARect.Right-ARect.Left)) div 2,0); taRightJustify: ARect.Offset(ABounds.Right - AHorizMargin - (ARect.Right-ARect.Left),0); else ARect.Offset(ABounds.Left + AHorizMargin,0); end; case AVertAlign of tlCenter: ARect.Offset(0, (ABounds.Top+ABounds.Bottom - (ARect.Bottom-ARect.Top)) div 2); tlBottom: ARect.Offset(0, ABounds.Bottom - AVertMargin - (ARect.Bottom-ARect.Top)); else ARect.Offset(0, ABounds.Top + AVertMargin); end; end; begin if (gw = 0) or (gh = 0) then exit(EmptyRect); if AOldPlacement then begin if ACaption = '' then begin w := 0; h := 0; end else CalculateTextSize(ACaption, AFont, w, h); l := rAvail.Right - Round(((rAvail.Right - rAvail.Left) + w + gw) / 2); t := rAvail.Bottom - Round(((rAvail.Bottom - rAvail.Top) + gh) / 2); result := rect(l,t,l+gw,t+gh); Inc(rAvail.Left, l + gw + AGlyphMargin); exit; end; GetGlyphActualLayout(ACaption, AFont, AGlyphAlignment, AGlyphMargin, horizAlign, vertAlign, relHorizAlign, relVertAlign, glyphHorzMargin, glyphVertMargin); if ACaption = '' then begin rGlyph := rect(0,0,gw,gh); AlignRect(rGlyph, rAvail, horizAlign, vertAlign, glyphHorzMargin, glyphVertMargin); exit(rGlyph); end else CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left); if relVertAlign in[tlTop,tlBottom] then begin w2 := max(w,gw+glyphHorzMargin); h2 := h+gh+glyphVertMargin; end else begin w2 := w+gw+glyphHorzMargin; if (ACaption <> '') and (w2 > rAvail.Right-rAvail.Left) then begin CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left - (gw+glyphHorzMargin)); w2 := w+gw+glyphHorzMargin; end; h2 := max(h,gh+glyphVertMargin); end; rAll := rect(0,0,w2,h2); AlignRect(rAll, rAvail, horizAlign, vertAlign); rText := rect(0,0,w,h); rGlyph := rect(0,0,gw,gh); case relVertAlign of tlTop: begin AlignRect(rGlyph, rAll, relHorizAlign, tlTop, glyphHorzMargin, glyphVertMargin); AlignRect(rText, rAll, horizAlign, tlBottom); end; tlBottom: begin AlignRect(rGlyph, rAll, relHorizAlign, tlBottom, glyphHorzMargin, glyphVertMargin); AlignRect(rText, rAll, horizAlign, tlTop); end; else begin if relHorizAlign = taRightJustify then begin AlignRect(rGlyph, rAll, taRightJustify, tlCenter, glyphHorzMargin, glyphHorzMargin); AlignRect(rText, rAll, taLeftJustify, tlCenter); end else begin AlignRect(rGlyph, rAll, taLeftJustify, tlCenter, glyphHorzMargin, glyphHorzMargin); AlignRect(rText, rAll, taRightJustify, tlCenter); end; end; end; result := rGlyph; if AFont.WordBreak and (rText.Right < rAvail.Right) then inc(rText.Right); //word-break computation may be one pixel off rAvail := rText; end; procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect; ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor; AOpacity: Byte); var p: ArrayOfTPointF; n: byte; temp: TBGRABitmap; w: Integer; begin // We can't draw outside rect w := Min(ASize, ARect.Right - ARect.Left); { Poly } SetLength(p, 3); temp := TBGRABitmap.Create(w+1, w+1,BGRAPixelTransparent); case ADirection of badDown: begin; p[0].x := 0; p[0].y := 0; p[1].x := w; p[1].y := 0; p[2].x := Round(w/2); p[2].y := w; end; badUp: begin p[0].x := Round(w/2); p[0].y := 0; p[1].x := 0; p[1].y := w; p[2].x := w; p[2].y := w; end; badLeft: begin p[0].x := 0; p[0].y := Round(w/2); p[1].x := w; p[1].y := 0; p[2].x := w; p[2].y := w; end; badRight: begin p[0].x := w; p[0].y := Round(w/2); p[1].x := 0; p[1].y := 0; p[2].x := 0; p[2].y := w; end; end; // Fill n times to get best quality for n := 1 to 6 do temp.FillPolyAntialias(p, ColorToBGRA(ColorToRGB(AColor),AOpacity)); ATargetBGRA.BlendImage( ARect.Right-Round( ((ARect.Right-ARect.Left)/2) + (w/2) ), ARect.Bottom-Round( ((ARect.Bottom-ARect.Top)/2) + (w/2) ), temp, boLinearBlend ); temp.Free; end; procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil); var backcolor: TBGRAPixel; multi: TBGRAMultishapeFiller; back: TBGRABitmap; grect1, grect2: TRect; gra: TBGRAGradientScanner; rx,ry: Byte; ropt: TRoundRectangleOptions; begin if (x1>=x2) or (y1>=y2) then exit; if ARounding = nil then begin rx := 0; ry := 0; ropt := []; end else begin rx := ARounding.RoundX; ry := ARounding.RoundY; ropt := ARounding.RoundOptions; end; { Background color } case ABackground.Style of bbsClear: backcolor := BGRAPixelTransparent; // TODO: Why if I use some system colors like clBtnFace, clActiveCaption etc. // without ColorToRGB, I always get Black? Interface: QT bbsColor: backcolor := ColorToBGRA(ColorToRGB(ABackground.Color), ABackground.ColorOpacity); end; case ABackground.Style of bbsClear, bbsColor: { Solid background color } ATargetBGRA.FillRoundRectAntialias(x1,y1,x2,y2, rx, ry, {%H-}backcolor, ropt); bbsGradient: begin { Using multishape filler to merge background gradient and border } multi := TBGRAMultishapeFiller.Create; multi.PolygonOrder := poFirstOnTop; { Border will replace background } { Gradients } back := TBGRABitmap.Create(ATargetBGRA.Width, ATargetBGRA.Height, BGRAPixelTransparent); grect1 := rect(floor(x1),floor(y1),ceil(x2)+1,ceil(y2)+1); grect2 := grect1; { Gradient 1 } if ABackground.Gradient1EndPercent > 0 then begin grect1.Bottom := grect1.top + Round(((grect1.Bottom-grect1.Top) / 100) * ABackground.Gradient1EndPercent); gra := CreateGradient(ABackground.Gradient1, grect1); back.FillRect(grect1.Left, grect1.Top, grect1.Right, grect1.Bottom, gra, dmSet ); gra.Free; end; { Gradient 2 } if ABackground.Gradient1EndPercent < 100 then begin grect2.Top := grect1.Bottom; gra := CreateGradient(ABackground.Gradient2, grect2); back.FillRect(grect2.Left, grect2.Top, grect2.Right, grect2.Bottom, gra, dmSet ); gra.Free; end; multi.AddRoundRectangle(x1,y1,x2,y2, rx, ry, back, ropt); multi.Draw(ATargetBGRA); multi.Free; back.Free; end; end; end; procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground; ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false); var extraSize: single; begin if AHasNoBorder then extraSize := 0.5 else extraSize := 0; RenderBackgroundF(ARect.Left-extraSize, ARect.Top-extraSize, ARect.Right-1+extraSize, ARect.Bottom-1+extraSize,ABackground,ATargetBGRA,ARounding); end; end.