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
17package android.theme.cts;
18
19import com.android.ddmlib.Log;
20import com.android.ddmlib.Log.LogLevel;
21
22import java.awt.Color;
23import java.awt.image.BufferedImage;
24import java.io.File;
25import java.io.IOException;
26import java.lang.String;
27import java.util.concurrent.Callable;
28
29import javax.imageio.ImageIO;
30
31/**
32 * Compares the images generated by the device with the reference images.
33 */
34public class ComparisonTask implements Callable<Boolean> {
35    private static final String TAG = "ComparisonTask";
36
37    private static final int IMAGE_THRESHOLD = 2;
38
39    private final File mExpected;
40    private final File mActual;
41
42    public ComparisonTask(File expected, File actual) {
43        mExpected = expected;
44        mActual = actual;
45    }
46
47    public Boolean call() {
48        boolean success = false;
49
50        try {
51            final BufferedImage expected = ImageIO.read(mExpected);
52            final BufferedImage actual = ImageIO.read(mActual);
53            if (compare(expected, actual, IMAGE_THRESHOLD)) {
54                success = true;
55            } else {
56                final File diff = File.createTempFile("diff_" + mExpected.getName(), ".png");
57                createDiff(expected, actual, diff);
58                Log.logAndDisplay(LogLevel.INFO, TAG, "Diff created: " + diff.getPath());
59            }
60        } catch (IOException e) {
61            Log.logAndDisplay(LogLevel.ERROR, TAG, e.toString());
62            e.printStackTrace();
63        }
64
65        return success;
66    }
67
68    /**
69     * Verifies that the pixels of reference and generated images are similar
70     * within a specified threshold.
71     *
72     * @param expected expected image
73     * @param actual actual image
74     * @param threshold maximum difference per channel
75     * @return {@code true} if the images are similar, false otherwise
76     */
77    private static int getAlphaScaledBlue(final int color) {
78        return (color & 0x000000FF) * getAlpha(color) / 255;
79    }
80
81    private static int getAlphaScaledGreen(final int color) {
82        return ((color & 0x0000FF00) >> 8) * getAlpha(color) / 255;
83    }
84
85    private static int getAlphaScaledRed(final int color) {
86        return ((color & 0x00FF0000) >> 16) * getAlpha(color) / 255;
87    }
88
89    private static int getAlpha(final int color) {
90        // use logical shift for keeping an unsigned value
91        return (color & 0xFF000000) >>> 24;
92    }
93
94    private static boolean compare(BufferedImage reference, BufferedImage generated, int threshold) {
95        final int w = generated.getWidth();
96        final int h = generated.getHeight();
97        if (w != reference.getWidth() || h != reference.getHeight()) {
98            return false;
99        }
100
101        for (int i = 0; i < w; i++) {
102            for (int j = 0; j < h; j++) {
103                final int p1 = reference.getRGB(i, j);
104                final int p2 = generated.getRGB(i, j);
105
106                final int dr = getAlphaScaledRed(p1) - getAlphaScaledRed(p2);
107                final int dg = getAlphaScaledGreen(p1) - getAlphaScaledGreen(p2);
108                final int db = getAlphaScaledBlue(p1) - getAlphaScaledBlue(p2);
109
110                if (Math.abs(db) > threshold ||
111                        Math.abs(dg) > threshold ||
112                        Math.abs(dr) > threshold) {
113                    return false;
114                }
115            }
116        }
117        return true;
118    }
119
120    private static void createDiff(BufferedImage expected, BufferedImage actual, File out)
121            throws IOException {
122        final int w1 = expected.getWidth();
123        final int h1 = expected.getHeight();
124        final int w2 = actual.getWidth();
125        final int h2 = actual.getHeight();
126        final int width = Math.max(w1, w2);
127        final int height = Math.max(h1, h2);
128
129        // The diff will contain image1, image2 and the difference between the two.
130        final BufferedImage diff = new BufferedImage(
131                width * 3, height, BufferedImage.TYPE_INT_ARGB);
132
133        for (int i = 0; i < width; i++) {
134            for (int j = 0; j < height; j++) {
135                final boolean inBounds1 = i < w1 && j < h1;
136                final boolean inBounds2 = i < w2 && j < h2;
137                int colorExpected = Color.WHITE.getRGB();
138                int colorActual = Color.WHITE.getRGB();
139                int colorDiff;
140                if (inBounds1 && inBounds2) {
141                    colorExpected = expected.getRGB(i, j);
142                    colorActual = actual.getRGB(i, j);
143                    colorDiff = colorExpected == colorActual ? colorExpected : Color.RED.getRGB();
144                } else if (inBounds1 && !inBounds2) {
145                    colorExpected = expected.getRGB(i, j);
146                    colorDiff = Color.BLUE.getRGB();
147                } else if (!inBounds1 && inBounds2) {
148                    colorActual = actual.getRGB(i, j);
149                    colorDiff = Color.GREEN.getRGB();
150                } else {
151                    colorDiff = Color.MAGENTA.getRGB();
152                }
153
154                int x = i;
155                diff.setRGB(x, j, colorExpected);
156                x += width;
157                diff.setRGB(x, j, colorActual);
158                x += width;
159                diff.setRGB(x, j, colorDiff);
160            }
161        }
162
163        ImageIO.write(diff, "png", out);
164    }
165
166}
167