chromium/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.paintpreview.player.frame;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Size;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.base.TraceEvent;

/**
 * Given a viewport {@link Rect} and a matrix of {@link Bitmap} tiles, this class draws the bitmaps
 * on a {@link Canvas}.
 */
class PlayerFrameBitmapPainter {
    private Size mTileSize;
    private Bitmap[][] mBitmapMatrix;
    private Rect mViewPort = new Rect();
    private Rect mDrawBitmapSrc = new Rect();
    private Rect mDrawBitmapDst = new Rect();
    private Runnable mInvalidateCallback;
    private Runnable mFirstPaintListener;
    private boolean mDestroyed;

    PlayerFrameBitmapPainter(
            @NonNull Runnable invalidateCallback, @Nullable Runnable firstPaintListener) {
        mInvalidateCallback = invalidateCallback;
        mFirstPaintListener = firstPaintListener;
    }

    void updateTileDimensions(Size tileDimensions) {
        mTileSize = tileDimensions;
    }

    void updateViewPort(int left, int top, int right, int bottom) {
        if (mDestroyed) return;

        mViewPort.set(left, top, right, bottom);
        mInvalidateCallback.run();
    }

    void updateBitmapMatrix(Bitmap[][] bitmapMatrix) {
        if (mDestroyed) return;

        mBitmapMatrix = bitmapMatrix;
        mInvalidateCallback.run();
    }

    /** Draws bitmaps on a given {@link Canvas} for the current viewport. */
    void onDraw(Canvas canvas) {
        if (mDestroyed) return;

        if (mBitmapMatrix == null) return;

        if (mViewPort.isEmpty()) return;

        if (mTileSize.getWidth() <= 0 || mTileSize.getHeight() <= 0) return;
        TraceEvent.begin("PlayerFrameBitmapPainter.onDraw");

        final int rowStart = mViewPort.top / mTileSize.getHeight();
        int rowEnd = (int) Math.ceil((double) mViewPort.bottom / mTileSize.getHeight());
        final int colStart = mViewPort.left / mTileSize.getWidth();
        int colEnd = (int) Math.ceil((double) mViewPort.right / mTileSize.getWidth());

        rowEnd = Math.min(rowEnd, mBitmapMatrix.length);
        colEnd = Math.min(colEnd, rowEnd >= 1 ? mBitmapMatrix[rowEnd - 1].length : 0);

        boolean needsInvalidate = false;
        for (int row = rowStart; row < rowEnd; row++) {
            for (int col = colStart; col < colEnd; col++) {
                Bitmap tileBitmap = mBitmapMatrix[row][col];

                // Request is ongoing or mid-replacement.
                if (tileBitmap == null || tileBitmap.isRecycled()) continue;

                // Calculate the portion of this tileBitmap that is visible in mViewPort.
                int bitmapLeft = Math.max(mViewPort.left - (col * mTileSize.getWidth()), 0);
                int bitmapTop = Math.max(mViewPort.top - (row * mTileSize.getHeight()), 0);
                int bitmapRight =
                        Math.min(
                                mTileSize.getWidth(),
                                bitmapLeft + mViewPort.right - (col * mTileSize.getWidth()));
                int bitmapBottom =
                        Math.min(
                                mTileSize.getHeight(),
                                bitmapTop + mViewPort.bottom - (row * mTileSize.getHeight()));
                mDrawBitmapSrc.set(bitmapLeft, bitmapTop, bitmapRight, bitmapBottom);

                // Calculate the portion of the canvas that tileBitmap is gonna be drawn on.
                int canvasLeft = Math.max((col * mTileSize.getWidth()) - mViewPort.left, 0);
                int canvasTop = Math.max((row * mTileSize.getHeight()) - mViewPort.top, 0);
                int canvasRight = canvasLeft + mDrawBitmapSrc.width();
                int canvasBottom = canvasTop + mDrawBitmapSrc.height();
                mDrawBitmapDst.set(canvasLeft, canvasTop, canvasRight, canvasBottom);

                canvas.drawBitmap(tileBitmap, mDrawBitmapSrc, mDrawBitmapDst, null);
                if (mFirstPaintListener != null) {
                    mFirstPaintListener.run();
                    mFirstPaintListener = null;
                }
            }
        }
        TraceEvent.end("PlayerFrameBitmapPainter.onDraw");
    }

    void destroy() {
        // Prevent future invalidation.
        mDestroyed = true;
        mBitmapMatrix = null;
    }
}