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 package android.uirendering.cts.bitmapcomparers;
17 
18 import android.graphics.Color;
19 import android.util.Log;
20 
21 /**
22  * Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
23  * Simoncelli. Details can be read in their paper :
24  *
25  * https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
26  */
27 public class MSSIMComparer extends BitmapComparer {
28     // These values were taken from the publication
29     public static final String TAG_NAME = "MSSIM";
30     public static final double CONSTANT_L = 254;
31     public static final double CONSTANT_K1 = 0.00001;
32     public static final double CONSTANT_K2 = 0.00003;
33     public static final double CONSTANT_C1 = Math.pow(CONSTANT_L * CONSTANT_K1, 2);
34     public static final double CONSTANT_C2 = Math.pow(CONSTANT_L * CONSTANT_K2, 2);
35     public static final int WINDOW_SIZE = 10;
36 
37     private double mThreshold;
38 
MSSIMComparer(double threshold)39     public MSSIMComparer(double threshold) {
40         mThreshold = threshold;
41     }
42 
43     @Override
verifySame(int[] ideal, int[] given, int offset, int stride, int width, int height)44     public boolean verifySame(int[] ideal, int[] given, int offset, int stride, int width,
45             int height) {
46         double SSIMTotal = 0;
47         int windows = 0;
48 
49         for (int currentWindowY = 0 ; currentWindowY < height ; currentWindowY += WINDOW_SIZE) {
50             for (int currentWindowX = 0 ; currentWindowX < width ; currentWindowX += WINDOW_SIZE) {
51                 int start = indexFromXAndY(currentWindowX, currentWindowY, stride, offset);
52                 if (isWindowWhite(ideal, start, stride) && isWindowWhite(given, start, stride)) {
53                     continue;
54                 }
55                 windows++;
56                 double[] means = getMeans(ideal, given, start, stride);
57                 double meanX = means[0];
58                 double meanY = means[1];
59                 double[] variances = getVariances(ideal, given, meanX, meanY, start, stride);
60                 double varX = variances[0];
61                 double varY = variances[1];
62                 double stdBoth = variances[2];
63                 double SSIM = SSIM(meanX, meanY, varX, varY, stdBoth);
64                 SSIMTotal += SSIM;
65             }
66         }
67 
68         if (windows == 0) {
69             return true;
70         }
71 
72         SSIMTotal /= windows;
73 
74         Log.d(TAG_NAME, "MSSIM = " + SSIMTotal);
75 
76         return (SSIMTotal >= mThreshold);
77     }
78 
isWindowWhite(int[] colors, int start, int stride)79     private boolean isWindowWhite(int[] colors, int start, int stride) {
80         for (int y = 0 ; y < WINDOW_SIZE ; y++) {
81             for (int x = 0 ; x < WINDOW_SIZE ; x++) {
82                 if (colors[indexFromXAndY(x, y, stride, start)] != Color.WHITE) {
83                     return false;
84                 }
85             }
86         }
87         return true;
88     }
89 
SSIM(double muX, double muY, double sigX, double sigY, double sigXY)90     private double SSIM(double muX, double muY, double sigX, double sigY, double sigXY) {
91         double SSIM = (((2 * muX * muY) + CONSTANT_C1) * ((2 * sigXY) + CONSTANT_C2));
92         double denom = ((muX * muX) + (muY * muY) + CONSTANT_C1)
93                 * (sigX + sigY + CONSTANT_C2);
94         SSIM /= denom;
95         return SSIM;
96     }
97 
98 
99     /**
100      * This method will find the mean of a window in both sets of pixels. The return is an array
101      * where the first double is the mean of the first set and the second double is the mean of the
102      * second set.
103      */
getMeans(int[] pixels0, int[] pixels1, int start, int stride)104     private double[] getMeans(int[] pixels0, int[] pixels1, int start, int stride) {
105         double avg0 = 0;
106         double avg1 = 0;
107         for (int y = 0 ; y < WINDOW_SIZE ; y++) {
108             for (int x = 0 ; x < WINDOW_SIZE ; x++) {
109                 int index = indexFromXAndY(x, y, stride, start);
110                 avg0 += getIntensity(pixels0[index]);
111                 avg1 += getIntensity(pixels1[index]);
112             }
113         }
114         avg0 /= WINDOW_SIZE * WINDOW_SIZE;
115         avg1 /= WINDOW_SIZE * WINDOW_SIZE;
116         return new double[] {avg0, avg1};
117     }
118 
119     /**
120      * Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
121      * return value is an array of doubles, the first is the variance of the first set of pixels,
122      * the second is the variance of the second set of pixels, and the third is the covariance.
123      */
getVariances(int[] pixels0, int[] pixels1, double mean0, double mean1, int start, int stride)124     private double[] getVariances(int[] pixels0, int[] pixels1, double mean0, double mean1,
125             int start, int stride) {
126         double var0 = 0;
127         double var1 = 0;
128         double varBoth = 0;
129         for (int y = 0 ; y < WINDOW_SIZE ; y++) {
130             for (int x = 0 ; x < WINDOW_SIZE ; x++) {
131                 int index = indexFromXAndY(x, y, stride, start);
132                 double v0 = getIntensity(pixels0[index]) - mean0;
133                 double v1 = getIntensity(pixels1[index]) - mean1;
134                 var0 += v0 * v0;
135                 var1 += v1 * v1;
136                 varBoth += v0 * v1;
137             }
138         }
139         var0 /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
140         var1 /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
141         varBoth /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
142         return new double[] {var0, var1, varBoth};
143     }
144 
145     /**
146      * Gets the intensity of a given pixel in RGB using luminosity formula
147      *
148      * l = 0.21R' + 0.72G' + 0.07B'
149      *
150      * The prime symbols dictate a gamma correction of 1.
151      */
getIntensity(int pixel)152     private double getIntensity(int pixel) {
153         final double gamma = 1;
154         double l = 0;
155         l += (0.21f * Math.pow(Color.red(pixel) / 255f, gamma));
156         l += (0.72f * Math.pow(Color.green(pixel) / 255f, gamma));
157         l += (0.07f * Math.pow(Color.blue(pixel) / 255f, gamma));
158         return l;
159     }
160 }
161