📜 ⬆️ ⬇️

Smooth text scaling

Scaling text is not as trivial a task as it may seem at first glance. With a simple change in the font size, we cannot get a smooth and proportional change in the width of the text. Changes occur "abruptly", which greatly hinders the development of various editors, graphs, diagrams, wherever scaling is used.

image

As an example. Developed a stamp editor. Due to the specifics of the subject area, work is being done with “microscopic” fonts, the size of which is both fractional and extremely small. Without scale is not enough. However, if at the maximum scale they put all the texts as they should, made the alignment, and everything is beautiful, then when returning to the “normal” scale, all the formatting can “fly”.

image
')
With a large scale, the logo on the right looks good. In the process of decreasing the scale, the situation appears from time to time, as shown in the figure on the left - the inscriptions “crawl away”.

image

The inscription consists of two parts. On the left, we see something like a merged text that looks like a single whole. But when reducing the scale between the inscriptions, a gap appears noticeably.
The scale function in such projects is an extremely important thing. And what was done with a large scale, should look also at any scale. No "small" shifts and errors are unacceptable.

Test application


To test the scale methods, we will make a small application. The source is presented in the archive.

image


TxDrawZoomFunc = function (ACanvas : TCanvas; //    ARect : TRect; //   AZoom, ASize : double;// ,   AText : string //    ) : boolean; //   


 //****************************************************************************** //        //****************************************************************************** function DrawZoomCalcRect (ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : TRect; var siz : TSize; begin //--    ,   --------------------------------- ACanvas.Font.Height := -trunc(ASize * ACanvas.Font.PixelsPerInch/72); //--         ----------- GetTextExtentPoint32(ACanvas.Handle,PWideChar(AText),Length(AText), siz); //---------------------------------------------------------------------------- //  ,     , //       //---------------------------------------------------------------------------- result := ARect; result.Right := result.Left + round (AZoom * siz.Width); result.Bottom := result.Top + round (AZoom * siz.Height); end; 


A number of auxiliary functions are used:
 //****************************************************************************** //     //****************************************************************************** function WidthRect (ARect : TRect) : Integer; begin result := ARect.Right - ARect.Left; end; function HeightRect (ARect : TRect) : Integer; begin result := ARect.Bottom - ARect.Top; end; //****************************************************************************** //      //****************************************************************************** function CheckParamsValid (ACanvas : TCanvas; ARect : TRect; AObject : TObject; AObjChecked : boolean = true) : boolean; begin result := (ACanvas <> nil) and ((not AObjChecked) or (AObject <> nil)) and (WidthRect (ARect) > 0) and (HeightRect (ARect)>0); end; //****************************************************************************** //     ARect //****************************************************************************** function CreateBmpRect (ARect : TRect) : TBitmap; begin result := TBitmap.Create; result.Width := abs (WidthRect (ARect)); result.Height := abs (HeightRect (ARect)); end; 


Method 1 "In the forehead." Fractional Font Size


If you solve the problem "in the forehead", then this method suggests itself: to change the font height depending on the scale. For this, a parameter such as Font.Height will do. This is the font height in pixels, and according to the logic of things, this should lead to a smooth zoom.

Font.Height=−trunc(AZoom∗ASize∗Font.PixelsPerInch/72);

Where:


Where did the formula come from? The height of the font in Windows is expressed in paragraphs, which came, in turn, from typography.

1 inch = 25.4 mm = 72 points

Thus, the first scaling function is as follows.

 //****************************************************************************** //  " " // 1  = 25.4  = 72  //****************************************************************************** function DrawZoomTextSimple (ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var rct : TRect; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText<>''); if not result then exit; rct := DrawZoomCalcRect(ACanvas, ARect, AZoom, ASize, AText); with Acanvas do begin Pen.Color := clGreen; Pen.Width := 1; Rectangle(rct); Font.Height := -trunc(AZoom * ASize * Font.PixelsPerInch / 72); TextOut (ARect.Left, ARect.Top, AText); GDiffWidth := WidthRect(rct) / TextWidth(AText); end; end; 

The result is visible in the figure.

image

If the left side of the two is clearly located on the gray line, then with a slight change in the scale in the right figure, the gray line crosses the two in the center.

The result is unsatisfactory.

Method 2 “World Coordinates” SetGraphicsMode


It is clear that the ax does not shoe a flea. You must use the tools that Windows provides.

 function SetGraphicsMode(hdc: HDC; iMode: Integer): Integer; 



The algorithm works as follows:

  1. Switch DC to GM_ADVANCED;
  2. Initialize the fields of the TXForm structure (which is actually a matrix). The conversion will be carried out according to the following formulas:
    x′=x∗eM11+y∗eM21+eDx
    y′=x∗eM12+y∗eM22+eDy
    As you can see, to realize the scale, we are interested in the eM11 and eM22 fields;
  3. Assign the transformation matrix: SetWorldTransform (DC, xFrm);
  4. To draw the text in “usual” coordinates, without taking into account the scale, in its “usual” size;
  5. Return the transformation to its original state.
  6. Revert the previous mode.

The second scaling function is as follows:

 //****************************************************************************** //  SetGraphicsMode (GM_ADVANCED) //    //****************************************************************************** function DrawZoomTextWorldMode(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var rct : TRect; oldM : integer; xFrm : TXForm; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText <> ''); if not result then exit; //--      , =1 ------------- rct := DrawZoomCalcRect(ACanvas,ARect,1,ASize,AText); //--  ""    ---------------------- oldM := SetGraphicsMode(ACanvas.Handle, GM_ADVANCED); try //--   ------------------------------------------------------ FillChar(xFrm,SizeOf(xFrm),0); //--    ------------------------------------- // x' = x * eM11 + y * eM21 + eDx // y' = x * eM12 + y * eM22 + eDy xFrm.eM11 := AZoom; xFrm.eM22 := AZoom; //--    -------------------------------------- SetWorldTransform(ACanvas.Handle, xFrm); //--  ,        --------------------- with Acanvas do begin Pen.Color := clRed; Pen.Width := 1; Rectangle (rct); TextOut (rct.Left, rct.Top, AText); //--    /   -------- GDiffWidth := WidthRect(rct)/TextWidth(AText); end; finally //--      -------------------------------- xFrm.eM11 := 1; xFrm.eM22 := 1; SetWorldTransform(ACanvas.Handle, xFrm); //--     --------------------------------------------- SetGraphicsMode(ACanvas.Handle, oldM); end; end; 

The result is:

image

The situation is similar to the previous one. On the left, the deuce is comfortably located between the gray borders of the cells, on the right, the cell line crosses it. Those. they didn’t get rid of “twitching” at scale.

However, there are positive moments: you can draw without worrying about the scale. That is, we have some very large function that draws something very disproportionately steep, but without taking into account the scale. Before the call, we can assign a transformation matrix, thereby obtaining the possibility of scaling. Involving, in this case, the parameters eDx and eDy, we also get the displacement.

It should be noted that the thickness of the lines also varies with the scale. Additional goodies and transformations are off topic.

Meanwhile, the desired result is not achieved.

Method 3 "Scale" SetMapMode / MM_ISOTROPIC


Coordinate conversion by Windows using Method 2 of SetGraphicsMode (GM_ADVANCED) does not end. Consider a bunch of the following functions:

 function SetMapMode(DC: HDC; p2: Integer): Integer; function SetWindowExtEx(DC: HDC; XExt, YExt: Integer; Size: PSize): BOOL; function SetViewportExtEx(DC: HDC; XExt, YExt: Integer; Size: PSize): BOOL; 

The SetMapMode function causes the selected device context to consider a pixel to be something else. Suppose a pixel can actually be 0.001 inches. It depends on the parameter p2, which can take the following values:


What does the phrase "X from the left - to the right, Y from below - up" mean? This means that the coordinates on X are quite ordinary, but on Y they are negative. That is, if you need to draw an ellipse in a rectangle (10,10,1000,1000), then to see it without additional transformations, you need to write Ellipse (10, -10,1000, -1000).

But we are interested in scale. And the most common, the same on all axes. Therefore, we use p2 = MM_ISOTROPIC.

After setting the mode we need to set the scale factor. This is done by a couple of functions SetWindowExtEx / SetViewportExtEx

  1. Setting the logical output window
    SetWindowExtEx (DC, logical width, logical height, nil);
  2. Installing the real output window
    SetViewportExtEx (DC, real width, real height, nil);

Now our scale is such that the actual width (height) of the screen, shape, paintbox, arbitrary rectangle, etc. corresponds to some logical width (height), for example, paper in the printer, or the width of a large image that needs to be displayed to scale, etc.

The scale factor is: F = (real value) / (logical value).
Since the scale should be the same on both axes, Windows chooses the smallest factor.

What is a logical quantity. If you want to reflect a certain image, then it will be its width and height, and the real value will be the area in pixels where you want to reflect.

The conversion functions are:
x '= x * F
y '= y * F

Thus, the actual value for the width: Zoom * Width and height: Zoom * Height.
The third scaling function looks like this:

 //****************************************************************************** // :    SetMapMode/SetWindowExtEx/SetViewportExtEx //****************************************************************************** function DrawZoomTextMapMode (ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var DC : HDC; rct : TRect; Old : integer; w,h : Integer; begin result := CheckParamsValid(ACanvas,ARect,nil,false); if not result then exit; //--   ,       ---- rct := DrawZoomCalcRect(ACanvas,ARect,1,ASize,AText) and (AText <> ''); //--       ----------------------------- DC := ACanvas.Handle; w := WidthRect(ARect); h := heightRect(ARect); //--     MM_ISOTROPIC    X  Y //--   (..       ) Old := SetMapMode(DC, MM_ISOTROPIC); //--     ---------------------- SetWindowExtEx(DC, w, h, nil); //--     ------------------------ SetViewportExtEx(DC, round(AZoom*W), round(AZoom*H), nil); //--  ------------------------------------------------- try with ACanvas do begin Pen.Color := clPurple; Pen.Width := 1; Rectangle(rct); TextOut (ARect.Left, ARect.Top, AText); GDiffWidth := WidthRect(rct)/TextWidth(AText); end; finally SetMapMode(DC, Old); end; end; 

However, the result is still not happy:

image

The situation is absolutely identical to the two previous ones.

The advantages of the method are similar to method 2 - you can not worry about the scale while writing the “drawing” function, but there is no possibility of moving. Transformation of movement - especially manual work.

In general, this function is not sharpened by transformation. It is more suitable for displaying something in units of this something. It is a kind of “translator” from one language of measurement to a display language.

Method 4 "Inches" SetMapMode / MM_HIENGLISH


But try another option. In method 3, the SetMapMode function is described in detail. Including flags of translation from metric systems to screen. Let's try to work in the inch coordinate system. Why not in millimeters - to avoid additional conversions. After all, we still initially had some inch indicators. Why do they additionally do at 25.4 (see method 1).

What inspired. All the same, the value of 0.001 inch is very small discrete. But what if?
The fourth scaling function is:

 //****************************************************************************** //     SetMapMode/SetWindowExtEx/SetViewportExtEx // MM_HIENGLISH -      0.001 . //****************************************************************************** function DrawZoomTextMapModeHIENGLISH(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var DC : HDC; Old: integer; pnt : TPoint; rct : TRect; siz : TSize; tmp : Integer; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText <> ''); if not result then exit; //--    ,     --------- ACanvas.Font.Height := -trunc(ASize * ACanvas.Font.PixelsPerInch / 72); tmp := ACanvas.Font.Height; DC := ACanvas.Handle; //--       ------------------------ pnt.X := GetDeviceCaps(DC,LogPixelsX); //--       -------------------------- pnt.Y := GetDeviceCaps(DC,LogPixelsY); //--     (0.001 )----------------------------------- GetTextExtentPoint32(DC,PWideChar(AText),Length(AText), siz); rct.Top := -round(1000* AZoom * ARect.Top / pnt.Y); rct.Left := round(1000* AZoom * ARect.Left / pnt.X); rct.Right := rct.Left + round(1000* AZoom * siz.Width / pnt.X); rct.Bottom := rct.Top - round(1000* AZoom * siz.Height / pnt.Y); ACanvas.Font.Height := -round(rct.Bottom-rct.Top) ; Old := SetMapMode(DC, MM_HIENGLISH); try with Acanvas do begin Pen.Color := clTeal; Pen.Width := 1; Rectangle (rct); TextOut (rct.Left, rct.Top, AText); GDiffWidth := WidthRect(rct) / TextWidth(AText); end; finally SetMapMode(DC, Old); ACanvas.Font.Height := tmp; end; end; 

Unfortunately, the result is no better than the previous ones:

image

Method 5 "Character Rendering"


In all previous methods there is a feeling that the integer part is TLogFont. lfHeight perceptibly spoils life and does not allow for fine-tuning for a certain scale. Eh ... it would be fractional ... Well, let's try to solve the problem differently.

The basic idea is this: a walk through all the characters of the text, counting the beginning on the X axis, where the symbol should be displayed. The conversion factor is calculated initially, as the ratio of the calculated width and real.

 //****************************************************************************** //    //****************************************************************************** function DrawZoomTextChar(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var rct : TRect; fct : double; i : Integer; w : Integer; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText <> ''); if not result then exit; //-- ,        ---------- rct := DrawZoomCalcRect(ACanvas,ARect,AZoom,ASize,AText); try with ACanvas do begin Pen.Color := clMaroon; Pen.Width := 1; Rectangle(rct); GDiffWidth := WidthRect (rct); //--   ---------------------------------------------- Font.Height := -trunc(AZoom * ASize * Font.PixelsPerInch/72); //--  ""    ---------------------------- fct := WidthRect (rct)/TextWidth(AText); //--     ,   ,  w := 0; for i := 1 to Length(AText) do begin TextOut (rct.Left, rct.Top, AText[i]); w := w + TextWidth(AText[i]); //--        ----- rct.Left := round (ARect.Left + w * fct); end; GDiffWidth := GDiffWidth / (rct.Left-ARect.Left); end; except result := false; end; end; 

Amazingly, it works:

image

Deuce tightly stuck to the line and does not leave it at any scale.

The first successful scaling method. The goal has been achieved, but I would like a better solution.

Method 6 “Bitmap Buffer”


The previous method consisted in the fact that a character-by-character “fit” occurred to the required size calculated in advance by shifting the start of the drawing of each character. And what if all the same to do on the basis of bitmap?

The idea is that the text is first drawn on some intermediate bitmap at a given scale. Let's call it a "combat" matrix. Then there is a stretch copying to another bitmap matrix, which is set to size, according to the counted values. After this, there is a “transparent” copying to the “working” canvas.

Function text:

 //****************************************************************************** //    TBitmap  StretchDraw //****************************************************************************** function DrawZoomTextBitmap(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var rct: TRect; val: TRect; siz: TSize; bmp: TBitmap; // - ""  dst: TBitmap; // -stretch  begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText <> ''); if not result then exit; //-- ,        ---------- rct := DrawZoomCalcRect(Acanvas,Arect,AZoom,ASize,AText); //--      ----------------------------- ACanvas.Font.Height := -trunc(AZoom * ASize * ACanvas.Font.PixelsPerInch / 72); GetTextExtentPoint32(ACanvas.Handle,PWideChar(AText),Length(AText), siz); val := ARect; val.Right := val.Left + siz.Width; val.Bottom := val.Top + siz.Height; //-- -,     ----------------------------------- bmp := CreateBMPRect (val);//  ,  ""  try with bmp.Canvas do begin Font.Assign(ACanvas.Font); Brush.Color := clWhite; TextOut(0,0,AText); end; //--      ---------------------------------- dst := CreateBmpRect(rct); //-- / ""   ,    dst.Canvas.StretchDraw(dst.Canvas.ClipRect,bmp); //--      --------------------------------------- dst.TransparentColor := clWhite; dst.Transparent := true; with ACanvas do begin Pen.Color := clBlue; Pen.Width := 1; Rectangle(rct); ACanvas.Draw(rct.Left,rct.Top,dst); end; GDiffWidth := WidthRect(rct) / dst.Width; finally if dst <> nil then dst.Free; bmp.Free; end; end; 

And this method also works perfectly:

image

The text seemed to stick to its cells. Extremely smooth scaling.

The second successful scaling method. The goal has been achieved, but I would like an even better solution. The last two methods are too resource intensive. This is now directly felt.

Method 7 "GDI +" Scale Font Size


So we came to an unambiguously correct and excellent tool, such as scaling and text output by GDI +.

There is nothing special to comment here. The main thing is the change in font size, according to the scale. And the text output using GDI + using antialiasing (TextRenderingHintAntiAlias). Everything else is quite understandable from the source:

 //****************************************************************************** //  GDI+     //****************************************************************************** function DrawZoomTextGDIPlus(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var clr : TColor; grp : TGPGraphics; brh : TGPSolidBrush; nam : TGPFontFamily; fsl : FontStyle; src : TGPRectF; fnt : TGPFont; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText<>''); if not result then exit; ACanvas.Font.Height := -trunc(AZoom * ASize * ACanvas.Font.PixelsPerInch / 72); grp := TGPGraphics.Create(ACanvas.Handle); try with ACanvas do begin clr := Font.Color; //--    --------------------------------------------- nam := TGPFontFamily.Create(Font.Name); //--    --------------------------------------------- fsl := FontStyleRegular; if fsBold in Font.Style then fsl := fsl + FontStyleBold; if fsItalic in Font.Style then fsl := fsl + FontStyleItalic; if fsUnderline in Font.Style then fsl := fsl + FontStyleUnderline; if fsStrikeOut in Font.Style then fsl := fsl + FontStyleStrikeout; //--    ""    ---- grp.SetTextRenderingHint(TextRenderingHintAntiAlias); //--    ,   ------------------------------- brh := TGPSolidBrush.Create(MakeColor(GetRValue(clr), GetGValue(clr), GetBValue(clr))); //--    ,  ""  ---------------------- Fnt := TGPFont.Create(nam, ASize * Font.PixelsPerInch / 72, fsl, UnitPixel); //--  ""  ------------------------------- grp.MeasureString(AText,-1,fnt,MakePoint(ARect.Left*1.0, ARect.Top*1.0),src); //--  ""  ------------------------------- Pen.Color := clNavy; pen.Width := 1; Rectangle (round(src.X),round(src.Y), round(src.X + AZoom*src.Width), round(src.Y + AZoom*src.Height)); //--    ,     ------------------- GDiffWidth := AZoom*src.Width; Fnt.Free; //--      ------------------------------------- Fnt := TGPFont.Create(nam, AZoom * ASize * Font.PixelsPerInch / 72, fsl, UnitPixel); grp.SetTextRenderingHint(TextRenderingHintAntiAlias); grp.DrawString(AText, -1, Fnt, MakePoint(ARect.Left*1.0, ARect.Top*1.0), brh); //--        ------------------ grp.MeasureString(AText,-1,fnt,MakePoint(ARect.Left*1.0, ARect.Top*1.0),src); GDiffWidth := GDiffWidth / src.Width; end; except result := false; end; Fnt.free; brh.free; nam.free; grp.free; end; 

The result naturally exceeded all expectations. I will not give screens, because they are similar to the above in the last two methods. Feel the power of GDI + better by running the executable file.

Method 8 "GDI +" Scale Transformation


And again GDI +. But this time we will use the scale transformation. Those.We draw the text in its “normal” size, and the GDI + engine will be engaged in its scaling. Transformation is performed by calling the ScaleTransform (AZoom, AZoom).

 //****************************************************************************** //  GDI+     //****************************************************************************** function DrawZoomTextGDIPlusScale(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var clr : TColor; grp : TGPGraphics; brh : TGPSolidBrush; nam : TGPFontFamily; fsl : FontStyle; src : TGPRectF; fnt : TGPFont; pnt : TGPPointF; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText<>''); if not result then exit; grp := TGPGraphics.Create(ACanvas.Handle); try with ACanvas do begin clr := Font.Color; pnt := MakePoint(ARect.Left*1.0, ARect.Top*1.0); //--    --------------------------------------------- nam := TGPFontFamily.Create(Font.Name); //--    --------------------------------------------- fsl := FontStyleRegular; if fsBold in Font.Style then fsl := fsl + FontStyleBold; if fsItalic in Font.Style then fsl := fsl + FontStyleItalic; if fsUnderline in Font.Style then fsl := fsl + FontStyleUnderline; if fsStrikeOut in Font.Style then fsl := fsl + FontStyleStrikeout; //--    ""    ---- grp.SetTextRenderingHint(TextRenderingHintAntiAlias); //--    ,   ------------------------------- brh := TGPSolidBrush.Create(MakeColor(GetRValue(clr), GetGValue(clr), GetBValue(clr))); //--    ,  ""  ---------------------- Fnt := TGPFont.Create(nam, ASize * Font.PixelsPerInch / 72, fsl, UnitPixel); //--  ""  ------------------------------- grp.MeasureString(AText,-1,fnt,pnt,src); //--  ""  ------------------------------- Pen.Color := $00BC6C01; pen.Width := 1; Rectangle (round(AZoom*src.X),round(AZoom*src.Y), round(AZoom*(src.X + src.Width)), round(AZoom*(src.Y + src.Height))); //--    ---------------------------------- grp.ScaleTransform(AZoom,AZoom); grp.DrawString(AText, -1, Fnt, pnt, brh); GDiffWidth := 1; end; except result := false; end; Fnt.free; brh.free; nam.free; grp.free; end; 

The best result of all of the above.

Test results


In the test program, you can start collecting statistics by clicking the "Start" button. A sequential enumeration of all the presented methods at all possible scales in the program will be carried out. At the end of the work, the following diagram will be displayed:

image

The first column is the average drawing time in milliseconds. The second is the relative deviation of the calculated values ​​from the actual ones. Simply put, the first column - how little time the operation takes, the second - how high the result of scaling.

As you can see, the methods are divided into 2 groups - the first 4 with an unsatisfactory result of the scale, the second 4 - scaling is successful, what was wanted.

Strange, but the most clumsy first method for speed showed better results than its clever counterparts in a group of losers. True, his deviations from the calculated values ​​are the biggest.

The clear winner is method 8 “GDI +” with scale transformation.
Therefore, we will draw the text in GDI + as a separate function.

Function of smooth scaling of the text with rotation on the set corner and anti-aliasing


 //****************************************************************************** //   GDI+ //****************************************************************************** function DrawGDIPlusText (ACanvas : TCanvas; ARect : TRect; Angle, ASize : double; AText : string; AZoom : double = 1) : boolean; var clr : TColor; grp : TGPGraphics; brh : TGPSolidBrush; nam : TGPFontFamily; fsl : FontStyle; fnt : TGPFont; pnt : TGPPointF; begin result := CheckParamsValid(ACanvas,ARect,nil,false) and (AText<>''); if not result then exit; grp := TGPGraphics.Create(ACanvas.Handle); try with ACanvas do begin clr := Font.Color; //--    --------------------------------------------- nam := TGPFontFamily.Create(Font.Name); //--    --------------------------------------------- fsl := FontStyleRegular; if fsBold in Font.Style then fsl := fsl + FontStyleBold; if fsItalic in Font.Style then fsl := fsl + FontStyleItalic; if fsUnderline in Font.Style then fsl := fsl + FontStyleUnderline; if fsStrikeOut in Font.Style then fsl := fsl + FontStyleStrikeout; //--    ,   ------------------------------- brh := TGPSolidBrush.Create(MakeColor(GetRValue(clr),GetGValue(clr),GetBValue(clr))); //--    ,  ""  ---------------------- Fnt := TGPFont.Create(nam, ASize * Font.PixelsPerInch / 72, fsl, UnitPixel); //--    ""    ---- grp.SetTextRenderingHint(TextRenderingHintAntiAlias); //--     -------------------------------------- pnt := MakePoint(ARect.Left*1.0, ARect.Top*1.0); //--  ,  ,       grp.TranslateTransform(pnt.X,pnt.y); //--   ,    ------------------ if Angle <> 0 then begin //--    ---------------------------------- grp.RotateTransform(Angle); end; //--      ""  ------------------- pnt := MakePoint(0.0,0.0); //--   ,    ------------------ if AZoom <> 1 then begin grp.ScaleTransform(AZoom,AZoom); end; //--      ------------------------------------- grp.DrawString(AText, -1, Fnt, pnt, brh); end; except result := false; end; Fnt.free; brh.free; nam.free; grp.free; end; 

Small conclusions and comments



As a demonstration of the latest statements, the statistics output function is written using SetGraphicsMode, which is responsible for scale and offset, and text output, including at an angle, using the DrawGDIPlusText function.

In the shape of:
 type TFmMain = class(TForm) … private FList : TxZoomStatList; //   (utlZoomStat) FListPoint : TPoint; FMouseDown : boolean; FMousePoint: TPoint; FProcessing : boolean; … End; procedure TFmMain.pbMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin FMouseDown := (Button = mbLeft) and //--  -    ----------------- (ComboBox1.ItemIndex=ComboBox1.Items.Count-1); if FMouseDown then begin //--  ,    ---------------- FMousePoint := Point(X,Y); //--    --------------------------------- FListPoint := Point(FList.OffX, FList.OffY); end; end; procedure TFmMain.pbMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if FMouseDown then begin //--    ------------------------------------------ FList.OffX := FListPoint.X + X-FMousePoint.X; FList.OffY := FListPoint.Y + Y-FMousePoint.Y; //--   ----------------------------------------------- pbPaint(Sender); end; end; procedure TFmMain.pbMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin //--   --------------------- FMouseDown := false; end; 


Description of class statistics
 type //****************************************************************************** //    //****************************************************************************** PxZoomStat = ^TxZoomStat; TxZoomStat = packed record FIndex : Integer; FColor : TColor; FName : string; FCount : Integer; FTime : extended; FDiff : extended; FTimeC : extended; FDiffC : extended; FTimeR : TRect; FDiffR : TRect; end; TxZoomStatList = class private FOffX : Integer; FOffY : Integer; FList : TList; FGDIPlus : boolean; function GetCount : Integer; function GetItem (Index : Integer) : PxZoomStat; public Constructor Create; virtual; Destructor Destroy; override; function Add (AIndex : Integer; AName : string; ATime, ADiff : Extended) : Integer; overload; function Add (AIndex : Integer; ATime, ADiff : Extended) : PxZoomStat; overload; procedure Delete (Index : Integer); procedure Clear; property Count : Integer read GetCount; property Items[Index : Integer] : PxZoomStat read GetItem; default; //-------------------------------------------------------------------------- property GDIPlus : boolean read FGDIPlus write FGDIPlus; property OffX : Integer read FOffX write FOffX; property OffY : Integer read FOffY write FOffY; end; 


We draw statistics. DrawZoomStatList:
 //****************************************************************************** //   //   .    ,      //  .  ,      . //        // SetGraphicsMode(DC, GM_ADVANCED); //****************************************************************************** function DrawZoomStatList(ACanvas : TCanvas; ARect : TRect; AZoom, ASize : double; AText : string) : boolean; var lst : TxZoomStatList; //     (  utlZoomStat) rct : TRect; val : TRect; str : string; i : Integer; p : PxZoomStat; wBar : Integer; //------------------------------------------------------------------------------ maxTime : Extended; maxDiff : Extended; minTime : Extended; minDiff : Extended; wTime : Extended; wDiff : Extended; //--  ------------------------------------------------------------------- DC : HDC; fnt : hFont; tmp : hFont; //-------------------------------------- oldM : integer; xFrm : TXForm; begin lst := xGZoomList(false); result := CheckParamsValid(ACanvas,ARect,lst,true); if not result then exit; DC := ACanvas.Handle; maxTime :=-1; maxDiff :=-1; minTime := MaxInt; minDiff := MaxInt; for i := 0 to lst.Count-1 do begin p := lst[i]; if (p = nil) or (p^.FCount = 0) then continue; p^.FTimeC := p^.FTime / p^.FCount; p^.FDiffC := p^.FDiff / p^.FCount; if p^.FTimeC > maxTime then maxTime := p^.FTimeC; if p^.FTimeC < minTime then minTime := p^.FTimeC; if p^.FDiffC > maxDiff then maxDiff := p^.FDiffC; if p^.FDiffC < minDiff then minDiff := p^.FDiffC; end; wTime := (maxTime - minTime) * 0.1; minTime := minTime - wTime; maxTime := maxTime + wTime; wDiff := (maxDiff - minDiff) * 0.1; minDiff := minDiff - wDiff; maxDiff := maxDiff + wDiff; with ACanvas do begin Font.Height := -trunc(ASize * Font.PixelsPerInch/72); wBar := TextWidth('F=0000.00000') div 2; //      end; //--       ----------------------------- oldM := SetGraphicsMode(DC, GM_ADVANCED); //--   ------------------------------------------------------ FillChar(xFrm,SizeOf(xFrm),0); //--    ------------------------------------- xFrm.eM11 := AZoom; //     ,  =1 xFrm.eM22 := AZoom; //     ,  =1 xFrm.eDx := lst.FOffX; //   X,      xFrm.eDy := lst.FOffY; //   Y,      //--    -------------------------------------- SetWorldTransform(DC, xFrm); rct := ARect; rct.Top := rct.Top + 10; rct.Bottom := rct.Top + round ( ASize * 190/6.5); //      if wTime <> 0 then wTime := (rct.Bottom - rct.Top) / (minTime - maxTime); if wDiff <> 0 then wDiff := (rct.Bottom - rct.Top) / (minDiff - maxDiff); try with ACanvas do begin val := rct; val.Left := val.Left + wBar; val.Right := val.Left + wBar; Pen.Width := 1; for i := 0 to lst.Count-1 do begin p := lst[i]; if (p = nil) or (p^.FCount = 0) then continue; Pen.Color := Darker(p^.FColor,10); //--    ------------------------------- OffsetRect (val,wBar,0); Brush.Color := Lighter(p^.FColor,50); val.Top := val.Bottom-round (wTime*(minTime-p^.FTimeC)); Rectangle(val); p^.FTimeR := val; //--    -------------------------- OffsetRect (val,wBar,0); Brush.Color := Lighter(p^.FColor,10); val.Top := val.Bottom-round (wDiff*(minDiff-p^.FDiffC)); Rectangle(val); p^.FDiffR := val; OffsetRect (val,wBar,0); end; for i := 0 to lst.Count-1 do begin p := lst[i]; if (p = nil) or (p^.FCount = 0) then continue; Brush.Style := bsClear; Font.Color := Darker(p^.FColor,10); val := p^.FTimeR; str := 't='+FormatFLoat('#0.000#',p^.FTimeC); OffsetRect(val,-1,HeightRect(val)+2); if lst.GDIPlus then DrawGDIPlusText (ACanvas, val, 0, ASize, str) else TextOut (val.Left,val.Top,str); Font.Color := Darker(p^.FColor,30); val := p^.FDiffR; str := 'f='+FormatFLoat('#0.000#',p^.FDiffC); OffsetRect(val,1,-TextHeight(str)-2); if lst.GDIPlus then DrawGDIPlusText (ACanvas, val, 0, ASize, str) else TextOut (val.Left, val.Top,str); val := p^.FDiffR; str := p^.FName; val.Top := val.Bottom+TextHeight(str)+2; val.Bottom := ARect.Bottom; if lst.GDIPlus then DrawGDIPlusText (ACanvas, val, 30, ASize, str) else begin fnt := CreateRotatedFont(Font, -30); tmp := SelectObject(DC,fnt); try TextOut (val.Left,val.Top, str); finally SelectObject(DC, tmp); DeleteObject(fnt); end; end; end; end; finally xFrm.eM11 := 1; xFrm.eM22 := 1; xFrm.eDx := 0; xFrm.eDy := 0; SetWorldTransform(DC, xFrm); //--     --------------------------------------------- SetGraphicsMode(DC, oldM); end; end; 


Download: Delphi XE 7 Source (70 Kb)

Source: https://habr.com/ru/post/352392/


All Articles