moagrius / TileView

TileView is a subclass of android.view.ViewGroup that asynchronously displays, pans and zooms tile-based images. Plugins are available for features like markers, hotspots, and path drawing.
MIT License
1.46k stars 337 forks source link

StreamProvider asked to provide data for rows and columns out of bounds with to small images #530

Closed tva-TIS closed 4 years ago

tva-TIS commented 5 years ago

I don't quite know if this really is an issue or belongs to the kind of exceptions StreamProviders seem to be allowed to throw.

I've written a custom StreamProvider that provides data that has been prepared dynamically. Since I don't have control over which image is displayed in my (probably special) usecase I had some images in the TileView that have lower resolution than the TileView covers. The StreamProvider is then asked to provide data for rows and columns that dont exist, as start and end row and column are calculated by the viewport scaling. Example: One image that i tested had dimensions of 190x266 pixels and the formula I chose to dynamically determine the TileSize chose a TileSize of 14. At this setup the Provider can Provide 190 / 14 = 13, 5 ~ 14 columns and 266 / 14 = 19 rows. But as the viewport on the device has a multiple of the dimensions the provider is aked to provide 45 columns and 63 rows.

As I said I don't now if this requires a change in the source code, but i thought that this case might appear should at least be brought to the authors attention.

moagrius commented 5 years ago

Sorry, I've read your example a couple of times and I'm not at all clear on the issue. What specifically is happening? Maybe you could post your project somewhere with STR...

tva-TIS commented 5 years ago

I'm afraid I probably cannot share the whole project, but i used a project that only had the following java file (and the layout file for the tileView) modified to test my hypothesis of the correlation of the image size with the NullpointerExceptions. MainActivity.zip

The following code examples however are from the real project.

My StreamProvider looks like this:

    private class BitmapStreamProvider implements StreamProvider {
        @Override
        public InputStream getStream(int column, int row, Context context, Object data) {

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                ((SparseArray<SparseArray<Bitmap>>) data).get(column).get(row).compress(Bitmap.CompressFormat.PNG, 0, bos);
            } catch (NullPointerException e){
                Log.w(TAG, "Invalid row or column requested, column " + column + " and row " + row, e);
                return null;
            }
            byte[] bitmapdata = bos.toByteArray();
            return new ByteArrayInputStream(bitmapdata);
        }
    }

And the tileView is initialized as following:

new TileView.Builder(tileView)
                .defineZoomLevel(bits)
                .setTileSize(tileSize)
                .setSize(originalBitmap.getWidth(), originalBitmap.getHeight())
                .setStreamProvider(new BitmapStreamProvider())
                .installPlugin(new MarkerPlugin(context))
                .build();

... with bits being the two-dimensional SparseArray with originalBitmap sliced down to tileSize width and height. So in my example originalBitmap would now be an image of size 190266 pixels. The tileSize is calculated by `tileSize = (int) Math.sqrt(Math.sqrt((originalBitmap.getWidth() originalBitmap.getHeight())));to have largertileSizefor larger images. For the example image thetileSizeis 14. The algorithm cutting the bitmaps fillsbitswith14SparseArrays each containing19bitmaps which would fit the calculations from my first comment. InpopulateTileGridFromViewporthowever (assuming no scale for now), thetileViewcalculates to go over 64 (0-64) rows and 49 (0-49) columns.tileSizestays14asgetScale()andmCurrentDetail.getSample()both return1.rows.startandcolumns.startboth are set to 0 as we want to start in the top left corner of the tiles.rows.endandcolumns.endhowever use viewport values which are not null, on my developement deviceviewport.bottomis896, andviewport.rightis688which are the layout dimensions of the tileView. When calculated through thetileSize` these values will result in a row count that is bigger than the amount of tiles that could be made from the image as long as the image has smaller dimensions than the viewport, because both have been divided by the tileSize, which will lead to NullPointerExceptions in the StreamProvider.

As I said: I'm both aware that the StreamProviders are allowed to throw Exceptions and your library usually displays quite large images, but thought this should be to attention anyway, even if its only for someone wandering about the occuring NullPointerExceptions.

moagrius commented 5 years ago

do you see the problem in the math where the grid is computed?

tva-TIS commented 4 years ago

I will close this issue now in favor of https://github.com/moagrius/TileView/issues/531, centralizing the knowledge of the problem . If the solution created there will not help in my usecase (which is unlikely) I'm gonna reopen it again.

Duplicate of #531

tva-TIS commented 4 years ago

There seem to be further issues with images that do not cover the Viewport of the TileView, even when applying the current change suggestions of https://github.com/moagrius/TileView/issues/531#issuecomment-516716875. Discovered so far:

I will see how far I can dig into the issues myself, but won't open separate issues for know as I don't know how far these issues are connected to each other or if they may be fixed in additional changes within #531