Search code examples
flutterdarttransform

Flutter: scaling and translate an image on zoom interaction


I'm writing in flutter, an image cropping editor, where I need a function that the user can scale and translate the image when the user zooms (pinch zoom or mouse scroll). I know Flutter has widgets like the InteractiveViewer, but in my case I can't use it. Simplified my widget looks like this:

Transform.scale(
    scale: _zoomFactor,
    alignment: Alignment.center,
    child: Transform.translate(
    offset: _translate,
    child: Listener(
      behavior: HitTestBehavior.translucent,
      onPointerSignal: isDesktop ? _mouseScroll : null,
      child: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onScaleStart: _onScaleStart,
        onScaleEnd: _onScaleEnd,
        onScaleUpdate: _onScaleUpdate,
        onDoubleTapDown: _handleDoubleTapDown,
        onDoubleTap: _handleDoubleTap,
        child: Image.network(
          'https://picsum.photos/id/176/2000',
          fit: BoxFit.contain,
        ),
      ),
    ),
  ),
);

The goal now is that when the user zooms to a point, the widget should scale and transform to that point. The Transform.translate part is a bit tricky, that it feels same as in other image editors. Does anyone know how this function should look? If necessary, you can see the full code and my current method on GitHub here.


Solution

  • I finally found a solution by myself. I post it below, maybe it will help someone else.

    
      void _mouseScroll(PointerSignalEvent event) {
        // Check if interaction is blocked
        if (_blockInteraction) return;
    
        if (event is PointerScrollEvent) {
          // Define zoom factor and extract vertical scroll delta
          double factor = 0.1;
          double deltaY = event.scrollDelta.dy;
    
          double startZoom = _userZoom;
          double newZoom = _userZoom;
          // Adjust zoom based on scroll direction
          if (deltaY > 0) {
            newZoom -= factor;
            newZoom = max(1, newZoom);
          } else if (deltaY < 0) {
            newZoom += factor;
            newZoom = min(7, newZoom);
          }
    
          // Get dimensions of the rendered image
          double imgW = _renderedImgConstraints.maxWidth;
          double imgH = _renderedImgConstraints.maxHeight;
    
          // Calculate the transformed local position of the pointer
          Offset transformedLocalPosition = event.localPosition * startZoom;
          // Calculate the size of the transformed image
          Size transformedImgSize = Size(
            imgW * startZoom,
            imgH * startZoom,
          );
    
          // Calculate the center offset point from the old zoomed view
          Offset realHitPoint = Offset(
            transformedLocalPosition.dx - transformedImgSize.width / 2,
            transformedLocalPosition.dy - transformedImgSize.height / 2,
          );
          // Calculate the center offset point from the old zoomed view
          Offset centerOffset = _translate + realHitPoint / startZoom;
          // Calculate the center offset point from the new zoomed view
          Offset centerZoomOffset = centerOffset * startZoom / newZoom;
    
          // Update translation and zoom values
          _translate -= centerOffset - centerZoomOffset;
          _userZoom = newZoom;
    
          // Set offset limits and trigger widget rebuild
          _setOffsetLimits();
          setState(() {});
        }
      }