1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.assetstudiolib; 18 19 import java.awt.Color; 20 import java.awt.Graphics; 21 import java.awt.image.BufferedImage; 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 import java.util.Map; 29 30 import javax.imageio.ImageIO; 31 32 import junit.framework.TestCase; 33 34 /** 35 * Shared test infrastructure for code generator 36 */ 37 public abstract class GeneratorTest extends TestCase implements GraphicGeneratorContext { 38 private static final String TEST_DATA_REL_PATH = 39 "assetstudio/tests/src/com/android/assetstudiolib/testdata"; 40 checkGraphic(int expectedFileCount, String folderName, String baseName, GraphicGenerator generator, GraphicGenerator.Options options)41 protected void checkGraphic(int expectedFileCount, String folderName, String baseName, 42 GraphicGenerator generator, GraphicGenerator.Options options) 43 throws IOException { 44 Map<String, Map<String, BufferedImage>> categoryMap = 45 new HashMap<String, Map<String, BufferedImage>>(); 46 options.sourceImage = GraphicGenerator.getClipartImage("android.png"); 47 generator.generate(null, categoryMap, this, options, baseName); 48 49 File targetDir = getTargetDir(); 50 51 List<String> errors = new ArrayList<String>(); 52 int fileCount = 0; 53 for (Map<String, BufferedImage> previews : categoryMap.values()) { 54 for (Map.Entry<String, BufferedImage> entry : previews.entrySet()) { 55 String relativePath = entry.getKey(); 56 BufferedImage image = entry.getValue(); 57 58 String path = "testdata" + File.separator + folderName + File.separator 59 + relativePath; 60 InputStream is = GeneratorTest.class.getResourceAsStream(path); 61 if (is == null) { 62 if (targetDir == null) { 63 fail("Did not find " + path 64 + ". Set ADT_SDK_SOURCE_PATH to have it created automatically"); 65 } 66 File fileName = new File(targetDir, folderName + File.separator 67 + relativePath); 68 assertFalse(fileName.exists()); 69 if (!fileName.getParentFile().exists()) { 70 boolean mkdir = fileName.getParentFile().mkdirs(); 71 assertTrue(fileName.getParent(), mkdir); 72 } 73 74 ImageIO.write(image, "PNG", fileName); 75 errors.add("File did not exist, created " + fileName.getPath()); 76 } else { 77 BufferedImage goldenImage = ImageIO.read(is); 78 assertImageSimilar(relativePath, goldenImage, image, 5.0f); 79 } 80 } 81 82 fileCount += previews.values().size(); 83 } 84 if (errors.size() > 0) { 85 fail(errors.toString()); 86 } 87 88 assertEquals("Wrong number of generated files", expectedFileCount, fileCount); 89 } 90 assertImageSimilar(String imageName, BufferedImage goldenImage, BufferedImage image, float maxPercentDifferent)91 private void assertImageSimilar(String imageName, BufferedImage goldenImage, 92 BufferedImage image, float maxPercentDifferent) throws IOException { 93 assertTrue("Widths differ too much for " + imageName, Math.abs(goldenImage.getWidth() 94 - image.getWidth()) < 2); 95 assertTrue("Widths differ too much for " + imageName, Math.abs(goldenImage.getHeight() 96 - image.getHeight()) < 2); 97 98 assertEquals(BufferedImage.TYPE_INT_ARGB, image.getType()); 99 100 if (goldenImage.getType() != BufferedImage.TYPE_INT_ARGB) { 101 BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(), 102 BufferedImage.TYPE_INT_ARGB); 103 temp.getGraphics().drawImage(goldenImage, 0, 0, null); 104 goldenImage = temp; 105 } 106 assertEquals(BufferedImage.TYPE_INT_ARGB, goldenImage.getType()); 107 108 int imageWidth = Math.min(goldenImage.getWidth(), image.getWidth()); 109 int imageHeight = Math.min(goldenImage.getHeight(), image.getHeight()); 110 111 // Blur the images to account for the scenarios where there are pixel 112 // differences 113 // in where a sharp edge occurs 114 // goldenImage = blur(goldenImage, 6); 115 // image = blur(image, 6); 116 117 int width = 3 * imageWidth; 118 int height = imageHeight; 119 BufferedImage deltaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 120 Graphics g = deltaImage.getGraphics(); 121 122 // Compute delta map 123 long delta = 0; 124 for (int y = 0; y < imageHeight; y++) { 125 for (int x = 0; x < imageWidth; x++) { 126 int goldenRgb = goldenImage.getRGB(x, y); 127 int rgb = image.getRGB(x, y); 128 if (goldenRgb == rgb) { 129 deltaImage.setRGB(imageWidth + x, y, 0x00808080); 130 continue; 131 } 132 133 // If the pixels have no opacity, don't delta colors at all 134 if (((goldenRgb & 0xFF000000) == 0) && (rgb & 0xFF000000) == 0) { 135 deltaImage.setRGB(imageWidth + x, y, 0x00808080); 136 continue; 137 } 138 139 int deltaR = ((rgb & 0xFF0000) >>> 16) - ((goldenRgb & 0xFF0000) >>> 16); 140 int newR = 128 + deltaR & 0xFF; 141 int deltaG = ((rgb & 0x00FF00) >>> 8) - ((goldenRgb & 0x00FF00) >>> 8); 142 int newG = 128 + deltaG & 0xFF; 143 int deltaB = (rgb & 0x0000FF) - (goldenRgb & 0x0000FF); 144 int newB = 128 + deltaB & 0xFF; 145 146 int avgAlpha = ((((goldenRgb & 0xFF000000) >>> 24) 147 + ((rgb & 0xFF000000) >>> 24)) / 2) << 24; 148 149 int newRGB = avgAlpha | newR << 16 | newG << 8 | newB; 150 deltaImage.setRGB(imageWidth + x, y, newRGB); 151 152 delta += Math.abs(deltaR); 153 delta += Math.abs(deltaG); 154 delta += Math.abs(deltaB); 155 } 156 } 157 158 // 3 different colors, 256 color levels 159 long total = imageHeight * imageWidth * 3L * 256L; 160 float percentDifference = (float) (delta * 100 / (double) total); 161 162 if (percentDifference > maxPercentDifferent) { 163 // Expected on the left 164 // Golden on the right 165 g.drawImage(goldenImage, 0, 0, null); 166 g.drawImage(image, 2 * imageWidth, 0, null); 167 168 // Labels 169 if (imageWidth > 80) { 170 g.setColor(Color.RED); 171 g.drawString("Expected", 10, 20); 172 g.drawString("Actual", 2 * imageWidth + 10, 20); 173 } 174 175 File output = new File(getTempDir(), "delta-" 176 + imageName.replace(File.separatorChar, '_')); 177 if (output.exists()) { 178 output.delete(); 179 } 180 ImageIO.write(deltaImage, "PNG", output); 181 String message = String.format("Images differ (by %.1f%%) - see details in %s", 182 percentDifference, output); 183 System.out.println(message); 184 fail(message); 185 } 186 187 g.dispose(); 188 } 189 getTempDir()190 protected File getTempDir() { 191 if (System.getProperty("os.name").equals("Mac OS X")) { 192 return new File("/tmp"); //$NON-NLS-1$ 193 } 194 195 return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ 196 } 197 loadImageResource(String path)198 public BufferedImage loadImageResource(String path) { 199 try { 200 return GraphicGenerator.getStencilImage(path); 201 } catch (IOException e) { 202 fail(e.toString()); 203 } 204 205 return null; 206 } 207 208 /** Get the location to write missing golden files to */ getTargetDir()209 protected File getTargetDir() { 210 // Set $ADT_SDK_SOURCE_PATH to point to your git "sdk" directory 211 String sdk = System.getenv("ADT_SDK_SOURCE_PATH"); 212 if (sdk != null) { 213 File sdkPath = new File(sdk); 214 if (sdkPath.exists()) { 215 File testData = new File(sdkPath, TEST_DATA_REL_PATH.replace('/', 216 File.separatorChar)); 217 if (testData.exists()) { 218 return testData; 219 } 220 } 221 } 222 223 return null; 224 } 225 } 226