1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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 android.theme.cts; 18 19 import com.android.ddmlib.Log; 20 import com.android.ddmlib.Log.LogLevel; 21 import com.android.tradefed.util.Pair; 22 23 import java.awt.Color; 24 import java.awt.image.BufferedImage; 25 import java.io.File; 26 import java.io.IOException; 27 import java.lang.String; 28 import java.util.concurrent.Callable; 29 30 import javax.imageio.ImageIO; 31 32 /** 33 * Compares the images generated by the device with the reference images. 34 */ 35 public class ComparisonTask implements Callable<Pair<String, File>> { 36 private static final String TAG = "ComparisonTask"; 37 38 private static final int IMAGE_THRESHOLD = 8; 39 40 /** Neutral gray for blending colors. */ 41 private static final int GRAY = 0xFF808080; 42 43 /** Maximum allowable number of consecutive failed pixels. */ 44 private static final int MAX_CONSECUTIVE_FAILURES = 2; 45 46 private final String mName; 47 private final File mExpected; 48 private final File mActual; 49 ComparisonTask(String name, File expected, File actual)50 public ComparisonTask(String name, File expected, File actual) { 51 mName = name; 52 mExpected = expected; 53 mActual = actual; 54 } 55 call()56 public Pair<String, File> call() { 57 try { 58 final BufferedImage expected = ImageIO.read(mExpected); 59 final BufferedImage actual = ImageIO.read(mActual); 60 if (!compare(expected, actual, IMAGE_THRESHOLD)) { 61 final File diff = File.createTempFile("diff_" + mExpected.getName(), ".png"); 62 createDiff(expected, actual, diff); 63 return new Pair<>(mName, diff); 64 } 65 } catch (IOException e) { 66 Log.logAndDisplay(LogLevel.ERROR, TAG, e.toString()); 67 e.printStackTrace(); 68 } 69 70 return null; 71 } 72 getAlphaScaledBlue(final int color)73 private static int getAlphaScaledBlue(final int color) { 74 return (color & 0x000000FF) * getAlpha(color) / 255; 75 } 76 getAlphaScaledGreen(final int color)77 private static int getAlphaScaledGreen(final int color) { 78 return ((color & 0x0000FF00) >> 8) * getAlpha(color) / 255; 79 } 80 getAlphaScaledRed(final int color)81 private static int getAlphaScaledRed(final int color) { 82 return ((color & 0x00FF0000) >> 16) * getAlpha(color) / 255; 83 } 84 getAlpha(final int color)85 private static int getAlpha(final int color) { 86 // use logical shift for keeping an unsigned value 87 return (color & 0xFF000000) >>> 24; 88 } 89 checkNeighbors(int x, int y, BufferedImage reference, BufferedImage generated, int threshold)90 private static boolean checkNeighbors(int x, int y, BufferedImage reference, 91 BufferedImage generated, int threshold) { 92 final int w = generated.getWidth(); 93 final int h = generated.getHeight(); 94 for (int i = x - MAX_CONSECUTIVE_FAILURES; i <= x + MAX_CONSECUTIVE_FAILURES; i++) { 95 if (i >= 0 && i != x && i < w) { 96 for (int j = y - MAX_CONSECUTIVE_FAILURES; j <= y + MAX_CONSECUTIVE_FAILURES; j++) { 97 if (j >= 0 && j != y && j < h) { 98 final int p1 = reference.getRGB(i, j); 99 final int p2 = generated.getRGB(i, j); 100 101 final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2); 102 final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2); 103 final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2); 104 105 if (Math.abs(db) <= threshold && 106 Math.abs(dg) <= threshold && 107 Math.abs(dr) <= threshold) { 108 // If we find at least one matching neighbor, we assume the difference 109 // is in antialiasing. 110 return true; 111 } 112 } 113 } 114 } 115 } 116 return false; 117 } 118 119 120 /** 121 * Verifies that the pixels of reference and generated images are similar 122 * within a specified threshold. 123 * 124 * @param reference expected image 125 * @param generated actual image 126 * @param threshold maximum difference per channel 127 * @return {@code true} if the images are similar, false otherwise 128 */ compare(BufferedImage reference, BufferedImage generated, int threshold)129 private static boolean compare(BufferedImage reference, BufferedImage generated, 130 int threshold) { 131 final int w = generated.getWidth(); 132 final int h = generated.getHeight(); 133 if (w != reference.getWidth() || h != reference.getHeight()) { 134 return false; 135 } 136 137 double maxDist = 0; 138 for (int i = 0; i < w; i++) { 139 140 for (int j = 0; j < h; j++) { 141 final int p1 = reference.getRGB(i, j); 142 final int p2 = generated.getRGB(i, j); 143 144 final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2); 145 final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2); 146 final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2); 147 148 if (Math.abs(db) > threshold || 149 Math.abs(dg) > threshold || 150 Math.abs(dr) > threshold) { 151 System.err.println("fail dr=" + dr+ " dg=" + dg+ " db=" + db); 152 if (!checkNeighbors(i, j, reference, generated, threshold)) { 153 System.err.println("consecutive fail"); 154 return false; 155 } 156 } 157 } 158 } 159 return true; 160 } 161 createDiff(BufferedImage expected, BufferedImage actual, File out)162 private static void createDiff(BufferedImage expected, BufferedImage actual, File out) 163 throws IOException { 164 final int w1 = expected.getWidth(); 165 final int h1 = expected.getHeight(); 166 final int w2 = actual.getWidth(); 167 final int h2 = actual.getHeight(); 168 final int width = Math.max(w1, w2); 169 final int height = Math.max(h1, h2); 170 171 // The diff will contain image1, image2 and the difference between the two. 172 final BufferedImage diff = new BufferedImage( 173 width * 3, height, BufferedImage.TYPE_INT_ARGB); 174 175 for (int i = 0; i < width; i++) { 176 for (int j = 0; j < height; j++) { 177 final boolean inBounds1 = i < w1 && j < h1; 178 final boolean inBounds2 = i < w2 && j < h2; 179 int colorExpected = Color.WHITE.getRGB(); 180 int colorActual = Color.WHITE.getRGB(); 181 int colorDiff; 182 if (inBounds1 && inBounds2) { 183 colorExpected = expected.getRGB(i, j); 184 colorActual = actual.getRGB(i, j); 185 colorDiff = colorExpected == colorActual ? colorExpected : Color.RED.getRGB(); 186 } else if (inBounds1 && !inBounds2) { 187 colorExpected = expected.getRGB(i, j); 188 colorDiff = Color.BLUE.getRGB(); 189 } else if (!inBounds1 && inBounds2) { 190 colorActual = actual.getRGB(i, j); 191 colorDiff = Color.GREEN.getRGB(); 192 } else { 193 colorDiff = Color.MAGENTA.getRGB(); 194 } 195 196 int x = i; 197 diff.setRGB(x, j, colorExpected); 198 x += width; 199 diff.setRGB(x, j, colorActual); 200 x += width; 201 diff.setRGB(x, j, colorDiff); 202 } 203 } 204 205 ImageIO.write(diff, "png", out); 206 } 207 208 } 209