/* * Copyright (C) 2015 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.assetstudiolib; import junit.framework.TestCase; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * Shared test infrastructure for asset (either bitmap or vector) generator. * TODO: Merge this file with the duplicated one in sdk-common. */ public abstract class GeneratorTest extends TestCase { protected abstract String getTestDataRelPath(); protected String generateGoldenImage(File targetDir, BufferedImage goldenImage, String missingFilePath, String filePath) throws IOException { if (targetDir == null) { fail( "Did not find " + missingFilePath + ". Set $ANDROID_SRC to have it created automatically"); } File fileName = new File(targetDir, filePath); assertFalse(fileName.exists()); if (!fileName.getParentFile().exists()) { boolean mkdir = fileName.getParentFile().mkdirs(); assertTrue(fileName.getParent(), mkdir); } ImageIO.write(goldenImage, "PNG", fileName); return fileName.getPath(); } public static void assertImageSimilar(String imageName, BufferedImage goldenImage, BufferedImage image, float maxPercentDifferent) throws IOException { assertTrue("Widths differ too much for " + imageName, Math.abs(goldenImage.getWidth() - image.getWidth()) < 2); assertTrue("Widths differ too much for " + imageName, Math.abs(goldenImage.getHeight() - image.getHeight()) < 2); assertEquals(BufferedImage.TYPE_INT_ARGB, image.getType()); if (goldenImage.getType() != BufferedImage.TYPE_INT_ARGB) { BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(), BufferedImage.TYPE_INT_ARGB); temp.getGraphics().drawImage(goldenImage, 0, 0, null); goldenImage = temp; } assertEquals(BufferedImage.TYPE_INT_ARGB, goldenImage.getType()); int imageWidth = Math.min(goldenImage.getWidth(), image.getWidth()); int imageHeight = Math.min(goldenImage.getHeight(), image.getHeight()); // Blur the images to account for the scenarios where there are pixel // differences // in where a sharp edge occurs // goldenImage = blur(goldenImage, 6); // image = blur(image, 6); int width = 3 * imageWidth; int height = imageHeight; BufferedImage deltaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics g = deltaImage.getGraphics(); // Compute delta map long delta = 0; for (int y = 0; y < imageHeight; y++) { for (int x = 0; x < imageWidth; x++) { int goldenRgb = goldenImage.getRGB(x, y); int rgb = image.getRGB(x, y); if (goldenRgb == rgb) { deltaImage.setRGB(imageWidth + x, y, 0x00808080); continue; } // If the pixels have no opacity, don't delta colors at all if (((goldenRgb & 0xFF000000) == 0) && (rgb & 0xFF000000) == 0) { deltaImage.setRGB(imageWidth + x, y, 0x00808080); continue; } int deltaR = ((rgb & 0xFF0000) >>> 16) - ((goldenRgb & 0xFF0000) >>> 16); int newR = 128 + deltaR & 0xFF; int deltaG = ((rgb & 0x00FF00) >>> 8) - ((goldenRgb & 0x00FF00) >>> 8); int newG = 128 + deltaG & 0xFF; int deltaB = (rgb & 0x0000FF) - (goldenRgb & 0x0000FF); int newB = 128 + deltaB & 0xFF; int avgAlpha = ((((goldenRgb & 0xFF000000) >>> 24) + ((rgb & 0xFF000000) >>> 24)) / 2) << 24; int newRGB = avgAlpha | newR << 16 | newG << 8 | newB; deltaImage.setRGB(imageWidth + x, y, newRGB); delta += Math.abs(deltaR); delta += Math.abs(deltaG); delta += Math.abs(deltaB); } } // 3 different colors, 256 color levels long total = imageHeight * imageWidth * 3L * 256L; float percentDifference = (float)(delta * 100 / (double)total); if (percentDifference > maxPercentDifferent) { // Expected on the left // Golden on the right g.drawImage(goldenImage, 0, 0, null); g.drawImage(image, 2 * imageWidth, 0, null); // Labels if (imageWidth > 80) { g.setColor(Color.RED); g.drawString("Expected", 10, 20); g.drawString("Actual", 2 * imageWidth + 10, 20); } File output = new File(getTempDir(), "delta-" + imageName.replace(File.separatorChar, '_')); if (output.exists()) { output.delete(); } ImageIO.write(deltaImage, "PNG", output); String message = String.format("Images differ (by %.1f%%) - see details in %s", percentDifference, output); System.out.println(message); fail(message); } g.dispose(); } protected static File getTempDir() { if (System.getProperty("os.name").equals("Mac OS X")) { return new File("/tmp"); //$NON-NLS-1$ } return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ } /** * Get the location to write missing golden files to */ protected File getTargetDir() { // Set $ANDROID_SRC to point to your git AOSP working tree String sdk = System.getenv("ANDROID_SRC"); if (sdk != null) { File root = new File(sdk); if (root.exists()) { File testData = new File(root, getTestDataRelPath().replace('/', File.separatorChar)); if (testData.exists()) { return testData; } } } return null; } }