Java – Continuously scrolls a large seamless image in any direction

Continuously scrolls a large seamless image in any direction… here is a solution to the problem.

Continuously scrolls a large seamless image in any direction

The issue is game-related, but I think it can be applied to other use cases.

I have a seamless image that is larger than the screen. About twice as big. The goal is to make this image scrollable in any direction on the surface. This is for Android.

I’ve seen some questions/answers about images in general, but not seamless images.

The difference here and my question is how to handle scrolling at some point, 4 separate image fragments can be displayed (when the user scrolls diagonally to half of the image).

It is obvious what to do when you want to scroll the image to the edge. Use world values and use rectangles to display some kind of “viewport” of your location.

Similar to:

Rect oRect1 = new Rect(dWorldX, dWorldY, dWorldX + canvas.getWidth(), dWorldY + canvas.getHeight());
Rect oRect2 = new Rect(0, 0, canvas.getWidth(), canvas.getHeight());

canvas.drawBitmap(largeBitmapTexture, oRect1, oRect2, new Paint());

But when bitmaps scroll seamlessly and continuously, this is a programming challenge for me. Do I use a bunch of if statements? Is there a more efficient way to do something like this?

EDIT: And some thoughts. A complicating problem here is that the world (x,y) is infinite. Therefore, I have to use the division of screen width and height to display what needs to be displayed. I initially considered only 4 iterations of Rect because the image on the screen will never exceed 4 blocks. If that makes sense. I guess I’m just a little vague about how to calculate.

EDIT: This is my code now, from @glowcoder’s suggestion.

public void draw(Canvas canvas){
    int iImagesOverX=0;
    int iImagesOverY=0;

iImagesOverX = (int)dX / mCloud.getIntrinsicWidth();
    iImagesOverY = (int)dY / mCloud.getIntrinsicHeight();
    mCloud.setBounds(iImagesOverX * (int)mCloud.getIntrinsicWidth() , iImagesOverY * (int)mCloud.getIntrinsicHeight() , (iImagesOverX * (int)mCloud.getIntrinsicWidth()) + mCloud.getIntrinsicWidth() ,  (iImagesOverY * (int)mCloud.getIntrinsicHeight()) + mCloud.getIntrinsicHeight() );
    Log.d(TAG, "bounds:"+ mCloud.getBounds());
    mCloud.draw(canvas);
    mCloud.setBounds((iImagesOverX + 1) * (int)mCloud.getIntrinsicWidth() , iImagesOverY * (int)mCloud.getIntrinsicHeight() , ((iImagesOverX + 1) * (int)mCloud.getIntrinsicWidth()) + mCloud.getIntrinsicWidth() ,  (iImagesOverY * (int)mCloud.getIntrinsicHeight()) + mCloud.getIntrinsicHeight() );
    mCloud.draw(canvas);
    mCloud.setBounds((iImagesOverX + 1) * (int)mCloud.getIntrinsicWidth() , (iImagesOverY + 1)* (int)mCloud.getIntrinsicHeight() , ((iImagesOverX + 1) * (int)mCloud.getIntrinsicWidth()) +  mCloud.getIntrinsicWidth() ,  ((iImagesOverY + 1) * (int)mCloud.getIntrinsicHeight()) + mCloud.getIntrinsicHeight() );
    mCloud.draw(canvas);
    mCloud.setBounds((iImagesOverX) * (int)mCloud.getIntrinsicWidth() , (iImagesOverY + 1)* (int)mCloud.getIntrinsicHeight() , ((iImagesOverX) * (int)mCloud.getIntrinsicWidth()) + mCloud.getIntrinsicWidth() ,  ((iImagesOverY + 1) * (int)mCloud.getIntrinsicHeight()) + mCloud.getIntrinsicHeight() );
    mCloud.draw(canvas);
    Log.d(TAG, "imagesoverx:"+ iImagesOverX);
}

I have to add dX and dY to the border to make the image move. However, once you move one “square block” to the right, the third square block does not appear.

So this part works, but not where I need it. There are 4 panels, but only because of the borders and 4 instances drawn. Wherever we use x and y, I need these 4 panels to draw where they need to be.

Solution

The basic idea is that you have an infinite plane with images that you repeat over and over again in all directions. Then you have a “viewport” – you can think of it as a window that limits your view of the infinite plane. In other words, the infinite flat area “below” the viewport is the area actually displayed on the screen.

So, when the upper-left corner of the viewport is at (0,0), you see an infinite planar portion from (0,0) to (50,50) (assuming the viewport is 50 pixels wide and high).

If your image is 100×100, your infinite plane will look like this:

     (-100,-100)  (0,-100)      (100,-100)    (200,-100)
    ******************************************
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *(-100,0)    *(0,0)        *(100,0)      *(200,0)
    ******************************************
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *(-100,100)  *(0,100)      *(100,100)    *(200,100)
    ******************************************
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *(-100,200)  *(0,200)      *(100,200)    *(200,200)
    ******************************************

Now, suppose the upper-left corner of the viewport is at 75,75. Graphically, it looks like:

     (-100,-100)  (0,-100)      (100,-100)    (200,-100)
    ******************************************
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *            *             *             *
    *(-100,0)    *(0,0)        *(100,0)      *(200,0)
    ******************************************
    *            *             *             *
    *            *             *             *
    *            *     (75,75) * (125,75)    *
    *            *          #######          *
    *(-100,100)  *(0,100)   #  *  #          *(200,100)
    ************************#*****#***********
    *            *          #  *  #          *
    *            *          #######          *
    *            *    (75,125) * (125,125)   *
    *            *             *             *
    *(-100,200)  *(0,200)      *(100,200)    *(200,200)
    ******************************************              

In this case, your viewport has corners at (75,75), (75,125

), (125,75), and (125,125). As a result, you see that the lower-right corner of the image is at (0,0), the lower-left corner of the image is at (100,0), and so on.

Now, suppose you implement a function to calculate which grid a particular point is in. Specifically, it returns the upper-left corner of the grid that contains the point. A few examples:

gridContainingPoint(0,0)      -> (0,0)
gridContainingPoint(50,50)    -> (0,0)
gridContainingPoint(100,100)  -> (100,100)
gridContainingPoint(123, -27) -> (100,-100)

The implementation of this function is fairly simple:

Point gridContainingPoint(Point pt) {
    int newX = ((int)Math.floor(pt.x/100f)) * 100;
    int newY = ((int)Math.floor(pt.y/100f)) * 100;
    return new Point(newX, newY);
}

Now, to determine which images you need to draw, call the gridContainingPoint() method for each corner of the viewport. In this example, you get:

    gridContainingPoint(75,75)   -> (0,0)
    gridContainingPoint(75,125)  -> (0,100)
    gridContainingPoint(125,75)  -> (100, 0)
    gridContainingPoint(125,125) -> (100, 100)

Now you know exactly what images you need to draw to fully cover the viewport.

Before drawing an image, you must set up the viewport correctly. By default, when you start drawing on a Canvas, the viewport is at (0,0). So you’ll only see what is drawn on the Canvas in the (0,0)x(50,50) area. However, we want the viewport to be in (75,75) so that we can see what’s in the (75,75)x(125,125) area. To do this, you call the Canvas.translate() method. For example, canvas.translate(-75,-75). It must be negative because it conceptually moves the Canvas below the viewport, not the viewport.

Now, using the information from the

4 calls from gridContainingPoint(), you can draw the image at (0,0), (0,100), (100, 0), and (100).

A few things to keep in mind – the numbers used in this example are not the actual numbers you want to use.

On the one hand, the size of the

viewport will not be 50×50, but the actual size of the View when drawn. You can use View.getWidth() and View.getHeight() to get this information.

Secondly, you need to adjust the grid size and related calculations according to the image size – I suspect you are using a 100×100 image.

Finally, remember that you call gridContainingPoint() for each corner of the viewport method, it may return the same grid for multiple corners. For example, if your viewport is at (25,25), it will return (0,0) images for each corner because (25,25), (25,75), (75,25), and (75,75) are all within the image from (0,0) to (100,100). Therefore, this will be the only image you will have to draw to fully cover the viewport.

Related Problems and Solutions