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)));
+ }
+};
+}
+