bool Yee_Compare(CompareArgs &args) { if ((args.ImgA->Get_Width() != args.ImgB->Get_Width()) || (args.ImgA->Get_Height() != args.ImgB->Get_Height())) { // args.ErrorStr = "Image dimensions do not match\n"; args.PixelsFailed = 0xffffffff; return false; } int dim = args.ImgA->Get_Width() * args.ImgA->Get_Height(); bool identical = true; for (int i = 0; i < dim; i++) { if (args.ImgA->Get(i) != args.ImgB->Get(i)) { identical = false; break; } } if (identical) { // args.ErrorStr = "Images are binary identical\n"; args.PixelsFailed = 0; return true; } // assuming colorspaces are in Adobe RGB (1998) convert to XYZ float *aX = new float[dim]; float *aY = new float[dim]; float *aZ = new float[dim]; float *bX = new float[dim]; float *bY = new float[dim]; float *bZ = new float[dim]; float *aLum = new float[dim]; float *bLum = new float[dim]; float *aA = new float[dim]; float *bA = new float[dim]; float *aB = new float[dim]; float *bB = new float[dim]; if (args.Verbose) printf("Converting RGB to XYZ\n"); unsigned int x, y, w, h; w = args.ImgA->Get_Width(); h = args.ImgA->Get_Height(); for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { float r, g, b, l; int i = x + y * w; r = powf(args.ImgA->Get_Red(i) / 255.0f, args.Gamma); g = powf(args.ImgA->Get_Green(i) / 255.0f, args.Gamma); b = powf(args.ImgA->Get_Blue(i) / 255.0f, args.Gamma); AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]); XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]); r = powf(args.ImgB->Get_Red(i) / 255.0f, args.Gamma); g = powf(args.ImgB->Get_Green(i) / 255.0f, args.Gamma); b = powf(args.ImgB->Get_Blue(i) / 255.0f, args.Gamma); AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]); XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]); aLum[i] = aY[i] * args.Luminance; bLum[i] = bY[i] * args.Luminance; } } if (args.Verbose) printf("Constructing Laplacian Pyramids\n"); LPyramid *la = new LPyramid(aLum, w, h); LPyramid *lb = new LPyramid(bLum, w, h); float num_one_degree_pixels = (float) (2 * tan( args.FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI); float pixels_per_degree = w / num_one_degree_pixels; if (args.Verbose) printf("Performing test\n"); float num_pixels = 1; unsigned int adaptation_level = 0; for (int i = 0; i < MAX_PYR_LEVELS; i++) { adaptation_level = i; if (num_pixels > num_one_degree_pixels) break; num_pixels *= 2; } float cpd[MAX_PYR_LEVELS]; cpd[0] = 0.5f * pixels_per_degree; for (int i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1]; float csf_max = csf(3.248f, 100.0f); float F_freq[MAX_PYR_LEVELS - 2]; for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f); unsigned int pixels_failed = 0; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { int index = x + y * w; float contrast[MAX_PYR_LEVELS - 2]; float sum_contrast = 0; for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) { float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1)); float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1)); float numerator = (n1 > n2) ? n1 : n2; float d1 = fabsf(la->Get_Value(x,y,i+2)); float d2 = fabsf(lb->Get_Value(x,y,i+2)); float denominator = (d1 > d2) ? d1 : d2; if (denominator < 1e-5f) denominator = 1e-5f; contrast[i] = numerator / denominator; sum_contrast += contrast[i]; } if (sum_contrast < 1e-5) sum_contrast = 1e-5f; float F_mask[MAX_PYR_LEVELS - 2]; float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level); adapt *= 0.5f; if (adapt < 1e-5) adapt = 1e-5f; for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) { F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); } float factor = 0; for (int i = 0; i < MAX_PYR_LEVELS - 2; i++) { factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast; } if (factor < 1) factor = 1; if (factor > 10) factor = 10; float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0)); bool pass = true; // pure luminance test if (delta > factor * tvi(adapt)) { pass = false; } else { // CIE delta E test with modifications float color_scale = 1.0f; // ramp down the color test in scotopic regions if (adapt < 10.0f) { color_scale = 1.0f - (10.0f - color_scale) / 10.0f; color_scale = color_scale * color_scale; } float da = aA[index] - bA[index]; float db = aB[index] - bB[index]; da = da * da; db = db * db; float delta_e = (da + db) * color_scale; if (delta_e > factor) { pass = false; } } if (!pass) { pixels_failed++; // if (args.ImgDiff) { // args.ImgDiff->Set(255, 0, 0, 255, index); // } } else { // if (args.ImgDiff) { // args.ImgDiff->Set(0, 0, 0, 255, index); // } } } } if (aX) delete[] aX; if (aY) delete[] aY; if (aZ) delete[] aZ; if (bX) delete[] bX; if (bY) delete[] bY; if (bZ) delete[] bZ; if (aLum) delete[] aLum; if (bLum) delete[] bLum; if (la) delete la; if (lb) delete lb; if (aA) delete aA; if (bA) delete bA; if (aB) delete aB; if (bB) delete bB; args.PixelsFailed = pixels_failed; return true; /* if (pixels_failed < args.ThresholdPixels) { args.ErrorStr = "Images are perceptually indistinguishable\n"; return true; } char different[100]; sprintf(different, "%d pixels are different\n", pixels_failed); args.ErrorStr = "Images are visibly different\n"; args.ErrorStr += different; if (args.ImgDiff) { if (args.ImgDiff->WritePPM()) { args.ErrorStr += "Wrote difference image to "; args.ErrorStr+= args.ImgDiff->Get_Name(); args.ErrorStr += "\n"; } else { args.ErrorStr += "Could not write difference image to "; args.ErrorStr+= args.ImgDiff->Get_Name(); args.ErrorStr += "\n"; } } return false; */ }
unsigned int slg::Yee_Compare( const float *rgbA, const float *rgbB, std::vector<bool> *diff, float *tviBuffer, const unsigned int width, const unsigned int height, const bool LuminanceOnly, const float FieldOfView, const float Gamma, const float Luminance, const float ColorFactor, const unsigned int DownSample) { unsigned int i, dim; dim = width * height; bool identical = true; for (i = 0; i < 3 * dim; i++) { if (rgbA[i] != rgbB[i]) { identical = false; break; } } if (identical) { // Images are binary identical return true; } // assuming colorspaces are in Adobe RGB (1998) convert to XYZ float *aX = new float[dim]; float *aY = new float[dim]; float *aZ = new float[dim]; float *bX = new float[dim]; float *bY = new float[dim]; float *bZ = new float[dim]; float *aLum = new float[dim]; float *bLum = new float[dim]; float *aA = new float[dim]; float *bA = new float[dim]; float *aB = new float[dim]; float *bB = new float[dim]; unsigned int x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { float r, g, b, l; i = x + y * width; r = powf(rgbA[3 * i], Gamma); g = powf(rgbA[3 * i + 1], Gamma); b = powf(rgbA[3 * i + 2], Gamma); AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]); XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]); r = powf(rgbB[3 * i], Gamma); g = powf(rgbB[3 * i + 1], Gamma); b = powf(rgbB[3 * i + 2], Gamma); AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]); XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]); aLum[i] = aY[i] * Luminance; bLum[i] = bY[i] * Luminance; } } // Constructing Laplacian Pyramids LPyramid *la = new LPyramid(aLum, width, height); LPyramid *lb = new LPyramid(bLum, width, height); float num_one_degree_pixels = (float) (2 * tan(FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI); float pixels_per_degree = width / num_one_degree_pixels; // Performing test float num_pixels = 1; unsigned int adaptation_level = 0; for (i = 0; i < MAX_PYR_LEVELS; i++) { adaptation_level = i; if (num_pixels > num_one_degree_pixels) break; num_pixels *= 2; } float cpd[MAX_PYR_LEVELS]; cpd[0] = 0.5f * pixels_per_degree; for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1]; float csf_max = csf(3.248f, 100.0f); float F_freq[MAX_PYR_LEVELS - 2]; for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f); unsigned int pixels_failed = 0; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { int index = x + y * width; float contrast[MAX_PYR_LEVELS - 2]; float sum_contrast = 0; for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1)); float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1)); float numerator = (n1 > n2) ? n1 : n2; float d1 = fabsf(la->Get_Value(x,y,i+2)); float d2 = fabsf(lb->Get_Value(x,y,i+2)); float denominator = (d1 > d2) ? d1 : d2; if (denominator < 1e-5f) denominator = 1e-5f; contrast[i] = numerator / denominator; sum_contrast += contrast[i]; } if (sum_contrast < 1e-5) sum_contrast = 1e-5f; float F_mask[MAX_PYR_LEVELS - 2]; float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level); adapt *= 0.5f; if (adapt < 1e-5) adapt = 1e-5f; for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); } float factor = 0; for (i = 0; i < MAX_PYR_LEVELS - 2; i++) { factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast; } if (factor < 1) factor = 1; if (factor > 10) factor = 10; float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0)); bool pass = true; // pure luminance test const float tviValue = tvi(adapt); if (tviBuffer) tviBuffer[x + y * width] = tviValue; if (delta > factor * tviValue) { pass = false; } else if (!LuminanceOnly) { // CIE delta E test with modifications float color_scale = ColorFactor; // ramp down the color test in scotopic regions if (adapt < 10.0f) { // Don't do color test at all. color_scale = 0.0; } float da = aA[index] - bA[index]; float db = aB[index] - bB[index]; da = da * da; db = db * db; float delta_e = (da + db) * color_scale; if (delta_e > factor) { pass = false; } } if (!pass) { pixels_failed++; if (diff) (*diff)[index] = false; } else { if (diff) (*diff)[index] = true; } } } if (aX) delete[] aX; if (aY) delete[] aY; if (aZ) delete[] aZ; if (bX) delete[] bX; if (bY) delete[] bY; if (bZ) delete[] bZ; if (aLum) delete[] aLum; if (bLum) delete[] bLum; if (la) delete la; if (lb) delete lb; if (aA) delete[] aA; if (bA) delete[] bA; if (aB) delete[] aB; if (bB) delete[] bB; return pixels_failed; }