bluefireteam / photo_view

📸 Easy to use yet very customizable zoomable image widget for Flutter, Photo View provides a gesture sensitive zoomable widget. Photo View is largely used to show interacive images and other stuff such as SVG.
MIT License
1.91k stars 547 forks source link

Example for getting pixel location on image, on onTap #546

Open ElecSmurf opened 12 months ago

ElecSmurf commented 12 months ago

Hi,

I'm using the PhotoView.customChild with a background image and a customPaint (stacked) over it. This is wrapped in a Listener to have scrolling with the mousewheel.

That is working great, love the performance and flexibility of your solution!

I'm now struggling however to get the pixel location on the image from an onTap event. I tried lots of ways, but don't seem to get the correct coords.

Is there any documentation or example on this that could help me?

Thanks in advance!

ElecSmurf commented 12 months ago

I have figured it out, depending on the image ratio vs available screen ratio, there can be either in width or height some empty space that needs to be taken into account. This is my solution should it help someone:

onPointerHover: (PointerHoverEvent event) {
   Size? sizeOnScreen = context.findRenderObject()?.paintBounds.size;
   double widthFactor = (sizeOnScreen?.width ?? 1) / imageWidth;
   double heightFactor = (sizeOnScreen?.height ?? 1) / imageHeight;
   if (widthFactor < heightFactor) {
      double x = event.localPosition.dx / widthFactor;
      double imageRatio = imageHeight / imageWidth;
      double heightOnScreen = imageRatio * (sizeOnScreen?.width ?? 1);
      double heightTopSpace = ((sizeOnScreen?.height ?? 1) - heightOnScreen) / 2;
      double y = (event.localPosition.dy - heightTopSpace) / widthFactor;
      print('$x,$y');
   } else {
      double y = event.localPosition.dy / heightFactor;
      double imageRatio = imageWidth / imageHeight;
      double widthOnScreen = imageRatio * (sizeOnScreen?.height ?? 1);
      double widthSideSpace =
      ((sizeOnScreen?.width ?? 1) - widthOnScreen) / 2;
      double x = (event.localPosition.dx - widthSideSpace) / heightFactor;
      print('$x,$y');
   }
},
ElecSmurf commented 12 months ago

After good night of sleep I found some errors and refactored to this solution. This works for all cases.

Offset? getPixelPosition(Offset mouseLocalLocation) {
   Size? screenSize = context.findRenderObject()?.paintBounds.size;
   double zoomFactor = _controller.scale ?? 1;

   if (screenSize != null && _controller.scale != null) {
      double xRelToCenScreenSpace = (screenSize.width / 2) - mouseLocalLocation.dx;
      double yRelToCenScreenSpace = (screenSize.height / 2) - mouseLocalLocation.dy;

      double x = ((imageWidth / 2) - (xRelToCenScreenSpace / zoomFactor)) - (_controller.position.dx / zoomFactor);
      double y = (imageHeight / 2) - (yRelToCenScreenSpace / zoomFactor) - (_controller.position.dy / zoomFactor);

      if (x < 0 || y < 0 || x > imageWidth || y > imageHeight) {
         return null;
      } else {
         print('Pos: ${x.toStringAsFixed(2)},${y.toStringAsFixed(2)}');
         return Offset(x, y);
      }
   }
   return null;
}

Maybe this method can be added as a static (helper) method on the controler?