0
votes

Problem:

Zooming in on image by scaling and moving using matrix causes the app to run out of memory and crash.

Additional Libraries used:

Gestouch - https://github.com/fljot/Gestouch

Description:

In my Flex Mobile app I have an Image inside a Group with pan/zoom enabled using the Gestouch library. The zoom works to an extent but causes the app to die (not freeze, just exit) with no error message after a certain zoom level.

This would be manageable except I can’t figure out how to implement a threshold to stop the zoom at, as it crashes at a different zoom level almost every time. I also use dynamic images so the source of the image could be any size or resolution.

They are usually JPEGS ranging from about 800x600 - 9000x6000 and are downloaded from a server so cannot be packaged with the app.

As of the AS3 docs there is no longer a limit to the size of the BitmapData object so that shouldn't be the issue.

“Starting with AIR 3 and Flash player 11, the size limits for a BitmapData object have been removed. The maximum size of a bitmap is now dependent on the operating system.”

The group is used as a marker layer for overlaying pins on.

The crash mainly happens on iPad Mini and older Android devices.

Things I have tried already tried:

1.Using Adobe Scout to pin point when the memory leak occurs.

2.Debugging to find the exact height and width of the marker layer and image at the time of crash.

3.Setting a max zoom variable based on the size of the image.

4.Cropping the image on zoom to only show the visible area. ( crashes on copyPixels function and BitmapData.draw() function )

5.Using imagemagick to make lower quality images ( small images still crash )

6.Using imagemagick to make very low res image and make a grid of smaller images . Displaying in the mobile app using a List and Tile layout.

7.Using weak references when adding event listeners.

Any suggestions would be appreciated.

Thanks

            private function layoutImageResized(e: Event):void
        {
            markerLayer.scaleX = markerLayer.scaleY = 1;
            markerLayer.x = markerLayer.y = 0;

            var scale: Number = Math.min(width / image.sourceWidth , height / image.sourceHeight);              
            image.scaleX = image.scaleY = scale;                
            _imageIsWide = (image.sourceWidth / image.sourceHeight) > (width / height);

            // centre image
            if(_imageIsWide)
            {
                markerLayer.y =  (height - image.sourceHeight * image.scaleY ) / 2 ;
            }
            else
            {
                markerLayer.x = (width -image.sourceWidth * image.scaleX ) / 2 ;
            }   
            // set max scale                                
            _maxScale = scale*_maxZoom;         
        }



private function onGesture(event:org.gestouch.events.GestureEvent):void
        {
            trace("Gesture start");
            // if the user starts moving around while the add Pin option is up 
            // the state will be changed and the menu will disappear
            if(currentState == "addPin")
            {
                return;
            }

            const gesture:TransformGesture = event.target as TransformGesture;                  
            ////trace("gesture state is ", gesture.state);
            if(gesture.state == GestureState.BEGAN)
            { 
                currentState = "zooming";

                imgOldX = image.x;
                imgOldY = image.y;

                oldImgWidth = markerLayer.width;
                oldImgHeight = markerLayer.height;

                if(!_hidePins)
                {
                    showHidePins(false);
                }                   
            }

            var matrix:Matrix = markerLayer.transform.matrix;
            // Pan
            matrix.translate(gesture.offsetX, gesture.offsetY);
            markerLayer.transform.matrix = matrix;

            if ( (gesture.scale != 1 || gesture.rotation != 0) && ( (markerLayer.scaleX < _maxScale && markerLayer.scaleY < _maxScale) || gesture.scale < 1 ) && gesture.scale < 1.4 ) 
            {
                storedScale = gesture.scale;
                // Zoom
                var transformPoint:Point = matrix.transformPoint(markerLayer.globalToLocal(gesture.location));
                matrix.translate(-transformPoint.x, -transformPoint.y);
                matrix.scale(gesture.scale, gesture.scale);
                /** THIS IS WHERE THE CRASH HAPPENS **/
                matrix.translate(transformPoint.x, transformPoint.y);
                markerLayer.transform.matrix = matrix;

            }

}
1
What did you find using Scout? - Nemi

1 Answers

0
votes

I would say that's not a good idea to work with such a large image like (9000x6000) on mobile devices. I suppose you are trying to implement some sort of map navigation so you need to zoom some areas hugely.

  • My solution would be to split that 9000x6000 into 2048x2048 pieces, then compress it using png2atf utility with mipmaps enabled.
  • Then you can use Starling to easily load these atf images and add it to stage3d and easily manage it. In case you are dealing with 9000x6000 image - you'll get about 15 2048x2048 pieces, having them all added on the stage at one time you might think it would be heavy, but mipmaps will make it so that there are only tiny thumbnails of image are in memory until they are not zoomed - so you'll never run out of memory in case you remove invisible pieces from stage from time to time while zooming in, and return it back on zoom out