source: trunk/Packages/bgracontrols/bctools.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 15.2 KB
Line 
1{ General framework methods for rendering background, borders, text, etc.
2
3 Copyright (C) 2012 Krzysztof Dibowski dibowski at interia.pl
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version with the following modification:
9
10 As a special exception, the copyright holders of this library give you
11 permission to link this library with independent modules to produce an
12 executable, regardless of the license terms of these independent modules,and
13 to copy and distribute the resulting executable under terms of your choice,
14 provided that you also meet, for each linked independent module, the terms
15 and conditions of the license of that module. An independent module is a
16 module which is not derived from or based on this library. If you modify
17 this library, you may extend this exception to your version of the library,
18 but you are not obligated to do so. If you do not wish to do so, delete this
19 exception statement from your version.
20
21 This program is distributed in the hope that it will be useful, but WITHOUT
22 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
24 for more details.
25
26 You should have received a copy of the GNU Library General Public License
27 along with this library; if not, write to the Free Software Foundation,
28 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29}
30
31unit BCTools;
32
33{$mode objfpc}{$H+}
34
35interface
36
37uses
38 Classes, SysUtils, Graphics, BGRABitmap, BGRABitmapTypes, bctypes,
39 Controls, BGRAGradientScanner;
40
41// This method prepare BGRABitmap for rendering BCFont type
42procedure AssignBCFont(AFont: TBCFont; out ATargetBGRA: TBGRABitmap);
43// Calculate text height and width (doesn't include wordwrap - just single line)
44procedure CalculateTextSize(const AText: String; AFont: TBCFont;
45 out ANewWidth, ANewHeight: integer);
46// As long as there are differences between the size of the font, this method is useless
47procedure CalculateTextRect(const AText: String; AFont: TBCFont; out ARect: TRect);
48// This method correct TRect to border width. As far as border width is bigger,
49// BGRA drawing rectangle with offset (half border width)
50procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
51// Create BGRA Gradient Scanner based on BCGradient properties
52function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
53// Render arrow (used by BCButton with DropDownMenu style)
54procedure RenderArrow(out ATargetBGRA: TBGRABitmap; const ARect: TRect;
55 ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor = clBlack;
56 AOpacity: Byte = 255);
57// Render customizable backgroud (used e.g. by TBCButton, TBCPanel, TBCLabel)
58procedure RenderBackground(const ARect: TRect; const ABackground: TBCBackground;
59 out ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
60// Render customizable border (used e.g. by TBCButton, TBCPanel, TBCLabel)
61procedure RenderBorder(const ARect: TRect; const ABorder: TBCBorder;
62 out ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
63// Render BCFont (used e.g. by TBCButton, TBCPanel, TBCLabel)
64procedure RenderText(const ARect: TRect; const AFont: TBCFont;
65 const AText: String; out ATargetBGRA: TBGRABitmap);
66// Return LCL horizontal equivalent for BCAlignment
67function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
68// Return LCL vertical equivalent for BCAlignment
69function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
70
71implementation
72
73uses Types, BGRAPolygon, BGRAFillInfo, BGRAText, math, LCLType, LCLIntf;
74
75procedure CalculateTextRect(const AText: String; AFont: TBCFont; out ARect: TRect);
76var
77 tmp: TBGRABitmap;
78 flags: LongInt;
79begin
80 tmp := TBGRABitmap.Create(0,0);
81 AssignBCFont(AFont,tmp);
82 flags := 0;
83 case AFont.TextAlignment of
84 bcaCenter, bcaCenterBottom, bcaCenterTop: flags := flags or DT_CENTER;
85 bcaRightCenter, bcaRightBottom, bcaRightTop: flags := flags or DT_RIGHT;
86 end;
87 case AFont.TextAlignment of
88 bcaLeftTop, bcaCenterTop, bcaRightTop: flags := flags or DT_TOP;
89 bcaLeftCenter, bcaCenter, bcaRightCenter: flags := flags or DT_VCENTER;
90 bcaLeftBottom, bcaCenterBottom, bcaRightBottom: flags := flags or DT_BOTTOM;
91 end;
92 if AFont.EndEllipsis then
93 flags := flags or DT_END_ELLIPSIS;
94 // This condition is from TCanvas.TextRect
95 if AFont.WordBreak then
96 begin
97 flags := flags or DT_WORDBREAK;
98 if AFont.EndEllipsis then
99 flags := flags and not DT_END_ELLIPSIS;
100 end;
101 if AFont.SingleLine then
102 flags := flags or DT_SINGLELINE;
103
104 flags := flags or DT_CALCRECT;
105
106 LCLIntf.DrawText(tmp.Canvas.Handle, PChar(AText), Length(AText), ARect, flags);
107
108 tmp.Free;
109end;
110
111procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
112begin
113 if ABorder = nil then Exit;
114 Inc(ARect.Left, Round(ABorder.Width / 2));
115 Inc(ARect.Top, Round(ABorder.Width / 2));
116 Dec(ARect.Right, Round(ABorder.Width / 2) + 1);
117 Dec(ARect.Bottom, Round(ABorder.Width / 2) + 1);
118end;
119
120function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
121begin
122 Result := TBGRAGradientScanner.Create(
123 ColorToBGRA(ColorToRGB(AGradient.StartColor), AGradient.StartColorOpacity),
124 ColorToBGRA(ColorToRGB(AGradient.EndColor), AGradient.EndColorOpacity),
125 AGradient.GradientType, PointF(ARect.Left + Round(
126 ((ARect.Right - ARect.Left) / 100) * AGradient.Point1XPercent),
127 ARect.Top + Round(((ARect.Bottom - ARect.Top) / 100) * AGradient.Point1YPercent)),
128 PointF(ARect.Left + Round(((ARect.Right - ARect.Left) / 100) *
129 AGradient.Point2XPercent), ARect.Top + Round(
130 ((ARect.Bottom - ARect.Top) / 100) * AGradient.Point2YPercent)),
131 AGradient.ColorCorrection, AGradient.Sinus);
132end;
133
134procedure RenderBorder(const ARect: TRect; const ABorder: TBCBorder; out
135 ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
136var
137 fiLight: TFillBorderRoundRectInfo;
138 rx,ry: Byte;
139 ropt: TRoundRectangleOptions;
140begin
141 if ABorder.Style=bboNone then Exit;
142
143 if ARounding = nil then
144 begin
145 rx := 0;
146 ry := 0;
147 ropt := [];
148 end else
149 begin
150 rx := ARounding.RoundX;
151 ry := ARounding.RoundY;
152 ropt := ARounding.RoundOptions;
153 end;
154
155 ATargetBGRA.RoundRectAntialias(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom,
156 rx, ry, ColorToBGRA(ColorToRGB(ABorder.Color),ABorder.ColorOpacity),
157 ABorder.Width, ropt);
158
159 if ABorder.LightWidth > 0 then
160 begin
161 //compute light position
162 fiLight := TFillBorderRoundRectInfo.Create(
163 ARect.Left, ARect.Top, ARect.Right, ARect.Bottom, rx,
164 ry, ABorder.Width + ABorder.LightWidth, ropt);
165 //check if there is an inner position
166 if fiLight.InnerBorder <> nil then
167 with fiLight.InnerBorder do //fill with light
168 ATargetBGRA.RoundRectAntialias(topleft.x, topleft.y, bottomright.x,
169 bottomright.y, radiusx, radiusY,
170 ColorToBGRA(ColorToRGB(ABorder.LightColor), ABorder.LightOpacity),
171 ABorder.LightWidth, ropt);
172 fiLight.Free;
173 end;
174end;
175
176procedure RenderText(const ARect: TRect; const AFont: TBCFont;
177 const AText: String; out ATargetBGRA: TBGRABitmap);
178var
179 shd: TBGRABitmap;
180 hal: TAlignment;
181 val: TTextLayout;
182 st: TTextStyle;
183begin
184 AssignBCFont(AFont,ATargetBGRA);
185
186 hal := BCAlign2HAlign(AFont.TextAlignment);
187 val := BCAlign2VAlign(AFont.TextAlignment);
188
189 FillChar(st, SizeOf(st),0);
190
191 st.Wordbreak := AFont.WordBreak;
192 st.Alignment := hal;
193 st.Layout := val;
194 st.SingleLine := AFont.SingleLine;
195 st.EndEllipsis := AFont.EndEllipsis;
196
197 if AFont.Shadow then
198 begin
199 shd := TBGRABitmap.Create(ATargetBGRA.Width,ATargetBGRA.Height,BGRAPixelTransparent);
200 shd.FontName := ATargetBGRA.FontName;
201 shd.FontStyle := ATargetBGRA.FontStyle;
202 shd.FontQuality := ATargetBGRA.FontQuality;
203 shd.FontHeight := ATargetBGRA.FontHeight;
204 shd.TextRect(ARect, ARect.Left, ARect.Top, AText, st, ColorToBGRA(ColorToRGB(AFont.ShadowColor),
205 AFont.ShadowColorOpacity));
206 BGRAReplace(shd, shd.FilterBlurRadial(AFont.ShadowRadius, rbFast));
207 ATargetBGRA.PutImage(AFont.ShadowOffsetX, AFont.ShadowOffsetY,
208 shd, dmDrawWithTransparency);
209 shd.Free;
210 end;
211
212 ATargetBGRA.TextRect(ARect,ARect.Left,ARect.Top,AText,st,ColorToBGRA(ColorToRGB(AFont.Color)));
213
214end;
215
216function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
217begin
218 if AAlign in [bcaCenter, bcaCenterTop, bcaCenterBottom] then
219 Result := taCenter
220 else if AAlign in [bcaRightCenter, bcaRightTop, bcaRightBottom] then
221 Result := taRightJustify
222 else
223 Result := taLeftJustify;
224end;
225
226function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
227begin
228 if AAlign in [bcaCenter, bcaLeftCenter, bcaRightCenter] then
229 Result := tlCenter
230 else if AAlign in [bcaCenterBottom, bcaLeftBottom, bcaRightBottom] then
231 Result := tlBottom
232 else
233 Result := tlTop;
234end;
235
236procedure AssignBCFont(AFont: TBCFont; out ATargetBGRA: TBGRABitmap);
237var c: TBitmap;
238begin
239 // Canvas is need for calculate font height
240 c := TBitmap.Create;
241 c.Canvas.Font.Name := AFont.Name;
242 c.Canvas.Font.Style := AFont.Style;
243 case AFont.FontQuality of
244 fqSystem: c.Canvas.Font.Quality := fqNonAntialiased;
245 fqFineAntialiasing: c.Canvas.Font.Quality := fqAntialiased;
246 fqFineClearTypeRGB: c.Canvas.Font.Quality := fqProof;
247 fqSystemClearType: c.Canvas.Font.Quality := fqCleartype;
248 end;
249 // FontAntialias is only backward compability for FontQuality property.
250 // FontQuality is published in TBCFont so we don't need FontAntialias anymore.
251 //ATargetBGRA.FontAntialias := AFont.FontAntialias;
252 ATargetBGRA.FontStyle := AFont.Style;
253
254 // If font quality is system, then we can leave default values. LCL will
255 // handle everything (when name is "default" or height 0)
256 if AFont.FontQuality in [fqSystem,fqSystemClearType] then
257 begin
258 ATargetBGRA.FontName := AFont.Name;
259 ATargetBGRA.FontHeight := AFont.Height;
260 end
261 else
262 begin
263 // Getting real font name
264 if SameText(AFont.Name,'default')
265 then ATargetBGRA.FontName := GetFontData(c.Canvas.Font.Handle).Name
266 else ATargetBGRA.FontName := AFont.Name;
267
268 // Calculate default height, because when font quality is <> fqSystemXXX
269 // then if height is 0 then it is 0 for real
270 if (AFont.Height=0) then
271 ATargetBGRA.FontHeight := -c.Canvas.TextHeight('Bgra')
272 else
273 ATargetBGRA.FontHeight := AFont.Height;
274 end;
275 ATargetBGRA.FontQuality := AFont.FontQuality;
276 c.Free;
277end;
278
279procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth,
280 ANewHeight: integer);
281var
282 s: TSize;
283 tmp: TBGRABitmap;
284begin
285 if (AText = '') or (AFont = nil) then
286 begin
287 ANewWidth := 0;
288 ANewHeight := 0;
289 Exit;
290 end;
291
292 {TODO: Check why BGRATextSize doesn't work. BGRABitmap call this method
293 and it work. Temporary I'm creating temp bitmap
294 }
295 {s := BGRAText.BGRATextSize(AFont,AFont.FontQuality,AText,FontAntialiasingLevel);
296 if (s.cy >= 24) and AFont.FontAntialias then
297 s := BGRAText.BGRATextSize(AFont,AFont.FontQuality,AText,4);}
298 tmp := TBGRABitmap.Create(0,0);
299 AssignBCFont(AFont, tmp);
300
301 s := tmp.TextSize(AText);
302 tmp.Free;
303
304 { shadow offset }
305 if AFont.Shadow then
306 begin
307 Inc(s.cx, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius);
308 Inc(s.cy, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius);
309 end;
310
311 ANewWidth := s.cx;
312 ANewHeight := s.cy;
313end;
314
315procedure RenderArrow(out ATargetBGRA: TBGRABitmap; const ARect: TRect;
316 ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor; AOpacity: Byte);
317var
318 p: ArrayOfTPointF;
319 n: byte;
320 temp: TBGRABitmap;
321 w: Integer;
322begin
323 // We can't draw outside rect
324 w := Min(ASize, ARect.Right - ARect.Left);
325
326 { Poly }
327 SetLength(p, 3);
328
329 temp := TBGRABitmap.Create(w+1, w+1,BGRAPixelTransparent);
330
331 case ADirection of
332 badDown:
333 begin;
334 p[0].x := 0;
335 p[0].y := 0;
336
337 p[1].x := w;
338 p[1].y := 0;
339
340 p[2].x := Round(w/2);
341 p[2].y := w;
342 end;
343 badUp:
344 begin
345 p[0].x := Round(w/2);
346 p[0].y := 0;
347
348 p[1].x := 0;
349 p[1].y := w;
350
351 p[2].x := w;
352 p[2].y := w;
353 end;
354 badLeft:
355 begin
356 p[0].x := 0;
357 p[0].y := Round(w/2);
358
359 p[1].x := w;
360 p[1].y := 0;
361
362 p[2].x := w;
363 p[2].y := w;
364 end;
365 badRight:
366 begin
367 p[0].x := w;
368 p[0].y := Round(w/2);
369
370 p[1].x := 0;
371 p[1].y := 0;
372
373 p[2].x := 0;
374 p[2].y := w;
375 end;
376 end;
377
378 // Fill n times to get best quality
379 for n := 1 to 6 do
380 temp.FillPolyAntialias(p, ColorToBGRA(ColorToRGB(AColor),AOpacity));
381
382 ATargetBGRA.PutImage(
383 ARect.Right-Round( ((ARect.Right-ARect.Left)/2) + (w/2) ),
384 ARect.Bottom-Round( ((ARect.Bottom-ARect.Top)/2) + (w/2) ),
385 temp,
386 dmDrawWithTransparency
387 );
388 temp.Free;
389end;
390
391procedure RenderBackground(const ARect: TRect; const ABackground: TBCBackground; out
392 ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
393var
394 backcolor: TBGRAPixel;
395 multi: TBGRAMultishapeFiller;
396 back: TBGRABitmap;
397 grect1, grect2: TRect;
398 gra: TBGRAGradientScanner;
399 fiLight: TFillBorderRoundRectInfo;
400 rx,ry: Byte;
401 ropt: TRoundRectangleOptions;
402begin
403
404 if ARounding = nil then
405 begin
406 rx := 0;
407 ry := 0;
408 ropt := [];
409 end else
410 begin
411 rx := ARounding.RoundX;
412 ry := ARounding.RoundY;
413 ropt := ARounding.RoundOptions;
414 end;
415
416 { Background color }
417 case ABackground.Style of
418 bbsClear: backcolor := BGRAPixelTransparent;
419 // TODO: Why if I use some system colors like clBtnFace, clActiveCaption etc.
420 // without ColorToRGB, I always get Black? Interface: QT
421 bbsColor: backcolor := ColorToBGRA(ColorToRGB(ABackground.Color), ABackground.ColorOpacity);
422 end;
423
424 case ABackground.Style of
425 bbsClear, bbsColor:
426 { Solid background color }
427 ATargetBGRA.FillRoundRectAntialias(ARect.Left, ARect.Top, ARect.Right,
428 ARect.Bottom, rx, ry, backcolor, ropt);
429 bbsGradient:
430 begin
431 { Using multishape filler to merge background gradient and border }
432 multi := TBGRAMultishapeFiller.Create;
433 multi.PolygonOrder := poFirstOnTop; { Border will replace background }
434
435 { Gradients }
436 back := TBGRABitmap.Create(ATargetBGRA.Width, ATargetBGRA.Height, BGRAPixelTransparent);
437 grect1 := ARect;
438 grect2 := ARect;
439 { Gradient 1 }
440 if ABackground.Gradient1EndPercent > 0 then
441 begin
442 grect1.Bottom := Round((grect1.Bottom / 100) * ABackground.Gradient1EndPercent);
443 gra := CreateGradient(ABackground.Gradient1, grect1);
444 back.FillRect(grect1.Left, grect1.Top, grect1.Right, grect1.Bottom,
445 gra, dmSet
446 );
447 gra.Free;
448 end;
449 { Gradient 2 }
450 if ABackground.Gradient1EndPercent < 100 then
451 begin
452 if grect1.Bottom < ARect.Bottom then
453 grect2.Top := grect1.Bottom - 1;
454 gra := CreateGradient(ABackground.Gradient2, grect2);
455 back.FillRect(grect2.Left, grect2.Top, grect2.Right, grect2.Bottom,
456 gra, dmSet
457 );
458 gra.Free;
459 end;
460
461 multi.AddRoundRectangle(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom,
462 rx, ry, back, ropt);
463
464 multi.Draw(ATargetBGRA);
465 multi.Free;
466 back.Free;
467 end;
468 end;
469end;
470
471end.
472
Note: See TracBrowser for help on using the repository browser.