Рисование на TCanvas в Delphi XE5 под Android как оказалось имеет некоторые особенности, которые по-началу ввели меня в легкое замешательство и которыми хотелось бы поделиться.
Нарисуем несколько параллельных линий.
Здесь хотелось бы отвлечься и заметить, что под Windows значение Stroke.Kind по умолчанию равно bkSolid, а под Android — bkNone. То есть, если явно не установить значение Stroke.Kind, то линии будут видны под Windows, но не под Android. Не знаю, чем такой подход может быть вызван.
procedure TForm2.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
I: Integer;
begin
if Canvas.BeginScene then
try
Canvas.Stroke.Thickness := 1.5;
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.Fill.Color := TAlphaColorRec.Black;
Canvas.Fill.Kind := TBrushKind.bkSolid;
for I := 1 to 9 do
Canvas.DrawLine(PointF(50 + I * 25, 0), PointF(50 + I * 25, ClientHeight), 1);
finally
Canvas.EndScene;
end;
end;
Вот, что у меня получилось:

Очевидно, некоторые линии получились толще других. Тот же код под Windows работает идеально.
Причина этого в том, что в отличие от Windows, на Android логический пиксел не всегда совпадает с физическим. И линия, попавшая между физических пикселей «размывается» на соседние, чтобы быть видимой. Таким образом достигается компромисс между точностью и качеством отрисовки.
Если все же нужно нарисовать одинаковые линии, то можно их сдвинуть на половину толщины, чтобы гарантировать их попадание в физические пиксели.
Именно так решает проблему компонент TLine и его предок TShape:
function TShape.GetShapeRect: TRectF;
begin
Result := LocalRect;
if FStroke.Kind <> TBrushKind.bkNone then
InflateRect(Result, -(FStroke.Thickness / 2), -(FStroke.Thickness / 2));
end;
procedure TLine.Paint;
begin
case FLineType of
TLineType.ltTop:
Canvas.DrawLine(GetShapeRect.TopLeft, PointF(GetShapeRect.Right, GetShapeRect.Top),
AbsoluteOpacity, FStroke);
TLineType.ltLeft:
Canvas.DrawLine(GetShapeRect.TopLeft, PointF(GetShapeRect.Left, GetShapeRect.Bottom),
AbsoluteOpacity, FStroke);
else
Canvas.DrawLine(GetShapeRect.TopLeft, GetShapeRect.BottomRight, AbsoluteOpacity, FStroke);
end;
end;
Изменив код соответствующим образом, можно добиться одинаковости линий:
procedure TForm2.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
I: Integer;
begin
if Canvas.BeginScene then
try
Canvas.Stroke.Thickness := 1.5;
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.Fill.Color := TAlphaColorRec.Black;
Canvas.Fill.Kind := TBrushKind.bkSolid;
for I := 1 to 9 do
begin
Canvas.DrawLine(PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), 0),
PointF(50 + I * 25 - (Canvas.Stroke.Thickness / 2), ClientHeight), 1);
end;
finally
Canvas.EndScene;
end;
end;
Результат:

Намного симпатичнее :)
Делать такой сдвиг всегда автоматически нельзя: в таком случае неточность координат приведет к скачкам при анимации. Но все равно хотелось бы видеть какой-то флаг, позволяющий переложить выбор между красотой и точностью на FireMonkey. Довольно утомительно постоянно это делать самостоятельно.
update
Alysson Cunha предложил еще один подход к решению проблемы:
function TForm2.RoundLogicPointsToMatchPixel(const LogicPoints: Single;
const AtLeastOnePixel: Boolean = False): Single;
var
ws: IFMXWindowService;
ScreenScale, Pixels: Single;
begin
ws := TPlatformServices.Current.GetPlatformService(IFMXWindowService) as IFMXWindowService;
ScreenScale := ws.GetWindowScale(Self);
// Maybe you will want to use Ceil or Trunc instead of Round
Pixels := Round(LogicPoints * ScreenScale);
if (Pixels < 1) and (AtLeastOnePixel) then
Pixels := 1.0;
Result := Pixels / ScreenScale;
end;
procedure TForm2.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
I: Integer;
begin
if Canvas.BeginScene then
try
Canvas.Stroke.Thickness := RoundLogicPointsToMatchPixel(1.0, True);
Canvas.Stroke.Kind := TBrushKind.bkSolid;
Canvas.Fill.Color := TAlphaColorRec.Black;
Canvas.Fill.Kind := TBrushKind.bkSolid;
for I := 1 to 9 do
Canvas.DrawLine(PointF(RoundLogicPointsToMatchPixel(50 + I * 25), 0),
PointF(RoundLogicPointsToMatchPixel(50 + I * 25), ClientHeight), 1);
finally
Canvas.EndScene;
end;
end;
1. Alex W. Lulin
8 Окт 2013 12:05 дп
Я такие «рудименты» встречал под iOS и xCode. Связаны они с антиалиасингом. Если я не ошибаюсь. Я их — поборол.
Именно ТАК как и пишет Роман:
«Если все же нужно нарисовать одинаковые линии, то можно их сдвинуть на половину толщины, чтобы гарантировать их попадание в физические пиксели.»
Год назад это было. Почти. Когда я рендеринг таблиц под xCode реализовывал.
2. Alex W. Lulin
8 Окт 2013 12:48 дп
Canvas.Stroke.Thickness := 1.5;
Почему кстати 1.5, а не 1.0? Я 1.0 — ставил.
3. Роман Янковский
8 Окт 2013 11:34 дп
Alex W. Lulin,
Это не принципиально, но связано с проблемой, с которой я столкнулся на своем девайсе.
4. Alex W. Lulin
1 Ноя 2013 12:00 дп
http://18delphi.blogspot.ru/2013/10/coretext_30.html
Вот то о чём я говорил.