internal_run(out_data, out_data2, out_data3, GL_UNSIGNED_BYTE, format, color_space, gamma_curve, alpha_format);
}
+void EffectChainTester::run_10_10_10_2(uint32_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
+{
+ internal_run<uint32_t>(out_data, NULL, NULL, GL_UNSIGNED_INT_2_10_10_10_REV, format, color_space, gamma_curve, alpha_format);
+}
+
template<class T>
void EffectChainTester::internal_run(T *out_data, T *out_data2, T *out_data3, GLenum internal_format, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format)
{
type = GL_UNSIGNED_BYTE;
} else if (framebuffer_format == GL_RGBA16F || framebuffer_format == GL_RGBA32F) {
type = GL_FLOAT;
+ } else if (framebuffer_format == GL_RGB10_A2) {
+ type = GL_UNSIGNED_INT_2_10_10_10_REV;
} else {
// Add more here as needed.
assert(false);
check_error();
}
- if (format == GL_RGBA) {
+ if (format == GL_RGBA && sizeof(*ptr) == 1) {
vertical_flip(ptr, width * 4, height);
} else {
vertical_flip(ptr, width, height);
delete[] result_float;
}
+void expect_equal(const int *ref, const int *result, unsigned width, unsigned height, unsigned largest_difference_limit, float rms_limit)
+{
+ assert(width > 0);
+ assert(height > 0);
+
+ float *ref_float = new float[width * height];
+ float *result_float = new float[width * height];
+
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ ref_float[y * width + x] = ref[y * width + x];
+ result_float[y * width + x] = result[y * width + x];
+ }
+ }
+
+ expect_equal(ref_float, result_float, width, height, largest_difference_limit, rms_limit);
+
+ delete[] ref_float;
+ delete[] result_float;
+}
+
void test_accuracy(const float *expected, const float *result, unsigned num_values, double absolute_error_limit, double relative_error_limit, double local_relative_error_limit, double rms_limit)
{
double squared_difference = 0.0;
void run(unsigned char *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(unsigned char *out_data, unsigned char *out_data2, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void run(unsigned char *out_data, unsigned char *out_data2, unsigned char *out_data3, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ void run_10_10_10_2(uint32_t *out_data, GLenum format, Colorspace color_space, GammaCurve gamma_curve, OutputAlphaFormat alpha_format = OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
void add_output(const ImageFormat &format, OutputAlphaFormat alpha_format);
void add_ycbcr_output(const ImageFormat &format, OutputAlphaFormat alpha_format, const YCbCrFormat &ycbcr_format, YCbCrOutputSplitting output_splitting = YCBCR_OUTPUT_INTERLEAVED);
void expect_equal(const float *ref, const float *result, unsigned width, unsigned height, float largest_difference_limit = 1.5 / 255.0, float rms_limit = 0.2 / 255.0);
void expect_equal(const unsigned char *ref, const unsigned char *result, unsigned width, unsigned height, unsigned largest_difference_limit = 1, float rms_limit = 0.2);
+void expect_equal(const int *ref, const int *result, unsigned width, unsigned height, unsigned largest_difference_limit = 1, float rms_limit = 0.2);
void test_accuracy(const float *expected, const float *result, unsigned num_values, double absolute_error_limit, double relative_error_limit, double local_relative_error_limit, double rms_limit);
// Convert an sRGB encoded value (0.0 to 1.0, inclusive) to linear light.
// changes, even within git versions. There is no specific version
// documentation outside the regular changelogs, though.
-#define MOVIT_VERSION 24
+#define MOVIT_VERSION 25
#endif // !defined(_MOVIT_VERSION_H)
assert(false);
}
+ const int num_levels = ycbcr_format.num_levels;
if (ycbcr_format.full_range) {
- // TODO: Use num_levels.
- offset[0] = 0.0 / 255.0;
- offset[1] = 128.0 / 255.0;
- offset[2] = 128.0 / 255.0;
+ offset[0] = 0.0 / (num_levels - 1);
+ offset[1] = double(num_levels / 2) / (num_levels - 1); // E.g. 128/255.
+ offset[2] = double(num_levels / 2) / (num_levels - 1);
scale[0] = 1.0;
scale[1] = 1.0;
scale[2] = 1.0;
} else {
- // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 4.
- // TODO: Use num_levels.
- offset[0] = 16.0 / 255.0;
- offset[1] = 128.0 / 255.0;
- offset[2] = 128.0 / 255.0;
-
- scale[0] = 255.0 / 219.0;
- scale[1] = 255.0 / 224.0;
- scale[2] = 255.0 / 224.0;
+ // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 5.
+ // Rec. 2020 contains the most generic formulas, which we use here.
+ const double s = num_levels / 256.0; // 2^(n-8) in Rec. 2020 parlance.
+ offset[0] = (s * 16.0) / (num_levels - 1);
+ offset[1] = (s * 128.0) / (num_levels - 1);
+ offset[2] = (s * 128.0) / (num_levels - 1);
+
+ scale[0] = double(num_levels - 1) / (s * 219.0);
+ scale[1] = double(num_levels - 1) / (s * 224.0);
+ scale[2] = double(num_levels - 1) / (s * 224.0);
}
// Matrix to convert RGB to YCbCr. See e.g. Rec. 601.
// range, 10-bit goes out of range (white gets to 942), while if you select
// 10-bit range, 8-bit gets only to 234, making true white impossible.
//
-// We currently support the 8-bit ranges only, since all of our Y'CbCr
-// handling effects happen to support only 8-bit at the moment. We will need
-// to fix this eventually, though, with an added field to YCbCrFormat.
+// Thus, you will need to specify the actual precision of the Y'CbCr source
+// (or destination); the num_levels field is the right place. Most people
+// will want to simply set this to 256, as 8-bit Y'CbCr is the most common,
+// but the right value will naturally depend on your input.
#include "image_format.h"
// JPEG uses the Rec. 601 luma coefficients, but full range.
bool full_range;
- // Currently unused, but should be set to 256 for future expansion,
- // indicating 8-bit interpretation (see file-level comment).
+ // Set to 2^n for n-bit Y'CbCr (e.g. 256 for 8-bit Y'CbCr).
+ // See file-level comment.
int num_levels;
// Sampling factors for chroma components. For no subsampling (4:4:4),
} else {
uniform_clamp_range = true;
- // These limits come from BT.601 page 8, or BT.701, page 5.
- // TODO: Use num_levels. Currently we support 8-bit levels only.
- uniform_ycbcr_min[0] = 16.0 / 255.0;
- uniform_ycbcr_min[1] = 16.0 / 255.0;
- uniform_ycbcr_min[2] = 16.0 / 255.0;
- uniform_ycbcr_max[0] = 235.0 / 255.0;
- uniform_ycbcr_max[1] = 240.0 / 255.0;
- uniform_ycbcr_max[2] = 240.0 / 255.0;
+ if (ycbcr_format.num_levels == 256) { // 8-bit.
+ // These limits come from BT.601 page 8, or BT.709, page 5.
+ uniform_ycbcr_min[0] = 16.0 / 255.0;
+ uniform_ycbcr_min[1] = 16.0 / 255.0;
+ uniform_ycbcr_min[2] = 16.0 / 255.0;
+ uniform_ycbcr_max[0] = 235.0 / 255.0;
+ uniform_ycbcr_max[1] = 240.0 / 255.0;
+ uniform_ycbcr_max[2] = 240.0 / 255.0;
+ } else if (ycbcr_format.num_levels == 1024) { // 10-bit.
+ // BT.709, page 5, or BT.2020, page 6.
+ uniform_ycbcr_min[0] = 64.0 / 1023.0;
+ uniform_ycbcr_min[1] = 64.0 / 1023.0;
+ uniform_ycbcr_min[2] = 64.0 / 1023.0;
+ uniform_ycbcr_max[0] = 940.0 / 1023.0;
+ uniform_ycbcr_max[1] = 960.0 / 1023.0;
+ uniform_ycbcr_max[2] = 960.0 / 1023.0;
+ } else if (ycbcr_format.num_levels == 4096) { // 12-bit.
+ // BT.2020, page 6.
+ uniform_ycbcr_min[0] = 256.0 / 4095.0;
+ uniform_ycbcr_min[1] = 256.0 / 4095.0;
+ uniform_ycbcr_min[2] = 256.0 / 4095.0;
+ uniform_ycbcr_max[0] = 3760.0 / 4095.0;
+ uniform_ycbcr_max[1] = 3840.0 / 4095.0;
+ uniform_ycbcr_max[2] = 3840.0 / 4095.0;
+ } else {
+ assert(false);
+ }
}
}
expect_equal(cr, out_cr, width, height);
}
+TEST(YCbCrConversionEffectTest, TenBitOutput) {
+ const int width = 1;
+ const int height = 5;
+
+ // Pure-color test inputs.
+ float data[width * height * 4] = {
+ 0.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 0.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f, 1.0f,
+ };
+ uint32_t out_data[width * height];
+ int expanded_out_data[width * height * 4];
+ int expected_data[width * height * 4] = {
+ // Expected results, calculated using formulas 3.2, 3.3 and 3.4
+ // from Rec. 709. (Except the first two, which are obvious
+ // given the 64–940 range of luminance.)
+ 64, 512, 512, 3,
+ 940, 512, 512, 3,
+ 250, 409, 960, 3,
+ 691, 167, 105, 3,
+ 127, 960, 471, 3,
+ };
+
+ EffectChainTester tester(NULL, width, height, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, GL_RGB10_A2);
+ tester.add_input(data, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ ImageFormat format;
+ format.color_space = COLORSPACE_sRGB;
+ format.gamma_curve = GAMMA_sRGB;
+
+ YCbCrFormat ycbcr_format;
+ ycbcr_format.luma_coefficients = YCBCR_REC_709;
+ ycbcr_format.full_range = false;
+ ycbcr_format.num_levels = 1024;
+ ycbcr_format.chroma_subsampling_x = 1;
+ ycbcr_format.chroma_subsampling_y = 1;
+ ycbcr_format.cb_x_position = 0.5f;
+ ycbcr_format.cb_y_position = 0.5f;
+ ycbcr_format.cr_x_position = 0.5f;
+ ycbcr_format.cr_y_position = 0.5f;
+
+ tester.add_ycbcr_output(format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format);
+ tester.run_10_10_10_2(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ // Unpack 10:10:10:2 to 32:32:32:32.
+ for (unsigned i = 0; i < width * height; ++i) {
+ expanded_out_data[i * 4 + 0] = out_data[i] & 0x3ff;
+ expanded_out_data[i * 4 + 1] = (out_data[i] >> 10) & 0x3ff;
+ expanded_out_data[i * 4 + 2] = (out_data[i] >> 20) & 0x3ff;
+ expanded_out_data[i * 4 + 3] = (out_data[i] >> 30);
+ }
+ expect_equal(expected_data, expanded_out_data, 4 * width, height);
+}
+
} // namespace movit