Skip to content

Commit

Permalink
support full color range transformation
Browse files Browse the repository at this point in the history
  • Loading branch information
ledyba-z committed Feb 7, 2020
1 parent 5a81a2e commit 3532b33
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 90 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_library(
src/avif/util/StreamWriter.cpp

src/avif/img/Image.hpp
src/avif/img/Spec.hpp
src/avif/img/Conversion.hpp
src/avif/img/Transform.hpp
src/avif/img/TransformImpl.hpp
Expand Down Expand Up @@ -51,6 +52,7 @@ add_library(
src/avif/ItemInfoBox.hpp
src/avif/ItemInfoEntry.hpp
src/avif/ItemInfoExtension.hpp
src/avif/ItemReferenceBox.hpp

src/avif/Parser.cpp
src/avif/Parser.hpp
Expand All @@ -66,7 +68,7 @@ add_library(
src/avif/av1/Parser.hpp
src/avif/av1/BitStreamReader.cpp
src/avif/av1/BitStreamReader.hpp
src/avif/ItemReferenceBox.hpp)
)

set_property(TARGET libavif-container PROPERTY CXX_STANDARD 17)
set_property(TARGET libavif-container PROPERTY CXX_FLAGS_DEBUG "-g3 -O0 -fno-omit-frame-pointer")
Expand Down
132 changes: 59 additions & 73 deletions src/avif/img/Conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,12 @@
#include <cstdint>
#include <cmath>
#include <tuple>

#include "Spec.hpp"
#include "Image.hpp"

namespace avif::img {

// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf

namespace spec {

template <uint8_t bitsPerComponent> struct YUV;
template <> struct YUV<8> {
using Type = uint8_t;
static constexpr uint8_t bias = 128;
};
template <> struct YUV<10> {
using Type = uint16_t;
static constexpr uint16_t bias = 512;
};
template <> struct YUV<12> {
using Type = uint16_t;
static constexpr uint16_t bias = 2048;
};


template <typename T, bool subX, bool subY>
struct ChromaSampler {
static_assert(!std::is_pointer<T>::value);
Expand All @@ -48,61 +30,64 @@ struct ChromaSampler {
}
};

}

namespace detail{

template <typename T>
constexpr T clamp(T value, T low, T high) {
return value < low ? low : (value > high ? high : value);
}
namespace detail {

template <size_t rgbBits, size_t yuvBits>
constexpr void calcYUV(uint16_t const ir, uint16_t const ig, uint16_t const ib, typename spec::YUV<yuvBits>::Type* dstY, typename spec::YUV<yuvBits>::Type* dstU, typename spec::YUV<yuvBits>::Type* dstV) {
using YUVType = typename spec::YUV<yuvBits>::Type;
float const r = static_cast<float>(ir) / spec::RGB<rgbBits>::max;
float const g = static_cast<float>(ig) / spec::RGB<rgbBits>::max;
float const b = static_cast<float>(ib) / spec::RGB<rgbBits>::max;
template <size_t rgbBits, size_t yuvBits, bool isFullRange>
constexpr void calcYUV(uint16_t const ir, uint16_t const ig, uint16_t const ib, typename avif::img::spec::YUV<yuvBits>::Type* dstY, typename avif::img::spec::YUV<yuvBits>::Type* dstU, typename avif::img::spec::YUV<yuvBits>::Type* dstV) {
using Quantizer = typename avif::img::spec::Quantizer<rgbBits, yuvBits, isFullRange>;
using RGBSpec = typename avif::img::spec::RGB<rgbBits>;
float const r = static_cast<float>(ir) / RGBSpec::max;
float const g = static_cast<float>(ig) / RGBSpec::max;
float const b = static_cast<float>(ib) / RGBSpec::max;

auto constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
int constexpr maxYUV = (1u << yuvBits) - 1u;
float const y = 0.2627f * r + 0.6780f * g + 0.0593f * b;
*dstY = static_cast<YUVType>(clamp(static_cast<int>(std::round(y * 219 + 16) * shift), 0, maxYUV));
*dstY = Quantizer::quantizeLuma(y);

if (dstU) {
float const u = (b - y) / 1.8814f;
*dstU = static_cast<YUVType>(clamp(static_cast<int>(std::round(u * 224 + 128) * shift), 0, maxYUV));
const float u = (b - y) / 1.8814f;
*dstU = Quantizer::quantizeChroma(u);
}
if (dstV) {
float const v = (r - y) / 1.4746f;
*dstV = static_cast<YUVType>(clamp(static_cast<int>(std::round(v * 224 + 128) * shift), 0, maxYUV));
const float v = (r - y) / 1.4746f;
*dstV = Quantizer::quantizeChroma(v);
}
}

template <size_t rgbBits, size_t yuvBits>
constexpr std::tuple<typename spec::RGB<rgbBits>::Type, typename spec::RGB<rgbBits>::Type, typename spec::RGB<rgbBits>::Type> calcRGB(typename spec::YUV<yuvBits>::Type const* srcY, typename spec::YUV<yuvBits>::Type const* srcU, typename spec::YUV<yuvBits>::Type const* srcV) {
using RGBType = typename spec::RGB<rgbBits>::Type;
template <size_t rgbBits, size_t yuvBits, bool isFullRange>
constexpr std::tuple<typename avif::img::spec::RGB<rgbBits>::Type, typename avif::img::spec::RGB<rgbBits>::Type, typename avif::img::spec::RGB<rgbBits>::Type> calcRGB(typename avif::img::spec::YUV<yuvBits>::Type const* srcY, typename avif::img::spec::YUV<yuvBits>::Type const* srcU, typename avif::img::spec::YUV<yuvBits>::Type const* srcV) {
using avif::img::spec::clamp;
using Quantizer = typename avif::img::spec::Quantizer<rgbBits, yuvBits, isFullRange>;
using RGBSpec = typename avif::img::spec::RGB<rgbBits>;
using YUVSpec = typename avif::img::spec::YUV<yuvBits>;

using RGBType = typename RGBSpec::Type;

auto constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
auto const y = ((static_cast<float>(*srcY) / shift) - 16.0f) / 219.0f;
auto const u = ((static_cast<float>(srcU != nullptr ? (*srcU) : spec::YUV<yuvBits>::bias) / shift) - 128.0f) / 224.0f;
auto const v = ((static_cast<float>(srcV != nullptr ? (*srcV) : spec::YUV<yuvBits>::bias) / shift) - 128.0f) / 224.0f;
auto const y = Quantizer::dequantizeLuma(*srcY);
auto const u = srcU != nullptr ? Quantizer::dequantizeChroma(*srcU) : 0.0f;
auto const v = srcV != nullptr ? Quantizer::dequantizeChroma(*srcV) : 0.0f;

float const r = y + (+1.47460f) * v;
float const g = y + (-0.16455f) * u + (-0.57135f) * v;
float const b = y + (+1.88140f) * u;

auto const ir = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(r * spec::RGB<rgbBits>::max)), 0, spec::RGB<rgbBits>::max));
auto const ig = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(g * spec::RGB<rgbBits>::max)), 0, spec::RGB<rgbBits>::max));
auto const ib = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(b * spec::RGB<rgbBits>::max)), 0, spec::RGB<rgbBits>::max));
auto const ir = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(r * RGBSpec::max)), 0, RGBSpec::max));
auto const ig = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(g * RGBSpec::max)), 0, RGBSpec::max));
auto const ib = static_cast<RGBType>(clamp<int>(static_cast<int>(std::round(b * RGBSpec::max)), 0, RGBSpec::max));
return std::make_tuple(ir, ig, ib);
}

template <uint8_t rgbBits, uint8_t yuvBits, bool subX, bool subY>
template <uint8_t rgbBits, uint8_t yuvBits, bool isFullRange, bool subX, bool subY>
void constexpr convertFromRGB(size_t width, size_t height, uint8_t bytesPerPixel, uint8_t const* src, size_t stride, uint8_t* dstY, size_t strideY, uint8_t* dstU, size_t strideU, uint8_t* dstV, size_t strideV) {
using YUVType = typename spec::YUV<yuvBits>::Type;
using RGBType = typename spec::RGB<rgbBits>::Type const;
spec::ChromaSampler<YUVType, subX, subY> sampler;
using avif::img::spec::clamp;
using RGBSpec = typename avif::img::spec::RGB<rgbBits>;
using YUVSpec = typename avif::img::spec::YUV<yuvBits>;

using RGBType = typename RGBSpec::Type const;
using YUVType = typename YUVSpec::Type;

using ChromaSampler = typename avif::img::ChromaSampler<YUVType, subX, subY>;
ChromaSampler sampler;

uint8_t* lineY = dstY;
uint8_t* lineU = dstU;
uint8_t* lineV = dstV;
Expand All @@ -116,7 +101,7 @@ void constexpr convertFromRGB(size_t width, size_t height, uint8_t bytesPerPixel
uint16_t const r = reinterpret_cast<RGBType const *>(ptr)[0];
uint16_t const g = reinterpret_cast<RGBType const *>(ptr)[1];
uint16_t const b = reinterpret_cast<RGBType const *>(ptr)[2];
calcYUV<rgbBits, yuvBits>(r, g, b, &ptrY[x], sampler.pixelInLine(ptrU, x), sampler.pixelInLine(ptrV, x));
calcYUV<rgbBits, yuvBits, isFullRange>(r, g, b, &ptrY[x], sampler.pixelInLine(ptrU, x), sampler.pixelInLine(ptrV, x));
ptr += bytesPerPixel;
}
lineY += strideY;
Expand All @@ -126,11 +111,16 @@ void constexpr convertFromRGB(size_t width, size_t height, uint8_t bytesPerPixel
}
}

template <uint8_t rgbBits, uint8_t yuvBits, bool subX, bool subY>
template <uint8_t rgbBits, uint8_t yuvBits, bool isFullRange, bool subX, bool subY>
void constexpr convertFromYUV(size_t width, size_t height, uint8_t bytesPerPixel, uint8_t* dst, size_t stride, uint8_t const* srcY, size_t strideY, uint8_t const* srcU, size_t strideU, uint8_t const* srcV, size_t strideV) {
using YUVType = typename spec::YUV<yuvBits>::Type const;
using RGBType = typename spec::RGB<rgbBits>::Type;
spec::ChromaSampler<YUVType, subX, subY> sampler;
using RGBSpec = typename avif::img::spec::RGB<rgbBits>;
using YUVSpec = typename avif::img::spec::YUV<yuvBits>;

using YUVType = typename YUVSpec::Type const;
using RGBType = typename RGBSpec::Type;
using ChromaSampler = typename avif::img::ChromaSampler<YUVType, subX, subY>;
ChromaSampler sampler;

uint8_t const* lineY = srcY;
uint8_t const* lineU = srcU;
uint8_t const* lineV = srcV;
Expand All @@ -144,7 +134,7 @@ void constexpr convertFromYUV(size_t width, size_t height, uint8_t bytesPerPixel
RGBType& r = reinterpret_cast<RGBType*>(ptr)[0];
RGBType& g = reinterpret_cast<RGBType*>(ptr)[1];
RGBType& b = reinterpret_cast<RGBType*>(ptr)[2];
std::tie(r,g,b) = calcRGB<rgbBits, yuvBits>(&ptrY[x], sampler.pixelInLine(ptrU, x), sampler.pixelInLine(ptrV, x));
std::tie(r,g,b) = calcRGB<rgbBits, yuvBits, isFullRange>(&ptrY[x], sampler.pixelInLine(ptrU, x), sampler.pixelInLine(ptrV, x));
ptr += bytesPerPixel;
}
lineY += strideY;
Expand All @@ -156,34 +146,30 @@ void constexpr convertFromYUV(size_t width, size_t height, uint8_t bytesPerPixel

}


template <uint8_t rgbBits, uint8_t yuvBits>
template <uint8_t rgbBits, uint8_t yuvBits, bool isFullRange>
struct FromRGB final {
void toI444(Image<rgbBits> const& src, uint8_t* dstY, size_t strideY, uint8_t* dstU, size_t strideU, uint8_t* dstV, size_t strideV) {
detail::convertFromRGB<rgbBits, yuvBits, false, false>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
detail::convertFromRGB<rgbBits, yuvBits, isFullRange, false, false>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
}
void toI422(Image<rgbBits> const& src, uint8_t* dstY, size_t strideY, uint8_t* dstU, size_t strideU, uint8_t* dstV, size_t strideV){
detail::convertFromRGB<rgbBits, yuvBits, true, false>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
detail::convertFromRGB<rgbBits, yuvBits, isFullRange, true, false>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
}
void toI420(Image<rgbBits> const& src, uint8_t* dstY, size_t strideY, uint8_t* dstU, size_t strideU, uint8_t* dstV, size_t strideV){
detail::convertFromRGB<rgbBits, yuvBits, true, true>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
detail::convertFromRGB<rgbBits, yuvBits, isFullRange, true, true>(src.width(), src.height(), src.bytesPerPixel(), src.data(), src.stride(), dstY, strideY, dstU, strideU, dstV, strideV);
}
};

template <uint8_t rgbBits, uint8_t yuvBits>
template <uint8_t rgbBits, uint8_t yuvBits, bool isFullRange>
struct ToRGB final {
void fromI444(Image<rgbBits>& dst, uint8_t* srcY, size_t strideY, uint8_t* srcU, size_t strideU, uint8_t* srcV, size_t strideV) {
detail::convertFromYUV<rgbBits, yuvBits, false, false>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
detail::convertFromYUV<rgbBits, yuvBits, isFullRange, false, false>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
}
void fromI422(Image<rgbBits>& dst, uint8_t* srcY, size_t strideY, uint8_t* srcU, size_t strideU, uint8_t* srcV, size_t strideV){
detail::convertFromYUV<rgbBits, yuvBits, true, false>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
detail::convertFromYUV<rgbBits, yuvBits, isFullRange, true, false>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
}
void fromI420(Image<rgbBits>& dst, uint8_t* srcY, size_t strideY, uint8_t* srcU, size_t strideU, uint8_t* srcV, size_t strideV){
detail::convertFromYUV<rgbBits, yuvBits, true, true>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
detail::convertFromYUV<rgbBits, yuvBits, isFullRange, true, true>(dst.width(), dst.height(), dst.bytesPerPixel(), dst.data(), dst.stride(), srcY, strideY, srcU, strideU, srcV, strideV);
}
};

}



18 changes: 2 additions & 16 deletions src/avif/img/Image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,9 @@
#include <cstdint>
#include <cassert>

namespace avif::img {

namespace spec {
#include "Spec.hpp"

template <uint8_t bitsPerComponent> struct RGB;
template <> struct RGB<8> {
using Type = uint8_t;
static constexpr float max = 255.0f;
static constexpr size_t bytesPerComponent = sizeof(uint8_t);
};
template <> struct RGB<16> {
using Type = uint16_t;
static constexpr float max = 65535.0f;
static constexpr size_t bytesPerComponent = sizeof(uint16_t);
};

}
namespace avif::img {

enum class PixelOrder {
RGB, /* [R,G,B], [R,G,B], ... */
Expand Down
118 changes: 118 additions & 0 deletions src/avif/img/Spec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// Created by psi on 2020/02/07.
//

#pragma once

#include <cstdint>
#include <cstddef>
#include <cmath>

// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf

namespace avif::img::spec {

template <typename T>
static T clamp(T value, T low, T high) {
return value < low ? low : (value > high ? high : value);
}

template <uint8_t bitsPerComponent> struct YUV;
template <> struct YUV<8> {
using Type = uint8_t;
static constexpr uint8_t bias = 128;
};
template <> struct YUV<10> {
using Type = uint16_t;
static constexpr uint16_t bias = 512;
};
template <> struct YUV<12> {
using Type = uint16_t;
static constexpr uint16_t bias = 2048;
};

template <uint8_t bitsPerComponent> struct RGB;
template <> struct RGB<8> {
using Type = uint8_t;
static constexpr float max = 255.0f;
static constexpr size_t bytesPerComponent = sizeof(uint8_t);
};
template <> struct RGB<16> {
using Type = uint16_t;
static constexpr float max = 65535.0f;
static constexpr size_t bytesPerComponent = sizeof(uint16_t);
};

template <size_t rgbBits, size_t yuvBits, bool isFullRange>
struct Quantizer final{};

// https://www.itu.int/rec/T-REC-H.273-201612-I/en
template <size_t rgbBits, size_t yuvBits>
struct Quantizer<rgbBits, yuvBits, true> final{
using YUVSpec = typename spec::YUV<yuvBits>;
using YUVType = typename YUVSpec::Type;
constexpr static YUVType quantizeLuma(float const luma) {
// https://www.itu.int/rec/T-REC-H.273-201612-I/en
// 8.3 Matrix coefficients
// Equation (29)
int constexpr maxYUV = (1u << yuvBits) - 1u;
return static_cast<YUVType>(clamp<int>(static_cast<int>(std::round(luma * maxYUV)), 0, maxYUV));
}
constexpr static float dequantizeLuma(YUVType const luma) {
// FIXME(ledyba): H.273 does not specify about dequantization
auto constexpr maxYUV = static_cast<float>((1u << yuvBits) - 1u);
return static_cast<float>(luma) / static_cast<float>(maxYUV);
}
constexpr static YUVType quantizeChroma(float const chroma) {
// https://www.itu.int/rec/T-REC-H.273-201612-I/en
// 8.3 Matrix coefficients
// Equation (30, 31)
int constexpr maxYUV = (1u << yuvBits) - 1u;
int constexpr bias = YUVSpec::bias;
return static_cast<YUVType>(clamp<int>(static_cast<int>(std::round(chroma * maxYUV) + bias), 0, maxYUV));
}
constexpr static float dequantizeChroma(YUVType const chroma) {
// FIXME(ledyba): H.273 does not specify about dequantization
int constexpr maxYUV = (1u << yuvBits) - 1u;
int constexpr bias = YUVSpec::bias;
return clamp<float>((static_cast<float>(chroma) - bias) / static_cast<float>(maxYUV), -0.5, 0.5);
}
};

template <size_t rgbBits, size_t yuvBits>
struct Quantizer<rgbBits, yuvBits, false> final{
using YUVSpec = typename spec::YUV<yuvBits>;
using YUVType = typename YUVSpec::Type;
static YUVType quantizeLuma(float const luma) {
// https://www.itu.int/rec/T-REC-H.273-201612-I/en
// 8.3 Matrix coefficients
// Equation (23)
int constexpr maxYUV = (1u << yuvBits) - 1u;
auto constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
return static_cast<YUVType>(clamp<int>(static_cast<int>(std::round((luma * 219 + 16) * shift)), 0, maxYUV));
}
constexpr static float dequantizeLuma(YUVType const luma) {
// FIXME(ledyba): H.273 does not specify about dequantization
auto constexpr maxYUV = static_cast<float>((1u << yuvBits) - 1u);
auto constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
return ((static_cast<float>(luma) / shift) - 16.0f) / 219.0f;
}
static YUVType quantizeChroma(float const chroma) {
// https://www.itu.int/rec/T-REC-H.273-201612-I/en
// 8.3 Matrix coefficients
// Equation (24, 25)
int constexpr maxYUV = (1u << yuvBits) - 1u;
int constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
return static_cast<YUVType>(clamp<int>(static_cast<int>(std::round((chroma * 224 + 128) * shift)), 0, maxYUV));
}
constexpr static float dequantizeChroma(YUVType const chroma) {
// FIXME(ledyba): H.273 does not specify about dequantization
int constexpr maxYUV = (1u << yuvBits) - 1u;
float constexpr bias = YUVSpec::bias;
auto constexpr shift = static_cast<float>(1u << (yuvBits - 8u));
return ((static_cast<float>(chroma) / shift) - 128.0f) / 224.0f;
}
};

}
1 change: 1 addition & 0 deletions src/avif/img/TransformImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include <tuple>
#include "Image.hpp"
#include "../ImageMirrorBox.hpp"
#include "../ImageRotationBox.hpp"
Expand Down

0 comments on commit 3532b33

Please sign in to comment.