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