What is the most performant way to draw a rotated

2019-09-07 20:29发布

问题:

I'm working on an app where we hand rolled some star maps. So I don't have to talk in the abstract, here is the app in question: https://play.google.com/store/apps/details?id=com.tw.fireballs

If you look on the store listing, you can see we use an image just above the horizon...mostly because it looks super pretty. It's not an issue on lower resolution devices, they don't seem to suffer a huge issue rendering it due to lower number of pixels involved. On a higher resolution device like my HTC One or my wifes Nexus 5, there is a big framerate drop when it's on the screen. It's not bad enough to prevent me releasing it, but I'd like to improve it if possible.

I'm currently just using a SurfaceView, and drawing to a canvas acquired by mSurfaceHolder.lockCanvas(null), so it's not hardware accelerated. I tried implementing it as a regular view which is, but overall it actually went slower, so before I take the plunge into OpenGL land I want to explore if there is something I can do to speed this up.

Essentially, we start with a thin (opaque) image "slice" through the horizon, scale it up to the size of the diagonal of the screen and rotate it around to match the orientation of the device.

The drawing code is as follows:

private void loadResources() {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    mHorizonImage = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.star_map_background, options);
}

@Override
public void updateDimensions(int width, int height) {
    super.updateDimensions(width, height);

    int horizonHeight = mCanvasHeight / 3;
    int horizonWidth = (int) Math.sqrt(Math.pow(mCanvasHeight, 2) + Math.pow(mCanvasWidth, 2));

    mHorizonImage = Bitmap.createScaledBitmap(mHorizonImage, horizonWidth, horizonHeight, false);
}

@Override
public void drawOn(Canvas canvas) {
    double upScreen = -mProjection.distanceFromScreen * Math.tan(Math.toRadians(mOrientation.elevation));
    double rightWithTiltCheat = upScreen * Math.sin(Math.toRadians(mOrientation.tilt));
    double upWithTiltCheat = upScreen * Math.cos(Math.toRadians(mOrientation.tilt));

    float x = (float) (mCanvasWidth / 2 * (1 + rightWithTiltCheat / mProjection.screenWidthInCm));
    float y = (float) (mCanvasHeight / 2 * (1 - upWithTiltCheat / mProjection.screenHeightInCm));

    canvas.save();
    canvas.translate(x, y);
    canvas.rotate((float) mOrientation.tilt);
    canvas.drawBitmap(mHorizonImage, -mHorizonImage.getWidth() / 2, -mHorizonImage.getHeight(), null);
    canvas.restore();
}

Is there a more efficient way to do this that won't be so affected by screen resolution? We've bandied around a few ideas, but I'd be keen to hear from someone who knows this stuff better than I. If you have any questions, please ask, I'd be grateful for any assistance.

Cheers, Nathan

回答1:

@pskink hit the nail on the head. Drawing the bitmap with a Matrix param is much more performant than the scaled bitmap and canvas rotation I was experiencing. On 1080P devices like my HTC One I used to watch the frame rate drop from 50+ to ~10 if I held the phone on a diagonal. Now I'd be lucky if it drops 5.

The modified code now looks like:

@Override
public void updateDimensions(int width, int height) {
    super.updateDimensions(width, height);

    mHorizonHeight = mCanvasHeight / 3;
    mHeightScale = (float)mHorizonHeight / (float)mHorizonImage.getHeight();

    mHorizonWidth = (int) Math.sqrt(Math.pow(mCanvasHeight, 2) + Math.pow(mCanvasWidth, 2));
    mWidthScale = (float)mHorizonWidth / (float)mHorizonImage.getWidth();
}

@Override
public void drawOn(Canvas canvas) {
    double upScreen = -mProjection.distanceFromScreen * Math.tan(Math.toRadians(mOrientation.elevation));
    double rightWithTiltCheat = upScreen * Math.sin(Math.toRadians(mOrientation.tilt));
    double upWithTiltCheat = upScreen * Math.cos(Math.toRadians(mOrientation.tilt));

    float x = (float) (mCanvasWidth / 2 * (1 + rightWithTiltCheat / mProjection.screenWidthInCm));
    float y = (float) (mCanvasHeight / 2 * (1 - upWithTiltCheat / mProjection.screenHeightInCm));

    mDrawMatrix.reset();
    mDrawMatrix.postScale(mWidthScale, mHeightScale);
    mDrawMatrix.postTranslate(x - mHorizonWidth / 2, y - mHorizonHeight);
    mDrawMatrix.postRotate((float) mOrientation.tilt, x, y);
    canvas.drawBitmap(mHorizonImage, mDrawMatrix, null);
    canvas.save();
}