blob: b81e1075ef51da08816d04b48bcd3c595d63c7e0 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// y4m (raw YCbCr bytes) encoding.
#include <cstdio>
#include "extras/ccsp_imageio.h"
#include "extras/extras.h"
#include "src/dsp/math.h"
namespace WP2 {
//------------------------------------------------------------------------------
WP2Status ToYCbCr(const YUVPlane& ccsp, const CSPMtx& ccsp_to_rgb,
BitDepth ycbcr_bit_depth,
const SamplingTaps* const downsample, YUVPlane* const ycbcr) {
WP2_CHECK_OK(!ccsp.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(ccsp_to_rgb.shift <= 16, WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(ycbcr_bit_depth.num_bits >= 8 && ycbcr_bit_depth.num_bits <= 12,
WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(!ycbcr_bit_depth.is_signed, WP2_STATUS_INVALID_PARAMETER);
// Combine CCSP->RGB and RGB->YCbCr into CCSP->YCbCr.
int32_t ccsp_to_ycbcr_matrix[9] = {};
Multiply(kRGBToYCbCrMatrix, ccsp_to_rgb.mtx(), ccsp_to_ycbcr_matrix);
const uint32_t ccsp_to_ycbcr_shift =
ccsp_to_rgb.shift + kRGBToYCbCrShift - (ycbcr_bit_depth.num_bits - 8);
// TODO(yguyon): Bypass sampling and matrix multiplication if it is ~identity.
YUVPlane source;
if (ccsp.IsDownsampled()) {
WP2_CHECK_STATUS(ycbcr->UpsampleFrom(
ccsp, (downsample != nullptr && *downsample == SamplingTaps::kDownAvg)
? SamplingTaps::kUpNearest
: SamplingTaps::kUpSmooth));
WP2_CHECK_STATUS(source.SetView(*ycbcr));
} else {
WP2_CHECK_STATUS(ycbcr->Resize(ccsp.GetWidth(), ccsp.GetHeight()));
WP2_CHECK_STATUS(source.SetView(ccsp));
}
for (uint32_t y = 0; y < source.GetHeight(); ++y) {
for (uint32_t x = 0; x < source.GetWidth(); ++x) {
// Internal precision is 64 bits as input samples are 16b and matrix 32b.
Multiply<int16_t, int32_t, int16_t, int64_t>(
source.Y.At(x, y), source.U.At(x, y), source.V.At(x, y),
ccsp_to_ycbcr_matrix, ccsp_to_ycbcr_shift,
&ycbcr->Y.At(x, y), &ycbcr->U.At(x, y), &ycbcr->V.At(x, y));
}
}
if (downsample != nullptr) WP2_CHECK_STATUS(ycbcr->Downsample(*downsample));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status ToYCbCr(const ArgbBuffer& rgb, BitDepth ycbcr_bit_depth,
const SamplingTaps* const downsample, YUVPlane* const ycbcr) {
WP2_CHECK_OK(!rgb.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(ycbcr_bit_depth.num_bits >= 8 && ycbcr_bit_depth.num_bits <= 12,
WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(!ycbcr_bit_depth.is_signed, WP2_STATUS_INVALID_PARAMETER);
const uint32_t shift = kRGBToYCbCrShift - (ycbcr_bit_depth.num_bits - 8);
WP2_CHECK_STATUS(ycbcr->Resize(rgb.width(), rgb.height()));
for (uint32_t y = 0; y < rgb.height(); ++y) {
const uint8_t* const row = rgb.GetRow8(y);
for (uint32_t x = 0; x < rgb.width(); ++x) {
const uint8_t* argb = &row[x * WP2FormatBpp(rgb.format())];
Multiply(argb[1], argb[2], argb[3], kRGBToYCbCrMatrix, shift,
&ycbcr->Y.At(x, y), &ycbcr->U.At(x, y), &ycbcr->V.At(x, y));
}
}
if (downsample != nullptr) WP2_CHECK_STATUS(ycbcr->Downsample(*downsample));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status WriteY4M(const YUVPlane& buffer, BitDepth bit_depth, FILE* const fout,
bool write_header) {
WP2_CHECK_OK(fout != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK((bit_depth.num_bits >= CCSPImageReader::kMinBitDepth &&
bit_depth.num_bits <= CCSPImageReader::kMaxBitDepth),
WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(!bit_depth.is_signed, WP2_STATUS_INVALID_PARAMETER);
if (write_header) {
const char* const color_space_tag =
buffer.IsDownsampled() ? "C420" : "C444";
constexpr const char* const kBitDepthTag[] = {"", "p9", "p10", "p11",
"p12"};
STATIC_ASSERT_ARRAY_SIZE(
kBitDepthTag,
CCSPImageReader::kMaxBitDepth - CCSPImageReader::kMinBitDepth + 1);
const char* const bit_depth_tag =
kBitDepthTag[bit_depth.num_bits - CCSPImageReader::kMinBitDepth];
fprintf(fout, "YUV4MPEG2 W%u H%u F25:1 Ip A1:1 %s%s\nFRAME\n", buffer.Y.w_,
buffer.Y.h_, color_space_tag, bit_depth_tag);
} else {
WP2_CHECK_OK(buffer.IsDownsampled(), WP2_STATUS_INVALID_PARAMETER);
}
// 'buffer' should be in YCbCr color space from unsigned alpha-premultiplied
// RGB samples, with meaningful values of Y in [16:235] and Cb, Cr in
// [-128+16:-128+240] (constants for a bit depth of 8).
const uint32_t shift = bit_depth.num_bits - 8;
const int32_t max = bit_depth.max();
const int32_t offset[3] = {16 << shift, 128 << shift, 128 << shift};
for (Channel channel : {kYChannel, kUChannel, kVChannel}) {
const Plane16& plane = buffer.GetChannel(channel);
Vector_u8 row;
WP2_CHECK_ALLOC_OK(row.resize(plane.w_ * (bit_depth.num_bits > 8 ? 2 : 1)));
for (uint32_t y = 0; y < plane.h_; ++y) {
const int16_t* const src = plane.Row(y);
// Offset to unsigned before saving.
if (bit_depth.num_bits <= 8) {
for (uint32_t x = 0; x < plane.w_; ++x) {
row[x] = (uint8_t)Clamp(src[x] + offset[channel], 0, max);
}
} else {
for (uint32_t x = 0; x < plane.w_; ++x) {
const uint16_t v = (uint16_t)Clamp(src[x] + offset[channel], 0, max);
row[2 * x + 0] = (v >> 0) & 0xff;
row[2 * x + 1] = (v >> 8) & 0xff;
}
}
// write row-by-row, for speed
WP2_CHECK_OK(fwrite(&row[0], row.size(), 1, fout) == 1,
WP2_STATUS_BAD_WRITE);
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2