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ị.