Khắc phục lỗi vẽ đồ họa trên TImage trong Delphi 11.1 với FMX và vấn đề liên quan đến DPI cao

Hai hình ảnh dưới đây cho thấy kết quả thực thi chương trình: về lý thuyết, cả hai hình bên trái và phải đều nên hiển thị một khung đỏ giống nhau được vẽ lên hình ảnh gốc. Tuy nhiên, khi chạy thử nghiệm trong Delphi 11.1 (FMX), chỉ hình bên phải hiển thị đúng — điều này dẫn đến sự hoang mang và mất nhiều thời gian để xác định nguyên nhân.

Vấn đề ban đầu

Đoạn mã sau minh họa cách người phát triển cố gắng vẽ trực tiếp lên Image1.Bitmap:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Bitmap.LoadFromFile('.\5.jpg');
  DrawRect(Image1.Bitmap);
end;

Trong khi đó, cách tiếp cận thứ hai tạo ra một đối tượng TBitmap độc lập, vẽ lên đó, rồi gán lại cho Image2.Bitmap:

procedure TForm1.Button2Click(Sender: TObject);
var
  tempBitmap: TBitmap;
begin
  tempBitmap := TBitmap.Create;
  try
    tempBitmap.LoadFromFile('.\5.jpg');
    DrawRect(tempBitmap);
    Image2.Bitmap.Assign(tempBitmap);
  finally
    tempBitmap.Free;
  end;
end;

Cả hai thao tác tưởng chừng như tương đương, nhưng kết quả lại khác biệt rõ rệt — chỉ cách thứ hai hoạt động chính xác.

Phân tích nguyên nhân

Sau khi trao đổi với cộng đồng và nhận được hỗ trợ từ các chuyên gia (đặc biệt là Aone), vấn đề được xác định không nằm ở logic code hay lỗi cú pháp, mà liên quan đến **xử lý độ phân giải cao (high-DPI)** trong FMX.

Khi làm việc với TCanvas trong môi trường high-DPI, thuộc tính Canvas.Scale sẽ tự động điều chỉnh để phù hợp với màn hình (ví dụ: 1.0, 1.25, 1.5, v.v.). Điều này có nghĩa rằng tọa độ và độ dày nét vẽ cần được hiệu chỉnh theo hệ số tỷ lệ này để đảm bảo hiển thị chính xác.

Trong trường hợp Image1.Bitmap, việc vẽ trực tiếp không tính đến Canvas.Scale, dẫn đến khung hình bị lệch tỷ lệ — cụ thể là vị trí và chiều dày đường viền bị giãn sai so với mong đợi.

Giải pháp

Để khắc phục, tất cả các giá trị liên quan đến tọa độ và độ dày nét vẽ phải được chia cho aBitMap.Canvas.Scale. Sửa đổi hàm DrawRect như sau:

procedure TForm1.DrawRect(aBitMap: TBitmap);
var
  rect: TRectF;
  top, left, height, width, bottom, right: Single;
begin
  top := 217;
  left := 767;
  height := 258;
  width := 198;
  bottom := top + height;
  right := left + width;

  aBitMap.Canvas.BeginScene;
  try
    // Hiệu chỉnh tọa độ theo hệ số Scale của Canvas
    rect := TRectF.Create(left / aBitMap.Canvas.Scale,
                         top / aBitMap.Canvas.Scale,
                         right / aBitMap.Canvas.Scale,
                         bottom / aBitMap.Canvas.Scale);

    with aBitMap.Canvas do
    begin
      Stroke.Kind := TBrushKind.Solid;
      Stroke.Color := TAlphaColors.Red;
      Stroke.Thickness := 8 / aBitMap.Canvas.Scale; // Điều chỉnh độ dày
      DrawRect(rect, 0, 0, AllCorners, 1.0);
    end;
  finally
    aBitMap.Canvas.EndScene;
  end;
end;

Sau khi áp dụng hiệu chỉnh theo Canvas.Scale, cả hai hình ảnh đều hiển thị chính xác như nhau, bất kể là vẽ trực tiếp hay thông qua bitmap tạm.

Kết luận

Lỗi này không phải là bug trong Delphi 11.1, mà là hệ quả của việc không xử lý đúng chuẩn với mô hình high-DPI trong FMX. Việc bỏ qua Canvas.Scale có thể dẫn đến hành vi không nhất quán giữa các thiết bị và cài đặt màn hình.

Bài học rút ra: Khi làm việc với đồ họa trong FireMonkey — đặc biệt trên các phiên bản hỗ trợ đa nền tảng và đa độ phân giải — luôn kiểm tra và sử dụng Canvas.Scale để đảm bảo tính nhất quán trong hiển thị.

Thẻ: Delphi FireMonkey High-DPI TCanvas TBitmap

Đăng vào ngày 21 tháng 6 lúc 03:33