admin管理员组

文章数量:1122846

I have a map image, width = 11,354 pixels, height = 8319 pixels. I have a Google Pixel 8 phone size 1008w x 2190h physical pixels. Using the phone as a viewport, I want to zoom into pixel (2643,1288) on the map image.

Using InteractiveViewer, I use the phone (1008x2100) as a viewport onto the map image (11354x8319).

    return MaterialApp(
        title: 'example',
        home: Scaffold(
          appBar: AppBar(
            title: const Text('peter67_transformationcontroller'),
          ),
          body: InteractiveViewer(
              maxScale: double.infinity,
              child: Image.asset(
                "assets/031e01.jpg",
                height: double.infinity,
                width: double.infinity,
                alignment: Alignment.center,
              )),
        ));

The interactiveViewer shrinks the map image(11354x8319) to fit the phone viewport(1008x2190) width with large margins of padding (about 1/3 of screen) on top and bottom of the shown image.

To zoom back so I can see the map image in its original size, I use the following code to calculate the zoom factor (I am trying to be device independent) and then place it in the transformationController.

    double mapwidth = mapSize.width.toDouble();
    double mapheight = mapSize.height.toDouble();
    double screenWidth = MediaQuery.of(context).size.width *
        MediaQuery.of(context).devicePixelRatio;
    double screenHeight = MediaQuery.of(context).size.height *
        MediaQuery.of(context).devicePixelRatio;
    double zoomfactorw = (mapwidth) / (screenWidth);
    double zoomfactorh = (mapheight) / (screenHeight);
    double zoomfactor = zoomfactorw < zoomfactorh ? zoomfactorh : zoomfactorw;

    TransformationController viewTC = TransformationController();
    viewTC.value.setEntry(0, 0, zoomfactor);
    viewTC.value.setEntry(1, 1, zoomfactor);
    viewTC.value.setEntry(2, 2, zoomfactor);

All of this works fine.

It is the translation to a specific pixel that I cannot seem to do.

In my example, the zoomfactor calculates correctly to 11354/1008 = 11.2639. This zoom should create a logical map image of (11354x8319) + margins top and bottom of 8177 pixels. The total logical image should be 11354x24668 (phone size * 11.2639). Then I could translate to map pixel (2643,1288) by adding the top margin to the y axis.

    double xTranslate = 2643.0;
    double yTranslate = 1288.0 + 8177.0; 
    viewTC.value.setEntry(0, 3, -xTranslate);
    viewTC.value.setEntry(1, 3, -yTranslate);

But this does not work. Instead the zoomfactor of 11.2639 creates a logical image of approximately (11,089 x 5598)??? Hence I have no idea how to calculate the xtranslate and ytranslate.

Is there a way to zoom and translate to a specific pixel in InteractiveViewer using the transformationController?

I have a map image, width = 11,354 pixels, height = 8319 pixels. I have a Google Pixel 8 phone size 1008w x 2190h physical pixels. Using the phone as a viewport, I want to zoom into pixel (2643,1288) on the map image.

Using InteractiveViewer, I use the phone (1008x2100) as a viewport onto the map image (11354x8319).

    return MaterialApp(
        title: 'example',
        home: Scaffold(
          appBar: AppBar(
            title: const Text('peter67_transformationcontroller'),
          ),
          body: InteractiveViewer(
              maxScale: double.infinity,
              child: Image.asset(
                "assets/031e01.jpg",
                height: double.infinity,
                width: double.infinity,
                alignment: Alignment.center,
              )),
        ));

The interactiveViewer shrinks the map image(11354x8319) to fit the phone viewport(1008x2190) width with large margins of padding (about 1/3 of screen) on top and bottom of the shown image.

To zoom back so I can see the map image in its original size, I use the following code to calculate the zoom factor (I am trying to be device independent) and then place it in the transformationController.

    double mapwidth = mapSize.width.toDouble();
    double mapheight = mapSize.height.toDouble();
    double screenWidth = MediaQuery.of(context).size.width *
        MediaQuery.of(context).devicePixelRatio;
    double screenHeight = MediaQuery.of(context).size.height *
        MediaQuery.of(context).devicePixelRatio;
    double zoomfactorw = (mapwidth) / (screenWidth);
    double zoomfactorh = (mapheight) / (screenHeight);
    double zoomfactor = zoomfactorw < zoomfactorh ? zoomfactorh : zoomfactorw;

    TransformationController viewTC = TransformationController();
    viewTC.value.setEntry(0, 0, zoomfactor);
    viewTC.value.setEntry(1, 1, zoomfactor);
    viewTC.value.setEntry(2, 2, zoomfactor);

All of this works fine.

It is the translation to a specific pixel that I cannot seem to do.

In my example, the zoomfactor calculates correctly to 11354/1008 = 11.2639. This zoom should create a logical map image of (11354x8319) + margins top and bottom of 8177 pixels. The total logical image should be 11354x24668 (phone size * 11.2639). Then I could translate to map pixel (2643,1288) by adding the top margin to the y axis.

    double xTranslate = 2643.0;
    double yTranslate = 1288.0 + 8177.0; 
    viewTC.value.setEntry(0, 3, -xTranslate);
    viewTC.value.setEntry(1, 3, -yTranslate);

But this does not work. Instead the zoomfactor of 11.2639 creates a logical image of approximately (11,089 x 5598)??? Hence I have no idea how to calculate the xtranslate and ytranslate.

Is there a way to zoom and translate to a specific pixel in InteractiveViewer using the transformationController?

Share Improve this question edited Dec 3, 2024 at 5:24 Ken White 126k15 gold badges233 silver badges463 bronze badges asked Nov 22, 2024 at 22:24 Peter ThompsonPeter Thompson 7710 bronze badges 5
  • "Translating" to a point also implies being able to "pan" / scroll when "zooming" in order to keep that point in view. Simply zooming in will "push" a target point outward unless it's dead center. – Gerry Schmitz Commented Nov 23, 2024 at 4:06
  • Yes, zooming only zooms to the top left corner of the image. The trouble is, the transformationController does not zoom by the factor given it. If I make the zoomfactor = 2, it only zooms by 1.5. That is, it zooms a 1000x2000 image to 1500x3000, not 2000x4000. It doesn't make sense. – Peter Thompson Commented Nov 24, 2024 at 20:22
  • "It" is zooming correctly for 1.5; it looks like it's not "taking" 2. That said, I checked my "zoom" settings ... I found that .16 to 1.4 was the only "useable" range for my situation (using a ScrollViewer). Beyond that was too extreme. (Animating user controls over a 6000x8000 topographical map) – Gerry Schmitz Commented Nov 25, 2024 at 18:38
  • How do I check my zoom settings for a "usable" range? I have tried zoom factor = 11.2639 and zoom factor = 2.0. I do get bigger or smaller images in InteractiveViewer but the resulting image size seems random. I do not think it is a range problem in my case. – Peter Thompson Commented Nov 27, 2024 at 22:26
  • I "tested" the "range" to get one I could live with. Below .16 (zoom out), my animations started to "ripple" due to "pixelation" of the "thinned" lines. Above 1.4 (zoom in), there isn't any more detail to see ... just "bigger" pixels. I mean, I load a legible map image ("actual size") in the first place ... zooming out puts more objects "in play" (on the map); zooming in accomplishes not much. (The 1.4 gives some "bounce" room). – Gerry Schmitz Commented Nov 28, 2024 at 7:01
Add a comment  | 

1 Answer 1

Reset to default 0

The problem is I was using physical pixels....

    double screenWidth = MediaQuery.of(context).size.width *
    MediaQuery.of(context).devicePixelRatio;
    double screenHeight = MediaQuery.of(context).size.height *
    MediaQuery.of(context).devicePixelRatio;

... and I should be using logical pixels

            RenderBox box =
                myKey.currentContext!.findRenderObject() as RenderBox;
            double height = box.size.height;
            double width = box.size.width;

But to get the logical pixel size of the (body: widget), the widget has to be completely rendered first. My solution was to show the map in the InteractiveViewer as per normal (see 1st half of code in question). I then get the screen size in logical pixels using the code below

WidgetsBinding.instance.addPostFrameCallback((_) {
  RenderBox box = myKey.currentContext!.findRenderObject() as RenderBox;
  double yyheight = box.size.height;
  double xxwidth = box.size.width;
  print("POSTFRAMECALLBACK **** RenderBax ($xxwidth, $yyheight)  *******");
});

I add a button saying "continue" which navigates to a new page with the correct screen size in logical pixels. The above code then works properly. For details on getting widget size, see

https://www.dhiwise.com/post/getting-the-right-size-a-tutorial-on-flutter-get-widget-size

本文标签: