I am trying to create a TImage with alpha transparency using code.
In this example, an anti-aliased circle.
I create an 8bit opacity map for the circle and then apply it to the TImage's TBitmap using this piece of code:
type
TOpacityMap = Array[0..4095] of PByteArray;
procedure DrawAntiAliasedCircle(srcBitmap: TBitmap; FillColor : TAlphaColor; CenterX, CenterY, Radius, LineWidth, Feather: single);
var
FillR : Integer;
FillG : Integer;
FillB : Integer;
FillRGB : Integer;
OpacityMap : TOpacityMap;
AlphaScanLine : Array[0..4095] of TAlphaColor;
bitmapData : FMX.Graphics.TBitmapData;
tmpScanLine : Pointer;
X,Y : Integer;
tmpMS : TMemoryStream;
begin
{Initialization}
FillR := TAlphaColorRec(FillColor).R;
FillG := TAlphaColorRec(FillColor).G;
FillB := TAlphaColorRec(FillColor).B;
CreateAntiAliasedCircleOpacityMap(OpacityMap, srcBitmap.Width, srcBitmap.Height, CenterX, CenterY, Radius, LineWidth, Feather);
{create image based on opacity map and free memory}
If srcBitmap.Map(TMapAccess.Write, bitmapData) then
try
FillRGB := (FillR shl 16)+(FillG shl 8)+FillB;
for Y := 0 to srcBitmap.Height-1 do
begin
for X := 0 to srcBitmap.Width-1 do
AlphaScanLine[X] := (OpacityMap[Y][X] shl 24)+FillRGB; // Opacity
tmpScanLine := bitmapData.GetScanline(Y);
AlphaColorToScanLine(#AlphaScanLine,tmpScanLine,srcBitmap.Width,srcBitmap.PixelFormat);
FreeMem(OpacityMap[Y]);
end;
finally
srcBitmap.Unmap(bitmapData);
end;
// Work-around fix
{tmpMS := TMemoryStream.Create;
srcBitmap.SaveToStream(tmpMS);
srcBitmap.LoadFromStream(tmpMS);
tmpMS.Free;}
end;
The result is the image on the left.
The actual TBitmap seems to be good, calling "srcBitmap.SaveToFile('circle.png')" results in a PNG file with a valid alpha channel.
I can work-around this issue by simply saving/loading the bitmap using a TMemoryStream.
How do I get the desired image on the right without the performance penalty of passing the image through a TMemoryStream?
These screenshots are from the minimal example project demonstrating this issue :
https://github.com/bLightZP/AntiAliasedCircle
edit #1 :
The github code linked above has been updated with an optimized (about 25% faster) version of Tom Brunberg's suggested fix.
You can achieve your goal with applying alpha channel premultiplying to your overlay image. For example in the loop where you add the alpha channel:
for X := 0 to srcBitmap.Width-1 do
begin
AlphaScanLine[X] := (OpacityMap[Y][X] shl 24)+FillRGB;
AlphaScanLine[X] := PremultiplyAlpha(AlphaScanLine[X]); // Add this for premultiplied Alpha
end;
The result looks like this (ofcourse without the stream work-around)
Related
I want to copy pixels from BMP1 to BMP2 but the copied image is gabbled. Why?
Note: The input image is pf8bit;
TYPE
TPixArray = array[0..4095] of Byte;
PPixArray = ^TPixArray;
procedure Tfrm1.CopyImage;
VAR
BMP1, BMP2: TBitmap;
y, x: Integer;
LineI, LineO: PPixArray;
begin
BMP1:= TBitmap.Create;
BMP2:= TBitmap.Create;
TRY
BMP1.LoadFromFile('test.bmp');
BMP2.SetSize(BMP1.Width, BMP1.Height);
BMP2.PixelFormat:= BMP1.PixelFormat;
for y:= 0 to BMP1.Height -1 DO
begin
LineI := BMP1.ScanLine[y];
LineO := BMP2.ScanLine[y];
for x := 0 to BMP1.Width -1 DO
LineO[x]:= LineI[x];
end;
//BMP2.SaveToFile('out.bmp');
imgOut.Picture.Assign(BMP2); //TImage
FINALLY
FreeAndNil(BMP2);
FreeAndNil(BMP1);
END;
end;
For the saved image, a graphic editor says "Pixel depth/colors: indexed, 256 color palette".
It might be worth pointing out that an 8-bit bitmap isn't necessarily greyscale.
Instead, it is a bitmap with a "colour table" consisting of up to 256 entries, and each pixel refers to an entry in this table. So if a pixel's value is 185, this means that it should use the colour at location 185 in the bitmap's "colour table". Hence, an 8-bit bitmap works entirely different compared to a 16-, 24- or 32-bit bitmap, which does not have a colour table, but instead has actual RGB(A) values at each pixel.
The problem in your case is likely that the target pixmap doesn't have the same colour table as the source bitmap.
I have actually never worked with 8-bit bitmaps and palettes before, but I think it is this simple:
var
s, t: TBitmap;
y: Integer;
sp, tp: PByte;
x: Integer;
begin
s := TBitmap.Create;
try
s.LoadFromFile('C:\Users\Andreas Rejbrand\Desktop\bitmap.bmp');
Assert(s.PixelFormat = pf8bit);
t := TBitmap.Create;
try
t.PixelFormat := pf8bit;
t.SetSize(s.Width, s.Height);
t.Palette := s.Palette; // <-- Let the new image have the same colour table
for y := 0 to s.Height - 1 do
begin
sp := s.ScanLine[y];
tp := t.ScanLine[y];
for x := 0 to s.Width - 1 do
tp[x] := sp[x];
end;
t.SaveToFile('C:\Users\Andreas Rejbrand\Desktop\bitmap2.bmp');
finally
t.Free;
end;
finally
s.Free;
end;
I'm trying to take a screenshot of a specific part of the screen. Here is the coordinates of the part of the screen i want to 'cut' :
Left : 442
Top : 440
Right : 792
Bottom : 520
That is, a rectangle of width 350px and height of 80px. But i don't know how to use CopyRect to achieve this task, instead i'm getting a blank image. Here is my code :
function screenshot: boolean;
var
Bild : TBitmap;
c: TCanvas;
rect_source, rect_destination : TRect;
begin
c := TCanvas.Create;
bild := tbitmap.Create;
c.Handle := GetWindowDC(GetDesktopWindow);
try
rect_source := Rect(0, 0, Screen.Width, Screen.Height);
rect_destination := Rect(442,440,792,520);
Bild.Width := 350;
Bild.Height := 80;
Bild.Canvas.CopyRect(rect_destination, c, rect_source);
Bild.savetofile('c:\users\admin\desktop\screen.bmp');
finally
ReleaseDC(0, c.Handle);
Bild.free;
c.Free;
end;
end;
What you are doing here is copying the whole screen and draw it at coordinate Rect(442,440,792,520); in your new bitmap... Which is off its canvas.
The coordinate Rect(442,440,792,520) correspond to the part you want to get from the source bitmap. You want to copy it "inside" your new bitmap, so within the rect Rect(0,0,350,80)
You can simply adjust your rect like this :
rect_source := Rect(442,440,792,520);
rect_destination := Rect(0,0,350,80);
The rest of your code seems correct.
I draw a dotted line on a layer of an ImgView32. Later, I want to save each layer as transparent PNGs.
For any other layer that I have, the saving works just fine. But for the drawing layer, it does not.
In order to make the question simpler to understand, take the example code from the gr32 library, more specifically the Layers example. One of the options in its main menu is to add a custom drawing layer (New Custom Layer -> Simple Drawing Layer).
Then try to save that layer as a transparent PNG image and you will end up with a corrupted PNG file (you can't open it with any other picture viewer like for example Paint.net or Microsoft Photo Viewer). Same thing happens if you try to save the layer's bitmap32 as a bitmap as you can see in the bellow code...
I tried two approaches for saving Bitmap32 as a transparent PNG, so the first one is as follows:
procedure TMainForm.SavePNGTransparentX(bm32:TBitmap32; dest:string);
var
Y: Integer;
X: Integer;
Png: TPortableNetworkGraphic32;
function IsBlack(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 0) and
(TColor32Entry(Color32).G = 0) and
(TColor32Entry(Color32).R = 0);
end;
function IsWhite(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 255) and
(TColor32Entry(Color32).G = 255) and
(TColor32Entry(Color32).R = 255);
end;
begin
bm32.ResetAlpha;
for Y := 0 to bm32.Height-1 do
for X := 0 to bm32.Width-1 do
begin
// if IsWhite(bm32.Pixel[X, Y]) then
// bm32.Pixel[X,Y]:=Color32(255,255,255, 0);
if IsBlack(bm32.Pixel[X, Y]) then
bm32.Pixel[X,Y]:=Color32( 0, 0, 0, 0);
end;
Png:= TPortableNetworkGraphic32.Create;
try
Png.Assign(bm32);
Png.SaveToFile(dest);
finally
Png.Free;
end;
end;
So the above method works if I have a PNG loaded into the layer like this:
mypng := TPortableNetworkGraphic32.Create;
mypng.LoadFromStream(myStream);
B := TBitmapLayer.Create(ImgView.Layers);
with B do
try
mypng.AssignTo(B.Bitmap);
...
But as soon as I try to save the layer created with the code from the Layers example, the result is corrupted.
Even if I try to save the layer as bitmap like this (though this is not my intention since I need them to be PNG):
mylay := TBitmapLayer(ImgView.Layers.Items[i]);
mylay.Bitmap.SaveToFile('C:\tmp\Layer'+IntToStr(i)+'.bmp');
the same corruption occurs.
So, it's not like I receive an exception or anything... it just gets saved corrupted somehow;
I also tried other ways to save the Bitmap32 as transparent PNG, like for instance the GR32_PNG approach:
function SaveBitmap32ToPNG (sourceBitmap: TBitmap32;transparent: Boolean;bgColor32: TColor32;filename: String;compressionLevel: TCompressionLevel = 9;interlaceMethod: TInterlaceMethod = imNone): boolean;
var png: TPNGImage;
begin
result := false;
try
png := Bitmap32ToPNG (sourceBitmap,false,transparent,WinColor(bgColor32),compressionLevel,interlaceMethod);
try
png.SaveToFile (filename);
result := true;
finally
png.Free;
end;
except
result := false;
end;
end;
where
function Bitmap32ToPNG (sourceBitmap: TBitmap32;paletted, transparent: Boolean;bgColor: TColor;compressionLevel: TCompressionLevel = 9;interlaceMethod: TInterlaceMethod = imNone): TPNGImage; // TPNGObject
var
bm: TBitmap;
png: TPNGImage;//TPngObject;
TRNS: TCHUNKtRNS;
p: pngImage.PByteArray;
x, y: Integer;
begin
Result := nil;
png := TPngImage.Create; // TPNGObject
try
bm := TBitmap.Create;
try
bm.Assign (sourceBitmap); // convert data into bitmap
// force paletted on TBitmap, transparent for the web must be 8bit
if paletted then
bm.PixelFormat := pf8bit;
png.interlaceMethod := interlaceMethod;
png.compressionLevel := compressionLevel;
png.Assign(bm); // convert bitmap into PNG
// this is where the access violation occurs
finally
FreeAndNil(bm);
end;
if transparent then begin
if png.Header.ColorType in [COLOR_PALETTE] then begin
if (png.Chunks.ItemFromClass(TChunktRNS) = nil) then png.CreateAlpha;
TRNS := png.Chunks.ItemFromClass(TChunktRNS) as TChunktRNS;
if Assigned(TRNS) then TRNS.TransparentColor := bgColor;
end;
if png.Header.ColorType in [COLOR_RGB, COLOR_GRAYSCALE] then png.CreateAlpha;
if png.Header.ColorType in [COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA] then
begin
for y := 0 to png.Header.Height - 1 do begin
p := png.AlphaScanline[y];
for x := 0 to png.Header.Width - 1
do p[x] := AlphaComponent(sourceBitmap.Pixel[x,y]); // TARGB(bm.Pixel[x,y]).a;
end;
end;
end;
Result := png;
except
png.Free;
end;
end;
but using this approach, I get an EAccessViolation when trying to save this particular layer. For any other layers (not drawing ones), it does not crash my project except for this custom drawing one.
The access violation occurs at this line:
png.Assign(bm);
inside the Bitmap32ToPNG function
Do you have any idea why that happens and how can I prevent this?
EDIT
I tried using TBitmapLayer instead, because the TPositionedLayer might lack the Bitmap32 for some reason.
So my code is like this:
// adding a BitmapLayer and setting it's onPaint event to my handler
procedure TMainForm.Mynewlayer1Click(Sender: TObject);
var
B: TBitmapLayer;
P: TPoint;
W, H: Single;
begin
B := TBitmapLayer.Create(ImgView.Layers);
with B do
try
Bitmap.SetSize(100,200);
Bitmap.DrawMode := dmBlend;
with ImgView.GetViewportRect do
P := ImgView.ControlToBitmap(GR32.Point((Right + Left) div 2, (Top + Bottom) div 2));
W := Bitmap.Width * 0.5;
H := Bitmap.Height * 0.5;
with ImgView.Bitmap do
Location := GR32.FloatRect(P.X - W, P.Y - H, P.X + W, P.Y + H);
Scaled := True;
OnMouseDown := LayerMouseDown;
OnPaint := PaintMy3Handler;
except
Free;
raise;
end;
Selection := B;
end;
// and the PaintHandler is as follows:
procedure TMainForm.PaintMy3Handler(Sender: TObject;Buffer: TBitmap32);
var
Cx, Cy: Single;
W2, H2: Single;
const
CScale = 1 / 200;
begin
if Sender is TBitmapLayer then
with TBitmapLayer(Sender).GetAdjustedLocation do
begin
// Five black pixels, five white pixels since width of the line is 5px
Buffer.SetStipple([clBlack32, clBlack32, clBlack32, clBlack32, clBlack32,
clWhite32, clWhite32, clWhite32, clWhite32, clWhite32]);
W2 := (Right - Left) * 0.5;
H2 := (Bottom - Top) * 0.5;
Cx := Left + W2;
Cy := Top + H2;
W2 := W2 * CScale;
H2 := H2 * CScale;
Buffer.PenColor := clRed32;
Buffer.StippleCounter := 0;
Buffer.MoveToF(Cx-2,Top);
Buffer.LineToFSP(Cx-2 , Bottom);
Buffer.StippleCounter := 0;
Buffer.MoveToF(Cx-1,Top);
Buffer.LineToFSP(Cx-1 , Bottom);
Buffer.StippleCounter := 0;
Buffer.MoveToF(Cx,Top);
Buffer.LineToFSP(Cx , Bottom);
Buffer.StippleCounter := 0;
Buffer.MoveToF(Cx+1,Top);
Buffer.LineToFSP(Cx+1 , Bottom);
Buffer.StippleCounter := 0;
Buffer.MoveToF(Cx+2,Top);
Buffer.LineToFSP(Cx+2 , Bottom);
end;
end;
Keep in mind that I use the default layers demo application. So this is just added code. I did not remove nor change anything in the demo code.
So I create a new layer (TBitmapLayer) and onPaint I do my drawing. In the end I want to save the contents of that layer as PNG. But it seems like the onPaint might draw somewhere else instead of the actual layer. Otherwise I do not understand why the saved image is empty.
So this time the resulted PNG is not corrupted, but it is empty...
The error is in the fact that the examples create TPositionedLayer layers which do not hold a bitmap. You can not type cast this layer type into a TBitmapLayer and expect it to create a bitmap image of the layer, as you do in this code:
mylay := TBitmapLayer(ImgView.Layers.Items[i]);
mylay.Bitmap.SaveToFile('C:\tmp\Layer'+IntToStr(i)+'.bmp');
I assume you do something similar to save to .png file, although you did not show that code.
The examples (with TPositionedLayer layers) use ImgView.Buffer for drawing on the screen. You can save that to a .png file like this:
SavePNGTransparentX(ImgView.Buffer, 'c:\tmp\imgs\buffer.png');
but I don't expect that to work satisfactorily for your separate layer images.
What is the reason you don't use TBitmapLayers as you have done before?
Edit after comments by user1137313
Inspired by the solution you found yourself (ref. your comment) I suggest the following which paints the layer to the extra bitmap only when needed for saving.
Starting from a menu item
procedure TMainForm.mnFileSaveClick(Sender: TObject);
begin
SaveLayerToPng(ImgView.Layers[ImgView.Layers.Count-1], 'c:\tmp\imgs\buffer.png');
end;
You possibly want to call SaveLayerToPng() in a loop if you save several layers at the same time, and also change the file name(s) as needed.
Then the SaveLayerToPng() procedure
procedure TMainForm.SaveLayerToPng(L: TCustomLayer; FileName: string);
var
bm32: TBitmap32;
begin
bm32:= TBitmap32.Create;
try
bm32.SetSizeFrom(ImgView.Buffer);
PaintSimpleDrawingHandler(L, bm32);
SavePNGTransparentX(bm32, FileName);
finally
bm32.Free;
end;
end;
It calls the existing PaintSimpleDrawingHandler(Sender: TObject; buffer: TBitmap32) procedure to paint to bm32 which it then passes on to `SavePNGTransparentX() for actual saving.
I used the paint handler of the Graphics32 example, but your PaintMy3Handler() can be used just as well.
The end result is same as your solution, just that the extra TBitmap32 is only painted when the file is to be saved.
I have problems saving an ImgView32 layer as a TRANSPARENT PNG.
I use the code from this question to do the saving.
However, the image saves with a white background.
Here is how I initialize my ImgView32, create a layer on it, and then draw a line on it:
procedure TputLine.FormCreate(Sender: TObject);
var
P: TPoint;
W, H: Single;
imwidth: integer;
imheight: integer;
begin
imwidth := Iv1.Width;
imheight := Iv1.Height;
with iv1 do
begin
Selection := nil;
Layers.Clear;
Scale := 1;
Scaled := True;
Bitmap.DrawMode := dmTransparent;
Bitmap.SetSize(imwidth, imheight);
Bitmap.Canvas.Pen.Width := 4;
end;
BL := TBitmapLayer.Create(iv1.Layers);
try
BL.Bitmap.DrawMode := dmTransparent;
BL.Bitmap.SetSize(imwidth,imheight);
BL.Bitmap.Canvas.Pen.Width := penwidth;
BL.Bitmap.Canvas.Pen.Color := pencolor;
BL.Location := GR32.FloatRect(0, 0, imwidth, imheight);
BL.Scaled := False;
except
BL.Free;
raise;
end;
end;
So iv1 is the name of my ImgView32.
Then I draw a line on it using this code:
var
bm32:TBitmapLayer;
...
begin
bm32:=(iv1.Layers[0] as TBitmapLayer).Bitmap;
bm32.canvas.pen.color:=clwhite;
bm32.canvas.brush.color:=clwhite;
bm32.canvas.rectangle(0,0,bm32.width-1, bm32.height-1);
bm32.canvas.Pen.Color:=WinColor(ColorPickerGTK1.SelectedColor);
bm32.canvas.brush.color:=clWhite;
bm32.Canvas.Pen.Width:=3;
bm32.Canvas.MoveTo(0,bm32.Height);
bm32.Canvas.LineTo(0+150,bm32.Height-250);
end;
If I use the clWhite32 for the above code when drawing the rectangle, then when saving the PNG, the background of the imgView turns black... So I do not understand the problem really.
I do the saving like this:
procedure TputLine.Button2Click(Sender: TObject);
var
myLay:TBitmapLayer;
begin
mylay := iv1.Layers.Items[0] as TBitmapLayer;
SavePNGTransparentX(mylay.Bitmap);
end;
and the actual saving code (from the link described above)
procedure TPutLine.SavePNGTransparentX(bm32:TBitmap32);
var
Y: Integer;
X: Integer;
Png: TPortableNetworkGraphic32;
function IsWhite(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 255) and
(TColor32Entry(Color32).G = 255) and
(TColor32Entry(Color32).R = 255);
end;
begin
bm32.ResetAlpha;
for Y := 0 to bm32.Height-1 do
for X := 0 to bm32.Width-1 do
begin
if IsWhite(bm32.Pixel[X, Y]) then
bm32.Pixel[X,Y]:=Color32(255,255,255,0);
end;
Png:= TPortableNetworkGraphic32.Create;
Png.Assign(bm32);
Png.SaveToFile('C:\ThisShouldBeTransparent.png');
Png.Free;
end;
I do not understand why it does not save the layer as transparent PNG.
How can I fix it? Any idea is welcome.
You can replicate my problem using the above code. It uses GR32_PNG and GR32_PortableNetworkGraphic. You only need to add a TImgView32 control to your form and add the code listed above.
The reason to the problem seems to be two-fold.
First, when you call Png.Assign(bm32); in unit GR32_PNG, it attempts to find out what the smallest format would be to store the image in. If the image has less than 256 distinct colors, it creates a paletted format, and depending on how many colors it finds, the bit depth can become 1, 2, 4 or 8. As far as my knowledge goes, only images with TrueColor and Alpha can be saved as variable transparency png images.
Secondly, you draw with only one color, which triggers the above problem. This is of course not your fault, IMO the above mentioned analysis should be possible to bypass.
The TPortableNetworkGraphic32 class has two properties, BitDepth and ColorType wich control the format of the png image, that would be useful, if they were settable! Attempting to set them in code as:
Png.BitDepth := 8;
Png.ColorType := ctTrueColorAlpha;
leads to exceptions
EPngError with message 'Bit depth may not be specified directly yet!
EPngError with message 'Color Type may not be specified directly yet!
From the wording we can assume some further development in the future.
The cure
To bypass the above described image analysis, you can change line 459 in GR32_PNG.pas.
procedure TPortableNetworkGraphic32.AssignPropertiesFromBitmap32()
var
...
begin
...
IsPalette := True; // <--- change to False
...
That will take care of the bit depth analysis and prevents palette creation if less than 256 colors.
After this hack you can use the SavePNGTransparentX() procedure to save a TBitmap32 to a .png file and preserving transparency.
Then, there's one change more that you may be interested in, regarding the SavePNGTransparentX() procedure. As you have seen, it requires the background of your drawing surface to be white, because it specifically sets the Alpha channel to zero for all white pixels. A TBitmapLayer is however initialized with all pixels (RGBA) as all zeros, so the color components of each pixel makes up to black color (which is not visible because the alpha channel is zero). Therefore you need to fill the drawing layer with white, which makes it opaque, which again covers up all lower layers (beneath the drawing layer).
To make a correction to this you can
remove the initialization of the drawing layer to all white
change the IsWhite function into an IsBlack function
change the assignment of the transparent pixels
Code would become
procedure TForm8.SavePNGTransparentX(bm32:TBitmap32);
var
Y: Integer;
X: Integer;
Png: TPortableNetworkGraphic32;
function IsBlack(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 0) and
(TColor32Entry(Color32).G = 0) and
(TColor32Entry(Color32).R = 0);
end;
function IsWhite(Color32: TColor32): Boolean;
begin
Result:= (TColor32Entry(Color32).B = 255) and
(TColor32Entry(Color32).G = 255) and
(TColor32Entry(Color32).R = 255);
end;
begin
bm32.ResetAlpha;
for Y := 0 to bm32.Height-1 do
for X := 0 to bm32.Width-1 do
begin
// if IsWhite(bm32.Pixel[X, Y]) then
// bm32.Pixel[X,Y]:=Color32(255,255,255, 0);
if IsBlack(bm32.Pixel[X, Y]) then
bm32.Pixel[X,Y]:=Color32( 0, 0, 0, 0);
end;
Png:= TPortableNetworkGraphic32.Create;
Png.Assign(bm32);
Png.SaveToFile('C:\tmp\imgs\ThisShouldBeTransparent3.png');
Png.Free;
end;
With this change you can see the layers beneath your drawing layer and the resulting file of your drawing layer will have all non-drawn pixels transparent.
There is however still one problem in general with the above procedure, and that is that partially transparent pixels loose their transparency, but that will remain for a future excercise.
Here's a few images, First the ImageView with a bitmap loaded on the bottom layer:
Then, I draw a blue cross on the BL layer (ref. your code) using basically the code in your Button1Click() but without the white rectangle.
Then I save the BL layer to a .png file and look at it with Windows Explorer "Preview":
Following code makes quite some troubles:
procedure TForm1.Button1Click(Sender: TObject);
var dc : HDC;
meta : TMetafile;
metaCanv : TMetafileCanvas;
cr : TRect;
sz : TSize;
begin
dc := GetDC(0);
SetWindowExtEx(dc, 4800, 1300, #sz);
ShowMessage(Format('size %d, %d', [sz.cx, sz.cy]));
meta := TMetafile.Create;
meta.SetSize(4500, 1300);
metaCanv := TMetafileCanvas.Create(meta, dc);
try
IntersectClipRect(metaCanv.Handle, 0, 0, 4600, 1300);
cr := metaCanv.ClipRect;
with cr do
ShowMessage(Format('clip rect: %d, %d, %d, %d', [Top, Left, Bottom, Right]));
finally
metaCanv.Free;
meta.Free;
end;
DeleteDC(dc);
end;
The problem is that the clipping rectangle is bound to the display resolution e.g. if your screen has 1920 pixels width the clipping rectangle is bound to this value.
Note it is NOT a problem to remove clipping at all and paint lines event to the complete bottom rect corner. The problem arises if a clipping region is set (e.g. to the complete metafile width/height as shown in the example) and then paint the line -> it is clipped to the screen width/height.
I know that I could use e.g. a printer dc as reference which will basically fix the problem but there are a few side effects (e.g. gdi+ drawing on metafiles with such
dc's simply does not work).
Anyone knows how to "trick" the system such that this odd clipping behaviour is not
there any more?
ClipRect being the only part in which you can draw is a false presumption.
The documentation on TCustomCanvas.ClipRect:
Use ClipRect to determine where the canvas needs painting.
This is easily verified by drawing beyond ClipRect and trying to show what has been drawn, for example as follows:
procedure TForm1.Button1Click(Sender: TObject);
var
MetaFile: TMetafile;
MetaCanvas: TMetafileCanvas;
begin
MetaFile := TMetafile.Create;
try
MetaCanvas := TMetafileCanvas.Create(MetaFile, 0);
try
MetaFile.SetSize(4500, 1300);
MetaCanvas.LineTo(4500, 1300);
finally
MetaCanvas.Free;
end;
Canvas.Draw(-4400, -1200, MetaFile);
finally
MetaFile.Free;
end;
end;