blob: e766f633cfb95bb382ee7e786d0aa011b33e1264 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.uirendering.cts.bitmapcomparers;
import com.android.cts.uirendering.R;
import com.android.cts.uirendering.ScriptC_MSSIMComparer;
import android.content.res.Resources;
import android.graphics.Color;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.util.Log;
/**
* Image comparison using Structural Similarity Index, developed by Wang, Bovik, Sheikh, and
* Simoncelli. Details can be read in their paper :
*
* https://ece.uwaterloo.ca/~z70wang/publications/ssim.pdf
*/
public class MSSIMComparer extends BaseRenderScriptComparer {
// These values were taken from the publication
public static final String TAG_NAME = "MSSIM";
public static final double CONSTANT_L = 254;
public static final double CONSTANT_K1 = 0.00001;
public static final double CONSTANT_K2 = 0.00003;
public static final double CONSTANT_C1 = Math.pow(CONSTANT_L * CONSTANT_K1, 2);
public static final double CONSTANT_C2 = Math.pow(CONSTANT_L * CONSTANT_K2, 2);
public static final int WINDOW_SIZE = 10;
private double mThreshold;
private ScriptC_MSSIMComparer mScript;
public MSSIMComparer(double threshold) {
mThreshold = threshold;
}
@Override
public boolean verifySame(int[] ideal, int[] given, int offset, int stride, int width,
int height) {
double SSIMTotal = 0;
int windows = 0;
for (int currentWindowY = 0 ; currentWindowY < height ; currentWindowY += WINDOW_SIZE) {
for (int currentWindowX = 0 ; currentWindowX < width ; currentWindowX += WINDOW_SIZE) {
int start = indexFromXAndY(currentWindowX, currentWindowY, stride, offset);
if (isWindowWhite(ideal, start, stride) && isWindowWhite(given, start, stride)) {
continue;
}
windows++;
double[] means = getMeans(ideal, given, start, stride);
double meanX = means[0];
double meanY = means[1];
double[] variances = getVariances(ideal, given, meanX, meanY, start, stride);
double varX = variances[0];
double varY = variances[1];
double stdBoth = variances[2];
double SSIM = SSIM(meanX, meanY, varX, varY, stdBoth);
SSIMTotal += SSIM;
}
}
if (windows == 0) {
return true;
}
SSIMTotal /= windows;
Log.d(TAG_NAME, "MSSIM = " + SSIMTotal);
return (SSIMTotal >= mThreshold);
}
@Override
public boolean verifySameRowsRS(Resources resources, Allocation ideal,
Allocation given, int offset, int stride, int width, int height,
RenderScript renderScript, Allocation inputAllocation, Allocation outputAllocation) {
if (mScript == null) {
mScript = new ScriptC_MSSIMComparer(renderScript);
}
mScript.set_WIDTH(width);
mScript.set_HEIGHT(height);
//Set the bitmap allocations
mScript.set_ideal(ideal);
mScript.set_given(given);
//Call the renderscript function on each row
mScript.forEach_calcSSIM(inputAllocation, outputAllocation);
float MSSIM = sum1DFloatAllocation(outputAllocation);
MSSIM /= height;
Log.d(TAG_NAME, "MSSIM RS : " + MSSIM);
return (MSSIM >= mThreshold);
}
private boolean isWindowWhite(int[] colors, int start, int stride) {
for (int y = 0 ; y < WINDOW_SIZE ; y++) {
for (int x = 0 ; x < WINDOW_SIZE ; x++) {
if (colors[indexFromXAndY(x, y, stride, start)] != Color.WHITE) {
return false;
}
}
}
return true;
}
private double SSIM(double muX, double muY, double sigX, double sigY, double sigXY) {
double SSIM = (((2 * muX * muY) + CONSTANT_C1) * ((2 * sigXY) + CONSTANT_C2));
double denom = ((muX * muX) + (muY * muY) + CONSTANT_C1)
* (sigX + sigY + CONSTANT_C2);
SSIM /= denom;
return SSIM;
}
/**
* This method will find the mean of a window in both sets of pixels. The return is an array
* where the first double is the mean of the first set and the second double is the mean of the
* second set.
*/
private double[] getMeans(int[] pixels0, int[] pixels1, int start, int stride) {
double avg0 = 0;
double avg1 = 0;
for (int y = 0 ; y < WINDOW_SIZE ; y++) {
for (int x = 0 ; x < WINDOW_SIZE ; x++) {
int index = indexFromXAndY(x, y, stride, start);
avg0 += getIntensity(pixels0[index]);
avg1 += getIntensity(pixels1[index]);
}
}
avg0 /= WINDOW_SIZE * WINDOW_SIZE;
avg1 /= WINDOW_SIZE * WINDOW_SIZE;
return new double[] {avg0, avg1};
}
/**
* Finds the variance of the two sets of pixels, as well as the covariance of the windows. The
* return value is an array of doubles, the first is the variance of the first set of pixels,
* the second is the variance of the second set of pixels, and the third is the covariance.
*/
private double[] getVariances(int[] pixels0, int[] pixels1, double mean0, double mean1,
int start, int stride) {
double var0 = 0;
double var1 = 0;
double varBoth = 0;
for (int y = 0 ; y < WINDOW_SIZE ; y++) {
for (int x = 0 ; x < WINDOW_SIZE ; x++) {
int index = indexFromXAndY(x, y, stride, start);
double v0 = getIntensity(pixels0[index]) - mean0;
double v1 = getIntensity(pixels1[index]) - mean1;
var0 += v0 * v0;
var1 += v1 * v1;
varBoth += v0 * v1;
}
}
var0 /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
var1 /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
varBoth /= (WINDOW_SIZE * WINDOW_SIZE) - 1;
return new double[] {var0, var1, varBoth};
}
/**
* Gets the intensity of a given pixel in RGB using luminosity formula
*
* l = 0.21R' + 0.72G' + 0.07B'
*
* The prime symbols dictate a gamma correction of 1.
*/
private double getIntensity(int pixel) {
final double gamma = 1;
double l = 0;
l += (0.21f * Math.pow(Color.red(pixel) / 255f, gamma));
l += (0.72f * Math.pow(Color.green(pixel) / 255f, gamma));
l += (0.07f * Math.pow(Color.blue(pixel) / 255f, gamma));
return l;
}
}