1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.systemui.screenshot; 17 18 import static android.graphics.ColorSpace.Named.SRGB; 19 20 import static java.util.Objects.requireNonNull; 21 22 import android.annotation.NonNull; 23 import android.graphics.Bitmap; 24 import android.graphics.ColorSpace; 25 import android.graphics.RecordingCanvas; 26 import android.graphics.Rect; 27 import android.graphics.RenderNode; 28 import android.media.Image; 29 30 /** 31 * Holds a hardware image, coordinates and render node to draw the tile. The tile manages clipping 32 * and dimensions. The tile must be drawn translated to the correct target position: 33 * <pre> 34 * ImageTile tile = getTile(); 35 * canvas.save(); 36 * canvas.translate(tile.getLeft(), tile.getTop()); 37 * canvas.drawRenderNode(tile.getDisplayList()); 38 * canvas.restore(); 39 * </pre> 40 */ 41 class ImageTile implements AutoCloseable { 42 private final Image mImage; 43 private final Rect mLocation; 44 private RenderNode mNode; 45 46 private static final ColorSpace COLOR_SPACE = ColorSpace.get(SRGB); 47 48 /** 49 * Create an image tile from the given image. 50 * 51 * @param image an image containing a hardware buffer 52 * @param location the captured area represented by image tile (virtual coordinates) 53 */ ImageTile(@onNull Image image, @NonNull Rect location)54 ImageTile(@NonNull Image image, @NonNull Rect location) { 55 mImage = requireNonNull(image, "image"); 56 mLocation = requireNonNull(location); 57 requireNonNull(mImage.getHardwareBuffer(), "image must be a hardware image"); 58 } 59 getDisplayList()60 synchronized RenderNode getDisplayList() { 61 if (mNode == null) { 62 mNode = new RenderNode("Tile{" + Integer.toHexString(mImage.hashCode()) + "}"); 63 } 64 if (mNode.hasDisplayList()) { 65 return mNode; 66 } 67 final int w = Math.min(mImage.getWidth(), mLocation.width()); 68 final int h = Math.min(mImage.getHeight(), mLocation.height()); 69 mNode.setPosition(0, 0, w, h); 70 71 RecordingCanvas canvas = mNode.beginRecording(w, h); 72 canvas.save(); 73 canvas.clipRect(0, 0, mLocation.width(), mLocation.height()); 74 canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE), 75 0, 0, null); 76 canvas.restore(); 77 mNode.endRecording(); 78 return mNode; 79 } 80 getLocation()81 Rect getLocation() { 82 return mLocation; 83 } 84 getLeft()85 int getLeft() { 86 return mLocation.left; 87 } 88 getTop()89 int getTop() { 90 return mLocation.top; 91 } 92 getRight()93 int getRight() { 94 return mLocation.right; 95 } 96 getBottom()97 int getBottom() { 98 return mLocation.bottom; 99 } 100 101 @Override close()102 public synchronized void close() { 103 mImage.close(); 104 if (mNode != null) { 105 mNode.discardDisplayList(); 106 } 107 } 108 109 @Override toString()110 public String toString() { 111 return "{location=" + mLocation + ", source=" + mImage 112 + ", buffer=" + mImage.getHardwareBuffer() + "}"; 113 } 114 } 115