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