0

I am working on a project in Android that builds Abelian Sandpiles (a type of 2D cellular Automaton). I have a grid of cells (that starts out small but later grows) and I'm drawing square (or circles) on the grid to show the state of each cell. At each step, I update usually less than 30% of the cells.

My basic approach is taken from this post: Android: How to get a custom view to redraw partially?

I draw all the shapes to the canvas and cache it as a bitmap, then at each step I update only the cells that need updating, then cache the result and repeat. This works well enough when the grid is fairly small (less than 50 x 50), but becomes increasingly unsatisfactory as the grid size increase. The problems are that

1) it is too slow when the number of updates becomes high

2) even when it runs smoothly, the drawing doesn't look clean - e.g., a line of small rectangles looks quite choppy and inconsistent (see image).

choppy artifacts from drawing small rectangles

I am sure that there must be a better way to approach this problem. With a larger grid (e.g., 150 x 150), I'm drawing rects or circles with a width of ~2.33, and this can't be optimal. Any advice for improving performance and/or image quality? Simplified drawing code is here:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (ready) {
            if (needsCompleteRedraw) {
                this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
                cacheCanvas = new Canvas(this.cachedBitmap);
                doInitialDrawing(cacheCanvas);
                canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
                needsCompleteRedraw = false;
            } else {
                canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
                doPartialRedraws(cacheCanvas);
            }
        }
    }

    private void doInitialDrawing(Canvas clean) {
        for (int i = 0; i < pile.gridHeight; i++) {
            for (int j = 0; j < pile.gridWidth; j++) {
                int state = pile.getGridValueAtPoint(new Point(j, i ));
                clean.drawRect(j * cellSize, i * cellSize, (j + 1 )* cellSize, (i + 1) * cellSize, paints[state]);
            }
        }
    }

    private void doPartialRedraws(Canvas cached) {
        for (Point p : pile.needsUpdateSet) {
            int state = pile.getGridValueAtPoint(p);
            cached.drawRect(p.x * cellSize, p.y * cellSize, (p.x + 1 )* cellSize, (p.y + 1) * cellSize, paints[state]);
        }
        pile.needsUpdateSet = new HashSet<Point>();
    }

and my paint objects are set with antialias as true, and I've tried setting the style to both FILL and FILL_AND_STROKE.

Any suggestions would be much appreciated.

c_booth
  • 2,185
  • 1
  • 13
  • 22
  • The more cells on a screen, the closer you are to writing a game (search online for various resources). Alternatively you may want to look at how other open source Game of Life type projects handle screen performance. – Morrison Chang Aug 03 '17 at 04:26
  • You can also refer to the Android documentation (Develop - API guides - Animation and graphics -Canvas and drawables) and use a SurfaceView you draw into using a separate thread It is very efficient and I am sure it will solve your problem. – Zelig63 Aug 03 '17 at 04:53

1 Answers1

1

Ok. Several problems here.

1)Don't ever create a canvas in onDraw. If you think you need to, you haven't architected your drawing code correctly.

2)The point of doing draws to a cache bitmap is NOT to draw the cache in onDraw, but to do it on its own thread- or at least not at draw time.

You should have a second thread that draws to the cached bitmap on demand, then calls postInvalidate() on the view. The onDraw function should only be a call to drawBitmap, drawing the cached bitmap to the screen.

Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127