blob: 12bf3d642caad43d2f417b9519d6187ec2e1bf09 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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.Rect;
import android.util.Size;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.UnguessableToken;
import org.chromium.components.paintpreview.player.PlayerCompositorDelegate;
/**
* Manages the bitmaps shown in the PlayerFrameView at a given scale factor.
*/
public class PlayerFrameBitmapState {
private final UnguessableToken mGuid;
/** Dimension of tiles. */
private final Size mTileSize;
/** The scale factor of bitmaps. */
private float mScaleFactor;
/** Bitmaps that make up the contents. */
private Bitmap[][] mBitmapMatrix;
/** Whether a request for a bitmap tile is pending. */
private boolean[][] mPendingBitmapRequests;
/**
* Whether we currently need a bitmap tile. This is used for deleting bitmaps that we don't
* need and freeing up memory.
*/
@VisibleForTesting
boolean[][] mRequiredBitmaps;
/** Delegate for accessing native to request bitmaps. */
private final PlayerCompositorDelegate mCompositorDelegate;
private final PlayerFrameMediatorDelegate mMediatorDelegate;
PlayerFrameBitmapState(UnguessableToken guid, int tileWidth, int tileHeight, float scaleFactor,
Size contentSize, PlayerCompositorDelegate compositorDelegate,
PlayerFrameMediatorDelegate mediatorDelegate) {
mGuid = guid;
mTileSize = new Size(tileWidth, tileHeight);
mScaleFactor = scaleFactor;
mCompositorDelegate = compositorDelegate;
mMediatorDelegate = mediatorDelegate;
// Each tile is as big as the initial view port. Here we determine the number of
// columns and rows for the current scale factor.
int rows = (int) Math.ceil((contentSize.getHeight() * scaleFactor) / tileHeight);
int cols = (int) Math.ceil((contentSize.getWidth() * scaleFactor) / tileWidth);
mBitmapMatrix = new Bitmap[rows][cols];
mPendingBitmapRequests = new boolean[rows][cols];
mRequiredBitmaps = new boolean[rows][cols];
}
Bitmap[][] getMatrix() {
return mBitmapMatrix;
}
Size getTileDimensions() {
return mTileSize;
}
/**
* Clears state so in-flight requests abort upon return.
*/
void clear() {
mBitmapMatrix = null;
mRequiredBitmaps = null;
mPendingBitmapRequests = null;
}
/**
* Clears all the required bitmaps before they are re-set in {@link #requestBitmapForRect()}
*/
void clearRequiredBitmaps() {
if (mRequiredBitmaps == null) return;
for (int row = 0; row < mRequiredBitmaps.length; row++) {
for (int col = 0; col < mRequiredBitmaps[row].length; col++) {
mRequiredBitmaps[row][col] = false;
}
}
}
/**
* Requests bitmaps for tiles that overlap with the provided rect. Also requests bitmaps for
* adjacent tiles.
* @param viewportRect The rect of the viewport for which bitmaps are needed.
*/
void requestBitmapForRect(Rect viewportRect) {
if (mRequiredBitmaps == null) return;
final int rowStart =
Math.max(0, (int) Math.floor((double) viewportRect.top / mTileSize.getHeight()));
final int rowEnd = Math.min(mRequiredBitmaps.length,
(int) Math.ceil((double) viewportRect.bottom / mTileSize.getHeight()));
final int colStart =
Math.max(0, (int) Math.floor((double) viewportRect.left / mTileSize.getWidth()));
final int colEnd = Math.min(mRequiredBitmaps[0].length,
(int) Math.ceil((double) viewportRect.right / mTileSize.getWidth()));
for (int col = colStart; col < colEnd; col++) {
for (int row = rowStart; row < rowEnd; row++) {
requestBitmapForTile(row, col);
}
}
// Request bitmaps for adjacent tiles that are not currently in the view port. The reason
// that we do this in a separate loop is to make sure bitmaps for tiles inside the view port
// are fetched first.
for (int col = colStart; col < colEnd; col++) {
for (int row = rowStart; row < rowEnd; row++) {
requestBitmapForAdjacentTiles(row, col);
}
}
}
private void requestBitmapForAdjacentTiles(int row, int col) {
if (mBitmapMatrix == null) return;
if (row > 0) {
requestBitmapForTile(row - 1, col);
}
if (row < mBitmapMatrix.length - 1) {
requestBitmapForTile(row + 1, col);
}
if (col > 0) {
requestBitmapForTile(row, col - 1);
}
if (col < mBitmapMatrix[row].length - 1) {
requestBitmapForTile(row, col + 1);
}
}
private void requestBitmapForTile(int row, int col) {
if (mRequiredBitmaps == null) return;
mRequiredBitmaps[row][col] = true;
if (mBitmapMatrix == null || mPendingBitmapRequests == null
|| mBitmapMatrix[row][col] != null || mPendingBitmapRequests[row][col]) {
return;
}
final int y = row * mTileSize.getHeight();
final int x = col * mTileSize.getWidth();
BitmapRequestHandler bitmapRequestHandler =
new BitmapRequestHandler(row, col, mScaleFactor);
mPendingBitmapRequests[row][col] = true;
mCompositorDelegate.requestBitmap(mGuid,
new Rect(x, y, x + mTileSize.getWidth(), y + mTileSize.getHeight()), mScaleFactor,
bitmapRequestHandler, bitmapRequestHandler::onError);
}
/**
* Remove previously fetched bitmaps that are no longer required according to
* {@link #mRequiredBitmaps}.
*/
private void deleteUnrequiredBitmaps() {
if (mBitmapMatrix == null) return;
for (int row = 0; row < mBitmapMatrix.length; row++) {
for (int col = 0; col < mBitmapMatrix[row].length; col++) {
Bitmap bitmap = mBitmapMatrix[row][col];
if (!mRequiredBitmaps[row][col] && bitmap != null) {
bitmap.recycle();
mBitmapMatrix[row][col] = null;
}
}
}
}
/**
* Used as the callback for bitmap requests from the Paint Preview compositor.
*/
private class BitmapRequestHandler implements Callback<Bitmap> {
int mRequestRow;
int mRequestCol;
float mRequestScaleFactor;
private BitmapRequestHandler(int requestRow, int requestCol, float requestScaleFactor) {
mRequestRow = requestRow;
mRequestCol = requestCol;
mRequestScaleFactor = requestScaleFactor;
}
/**
* Called when bitmap is successfully composited.
* @param result
*/
@Override
public void onResult(Bitmap result) {
if (result == null) {
onError();
return;
}
if (mBitmapMatrix == null || !mPendingBitmapRequests[mRequestRow][mRequestCol]
|| !mRequiredBitmaps[mRequestRow][mRequestCol]) {
result.recycle();
deleteUnrequiredBitmaps();
return;
}
mPendingBitmapRequests[mRequestRow][mRequestCol] = false;
mBitmapMatrix[mRequestRow][mRequestCol] = result;
mMediatorDelegate.updateBitmapMatrix(mBitmapMatrix);
deleteUnrequiredBitmaps();
}
/**
* Called when there was an error compositing the bitmap.
*/
public void onError() {
if (mPendingBitmapRequests == null) return;
// TODO(crbug.com/1021590): Handle errors.
assert mBitmapMatrix != null;
assert mBitmapMatrix[mRequestRow][mRequestCol] == null;
assert mPendingBitmapRequests[mRequestRow][mRequestCol];
mPendingBitmapRequests[mRequestRow][mRequestCol] = false;
}
}
}