Add helpers for DVB-sub colors.
Issue #832 Change-Id: I6350306c7d9a6450d82994bbd9a9a239986bc3fa
This commit is contained in:
parent
427d264b46
commit
32c5393fba
|
@ -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',
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue