From cddefd8ed9ddbe717ce858e1e1a3756909335416 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Sun, 25 Aug 2013 06:27:13 -0700 Subject: [PATCH] Add RGB <-> YCbCr conversion stuff to libaegisub --- build/libaegisub/libaegisub.vcxproj | 2 + build/libaegisub/libaegisub.vcxproj.filters | 8 +- libaegisub/Makefile | 1 + libaegisub/common/ycbcr_conv.cpp | 98 +++++++++++++++++++++ libaegisub/include/libaegisub/ycbcr_conv.h | 89 +++++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 libaegisub/common/ycbcr_conv.cpp create mode 100644 libaegisub/include/libaegisub/ycbcr_conv.h diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj index 36501fea3..030f4b70e 100644 --- a/build/libaegisub/libaegisub.vcxproj +++ b/build/libaegisub/libaegisub.vcxproj @@ -84,6 +84,7 @@ + @@ -120,6 +121,7 @@ + diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index 20ee49abd..a9e8cb417 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -191,6 +191,9 @@ Lua + + Header Files + @@ -322,10 +325,13 @@ Lua\Modules + + Source Files\Common + Header Files - + \ No newline at end of file diff --git a/libaegisub/Makefile b/libaegisub/Makefile index a1be865a1..2008256ce 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -41,6 +41,7 @@ SRC += \ common/thesaurus.cpp \ common/util.cpp \ common/vfr.cpp \ + common/ycbcr_conv.cpp \ lua/modules.cpp \ lua/modules/lfs.cpp \ lua/modules/lpeg.c \ diff --git a/libaegisub/common/ycbcr_conv.cpp b/libaegisub/common/ycbcr_conv.cpp new file mode 100644 index 000000000..a8e8894f9 --- /dev/null +++ b/libaegisub/common/ycbcr_conv.cpp @@ -0,0 +1,98 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include "libaegisub/ycbcr_conv.h" + +namespace { +double matrix_coefficients[][3] = { + {.299, .587, .114}, // BT.601 + {.2126, .7152, .0722}, // BT.709 + {.3, .59, .11}, // FCC + {.212, .701, .087}, // SMPTE 240M +}; + +void row_mult(std::array& arr, std::array values) { + size_t i = 0; + for (auto v : values) { + arr[i++] *= v; + arr[i++] *= v; + arr[i++] *= v; + } +} + +void col_mult(std::array& m, std::array v) { + m = {{ + m[0] * v[0], m[1] * v[1], m[2] * v[2], + m[3] * v[0], m[4] * v[1], m[5] * v[2], + m[6] * v[0], m[7] * v[1], m[8] * v[2], + }}; +} +} + +namespace agi { +void ycbcr_converter::init_src(ycbcr_matrix src_mat, ycbcr_range src_range) { + auto coeff = matrix_coefficients[(int)src_mat]; + double Kr = coeff[0]; + double Kg = coeff[1]; + double Kb = coeff[2]; + to_ycbcr = {{ + Kr, Kg, Kb, + -Kr/(1-Kb), -Kg/(1-Kb), 1, + 1, -Kg/(1-Kr), -Kb/(1-Kr), + }}; + + if (src_range == ycbcr_range::pc) { + row_mult(to_ycbcr, {{1., .5, .5}}); + shift_to = {{0, 128., 128.}}; + } + else { + row_mult(to_ycbcr, {{219./255., 112./255., 112./255.}}); + shift_to = {{16., 128., 128.}}; + } +} + +void ycbcr_converter::init_dst(ycbcr_matrix dst_mat, ycbcr_range dst_range) { + auto coeff = matrix_coefficients[(int)dst_mat]; + double Kr = coeff[0]; + double Kg = coeff[1]; + double Kb = coeff[2]; + from_ycbcr = {{ + 1, 0, (1-Kr), + 1, -(1-Kb)*Kb/Kg, -(1-Kr)*Kr/Kg, + 1, (1-Kb), 0, + }}; + + if (dst_range == ycbcr_range::pc) { + col_mult(from_ycbcr, {{1., 2., 2.}}); + shift_from = {{0, -128., -128.}}; + } + else { + col_mult(from_ycbcr, {{255./219., 255./112., 255./112.}}); + shift_from = {{-16., -128., -128.}}; + } +} + +ycbcr_converter::ycbcr_converter(ycbcr_matrix mat, ycbcr_range range) { + init_src(mat, range); + init_dst(mat, range); +} + +ycbcr_converter::ycbcr_converter(ycbcr_matrix src_mat, ycbcr_range src_range, ycbcr_matrix dst_mat, ycbcr_range dst_range) { + init_src(src_mat, src_range); + init_dst(dst_mat, dst_range); +} +} + diff --git a/libaegisub/include/libaegisub/ycbcr_conv.h b/libaegisub/include/libaegisub/ycbcr_conv.h new file mode 100644 index 000000000..9eeb29343 --- /dev/null +++ b/libaegisub/include/libaegisub/ycbcr_conv.h @@ -0,0 +1,89 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include +#include + +namespace agi { +enum class ycbcr_matrix { + bt601, + bt709, + fcc, + smpte_240m +}; + +enum class ycbcr_range { + tv, + pc +}; + +/// A converter between YCbCr colorspaces and RGB +class ycbcr_converter { + std::array from_ycbcr; + std::array to_ycbcr; + + std::array shift_from; + std::array shift_to; + + void init_dst(ycbcr_matrix dst_mat, ycbcr_range dst_range); + void init_src(ycbcr_matrix src_mat, ycbcr_range src_range); + + template + static std::array prod(std::array m, std::array v) { + return {{ + m[0] * v[0] + m[1] * v[1] + m[2] * v[2], + m[3] * v[0] + m[4] * v[1] + m[5] * v[2], + m[6] * v[0] + m[7] * v[1] + m[8] * v[2], + }}; + } + + template + static std::array add(std::array left, std::array right) { + return {{left[0] + right[0], left[1] + right[1], left[2] + right[2]}}; + } + + static uint8_t clamp(double v) { + auto i = static_cast(v); + i = i > 255 ? 255 : i; + return i < 0 ? 0 : i; + } + + static std::array to_uint8_t(std::array val) { + return {{clamp(val[0] + .5), clamp(val[1] + .5), clamp(val[2] + .5)}}; + } + +public: + ycbcr_converter(ycbcr_matrix mat, ycbcr_range range); + ycbcr_converter(ycbcr_matrix src_mat, ycbcr_range src_range, ycbcr_matrix dst_mat, ycbcr_range dst_range); + + /// Convert from rgb to dst_mat/dst_range + std::array rgb_to_ycbcr(std::array input) const { + return to_uint8_t(add(prod(to_ycbcr, input), shift_to)); + } + + /// Convert from src_mat/src_range to rgb + std::array ycbcr_to_rgb(std::array input) const { + return to_uint8_t(prod(from_ycbcr, add(input, shift_from))); + } + + /// Convert rgb to ycbcr using src_mat and then back using dst_mat + std::array rgb_to_rgb(std::array input) const { + return to_uint8_t(prod(from_ycbcr, + add(add(prod(to_ycbcr, input), shift_to), shift_from))); + } +}; +} +