/* * Copyright (C) 2007 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.ddmlib; import java.nio.ByteBuffer; /** * Data representing an image taken from a device frame buffer. */ public final class RawImage { public int version; public int bpp; public int size; public int width; public int height; public int red_offset; public int red_length; public int blue_offset; public int blue_length; public int green_offset; public int green_length; public int alpha_offset; public int alpha_length; public byte[] data; /** * Reads the header of a RawImage from a {@link ByteBuffer}. * <p/>The way the data is sent over adb is defined in system/core/adb/framebuffer_service.c * @param version the version of the protocol. * @param buf the buffer to read from. * @return true if success */ public boolean readHeader(int version, ByteBuffer buf) { this.version = version; if (version == 16) { // compatibility mode with original protocol this.bpp = 16; // read actual values. this.size = buf.getInt(); this.width = buf.getInt(); this.height = buf.getInt(); // create default values for the rest. Format is 565 this.red_offset = 11; this.red_length = 5; this.green_offset = 5; this.green_length = 6; this.blue_offset = 0; this.blue_length = 5; this.alpha_offset = 0; this.alpha_length = 0; } else if (version == 1) { this.bpp = buf.getInt(); this.size = buf.getInt(); this.width = buf.getInt(); this.height = buf.getInt(); this.red_offset = buf.getInt(); this.red_length = buf.getInt(); this.blue_offset = buf.getInt(); this.blue_length = buf.getInt(); this.green_offset = buf.getInt(); this.green_length = buf.getInt(); this.alpha_offset = buf.getInt(); this.alpha_length = buf.getInt(); } else { // unsupported protocol! return false; } return true; } /** * Returns the mask value for the red color. * <p/>This value is compatible with org.eclipse.swt.graphics.PaletteData */ public int getRedMask() { return getMask(red_length, red_offset); } /** * Returns the mask value for the green color. * <p/>This value is compatible with org.eclipse.swt.graphics.PaletteData */ public int getGreenMask() { return getMask(green_length, green_offset); } /** * Returns the mask value for the blue color. * <p/>This value is compatible with org.eclipse.swt.graphics.PaletteData */ public int getBlueMask() { return getMask(blue_length, blue_offset); } /** * Returns the size of the header for a specific version of the framebuffer adb protocol. * @param version the version of the protocol * @return the number of int that makes up the header. */ public static int getHeaderSize(int version) { switch (version) { case 16: // compatibility mode return 3; // size, width, height case 1: return 12; // bpp, size, width, height, 4*(length, offset) } return 0; } /** * Returns a rotated version of the image * The image is rotated counter-clockwise. */ public RawImage getRotated() { RawImage rotated = new RawImage(); rotated.version = this.version; rotated.bpp = this.bpp; rotated.size = this.size; rotated.red_offset = this.red_offset; rotated.red_length = this.red_length; rotated.blue_offset = this.blue_offset; rotated.blue_length = this.blue_length; rotated.green_offset = this.green_offset; rotated.green_length = this.green_length; rotated.alpha_offset = this.alpha_offset; rotated.alpha_length = this.alpha_length; rotated.width = this.height; rotated.height = this.width; int count = this.data.length; rotated.data = new byte[count]; int byteCount = this.bpp >> 3; // bpp is in bits, we want bytes to match our array final int w = this.width; final int h = this.height; for (int y = 0 ; y < h ; y++) { for (int x = 0 ; x < w ; x++) { System.arraycopy( this.data, (y * w + x) * byteCount, rotated.data, ((w-x-1) * h + y) * byteCount, byteCount); } } return rotated; } /** * Returns an ARGB integer value for the pixel at <var>index</var> in {@link #data}. */ public int getARGB(int index) { int value; int r, g, b, a; if (bpp == 16) { value = data[index] & 0x00FF; value |= (data[index+1] << 8) & 0x0FF00; // RGB565 to RGB888 // Multiply by 255/31 to convert from 5 bits (31 max) to 8 bits (255) r = ((value >>> 11) & 0x1f) * 255/31; g = ((value >>> 5) & 0x3f) * 255/63; b = ((value) & 0x1f) * 255/31; a = 0xFF; // force alpha to opaque if there's no alpha value in the framebuffer. } else if (bpp == 32) { value = data[index] & 0x00FF; value |= (data[index+1] & 0x00FF) << 8; value |= (data[index+2] & 0x00FF) << 16; value |= (data[index+3] & 0x00FF) << 24; r = ((value >>> red_offset) & getMask(red_length)) << (8 - red_length); g = ((value >>> green_offset) & getMask(green_length)) << (8 - green_length); b = ((value >>> blue_offset) & getMask(blue_length)) << (8 - blue_length); a = ((value >>> alpha_offset) & getMask(alpha_length)) << (8 - alpha_length); } else { throw new UnsupportedOperationException("RawImage.getARGB(int) only works in 16 and 32 bit mode."); } return a << 24 | r << 16 | g << 8 | b; } /** * creates a mask value based on a length and offset. * <p/>This value is compatible with org.eclipse.swt.graphics.PaletteData */ private int getMask(int length, int offset) { int res = getMask(length) << offset; // if the bpp is 32 bits then we need to invert it because the buffer is in little endian if (bpp == 32) { return Integer.reverseBytes(res); } return res; } /** * Creates a mask value based on a length. * @param length * @return */ private static int getMask(int length) { return (1 << length) - 1; } }