Author |
Topic |
|
timfa
USA
8 Posts |
Posted - Oct 04 2024 : 13:04:03
|
Hello,
I'm trying to display a selection rectangle in an image using "TImageEnView.MouseInteractGeneral := [miSelect];" to setup the correct mode. I am using RAD Studio/Delphi 12.1 Patch 1 with ImageEn 13.5.0. As long as there are no other layer objects added to the background image the selection rectangle displays and works fine. However, as soon as you add a layer object such as a TIEPolylineLayer object then the selection rectangle won't display again (when you left click in the image). This isn't quite correct because you might be able to get the selection rectangle to display if you left click on a layer near the top left of the image. I'm not fully sure where you have to click the second time to make the selection rectangle appear as it doesn't seem to always work.
To replicate this issue, using the Demos\Other\MouseInteract\MouseInteract.dproj project, perform these steps: - Open an image file - Click miSelect in the upper left list. - Click the left mouse button and drag creating a selection rectangle anywhere in the image. - You can repeat creating selection rectangle repeatedly anywhere with no issue. - Click mlCreateShapeLayers in the left bottom list (msSelect is auto unchecked). - Left click and drag in the image to create a circular shape. - Click miSelect in the upper left list. - Click the left mouse button and drag. (Just to be safe, perform this click left and above the shape that was created.) A selection rectangle should be created, but it isn't on my system. - Interestingly, left click in the shape object that was created earlier and drag to the right. This works. - The only way I can get a selection rectangle to show now is to start or end the drag operation such that the created rectangle touches the shape somewhere.
I don't think this behavior is the expected way this miSelect functionality is meant to work. What do I have to do to be able to define selection rectangles anywhere in the image regardless of it touching an existing layer object?
Thank you.
Tim
Tim F |
|
xequte
38586 Posts |
Posted - Oct 05 2024 : 00:15:19
|
Hi Tim
miSelect creates a selection in the current layer. When you use mlCreateShapeLayers to create a layer, the new layer becomes the "current" layer and is now the only layer accepting selection.
In order to be able to highlight a layer (make it the current one) when you click it, and then perform selection, you need to include one of the layer editing interactions, such as mlMoveLayers or mlResizeLayers (or add your own code in the mouse down event to set ImageEnView1.LayersCurrent).
https://www.imageen.com/help/TImageEnView.LayersCurrent.html
One other matter to remember is that, by default, the background layer (Layer 0) is not selectable, so you would need to enable ImageEView1.Layers[0].Selectable to be able to click and select it:
http://www.imageen.com/help/TIELayer.Selectable.html
Nigel Xequte Software www.imageen.com
|
|
|
timfa
USA
8 Posts |
Posted - Oct 08 2024 : 19:30:56
|
Hello again Nigel, I spent a little time playing around with your suggestions and finally gave up on using your layers and setMouseInteractGeneral := []. Now, I capture the starting mouse location in the mousedown event (when in the appropriate mode) and then in the mousemove event I save the move point. Then, I invalidate the ImageEnView object so the TImageEnView.OnDrawCanvas event fires and in that routine I draw the rectangle. This works fine. The problem is in the mousemove code which works fine also until I try to add scrolling as I drag. And this is because I can’t figure out how to correctly use the ViewX, ExtendX, OffsetX, Ruler parameters, etc. This just shouldn’t be this difficult, but I’m just not following your mapping between screen/windows coordinates and bitmaps coordinates, and related relationships. When I draw these rectangles, I can be zoomed such that the image is well inside the TImageEnView object (I call it ImgEdit in my code) and/or zoomed way in so the image has to scrolled.
Any help you can provide is appreciated.
Tim
Some of the code discussed above follows:
The code to draw the rectangle:
// The following instance variables are used when drawing the Region of Interest (ROI).
FDrawingROI : Boolean;
FROIStart : TPoint;
FROIEnd : TPoint;
procedure TImageEnWindowForm.ImgEditDrawCanvas(Sender: TObject;
ACanvas: TCanvas; ARect: TRect);
var
P : TPoint;
roiRect : TRect;
begin
// Unrelated code snipped
if FDrawingROI then
begin
// This is the section that draws a rectangle…
roiRect.TopLeft.X := ImgEdit.XBmp2Scr(FROIStart.X);
roiRect.TopLeft.Y := ImgEdit.YBmp2Scr(FROIStart.Y);
roiRect.BottomRight.X := ImgEdit.XBmp2Scr(FROIEnd.X);
roiRect.BottomRight.Y := ImgEdit.YBmp2Scr(FROIEnd.Y);
roiRect.NormalizeRect;
ACanvas.Brush.Style := bsClear;
ACanvas.Pen.Width := 1;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Mode := pmNot;
ACanvas.Rectangle(roiRect.TopLeft.X, roiRect.TopLeft.Y, roiRect.BottomRight.X, roiRect.BottomRight.Y);
end;
end;
The disastrous MouseMove event…
procedure TImageEnWindowForm.ImgEditMouseMove (Sender: TObject;
Shift: TShiftState;
X, Y: Integer);
var
ImageRow : Single;
ImageColumn : Single;
ImageRowInt : Integer;
ImageColumnInt : Integer;
IsVisible : Boolean;
scrnBmpX00, scrnBmpY00 : Integer;
scrnBmpX0, scrnBmpY0 : Integer;
scrnBmpX2, scrnBmpY2 : Integer;
scrnBmpLeft, scrnBmpTop : Integer;
scrnBmpRight, scrnBmpBottom : Integer;
BmpLeftEdge, BmpTopEdge : Integer;
BmpRightEdge, BmpBottomEdge : Integer;
viewChanged : Boolean;
begin
// ImgEdit.Invalidate; // Do this so ImgEditDrawCanvas executes.
// Convert X and Y to bitmap row and column:
ImageColumnInt := ImgEdit.XScr2Bmp(X, False);
ImageRowInt := ImgEdit.YScr2Bmp(Y, False);
// Convert X and Y to bitmap subpixel-level accuracy row and column:
IsVisible := ConvertClientToImage (X, Y, ImageRow, ImageColumn);
viewChanged := false;
if FDrawingROI then
begin
if ImageRowInt < 0 then
FROIEnd.Y := 0
else if ImageRowInt >= ImgEdit.IEBitmap.Height then
FROIEnd.Y := ImgEdit.IEBitmap.Height - 1
else
FROIEnd.Y := ImageRowInt;
if ImageColumnInt < 0 then
FROIEnd.X := 0
else if ImageColumnInt >= ImgEdit.IEBitmap.Width then
FROIEnd.X := ImgEdit.IEBitmap.Width - 1
else
FROIEnd.X := Trunc(ImageColumnInt);
// Scroll to the mouse if needed...
if ImgEdit.OffsetX > 0 then
begin
// FOLLOWING IS WHERE THE PROBLEMS BEGIN:
// I have tried so many things that the state of the following is a disaster.
// scrnBmpLeft := ImgEdit.XBmp2Scr(0);
// scrnBmpRight := ImgEdit.XBmp2Scr(ImgEdit.IEBitmap.Width-1);
//
// BmpLeftEdge := ImgEdit.XScr2Bmp(ImgEdit.ViewX);
// BmpRightEdge := ImgEdit.XScr2Bmp(ImgEdit.ViewX + ImgEdit.ExtentX - 1);
//
// if (BmpLeftEdge <= ImageColumnInt) AND (ImageColumnInt <= BmpRightEdge) then
// begin
// ; // do nothing
// end
// else if BmpLeftEdge > ImageColumnInt then
// begin
// ImgEdit.ViewX := iMax(X,scrnBmpLeft);
// viewChanged := true;
// end
// else if ImageColumnInt > BmpRightEdge then
// begin
// ImgEdit.ViewX := iMin(X,scrnBmpRight) - ImgEdit.ExtentX;
// viewChanged := true;
// end;
scrnBmpX00 := ImgEdit.XBmp2Scr(0);
scrnBmpX0 := ImgEdit.OffsetX + ImgEdit.RulerParams.RulerAreaLeft;
scrnBmpLeft := scrnBmpX0 + ImgEdit.ViewX;
scrnBmpRight := scrnBmpLeft + ImgEdit.ExtentX - 1;
scrnBmpX2 := ImgEdit.XBmp2Scr(ImgEdit.IEBitmap.Width-1);
// if (scrnBmpLeft <= X) AND (X <= scrnBmpRight) then
if (ImgEdit.ViewX <= X) AND (X <= ImgEdit.ViewX + ImgEdit.ExtentX - 1) then
begin
; // do nothing
end
// else if scrnBmpLeft > X then
else if ImgEdit.ViewX > X then
begin
if ImgEdit.ViewX > 0 then
begin
ImgEdit.ViewX := iMax(X,scrnBmpX00); // iMax(X,scrnBmpX0);
viewChanged := true;
end;
end
// else if X > scrnBmpRight then
else if X >= ImgEdit.ViewX + ImgEdit.ExtentX then
begin
ImgEdit.ViewX := iMin(X,scrnBmpX2) - ImgEdit.ExtentX;
viewChanged := true;
end;
// if (ImgEdit.ViewX <= X) AND (X < ImgEdit.ViewX + ImgEdit.ExtentX) then
// begin
// ; // do nothing
// end
// else if ImgEdit.ViewX > X then
// ImgEdit.ViewX := iMax(X,scrnBmpLeft)
// else if ImgEdit.ViewX + ImgEdit.ExtentX < X then
// ImgEdit.ViewX := iMin(X,scrnBmpRight) - ImgEdit.ExtentX;
end;
if ImgEdit.OffsetY > 0 then
begin
// scrnBmpTop := ImgEdit.YBmp2Scr(0);
// scrnBmpBottom := ImgEdit.YBmp2Scr(ImgEdit.IEBitmap.Height-1);
//
// BmpTopEdge := ImgEdit.YScr2Bmp(ImgEdit.ViewY);
// BmpBottomEdge := ImgEdit.YScr2Bmp(ImgEdit.ViewY + ImgEdit.ExtentY - 1);
// if (BmpTopEdge <= ImageRowInt) AND (ImageRowInt <= BmpBottomEdge) then
// begin
// ; // do nothing
// end
// else if BmpTopEdge > ImageRowInt then
// begin
// ImgEdit.ViewY := iMax(Y,scrnBmpTop);
// viewChanged := true;
// end
// else if ImageRowInt > BmpBottomEdge then
// begin
// ImgEdit.ViewY := iMin(Y,scrnBmpBottom) - ImgEdit.ExtentY;
// viewChanged := true;
// end;
scrnBmpY00 := ImgEdit.YBmp2Scr(0);
scrnBmpY0 := ImgEdit.OffsetY + ImgEdit.RulerParams.RulerAreaTop;
scrnBmpTop := scrnBmpY0 + ImgEdit.ViewY;
scrnBmpBottom := scrnBmpTop + ImgEdit.ExtentY - 1;
scrnBmpY2 := ImgEdit.YBmp2Scr(ImgEdit.IEBitmap.Height-1);
if (scrnBmpTop <= Y) AND (Y <= scrnBmpBottom) then
begin
; // do nothing
// end
// else if scrnBmpTop > Y then
// begin
// ImgEdit.ViewY := iMax(Y,scrnBmpY0);
// viewChanged := true;
// end
// else if X > scrnBmpBottom then
// begin
// ImgEdit.ViewX := iMin(Y,scrnBmpY2) - ImgEdit.ExtentY;
// viewChanged := true;
end;
// if (ImgEdit.ViewY <= Y) AND (Y <= ImgEdit.ViewY + ImgEdit.ExtentY) then
// begin
// ; // do nothing
// end
// else if ImgEdit.ViewY > Y then
// ImgEdit.ViewY := iMax(Y,scrnBmpTop)
// else if ImgEdit.ViewY + ImgEdit.ExtentY < Y then
// ImgEdit.ViewY := iMin(Y,scrnBmpBottom) - ImgEdit.ExtentY;
end;
end;
if viewChanged then
ImgEdit.update
else
ImgEdit.Invalidate; // Do this so ImgEditDrawCanvas executes.
// And a bunch of unrelated code that I cut.
end;
Tim F |
|
|
xequte
38586 Posts |
Posted - Oct 08 2024 : 21:32:31
|
Hi Tim
Sorry, I may not be understanding the purpose of your code well enough, but why wouldn't the MouseMove code just be:
FROIEnd.X := ImageEnView1.XScr2Bmp(X, False); FROIEnd.Y := ImageEnView1.YScr2Bmp(Y, False);
Here's my test project:
attach/xequte/2024108213219_ROI.zip 68.39 KB
procedure TForm1.ImageEnView1DrawCanvas(Sender: TObject; ACanvas: TCanvas;
ARect: TRect);
var
roiRect : TRect;
begin
if FDrawingROI then
begin
roiRect.TopLeft.X := ImageEnView1.XBmp2Scr(FROIStart.X);
roiRect.TopLeft.Y := ImageEnView1.YBmp2Scr(FROIStart.Y);
roiRect.BottomRight.X := ImageEnView1.XBmp2Scr(FROIEnd.X);
roiRect.BottomRight.Y := ImageEnView1.YBmp2Scr(FROIEnd.Y);
roiRect.NormalizeRect;
ACanvas.Brush.Style := bsClear;
ACanvas.Pen.Width := 1;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Mode := pmNot;
ACanvas.Rectangle(roiRect.TopLeft.X, roiRect.TopLeft.Y, roiRect.BottomRight.X, roiRect.BottomRight.Y);
end;
end;
procedure TForm1.ImageEnView1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FDrawingROI := True;
FROIStart.X := ImageEnView1.XScr2Bmp(X, False);
FROIStart.Y := ImageEnView1.YScr2Bmp(Y, False);
end;
procedure TForm1.ImageEnView1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if FDrawingROI then
begin
FROIEnd.X := ImageEnView1.XScr2Bmp(X, False);
FROIEnd.Y := ImageEnView1.YScr2Bmp(Y, False);
ImageEnView1.Invalidate();
end;
end;
procedure TForm1.ImageEnView1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FDrawingROI := False;
ImageEnView1.Invalidate();
end;
Nigel Xequte Software www.imageen.com
|
|
|
timfa
USA
8 Posts |
Posted - Oct 09 2024 : 09:15:09
|
Nigel,
Thanks for the quick response. Basically, your code does what mine does except for ensuring the end point is constrained to be in the range of 0 to ImageEnView1.IEBitmap.Width-1 or ImageEnView1.IEBitmap.height-1. This part of my code works. However, I was hoping for guidance on how to make the ImageEnView pan/scroll when the mouse is dragged outside the image area. My thought was that in the mousemove event I need to change the ViewX and ViewY values to shift the image such that it scrolls to the cursor location or the image boundary, whichever come first. That's what the following code was attempting to do for the X value (a similar block is present for the Y value)... if (ImgEdit.ViewX <= X) AND (X <= ImgEdit.ViewX + ImgEdit.ExtentX - 1) then begin ; // Do nothing as the mouse is in the visible range of the image. end else if ImgEdit.ViewX > X then begin // Pan/Scroll the image to the right... ImgEdit.ViewX := iMax(X,scrnBmpX00); // iMax(X,scrnBmpX0); viewChanged := true; end else // if X >= ImgEdit.ViewX + ImgEdit.ExtentX then begin // Pan/Scroll the image to the left... ImgEdit.ViewX := iMin(X,scrnBmpX2) - ImgEdit.ExtentX; viewChanged := true; end;
Unfortunately, this code doesn't work. Can you suggest how to fix it so panning/scrolling to the image boundaries works?
By the way, I normally, but they are optional, have horizontal and vertical rulers shown which may complicate some of this.
Thanks for any additional help you can provide.
Tim F |
|
|
timfa
USA
8 Posts |
Posted - Oct 09 2024 : 11:48:54
|
Nigel,
I enhanced your example to demonstrate the issue better. My code is attached...
attach/timfa/2024109114024_2024108213219_ROI _Enhanced__20241009T1129.zip 138.17 KB
Build the updated project and with the left mouse button down, drag out a rectangle and release the mouse. A blue rectangle will be displayed. Repeat this in the image area. Now scroll the image so the top left isn't at the image origin. Try creating more rectangle regions. You should see some weird behavior where the rectangles are shifted horizontally and/or vertically from where you drew the rectangle. Repeat this with the image scrolled in various locations and perhaps altering the zoom setting. I think you will see unexpected behavior. I added a checkbox to include / exclude rulers and a button to clear the blue rectangles.
I appreciation you assistance.
Tim F |
|
|
xequte
38586 Posts |
Posted - Oct 09 2024 : 20:50:01
|
Hi Tim
You need to consider whether you need x,y values in screen or bitmap values.
For MouseDown and drawing to the control canvas, you need screen value.
For drawing to the bitmap, or adding layers you need bitmap values (for layers the position and size is relative to the background layer, layer 0).
Your demo works if I change the LoadNormalizedRect method to:
procedure TForm1.LoadNormalizedRect(ForScreen: Boolean);
begin
if ForScreen then
begin
FNormalizedRect.TopLeft.X := ImageEnView1.XBmp2Scr(FROIStart.X);
FNormalizedRect.TopLeft.Y := ImageEnView1.YBmp2Scr(FROIStart.Y);
FNormalizedRect.BottomRight.X := ImageEnView1.XBmp2Scr(FROIEnd.X);
FNormalizedRect.BottomRight.Y := ImageEnView1.YBmp2Scr(FROIEnd.Y);
end
else
begin
FNormalizedRect.TopLeft := FROIStart;
FNormalizedRect.BottomRight := FROIEnd;
end;
FNormalizedRect.NormalizeRect;
end;
In ImageEnView1DrawCanvas use LoadNormalizedRect( True ); In CreateRegionRect use LoadNormalizedRect( False );
Updated demo: attach/xequte/2024109205048_roi2.zip 136.21 KB
Nigel Xequte Software www.imageen.com
|
|
|
timfa
USA
8 Posts |
Posted - Oct 10 2024 : 16:38:55
|
Nigel,
In my real code I did correctly handle the screen versus bitmap coordinates. I just missed that here. Thanks for catching that.
What I was really looking for was how to get the image to pan as I dragged out the rectangle area. It took me quite a while today to figure this out, but in the attached code I have figured it out. I added several local variables in my mousemove function to make it easier to analyze in the debugger what values were useful. In the online ImageEn documentation, or at least the documentation I've seen, it just isn't clear to me what coordinate system is being used for various properties/arguments. I'm fairly certain that you would have been able to do what I did much faster than it took me and with far less code. However, as I have said, the attached code works as desired now. Check it out and let me know if you have any feedback.
attach/timfa/20241010163715_2024109205048_roi2_Enhanced__20241010TT1616.zip 138.45 KB
I appreciate your help.
Tim F |
|
|
timfa
USA
8 Posts |
Posted - Oct 10 2024 : 18:01:49
|
I integrated my changes from the sample project I uploaded earlier and when testing at a zoom of >~160% I back to bad things happening with the panning while dragging. I clearly am missing something.
Tim F |
|
|
xequte
38586 Posts |
Posted - Nov 04 2024 : 20:05:50
|
Sorry Tim,
I'm not really following your code. Looking for issues, i see there are a number of situations where you multiply by the zoom only, e.g. ImageEnView1.ZoomX / 100.0. You need to check those references should not be ImageEnView1.XBmp2Scr(), for example.
I think you should email me for the latest beta. I have added a TImageEnView.AutoScrollTowardCursor() method, that you should be able to use instead of setting ViewX/ViewY.
You can use it like this:
procedure TForm1.ImageEnView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
fMyCustomSelectionActive := True;
fMyCustomSelectionStart.X := ImageEnView1.XScr2Bmp(X, False);
fMyCustomSelectionStart.Y := ImageEnView1.YScr2Bmp(Y, False);
end;
procedure TForm1.ImageEnView1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if fMyCustomSelectionActive then
begin
ImageEnView1.AutoScrollTowardCursor();
fMyCustomSelectionCurr.X := ImageEnView1.XScr2Bmp(X, False);
fMyCustomSelectionCurr.Y := ImageEnView1.YScr2Bmp(Y, False);
ImageEnView1.Invalidate();
end;
end;
procedure TForm1.ImageEnView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
fMyCustomSelectionActive := False;
end;
procedure TForm1.ImageEnView1DrawCanvas(Sender: TObject; ACanvas: TCanvas; ARect: TRect);
var
scrRect: TRect;
begin
if fMyCustomSelectionActive then
begin
scrRect.Left := ImageEnView1.XBmp2Scr( fMyCustomSelectionStart.X );
scrRect.Top := ImageEnView1.YBmp2Scr( fMyCustomSelectionStart.Y );
scrRect.Right := ImageEnView1.XBmp2Scr( fMyCustomSelectionCurr.X );
scrRect.Bottom := ImageEnView1.YBmp2Scr( fMyCustomSelectionCurr.Y );
ACanvas.Brush.Style := bsClear;
ACanvas.Pen.Width := 3;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Mode := pmNot;
ACanvas.Rectangle( scrRect.Left, scrRect.Top, scrRect.Right, scrRect.Bottom);
end;
end;
Nigel Xequte Software www.imageen.com
|
|
|
|
Topic |
|
|
|