I am trying to make a kind of bitmap editor so I just want to draw a line on a bitmap with no anti-aliasing effect in Firemonkey. Something like this:
var
Bmp: TBitmap;
Bmp := TBitmap.Create(2000, 2000);
if (Bmp.Canvas.BeginScene) then
begin
Bmp.Canvas.Stroke.Color := TAlphaColors.Aquamarine;
Bmp.Canvas.DrawLine(PointF(5, 5), PointF(100, 100), 1);
Bmp.Canvas.EndScene;
Bmp.SaveToFile('c:\temp\result.bmp');
end;
FreeAndNil(Bmp);
But it doesn't work. I am trying for a while with several ideas with no luck:
Using Map/Unmap to access the bitmap data directly is very slow with big bitmaps according to my coworkers.
Using a TImage with DisableInterpolation=true and even GlobalUseGPUCanvas=False doesn't work.
Using a TPaintBox component doesn't fit our needs.
The solution would be the use of Canvas.Quality=HighPerformance but it's a read-only property. I tried to change the bitmap Canvas.Quality in different ways but it doesn't work.
So how can I simply draw a line with no anti-aliasing effect at all in Firemonkey?
PS: I am using Delphi 10.2.3 (Tokyo)
Finally I found a way to do this. It's so simple that I am wondering if there is some hidden poison in the solution (LOL). TCanvasManager allows the creation of a HighPerformance Canvas from a given bitmap. It draws with no antialiasing according to my tests. Here the code:
var
Bmp: TBitmap;
TmpCanvas: TCanvas;
begin
Bmp := TBitmap.Create(2000, 2000);
TmpCanvas := TCanvasManager.CreateFromBitmap(Bmp, TCanvasQuality.HighPerformance);
if (TmpCanvas.BeginScene) then
begin
TmpCanvas.Stroke.Color := TAlphaColors.Aquamarine;
TmpCanvas.DrawLine(PointF(5, 5), PointF(100, 100), 1);
TmpCanvas.EndScene;
Bmp.SaveToFile('c:\temp\result.bmp');
end;
FreeAndNil(TmpCanvas);
FreeAndNil(Bmp);
I also found that it doesn't work with the method to write text on Canvas (Canvas.FillText).
I hope this helps many others with the same problem.
FireMonkey paints lines on the grid between the pixels and not on the pixels. So you have to add 0.5 to each coordinate in order to paint on the pixels:
Bmp.Canvas.DrawLine(PointF(5.5, 5.5), PointF(100.5, 100.5), 1);
This does not disable anti-aliasing, but avoids the excessive anti-aliasing that happens otherwise. I'm not aware of a FireMonkey function that disables anti-alisiasing. You would have to call a native OS function, for example CGContextSetAllowsAntialiasing on MacOS, but usually it is not needed anymore as soon as you figure out how to paint on the pixels.
Related
I want to take a screenshot of my page and put the result into a bitmap, Because there is a scrollbar on the page, i have to take several screenshots, and i want to merge those bitmaps.
if have used this code to make a screenshot and save it: Take a screenshot of a particular area in Delphi 7
i used the code to merge them from this page http://www.delphigroups.info/2/8/309463.html
if i copied it directly it would result in the first image being used, and i white rectangle for the second. so i tried to change it a little bit, and now i'm getting both images in one file.
This is the code i use to concatenate the bitmaps:
function ConcatenateBitmaps(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap): TBitmap;
begin
Result := MainBitmap;
If BitmapToAdd.Width > MainBitmap.Width then
Result.Width := BitmapToAdd.Width;
Result.Height := MainBitmap.Height + MainBitmap.Height;
Result.Canvas.CopyRect(
Rect(0,MainBitmap.Height,BitmapToAdd.Width,BitmapToAdd.Height),
BitmapToAdd.Canvas,
Rect(0,0,BitmapToAdd.Width,BitmapToAdd.Height)
);
end;
The problem is that te second image is being flipped, vertical and horizontal;
What am i doing wrong here?
EDIT:
An example of the result, the first image is good, the second image is flipped:
as i see now, my description was wrong, it's horizontaly mirrored, and verticaly flipped
Cause and quickfix:
The problem is in this part:
Rect(0,MainBitmap.Height,BitmapToAdd.Width,BitmapToAdd.Height)
You make a rectangle of which the top is the total height of the resulting image, and the bottom is the height of the bitmap to add. So this rectangle is basically inverted (its bottom is above its top).
And it's likely deformed as well, since the height of this rectangle is not the height of the bitmap to add.
The quickfix would be:
Rect(0,Result.Height- BitmapToAdd.Height,BitmapToAdd.Width,Result.Height)
Other issues and confusion:
But I think the cause of your confusion is because you think that Result and MainBitmap are two different bitmaps, while actually they are both references to the same bitmap. The assignment you do in the beginning just copies the reference, not the actual TBitmap object.
In addition, you mix up 'height' and 'bottom'. TRect expects you to set top and bottom coordinates, not top and height. This, together with the previous issue, causes not only that the bitmap is upside down, but also that it will be stretched, and partially covering the previous images. The more images you add, the more clear that effect will be.
Personally I think it's way more efficient to modify the existing bitmap in this scenario, mainly because you would otherwise have to clean up your old bitmap all the time, plus that you have a function that magically creates bitmaps. You get the question of ownership of the bitmap objects, and with that, the risk of memory leaks, which is not good, especially when dealing with large bitmaps.
My suggested version:
So, I would just make it a procedure, where the first bitmap is modified by adding the second bitmap to it.
In the version below, I also used Canvas.ClipRect, which is for a bitmap essentially the bounding rectangle of the bitmap. And then I used OffsetRect to 'move' this rectangle(increasing its top Y and bottom Y).
By doing this in a separate variable, you can have a relatively clean version compared to the quick fix I presented above, because you can use the dimensions of MainBitmap before actually modifying it.
procedure AppendBitmap(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap);
var
TargetRect: TRect;
begin
// Widen the main bitmap if needed
if BitmapToAdd.Width > MainBitmap.Width then
MainBitmap.Width := BitmapToAdd.Width;
// Set TargetRect to the right size
TargetRect := BitmapToAdd.Canvas.ClipRect;
// And then to the right position
OffsetRect(TargetRect, 0, MainBitmap.Height);
// Make room for the bitmap to add
MainBitmap.Height := MainBitmap.Height + BitmapToAdd.Height;
// Draw it in the created space
MainBitmap.Canvas.CopyRect(
TargetRect,
BitmapToAdd.Canvas,
BitmapToAdd.Canvas.ClipRect
);
end;
And if you like, you can make a wrapper function with the signature of the original, that creates a copy of the main image and returns that. Note though, that MainBitmap and the result of this function are no longer the same bitmap, and you have to make sure to properly free both of them when you're done.
function ConcatenateBitmaps(const MainBitmap: TBitmap; const BitmapToAdd:
TBitmap): TBitmap;
begin
Result := TBitmap.Create;
Result.Assign(MainBitmap);
AppendBitmap(Result, BitmapToAdd);
end;
PS: I like questions like this from which I learn something. I never realized you could flip an image by flipping the rect passed to CopyRect. :D
Hi i have the following code to add an image from a Timage that for now is populated from a blob. My issue is this code does not add the image to the paintbox but rather to the form.
var
RectangleCanvas, RectanglePicture: TRectF;
BlobStream: TStream;
begin
BlobStream := qrypunchsheetitemphoto.CreateBlobStream(qrypunchsheetitemphoto.FieldByName('Photo'),TBlobStreamMode.bmRead);
imgviewimage.Bitmap.LoadFromStream(BlobStream);
fdrawbox:= TMyPaintBox.Create(panel1);
fdrawbox.Canvas.BeginScene;
fdrawbox.BitmapStamp := imgviewimage.Bitmap;
fdrawbox.Height := imgviewimage.Bitmap.Height;
fdrawbox.Width := imgviewimage.Bitmap.Width;
RectangleCanvas := RectF(10, 10, imgviewimage.Bitmap.Width, imgviewimage.Bitmap.Height);
RectanglePicture := RectF(10, 10, imgviewimage.Bitmap.Width, imgviewimage.Bitmap.Height);
fdrawbox.Canvas.DrawBitmap(imgviewimage.Bitmap, RectangleCanvas , RectanglePicture, 1);
fdrawbox.Canvas.EndScene;
fdrawbox.BringToFront;
BlobStream.Free;
TabControl1.ActiveTab := tabViewImage;
end;
end;
The FMX Paintbox is different to older Delphi Paintboxes. Previously you could put a Paintbox anywhere on your form and start drawing. The results would be within the confines of the Paintbox where you placed it.
The FMX Paintbox isn't like that and I don't understand their reasoning. I've been told it has a something to do with cross-platform compatibility and how devices handle canvas operations.
You can verify canvas width for yourself easily enough.
If you have a form width of 640 pixels and place a 50 x 50 Paintbox in the middle you'd expect drawing to occur in the middle.
Check it yourself;
ShowMessage(FloatToStr(Paintbox1.Width)); // Result will be 50
Now check Paintbox1.Canvas.Width and you'll get a different result.
ShowMessage(IntToStr(Paintbox1.Canvas.Width)); // Result is 640
When you pass parameters to drawing functions you need to take this into account and offset accordingly. I have read something about parental clipping having some effect, but I've not seen it work.
Another potential solution is to use a TPanel and draw on it's canvas.
I want to quickly resize an image (shrink/enlarge). The resulted image should be of high quality so I cannot use the classic StretchDraw or something like this. The JanFX and Graphics32 libraries offer high quality resamplers. The quality is high but they are terribly slow (seconds to process a 2000x1000 image).
I want to try FMX CreateThumbnail to see how fast it is:
FMX.Graphics.BMP.CreateThumbnail
I created a FMX bitmap in a VCL application and tried to assign a 'normal' bitmap to it.
fmxBMP.Assign(vclBMP);
But I get an error: Cannot assign TBitmap to a TBitmap. Obviously the two bitmaps are different.
My questions:
1. Are the image processing routines in FMX much faster then the normal VCL routines?
2. Most important: how can I assign a VCL bitmap to a FMX bitmap (and vice versa)?
You can use GDI+ scaling.
You can alter result quality and speed specifying different interpolation, pixel offset and smoothing modes defined in GDIPAPI.
uses
GDIPAPI,
GDIPOBJ;
procedure ScaleBitmap(Source, Dest: TBitmap; OutWidth, OutHeight: integer);
var
src, dst: TGPBitmap;
g: TGPGraphics;
h: HBITMAP;
begin
src := TGPBitmap.Create(Source.Handle, 0);
try
dst := TGPBitmap.Create(OutWidth, OutHeight);
try
g := TGPGraphics.Create(dst);
try
g.SetInterpolationMode(InterpolationModeHighQuality);
g.SetPixelOffsetMode(PixelOffsetModeHighQuality);
g.SetSmoothingMode(SmoothingModeHighQuality);
g.DrawImage(src, 0, 0, dst.GetWidth, dst.GetHeight);
finally
g.Free;
end;
dst.GetHBITMAP(0, h);
Dest.Handle := h;
finally
dst.Free;
end;
finally
src.Free;
end;
end;
I've spended hours for this (simple) one and don't find a solution :/
I'm using D7 and the TImageList. The ImageList is assigned to a toolbar.
When I populate the ImageList at designtime, the icons (with partial transparency) are looking fine.
But I need to populate it at runtime, and when I do this the icons are looking pretty shitty - complete loose of the partial transparency.
I just tried to load the icons from a .res file - with the same result.
I've tried third party image lists also without success.
I have no clue what I could do :/
Thanks 2 all ;)
edit:
To be honest I dont know exactly whats going on. Alpha blending is the correkt term...
Here are 2 screenies:
Icon added at designtime:
(source: shs-it.de)
Icon added at runtime:
(source: shs-it.de)
Your comment that alpha blending is not supported just brought the solution:
I've edited the image in an editor and removed the "alpha blended" pixels - and now it looks fine.
But its still strange that the icons look other when added at runtime instead of designtime. If you (or somebody else ;) can explain it, I would be happy ;)
thanks for you support!
To support alpha transparency, you need to create the image list and populate it at runtime:
function AddIconFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Icon: TIcon;
begin
Icon := TIcon.Create;
try
Icon.LoadFromResourceID(HInstance, ResID);
Result := ImageList.AddIcon(Icon);
finally
Icon.Free;
end;
end;
function AddPngFromResource(ImageList: TImageList; ResID: Integer): Integer;
var
Png: TPngGraphic;
ResStream: TStream;
Bitmap: TBitmap;
begin
ResStream := nil;
Png := nil;
Bitmap := nil;
try
ResStream := TResourceStream.CreateFromID(HInstance, ResID, RT_RCDATA);
Png := TPNGGraphic.Create;
Png.LoadFromStream(ResStream);
FreeAndNil(ResStream);
Bitmap := TBitmap.Create;
Bitmap.Assign(Png);
FreeAndNil(Png);
Result := ImageList.Add(Bitmap, nil);
finally
Bitmap.Free;
ResStream.Free;
Png.Free;
end;
end;
// this could be e.g. in the form's or datamodule's OnCreate event
begin
// create the imagelist
ImageList := TImageList.Create(Self);
ImageList.Name := 'ImageList';
ImageList.DrawingStyle := dsTransparent;
ImageList.Handle := ImageList_Create(ImageList.Width, ImageList.Height, ILC_COLOR32 or ILC_MASK, 0, ImageList.AllocBy);
// populate the imagelist with png images from resources
AddPngFromResource(ImageList, ...);
// or icons
AddIconFromResource(ImageList, ...);
end;
I had the exact same problems a couple of years ago. It's a Delphi problem. I ended up putting the images in the list at design time, even though I really didn't want to. I also had to use a DevExpress image list to get the best results and to use 32 bit color images.
As Jeremy said this is indeed a Delphi limitation.
One work around I've used for images that I was putting onto buttons (PNGs with alpha transparency in my case) is to store the PNGs as resources, and at run time paint them onto a button sized bitmap filled with clBtnFace. The bitmap was then used as the control's glyph.
Delphi's built in support for icons with alpha masks is very limited, however there's an excellent icon library kicon which may help.
I've narrowed a problem I have drawing on TImage.Canvas in Delphi 2009 down to the following reproducible case:
Given: a form, a TImage, TLabel and TButton on it. The TImage is anchored to all four edges so that resizing the form will resize the TImage. What I want to be able to do is draw on the maximal area of Image1 available to me after resizing. So in my test case I have the following code in my the Button's OnClick handler:
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption:= IntToStr (Image1.Width)+' x '+IntToStr(Image1.Height);
Image1.Canvas.Pen.Color:= 0;
Image1.Canvas.Rectangle(0,0,Image1.Width, Image1.Height);
end;
You'll see that if the form is resized, Image1.Width and .Height change as expected, however the rectangle that is drawn if the resized form is larger than the original one, will be incomplete, only drawing on the same area that was there previously.
How do I get it do use the entire resized area?
For what it's worth, in my original problem I had played with Image1.Stretch, which allows me to use more of the area upon resizing but will result in my drawings being distorted (not desired). If I also use Image1.Proportional, then it's better but I still can't use the full area available. Image1.AutoSize doesn't seem to be doing anything useful to me either.
Any help appreciated.
Add an OnResize-event to your form:
procedure TForm1.FormResize(Sender: TObject);
begin
Image1.Picture.Bitmap.Width := Image1.Width;
Image1.Picture.Bitmap.Height := Image1.Height;
end;
Also, if you are using the component to draw on, rather than displaying images from file etc, consider using the TPaintBox rather than TImage.
Maybe you have to also adjust Image1.Picture.Width/Height or Image1.Picture.Bitmap.Width/Height.