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