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 = 1; 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 90 91 /** 92 * Verifies that the pixels of reference and generated images are similar 93 * within a specified threshold. 94 * 95 * @param reference expected image 96 * @param generated actual image 97 * @param threshold maximum difference per channel 98 * @return {@code true} if the images are similar, false otherwise 99 */ compare(BufferedImage reference, BufferedImage generated, int threshold)100 private static boolean compare(BufferedImage reference, BufferedImage generated, 101 int threshold) { 102 final int w = generated.getWidth(); 103 final int h = generated.getHeight(); 104 if (w != reference.getWidth() || h != reference.getHeight()) { 105 return false; 106 } 107 108 double maxDist = 0; 109 for (int i = 0; i < w; i++) { 110 int consecutive = 0; 111 112 for (int j = 0; j < h; j++) { 113 final int p1 = reference.getRGB(i, j); 114 final int p2 = generated.getRGB(i, j); 115 116 final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2); 117 final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2); 118 final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2); 119 120 if (Math.abs(db) > threshold || 121 Math.abs(dg) > threshold || 122 Math.abs(dr) > threshold) { 123 System.err.println("fail dr=" + dr+ " dg=" + dg+ " db=" + db); 124 125 consecutive++; 126 127 if (consecutive > MAX_CONSECUTIVE_FAILURES) { 128 System.err.println("consecutive fail"); 129 return false; 130 } 131 } else { 132 consecutive = 0; 133 } 134 } 135 } 136 return true; 137 } 138 createDiff(BufferedImage expected, BufferedImage actual, File out)139 private static void createDiff(BufferedImage expected, BufferedImage actual, File out) 140 throws IOException { 141 final int w1 = expected.getWidth(); 142 final int h1 = expected.getHeight(); 143 final int w2 = actual.getWidth(); 144 final int h2 = actual.getHeight(); 145 final int width = Math.max(w1, w2); 146 final int height = Math.max(h1, h2); 147 148 // The diff will contain image1, image2 and the difference between the two. 149 final BufferedImage diff = new BufferedImage( 150 width * 3, height, BufferedImage.TYPE_INT_ARGB); 151 152 for (int i = 0; i < width; i++) { 153 for (int j = 0; j < height; j++) { 154 final boolean inBounds1 = i < w1 && j < h1; 155 final boolean inBounds2 = i < w2 && j < h2; 156 int colorExpected = Color.WHITE.getRGB(); 157 int colorActual = Color.WHITE.getRGB(); 158 int colorDiff; 159 if (inBounds1 && inBounds2) { 160 colorExpected = expected.getRGB(i, j); 161 colorActual = actual.getRGB(i, j); 162 colorDiff = colorExpected == colorActual ? colorExpected : Color.RED.getRGB(); 163 } else if (inBounds1 && !inBounds2) { 164 colorExpected = expected.getRGB(i, j); 165 colorDiff = Color.BLUE.getRGB(); 166 } else if (!inBounds1 && inBounds2) { 167 colorActual = actual.getRGB(i, j); 168 colorDiff = Color.GREEN.getRGB(); 169 } else { 170 colorDiff = Color.MAGENTA.getRGB(); 171 } 172 173 int x = i; 174 diff.setRGB(x, j, colorExpected); 175 x += width; 176 diff.setRGB(x, j, colorActual); 177 x += width; 178 diff.setRGB(x, j, colorDiff); 179 } 180 } 181 182 ImageIO.write(diff, "png", out); 183 } 184 185 } 186