Огненная обезьяна Повернуть текст

Я хочу нарисовать текст на холсте. Для ротации я использовал следующий код из https://forums.embarcadero.com/thread.jspa?messageID=440010

//bm is a TImage    
a := 45;
c:= bm.Canvas;
CurrentM := c.Matrix;
a:=-radian(a);
m.m11:= cos(a); m.m12:=sin(a); m.m13:=0;
m.m21:=-sin(a); m.m22:=cos(a); m.m23:=0;
m.m31:=0;       m.m32:=0;      m.m33:=1;
c.setmatrix(M);

c.BeginScene;
    c.filltext(rectf(100,100,5000,5000), 'test rotated string', false,1,[],ttextalign.taLeading,ttextalign.taLeading);
c.EndScene;

Это прекрасно работает. Я установил право и низ моего прямоугольника на 5000, так что мне не нужно беспокоиться о том, что мой прямоугольник слишком мал.

Проблема в том, что теперь я хочу изменить свои свойства TextAlignment. Итак, чтобы нарисовать текст справа налево, мне пришлось настроить прямоугольник, а затем нарисовать его следующим образом:

c.BeginScene;
    c.filltext(rectf((100 - 5000),100,100,5000), 'test rotated string', false,1,[],ttextalign.taTrailing,ttextalign.taLeading);
c.EndScene;

Итак, в основном я переместил значение x TopLeft моего прямоугольника и переместил его назад на 5000 (снова я использую 5000, чтобы убедиться, что мой текст подходит). Затем я устанавливаю значение x правого нижнего края моего прямоугольника на то место, где значение x было в верхнем левом углу прямоугольника моего предыдущего примера. Это отлично работает для поворота на 0 градусов, но как только я меняю градусы, я не рисую свой текст в нужном месте. Я предполагаю, что это связано с тем, что текст будет вращаться вокруг верхней левой позиции прямоугольника (которая изменена, чтобы текст писался справа налево).


person Christo    schedule 14.08.2013    source источник
comment
Есть ли веская причина, почему бы не использовать TLabel поверх TImage? Вы можете вращать TLabel и выравнивать текст по своему усмотрению. Это решение было хорошо в VCL, но в FMX любой компонент может быть контейнером другого компонента.   -  person AvgustinTomsic    schedule 14.08.2013
comment
Мне нужно рисовать прямо на холсте. Я разрабатываю программу CAD, которая использует векторную графику, поэтому время от времени нужно рисовать большое количество текста, поэтому метка не подходит для моей цели.   -  person Christo    schedule 15.08.2013


Ответы (3)


Я предполагаю, что это связано с тем, что текст будет вращаться вокруг верхней левой позиции прямоугольника.

Нет, вращение сосредоточено вокруг текущего начала холста. По умолчанию это координата 0, 0, но она может быть изменена текущей установленной матрицей деформации. Типичный способ: выбрать центр вращения, переместить исходную точку в этот центр, повернуть, вернуться к измененной исходной точке и затем нарисовать. См. TControl.MatrixChanged для справки. Но есть много других способов.

Вот пример того, как рисовать текст из нижнего левого угла в верхний правый в форме:

введите здесь описание изображения

procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
var
  Angle: Single;
  R: TRectF;
  S: String;
  H: Single;
  Matrix: TMatrix;
begin
  Canvas.Fill.Color := TAlphaColors.Black;
  Angle := -ArcTan2(ClientHeight, ClientWidth);
  R := ClientRect;
  S := 'Text from bottom-left...';
  H := Canvas.TextHeight(S);
  Matrix := CreateRotationMatrix(Angle);
  Matrix.m31 := Sin(Angle) * (ClientHeight - H);
  Matrix.m32 := ClientHeight  * (1 - Cos(Angle));
  Canvas.SetMatrix(Matrix);
  Canvas.FillText(R, S, False, 1, [], TTextAlign.taLeading,
    TTextAlign.taTrailing);
  S := '...to top-right';
  Matrix.m31 := ClientWidth * (1 - Cos(Angle)) + Sin(Angle) * H;
  Matrix.m32 := -Sin(Angle) * ClientWidth;
  Canvas.SetMatrix(Matrix);
  Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
    TTextAlign.taLeading);
end;

Обновлять:

Этот код еще не учитывает уже смещенное начало координат.

В ответ на ваш комментарий следующий код рисует текст с координаты 50, 100 вниз, повернутой на 90° вокруг этой точки, используя метод, описанный выше, в PaintBox, который произвольно расположен на форме.

procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
const
  S = 'Hello World';
var
  R: TRectF;
  OriginalMatrix: TMatrix;
  ShiftMatrix: TMatrix;
  RotationMatrix: TMatrix;
  ShiftBackMatrix: TMatrix;
  Matrix: TMatrix;
begin
  PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
  R.Right := 50;
  R.Bottom := 100;
  R.Left := R.Right - 5000;
  R.Top := R.Bottom - 5000;
  OriginalMatrix := PaintBox1.Canvas.Matrix;
  ShiftMatrix := IdentityMatrix;
  ShiftMatrix.m31 := -R.Right;
  ShiftMatrix.m32 := -R.Bottom;
  RotationMatrix := CreateRotationMatrix(DegToRad(-90));
  ShiftBackMatrix := IdentityMatrix;
  ShiftBackMatrix.m31 := R.Right;
  ShiftBackMatrix.m32 := R.Bottom;
  Matrix := MatrixMultiply(RotationMatrix, ShiftBackMatrix);
  Matrix := MatrixMultiply(ShiftMatrix, Matrix);
  Matrix := MatrixMultiply(Matrix, OriginalMatrix);
  PaintBox1.Canvas.SetMatrix(Matrix);
  PaintBox1.Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
    TTextAlign.taTrailing);
  PaintBox1.Canvas.SetMatrix(OriginalMatrix);
end;

Что можно сократить до:

procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
const
  S = 'Hello World';
var
  R: TRectF;
  SaveMatrix: TMatrix;
  Matrix: TMatrix;
begin
  PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
  R := RectF(-Canvas.TextWidth(S), -Canvas.TextHeight(S), 0, 0);
  SaveMatrix := PaintBox1.Canvas.Matrix;
  Matrix := CreateRotationMatrix(DegToRad(-90));
  Matrix.m31 := 50;
  Matrix.m32 := 100;
  PaintBox1.Canvas.MultyMatrix(Matrix);
  PaintBox1.Canvas.FillText(R, S, False, 1, [], TTextAlign.taTrailing,
    TTextAlign.taTrailing);
  PaintBox1.Canvas.SetMatrix(SaveMatrix);
end;

Что, в свою очередь, превращается в эту общую рутину:

procedure DrawRotatedText(Canvas: TCanvas; const P: TPointF; RadAngle: Single;
  const S: String; HTextAlign, VTextAlign: TTextAlign);
var
  W: Single;
  H: Single;
  R: TRectF;
  SaveMatrix: TMatrix;
  Matrix: TMatrix;
begin
  W := Canvas.TextWidth(S);
  H := Canvas.TextHeight(S);
  case HTextAlign of
    TTextAlign.taCenter:   R.Left := -W / 2;
    TTextAlign.taLeading:  R.Left := 0;
    TTextAlign.taTrailing: R.Left := -W;
  end;
  R.Width := W;
  case VTextAlign of
    TTextAlign.taCenter:   R.Top := -H / 2;
    TTextAlign.taLeading:  R.Top := 0;
    TTextAlign.taTrailing: R.Top := -H;
  end;
  R.Height := H;
  SaveMatrix := Canvas.Matrix;
  Matrix := CreateRotationMatrix(RadAngle);
  Matrix.m31 := P.X;
  Matrix.m32 := P.Y;
  Canvas.MultyMatrix(Matrix);
  Canvas.FillText(R, S, False, 1, [], HTextAlign, VTextAlign);
  Canvas.SetMatrix(SaveMatrix);
end;

procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
  PaintBox1.Canvas.Fill.Color := TAlphaColors.Black;
  DrawRotatedText(PaintBox1.Canvas, PointF(50, 100), DegToRad(-90),
    'Hello world', TTextAlign.taTrailing, TTextAlign.taTrailing);
end;
person NGLN    schedule 06.10.2013
comment
Во-первых, в чем разница между координатами 0,0 на текущем холсте и TopLeft прямоугольника? Тогда что мне делать, если я не знаю длины текста? - person Christo; 07.10.2013
comment
TopLeft прямоугольника может быть любым, не так ли? Ну, в приведенном выше примере ширина текста не важна, но вы можете получить ее с помощью Canvas.TextWidth. Что ты хочешь делать? - person NGLN; 07.10.2013
comment
Я хочу иметь возможность вращать любой заданный текст в любой точке вокруг верхней правой точки холста. Пример: у меня есть текст «привет, мир», который начинается с точки 50;100. И хотите повернуть текст на 90 градусов в правом нижнем углу текста (то есть на 90 градусов вокруг буквы «d»). - person Christo; 07.10.2013

 procedure TmsLineWithArrow.DoDrawTo(const aCanvas: TCanvas;
  const aOrigin: TPointF);
var
 l_Proxy : TmsShape;
 l_OriginalMatrix: TMatrix;
 l_Matrix: TMatrix;
 l_Angle : Single;
 l_CenterPoint : TPointF;

 l_TextRect : TRectF;
begin
 inherited;

 aCanvas.BeginScene;  

 if (StartPoint <> FinishPoint) then
 begin
  l_OriginalMatrix := aCanvas.Matrix;
  try
   l_Proxy := TmsSmallTriangle.Create(FinishPoint);
   try
    // in Radian
    l_Angle := GetArrowAngleRotation;

    // create a point around which will rotate
    l_CenterPoint := TPointF.Create(FinishPoint.X, FinishPoint.Y);

    l_Matrix := l_OriginalMatrix;
    l_Matrix := l_Matrix * TMatrix.CreateTranslation(-l_CenterPoint.X,-l_CenterPoint.Y);
    l_Matrix := l_Matrix * TMatrix.CreateRotation(l_Angle);
    l_Matrix := l_Matrix * TMatrix.CreateTranslation(l_CenterPoint.X,l_CenterPoint.Y);

    aCanvas.SetMatrix(l_Matrix);

    l_Proxy.DrawTo(aCanvas, aOrigin);
   finally
    FreeAndNil(l_Proxy);
   end;//try..finally
  finally
    aCanvas.SetMatrix(l_OriginalMatrix);
    aCanvas.EndScene;
  end;
 end;//(StartPoint <> FinishPoint)
end;

Этот код работает в приложении XE5 Firemonkey. все источники здесь https://bitbucket.org/ingword/mindstream

person Ingword    schedule 30.07.2014
comment
Тем не менее, как я могу указать, что я хочу повернуть либо слева внизу, слева вверху, по центру, справа внизу или справа вверху текста. В предоставленном вами исходнике я вообще не вижу текста? - person Christo; 31.07.2014

Для тех, кому интересно, здесь вы найдете версию C++Builder, которую я использую для FMX (проверено на 10.2/Токио):

Функция:

// Рисуем повернутый текст на pMainBitmap ; Rot = должен быть кратен 90°!

void DrawRotatedText (TBitmap * pMainBitmap, TRectF TextDestRect, String StrTxt, int Rot)

(добавьте, конечно, "{" и "}" между следующим кодом... всегда ошибка с редактором, чтобы опубликовать этот ответ...)

int SizeTextW = pMainBitmap->Canvas->TextWidth( StrTxt );
int SizeTextH = pMainBitmap->Canvas->TextHeight( StrTxt );
int SizeTextMax = (SizeTextW>SizeTextH)?SizeTextW:SizeTextH;
TRectF TheTextRect;
TheTextRect.init( 0, 0, SizeTextMax, SizeTextMax );

TBitmap * pBitmapText = new TBitmap( SizeTextMax, SizeTextMax );
if ( pBitmapText )
{
    /* background color used */
    pBitmapText->Clear( claBlack );

    pBitmapText->Canvas->BeginScene();
    // use same color than main bitmap for text
    pBitmapText->Canvas->Fill->Color = pMainBitmap->Canvas->Fill->Color;
    pBitmapText->Canvas->FillText(TheTextRect, StrTxt, false, 100,
        TFillTextFlags()/* << TFillTextFlag::RightToLeft*/, TTextAlign::Center,
        TTextAlign::Center);
    pBitmapText->Canvas->EndScene();
    // Canvas->EndScene must be done before doing bitmap rotate/flip...!
    if( Rot==180 )
        pBitmapText->FlipVertical( );
    else if ( Rot!=0 )
        pBitmapText->Rotate( Rot );

    int PosSrcX = 0;
    int PosSrcY = 0;
    if ( SizeTextW>SizeTextH )
        PosSrcX = (SizeTextMax-SizeTextH)/2;
    else
        PosSrcY = (SizeTextMax-SizeTextW)/2;
    TheTextRect.init( PosSrcX, PosSrcY, PosSrcX+SizeTextH, PosSrcY+SizeTextW );
    int iPosDestX = TextDestRect.left;
    int iPosDestY = TextDestRect.top;
    if ( (TextDestRect.right-TextDestRect.left)>SizeTextH )
        iPosDestX = iPosDestX+(TextDestRect.right-TextDestRect.left-SizeTextH)/2;
    if ( (TextDestRect.bottom-TextDestRect.top)>SizeTextW )
        iPosDestY = iPosDestY + (TextDestRect.bottom-TextDestRect.top-SizeTextW)/2;
    TextDestRect.left = iPosDestX;
    TextDestRect.top = iPosDestY;
    TextDestRect.right = TextDestRect.left+SizeTextH;
    TextDestRect.bottom = TextDestRect.top+SizeTextW;
    pMainBitmap->Canvas->DrawBitmap( pBitmapText, TheTextRect, TextDestRect, 100, true );

    delete( pBitmapText );
}
person Mavati    schedule 07.03.2018