| /* |
| * Copyright (C) 2013 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.rendering; |
| |
| import junit.framework.TestCase; |
| import org.jetbrains.android.AndroidTestCase; |
| |
| import javax.imageio.ImageIO; |
| import java.awt.*; |
| import java.awt.image.BufferedImage; |
| import java.io.File; |
| import java.io.IOException; |
| |
| public class ImageUtilsTest extends TestCase { |
| public void testScaleImage() throws Exception { |
| @SuppressWarnings("UndesirableClassUsage") |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0xFF00FF00, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.setColor(new Color(0xFFFF0000, true)); |
| g.fillRect(25, 25, 50, 50); |
| g.dispose(); |
| |
| BufferedImage scaled = ImageUtils.scale(image, 0.5, 0.5); |
| assertEquals(50, scaled.getWidth()); |
| assertEquals(50, scaled.getHeight()); |
| assertEquals(0xFF00FF00, scaled.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, scaled.getRGB(49, 49)); |
| assertEquals(0xFFFF0000, scaled.getRGB(25, 25)); |
| |
| scaled = ImageUtils.scale(image, 2.0, 2.0); |
| assertEquals(200, scaled.getWidth()); |
| assertEquals(200, scaled.getHeight()); |
| assertEquals(0xFF00FF00, scaled.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, scaled.getRGB(48, 48)); |
| assertEquals(0xFFFF0000, scaled.getRGB(100, 100)); |
| assertEquals(0xFF00FF00, scaled.getRGB(199, 199)); |
| |
| scaled = ImageUtils.scale(image, 0.25, 0.25); |
| assertEquals(25, scaled.getWidth()); |
| assertEquals(25, scaled.getHeight()); |
| assertEquals(0xFF00FF00, scaled.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, scaled.getRGB(24, 24)); |
| assertEquals(0xFFFF0000, scaled.getRGB(13, 13)); |
| |
| scaled = ImageUtils.scale(image, 0.25, 0.25, 75, 95); |
| assertEquals(100, scaled.getWidth()); |
| assertEquals(120, scaled.getHeight()); |
| assertEquals(0xFF00FF00, scaled.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, scaled.getRGB(24, 24)); |
| assertEquals(0xFFFF0000, scaled.getRGB(13, 13)); |
| } |
| |
| public void testRotation() throws IOException { |
| String path = AndroidTestCase.getAbsoluteTestDataPath() + File.separator + "render" + File.separator + "imageutils"; |
| |
| BufferedImage srcImage = readImage(path, "0.png"); |
| BufferedImage expected90 = readImage(path, "90.png"); |
| BufferedImage expected180 = readImage(path, "180.png"); |
| BufferedImage expected270 = readImage(path, "270.png"); |
| |
| assertEqualsImage(expected90, ImageUtils.rotateByRightAngle(srcImage, 90)); |
| assertEqualsImage(expected180, ImageUtils.rotateByRightAngle(srcImage, 180)); |
| assertEqualsImage(expected270, ImageUtils.rotateByRightAngle(srcImage, 270)); |
| |
| // verify that rotating the image 4 times by 90 degrees doesn't lose any data |
| BufferedImage img = readImage(path, "0.png"); |
| for (int i = 0; i < 4; i++) { |
| img = ImageUtils.rotateByRightAngle(img, 90); |
| } |
| assertEqualsImage(srcImage, img); |
| } |
| |
| public void testAddMargin() { |
| BufferedImage image = createTestImage(100, 200, BufferedImage.TYPE_INT_ARGB, Color.RED); |
| |
| BufferedImage zeroMargin = ImageUtils.addMargin(image, 0); |
| assertEqualsImage(image, zeroMargin); |
| |
| BufferedImage margin1 = ImageUtils.addMargin(image, 1); |
| assertEquals(margin1.getWidth(), image.getWidth() + 2); |
| assertEquals(margin1.getHeight(), image.getHeight() + 2); |
| |
| image = createTestImage(50, 50, BufferedImage.TYPE_INT_RGB, Color.BLUE); |
| margin1 = ImageUtils.addMargin(image, 1); |
| assertFalse(image.getColorModel().hasAlpha()); |
| assertTrue(margin1.getColorModel().hasAlpha()); |
| assertEqualsImageExcludingMargin(image, margin1, 1); |
| } |
| |
| private static BufferedImage createTestImage(int w, int h, int imageType, Color fill) { |
| @SuppressWarnings("UndesirableClassUsage") |
| BufferedImage image = new BufferedImage(w, h, imageType); |
| Graphics g = image.getGraphics(); |
| g.setColor(fill); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.dispose(); |
| return image; |
| } |
| |
| private static BufferedImage readImage(String folder, String fileName) throws IOException { |
| File f = new File(folder, fileName); |
| return ImageIO.read(f); |
| } |
| |
| private static void assertEqualsImage(BufferedImage expected, BufferedImage actual) { |
| assertEqualsImageExcludingMargin(expected, actual, 0); |
| } |
| |
| private static void assertEqualsImageExcludingMargin(BufferedImage expected, BufferedImage actual, int margin) { |
| assertEquals(expected.getWidth(), actual.getWidth() - 2 * margin); |
| assertEquals(expected.getHeight(), actual.getHeight() - 2 * margin); |
| |
| for (int x = 0; x < expected.getWidth(); x++) { |
| for (int y = 0; y < expected.getHeight(); y++) { |
| assertEquals(expected.getRGB(x, y), actual.getRGB(x + margin, y + margin)); |
| } |
| } |
| } |
| |
| public void testCropBlank() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, null); |
| assertNull(crop); |
| } |
| |
| public void testCropBlankPre() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, new Rectangle(5, 5, 80, 80)); |
| assertNull(crop); |
| } |
| |
| public void testCropTopLine() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, false)); |
| g.fillRect(0, 1, image.getWidth(), image.getHeight() - 1); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, null); |
| assertNotNull(crop); |
| assertEquals(image.getWidth(), crop.getWidth()); |
| assertEquals(image.getHeight() - 1, crop.getHeight()); |
| } |
| |
| public void testCropNonBlank() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, false)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, null); |
| assertNotNull(crop); |
| assertEquals(crop.getWidth(), image.getWidth()); |
| assertEquals(crop.getHeight(), image.getHeight()); |
| } |
| |
| public void testCropSomething() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.setColor(new Color(0xFF00FF00, true)); |
| g.fillRect(25, 25, 50, 50); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, null); |
| assertNotNull(crop); |
| assertEquals(50, crop.getWidth()); |
| assertEquals(50, crop.getHeight()); |
| assertEquals(0xFF00FF00, crop.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, crop.getRGB(49, 49)); |
| } |
| |
| public void testCropSomethingPre() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.setColor(new Color(0xFF00FF00, true)); |
| g.fillRect(25, 25, 50, 50); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, new Rectangle(0, 0, 100, 100)); |
| assertNotNull(crop); |
| assertEquals(50, crop.getWidth()); |
| assertEquals(50, crop.getHeight()); |
| assertEquals(0xFF00FF00, crop.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, crop.getRGB(49, 49)); |
| } |
| |
| public void testCropSomethingPre2() throws Exception { |
| BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB_PRE); |
| Graphics g = image.getGraphics(); |
| g.setColor(new Color(0, true)); |
| g.fillRect(0, 0, image.getWidth(), image.getHeight()); |
| g.setColor(new Color(0xFF00FF00, true)); |
| g.fillRect(25, 25, 50, 50); |
| g.dispose(); |
| |
| BufferedImage crop = ImageUtils.cropBlank(image, new Rectangle(5, 5, 80, 80)); |
| assertNotNull(crop); |
| assertEquals(50, crop.getWidth()); |
| assertEquals(50, crop.getHeight()); |
| assertEquals(0xFF00FF00, crop.getRGB(0, 0)); |
| assertEquals(0xFF00FF00, crop.getRGB(49, 49)); |
| } |
| |
| /** |
| * Paints a set of {@link Rectangle} object out of a rendered {@link BufferedImage} |
| * such that the resulting image is transparent except for a minimum bounding |
| * rectangle of the selected elements. |
| * |
| * @param image the source image |
| * @param rectangles the set of rectangles to copy |
| * @param boundingBox the bounding rectangle of the set of rectangles to copy, can be |
| * computed by {@link ImageUtils#getBoundingRectangle} |
| * @param scale a scale factor to apply to the result, e.g. 0.5 to shrink the |
| * destination down 50%, 1.0 to leave it alone and 2.0 to zoom in by |
| * doubling the image size |
| * @return a rendered image, or null |
| */ |
| public static BufferedImage drawRectangles(BufferedImage image, |
| java.util.List<Rectangle> rectangles, Rectangle boundingBox, double scale) { |
| |
| // This code is not a test. When I implemented image cropping, I first implemented |
| // it for BufferedImages (since it's easier; easy image painting, easy scaling, |
| // easy transparency handling, etc). However, this meant that we would need to |
| // convert the SWT images from the ImageOverlay to BufferedImages, crop and convert |
| // back; not ideal, so I rewrote it in SWT (see SwtUtils). However, I |
| // don't want to throw away the code in case we start keeping BufferedImages rather |
| // than SWT images or need it for other purposes, but rather than place it in the |
| // production codebase I'm leaving this utility here in the associated ImageUtils |
| // test class. It was used like this: |
| // @formatter:off |
| // |
| // BufferedImage wholeImage = SwtUtils.convertToAwt(image); |
| // BufferedImage result = ImageUtils.cropSelection(wholeImage, |
| // rectangles, boundingBox, scale); |
| // e.image = SwtUtils.convertToSwt(image.getDevice(), result, true, |
| // DRAG_TRANSPARENCY); |
| // |
| // @formatter:on |
| |
| if (boundingBox == null) { |
| return null; |
| } |
| |
| int destWidth = (int) (scale * boundingBox.width); |
| int destHeight = (int) (scale * boundingBox.height); |
| BufferedImage dest = new BufferedImage(destWidth, destHeight, image.getType()); |
| |
| Graphics2D g = dest.createGraphics(); |
| |
| for (Rectangle bounds : rectangles) { |
| int dx1 = bounds.x - boundingBox.x; |
| int dy1 = bounds.y - boundingBox.y; |
| int dx2 = dx1 + bounds.width; |
| int dy2 = dy1 + bounds.height; |
| |
| dx1 *= scale; |
| dy1 *= scale; |
| dx2 *= scale; |
| dy2 *= scale; |
| |
| int sx1 = bounds.x; |
| int sy1 = bounds.y; |
| int sx2 = sx1 + bounds.width; |
| int sy2 = sy1 + bounds.height; |
| |
| g.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null); |
| } |
| |
| g.dispose(); |
| |
| return dest; |
| } |
| } |