Merge "Refactor ImageUtils to eliminate extra copy of image data that was being done. This yields a small (but noticable) performance improvement."

This commit is contained in:
Bill Napier
2010-04-27 10:55:37 -07:00
committed by Android (Google) Code Review
11 changed files with 502 additions and 71 deletions

View File

@@ -16,3 +16,4 @@
MONKEYRUNNER_LOCAL_DIR := $(call my-dir) MONKEYRUNNER_LOCAL_DIR := $(call my-dir)
include $(MONKEYRUNNER_LOCAL_DIR)/etc/Android.mk include $(MONKEYRUNNER_LOCAL_DIR)/etc/Android.mk
include $(MONKEYRUNNER_LOCAL_DIR)/src/Android.mk include $(MONKEYRUNNER_LOCAL_DIR)/src/Android.mk
include $(MONKEYRUNNER_LOCAL_DIR)/test/Android.mk

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2010 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.monkeyrunner;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.RawImage;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
/**
* Utility program to capture raw and converted images from a device and write them to a file.
* This is used to generate the test data for ImageUtilsTest.
*/
public class CaptureRawAndConvertedImage {
public static class MonkeyRunnerRawImage implements Serializable {
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;
public MonkeyRunnerRawImage(RawImage rawImage) {
version = rawImage.version;
bpp = rawImage.bpp;
size = rawImage.size;
width = rawImage.width;
height = rawImage.height;
red_offset = rawImage.red_offset;
red_length = rawImage.red_length;
blue_offset = rawImage.blue_offset;
blue_length = rawImage.blue_length;
green_offset = rawImage.green_offset;
green_length = rawImage.green_length;
alpha_offset = rawImage.alpha_offset;
alpha_length = rawImage.alpha_length;
data = rawImage.data;
}
public RawImage toRawImage() {
RawImage rawImage = new RawImage();
rawImage.version = version;
rawImage.bpp = bpp;
rawImage.size = size;
rawImage.width = width;
rawImage.height = height;
rawImage.red_offset = red_offset;
rawImage.red_length = red_length;
rawImage.blue_offset = blue_offset;
rawImage.blue_length = blue_length;
rawImage.green_offset = green_offset;
rawImage.green_length = green_length;
rawImage.alpha_offset = alpha_offset;
rawImage.alpha_length = alpha_length;
rawImage.data = data;
return rawImage;
}
}
public static void main(String[] args) throws IOException {
DebugBridge adb = DebugBridge.createDebugBridge();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
IDevice device = adb.getPreferredDevice();
RawImage screenshot = device.getScreenshot();
BufferedImage convertImage = ImageUtils.convertImage(screenshot);
// write out to a file
ImageIO.write(convertImage, "png", new File("output.png"));
writeOutImage(screenshot, "output.raw");
}
private static void writeOutImage(RawImage screenshot, String name) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(name));
out.writeObject(new MonkeyRunnerRawImage(screenshot));
out.close();
}
}

View File

@@ -17,7 +17,14 @@ package com.android.monkeyrunner;
import com.android.ddmlib.RawImage; import com.android.ddmlib.RawImage;
import java.awt.Point;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.Hashtable;
/** /**
* Useful image related functions. * Useful image related functions.
*/ */
@@ -25,30 +32,38 @@ public class ImageUtils {
// Utility class // Utility class
private ImageUtils() { } private ImageUtils() { }
public static BufferedImage convertImage(RawImage rawImage, BufferedImage image) { private static Hashtable<?,?> EMPTY_HASH = new Hashtable();
if (image == null || rawImage.width != image.getWidth() || private static int[] BAND_OFFSETS_32 = { 0, 1, 2, 3 };
rawImage.height != image.getHeight()) { private static int[] BAND_OFFSETS_16 = { 0, 1 };
image = new BufferedImage(rawImage.width, rawImage.height,
BufferedImage.TYPE_INT_ARGB);
}
/**
* Convert a raw image into a buffered image.
*
* @param rawImage the raw image to convert
* @param image the old image to (possibly) recycle
* @return the converted image
*/
public static BufferedImage convertImage(RawImage rawImage, BufferedImage image) {
switch (rawImage.bpp) { switch (rawImage.bpp) {
case 16: case 16:
rawImage16toARGB(image, rawImage); return rawImage16toARGB(image, rawImage);
break;
case 32: case 32:
rawImage32toARGB(image, rawImage); return rawImage32toARGB(rawImage);
break; }
} return null;
return image;
} }
/**
* Convert a raw image into a buffered image.
*
* @param rawImage the image to convert.
* @return the converted image.
*/
public static BufferedImage convertImage(RawImage rawImage) { public static BufferedImage convertImage(RawImage rawImage) {
return convertImage(rawImage, null); return convertImage(rawImage, null);
} }
private static int getMask(int length) { static int getMask(int length) {
int res = 0; int res = 0;
for (int i = 0 ; i < length ; i++) { for (int i = 0 ; i < length ; i++) {
res = (res << 1) + 1; res = (res << 1) + 1;
@@ -57,67 +72,29 @@ public class ImageUtils {
return res; return res;
} }
private static void rawImage32toARGB(BufferedImage image, RawImage rawImage) { private static BufferedImage rawImage32toARGB(RawImage rawImage) {
int[] scanline = new int[rawImage.width]; // Do as much as we can to not make an extra copy of the data. This is just a bunch of
byte[] buffer = rawImage.data; // classes that wrap's the raw byte array of the image data.
int index = 0; DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
final int redOffset = rawImage.red_offset; PixelInterleavedSampleModel sampleModel =
final int redLength = rawImage.red_length; new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
final int redMask = getMask(redLength); 4, rawImage.width * 4, BAND_OFFSETS_32);
final int greenOffset = rawImage.green_offset; WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
final int greenLength = rawImage.green_length; new Point(0, 0));
final int greenMask = getMask(greenLength); return new BufferedImage(new ThirtyTwoBitColorModel(rawImage), raster, false, EMPTY_HASH);
final int blueOffset = rawImage.blue_offset;
final int blueLength = rawImage.blue_length;
final int blueMask = getMask(blueLength);
final int alphaLength = rawImage.alpha_length;
final int alphaOffset = rawImage.alpha_offset;
final int alphaMask = getMask(alphaLength);
for (int y = 0 ; y < rawImage.height ; y++) {
for (int x = 0 ; x < rawImage.width ; x++) {
int value = buffer[index++] & 0x00FF;
value |= (buffer[index++] & 0x00FF) << 8;
value |= (buffer[index++] & 0x00FF) << 16;
value |= (buffer[index++] & 0x00FF) << 24;
int r = ((value >>> redOffset) & redMask) << (8 - redLength);
int g = ((value >>> greenOffset) & greenMask) << (8 - greenLength);
int b = ((value >>> blueOffset) & blueMask) << (8 - blueLength);
int a = 0xFF;
if (alphaLength != 0) {
a = ((value >>> alphaOffset) & alphaMask) << (8 - alphaLength);
} }
scanline[x] = a << 24 | r << 16 | g << 8 | b; private static BufferedImage rawImage16toARGB(BufferedImage image, RawImage rawImage) {
} // Do as much as we can to not make an extra copy of the data. This is just a bunch of
// classes that wrap's the raw byte array of the image data.
DataBufferByte dataBuffer = new DataBufferByte(rawImage.data, rawImage.size);
image.setRGB(0, y, rawImage.width, 1, scanline, PixelInterleavedSampleModel sampleModel =
0, rawImage.width); new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height,
} 2, rawImage.width * 2, BAND_OFFSETS_16);
} WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer,
new Point(0, 0));
private static void rawImage16toARGB(BufferedImage image, RawImage rawImage) { return new BufferedImage(new SixteenBitColorModel(rawImage), raster, false, EMPTY_HASH);
int[] scanline = new int[rawImage.width];
byte[] buffer = rawImage.data;
int index = 0;
for (int y = 0 ; y < rawImage.height ; y++) {
for (int x = 0 ; x < rawImage.width ; x++) {
int value = buffer[index++] & 0x00FF;
value |= (buffer[index++] << 8) & 0x0FF00;
int r = ((value >> 11) & 0x01F) << 3;
int g = ((value >> 5) & 0x03F) << 2;
int b = ((value ) & 0x01F) << 3;
scanline[x] = 0xFF << 24 | r << 16 | g << 8 | b;
}
image.setRGB(0, y, rawImage.width, 1, scanline,
0, rawImage.width);
}
} }
} }

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2010 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.monkeyrunner;
import com.android.ddmlib.RawImage;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
/**
* Internal color model used to do conversion of 16bpp RawImages.
*/
class SixteenBitColorModel extends ColorModel {
private static final int[] BITS = {
8, 8, 8, 8
};
public SixteenBitColorModel(RawImage rawImage) {
super(32
, BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
true, false, Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
}
@Override
public boolean isCompatibleRaster(Raster raster) {
return true;
}
private int getPixel(Object inData) {
byte[] data = (byte[]) inData;
int value = data[0] & 0x00FF;
value |= (data[1] << 8) & 0x0FF00;
return value;
}
@Override
public int getAlpha(Object inData) {
return 0xff;
}
@Override
public int getBlue(Object inData) {
int pixel = getPixel(inData);
return ((pixel >> 0) & 0x01F) << 3;
}
@Override
public int getGreen(Object inData) {
int pixel = getPixel(inData);
return ((pixel >> 5) & 0x03F) << 2;
}
@Override
public int getRed(Object inData) {
int pixel = getPixel(inData);
return ((pixel >> 11) & 0x01F) << 3;
}
@Override
public int getAlpha(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getBlue(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getGreen(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getRed(int pixel) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2010 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.monkeyrunner;
import com.android.ddmlib.RawImage;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
/**
* Internal color model used to do conversion of 32bpp RawImages.
*/
class ThirtyTwoBitColorModel extends ColorModel {
private static final int[] BITS = {
8, 8, 8, 8,
};
private final int alphaLength;
private final int alphaMask;
private final int alphaOffset;
private final int blueMask;
private final int blueLength;
private final int blueOffset;
private final int greenMask;
private final int greenLength;
private final int greenOffset;
private final int redMask;
private final int redLength;
private final int redOffset;
public ThirtyTwoBitColorModel(RawImage rawImage) {
super(32, BITS, ColorSpace.getInstance(ColorSpace.CS_sRGB),
true, false, Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
redOffset = rawImage.red_offset;
redLength = rawImage.red_length;
redMask = ImageUtils.getMask(redLength);
greenOffset = rawImage.green_offset;
greenLength = rawImage.green_length;
greenMask = ImageUtils.getMask(greenLength);
blueOffset = rawImage.blue_offset;
blueLength = rawImage.blue_length;
blueMask = ImageUtils.getMask(blueLength);
alphaLength = rawImage.alpha_length;
alphaOffset = rawImage.alpha_offset;
alphaMask = ImageUtils.getMask(alphaLength);
}
@Override
public boolean isCompatibleRaster(Raster raster) {
return true;
}
private int getPixel(Object inData) {
byte[] data = (byte[]) inData;
int value = data[0] & 0x00FF;
value |= (data[1] & 0x00FF) << 8;
value |= (data[2] & 0x00FF) << 16;
value |= (data[3] & 0x00FF) << 24;
return value;
}
@Override
public int getAlpha(Object inData) {
int pixel = getPixel(inData);
if(alphaLength == 0) {
return 0xff;
}
return ((pixel >>> alphaOffset) & alphaMask) << (8 - alphaLength);
}
@Override
public int getBlue(Object inData) {
int pixel = getPixel(inData);
return ((pixel >>> blueOffset) & blueMask) << (8 - blueLength);
}
@Override
public int getGreen(Object inData) {
int pixel = getPixel(inData);
return ((pixel >>> greenOffset) & greenMask) << (8 - greenLength);
}
@Override
public int getRed(Object inData) {
int pixel = getPixel(inData);
return ((pixel >>> redOffset) & redMask) << (8 - redLength);
}
@Override
public int getAlpha(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getBlue(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getGreen(int pixel) {
throw new UnsupportedOperationException();
}
@Override
public int getRed(int pixel) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,23 @@
#
# Copyright (C) 2010 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := MonkeyRunnerTest
LOCAL_JAVA_LIBRARIES := junit monkeyrunner ddmlib
include $(BUILD_HOST_JAVA_LIBRARY)

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2010 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.monkeyrunner;
import com.android.ddmlib.RawImage;
import com.android.monkeyrunner.CaptureRawAndConvertedImage.MonkeyRunnerRawImage;
import junit.framework.TestCase;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import javax.imageio.ImageIO;
public class ImageUtilsTest extends TestCase {
private static BufferedImage createBufferedImage(String name) throws IOException {
InputStream is = ImageUtilsTest.class.getResourceAsStream(name);
BufferedImage img = ImageIO.read(is);
is.close();
return img;
}
private static RawImage createRawImage(String name) throws IOException, ClassNotFoundException {
ObjectInputStream is =
new ObjectInputStream(ImageUtilsTest.class.getResourceAsStream(name));
CaptureRawAndConvertedImage.MonkeyRunnerRawImage wrapper = (MonkeyRunnerRawImage) is.readObject();
is.close();
return wrapper.toRawImage();
}
/**
* Check that the two images will draw the same (ie. have the same pixels). This is different
* that BufferedImage.equals(), which also wants to check that they have the same ColorModel
* and other parameters.
*
* @param i1 the first image
* @param i2 the second image
* @return true if both images will draw the same (ie. have same pixels).
*/
private static boolean checkImagesHaveSamePixels(BufferedImage i1, BufferedImage i2) {
if (i1.getWidth() != i2.getWidth()) {
return false;
}
if (i1.getHeight() != i2.getHeight()) {
return false;
}
for (int y = 0; y < i1.getHeight(); y++) {
for (int x = 0; x < i1.getWidth(); x++) {
int p1 = i1.getRGB(x, y);
int p2 = i2.getRGB(x, y);
if (p1 != p2) {
WritableRaster r1 = i1.getRaster();
WritableRaster r2 = i2.getRaster();
return false;
}
}
}
return true;
}
public void testImageConversionOld() throws IOException, ClassNotFoundException {
RawImage rawImage = createRawImage("image1.raw");
BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
BufferedImage correctConvertedImage = createBufferedImage("image1.png");
assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
}
public void testImageConversionNew() throws IOException, ClassNotFoundException {
RawImage rawImage = createRawImage("image2.raw");
BufferedImage convertedImage = ImageUtils.convertImage(rawImage);
BufferedImage correctConvertedImage = createBufferedImage("image2.png");
assertTrue(checkImagesHaveSamePixels(convertedImage, correctConvertedImage));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB