How to navigation image? lazarus / 39-Graphics ---------------------------------------------------------------------- barsoom · 2021-08-18 22:54 I would like to havo something like the navigation map of TChart, but for an image. It is: To have a big image inside a scrollbox, and, in the same form, the same image as a thumbnail with a red box showing the portion of the image showed in the scrollbox (since the full-size image is bigger than the client size of the scrollbox. The idea is to allow the user, not only to know which section of the image is observing, but also to move the redbox windows to change the portion of the image showed in the scrollbox. For the moment, what i have is a imag showed both at full size in side the scrollbox, and in a small panel close to it. I created a shape on top of the thumbail, and this is the code i have to calculate the size of the red window marking the visible area of the image, and its position. I added a quick and dirty demo project to help you to understand what i want to do. My first problem is the position, since i do not know how to know the coordinates of the iupperleft corner of the section of the image showed in the scrollbox. This is the code and where i have the problem: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.RedBoxExecute(Sender: TObject);var  imagew: integer;  //Original image width at full size  imageh: integer;  // Original image height  at full size  windoww: integer; // frame width  (scrollbox)  windowh: integer; // frame height  (scrollbox)  thumbnailw: integer;  //map width: the same image but small outsize the scrollbox  thumbnailh: integer;  //map height: the same image but small outsize the scrollbox  redboxw: integer; // calculated redbox width  redboxh: integer; // calculated redbox height  imageX: integer;  // X coordinate of the upperleft pixel of the image showed in the scrollbox  imageY: integer;  // Y coordinate of the upperleft pixel of the image showed in the scrollbox  redboxX: integer; // Calculated left position of the shape  redboxY: integer; // Calculated top position of the shapebegin   imagew := image23.Picture.Bitmap.Width;   imageh := image23.Picture.Bitmap.Height;   windoww := scrollbox3.clientWidth;   windowh := scrollbox3.clientHeight;   thumbnailw := image21.Width;   thumbnailh := image21.Height;   redboxw := round((Thumbnailw*windoww) div imagew);   redboxh := round((Thumbnailh*windowh)div imageh);   shape1.Width := redboxw;   shape1.Height := redboxh;    imageX := scrollbox3.HorzScrollBar.ScrollPos;  <-- this is not correct at all    imageY := scrollbox3.vertScrollBar.ScrollPos;   <-- this is not correct at all   redboxX := round(( imagew*imageX) div thumbnailw);   redboxY := round(( imageh*imageY) div thumbnailh);   shape1.Left := redboxX;   shape1.Top := redboxY;end; I know this code is not perfect, but it is my testing code to understand what i am doing before and fsolve problems before to do it more efficient and simple. So the question now is: How could i know the coordinates of the upperleft pixel of the image showed in the scrollbox in any moment? Thanks! ---------------------------------------------------------------------- loaded · 2021-08-19 12:10 It is possible to make any transaction you want with a few changes. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);begin  if openpicturedialog1.Execute then     begin       image1.Proportional:=false;  // added       ScrollBox1.HorzScrollBar.Tracking:=true; // added       ScrollBox1.VertScrollBar.Tracking:=true; // added        image1.Picture.LoadFromFile(openpicturedialog1.FileName);       image2.Picture.LoadFromFile(openpicturedialog1.FileName);       RedBox.Execute;     end;end;  procedure TForm1.RedBoxExecute(Sender: TObject);begin //modify ...  Shape1.Width:=round(ScrollBox1.ClientWidt h*(image1.Width/Image2.Picture.Width));  Shape1.Height:=round(ScrollBo x1.ClientHeight*(image1.Height/Image2.Picture.Height));  Shape1.Left:= round(Scrollbox1.HorzScrollBar.ScrollPos*(image1.Width/Image2.Picture. Width));  Shape1.Top:=round(Scrollbox1.VertScrollBar.ScrollPos*(image1 .Height/Image2.Picture.Height));end; ---------------------------------------------------------------------- barsoom · 2021-08-19 17:45 Thanks @Loaded! What simple!! Thanks so much!! I added your correction to the example project, and it works great. I added panning option to the main image, and the redbox reflect the changes. To reduce the flickering i added this code on form creation: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FormCreate(Sender: TObject);begin  Form1.DoubleBuffered := True;  Scrollbox1.DoubleBuffered:=True;end; Next step (and problem) is to move the redbox on the thumbnail, and show on the main image the portion marked in the redbox. To do that, i added onMoveup, onMousemove and onMouseMove actions: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Shape1MouseDown(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);begin  screen.cursor:=crHandPoint;end; procedure TForm1.Shape1MouseLeave(Sender: TObject);begin  Screen.cursor:=crDefault;end; procedure TForm1.Shape1MouseMove(Sender: TObject; Shift: TShiftState; X,  Y: Integer);var  redboxX: integer;  redboxY: integer;begin  if ssleft in shift then    begin      redboxX:= shape1.Left;      redboxY:= shape1.Top;      Shape1.Left:= round(redboxX+X);      Shape1.Top:= round(redboxY+Y);      Scrollbox1.HorzScrollBar.ScrollPos := round((Shape1.Left * Image2.Picture.Width) / image1.Width);  <-- This produces a compilation error      Scrollbox1.VertScrollBar.ScrollPos := round(Shape1.Top / (image1.Height/Image2.Picture.Height)); <-- This produces a compilation error    endend;                         I tried to reverse the piece of code you proposed to calculate the shape size and position. I took the position algorith and reversed, but it results on compilation error: Quoteunit1.pas(157,17) Error: Argument cannot be assigned to unit1.pas(158,17) Error: Argument cannot be assigned to Those lines are these: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Scrollbox1.HorzScrollBar.ScrollPos := round((Shape1.Left * Image2.Picture.Width) / image1.Width);  <-- This produces a compilation error      Scrollbox1.VertScrollBar.ScrollPos := round(Shape1.Top / (image1.Height/Image2.Picture.Height)); I attach the new version of the demo project ---------------------------------------------------------------------- howardpc · 2021-08-19 18:07 Try this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Shape1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);var  deltaX, deltaY: Integer;begin  if ssleft in shift then  begin      deltaX := Round((Shape1.Left * Image2.Picture.Width) / image1.Width);      deltaY := Round(Shape1.Top / (Image1.Height/Image2.Picture.Height));      Shape1.Left := Shape1.Left + X;      Shape1.Top := Shape1.Top + Y;      ScrollBox1.ScrollBy(deltaX, deltaY);    endend; ---------------------------------------------------------------------- barsoom · 2021-08-19 18:26 Thanks @howardpc. Now it compiles, but it does not change the position of the main image... in fact, after releasing the mouse left button, the redbox returns to the original position! Meanwhile the mainimage does not change at all its position. ---------------------------------------------------------------------- loaded · 2021-08-19 19:16 If you want the process to work bidirectionally, we will need to make minor changes. First, let's add the following variables: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  Form1: TForm1;  FShapeDragging : boolean;  FShapeDownLocation,FShapeStartingLocation : TPoint; Let's set the mouse events of the shape as follows. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Shape1MouseDown(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);begin  FShapeDragging := true;  FShapeDownLocation := Mouse.CursorPos;  FShapeStartingLocation := TPoint.Create(TShape(sender).left, TShape(sender).top);end;   procedure TForm1.Shape1MouseMove(Sender: TObject; Shift: TShiftState; X,  Y: Integer);begin  if FShapeDragging then begin  TShape(sender).left := FShapeStartingLocation.X + (Mouse.CursorPos.X - FShapeDownLocation.X);  TShape(sender).top := FShapeStartingLocation.Y + (Mouse.CursorPos.Y - FShapeDownLocation.Y);  Scrollbox1.HorzScrollBar.Position:=round(Shape 1.Left/(image1.Width/Image2.Picture.Width));  Scrollbox1.VertScrollBar .Position:=round(Shape1.Top/(image1.Height/Image2.Picture.Height))  end;end; procedure TForm1.Shape1MouseUp(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);begin  FShapeDragging := false;end; And let's put the final touch here. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.RedBoxExecute(Sender: TObject);begin  if not FShapeDragging then begin //added   Shape1.Width:=round(ScrollBox1.ClientWidth*(image 1.Width/Image2.Picture.Width));  Shape1.Height:=round(ScrollBox1.Clien tHeight*(image1.Height/Image2.Picture.Height));  Shape1.Left:=round(Sc rollbox1.HorzScrollBar.ScrollPos*(image1.Width/Image2.Picture.Width)); Shape1.Top:=round(Scrollbox1.VertScrollBar.ScrollPos*(image1.Height/I mage2.Picture.Height));  end;end; ---------------------------------------------------------------------- barsoom · 2021-08-19 20:48 Great solution @Loaded!! It works nicely. Nice idea the final touch. I didn´t think on that option. The unique issues i could see are: 1)  that the scrollbox moves really quick as you move the redbox, but the image itselft replies with a delay. May be because i am testing with  relatively big image about 2600x1800 pixels. However, it also occurs with the main image panning. 2) the redbox shows also a delay respect the movement of the pointer. I attach the new version with a modification on the onMouseUp event to avoid leave the redbox outside the thumbnail, using this code: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Shape1MouseUp(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);begin  FShapeDragging := false;  //few new lines of code to avoid leave the redbox outside the thumbnail  if shape1.top < 0 then shape1.top := 0    else if shape1.top > (image1.Height - shape1.Height) then shape1.Top := (image1.Height - shape1.Height);  if shape1.left < 0 then shape1.left := 0    else if shape1.left > (image1.Width - shape1.Width) then shape1.Left := (image1.Width - shape1.Width)end; ---------------------------------------------------------------------- loaded · 2021-08-20 07:55 Yes, it is inevitable to have stutters in large image files. But the last point I will put forward with the material at hand and my apprenticeship knowledge; It will improve the situation just a little bit. Of course there are better ones, but I don't know that either. Primarily for large memory requirements; Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$R *.lfm}{$SetPEFlags $0020}     We save memory by shrinking the image preview process; Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  buffer:TPicture;  //added for resize imagebegin  if openpicturedialog1.Execute then     begin     Screen.cursor:=crHourGlass;       image1.Proportional:=false;       buffer:=TPicture.Create; //added       buffer.LoadFromFile(openpicturedialog1.FileName); //added       image1.Canvas.StretchDraw(Rect(0, 0, image1.Width, Image1.Height), buffer.Bitmap); //added       Image2.Enabled:=True;       Image2.AntialiasingMode:=amOff; //added       image2.Picture.Assign(buffer); //added       buffer.free; //added     RedBox.Execute;       Shape1.visible:=True;     end;  Screen.cursor := crDefault;end; ---------------------------------------------------------------------- barsoom · 2021-08-21 21:45 wow @loaded, Really much much better! Thanks so much!! I added your inputs and it work nicely well. I also added a magnifier option. I used it in a short program i did long long time ago with delphi, but still works in Lazarus. I am sure it could be improved, but here it is. May be it coud be usefull to others.