Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(101)

Unified Diff: media/base/video_color_space.cc

Issue 2088273003: Video Color Managament (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: bugfix Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/base/video_color_space.cc
diff --git a/media/base/video_color_space.cc b/media/base/video_color_space.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cfc8085739920eb6533a154c6d0af4e87c5864e1
--- /dev/null
+++ b/media/base/video_color_space.cc
@@ -0,0 +1,879 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/video_color_space.h"
+
+#include "media/base/video_frame.h"
+#include "media/base/video_frame_metadata.h"
+
+namespace media {
+
+VideoColorSpace::VideoColorSpace()
+ : primaries(PRI_UNSPECIFIED),
+ transfer(TRC_UNSPECIFIED),
+ matrix(SPC_UNSPECIFIED),
+ full_range(false) {}
+
+VideoColorSpace::VideoColorSpace(PrimaryID primaries_,
+ TransferID transfer_,
+ MatrixID matrix_,
+ bool full_range_)
+ : primaries(primaries_),
+ transfer(transfer_),
+ matrix(matrix_),
+ full_range(full_range_) {}
+
+VideoColorSpace::VideoColorSpace(const scoped_refptr<VideoFrame>& frame) {
+ int primaries_tmp;
+ int transfer_tmp;
+ int matrix_tmp;
+ bool full_range_tmp;
+ if (frame->metadata()->GetInteger(VideoFrameMetadata::COLOR_SPACE_PRIMARIES,
+ &primaries_tmp) &&
+ frame->metadata()->GetInteger(VideoFrameMetadata::COLOR_SPACE_TRANSFER,
+ &transfer_tmp) &&
+ frame->metadata()->GetInteger(VideoFrameMetadata::COLOR_SPACE_MATRIX,
+ &matrix_tmp) &&
+ frame->metadata()->GetBoolean(VideoFrameMetadata::COLOR_SPACE_FULL_RANGE,
+ &full_range_tmp)) {
+ primaries = static_cast<PrimaryID>(primaries_tmp);
+ transfer = static_cast<TransferID>(transfer_tmp);
+ matrix = static_cast<MatrixID>(matrix_tmp);
+ full_range = full_range_tmp;
+ }
+ int color_space_tmp;
+ if (frame->metadata()->GetInteger(VideoFrameMetadata::COLOR_SPACE,
+ &color_space_tmp)) {
+ VideoColorSpace tmp(static_cast<ColorSpace>(color_space_tmp));
+ *this = tmp;
+ return;
+ }
+ primaries = PRI_UNSPECIFIED;
+ transfer = TRC_UNSPECIFIED;
+ matrix = SPC_UNSPECIFIED;
+ full_range = false;
+}
+
+VideoColorSpace::VideoColorSpace(ColorSpace color_space) {
+ switch (color_space) {
+ case COLOR_SPACE_UNSPECIFIED:
+ primaries = PRI_UNSPECIFIED;
+ transfer = TRC_UNSPECIFIED;
+ matrix = SPC_UNSPECIFIED;
+ full_range = false;
+ break;
+
+ case COLOR_SPACE_JPEG:
+ // TODO(hubbe): Check that these are right.
+ primaries = PRI_BT709;
+ transfer = TRC_IEC61966_2_1;
+ matrix = SPC_BT709;
+ full_range = true;
+ break;
+
+ case COLOR_SPACE_HD_REC709:
+ primaries = PRI_BT709;
+ transfer = TRC_BT709;
+ matrix = SPC_BT709;
+ full_range = false;
+ break;
+
+ case COLOR_SPACE_SD_REC601:
+ primaries = PRI_SMPTE170M;
+ transfer = TRC_SMPTE170M;
+ matrix = SPC_SMPTE170M;
+ full_range = false;
+ break;
+ }
+}
+
+void VideoColorSpace::SetVideoFrameColorSpace(
+ const scoped_refptr<VideoFrame>& frame) {
+ frame->metadata()->SetInteger(VideoFrameMetadata::COLOR_SPACE_PRIMARIES,
+ primaries);
+ frame->metadata()->SetInteger(VideoFrameMetadata::COLOR_SPACE_TRANSFER,
+ transfer);
+ frame->metadata()->SetInteger(VideoFrameMetadata::COLOR_SPACE_MATRIX, matrix);
+ frame->metadata()->SetBoolean(VideoFrameMetadata::COLOR_SPACE_FULL_RANGE,
+ full_range);
+}
+
+bool VideoColorSpace::operator==(const VideoColorSpace& other) const {
+ return primaries == other.primaries && transfer == other.transfer &&
+ matrix == other.matrix && full_range == other.full_range;
+}
+
+bool VideoColorSpace::operator<(const VideoColorSpace& other) const {
+ if (primaries < other.primaries)
+ return true;
+ if (primaries > other.primaries)
+ return false;
+ if (transfer < other.transfer)
+ return true;
+ if (transfer > other.transfer)
+ return false;
+ if (matrix < other.matrix)
+ return true;
+ if (matrix > other.matrix)
+ return false;
+ return full_range < other.full_range;
+}
+
+VideoColorSpace VideoColorSpace::XYZ() {
+ VideoColorSpace ret;
+ ret.primaries = PRI_XYZ_D50;
+ ret.transfer = TRC_LINEAR;
+ ret.matrix = SPC_RGB;
+ ret.full_range = true;
+ return ret;
+}
+
+VideoColorSpace VideoColorSpace::SRGB() {
+ VideoColorSpace ret;
+ ret.primaries = PRI_BT709;
+ ret.transfer = TRC_IEC61966_2_1;
+ ret.matrix = SPC_RGB;
+ ret.full_range = true;
+ return ret;
+}
+
+std::vector<VideoColorSpace::XY> VideoColorSpace::GetPrimaries(PrimaryID id) {
+ std::vector<XY> ret(4);
+ switch (id) {
+ default:
+ // If we don't know, assume BT709
+
+ case PRI_BT709:
+ // Red
+ ret[0].first = 0.640;
+ ret[0].second = 0.330;
+ // Green
+ ret[1].first = 0.300;
+ ret[1].second = 0.600;
+ // Blue
+ ret[2].first = 0.150;
+ ret[2].second = 0.060;
+ // Whitepoint (D65)
+ ret[3].first = 0.3127;
+ ret[3].second = 0.3290;
+ break;
+
+ case PRI_BT470M:
+ // Red
+ ret[0].first = 0.67;
+ ret[0].second = 0.33;
+ // Green
+ ret[1].first = 0.21;
+ ret[1].second = 0.71;
+ // Blue
+ ret[2].first = 0.14;
+ ret[2].second = 0.08;
+ // Whitepoint
+ ret[3].first = 0.31;
+ ret[3].second = 0.316;
+ break;
+
+ case PRI_BT470BG:
+ // Red
+ ret[0].first = 0.64;
+ ret[0].second = 0.33;
+ // Green
+ ret[1].first = 0.29;
+ ret[1].second = 0.60;
+ // Blue
+ ret[2].first = 0.15;
+ ret[2].second = 0.06;
+ // Whitepoint (D65)
+ ret[3].first = 0.3127;
+ ret[3].second = 0.3290;
+ break;
+
+ case PRI_SMPTE170M:
+ case PRI_SMPTE240M:
+ // Red
+ ret[0].first = 0.630;
+ ret[0].second = 0.340;
+ // Green
+ ret[1].first = 0.310;
+ ret[1].second = 0.595;
+ // Blue
+ ret[2].first = 0.155;
+ ret[2].second = 0.070;
+ // Whitepoint (D65)
+ ret[3].first = 0.3127;
+ ret[3].second = 0.3290;
+ break;
+
+ case PRI_FILM:
+ // Red
+ ret[0].first = 0.681;
+ ret[0].second = 0.319;
+ // Green
+ ret[1].first = 0.243;
+ ret[1].second = 0.692;
+ // Blue
+ ret[2].first = 0.145;
+ ret[2].second = 0.049;
+ // Whitepoint (C)
+ ret[3].first = 0.310;
+ ret[3].second = 0.136;
+ break;
+
+ case PRI_BT2020:
+ // Red
+ ret[0].first = 0.708;
+ ret[0].second = 0.292;
+ // Green
+ ret[1].first = 0.170;
+ ret[1].second = 0.797;
+ // Blue
+ ret[2].first = 0.131;
+ ret[2].second = 0.046;
+ // Whitepoint (D65)
+ ret[3].first = 0.3127;
+ ret[3].second = 0.3290;
+ break;
+
+ case PRI_SMPTEST428_1:
+ // X
+ ret[0].first = 1.0;
+ ret[0].second = 0.0;
+ // Y
+ ret[1].first = 0.0;
+ ret[1].second = 1.0;
+ // Z
+ ret[2].first = 0.0;
+ ret[2].second = 0.0;
+ // Whitepoint (E)
+ ret[3].first = 1.0f / 3.0f;
+ ret[3].second = 1.0f / 3.0f;
+ break;
+
+ case PRI_SMPTEST431_2:
+ // Red
+ ret[0].first = 0.680;
+ ret[0].second = 0.320;
+ // Green
+ ret[1].first = 0.265;
+ ret[1].second = 0.690;
+ // Blue
+ ret[2].first = 0.150;
+ ret[2].second = 0.060;
+ // Whitepoint
+ ret[3].first = 0.314;
+ ret[3].second = 0.351;
+ break;
+
+ case PRI_SMPTEST432_1:
+ // Red
+ ret[0].first = 0.680;
+ ret[0].second = 0.320;
+ // Green
+ ret[1].first = 0.265;
+ ret[1].second = 0.690;
+ // Blue
+ ret[2].first = 0.150;
+ ret[2].second = 0.060;
+ // Whitepoint (D65)
+ ret[3].first = 0.3127;
+ ret[3].second = 0.3290;
+ break;
+
+ case PRI_XYZ_D50:
+ // X
+ ret[0].first = 1.0;
+ ret[0].second = 0.0;
+ // Y
+ ret[1].first = 0.0;
+ ret[1].second = 1.0;
+ // Z
+ ret[2].first = 0.0;
+ ret[2].second = 0.0;
+ // D50
+ ret[3].first = 0.34567;
+ ret[3].second = 0.35850;
+ break;
+ }
+ return ret;
+}
+
+float VideoColorSpace::fromLinear(TransferID id, float v) {
+ switch (id) {
+ default:
+ case TRC_BT709:
+ case TRC_SMPTE170M:
+ case TRC_BT2020_10:
+ case TRC_BT2020_12: {
+ v = fmax(0.0f, v);
+ float a = 1.099296826809442f;
+ float b = 0.018053968510807;
+ if (v <= b) {
+ return 4.5f * v;
+ } else {
+ return a * pow(v, 0.45f) - (a - 1.0f);
+ }
+ }
+
+ case TRC_GAMMA22:
+ v = fmax(0.0f, v);
+ return pow(v, 1.0f / 2.2f);
+
+ case TRC_GAMMA28:
+ v = fmax(0.0f, v);
+ return pow(v, 1.0f / 2.8f);
+
+ case TRC_SMPTE240M: {
+ v = fmax(0.0f, v);
+ float a = 1.11157219592173128753f;
+ float b = 0.02282158552944503135f;
+ if (v <= b) {
+ return 4.0f * v;
+ } else {
+ return a * pow(v, 0.45f) - (a - 1.0f);
+ }
+ }
+
+ case TRC_LINEAR:
+ return v;
+
+ case TRC_LOG:
+ if (v < 0.01)
+ return 0.0;
+ return 1.0 + log(v) / log(10.0f) / 2.0f;
+
+ case TRC_LOG_SQRT:
+ if (v < sqrt(10.0f) / 1000.0)
+ return 0.0;
+ return 1.0 + log(v) / log(10.0f) / 2.5f;
+
+ case TRC_IEC61966_2_4: {
+ float a = 1.099296826809442f;
+ float b = 0.018053968510807f;
+ if (v < -b) {
+ return -a * pow(-v, 0.45) + (a - 1.0f);
+ } else if (v <= b) {
+ return 4.5 * v;
+ } else {
+ return a * pow(v, 0.45) - (a - 1.0f);
+ }
+ }
+
+ case TRC_BT1361_ECG: {
+ float a = 1.099;
+ float b = 0.018;
+ float l = 0.0045;
+ if (v < -l) {
+ return -(a * pow(-4.0f * v, 0.45) + (a - 1.0f)) / 4.0f;
+ } else if (v <= b) {
+ return 4.5 * v;
+ } else {
+ return a * pow(v, 0.45) - (a - 1.0f);
+ }
+ }
+
+ case TRC_IEC61966_2_1: { // SRGB
+ v = fmax(0.0f, v);
+ float a = 1.055f;
+ float b = 0.0031308f;
+ if (v < b) {
+ return 12.92f * v;
+ } else {
+ return a * pow(v, 1.0f / 2.4f) - (a - 1.0f);
+ }
+ }
+ case TRC_SMPTEST2084: {
+ v = fmax(0.0f, v);
+ float m1 = (2610.0 / 4096.0) / 4.0;
+ float m2 = (2523.0 / 4096.0) * 128.0;
+ float c1 = 3424.0 / 4096.0;
+ float c2 = (2413.0 / 4096.0) * 32.0;
+ float c3 = (2392.0 / 4096.0) * 32.0;
+ return pow((c1 + c2 * pow(v, m1)) / (1.0f + c3 * pow(v, m1)), m2);
+ }
+
+ case TRC_SMPTEST428_1:
+ v = fmax(0.0f, v);
+ return pow(48.0f * v + 52.37f, 1.0f / 2.6f);
+
+ // Chrome-specific values below
+ case TRC_GAMMA24:
+ v = fmax(0.0f, v);
+ return pow(v, 1.0f / 2.4f);
+ }
+}
+
+float VideoColorSpace::toLinear(TransferID id, float v) {
+ switch (id) {
+ default:
+ case TRC_BT709:
+ case TRC_SMPTE170M:
+ case TRC_BT2020_10:
+ case TRC_BT2020_12: {
+ v = fmax(0.0f, v);
+ float a = 1.099296826809442f;
+ float b = 0.018053968510807;
+ if (v < fromLinear(TRC_BT709, b)) {
+ return v / 4.5f;
+ } else {
+ return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
+ }
+ }
+
+ case TRC_GAMMA22:
+ v = fmax(0.0f, v);
+ return pow(v, 2.2f);
+
+ case TRC_GAMMA28:
+ v = fmax(0.0f, v);
+ return pow(v, 2.8f);
+
+ case TRC_SMPTE240M: {
+ v = fmax(0.0f, v);
+ float a = 1.11157219592173128753f;
+ float b = 0.02282158552944503135f;
+ if (v <= fromLinear(TRC_SMPTE240M, b)) {
+ return v / 4.0f;
+ } else {
+ return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
+ }
+ }
+
+ case TRC_LINEAR:
+ return v;
+
+ case TRC_LOG:
+ if (v < 0.0)
+ return 0.0;
+ return pow(10.0, (v - 1.0f) * 2.0f);
+
+ case TRC_LOG_SQRT:
+ if (v < 0.0)
+ return 0.0;
+ return pow(10.0, (v - 1.0f) * 2.5f);
+
+ case TRC_IEC61966_2_4: {
+ float a = 1.099296826809442f;
+ float b = 0.018053968510807f;
+ if (v < fromLinear(TRC_IEC61966_2_4, -a)) {
+ return -pow((a - 1.0f - v) / a, 1.0f / 0.45f);
+ } else if (v <= fromLinear(TRC_IEC61966_2_4, b)) {
+ return v / 4.5f;
+ } else {
+ return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
+ }
+ }
+
+ case TRC_BT1361_ECG: {
+ float a = 1.099;
+ float b = 0.018;
+ float l = 0.0045;
+ if (v < fromLinear(TRC_BT1361_ECG, -l)) {
+ return -pow((1.0f - a - v * 4.0) / a, 1.0f / 0.45f) / 4.0f;
+ } else if (v <= fromLinear(TRC_BT1361_ECG, b)) {
+ return v / 4.5f;
+ } else {
+ return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
+ }
+ }
+
+ case TRC_IEC61966_2_1: { // SRGB
+ v = fmax(0.0f, v);
+ float a = 1.055f;
+ float b = 0.0031308f;
+ if (v < fromLinear(TRC_IEC61966_2_1, b)) {
+ return v / 12.92f;
+ } else {
+ return pow((v + a - 1.0f) / a, 2.4f);
+ }
+ }
+
+ case TRC_SMPTEST2084: {
+ v = fmax(0.0f, v);
+ float m1 = (2610.0f / 4096.0f) / 4.0f;
+ float m2 = (2523.0f / 4096.0f) * 128.0f;
+ float c1 = 3424.0f / 4096.0f;
+ float c2 = (2413.0f / 4096.0f) * 32.0f;
+ float c3 = (2392.0f / 4096.0f) * 32.0f;
+ return pow(fmax(pow(v, 1.0 / m2) - c1, 0) / (c2 - c3 * pow(v, 1.0 / m2)),
+ 1.0f / m1);
+ }
+
+ case TRC_SMPTEST428_1:
+ return (pow(v, 2.6f) - 52.37f) / 48.0f;
+
+ // Chrome-specific values below
+ case TRC_GAMMA24:
+ v = fmax(0.0f, v);
+ return pow(v, 2.4f);
+ }
+}
+
+VideoColorSpace::Matrix4x4 VideoColorSpace::GetTransferMatrix(MatrixID id) {
+ Matrix4x4 ret;
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ ret.rows[i][j] = 0.0f;
+
+ float Kr, Kb;
+ switch (id) {
+ case SPC_RGB:
+ ret.rows[0][0] = 1.0;
+ ret.rows[1][1] = 1.0;
+ ret.rows[2][2] = 1.0;
+ ret.rows[3][3] = 1.0;
+ return ret;
+
+ case SPC_BT709:
+ case SPC_UNSPECIFIED:
+ case SPC_RESERVED:
+ Kr = 0.2126f;
+ Kb = 0.0722f;
+ break;
+
+ case SPC_FCC:
+ Kr = 0.30f;
+ Kb = 0.11f;
+ break;
+
+ case SPC_BT470BG:
+ case SPC_SMPTE170M:
+ Kr = 0.299f;
+ Kb = 0.144f;
+ break;
+
+ case SPC_SMPTE240M:
+ Kr = 0.212f;
+ Kb = 0.087f;
+ break;
+
+ case SPC_YCOCG:
+ ret.rows[0][0] = 0.25f;
+ ret.rows[0][1] = 0.5f;
+ ret.rows[0][2] = 0.25f;
+ ret.rows[0][3] = 0.5;
+ ret.rows[1][0] = -0.25f;
+ ret.rows[1][1] = 0.5f;
+ ret.rows[1][2] = -0.25f;
+ ret.rows[1][3] = 0.5;
+ ret.rows[2][0] = 0.5f;
+ ret.rows[2][1] = 0.0f;
+ ret.rows[2][2] = -0.5f;
+ ret.rows[3][3] = 1.0f;
+ return ret;
+
+ // TODO(hubbe): Check if the CL equation is right.
+ case SPC_BT2020_NCL:
+ case SPC_BT2020_CL:
+ Kr = 0.2627f;
+ Kb = 0.0593f;
+ break;
+
+ case SPC_YDZDX:
+ ret.rows[0][1] = 1.0f;
+ ret.rows[1][1] = -0.5f;
+ ret.rows[1][2] = 0.986566f / 2.0f;
+ ret.rows[1][3] = 0.5f;
+ ret.rows[2][0] = 0.5f;
+ ret.rows[2][1] = -0.991902f / 2.0f;
+ ret.rows[2][3] = 0.5f;
+ ret.rows[3][3] = 1.0f;
+ return ret;
+ }
+ ret.rows[0][0] = Kr;
+ ret.rows[0][1] = 1.0f - Kr - Kb;
+ ret.rows[0][2] = Kb;
+
+ float u_mult = 0.5f / (1.0f - Kb);
+ ret.rows[1][0] = u_mult * -Kr;
+ ret.rows[1][1] = u_mult * -(1.0f - Kr - Kb);
+ ret.rows[1][2] = u_mult * (1.0f - Kb);
+ ret.rows[1][3] = 0.5f;
+
+ float v_mult = 0.5f / (1.0f - Kr);
+ ret.rows[2][0] = v_mult * (1.0f - Kr);
+ ret.rows[2][1] = v_mult * -(1.0f - Kr - Kb);
+ ret.rows[2][2] = v_mult * -Kb;
+ ret.rows[2][3] = 0.5f;
+
+ ret.rows[3][3] = 1.0f;
+ return ret;
+}
+
+bool VideoColorSpace::GetRangeAdjust(TriStim* offset,
+ TriStim* multiplier) const {
+ if (full_range) {
+ *offset = TriStim(0.0f, 0.0f, 0.0f);
+ *multiplier = TriStim(1.0f, 1.0f, 1.0f);
+ return false;
+ }
+ switch (matrix) {
+ case SPC_RGB:
+ case SPC_YCOCG:
+ *offset = TriStim(16.0f / 255.0f, 16.0f / 255.0f, 16.0f / 255.0f);
+ *multiplier = TriStim(255.0f / 219.0f, 255.0f / 219.0f, 255.0f / 219.0f);
+ return true;
+
+ default:
+ // Note: offset of 15.5 is needed to make sure that 128 is decoded as 0.5.
+ *offset = TriStim(16.0f / 255.0f, 15.5f / 255.0f, 15.5f / 255.0f);
+ *multiplier = TriStim(255.0f / 219.0f, 255.0f / 224.0f, 255.0f / 224.0f);
+ return true;
+ }
+}
+
+VideoColorSpace::Matrix4x4 VideoColorSpace::GetRangeAdjustMatrix() const {
+ TriStim offset, multiplier;
+ GetRangeAdjust(&offset, &multiplier);
+ Matrix4x4 ret;
+ for (int y = 0; y < 3; y++) {
+ for (int x = 0; x < 3; x++) {
+ ret.rows[y][x] = x == y ? multiplier.values[y] : 0.0f;
+ }
+ ret.rows[y][3] = -offset.values[y] * multiplier.values[y];
+ ret.rows[3][y] = 0.0f;
+ }
+ ret.rows[3][3] = 1.0f;
+ return ret;
+}
+
+VideoColorSpace::Matrix4x4 VideoColorSpace::Multiply(const Matrix4x4& a,
+ const Matrix4x4& b) {
+ Matrix4x4 ret;
+ for (int x = 0; x < 4; x++) {
+ for (int y = 0; y < 4; y++) {
+ float sum = 0.0;
+ for (int z = 0; z < 4; z++) {
+ sum += a.rows[z][x] * b.rows[y][z];
+ }
+ ret.rows[y][x] = sum;
+ }
+ }
+ return ret;
+}
+
+VideoColorSpace::TriStim VideoColorSpace::Multiply(const Matrix4x4& a,
+ const TriStim& b) {
+ TriStim ret;
+ ret.values[0] = a.rows[0][0] * b.values[0] + a.rows[0][1] * b.values[1] +
+ a.rows[0][2] * b.values[2] + a.rows[0][3];
+ ret.values[1] = a.rows[1][0] * b.values[0] + a.rows[1][1] * b.values[1] +
+ a.rows[1][2] * b.values[2] + a.rows[1][3];
+ ret.values[2] = a.rows[2][0] * b.values[0] + a.rows[2][1] * b.values[1] +
+ a.rows[2][2] * b.values[2] + a.rows[2][3];
+ return ret;
+}
+
+// Simple gaussian elimination.
+VideoColorSpace::Matrix4x4 VideoColorSpace::Invert(Matrix4x4 m) {
+ Matrix4x4 ret;
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 4; x++) {
+ ret.rows[y][x] = x == y ? 1.0f : 0.0f;
+ }
+ }
+ for (int x = 0; x < 4; x++) {
+ if (m.rows[x][x] != 1.0) {
+ int best = x;
+ for (int y = x + 1; y < 4; y++) {
+ if (fabs(m.rows[y][x]) > fabs(m.rows[best][x])) {
+ best = y;
+ }
+ }
+ if (best != x) {
+ // Swap rows x, best
+ std::swap(m.rows[x], m.rows[best]);
+ std::swap(ret.rows[x], ret.rows[best]);
+ }
+ if (m.rows[x][x] == 0.0f) {
+ // Failed, shouldn't happen.
+ NOTREACHED();
+ return ret;
+ }
+ float mult = 1.0f / m.rows[x][x];
+ for (int x2 = 0; x2 < 4; x2++) {
+ m.rows[x][x2] *= mult;
+ ret.rows[x][x2] *= mult;
+ }
+ m.rows[x][x] = 1.0f;
+ }
+ for (int y = 0; y < 4; y++) {
+ if (y == x)
+ continue;
+ float mult = m.rows[y][x];
+ if (mult == 0.0f)
+ continue;
+
+ for (int x2 = 0; x2 < 4; x2++) {
+ m.rows[y][x2] -= m.rows[x][x2] * mult;
+ ret.rows[y][x2] -= ret.rows[x][x2] * mult;
+ }
+ m.rows[y][x] = 0.0f;
+ }
+ }
+ return ret;
+}
+
+namespace {
+
+VideoColorSpace::TriStim xy2xyz(const VideoColorSpace::XY& xy) {
+ VideoColorSpace::TriStim ret;
+ ret.values[0] = xy.first;
+ ret.values[1] = xy.second;
+ ret.values[2] = 1.0f - xy.first - xy.second;
+ return ret;
+}
+
+} // namespace
+
+VideoColorSpace::Matrix4x4 VideoColorSpace::GetPrimaryMatrix(
+ const std::vector<XY>& primaries) {
+ TriStim Rxyz = xy2xyz(primaries[0]);
+ TriStim Gxyz = xy2xyz(primaries[1]);
+ TriStim Bxyz = xy2xyz(primaries[2]);
+ TriStim Wxyz = xy2xyz(primaries[3]);
+ TriStim WXYZ;
+
+ WXYZ.values[0] = Wxyz.values[0] / Wxyz.values[1];
+ WXYZ.values[1] = 1.0f;
+ WXYZ.values[2] = Wxyz.values[2] / Wxyz.values[1];
+
+ Matrix4x4 tmp;
+ for (int i = 0; i < 3; i++) {
+ tmp.rows[i][0] = Rxyz.values[i];
+ tmp.rows[i][1] = Gxyz.values[i];
+ tmp.rows[i][2] = Bxyz.values[i];
+ tmp.rows[3][i] = 0.0f;
+ tmp.rows[i][3] = 0.0f;
+ }
+ tmp.rows[3][3] = 1.0;
+ Matrix4x4 tmpinv = Invert(tmp);
+ TriStim conv = Multiply(tmpinv, WXYZ);
+
+ Matrix4x4 ret;
+ for (int y = 0; y < 3; y++) {
+ for (int x = 0; x < 3; x++) {
+ ret.rows[y][x] = conv.values[x] * tmp.rows[y][x];
+ }
+ ret.rows[3][y] = 0.0f;
+ ret.rows[y][3] = 0.0f;
+ }
+ ret.rows[3][3] = 1.0f;
+
+ // Chromatic adaptation.
+ Matrix4x4 bradford;
+ bradford.rows[0][0] = 0.8951000f;
+ bradford.rows[0][1] = 0.2664000f;
+ bradford.rows[0][2] = -0.1614000f;
+ bradford.rows[0][3] = 0.0f;
+
+ bradford.rows[1][0] = -0.7502000f;
+ bradford.rows[1][1] = 1.7135000f;
+ bradford.rows[1][2] = 0.0367000f;
+ bradford.rows[1][3] = 0.0f;
+
+ bradford.rows[2][0] = 0.0389000f;
+ bradford.rows[2][1] = -0.0685000f;
+ bradford.rows[2][2] = 1.0296000f;
+ bradford.rows[2][3] = 0.0f;
+
+ bradford.rows[3][0] = 0.0f;
+ bradford.rows[3][1] = 0.0f;
+ bradford.rows[3][2] = 0.0f;
+ bradford.rows[3][3] = 1.0f;
+
+ Matrix4x4 bradford_inv = Invert(bradford);
+
+ TriStim D50(0.9642, 1.0000, 0.8249);
+ TriStim source_response = Multiply(bradford, WXYZ);
+ TriStim dest_response = Multiply(bradford, D50);
+
+ Matrix4x4 adapter;
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 4; x++) {
+ adapter.rows[y][x] = 0.0f;
+ }
+ }
+
+ for (int c = 0; c < 3; c++) {
+ adapter.rows[c][c] = dest_response.values[c] / source_response.values[c];
+ }
+ adapter.rows[3][3] = 1.0;
+ ret = Multiply(bradford_inv, ret);
+ ret = Multiply(adapter, ret);
+ ret = Multiply(bradford, ret);
+ return ret;
+}
+
+ColorTransform::ColorTransform(const VideoColorSpace& from,
+ const VideoColorSpace& to,
+ Intent intent)
+ : from_(from), to_(to) {
+ if (intent == INTENT_PERCEIVED) {
+ switch (from_.transfer) {
+ case VideoColorSpace::TRC_UNSPECIFIED:
+ case VideoColorSpace::TRC_BT709:
+ case VideoColorSpace::TRC_SMPTE170M:
+ // See SMPTE 1886
+ from_.transfer = VideoColorSpace::TRC_GAMMA24;
+ // from_.transfer = VideoColorSpace::TRC_IEC61966_2_1;
+
+ default: // Do nothing
+ break;
+ }
+
+ // TODO(hubbe): stretch/shrink gamuts here
+ }
+ for (int i = 0; i < 3; i++) {
+ for (int x = 0; x < 4; x++) {
+ for (int y = 0; y < 4; y++) {
+ transforms_[i].rows[y][x] = x == y ? 1.0f : 0.0f;
+ }
+ }
+ }
+ int from_transfer_matrix =
+ from_.matrix == VideoColorSpace::SPC_BT2020_CL ? 1 : 0;
+ int to_transfer_matrix = to_.matrix == VideoColorSpace::SPC_BT2020_CL ? 1 : 2;
+ transforms_[0] = from_.GetRangeAdjustMatrix();
+ transforms_[from_transfer_matrix] = VideoColorSpace::Multiply(
+ transforms_[from_transfer_matrix],
+ VideoColorSpace::Invert(from_.GetTransferMatrix()));
+
+ transforms_[1] =
+ VideoColorSpace::Multiply(transforms_[1], from_.GetPrimaryMatrix());
+ transforms_[1] = VideoColorSpace::Multiply(
+ transforms_[1], VideoColorSpace::Invert(to_.GetPrimaryMatrix()));
+
+ transforms_[to_transfer_matrix] = VideoColorSpace::Multiply(
+ transforms_[to_transfer_matrix], to_.GetTransferMatrix());
+
+ transforms_[2] = VideoColorSpace::Multiply(
+ transforms_[2], VideoColorSpace::Invert(to_.GetRangeAdjustMatrix()));
+
+ for (int i = 0; i < 3; i++) {
+ for (int row = 0; row < 4; row++) {
+ VLOG(2) << i << ":" << transforms_[i].rows[row][0] << ", "
+ << transforms_[i].rows[row][1] << ", "
+ << transforms_[i].rows[row][2] << ", "
+ << transforms_[i].rows[row][3];
+ }
+ }
+}
+
+void ColorTransform::transform(VideoColorSpace::TriStim* colors, size_t num) {
+ // Fix input range.
+ for (size_t i = 0; i < num; i++) {
+ VideoColorSpace::TriStim c = colors[i];
+ c = VideoColorSpace::Multiply(transforms_[0], c);
+ for (int j = 0; j < 3; j++) {
+ c.values[j] = from_.toLinear(c.values[j]);
+ }
+ c = VideoColorSpace::Multiply(transforms_[1], c);
+ for (int j = 0; j < 3; j++) {
+ c.values[j] = to_.fromLinear(c.values[j]);
+ }
+ c = VideoColorSpace::Multiply(transforms_[2], c);
+ colors[i] = c;
+ }
+}
+};

Powered by Google App Engine
This is Rietveld 408576698