diff --git a/tools/monkeyrunner/Android.mk b/tools/monkeyrunner/Android.mk index d15c67e3f..21cf67a74 100644 --- a/tools/monkeyrunner/Android.mk +++ b/tools/monkeyrunner/Android.mk @@ -16,3 +16,4 @@ MONKEYRUNNER_LOCAL_DIR := $(call my-dir) include $(MONKEYRUNNER_LOCAL_DIR)/etc/Android.mk include $(MONKEYRUNNER_LOCAL_DIR)/src/Android.mk +include $(MONKEYRUNNER_LOCAL_DIR)/test/Android.mk diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java new file mode 100644 index 000000000..31d8981f6 --- /dev/null +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/CaptureRawAndConvertedImage.java @@ -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(); + } +} diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java index 870fafa33..e4d022c6c 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ImageUtils.java @@ -17,7 +17,14 @@ package com.android.monkeyrunner; import com.android.ddmlib.RawImage; +import java.awt.Point; 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. */ @@ -25,30 +32,38 @@ public class ImageUtils { // Utility class private ImageUtils() { } - public static BufferedImage convertImage(RawImage rawImage, BufferedImage image) { - if (image == null || rawImage.width != image.getWidth() || - rawImage.height != image.getHeight()) { - image = new BufferedImage(rawImage.width, rawImage.height, - BufferedImage.TYPE_INT_ARGB); - } + private static Hashtable EMPTY_HASH = new Hashtable(); + private static int[] BAND_OFFSETS_32 = { 0, 1, 2, 3 }; + private static int[] BAND_OFFSETS_16 = { 0, 1 }; + /** + * 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) { case 16: - rawImage16toARGB(image, rawImage); - break; + return rawImage16toARGB(image, rawImage); case 32: - rawImage32toARGB(image, rawImage); - break; + return rawImage32toARGB(rawImage); } - - return image; + return null; } + /** + * Convert a raw image into a buffered image. + * + * @param rawImage the image to convert. + * @return the converted image. + */ public static BufferedImage convertImage(RawImage rawImage) { return convertImage(rawImage, null); } - private static int getMask(int length) { + static int getMask(int length) { int res = 0; for (int i = 0 ; i < length ; i++) { res = (res << 1) + 1; @@ -57,67 +72,29 @@ public class ImageUtils { return res; } - private static void rawImage32toARGB(BufferedImage image, RawImage rawImage) { - int[] scanline = new int[rawImage.width]; - byte[] buffer = rawImage.data; - int index = 0; + private static BufferedImage rawImage32toARGB(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); - final int redOffset = rawImage.red_offset; - final int redLength = rawImage.red_length; - final int redMask = getMask(redLength); - final int greenOffset = rawImage.green_offset; - final int greenLength = rawImage.green_length; - final int greenMask = getMask(greenLength); - 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; - } - - image.setRGB(0, y, rawImage.width, 1, scanline, - 0, rawImage.width); - } + PixelInterleavedSampleModel sampleModel = + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, rawImage.width, rawImage.height, + 4, rawImage.width * 4, BAND_OFFSETS_32); + WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, + new Point(0, 0)); + return new BufferedImage(new ThirtyTwoBitColorModel(rawImage), raster, false, EMPTY_HASH); } - private static void rawImage16toARGB(BufferedImage image, RawImage rawImage) { - int[] scanline = new int[rawImage.width]; - byte[] buffer = rawImage.data; - int index = 0; + 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); - 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); - } + PixelInterleavedSampleModel sampleModel = + 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)); + return new BufferedImage(new SixteenBitColorModel(rawImage), raster, false, EMPTY_HASH); } } diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java new file mode 100644 index 000000000..8653d221c --- /dev/null +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/SixteenBitColorModel.java @@ -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(); + } +} diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java new file mode 100644 index 000000000..3099a8b34 --- /dev/null +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ThirtyTwoBitColorModel.java @@ -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(); + } +} diff --git a/tools/monkeyrunner/test/Android.mk b/tools/monkeyrunner/test/Android.mk new file mode 100644 index 000000000..19a64ed8e --- /dev/null +++ b/tools/monkeyrunner/test/Android.mk @@ -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) diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java new file mode 100644 index 000000000..5b1795de6 --- /dev/null +++ b/tools/monkeyrunner/test/com/android/monkeyrunner/ImageUtilsTest.java @@ -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)); + } +} diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png new file mode 100644 index 000000000..cac80f492 Binary files /dev/null and b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.png differ diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw new file mode 100644 index 000000000..a22879393 Binary files /dev/null and b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image1.raw differ diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png new file mode 100644 index 000000000..e6922f1e2 Binary files /dev/null and b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.png differ diff --git a/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw new file mode 100644 index 000000000..77333cb6e Binary files /dev/null and b/tools/monkeyrunner/test/resources/com/android/monkeyrunner/image2.raw differ