Why did CopyRect Flipped the second image in delphi 10.3? - delphi

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

Related

Delphi TImage32 - how to make the component invisible if no picture is loaded?

If you place a normal TImage component on a form or panel over other components >> it is invisible at runtime as long no picture is loaded. So other things under it are visible.
But TImage32 is painting a gray box by default.
How do I make the it invisible while leaving the setting: .Visible:=True; if no picture is loaded?
(I still need events working on the component, like OnClick...)
Yes, this is a duplicate question, BUT the solution-link from the previous topic is dead. :(
While I still have access to the newsgroup posts, I don't know how the topic ID relates to the topic title (which is all I have). However, based an a search in the newsgroup I found several places where TImage32Ex was mentioned. I guess that this component (which is not part of the core library) was part of the solution in some way.
So, while the extension pack where this component comes from is no longer maintained, let's dig deeper in what it did.
First, I must say that TImage32 will always paint (copy) the content of its buffer to the display. This means whatever graphic is behind this component will get overwritten by default.
The trick TImage32Ex does is to get the parents content and draws it into the buffer.
With adaptions the code looks like this
var
P: TPoint;
SaveIndex: Integer;
begin
SaveIndex := SaveDC(Buffer.Handle);
try
GetViewportOrgEx(Buffer.Handle, P);
SetViewportOrgEx(Buffer.Handle, P.X - Left, P.Y - Top, nil);
IntersectClipRect(Buffer.Handle, 0, 0, Parent.ClientWidth, Parent.ClientHeight);
Parent.Perform(WM_ERASEBKGND, Buffer.Handle, 0);
Parent.Perform(WM_PAINT, Buffer.Handle, 0);
finally
RestoreDC(Buffer.Handle, SaveIndex);
end;
end;
The above code draws (WM_PAINT) the parent's content onto the buffer.
For example if you want to make the TPaintBox32 instance called PaintBox32 to be transparent just add the following code to the 'PaintBuffer' handler:
procedure TForm3.PaintBox32PaintBuffer(Sender: TObject);
var
P: TPoint;
SaveIndex: Integer;
begin
SaveIndex := SaveDC(PaintBox32.Buffer.Handle);
try
GetViewportOrgEx(PaintBox32.Buffer.Handle, P);
SetViewportOrgEx(PaintBox32.Buffer.Handle, P.X - PaintBox32.Left, P.Y - PaintBox32.Top, nil);
IntersectClipRect(PaintBox32.Buffer.Handle, 0, 0, PaintBox32.Parent.ClientWidth, PaintBox32.Parent.ClientHeight);
PaintBox32.Parent.Perform(WM_ERASEBKGND, PaintBox32.Buffer.Handle, 0);
PaintBox32.Parent.Perform(WM_PAINT, PaintBox32.Buffer.Handle, 0);
finally
RestoreDC(PaintBox32.Buffer.Handle, SaveIndex);
end;
end;
Note: While this works basically, it might not capture the parent's sub controls properly. This is especially true for TWinControl descendants. While there are solutions around to cover this scenario as well, it's far more complicated to cover this in every detail (e.g. the blinking cursor of an underlying TEdit instance)
I use a timage to mask a progress bar and give it a shape.
What I do is load a png with parts that are transparent and then place it over my progress bar.
I think this should achieve your goal. Place a transparent png in your timage.
Cheers,
E.

Resizing main menu for high DPI/font size

I have an issue with font height in standard main menu/popup menu when it contains images. Looks like this.
When there are no images, there are no problems as displayed above. Main menu uses TImageList with image width/height set to 16.
So I want to preserve image size at 16x16 and center it, to get something like this:
How can I read the font height of the main menu and adjust images in TImageList accordingly? One idea I have is to copy images from one TImageList to another with larger image width/height but I still need to determine proper size from the font size. How do I do that?
UPDATE
I solved this by examining SystemParametersInfo - SPI_GETNONCLIENTMETRICS value and using the iMenuHeight value for TImageList Width/Height. As images are deleted after changing Width/Height, I copied another to another TImageList. Works exactly as it should. Thank you everyone for your most helpful answers.
UPDATE 2
After examining the problem futher the solution which I marked as correct down there is giving better result so I switched to that one instead. Tested on Win7 and XP, appears to be working properly.
You can get the height of Screen.MenuFont by selecting it to a temporary DC:
function GetMenuFontHeight: Integer;
var
DC: HDC;
SaveObj: HGDIOBJ;
Size: TSize;
begin
DC := GetDC(HWND_DESKTOP);
try
SaveObj := SelectObject(DC, Screen.MenuFont.Handle);
GetTextExtentPoint32(DC, '|', 1, Size); // the character doesn't really matter
Result := Size.cy;
SelectObject(DC, SaveObj);
finally
ReleaseDC(HWND_DESKTOP, DC);
end;
end;
Well, Canvas.GetTextHeight('gh') usually helps to get height of text. But in case of different DPI, you can simply scale by Screen.PixelsPerInch / 96.0.
The text height is probably not what you need to use. I suggest that you use icons whose square dimension is equal to the prevailing small icon size. That's the system metric whose ID is SM_CXSMICON. Retrieve the value by calling GetSystemMetrics passing that ID.
You can use Power Menu Component with many advanced features
Download from here : http://elvand.com/downloads/DELPHI/PowerMenu.zip
Delphi7-XE2
size=193 KB
#include <windows.h>
int GetMainMenuHeight(void)
{
NONCLIENTMETRICS Rec;
Rec.cbSize = sizeof(Rec);
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, Rec.cbSize, &Rec.cbSize, 0))
return Rec.iMenuHeight;
else return -1;
}

How can I copy TBitmap memory using with windows CopyMemory function

I have 1 bitmap object witdh : 1024px and height : 768 px
I want to cut this bitmap object to 2 part like left and right but I don't want to use DrawBitmap method in canvas because this method can use more CPU then CopyMemory.
I don't want to use this method ( leftImg.Canvas.DrawBitmap(MainBmp, RectF(0,0, MainBmp.Width div 2, bmp.Height),
RectF(0,0, leftImg.Width, leftImg.Height), 1, True); )
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftBits := PAlphaColorArray(leftImg.Scanline[0]);
CopyMemory(#leftBits[0], #MainBmp.StartLine[0], (MainBmp.Width div 2) * bmp.Height);
if I am doing like this he can copy but not left part of bitmap :( he copy half of top to bottom.
That drawing is exactly what I want to do.
After cut procces, i need like this without using any loop (like while or for)
Thanks
No can do! As you've found out image data is layout in the memory line by line (hence scanline). What you want could only be possible if it was column by column. Without any loops this is not possible.
As you noticed, a scanline is a row of pixels, from left to right. There is one scanline for each pixel of vertical height in the image.
Your 1024px x 768px images have 768 scanlines. Copying the first half of the data from scanlines yields you the top half of the image.
You wouldn't have to go through every pixel, you can skip ahead since everything is indexed.
However, since you want both halves, you're not wasting any work by going through the whole thing. As you iterate through the data, copy both the left and right parts out at the same time. So, for the first scanline, copy the first half of pixels to the left image and the rest of the pixels to the right image, go to the next line, and repeat.
This should be less work than DrawBitmap twice.
Also, rather than loading the image, displaying it, then splitting it, split it while you're loading the image.
You'll still need a loop, unless you want to write everything 768 times.
Technically, you could rotate the image and do it the way you want, but rotating it would require loops too, and you'd have to rotate it back when you're done.
Use the TCanvas.CopyRect() method to copy portions of one TCanvas to another TCanvas. It allows the two bitmaps to have different pixel formats. The OS will handle the differences internally for you:
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftImg.Canvas.CopyRect(
Rect(0, 0, leftImg.Width, leftImg.Height),
MainBmp.Canvas,
Rect(0, 0, leftImg.Width, leftImg.Height)
);
rightImg.Canvas.CopyRect(
Rect(0, 0, rightImg.Width, rightImg.Height),
MainBmp.Canvas,
Rect(leftBmp.Width, 0, rightImg.Width, rightImg.Height)
);

Problem with TImage and TScrollBox

I am working with delphi.
I have one scroll box in which I am putting TImage control. Now I wanted to zoom the image rendered into TImage control. So, I am using stretchDraw method of TCanvas. My code is -
if sbZoom.Down then begin
rct := imgmain.Picture.Bitmap.Canvas.ClipRect;
rct := Rect(rct.Left * 2,rct.Top * 2,rct.Right * 2,rct.Bottom * 2);
imgmain.Picture.Bitmap.Canvas.StretchDraw(rct,imgmain.Picture.Bitmap);
imgmain.Repaint;
end;
It is correctly zooming the image, my problem is I want the size of scroll box also should be changed with zooming of image.
Also explain me parameters of Canvas.StretchDraw method. I am little confused with it.
Thank You.
You can do this quite easily without calling StretchDraw:
if Zoomed then begin
Image1.AutoSize := false;
Image1.Stretch := true;
Image1.Width := 2*Image1.Width;
Image1.Height := 2*Image1.Height;
end
else begin
Image1.Stretch := false;
Image1.AutoSize := true;
end;
AutoSize := true assures that the TImage is the same size as the picture inside. During zoom we switch AutoSize off and Stretch on, so the picture is resized to the TImage size (which is still the same here). Then we double the size of the TImage to make the zoom effect. As the TImage is now larger, the scrollbox can work properly.
Uwe Raabe is giving you the right way to do it. Here's why your way doesn't work: A scroll box will show scrollbars and help you see whole controls. In your case, it will only show scrollbars when the TImage object grows larger then the Scrollbox. The Scrollbox can't possibly know the internals of TImage so it doesn't care about TImage.Picture, it only cares about the control. And a TImage that has AutoSize = False doesn't care about it's Picture, it's size stays the same at all times.
Your code repaints the base bitmap onto itself. The problem is, the bitmap has fixed Width and Height: if you paint outside the bitmap's area you're basically silently ignored. When you're "zooming" by StretchDrawing the bitmap onto itself (and I'm surprised it worked to start with!) you're not making the bitmap larger and the stuff that doesn't fit gets silently clipped away. If you do want the internal bitmap to change size then you'll first need to create a new, larger bitmap, draw your enlarged image to the new bitmap and then assign the bitmap to your TImage. If you do this, make sure TImage.AutoSize = True.
You should set the size of the image control to the size of the bitmap.

How to draw on the entire area of a resized TImage in Delphi?

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.

Resources