| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tools.idea.debug; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.intellij.debugger.engine.DebugProcessImpl; |
| import com.intellij.debugger.engine.DebuggerUtils; |
| import com.intellij.debugger.engine.evaluation.EvaluateException; |
| import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; |
| import com.intellij.debugger.impl.DebuggerUtilsEx; |
| import com.intellij.debugger.jdi.VirtualMachineProxyImpl; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.sun.jdi.*; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| import java.awt.image.BufferedImage; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class BitmapEvaluator { |
| private static final Logger LOG = Logger.getInstance(BitmapEvaluator.class); |
| |
| /** Maximum height or width of image beyond which we scale it on the device before retrieving. */ |
| private static final int MAX_DIMENSION = 1024; |
| |
| @Nullable |
| public static BufferedImage getBitmap(EvaluationContextImpl evaluationContext, Value bitmap) throws EvaluateException { |
| // retrieve the bitmap from bitmap drawables |
| String fqcn = bitmap.type().name(); |
| if (BitmapDrawableRenderer.BITMAP_DRAWABLE_FQCN.equals(fqcn)) { |
| bitmap = getBitmapFromDrawable(evaluationContext, (ObjectReference)bitmap); |
| if (bitmap == null) { |
| throw new RuntimeException("Unable to obtain bitmap from drawable"); |
| } |
| } |
| |
| String config = getBitmapConfigName((ObjectReference)bitmap, evaluationContext); |
| if (!"\"ARGB_8888\"".equals(config)) { |
| throw new RuntimeException("Unsupported bitmap configuration: " + config); |
| } |
| |
| Dimension size = getDimension(evaluationContext, bitmap); |
| if (size == null) { |
| throw new RuntimeException("Unable to determine image dimensions."); |
| } |
| |
| // if the image is rather large, then scale it down |
| if (size.width > MAX_DIMENSION || size.height > MAX_DIMENSION) { |
| LOG.debug("Scaling down bitmap"); |
| bitmap = createScaledBitmap(evaluationContext, (ObjectReference)bitmap, size); |
| if (bitmap == null) { |
| throw new RuntimeException("Unable to create scaled bitmap"); |
| } |
| |
| size = getDimension(evaluationContext, bitmap); |
| if (size == null) { |
| throw new RuntimeException("Unable to obtained scaled bitmap's dimensions"); |
| } |
| } |
| |
| List<Value> pixelValues; |
| |
| Field bufferField = ((ObjectReference)bitmap).referenceType().fieldByName("mBuffer"); |
| if (bufferField != null) { |
| // if the buffer field is available, we can directly copy over the values |
| Value bufferValue = ((ObjectReference)bitmap).getValue(bufferField); |
| if (!(bufferValue instanceof ArrayReference)) { |
| throw new RuntimeException("Image Buffer is not an array"); |
| } |
| pixelValues =((ArrayReference)bufferValue).getValues(); |
| } else { |
| // if there is no buffer field (on older platforms that store data on native heap), then resort to creating a new buffer, |
| // and invoking copyPixelsToBuffer to copy the pixel data into the newly created buffer |
| pixelValues = copyToBuffer(evaluationContext, (ObjectReference)bitmap, size); |
| if (pixelValues == null) { |
| throw new RuntimeException("Unable to extract image data: Bitmap has no buffer field."); |
| } |
| } |
| |
| byte[] argb = new byte[pixelValues.size()]; |
| for (int i = 0; i < pixelValues.size(); i++) { |
| Value pixelValue = pixelValues.get(i); |
| if (pixelValue instanceof ByteValue) { |
| argb[i] = ((ByteValue)pixelValue).byteValue(); |
| } |
| } |
| |
| return createBufferedImage(size.width, size.height, argb); |
| } |
| |
| @Nullable |
| private static String getBitmapConfigName(ObjectReference bitmap, EvaluationContextImpl evaluationContext) throws EvaluateException { |
| Value config = getBitmapConfig(evaluationContext, bitmap); |
| if (!(config instanceof ObjectReference)) { |
| return null; |
| } |
| |
| Field f = ((ObjectReference)config).referenceType().fieldByName("name"); |
| if (f == null) { |
| return null; |
| } |
| |
| return ((ObjectReference)config).getValue(f).toString(); |
| } |
| |
| @Nullable |
| private static List<Value> copyToBuffer(EvaluationContextImpl evaluationContext, ObjectReference bitmap, Dimension size) throws EvaluateException { |
| DebugProcessImpl debugProcess = evaluationContext.getDebugProcess(); |
| VirtualMachineProxyImpl virtualMachineProxy = debugProcess.getVirtualMachineProxy(); |
| |
| List<ReferenceType> classes = virtualMachineProxy.classesByName("byte[]"); |
| if (classes.size() != 1 || !(classes.get(0) instanceof ArrayType)) { |
| return null; |
| } |
| ArrayType byteArrayType = (ArrayType)classes.get(0); |
| |
| classes = virtualMachineProxy.classesByName("java.nio.ByteBuffer"); |
| if (classes.size() != 1 || !(classes.get(0) instanceof ClassType)) { |
| return null; |
| } |
| ClassType byteBufferType = (ClassType)classes.get(0); |
| Method wrapMethod = DebuggerUtils.findMethod(byteBufferType, "wrap", "([B)Ljava/nio/ByteBuffer;"); |
| if (wrapMethod == null) { |
| return null; |
| } |
| |
| ArrayReference byteArray = byteArrayType.newInstance(size.width * size.height * 4); |
| Value byteBufferRef = debugProcess.invokeMethod(evaluationContext, byteBufferType, wrapMethod, ImmutableList.of(byteArray)); |
| |
| Method copyToBufferMethod = DebuggerUtils.findMethod(bitmap.referenceType(), "copyPixelsToBuffer", "(Ljava/nio/Buffer;)V"); |
| if (copyToBufferMethod == null) { |
| return null; |
| } |
| |
| debugProcess.invokeMethod(evaluationContext, bitmap, copyToBufferMethod, ImmutableList.of(byteBufferRef)); |
| return byteArray.getValues(); |
| } |
| |
| @Nullable |
| private static Dimension getDimension(@NotNull EvaluationContextImpl context, @NotNull Value bitmap) throws EvaluateException { |
| DebugProcessImpl debugProcess = context.getDebugProcess(); |
| |
| Integer w = getImageDimension(context, (ObjectReference)bitmap, debugProcess, "getWidth"); |
| Integer h = getImageDimension(context, (ObjectReference)bitmap, debugProcess, "getHeight"); |
| |
| return (w != null & h != null) ? new Dimension(w, h) : null; |
| } |
| |
| @Nullable |
| private static Value getBitmapConfig(EvaluationContextImpl context, ObjectReference bitmap) throws EvaluateException { |
| DebugProcessImpl debugProcess = context.getDebugProcess(); |
| Method getConfig = DebuggerUtils.findMethod(bitmap.referenceType(), "getConfig", "()Landroid/graphics/Bitmap$Config;"); |
| if (getConfig == null) { |
| return null; |
| } |
| return debugProcess.invokeMethod(context, bitmap, getConfig, Collections.emptyList()); |
| } |
| |
| @Nullable |
| private static Value getBitmapFromDrawable(@NotNull EvaluationContextImpl context, @NotNull ObjectReference bitmap) |
| throws EvaluateException { |
| DebugProcessImpl debugProcess = context.getDebugProcess(); |
| Method getBitmapMethod = DebuggerUtils |
| .findMethod(bitmap.referenceType(), "getBitmap", "()Landroid/graphics/Bitmap;"); |
| if (getBitmapMethod == null) { |
| return null; |
| } |
| return debugProcess.invokeMethod(context, bitmap, getBitmapMethod, Collections.emptyList()); |
| } |
| |
| @Nullable |
| private static Value createScaledBitmap(@NotNull EvaluationContextImpl context, |
| @NotNull ObjectReference bitmap, |
| @NotNull Dimension currentDimensions) throws EvaluateException { |
| DebugProcessImpl debugProcess = context.getDebugProcess(); |
| Method createScaledBitmapMethod = DebuggerUtils |
| .findMethod(bitmap.referenceType(), "createScaledBitmap", "(Landroid/graphics/Bitmap;IIZ)Landroid/graphics/Bitmap;"); |
| if (createScaledBitmapMethod == null) { |
| return null; |
| } |
| |
| double s = Math.max(currentDimensions.getHeight(), currentDimensions.getWidth()) / MAX_DIMENSION; |
| |
| VirtualMachineProxyImpl vm = context.getDebugProcess().getVirtualMachineProxy(); |
| Value dstWidth = DebuggerUtilsEx.createValue(vm, "int", (int)(currentDimensions.getWidth() / s)); |
| Value dstHeight = DebuggerUtilsEx.createValue(vm, "int", (int)(currentDimensions.getHeight() / s)); |
| Value filter = DebuggerUtilsEx.createValue(vm, "boolean", Boolean.FALSE); |
| return debugProcess.invokeMethod(context, bitmap, createScaledBitmapMethod, |
| Arrays.asList(bitmap, dstWidth, dstHeight, filter)); |
| } |
| |
| @Nullable |
| private static Integer getImageDimension(EvaluationContextImpl context, |
| ObjectReference bitmap, |
| DebugProcessImpl debugProcess, |
| String methodName) throws EvaluateException { |
| Method method = DebuggerUtils.findMethod((bitmap).referenceType(), methodName, "()I"); |
| if (method != null) { |
| Value widthValue = debugProcess.invokeMethod(context, bitmap, method, Collections.emptyList()); |
| if (widthValue instanceof IntegerValue) { |
| return ((IntegerValue)widthValue).value(); |
| } |
| } |
| |
| return null; |
| } |
| |
| private static BufferedImage createBufferedImage(int width, int height, byte[] rgba) { |
| @SuppressWarnings("UndesirableClassUsage") |
| BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
| |
| for (int y = 0; y < height; y++) { |
| for (int x = 0; x < width; x++) { |
| int i = (y * width + x) * 4; |
| long rgb = 0; |
| rgb |= ((long)rgba[i+0] & 0xff) << 16; // r |
| rgb |= ((long)rgba[i+1] & 0xff) << 8; // g |
| rgb |= ((long)rgba[i+2] & 0xff) << 0; // b |
| rgb |= ((long)rgba[i+3] & 0xff) << 24; // a |
| bufferedImage.setRGB(x, y, (int)(rgb & 0xffffffff)); |
| } |
| } |
| |
| return bufferedImage; |
| } |
| } |