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