pixel_t get_color(array_t *image, size_t bitdepth) { uint32_t pixel_num = 0; lab_pixel_t *avg = NULL; for (size_t i = 0; i < image->size; i++) { array_t *row = ((array_t *) image->ptr); for (size_t j = 0; j < row->size; j++) { pixel_num++; pixel_t rgb = ((pixel_t *) row->ptr)[j]; xyz_pixel_t xyz = rgb_to_xyz(rgb); lab_pixel_t lab = xyz_to_lab(xyz); if (avg == NULL) { avg = malloc(sizeof(lab_pixel_t)); *avg = lab; } else { avg->L = running_avg(avg->L, lab->L, pixel_num); avg->a = running_avg(avg->a, lab->a, pixel_num); avg->b = running_avg(avg->b, lab->b, pixel_num); } } } xyz_pixel_t xyz_avg = lab_to_xyz(*avg); return xyz_to_rgb(xyz_avg); }
static void color_LinearRGB_to_Lab(struct color *c, uint8_t extra) { double R, G, B, X, Y, Z; assert(c != NULL); assert(c->type == COLOR_LINEAR_RGB); R = c->LinearRGB.R; G = c->LinearRGB.G; B = c->LinearRGB.B; // linear sRGB -> normalized XYZ (X,Y,Z are all in 0...1) X = xyz_to_lab(R * (10135552.0/23359437.0) + G * (8788810.0/23359437.0) + B * (4435075.0/23359437.0)); Y = xyz_to_lab(R * (871024.0/4096299.0) + G * (8788810.0/12288897.0) + B * (887015.0/12288897.0)); Z = xyz_to_lab(R * (158368.0/8920923.0) + G * (8788810.0/80288307.0) + B * (70074185.0/80288307.0)); // normalized XYZ -> Lab c->Lab.L = Y * 116.0 - 16.0; c->Lab.a = (X - Y) * 500.0; c->Lab.b = (Y - Z) * 200.0; c->type = COLOR_LAB; }
/* Create a PDF Lab color space corresponding to a CIEBased color space. */ static int lab_range(gs_range range_out[3] /* only [1] and [2] used */, const gs_color_space *pcs, const gs_cie_common *pciec, const gs_range *ranges, gs_memory_t *mem) { /* * Determine the range of a* and b* by evaluating the color space * mapping at all of its extrema. */ int ncomp = gs_color_space_num_components(pcs); gs_imager_state *pis; int code = gx_cie_to_xyz_alloc(&pis, pcs, mem); int i, j; if (code < 0) return code; for (j = 1; j < 3; ++j) range_out[j].rmin = 1000.0, range_out[j].rmax = -1000.0; for (i = 0; i < 1 << ncomp; ++i) { double in[4], xyz[3]; for (j = 0; j < ncomp; ++j) in[j] = (i & (1 << j) ? ranges[j].rmax : ranges[j].rmin); if (cie_to_xyz(in, xyz, pcs, pis) >= 0) { double lab[3]; xyz_to_lab(xyz, lab, pciec); for (j = 1; j < 3; ++j) { range_out[j].rmin = min(range_out[j].rmin, lab[j]); range_out[j].rmax = max(range_out[j].rmax, lab[j]); } } } gx_cie_to_xyz_free(pis); return 0; }
inline value_type rgb_to_lab(const value_type& v) { return xyz_to_lab(rgb_to_xyz(v)) + value_type(0,128,128); }
bool yee_compare(CompareArgs &args) { if ((args.image_a_->get_width() != args.image_b_->get_width()) or (args.image_a_->get_height() != args.image_b_->get_height())) { args.error_string_ = "Image dimensions do not match\n"; return false; } const auto w = args.image_a_->get_width(); const auto h = args.image_a_->get_height(); const auto dim = w * h; auto identical = true; for (auto i = 0u; i < dim; i++) { if (args.image_a_->get(i) != args.image_b_->get(i)) { identical = false; break; } } if (identical) { args.error_string_ = "Images are binary identical\n"; return true; } // Assuming colorspaces are in Adobe RGB (1998) convert to XYZ. std::vector<float> a_lum(dim); std::vector<float> b_lum(dim); std::vector<float> a_a(dim); std::vector<float> b_a(dim); std::vector<float> a_b(dim); std::vector<float> b_b(dim); if (args.verbose_) { std::cout << "Converting RGB to XYZ\n"; } const auto gamma = args.gamma_; const auto luminance = args.luminance_; #pragma omp parallel for shared(args, a_lum, b_lum, a_a, a_b, b_a, b_b) for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++) { for (auto x = 0u; x < w; x++) { const auto i = x + y * w; const auto a_color_r = powf(args.image_a_->get_red(i) / 255.0f, gamma); const auto a_color_g = powf(args.image_a_->get_green(i) / 255.0f, gamma); const auto a_color_b = powf(args.image_a_->get_blue(i) / 255.0f, gamma); float a_x; float a_y; float a_z; adobe_rgb_to_xyz(a_color_r, a_color_g, a_color_b, a_x, a_y, a_z); float l; xyz_to_lab(a_x, a_y, a_z, l, a_a[i], a_b[i]); const auto b_color_r = powf(args.image_b_->get_red(i) / 255.0f, gamma); const auto b_color_g = powf(args.image_b_->get_green(i) / 255.0f, gamma); const auto b_color_b = powf(args.image_b_->get_blue(i) / 255.0f, gamma); float b_x; float b_y; float b_z; adobe_rgb_to_xyz(b_color_r, b_color_g, b_color_b, b_x, b_y, b_z); xyz_to_lab(b_x, b_y, b_z, l, b_a[i], b_b[i]); a_lum[i] = a_y * luminance; b_lum[i] = b_y * luminance; } } if (args.verbose_) { std::cout << "Constructing Laplacian Pyramids\n"; } const LPyramid la(a_lum, w, h); const LPyramid lb(b_lum, w, h); const auto num_one_degree_pixels = to_degrees(2 * std::tan(args.field_of_view_ * to_radians(.5f))); const auto pixels_per_degree = w / num_one_degree_pixels; if (args.verbose_) { std::cout << "Performing test\n"; } const auto adaptation_level = adaptation(num_one_degree_pixels); float cpd[MAX_PYR_LEVELS]; cpd[0] = 0.5f * pixels_per_degree; for (auto i = 1u; i < MAX_PYR_LEVELS; i++) { cpd[i] = 0.5f * cpd[i - 1]; } const auto csf_max = csf(3.248f, 100.0f); static_assert(MAX_PYR_LEVELS > 2, "MAX_PYR_LEVELS must be greater than 2"); float f_freq[MAX_PYR_LEVELS - 2]; for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++) { f_freq[i] = csf_max / csf(cpd[i], 100.0f); } auto pixels_failed = 0u; auto error_sum = 0.; #pragma omp parallel for reduction(+ : pixels_failed, error_sum) \ shared(args, a_a, a_b, b_a, b_b, cpd, f_freq) for (auto y = 0; y < static_cast<ptrdiff_t>(h); y++) { for (auto x = 0u; x < w; x++) { const auto index = y * w + x; const auto adapt = std::max((la.get_value(x, y, adaptation_level) + lb.get_value(x, y, adaptation_level)) * 0.5f, 1e-5f); auto sum_contrast = 0.f; auto factor = 0.f; for (auto i = 0u; i < MAX_PYR_LEVELS - 2; i++) { const auto n1 = fabsf(la.get_value(x, y, i) - la.get_value(x, y, i + 1)); const auto n2 = fabsf(lb.get_value(x, y, i) - lb.get_value(x, y, i + 1)); const auto numerator = std::max(n1, n2); const auto d1 = fabsf(la.get_value(x, y, i + 2)); const auto d2 = fabsf(lb.get_value(x, y, i + 2)); const auto denominator = std::max(std::max(d1, d2), 1e-5f); const auto contrast = numerator / denominator; const auto f_mask = mask(contrast * csf(cpd[i], adapt)); factor += contrast * f_freq[i] * f_mask; sum_contrast += contrast; } sum_contrast = std::max(sum_contrast, 1e-5f); factor /= sum_contrast; factor = std::min(std::max(factor, 1.f), 10.f); const auto delta = fabsf(la.get_value(x, y, 0) - lb.get_value(x, y, 0)); error_sum += delta; auto pass = true; // pure luminance test if (delta > factor * tvi(adapt)) { pass = false; } if (not args.luminance_only_) { // CIE delta E test with modifications auto color_scale = args.color_factor_; // ramp down the color test in scotopic regions if (adapt < 10.0f) { // Don't do color test at all. color_scale = 0.0; } const auto da = a_a[index] - b_a[index]; const auto db = a_b[index] - b_b[index]; const auto delta_e = (da * da + db * db) * color_scale; error_sum += delta_e; if (delta_e > factor) { pass = false; } } if (not pass) { pixels_failed++; if (args.image_difference_) { args.image_difference_->set(255, 0, 0, 255, index); } } else { if (args.image_difference_) { args.image_difference_->set(0, 0, 0, 255, index); } } } } const auto error_sum_buff = std::to_string(error_sum) + " error sum\n"; const auto different = std::to_string(pixels_failed) + " pixels are different\n"; // Always output image difference if requested. if (args.image_difference_) { args.image_difference_->write_to_file(args.image_difference_->get_name()); args.error_string_ += "Wrote difference image to "; args.error_string_ += args.image_difference_->get_name(); args.error_string_ += "\n"; } if (pixels_failed < args.threshold_pixels_) { args.error_string_ = "Images are perceptually indistinguishable\n"; args.error_string_ += different; return true; } args.error_string_ = "Images are visibly different\n"; args.error_string_ += different; if (args.sum_errors_) { args.error_string_ += error_sum_buff; } return false; }