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