Add helpers for DVB-sub colors.

Issue #832

Change-Id: I6350306c7d9a6450d82994bbd9a9a239986bc3fa
This commit is contained in:
Jacob Trimble 2020-10-05 12:15:11 -07:00
parent 427d264b46
commit 32c5393fba
5 changed files with 782 additions and 0 deletions

View File

@ -0,0 +1,39 @@
# Copyright 2020 Google LLC. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
{
'variables': {
'shaka_code': 1,
},
'targets': [
{
'target_name': 'dvb',
'type': '<(component)',
'sources': [
'dvb_image.cc',
'dvb_image.h',
],
'dependencies': [
'../../base/media_base.gyp:media_base',
'../../../third_party/libpng/libpng.gyp:libpng',
],
},
{
'target_name': 'dvb_unittest',
'type': '<(gtest_target_type)',
'sources': [
'dvb_image_unittest.cc',
],
'dependencies': [
'../../../testing/gtest.gyp:gtest',
'../../../testing/gmock.gyp:gmock',
'../../event/media_event.gyp:mock_muxer_listener',
'../../test/media_test.gyp:media_test_support',
'dvb',
]
},
],
}

View File

@ -0,0 +1,269 @@
// Copyright 2020 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/formats/dvb/dvb_image.h"
#include <algorithm>
#include <cstring>
#include <tuple>
#include "packager/base/logging.h"
namespace shaka {
namespace media {
namespace {
// See ETSI EN 300 743 Section 9.1.
constexpr const uint8_t k4To2ReductionMap[] = {
0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
};
// The only time when A==0 is when it is transparent. This means we can use
// other values internally for special values.
constexpr const RgbaColor kNoColor{145, 92, 47, 0};
// DVB uses transparency, but libpng uses alpha, so we need to reverse the T
// value so we can pass the value to libpng.
#define COLOR(r, g, b, t) \
RgbaColor { \
static_cast<uint8_t>(255 * (r) / 100), \
static_cast<uint8_t>(255 * (g) / 100), \
static_cast<uint8_t>(255 * (b) / 100), \
static_cast<uint8_t>(255 * (100 - t) / 100) \
}
// Default color maps see ETSI EN 300 743 Section 10.
constexpr const RgbaColor k2BitDefaultColors[] = {
COLOR(0, 0, 0, 100), // 0 = 0b00
COLOR(100, 100, 100, 0), // 1 = 0b01
COLOR(0, 0, 0, 0), // 2 = 0b10
COLOR(50, 50, 50, 0), // 3 = 0b11
};
// Default color maps see ETSI EN 300 743 Section 10.
constexpr const RgbaColor k4BitDefaultColors[] = {
COLOR(0, 0, 0, 100), // 0 = 0b0000
COLOR(100, 0, 0, 0), // 1 = 0b0001
COLOR(0, 100, 0, 0), // 2 = 0b0010
COLOR(100, 100, 0, 0), // 3 = 0b0011
COLOR(0, 0, 100, 0), // 4 = 0b0100
COLOR(100, 0, 100, 0), // 5 = 0b0101
COLOR(0, 100, 100, 0), // 6 = 0b0110
COLOR(100, 100, 100, 0), // 7 = 0b0111
COLOR(0, 0, 0, 0), // 8 = 0b1000
COLOR(50, 0, 0, 0), // 9 = 0b1001
COLOR(0, 50, 0, 0), // 10 = 0b1010
COLOR(50, 50, 0, 0), // 11 = 0b1011
COLOR(0, 0, 50, 0), // 12 = 0b1100
COLOR(50, 0, 50, 0), // 13 = 0b1101
COLOR(0, 50, 50, 0), // 14 = 0b1110
COLOR(50, 50, 50, 0), // 15 = 0b1111
};
#define GET_BIT(n) ((entry_id >> (8 - (n))) & 0x1)
// Default color maps see ETSI EN 300 743 Section 10.
RgbaColor Get8BitDefaultColor(uint8_t entry_id) {
uint8_t r, g, b, t;
if (entry_id == 0) {
return COLOR(0, 0, 0, 100);
} else if ((entry_id & 0xf8) == 0) {
r = 100 * GET_BIT(8);
g = 100 * GET_BIT(7);
b = 100 * GET_BIT(6);
t = 75;
} else if (!GET_BIT(1)) {
r = (33 * GET_BIT(8)) + (67 * GET_BIT(4));
g = (33 * GET_BIT(7)) + (67 * GET_BIT(3));
b = (33 * GET_BIT(6)) + (67 * GET_BIT(2));
t = GET_BIT(5) ? 50 : 0;
} else {
r = (17 * GET_BIT(8)) + (33 * GET_BIT(4)) + (GET_BIT(5) ? 0 : 50);
g = (17 * GET_BIT(7)) + (33 * GET_BIT(3)) + (GET_BIT(5) ? 0 : 50);
b = (17 * GET_BIT(6)) + (33 * GET_BIT(2)) + (GET_BIT(5) ? 0 : 50);
t = 0;
}
return COLOR(r, g, b, t);
}
#undef GET_BIT
#undef COLOR
} // namespace
DvbImageColorSpace::DvbImageColorSpace() {
for (auto& item : color_map_2_)
item = kNoColor;
for (auto& item : color_map_4_)
item = kNoColor;
for (auto& item : color_map_8_)
item = kNoColor;
}
DvbImageColorSpace::~DvbImageColorSpace() {}
RgbaColor DvbImageColorSpace::GetColor(BitDepth bit_depth,
uint8_t entry_id) const {
auto color = GetColorRaw(bit_depth, entry_id);
if (color != kNoColor)
return color;
// If we don't have the exact bit-depth, try mapping to another bit-depth.
// See ETSI EN 300 743 Section 9.
RgbaColor default_color, alt1, alt2;
switch (bit_depth) {
case BitDepth::k2Bit:
DCHECK_LT(entry_id, 4u);
alt1 = GetColorRaw(BitDepth::k4Bit, bit_depth_2_to_4_[entry_id]);
alt2 = GetColorRaw(BitDepth::k8Bit, bit_depth_2_to_8_[entry_id]);
default_color = k2BitDefaultColors[entry_id];
break;
case BitDepth::k4Bit:
DCHECK_LT(entry_id, 16u);
alt1 = GetColorRaw(BitDepth::k8Bit, bit_depth_4_to_8_[entry_id]);
alt2 = GetColorRaw(BitDepth::k2Bit, k4To2ReductionMap[entry_id]);
default_color = k4BitDefaultColors[entry_id];
break;
case BitDepth::k8Bit:
// 8-to-4-bit reduction is just take the low bits.
alt1 = GetColorRaw(BitDepth::k4Bit, entry_id & 0xf);
alt2 = GetColorRaw(BitDepth::k2Bit, k4To2ReductionMap[entry_id & 0xf]);
default_color = Get8BitDefaultColor(entry_id);
break;
default:
// Windows can't detect that all enums are handled and doesn't like
// NOTREACHED.
return kNoColor;
}
if (alt1 != kNoColor)
return alt1;
if (alt2 != kNoColor)
return alt2;
return default_color;
}
void DvbImageColorSpace::SetColor(BitDepth bit_depth,
uint8_t entry_id,
RgbaColor color) {
DCHECK(color != kNoColor);
switch (bit_depth) {
case BitDepth::k2Bit:
DCHECK_LT(entry_id, 4u);
color_map_2_[entry_id] = color;
break;
case BitDepth::k4Bit:
DCHECK_LT(entry_id, 16u);
color_map_4_[entry_id] = color;
break;
case BitDepth::k8Bit:
color_map_8_[entry_id] = color;
break;
}
}
void DvbImageColorSpace::Set2To4BitDepthMap(const uint8_t* map) {
memcpy(bit_depth_2_to_4_, map, sizeof(bit_depth_2_to_4_));
}
void DvbImageColorSpace::Set2To8BitDepthMap(const uint8_t* map) {
memcpy(bit_depth_2_to_8_, map, sizeof(bit_depth_2_to_8_));
}
void DvbImageColorSpace::Set4To8BitDepthMap(const uint8_t* map) {
memcpy(bit_depth_4_to_8_, map, sizeof(bit_depth_4_to_8_));
}
RgbaColor DvbImageColorSpace::GetColorRaw(BitDepth bit_depth,
uint8_t entry_id) const {
switch (bit_depth) {
case BitDepth::k2Bit:
return color_map_2_[entry_id];
case BitDepth::k4Bit:
return color_map_4_[entry_id];
case BitDepth::k8Bit:
return color_map_8_[entry_id];
}
// Not reached, but Windows doesn't like NOTREACHED.
return kNoColor;
}
DvbImageBuilder::DvbImageBuilder(const DvbImageColorSpace* color_space,
const RgbaColor& default_color,
uint16_t max_width,
uint16_t max_height)
: pixels_(new RgbaColor[max_width * max_height]),
color_space_(color_space),
top_pos_{0, 0},
bottom_pos_{0, 1}, // Skip top row for bottom row.
max_width_(max_width),
max_height_(max_height),
width_(0) {
for (size_t i = 0; i < static_cast<size_t>(max_width) * max_height; i++)
pixels_[i] = default_color;
}
DvbImageBuilder::~DvbImageBuilder() {}
bool DvbImageBuilder::AddPixel(BitDepth bit_depth,
uint8_t byte_code,
bool is_top_rows) {
auto& pos = is_top_rows ? top_pos_ : bottom_pos_;
if (pos.x >= max_width_ || pos.y >= max_height_) {
LOG(ERROR) << "DVB-sub image cannot fit in region/window";
return false;
}
pixels_[pos.y * max_width_ + pos.x++] =
color_space_->GetColor(bit_depth, byte_code);
if (pos.x > width_)
width_ = pos.x;
return true;
}
void DvbImageBuilder::NewRow(bool is_top_rows) {
auto& pos = is_top_rows ? top_pos_ : bottom_pos_;
pos.x = 0;
pos.y += 2; // Skip other row.
}
void DvbImageBuilder::MirrorToBottomRows() {
for (size_t line = 0; line < max_height_ - 1u; line += 2) {
std::memcpy(&pixels_[(line + 1) * max_width_], &pixels_[line * max_width_],
max_width_ * sizeof(RgbaColor));
}
bottom_pos_ = top_pos_;
if (max_height_ % 2 == 0)
bottom_pos_.y++;
else
bottom_pos_.y--; // Odd-height images don't end in odd-row, so move back.
}
bool DvbImageBuilder::GetPixels(const RgbaColor** pixels,
uint16_t* width,
uint16_t* height) const {
size_t max_y, min_y;
std::tie(min_y, max_y) = std::minmax(top_pos_.y, bottom_pos_.y);
if (max_y == 1 || max_y != min_y + 1) {
// 1. We should have at least one row.
// 2. Both top-rows and bottom-rows should have the same number of rows.
LOG(ERROR) << "Incomplete DVB-sub image";
return false;
}
*width = width_;
// We skipped the other row in NewRow, so rollback.
*height = static_cast<uint16_t>(max_y - 1);
*pixels = pixels_.get();
if (*height > max_height_) {
LOG(ERROR) << "DVB-sub image cannot fit in region/window";
return false;
}
return true;
}
} // namespace media
} // namespace shaka

View File

@ -0,0 +1,137 @@
// Copyright 2020 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef PACKAGER_MEDIA_DVB_DVB_IMAGE_H_
#define PACKAGER_MEDIA_DVB_DVB_IMAGE_H_
#include <memory>
#include <type_traits>
namespace shaka {
namespace media {
struct RgbaColor {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
bool operator==(const RgbaColor& other) const {
return r == other.r && g == other.g && b == other.b && a == other.a;
}
bool operator!=(const RgbaColor& other) const { return !(*this == other); }
};
// To avoid copying, we pass an RgbaColor array as a uint8_t* pointer to libpng
// for RGBA.
static_assert(std::is_pod<RgbaColor>::value, "RgbaColor must be POD");
static_assert(sizeof(RgbaColor) == 4, "RgbaColor not packed correctly");
enum class BitDepth : uint8_t {
k2Bit,
k4Bit,
k8Bit,
};
/// Defines a color-space for DVB-sub images. This maps to a single CLUT in the
/// spec. This holds a map of the byte codes to the respective RGB colors.
/// This also handles getting the default colors when none are provided and
/// converting between bit-depths if applicable.
///
/// When handling bit-depths, this will attempt to use the bit-depth provided
/// before converting upward then downward. Each color is only set for that
/// specific bit-depth; meaning different bit-depths can have different colors
/// mapped to the same byte-code.
class DvbImageColorSpace {
public:
DvbImageColorSpace();
~DvbImageColorSpace();
DvbImageColorSpace(const DvbImageColorSpace&) = delete;
DvbImageColorSpace& operator=(const DvbImageColorSpace&) = delete;
RgbaColor GetColor(BitDepth bit_depth, uint8_t entry_id) const;
void SetColor(BitDepth bit_depth, uint8_t entry_id, RgbaColor color);
/// Must pass a 4-element array; elements are copied over.
void Set2To4BitDepthMap(const uint8_t* map);
/// Must pass a 4-element array; elements are copied over.
void Set2To8BitDepthMap(const uint8_t* map);
/// Must pass a 16-element array; elements are copied over.
void Set4To8BitDepthMap(const uint8_t* map);
private:
RgbaColor GetColorRaw(BitDepth bit_depth, uint8_t entry_id) const;
// These hold the colors for each entry ID. Each value is initialized to the
// special value kNoColor meaning there isn't a value present.
RgbaColor color_map_2_[4];
RgbaColor color_map_4_[16];
RgbaColor color_map_8_[256];
// See ETSI EN 300 743 Sections 10.4, 10.5, 10.6 for defaults.
uint8_t bit_depth_2_to_4_[4] = {0x0, 0x7, 0x8, 0xf};
uint8_t bit_depth_2_to_8_[4] = {0x0, 0x77, 0x88, 0xff};
uint8_t bit_depth_4_to_8_[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
0xcc, 0xdd, 0xee, 0xff};
};
/// Defines a builder that generates an image from a DVB-sub byte stream. This
/// allocates a single buffer big enough to hold the max-sized image and fills
/// it in series. The NewRow() method must be called to start a new line of the
/// image.
///
/// This adds pixels in an interlaced format. Adding pixels and new rows on
/// top-rows doesn't affect the bottom-rows. Top-rows refers to even-indexed
/// lines (e.g. 0,2,4).
class DvbImageBuilder {
public:
DvbImageBuilder(const DvbImageColorSpace* color_space,
const RgbaColor& default_color,
uint16_t max_width,
uint16_t max_height);
~DvbImageBuilder();
DvbImageBuilder(const DvbImageBuilder&) = delete;
DvbImageBuilder& operator=(const DvbImageBuilder&) = delete;
uint16_t max_width() const { return max_width_; }
uint16_t max_height() const { return max_height_; }
bool AddPixel(BitDepth bit_depth, uint8_t byte_code, bool is_top_rows);
void NewRow(bool is_top_rows);
/// Copies the top-rows to the bottom rows.
void MirrorToBottomRows();
/// Gets the pixel buffer. Each row is based on the max_width field, but
/// the max filled row with will be given in `width`. This assumes that
/// NewRow was called recently and we are at the beginning of the rows.
///
/// @param pixels A pointer to a RgbaColor* variable that will be set to the
/// pixel data pointer.
/// @param width Will be filled with the max width of all rows.
/// @param height Will be filled with the number of rows set.
/// @return True on success, false on error.
bool GetPixels(const RgbaColor** pixels,
uint16_t* width,
uint16_t* height) const;
private:
struct Position {
uint16_t x, y;
};
const std::unique_ptr<RgbaColor[]> pixels_;
const DvbImageColorSpace* const color_space_;
Position top_pos_, bottom_pos_;
const uint16_t max_width_;
const uint16_t max_height_;
uint16_t width_;
};
} // namespace media
} // namespace shaka
#endif // PACKAGER_MEDIA_DVB_DVB_IMAGE_H_

View File

@ -0,0 +1,335 @@
// Copyright 2020 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/formats/dvb/dvb_image.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "packager/media/base/rcheck.h"
namespace shaka {
namespace media {
namespace {
// Use an unusual alpha value to avoid being equal to a default color.
const RgbaColor kRed{255, 0, 0, 211};
const RgbaColor kGreen{0, 255, 0, 211};
const RgbaColor kBlue{0, 0, 255, 211};
const RgbaColor kBlack{0, 0, 0, 211};
const RgbaColor kWhite{255, 255, 255, 211};
const RgbaColor kYellow{255, 255, 0, 211};
const uint8_t kRedId = 0;
const uint8_t kGreenId = 1;
const uint8_t kBlueId = 2;
const uint8_t kBlackId = 3;
const uint8_t kWhiteId = 4;
const uint8_t kYellowId = 5;
const bool kTopRow = true;
const bool kBottomRow = false;
void FillDefaultColorSpace(DvbImageColorSpace* space) {
for (auto depth : {BitDepth::k2Bit, BitDepth::k4Bit, BitDepth::k8Bit}) {
space->SetColor(depth, kRedId, kRed);
space->SetColor(depth, kGreenId, kGreen);
space->SetColor(depth, kBlueId, kBlue);
space->SetColor(depth, kBlackId, kBlack);
if (depth != BitDepth::k2Bit) {
space->SetColor(depth, kWhiteId, kWhite);
space->SetColor(depth, kYellowId, kYellow);
}
}
}
bool AddPixelRow(DvbImageBuilder* image,
uint16_t width,
uint8_t color_id,
bool is_top_rows) {
for (size_t i = 0; i < width; i++)
RCHECK(image->AddPixel(BitDepth::k8Bit, color_id, is_top_rows));
image->NewRow(is_top_rows);
return true;
}
void CheckImagePixels(const DvbImageBuilder* image,
uint16_t width,
std::initializer_list<RgbaColor> rows) {
uint16_t actual_width, height;
const RgbaColor* pixels = nullptr;
ASSERT_TRUE(image->GetPixels(&pixels, &actual_width, &height));
ASSERT_EQ(actual_width, width);
ASSERT_EQ(height, rows.size());
size_t row = 0;
for (const auto& color : rows) {
for (size_t i = 0; i < width; i++)
EXPECT_EQ(pixels[image->max_width() * row + i], color);
row++;
}
}
} // namespace
TEST(DvbImageColorSpaceTest, GetsColors) {
DvbImageColorSpace space;
space.SetColor(BitDepth::k8Bit, 0, kRed);
space.SetColor(BitDepth::k8Bit, 1, kGreen);
space.SetColor(BitDepth::k8Bit, 2, kBlue);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0), kRed);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 1), kGreen);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 2), kBlue);
}
TEST(DvbImageColorSpaceTest, BitDepthsAreDifferent) {
DvbImageColorSpace space;
space.SetColor(BitDepth::k8Bit, 0, kRed);
space.SetColor(BitDepth::k8Bit, 1, kGreen);
space.SetColor(BitDepth::k4Bit, 0, kBlue);
space.SetColor(BitDepth::k4Bit, 1, kBlack);
space.SetColor(BitDepth::k2Bit, 0, kWhite);
space.SetColor(BitDepth::k2Bit, 1, kYellow);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0), kRed);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 1), kGreen);
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0), kBlue);
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 1), kBlack);
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0), kWhite);
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 1), kYellow);
}
TEST(DvbImageColorSpaceTest, HandlesBitDepthReduction) {
DvbImageColorSpace space;
space.SetColor(BitDepth::k2Bit, 0x0, kRed);
space.SetColor(BitDepth::k2Bit, 0x1, kGreen);
space.SetColor(BitDepth::k4Bit, 0x1, kWhite);
space.SetColor(BitDepth::k4Bit, 0x5, kBlue);
space.SetColor(BitDepth::k4Bit, 0x7, kBlack);
space.SetColor(BitDepth::k4Bit, 0x9, kYellow);
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x00), kRed); // 0x0 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x02), kGreen); // 0x1 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x72), kGreen); // 0x1 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x35), kBlue); // 0x5 in 4-bit
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x17), kBlack); // 0x7 in 4-bit
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x09), kYellow); // Exact match
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x0), kRed); // 0x0 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x2), kGreen); // 0x1 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x3), kGreen); // 0x1 in 2-bit
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x1), kWhite); // Exact match
}
TEST(DvbImageColorSpaceTest, HandlesBitDepthExpansion) {
DvbImageColorSpace space;
space.SetColor(BitDepth::k2Bit, 0x0, kRed);
space.SetColor(BitDepth::k4Bit, 0x7, kGreen);
space.SetColor(BitDepth::k4Bit, 0x8, kBlue);
space.SetColor(BitDepth::k8Bit, 0x11, kBlack);
space.SetColor(BitDepth::k8Bit, 0xff, kYellow);
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x0), kRed); // Exact match
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x1), kGreen); // 0x07 in 4-bit
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x3), kYellow); // 0xff in 8-bit
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x7), kGreen); // Exact match
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x1), kBlack); // 0x11 in 8-bit
}
TEST(DvbImageColorSpaceTest, HandlesCustomBitDepthExpansion) {
const uint8_t k2To4Map[] = {0x0, 0x6, 0x7, 0x0};
const uint8_t k2To8Map[] = {0x0, 0xa, 0xb, 0x0};
const uint8_t k4To8Map[] = {0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
DvbImageColorSpace space;
space.Set2To4BitDepthMap(k2To4Map);
space.Set2To8BitDepthMap(k2To8Map);
space.Set4To8BitDepthMap(k4To8Map);
space.SetColor(BitDepth::k2Bit, 0x0, kRed);
space.SetColor(BitDepth::k4Bit, 0x0, kGreen);
space.SetColor(BitDepth::k4Bit, 0x6, kBlue);
space.SetColor(BitDepth::k8Bit, 0x0, kBlack);
space.SetColor(BitDepth::k8Bit, 0xb, kWhite);
space.SetColor(BitDepth::k8Bit, 0x12, kYellow);
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x0), kRed); // Exact match
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x1), kBlue); // 0x06 in 4-bit
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x2), kWhite); // 0xb in 8-bit
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x0), kGreen); // Exact match
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x1), kYellow); // 0x12 in 8-bit
}
TEST(DvbImageColorSpaceTest, HandlesDefaultColors) {
DvbImageColorSpace space;
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x0).a, 0u); // Only T is spec'd
EXPECT_EQ(space.GetColor(BitDepth::k2Bit, 0x2), (RgbaColor{0, 0, 0, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x0).a, 0u); // Only T is spec'd
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x1), (RgbaColor{255, 0, 0, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x2), (RgbaColor{0, 255, 0, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x5),
(RgbaColor{255, 0, 255, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0x9), (RgbaColor{127, 0, 0, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k4Bit, 0xa), (RgbaColor{0, 127, 0, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x0).a, 0u); // Only T is spec'd
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x1), (RgbaColor{255, 0, 0, 63}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x3), (RgbaColor{255, 255, 0, 63}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x61),
(RgbaColor{84, 170, 170, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x46),
(RgbaColor{0, 84, 255, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0x1a),
(RgbaColor{170, 84, 0, 127}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0xf2),
(RgbaColor{211, 255, 211, 255}));
EXPECT_EQ(space.GetColor(BitDepth::k8Bit, 0xbe),
(RgbaColor{84, 127, 43, 255}));
}
TEST(DvbImageBuilderTest, BasicFlow) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
DvbImageBuilder image(&colors, kBlack, kWidth, 5);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kGreenId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kWhiteId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kBlueId, kBottomRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kBlackId, kBottomRow));
CheckImagePixels(&image, kWidth, {kRed, kBlue, kGreen, kBlack, kWhite});
}
TEST(DvbImageBuilderTest, AllowsSmallerImages) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
DvbImageBuilder image(&colors, kBlack, kWidth + 10, 5);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kGreenId, kBottomRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kBlueId, kTopRow));
CheckImagePixels(&image, kWidth, {kRed, kGreen, kBlue});
}
TEST(DvbImageBuilderTest, ValidatesMaxWidth) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
DvbImageBuilder image(&colors, kBlack, kWidth, 5);
for (size_t i = 0; i < kWidth; i++)
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
// Cannot exceed max_width on first line.
ASSERT_FALSE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
// Despite the error, the image should still be in the same state as before.
image.NewRow(kTopRow);
for (size_t i = 0; i < kWidth; i++)
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
// Cannot exceed max_width on other lines.
ASSERT_FALSE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
}
TEST(DvbImageBuilderTest, SupportsInconsistentWidths) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
DvbImageBuilder image(&colors, kBlack, 10, 10);
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
image.NewRow(kTopRow);
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kBlueId, kBottomRow));
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kBlueId, kBottomRow));
image.NewRow(kBottomRow);
ASSERT_TRUE(image.AddPixel(BitDepth::k8Bit, kYellowId, kTopRow));
image.NewRow(kTopRow);
const RgbaColor* pixels;
uint16_t width, height;
ASSERT_TRUE(image.GetPixels(&pixels, &width, &height));
EXPECT_EQ(width, 3);
EXPECT_EQ(height, 3);
EXPECT_EQ(pixels[0], kRed);
EXPECT_EQ(pixels[1], kRed);
EXPECT_EQ(pixels[2], kRed);
EXPECT_EQ(pixels[10], kBlue);
EXPECT_EQ(pixels[11], kBlue);
EXPECT_EQ(pixels[12], kBlack);
EXPECT_EQ(pixels[20], kYellow);
EXPECT_EQ(pixels[21], kBlack);
EXPECT_EQ(pixels[22], kBlack);
}
TEST(DvbImageBuilderTest, ValidatesTotalLength) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
DvbImageBuilder image(&colors, kBlack, kWidth, 3);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kBottomRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_FALSE(image.AddPixel(BitDepth::k8Bit, kRedId, kTopRow));
ASSERT_FALSE(image.AddPixel(BitDepth::k8Bit, kRedId, kBottomRow));
}
TEST(DvbImageBuilderTest, ValidatesTopBottomFieldsMatch) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
DvbImageBuilder image(&colors, kBlack, kWidth, 5);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kBottomRow));
// Not enough bottom rows.
uint16_t width, height;
const RgbaColor* pixels;
ASSERT_FALSE(image.GetPixels(&pixels, &width, &height));
}
TEST(DvbImageBuilderTest, MirrorToBottomRowsEven) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
const uint16_t kHeight = 4;
DvbImageBuilder image(&colors, kBlack, kWidth, kHeight);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kGreenId, kTopRow));
image.MirrorToBottomRows();
CheckImagePixels(&image, kWidth, {kRed, kRed, kGreen, kGreen});
}
TEST(DvbImageBuilderTest, MirrorToBottomRowsOdd) {
DvbImageColorSpace colors;
FillDefaultColorSpace(&colors);
const uint16_t kWidth = 4;
const uint16_t kHeight = 5;
DvbImageBuilder image(&colors, kBlack, kWidth, kHeight);
ASSERT_TRUE(AddPixelRow(&image, kWidth, kRedId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kGreenId, kTopRow));
ASSERT_TRUE(AddPixelRow(&image, kWidth, kBlueId, kTopRow));
image.MirrorToBottomRows();
CheckImagePixels(&image, kWidth, {kRed, kRed, kGreen, kGreen, kBlue});
}
} // namespace media
} // namespace shaka

View File

@ -35,6 +35,7 @@
'media/crypto/crypto.gyp:crypto',
'media/demuxer/demuxer.gyp:demuxer',
'media/event/media_event.gyp:media_event',
'media/formats/dvb/dvb.gyp:dvb',
'media/formats/mp2t/mp2t.gyp:mp2t',
'media/formats/mp4/mp4.gyp:mp4',
'media/formats/packed_audio/packed_audio.gyp:packed_audio',
@ -213,6 +214,7 @@
'media/crypto/crypto.gyp:crypto_unittest',
'media/demuxer/demuxer.gyp:demuxer_unittest',
'media/event/media_event.gyp:media_event_unittest',
'media/formats/dvb/dvb.gyp:dvb_unittest',
'media/formats/mp2t/mp2t.gyp:mp2t_unittest',
'media/formats/mp4/mp4.gyp:mp4_unittest',
'media/formats/packed_audio/packed_audio.gyp:packed_audio_unittest',