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