// changes, even within git versions. There is no specific version
// documentation outside the regular changelogs, though.
-#define MOVIT_VERSION 28
+#define MOVIT_VERSION 29
#endif // !defined(_MOVIT_VERSION_H)
}
register_int("needs_mipmaps", &needs_mipmaps);
+ register_uniform_mat3("inv_ycbcr_matrix", &uniform_ycbcr_matrix);
+ register_uniform_vec3("offset", uniform_offset);
+ register_uniform_vec2("cb_offset", (float *)&uniform_cb_offset);
+ register_uniform_vec2("cr_offset", (float *)&uniform_cr_offset);
}
YCbCrInput::~YCbCrInput()
void YCbCrInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
{
+ compute_ycbcr_matrix(ycbcr_format, uniform_offset, &uniform_ycbcr_matrix, type);
+
+ uniform_cb_offset.x = compute_chroma_offset(
+ ycbcr_format.cb_x_position, ycbcr_format.chroma_subsampling_x, widths[1]);
+ uniform_cb_offset.y = compute_chroma_offset(
+ ycbcr_format.cb_y_position, ycbcr_format.chroma_subsampling_y, heights[1]);
+
+ uniform_cr_offset.x = compute_chroma_offset(
+ ycbcr_format.cr_x_position, ycbcr_format.chroma_subsampling_x, widths[2]);
+ uniform_cr_offset.y = compute_chroma_offset(
+ ycbcr_format.cr_y_position, ycbcr_format.chroma_subsampling_y, heights[2]);
+
for (unsigned channel = 0; channel < num_channels; ++channel) {
glActiveTexture(GL_TEXTURE0 + *sampler_num + channel);
check_error();
string YCbCrInput::output_fragment_shader()
{
- float offset[3];
- Matrix3d ycbcr_to_rgb;
- compute_ycbcr_matrix(ycbcr_format, offset, &ycbcr_to_rgb, type);
-
string frag_shader;
- frag_shader = output_glsl_mat3("PREFIX(inv_ycbcr_matrix)", ycbcr_to_rgb);
- frag_shader += output_glsl_vec3("PREFIX(offset)", offset[0], offset[1], offset[2]);
-
- float cb_offset_x = compute_chroma_offset(
- ycbcr_format.cb_x_position, ycbcr_format.chroma_subsampling_x, widths[1]);
- float cb_offset_y = compute_chroma_offset(
- ycbcr_format.cb_y_position, ycbcr_format.chroma_subsampling_y, heights[1]);
- frag_shader += output_glsl_vec2("PREFIX(cb_offset)", cb_offset_x, cb_offset_y);
-
- float cr_offset_x = compute_chroma_offset(
- ycbcr_format.cr_x_position, ycbcr_format.chroma_subsampling_x, widths[2]);
- float cr_offset_y = compute_chroma_offset(
- ycbcr_format.cr_y_position, ycbcr_format.chroma_subsampling_y, heights[2]);
- frag_shader += output_glsl_vec2("PREFIX(cr_offset)", cr_offset_x, cr_offset_y);
-
if (ycbcr_input_splitting == YCBCR_INPUT_INTERLEAVED) {
frag_shader += "#define Y_CB_CR_SAME_TEXTURE 1\n";
} else if (ycbcr_input_splitting == YCBCR_INPUT_SPLIT_Y_AND_CBCR) {
- bool cb_cr_offsets_equal =
+ cb_cr_offsets_equal =
(fabs(ycbcr_format.cb_x_position - ycbcr_format.cr_x_position) < 1e-6) &&
(fabs(ycbcr_format.cb_y_position - ycbcr_format.cr_y_position) < 1e-6);
char buf[256];
return frag_shader;
}
+void YCbCrInput::change_ycbcr_format(const YCbCrFormat &ycbcr_format)
+{
+ if (cb_cr_offsets_equal) {
+ assert((fabs(ycbcr_format.cb_x_position - ycbcr_format.cr_x_position) < 1e-6) &&
+ (fabs(ycbcr_format.cb_y_position - ycbcr_format.cr_y_position) < 1e-6));
+ }
+ if (ycbcr_input_splitting == YCBCR_INPUT_INTERLEAVED) {
+ assert(ycbcr_format.chroma_subsampling_x == 1);
+ assert(ycbcr_format.chroma_subsampling_y == 1);
+ }
+ this->ycbcr_format = ycbcr_format;
+}
+
void YCbCrInput::invalidate_pixel_data()
{
for (unsigned channel = 0; channel < 3; ++channel) {
// uniform sampler2D PREFIX(tex_cbcr); // If CB_CR_SAME_TEXTURE.
// uniform sampler2D PREFIX(tex_cb); // If not CB_CR_SAME_TEXTURE.
// uniform sampler2D PREFIX(tex_cr); // If not CB_CR_SAME_TEXTURE.
+// uniform mat3 PREFIX(ycbcr_matrix);
+// uniform vec3 PREFIX(offset);
+// uniform vec2 PREFIX(cb_offset);
+// uniform vec2 PREFIX(cr_offset);
vec4 FUNCNAME(vec2 tc) {
// OpenGL's origin is bottom-left, but most graphics software assumes
this->owns_texture[channel] = false;
}
+ // You can change the Y'CbCr format freely, also after finalize,
+ // although with one limitation: If Cb and Cr come from the same
+ // texture and their offsets offsets are the same (ie., within 1e-6)
+ // when finalizing, they most continue to be so forever, as this
+ // optimization is compiled into the shader.
+ //
+ // If you change subsampling parameters, you'll need to call
+ // set_width() / set_height() again after this.
+ void change_ycbcr_format(const YCbCrFormat &ycbcr_format);
+
virtual void inform_added(EffectChain *chain)
{
resource_pool = chain->get_resource_pool();
GLenum type;
GLuint pbos[3], texture_num[3];
GLint uniform_tex_y, uniform_tex_cb, uniform_tex_cr;
+ Eigen::Matrix3d uniform_ycbcr_matrix;
+ float uniform_offset[3];
+ Point2D uniform_cb_offset, uniform_cr_offset;
+ bool cb_cr_offsets_equal;
unsigned width, height, widths[3], heights[3];
const unsigned char *pixel_data[3];
expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
}
+// Very similar to Rec709.
+TEST(YCbCrInputTest, ChangeFormat) {
+ const int width = 1;
+ const int height = 5;
+
+ // Pure-color test inputs, calculated with the formulas in Rec. 709
+ // page 19, items 3.4 and 3.5.
+ unsigned char y[width * height] = {
+ 16, 235, 63, 173, 32,
+ };
+ unsigned char cb[width * height] = {
+ 128, 128, 102, 42, 240,
+ };
+ unsigned char cr[width * height] = {
+ 128, 128, 240, 26, 118,
+ };
+ float expected_data[4 * width * height] = {
+ 0.0, 0.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0,
+ 1.0, 0.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0, 1.0,
+ };
+ float out_data[4 * width * height];
+
+ EffectChainTester tester(NULL, width, height);
+
+ ImageFormat format;
+ format.color_space = COLORSPACE_sRGB;
+ format.gamma_curve = GAMMA_sRGB;
+
+ // Basically all of these values will be changed after finalize.
+ YCbCrFormat initial_ycbcr_format;
+ initial_ycbcr_format.luma_coefficients = YCBCR_REC_601;
+ initial_ycbcr_format.full_range = true;
+ initial_ycbcr_format.num_levels = 1024;
+ initial_ycbcr_format.chroma_subsampling_x = 1;
+ initial_ycbcr_format.chroma_subsampling_y = 5;
+ initial_ycbcr_format.cb_x_position = 0.0f;
+ initial_ycbcr_format.cb_y_position = 0.5f;
+ initial_ycbcr_format.cr_x_position = 0.0f;
+ initial_ycbcr_format.cr_y_position = 0.5f;
+
+ YCbCrInput *input = new YCbCrInput(format, initial_ycbcr_format, width, height);
+ input->set_pixel_data(0, y);
+ input->set_pixel_data(1, cb);
+ input->set_pixel_data(2, cr);
+ tester.get_chain()->add_input(input);
+
+ tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ // Rerun with the right format.
+ YCbCrFormat ycbcr_format;
+ ycbcr_format.luma_coefficients = YCBCR_REC_709;
+ ycbcr_format.full_range = false;
+ ycbcr_format.num_levels = 256;
+ 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;
+
+ input->change_ycbcr_format(ycbcr_format);
+ input->set_width(width);
+ input->set_height(height);
+
+ tester.run(out_data, GL_RGBA, COLORSPACE_sRGB, GAMMA_sRGB);
+
+ // Y'CbCr isn't 100% accurate (the input values are rounded),
+ // so we need some leeway.
+ expect_equal(expected_data, out_data, 4 * width, height, 0.025, 0.002);
+}
+
TEST(YCbCrInputTest, Subsampling420) {
const int width = 4;
const int height = 4;