blob: 12e06116961913758b9b067aa6e6e88922839f56 [file] [log] [blame]
// Copyright 2019 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.
// -----------------------------------------------------------------------------
//
// Everything related to visual debug
//
// Author: Skal (pascal.massimino@gmail.com)
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <numeric>
#include <string>
#include <unordered_set>
#include "src/common/color_precision.h"
#include "src/common/lossy/block.h"
#include "src/common/lossy/block_size.h"
#include "src/common/lossy/predictor.h"
#include "src/dec/filters/alpha_filter.h"
#include "src/dec/filters/deblocking_filter.h"
#include "src/dec/filters/directional_filter.h"
#include "src/dec/filters/grain_filter.h"
#include "src/dec/filters/intertile_filter.h"
#include "src/dec/filters/restoration_filter.h"
#include "src/dec/tile_dec.h"
#include "src/dec/wp2_dec_i.h"
#include "src/dsp/dsp.h"
#include "src/dsp/lossless/dspl.h"
#include "src/dsp/math.h"
#include "src/enc/partitioner.h"
#include "src/enc/wp2_enc_i.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/decode.h"
#include "src/wp2/format_constants.h"
//------------------------------------------------------------------------------
// Non-essential code starts here:
#if !defined(WP2_REDUCE_BINARY_SIZE)
namespace WP2 {
//------------------------------------------------------------------------------
// Unused functions at the moment
void SyntaxWriter::PutRawPixels(const Block& block, const YUVPlane& pixels,
ANSEnc* const enc) {
enc->PutRValue(block.x(), SizeBlocks(tile_rect_.width), "DEEP_MATCH_X");
enc->PutRValue(block.y(), SizeBlocks(tile_rect_.height), "DEEP_MATCH_Y");
enc->PutRValue(block.w(), kMaxBlockSize + 1, "DEEP_MATCH_W");
enc->PutRValue(block.h(), kMaxBlockSize + 1, "DEEP_MATCH_H");
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (pixels.GetChannel(channel).IsEmpty()) continue;
for (uint32_t y = 0; y < block.h_pix(); ++y) {
for (uint32_t x = 0; x < block.w_pix(); ++x) {
enc->PutSUValue(pixels.GetChannel(channel).At(x, y),
gparams_->transf_.GetYUVPrecisionBits() + 1,
kChannelStr[channel]);
}
}
}
}
void SyntaxReader::ReadAndCompareRawPixels(const Block& block,
const YUVPlane& pixels,
ANSDec* const dec) {
const uint32_t block_x = dec->ReadRValue(SizeBlocks(width_), "DEEP_MATCH_X");
const uint32_t block_y = dec->ReadRValue(SizeBlocks(height_), "DEEP_MATCH_Y");
const uint32_t block_w = dec->ReadRValue(kMaxBlockSize + 1, "DEEP_MATCH_W");
const uint32_t block_h = dec->ReadRValue(kMaxBlockSize + 1, "DEEP_MATCH_H");
if (block_x != block.x() || block_y != block.y()) assert(false);
if (block_w != block.w() || block_h != block.h()) assert(false);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (pixels.GetChannel(channel).IsEmpty()) continue;
for (uint32_t y = 0; y < block.h_pix(); ++y) {
for (uint32_t x = 0; x < block.w_pix(); ++x) {
const int32_t px = dec->ReadSUValue(
gparams_->transf_.GetYUVPrecisionBits() + 1, kChannelStr[channel]);
if (px != pixels.GetChannel(channel).At(x, y)) assert(false);
}
}
}
}
//------------------------------------------------------------------------------
// Related to BitTrace
WP2Status RegisterBitTrace(const DecoderConfig& config, uint32_t num_bytes,
WP2_OPT_LABEL) {
(void)config, (void)num_bytes, (void)label;
#if defined(WP2_BITTRACE)
if (config.info != nullptr) {
LabelStats* const stats = &config.info->bit_traces[label];
stats->bits += num_bytes * 8.;
stats->num_occurrences += 1;
stats->type = LabelStats::Type::Symbol;
stats->histo[0] += 1;
}
#endif // WP2_BITTRACE
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
#if defined(WP2_BITTRACE)
void CodedBlock::ToBlockInfo(BlockInfo* const blk) const {
const CodingParams& y_params = GetCodingParams(kYChannel);
blk->rect = {x_pix(), y_pix(), w_pix(), h_pix()};
blk->segment_id = id_;
blk->is420 = is420_;
blk->tf_x = (uint8_t)y_params.tf_x();
blk->tf_y = (uint8_t)y_params.tf_y();
blk->y_context_is_constant = y_context_is_constant_;
blk->y_pred = y_params.pred->mode();
blk->has_lossy_alpha = HasLossyAlpha();
if (HasLossyAlpha()) {
const CodingParams& a_params = GetCodingParams(kAChannel);
blk->a_pred = a_params.pred->mode();
}
blk->uv_pred = GetCodingParams(kUChannel).pred->mode();
std::memcpy(blk->pred_scores, pred_scores_, sizeof(pred_scores_));
std::memset(blk->coeffs, 0, sizeof(blk->coeffs));
std::fill(blk->split_tf, blk->split_tf + 4, false);
std::fill(blk->encoding_method[0], blk->encoding_method[0] + 4 * 4, -1);
for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (c == kAChannel && !HasLossyAlpha()) continue;
blk->split_tf[c] = GetCodingParams(c).split_tf;
for (uint32_t i = 0; i < GetNumTransforms(c); ++i) {
blk->encoding_method[c][i] = (int8_t)method_[c][i];
std::copy(coeffs_[c][i], coeffs_[c][i] + NumCoeffsPerTransform(c),
blk->coeffs[c][i]);
}
}
blk->bits = 0; // unknown for now
}
#endif // WP2_BITTRACE
//------------------------------------------------------------------------------
// Declared in segment.h.
const Argb32b kSegmentColors[kMaxNumSegments]{
{0xff, 0x30, 0xf0, 0xd0}, // Teal
{0xff, 0x3d, 0x70, 0xf0}, // Blue
{0xff, 0xb3, 0x30, 0xf0}, // Purple
{0xff, 0xe4, 0x30, 0xf0}, // Pink
{0xff, 0xf0, 0x30, 0x50}, // Red
{0xff, 0xf0, 0x8c, 0x4d}, // Orange
{0xff, 0xf0, 0xd0, 0x30}, // Yellow
{0xff, 0x20, 0xf0, 0x60}, // Green
};
//------------------------------------------------------------------------------
// Sets pixel at (x, y) with 'color' if it fits in 'debug_output' boundaries.
static void MarkPixelForVDebug(int32_t x, int32_t y, Argb32b color,
ArgbBuffer* const debug_output) {
assert(debug_output->format() == WP2_Argb_32);
if (x >= 0 && x < (int32_t)debug_output->width() && // Within bounds.
y >= 0 && y < (int32_t)debug_output->height()) {
ToUInt8(color, debug_output->GetRow8(y) + x * 4);
}
}
// Sets pixels with 'color' (or respectively 'bg_color' if not fully
// transparent), in the rectangle (w, h) starting at (x, y), for non-space
// characters in 'mask' (or respectively spaces).
static void DrawPixelArray(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
Argb32b color, Argb32b bg_color,
const std::vector<std::string>& mask, uint32_t scale,
ArgbBuffer* const debug_output) {
w += x;
h += y;
for (uint32_t l = 0; l < mask.size(); ++l) {
for (uint32_t ls = 0; ls < scale; ++ls) {
const uint32_t final_y = y + l * scale + ls;
if (final_y >= h) break;
for (uint32_t c = 0; mask[l][c] != '\0'; ++c) {
for (uint32_t cs = 0; cs < scale; ++cs) {
const uint32_t final_x = x + c * scale + cs;
if (final_x >= w) break;
uint8_t* const pixel = debug_output->GetRow8(final_y) + final_x * 4;
if (mask[l][c] != ' ') {
ToUInt8(color, pixel);
} else if (bg_color.a > 0) {
ToUInt8(bg_color, pixel);
}
}
}
}
}
}
//------------------------------------------------------------------------------
void VDDrawUndefinedPattern(ArgbBuffer* const debug_output) {
// Easily recognizable striped background.
for (uint32_t y = 0; y < debug_output->height(); ++y) {
for (uint32_t x = 0; x < debug_output->width(); ++x) {
ToUInt8(((x % 10) == (y % 10)) ? Argb32b{0xff, 0x22, 0x22, 0x22}
: Argb32b{0xff, 0x00, 0x00, 0x00},
debug_output->GetRow8(y) + x * 4);
}
}
// Display "undefined" if the 'debug_output' content is not overwritten.
DrawPixelArray(debug_output->width() / 3, debug_output->height() / 3,
debug_output->width() - debug_output->width() / 3,
debug_output->height() - debug_output->height() / 3,
Argb32b{0xff, 0x55, 0x55, 0x55}, Argb32b{0, 0, 0, 0},
{
" # # # #",
"# # # # ## ## # # # ## ##",
"# # ## # # # #### ### # ## # #### # #",
"# ## # # # # # # # # # # # #",
" # # # # ## ## # # # # ## ##",
},
/*scale=*/Clamp(debug_output->width() / 128u, 1u, 4u),
debug_output);
}
//------------------------------------------------------------------------------
// Returns true if the 'str' contains the 'token'. Tokens are separated by '/'.
static bool VDMatch(const char str[], const char token[]) {
if (str != nullptr && token != nullptr) {
const uint32_t token_length = std::strlen(token);
if (token_length == 0) return true;
for (const char* pos = str; (pos = std::strstr(pos, token)) != nullptr;
pos += token_length) {
if ((pos == str || pos[-1] == '/') && // Beginning of a token.
(pos[token_length] == '\0' || pos[token_length] == '/')) { // End.
return true; // A complete token is matched.
}
}
}
return false;
}
static Channel VDChannel(const char visual_debug[]) {
if (VDMatch(visual_debug, "y")) return kYChannel;
if (VDMatch(visual_debug, "u")) return kUChannel;
if (VDMatch(visual_debug, "v")) return kVChannel;
assert(VDMatch(visual_debug, "a"));
return kAChannel;
}
// Returns true if an 'index' exists as a token in 'str' and outputs it.
static bool VDIndex(const char str[], uint32_t* const index) {
if (str != nullptr) {
// For each 'token' in 'str'.
for (const char* token = str; *token != '\0'; ++token) {
if (*token == '/') continue; // Empty token.
bool keep_token = true;
uint32_t token_value = 0;
static constexpr uint32_t max_value =
std::numeric_limits<decltype(token_value)>::max() / 10;
// For each character in 'token'.
for (const char* pos = token; *pos != '\0'; ++pos) {
if (*pos == '/') break; // Token ended.
if (*pos < '0' || *pos > '9') keep_token = false;
if (keep_token && token_value > max_value) keep_token = false;
if (keep_token) token_value = token_value * 10 + (*pos - '0');
}
if (keep_token) {
*index = token_value;
return true;
}
}
}
return false;
}
// Returns true if the point 'config.info->selection' is in 'rect'.
template <typename TConfig>
static bool VDSelected(const Rectangle& rect, const TConfig& config) {
return (config.info != nullptr && config.info->selection.width > 0 &&
config.info->selection.height > 0 &&
config.info->selection.x >= rect.x &&
config.info->selection.x < rect.x + rect.width &&
config.info->selection.y >= rect.y &&
config.info->selection.y < rect.y + rect.height);
}
// Declared in wp2_dec_i.h.
bool VDMatch(const DecoderConfig& config, const char token[]) {
if (config.info != nullptr) return VDMatch(config.info->visual_debug, token);
return false;
}
Channel VDChannel(const DecoderConfig& config) {
return VDChannel(config.info->visual_debug);
}
bool VDSelected(uint32_t tile_x, uint32_t tile_y, const Rectangle& rect,
const DecoderConfig& config) {
return VDSelected({tile_x + rect.x, tile_y + rect.y, rect.width, rect.height},
config);
}
// Declared in wp2_enc_i.h.
bool VDMatch(const EncoderConfig& config, const char token[]) {
if (config.info != nullptr) return VDMatch(config.info->visual_debug, token);
return false;
}
Channel VDChannel(const EncoderConfig& config) {
return VDChannel(config.info->visual_debug);
}
bool VDSelected(uint32_t tile_x, uint32_t tile_y, const Rectangle& rect,
const EncoderConfig& config) {
return VDSelected({tile_x + rect.x, tile_y + rect.y, rect.width, rect.height},
config);
}
float VDGetParam(const EncoderConfig& config, float default_value) {
float value;
return (config.info != nullptr && config.info->visual_debug != nullptr &&
std::sscanf(config.info->visual_debug, "p=%f", &value) == 1)
? value
: default_value;
}
//------------------------------------------------------------------------------
// Appends a formatted literal to a std::string. Avoids <sstream>.
#define WP2SAppend(str_ptr, ...) \
do { \
const uint32_t size = std::snprintf(nullptr, 0, __VA_ARGS__) + 1; \
(str_ptr)->resize((str_ptr)->size() + size); \
std::snprintf((char*)&(str_ptr)->at((str_ptr)->size() - size), size, \
__VA_ARGS__); \
(str_ptr)->pop_back(); /* Remove ending '\0' */ \
} while (false)
// Maps a value in [0; num_values) to the [-512;512] range.
static int16_t ToPixel10(uint32_t value, uint32_t num_values) {
if (num_values <= 1) return 0;
return (int16_t)(DivRound(value * 1024u, num_values - 1u) - 512);
}
// Maps a value in [0; num_values) to the [0;255] range.
static int16_t ToPixel8(uint32_t value, uint32_t num_values) {
if (num_values <= 1) return 0;
return (uint8_t)DivRound(value * 255u, num_values - 1u);
}
static const Argb32b ColorMult(Argb32b color, float mutiplier) {
return {color.a, (uint8_t)(color.r * mutiplier),
(uint8_t)(color.g * mutiplier), (uint8_t)(color.b * mutiplier)};
}
//------------------------------------------------------------------------------
void CodedBlock::StoreTransform(const DecoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
ArgbBuffer* const debug_output) const {
const CodingParams& params = GetCodingParams(kYChannel);
if (VDMatch(config, "xy")) {
} else {
}
const WP2TransformType tf_x = kTfX[params.tf];
const WP2TransformType tf_y = kTfY[params.tf];
const bool all_zero = !HasCoeffs(kYChannel);
static constexpr std::array<Argb32b, kNumTransforms> kColors = {
{{0xff, 0xff, 0xff, 0xff}, // DCT is white.
{0xff, 0xff, 0, 0xff}, // Adst is pink.
{0xff, 0, 0xff, 0xff}, // Hadamaart is
{0xff, 0xff, 0xff, 0}}}; // Identity is yellow.
const Argb32b x_color = ColorMult(kColors[tf_x], all_zero ? 0.3f : 1.f);
const Argb32b y_color = ColorMult(kColors[tf_y], all_zero ? 0.3f : 1.f);
const uint32_t w = std::min(w_pix(), debug_output->width() - x_pix());
const uint32_t h = std::min(h_pix(), debug_output->height() - y_pix());
const Rectangle r_px{x_pix(), y_pix(), w, h};
if (VDMatch(config, "x") || (tf_x == tf_y)) {
debug_output->Fill(r_px, x_color);
} else if (VDMatch(config, "y")) {
debug_output->Fill(r_px, y_color);
} else {
// Two different transforms. Show them as a checkerboard.
const Argb32b x_color_dark = ColorMult(x_color, 0.8f);
const Argb32b y_color_dark = ColorMult(y_color, 0.8f);
for (uint32_t y = 0; y < h; ++y) {
for (uint32_t x = 0; x < w; ++x) {
const Rectangle pixel = {x_pix() + x, y_pix() + y, 1, 1};
if (((x >> 1) % 2) == ((y >> 1) % 2)) {
debug_output->Fill(pixel, (y % 2) ? x_color : x_color_dark);
} else {
debug_output->Fill(pixel, (x % 2) ? y_color : y_color_dark);
}
}
}
}
if (VDSelected(tile_x, tile_y, r_px, config)) {
debug_output->DrawRect(r_px, Argb32b{0xff, 0xff, 0x00, 0x00});
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "Block at %u, %u px (w %u, h %u)\n", x_pix() + tile_x,
y_pix() + tile_y, w_pix(), h_pix());
if (all_zero) WP2SAppend(str_ptr, "All zero\n");
WP2SAppend(str_ptr, "Transform x: %s\n", WP2TransformNames[params.tf_x()]);
WP2SAppend(str_ptr, "Transform y: %s\n", WP2TransformNames[params.tf_y()]);
}
}
// If the block is currently selected, outputs the pixel values from 'pixels'
// (in local block coordinates) to the debug selection_info.
static void OutputPixelValuesWithContext(const DecoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
const CodedBlock& cb,
const Plane16& pixels) {
if (VDSelected(tile_x, tile_y, cb.blk().rect_pix(), config)) {
// Print pixels with the same formatting as "prediction/modes".
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "Block at %u, %u px (w %u, h %u)\n",
tile_x + cb.x_pix(), tile_y + cb.y_pix(), cb.w_pix(),
cb.h_pix());
WP2SAppend(str_ptr, "\n\n\n\n"); // See DisplayPredictionMode().
const int16_t* const context =
cb.GetContext(VDChannel(config), kContextSmall);
const int16_t* const context_right =
cb.GetContext(VDChannel(config), kContextExtendRight);
const int16_t* const context_left =
cb.GetContext(VDChannel(config), kContextExtendLeft);
*str_ptr += GetContextAndBlockPixelsStr(
context, context_right, context_left, cb.w_pix(), cb.h_pix(),
pixels.Row(0), pixels.Step());
}
}
void CodedBlock::StoreResiduals(const DecoderConfig& config, uint32_t tile_x,
uint32_t tile_y, const QuantMtx& quant,
Channel channel,
Plane16* const dst_plane) const {
const CodedBlock::CodingParams& params = GetCodingParams(channel);
const BlockSize split_size = GetSplitSize(dim(), params.split_tf);
const uint32_t split_w = BlockWidthPix(split_size);
const uint32_t split_h = BlockHeightPix(split_size);
const TrfSize tf_size = tdim(channel);
const bool reduced_transform =
(channel == kUChannel || channel == kVChannel) && is420_;
uint32_t tf_i = 0;
std::string* const str_ptr = &config.info->selection_info;
for (uint32_t split_y = 0; split_y < h_pix(); split_y += split_h) {
for (uint32_t split_x = 0; split_x < w_pix(); split_x += split_w) {
int32_t res[kMaxBlockSizePix2];
// TODO(skal): rnd_mtx
quant.Dequantize(coeffs_[channel][tf_i], num_coeffs_[channel][tf_i],
tf_size, res);
WP2InvTransform2D(res, params.tf_x(), params.tf_y(), split_w, split_h,
res, reduced_transform);
const bool selected = VDSelected(
tile_x, tile_y,
{x_pix() + split_x, y_pix() + split_y, split_w, split_h}, config);
for (uint32_t y = 0; y < split_h; ++y) {
for (uint32_t x = 0; x < split_w; ++x) {
dst_plane->At(x_pix() + split_x + x, y_pix() + split_y + y) =
res[x + split_w * y];
if (selected) WP2SAppend(str_ptr, "%4d ", res[x + split_w * y]);
}
if (selected) WP2SAppend(str_ptr, "\n");
}
++tf_i;
}
}
}
void CodedBlock::StoreOriginalResiduals(
const EncoderConfig& config, uint32_t tile_pos_x, uint32_t tile_pos_y,
int32_t original_res[kMaxNumTransformsPerBlock][kMaxBlockSizePix2],
Plane16* const dst_plane) const {
const BlockSize split_size =
GetSplitSize(dim(), GetCodingParams(VDChannel(config)).split_tf);
const uint32_t split_w = BlockWidthPix(split_size);
const uint32_t split_h = BlockHeightPix(split_size);
uint32_t tf_i = 0;
std::string* const str_ptr = &config.info->selection_info;
for (uint32_t split_y = 0; split_y < h_pix(); split_y += split_h) {
for (uint32_t split_x = 0; split_x < w_pix(); split_x += split_w) {
const bool selected = VDSelected(
tile_pos_x, tile_pos_y,
{x_pix() + split_x, y_pix() + split_y, split_w, split_h}, config);
for (uint32_t k = 0, y = 0; y < split_h; ++y) {
for (uint32_t x = 0; x < split_w; ++x, ++k) {
const int32_t value = original_res[tf_i][k];
dst_plane->At(x_pix() + split_x + x, y_pix() + split_y + y) = value;
if (selected) WP2SAppend(str_ptr, "%4d ", value);
}
if (selected) WP2SAppend(str_ptr, "\n");
}
++tf_i;
}
}
}
static Argb32b CtxtToColor(int16_t context) {
const uint8_t v = Clamp(DivRound((context + 512) * 255, 1023), 0x00, 0xff);
return Argb32b{0xff, v, v, v};
}
// Draws the context used for the prediction of 'cb'.
static void DisplayPredContext(const CodedBlock& cb, Channel channel,
bool draw_red_border,
ArgbBuffer* const debug_output) {
const uint32_t x = cb.x_pix(), y = cb.y_pix(), w = cb.w_pix(), h = cb.h_pix();
const Rectangle rect = {x, y, std::min(w, debug_output->width() - x),
std::min(h, debug_output->height() - y)};
if (draw_red_border) { // Encircle the left, top, right context lines.
const uint32_t bx = SafeSub(x, 2u), by = SafeSub(y, 2u);
const Argb32b red = {0xff, 0xff, 0, 0};
debug_output->DrawRect({bx, by, x - bx + 1, y + rect.height * 2 - by}, red);
debug_output->DrawRect({bx, by, x + rect.width - bx, y - by + 1}, red);
debug_output->DrawRect({x + w - 1, by, 3, y + rect.height - by}, red);
debug_output->DrawRect(
{x + w - 1, by,
2 + ContextSize(kContextExtendRight, w, h) - (h + 1 + w), 3},
red);
}
const int16_t* const context = cb.GetContext(channel, kContextSmall);
if (x >= 1) {
for (uint32_t j = 0; j < rect.height; ++j) { // Left
debug_output->Fill({x - 1, y + j, 1, 1}, CtxtToColor(context[h - j - 1]));
}
if (y >= 1) { // Top-left
debug_output->Fill({x - 1, y - 1, 1, 1}, CtxtToColor(context[h]));
}
}
if (y >= 1) {
for (uint32_t i = 0; i < rect.width; ++i) { // Top
debug_output->Fill({x + i, y - 1, 1, 1}, CtxtToColor(context[h + 1 + i]));
}
if (x + w < debug_output->width()) { // Top-right
debug_output->Fill({x + w, y - 1, 1, 1}, CtxtToColor(context[h + 1 + w]));
}
}
if (x + w < debug_output->width()) {
for (uint32_t j = 0; j < rect.height; ++j) { // Right
debug_output->Fill({x + w, y + j, 1, 1},
CtxtToColor(context[h + 1 + w + 1 + j]));
}
}
// Extended right
const int16_t* const tr_context = cb.GetContext(channel, kContextExtendRight);
const uint32_t tr_size = ContextSize(kContextExtendRight, w, h);
for (uint32_t i = h + 1 + w + 1, tr_x = x + w + 1; i < tr_size; ++i, ++tr_x) {
debug_output->Fill({tr_x, y - 1, 1, 1}, CtxtToColor(tr_context[i]));
}
// Extended left
const int16_t* const left_context =
cb.GetContext(channel, kContextExtendLeft);
for (uint32_t j = h; j < 2 * h; ++j) {
if (y + j + 1 >= debug_output->height()) break;
debug_output->Fill({x - 1, y + j, 1, 1},
CtxtToColor(left_context[2 * h - j - 1]));
}
}
static constexpr const char* kSearchTypeNames[] = {"tf", "pred&tf", "split"};
void CodedBlock::StorePredictionScore(
const EncoderConfig& config, const Rectangle& tile_rect, Channel channel,
const Predictor& pred, TransformPair tf, float dist, float lambda,
float res_rate, float pred_rate, float tf_rate, float score, bool is_best,
SearchType type) const {
if (!VDMatch(config, "prediction-scores")) return;
if (VDChannel(config) != channel) return;
ArgbBuffer debug_output; // Tile view.
WP2_ASSERT_STATUS(debug_output.SetView(config.info->debug_output, tile_rect));
const Rectangle rect = {
// Block rect.
x_pix(), y_pix(), std::min(w_pix(), debug_output.width() - x_pix()),
std::min(h_pix(), debug_output.height() - y_pix())};
const bool selected = VDSelected(tile_rect.x, tile_rect.y, rect, config);
debug_output.Fill(rect, Argb32b{0xff, 0x00, 0x22, 0x00});
if (selected) {
debug_output.DrawRect(rect, Argb32b{0xff, 0xff, 0x33, 0x33});
// Check the formula did not change.
assert(std::abs(dist + lambda * (res_rate + pred_rate + tf_rate) - score) <
0.001f);
// Print at least all better predictors. Skip worse ones if too many.
std::string* const str_ptr = &config.info->selection_info;
if (is_best || std::count(str_ptr->begin(), str_ptr->end(), '\n') < 30) {
WP2SAppend(str_ptr, "Score %9.1f = dist %9.1f", score, dist);
WP2SAppend(str_ptr,
" + lambda %9.1f * (res %7.4f + pred %7.4f + tf %7.4f)",
lambda, res_rate, pred_rate, tf_rate);
WP2SAppend(str_ptr, " %s (%s - %s/%s x%u) (%s)\n",
is_best ? "best" : " ", pred.GetName().c_str(),
WP2TransformNames[kTfX[tf]], WP2TransformNames[kTfY[tf]],
GetNumTransforms(channel), kSearchTypeNames[(int)type]);
} else {
// Print in console instead.
printf(
"Block at (%d %d) Score %9.1f = dist %9.1f + lambda %9.1f * (res "
"%7.4f + pred %7.4f + "
"tf %7.4f) %s (%s - %s/%s x%u) (%s)\n",
x_pix(), y_pix(), score, dist, lambda, res_rate, pred_rate, tf_rate,
is_best ? "best" : " ", pred.GetName().c_str(),
WP2TransformNames[kTfX[tf]], WP2TransformNames[kTfY[tf]],
GetNumTransforms(channel), kSearchTypeNames[(int)type]);
}
}
DisplayPredContext(*this, channel, /*draw_red_border=*/selected,
&debug_output);
}
// Prints the selected block prediction to 'config.info->selection_info' and
// fills either the 'raw_prediction' (values) or the 'debug_output' (mode).
static void DisplayPredictionMode(const DecoderConfig& config,
const Rectangle& tile_rect,
const CodedBlock& cb, uint32_t num_preds,
const Predictor& p, Channel channel,
const Rectangle& block_rect, bool selected,
Plane16* const raw_prediction,
ArgbBuffer* const debug_output) {
if (raw_prediction != nullptr) {
Plane16 dst_view;
WP2_ASSERT_STATUS(dst_view.SetView(*raw_prediction, cb.blk().rect_pix()));
const Predictor& pred = *cb.GetCodingParams(channel).pred;
pred.Predict(cb, channel, dst_view.Row(0), dst_view.Step());
} else {
assert(debug_output != nullptr);
const uint8_t c = ToPixel8(p.mode(), num_preds);
debug_output->Fill(block_rect, Argb32b{255, c, c, c});
p.Draw(block_rect, debug_output);
if (selected) {
debug_output->DrawRect(block_rect, Argb32b{0xff, 0xff, 0x33, 0x33});
}
}
if (selected) {
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "Prediction block at %u, %u px (w %u, h %u)\n\n",
block_rect.x + tile_rect.x, block_rect.y + tile_rect.y,
block_rect.width, block_rect.height);
WP2SAppend(str_ptr, " %u: %s\n", p.mode(), p.GetName().c_str());
if (!VDMatch(config, "short")) {
const std::string prediction = p.GetPredStr(cb, channel);
WP2SAppend(str_ptr, "\nPrediction:\n%s", prediction.c_str());
}
}
}
void CodedBlock::StorePredictionModes(const DecoderConfig& config,
const Rectangle& tile_rect,
Channel channel, const Predictors& preds,
Plane16* const raw_prediction,
ArgbBuffer* const debug_output) const {
const Rectangle r_px{x_pix(), y_pix(),
std::min(w_pix(), tile_rect.width - x_pix()),
std::min(h_pix(), tile_rect.height - y_pix())};
// Whole block
const bool selected = VDSelected(tile_rect.x, tile_rect.y, r_px, config);
const CodingParams& params = GetCodingParams(channel);
DisplayPredictionMode(config, tile_rect, *this, preds.GetMaxMode(),
*params.pred, channel, r_px, selected, raw_prediction,
debug_output);
if (selected && debug_output != nullptr) {
DisplayPredContext(*this, channel, /*draw_red_border=*/true, debug_output);
}
}
void CodedBlock::AppendOriginalPixels(const DecoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
const CSPTransform& csp_tranform,
ArgbBuffer* const debug_output) const {
if (VDSelected(tile_x, tile_y, {x_pix(), y_pix(), w_pix(), h_pix()},
config)) {
const Channel channel = VDChannel(config);
YUVPlane original_yuv; // 'debug_output' should be the original RGB pixels.
WP2_ASSERT_STATUS(
original_yuv.Import(*debug_output, /*import_alpha=*/true, csp_tranform,
/*resize_if_needed=*/true, /*pad=*/kPredWidth));
// Use a dummy CodedBlock to get the 'context' out of 'original_yuv'.
CodedBlock cb;
cb.SetDimDefault(blk(), /*full_left_ctx=*/true);
if (cb.x_pix() + cb.w_pix() < original_yuv.Y.w_) {
cb.right_occupancy_ = cb.y() + cb.h(); // Full right context.
}
ContextCache context_cache;
cb.SetContextInput(original_yuv, &context_cache);
YUVPlane view;
WP2_ASSERT_STATUS(view.SetView(original_yuv, blk().rect_pix()));
OutputPixelValuesWithContext(config, tile_x, tile_y, cb,
view.GetChannel(channel));
}
}
void CodedBlock::AppendCompressedPixels(const DecoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
ArgbBuffer* const debug_output) const {
if (VDMatch(config, "r") || VDMatch(config, "g") || VDMatch(config, "b")) {
return;
}
if (VDSelected(tile_x, tile_y, blk().rect_pix(), config)) {
OutputPixelValuesWithContext(config, tile_x, tile_y, *this,
out_.GetChannel(VDChannel(config)));
WP2SAppend(
&config.info->selection_info,
"Compressed values are before applying post processing filters\n");
}
}
void SyntaxReader::StoreBitCost(const DecoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
const Block& block,
Plane16* const dst_plane) const {
(void)config, (void)tile_x, (void)tile_y, (void)block, (void)dst_plane;
#if defined(WP2_BITTRACE)
const Rectangle& rect = block.rect_pix();
const std::string prefix =
VDMatch(config, "overall") ? "" : kCoeffsStr[VDChannel(config)];
double num_bits = 0.;
for (const auto& bt : dec_->GetBitTracesCustom()) {
if (bt.first.size() >= prefix.size() &&
std::strncmp(bt.first.c_str(), prefix.c_str(), prefix.size()) == 0) {
num_bits += bt.second.bits;
}
}
// Max value is 6bpp (which is a lot!). Use 8b fixed point precision.
const int16_t value = ToPixel10(
(uint32_t)std::lround(num_bits * 256. / rect.GetArea()), 6u << 8);
dst_plane->Fill(rect, value);
if (VDSelected(tile_x, tile_y, rect, config)) {
dst_plane->DrawRect(rect, 127);
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nBlock at %u, %u px\n\n", rect.x, rect.y);
WP2SAppend(str_ptr, "%f bits over %u pixels\n", num_bits,
(uint32_t)rect.GetArea());
WP2SAppend(str_ptr, " = %f bpp\n", num_bits / rect.GetArea());
}
#endif // defined(WP2_BITTRACE)
}
void CodedBlock::StoreCoeffMethod(const DecoderConfig& config,
Plane16* const dst_plane) const {
const Channel channel = VDChannel(config);
const BlockSize split_size =
GetSplitSize(dim(), GetCodingParams(channel).split_tf);
const uint32_t split_w = BlockWidthPix(split_size);
const uint32_t split_h = BlockHeightPix(split_size);
uint32_t tf_i = 0;
for (uint32_t split_y = 0; split_y < h_pix(); split_y += split_h) {
for (uint32_t split_x = 0; split_x < w_pix(); split_x += split_w) {
dst_plane->Fill(
{x_pix() + split_x, y_pix() + split_y, split_w - 1, split_h - 1},
ToPixel10((uint32_t)method_[channel][tf_i],
(uint32_t)EncodingMethod::kNumMethod));
++tf_i;
}
}
}
// Similar to CflPredictor::LinearRegression, but uses the actual block
// instead of the block context.
void CodedBlock::CflLinearRegression(Channel channel, int16_t yuv_min,
int16_t yuv_max, float* const a,
float* const b,
std::string* const debug_str) const {
int16_t a_int, b_int;
CflPredictor cfl_predictor(yuv_min, yuv_max);
cfl_predictor.BlockLinearRegression(channel, *this, &a_int, &b_int);
*a = (float)a_int / (1 << CflPredictor::kAPrecShift);
*b = (float)b_int / (1 << CflPredictor::kBPrecShift);
if (debug_str != nullptr) {
WP2SAppend(debug_str, "Chroma = %.2f * luma + %.2f\n", *a, *b);
}
}
void CodedBlock::StoreBestCflSlope(Channel channel, int16_t yuv_min,
int16_t yuv_max, Plane16* const dst_plane,
std::string* const debug_str) const {
float a, b;
CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str);
dst_plane->Fill(blk_.rect_pix(), (int16_t)std::lround(a * 100.f));
}
void CodedBlock::StoreCflSlope(Channel channel, int16_t yuv_min,
int16_t yuv_max, Plane16* const dst_plane,
std::string* const debug_str) const {
int16_t a, b;
CflPredictor cfl_predictor(yuv_min, yuv_max);
cfl_predictor.ContextLinearRegression(channel, *this, &a, &b);
dst_plane->Fill(
blk_.rect_pix(),
(int16_t)std::lround(a * 100.f / (1 << CflPredictor::kAPrecShift)));
if (debug_str != nullptr) {
WP2SAppend(debug_str, "Chroma = %.2f * luma + %.2f\n",
(float)a / (1 << CflPredictor::kAPrecShift),
(float)b / (1 << CflPredictor::kBPrecShift));
}
}
void CodedBlock::StoreCflIntercept(Channel channel, int16_t yuv_min,
int16_t yuv_max, Plane16* const dst_plane,
std::string* const debug_str) const {
int16_t a, b;
CflPredictor cfl_predictor(yuv_min, yuv_max);
cfl_predictor.ContextLinearRegression(channel, *this, &a, &b);
dst_plane->Fill(
blk_.rect_pix(),
(int16_t)std::lround(b / (1 << (CflPredictor::kBPrecShift + 1))));
if (debug_str != nullptr) {
WP2SAppend(debug_str, "Chroma = %.2f * luma + %.2f\n",
(float)a / (1 << CflPredictor::kAPrecShift),
(float)b / (1 << CflPredictor::kBPrecShift));
}
}
void CodedBlock::StoreBestCflIntercept(Channel channel, int16_t yuv_min,
int16_t yuv_max,
Plane16* const dst_plane,
std::string* const debug_str) const {
float a, b;
CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str);
dst_plane->Fill(blk_.rect_pix(), (int16_t)std::lround(b / 2.f));
}
void CodedBlock::StoreBestCflPrediction(Channel channel, int16_t yuv_min,
int16_t yuv_max,
Plane16* const dst_plane,
std::string* const debug_str) const {
const Plane16& luma = out_.GetChannel(kYChannel);
float a, b;
CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str);
for (uint32_t y = 0; y < h_pix(); ++y) {
for (uint32_t x = 0; x < w_pix(); ++x) {
const int32_t v = std::lround(a * luma.At(x, y) + b);
dst_plane->At(x_pix() + x, y_pix() + y) = (int16_t)v;
}
}
}
void CodedBlock::StoreBestCflResiduals(Channel channel, int16_t yuv_min,
int16_t yuv_max,
Plane16* const dst_plane,
std::string* const debug_str) const {
const Plane16& luma = out_.GetChannel(kYChannel);
const Plane16& chroma = in_.GetChannel(channel);
float a, b;
CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str);
for (uint32_t y = 0; y < h_pix(); ++y) {
for (uint32_t x = 0; x < w_pix(); ++x) {
const int32_t v = std::lround(a * luma.At(x, y) + b);
dst_plane->At(x_pix() + x, y_pix() + y) = (int16_t)(chroma.At(x, y) - v);
}
}
}
//------------------------------------------------------------------------------
void CodedBlock::StoreErrorDiffusion(const EncoderConfig& config,
uint32_t tile_x, uint32_t tile_y,
Plane16* const dst_plane) const {
if (!VDMatch(config, "error-diffusion")) return;
const Channel channel = VDChannel(config);
if (VDSelected(tile_x, tile_y, blk().rect_pix(), config)) {
WP2SAppend(&config.info->selection_info,
"Block at %4u, %4u (%2u x %2u px)\n", tile_x + x_pix(),
tile_y + y_pix(), w_pix(), h_pix());
WP2SAppend(&config.info->selection_info,
"Propagated error = %d | New error = %d\n", dc_error_[channel],
dc_error_next_[channel]);
}
const int16_t error = VDMatch(config, "propagated-error")
? dc_error_[channel]
: dc_error_next_[channel];
dst_plane->Fill(blk_.rect_pix(), error / 2);
}
//------------------------------------------------------------------------------
void Block::Draw(YUVPlane* const yuv) const {
const uint32_t x0 = x_pix();
const uint32_t y0 = y_pix();
for (uint32_t i = 0; i < w_pix(); ++i) {
yuv->Y.At(x0 + i, y0) = 0x1ff;
yuv->U.At(x0 + i, y0) = 0x100;
yuv->V.At(x0 + i, y0) = 0x100;
}
for (uint32_t j = 0; j < h_pix(); ++j) {
yuv->Y.At(x0, y0 + j) = 0x1ff;
yuv->U.At(x0, y0 + j) = 0x100;
yuv->V.At(x0, y0 + j) = 0x100;
}
}
//------------------------------------------------------------------------------
WP2Status CodedBlock::Draw(const DecoderConfig& config, uint32_t tile_x,
uint32_t tile_y, const GlobalParams& gparams,
ArgbBuffer* const debug_output) const {
const uint32_t x = x_pix(), bw = w_pix();
const uint32_t y = y_pix(), bh = h_pix();
const uint32_t w = std::min(bw, debug_output->width() - x); // Border
const uint32_t h = std::min(bh, debug_output->height() - y);
const uint32_t mid_w = std::min(bw - 2, debug_output->width() - (x + 1));
const uint32_t mid_h = std::min(bh - 2, debug_output->height() - (y + 1));
if (VDMatch(config, "partition")) {
if (VDMatch(config, "split-tf")) {
const BlockSize split_size =
GetSplitSize(dim(), GetCodingParams(kYChannel).split_tf);
const uint32_t split_w = BlockWidthPix(split_size);
const uint32_t split_h = BlockHeightPix(split_size);
for (uint32_t split_y = 0; split_y < h_pix(); split_y += split_h) {
for (uint32_t split_x = 0; split_x < w_pix(); split_x += split_w) {
debug_output->Fill({x + split_x, y + split_y, 1, split_h},
Argb32b{0xff, 0x00, 0x30, 0xf0});
debug_output->Fill({x + split_x, y + split_y, split_w, 1},
Argb32b{0xff, 0x00, 0x40, 0xf0});
}
}
}
debug_output->Fill({x, y, 1, h}, Argb32b{0xff, 0xff, 0xff, 0x30});
debug_output->Fill({x, y, w, 1}, Argb32b{0xff, 0xff, 0xff, 0x40});
} else if (VDMatch(config, "segment-ids") ||
VDMatch(config, "quantization")) {
const bool is_alpha =
VDMatch(config, "quantization") && (VDChannel(config) == kAChannel);
if (is_alpha && !HasLossyAlpha()) {
debug_output->Fill({x, y, w, h}, Argb32b{255, 0, 0, 0});
return WP2_STATUS_OK;
}
const Segment& segment = gparams.segments_[is_alpha ? 0 : id_];
Argb32b color;
if (VDMatch(config, "segment-ids")) {
color = kSegmentColors[id_];
if (mid_w > 0 && mid_h > 0) {
debug_output->Fill({x + 1, y + 1, mid_w, mid_h}, color);
}
} else {
// showing quantization
uint16_t quant[kNumQuantZones];
segment.GetQuantSteps(quant, VDChannel(config));
// Quant expressed in [0 .. 1]
const float normalized_quant = (float)quant[0] / kQFactorMax;
// Gamma it not really necessary but just to show I studied colors.
constexpr float kGamma = 2.2f;
const float red = std::pow(normalized_quant, 1.f / kGamma);
const float green = std::pow((1.f - normalized_quant), 1.f / kGamma);
// Max out saturation.
const float mult = std::min(1.f / red, 1.f / green) * 255.f;
color = Argb32b{255, (uint8_t)std::min((uint32_t)(red * mult), 255u),
(uint8_t)std::min((uint32_t)(green * mult), 255u), 0};
debug_output->Fill({x, y, w, h}, color);
}
if (VDSelected(tile_x, tile_y, {x, y, bw, bh}, config)) {
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nSegment id: %u\n", id_);
if (segment.use_quality_factor_) {
const float q = 1.f * segment.quality_factor_ / kQFactorMax;
WP2SAppend(str_ptr, " quality_factor_: %d (q ~= %.1f)\n",
segment.quality_factor_, kMaxLossyQuality * (1.f - q));
}
if (is_alpha) {
WP2SAppend(str_ptr, " A quant_steps: %d %d %d %d\n",
segment.quant_steps_[kAChannel][0],
segment.quant_steps_[kAChannel][1],
segment.quant_steps_[kAChannel][2],
segment.quant_steps_[kAChannel][3]);
} else {
WP2SAppend(str_ptr, " Y quant_steps: %d %d %d %d\n",
segment.quant_steps_[kYChannel][0],
segment.quant_steps_[kYChannel][1],
segment.quant_steps_[kYChannel][2],
segment.quant_steps_[kYChannel][3]);
WP2SAppend(str_ptr, " U quant_steps: %d %d %d %d\n",
segment.quant_steps_[kUChannel][0],
segment.quant_steps_[kUChannel][1],
segment.quant_steps_[kUChannel][2],
segment.quant_steps_[kUChannel][3]);
WP2SAppend(str_ptr, " V quant_steps: %d %d %d %d\n",
segment.quant_steps_[kVChannel][0],
segment.quant_steps_[kVChannel][1],
segment.quant_steps_[kVChannel][2],
segment.quant_steps_[kVChannel][3]);
}
debug_output->Fill({x, y, w, h}, Argb32b{255, 0, 0, 0});
if (mid_w > 0 && mid_h > 0) {
debug_output->Fill({x + 1, y + 1, mid_w, mid_h}, color);
}
}
} else if (VDMatch(config, "is420")) {
debug_output->DrawRect(
{x, y, w, h}, is420_ ? Argb32b{0xff, 0x11, 0xff, 0x11}
: Argb32b{0xff, 0xdd, 0xdd, 0xdd});
if (is420_ && mid_w > 0 && mid_h > 0) {
DrawPixelArray(x + 1, y + 1, mid_w, mid_h,
Argb32b{0xff, 0xaa, 0xff, 0xaa},
Argb32b{0xff, 0x00, 0x00, 0x00},
{
" ",
" # # # ",
" ## # # # # ",
" # # # # # ",
" ### # # # ",
" # ### # ",
" ",
},
1, debug_output);
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status CodedBlock::Store420Scores(const EncoderConfig& config,
uint32_t pos_x, uint32_t pos_y,
float lambda_u, float lambda_v,
bool reduced, uint32_t disto,
float rate_u, float rate_v) {
if (!VDMatch(config, "is420-scores")) return WP2_STATUS_OK;
if (VDSelected(pos_x, pos_y, blk().rect_pix(), config)) {
WP2SAppend(&config.info->selection_info,
"%s score: %f = disto %d + lambda_u %f * rate_u %f + lambda_v "
"%f * rate_v %f\n",
reduced ? "420" : "444",
disto + lambda_u * rate_u + lambda_v * rate_v, disto,
lambda_u, rate_u, lambda_v, rate_v);
}
return WP2_STATUS_OK;
}
WP2Status CodedBlock::Store420Decision(const EncoderConfig& config,
uint32_t pos_x, uint32_t pos_y,
Debug420Decision decision) const {
if (!VDMatch(config, "is420-scores")) return WP2_STATUS_OK;
if (VDSelected(pos_x, pos_y, blk().rect_pix(), config)) {
WP2SAppend(&config.info->selection_info,
decision == Debug420Decision::k444EarlyExit
? "Decision: 444 early exit (DC only)\n"
: decision == Debug420Decision::k444 ? "Decision: 444\n"
: "Decision: 420\n");
}
ArgbBuffer& debug_output = config.info->debug_output;
Argb32b colors[3] = {Argb32b{0xff, 0xff, 0x11, 0xff},
Argb32b{0xff, 0xff, 0x11, 0x11},
Argb32b{0xff, 0x11, 0xff, 0x11}};
const Rectangle rect = {x_pix(), y_pix(),
std::min(w_pix(), debug_output.width() - x_pix()),
std::min(h_pix(), debug_output.height() - y_pix())};
debug_output.Fill(rect, colors[(int)decision]);
debug_output.DrawRect(rect, Argb32b{0xff, 0x00, 0x00, 0x00});
return WP2_STATUS_OK;
}
WP2Status CodedBlock::StoreLambdaMult(const EncoderConfig& config,
uint32_t pos_x, uint32_t pos_y) const {
if (!VDMatch(config, "lambda-mult")) return WP2_STATUS_OK;
const bool selected = VDSelected(pos_x, pos_y, blk().rect_pix(), config);
if (selected) {
WP2SAppend(&config.info->selection_info, "lambda-mult: %.3f", lambda_mult_);
}
ArgbBuffer& debug_output = config.info->debug_output;
pos_x += x_pix();
pos_y += y_pix();
const Rectangle rect = {pos_x, pos_y,
std::min(w_pix(), debug_output.width() - pos_x),
std::min(h_pix(), debug_output.height() - pos_y)};
const uint8_t gray = (uint8_t)Clamp((lambda_mult_ - 1.0f) * 500.f + 128.f,
0.f, 255.f);
debug_output.Fill(rect, Argb32b{0xff, gray, gray, gray});
if (selected) {
debug_output.DrawRect(rect, Argb32b{0xff, 0xc0, 0x30, 0x00});
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
void Tile::Draw(const DecoderConfig& config) {
Argb32b color{0xff, 0xbb, 0xbb, 0x66};
if (VDSelected(rect, config)) {
color.r = color.g = color.b = 0xff; // Highlight the tile's border.
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nTile at %u, %u px (%u x %u)\n", rect.x, rect.y,
rect.width, rect.height);
assert(chunk_size_is_known);
WP2SAppend(str_ptr, "Tile size: %u bytes\n", chunk_size);
}
config.info->debug_output.DrawRect(rect, color);
}
//------------------------------------------------------------------------------
// Copies pixels in 'area'.
static void CopyArea(const YUVPlane& from, const Rectangle& area,
YUVPlane* const to) {
for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (c == kAChannel && !from.HasAlpha()) continue;
const Plane16& from_plane = from.GetChannel(c);
Plane16* const to_plane = &to->GetChannel(c);
for (uint32_t y = 0; y < area.height; ++y) {
for (uint32_t x = 0; x < area.width; ++x) {
const uint32_t px = area.x + x, py = area.y + y;
to_plane->At(px, py) = from_plane.At(px, py);
}
}
}
}
// Saves the unfiltered 'canvas' to compare with the filtered ones.
static void SavePixelsForVDebugDiff(const YUVPlane& canvas, uint32_t num_rows,
YUVPlane* const unfiltered_pixels,
uint32_t* const num_debug_rows) {
if (unfiltered_pixels->Y.w_ != canvas.Y.w_ ||
unfiltered_pixels->Y.h_ != canvas.Y.h_) {
WP2_ASSERT_STATUS(unfiltered_pixels->Resize(canvas.Y.w_, canvas.Y.h_,
/*pad=*/1, canvas.HasAlpha()));
}
// Copy the unfiltered available rows.
assert(*num_debug_rows <= num_rows && num_rows <= canvas.Y.h_);
CopyArea(canvas,
{0, *num_debug_rows, canvas.Y.w_, num_rows - *num_debug_rows},
unfiltered_pixels);
*num_debug_rows = num_rows;
}
// Set pixels of 'debug_output' in 'rect' as the difference between the pixels
// of 'unfiltered_pixels' and 'filtered_pixels', scaled by 'multiplier'.
static void ApplyVDebugDiff(const YUVPlane& unfiltered_pixels,
const YUVPlane& filtered_pixels,
int32_t yuv_max_value, int32_t multiplier,
const Rectangle& rect,
ArgbBuffer* const debug_output) {
assert(unfiltered_pixels.GetWidth() == filtered_pixels.GetWidth());
assert(unfiltered_pixels.GetHeight() == filtered_pixels.GetHeight());
assert(unfiltered_pixels.GetWidth() >= rect.width);
assert(unfiltered_pixels.GetHeight() >= rect.height);
YUVPlane diff_pixels;
WP2_ASSERT_STATUS(diff_pixels.Resize(rect.width, rect.height));
for (Channel c : {kYChannel, kUChannel, kVChannel}) {
const Plane16& unfiltered = unfiltered_pixels.GetChannel(c);
const Plane16& filtered = filtered_pixels.GetChannel(c);
Plane16* const diff = &diff_pixels.GetChannel(c);
for (uint32_t y = 0; y < rect.height; ++y) {
for (uint32_t x = 0; x < rect.width; ++x) {
diff->At(x, y) =
Clamp((filtered.At(x, y) - unfiltered.At(x, y)) * multiplier,
-yuv_max_value, yuv_max_value);
}
}
}
// TODO(yguyon): Use the actual CSPTransform
ArgbBuffer debug_output_view(debug_output->format());
WP2_ASSERT_STATUS(debug_output_view.SetView(*debug_output, rect));
WP2_ASSERT_STATUS(diff_pixels.Export(
CSPTransform(), /*resize_if_needed=*/false, &debug_output_view));
}
static void ApplyVDebugAlphaDiff(const YUVPlane& unfiltered_pixels,
const YUVPlane& filtered_pixels,
const Rectangle& rect,
ArgbBuffer* const debug_output) {
if (!unfiltered_pixels.HasAlpha()) return;
assert(unfiltered_pixels.GetWidth() == filtered_pixels.GetWidth());
assert(unfiltered_pixels.GetHeight() == filtered_pixels.GetHeight());
assert(unfiltered_pixels.GetWidth() >= rect.width);
assert(unfiltered_pixels.GetHeight() >= rect.height);
Channel c = kAChannel;
const Plane16& unfiltered = unfiltered_pixels.GetChannel(c);
const Plane16& filtered = filtered_pixels.GetChannel(c);
const int32_t kMultiplier = 4; // Multiplier to make things more visible.
for (uint32_t y = 0; y < rect.height; ++y) {
uint8_t* row = debug_output->GetRow8(rect.y + y);
for (uint32_t x = 0; x < rect.width; ++x) {
const int32_t diff = filtered.At(x, y) - unfiltered.At(x, y);
const int32_t v = diff * kMultiplier * 255 / (int32_t)kAlphaMax + 128;
const uint32_t abs_x = rect.x + x;
row[4 * abs_x + 0] = 255;
row[4 * abs_x + 1] = row[4 * abs_x + 2] = row[4 * abs_x + 3] =
Clamp<int32_t>(v, 0, 255);
}
}
}
//------------------------------------------------------------------------------
// Filters
// Simple tool for gathering and displaying min, max and average values.
class MinMaxAvg {
public:
void Add(double value) {
if (num_values_ == 0) min_ = max_ = sum_ = value;
min_ = std::min(min_, value), max_ = std::max(max_, value), sum_ += value;
++num_values_;
}
uint32_t GetNumValues() const { return num_values_; }
double GetMin() const { return min_; }
double GetMax() const { return max_; }
double GetAvg() const { return (num_values_ > 0) ? sum_ / num_values_ : 0; }
std::string ToString() const {
std::string str;
WP2SAppend(&str, "min: %.3f, avg: %.3f, avg: %.3f (%u)", min_, GetAvg(),
max_, num_values_);
return str;
}
private:
uint32_t num_values_ = 0;
double sum_ = 0, min_ = 0, max_ = 0;
};
void FilterBlockMap::ApplyVDebug(const DecoderConfig& config,
ArgbBuffer* const debug_output) {
const int vd = VDMatch(config, "bpp") ? -1 : VDChannel(config);
std::unordered_set<uint32_t> done; // NOLINT (absl::flat_hash_set)
for (uint32_t by = 0; by < tile_rect_.height / kMinBlockSizePix; ++by) {
for (uint32_t bx = 0; bx < tile_rect_.width / kMinBlockSizePix; ++bx) {
const FilterBlockMap::BlockFeatures& block_features =
features_[by * max_num_blocks_x_ + bx];
if (!done.insert(block_features.index).second) continue;
const uint32_t x = bx * kMinBlockSizePix, y = by * kMinBlockSizePix;
const uint32_t w =
std::min(BlockWidthPix(block_features.size), tile_rect_.width - x);
const uint32_t h =
std::min(BlockHeightPix(block_features.size), tile_rect_.height - y);
const uint8_t color =
(vd == -1) ? block_features.min_bpp : block_features.res_den[vd];
debug_output->Fill({x, y, w, h}, Argb32b{255, color, 0, 0});
debug_output->DrawRect({x, y, w, h}, Argb32b{255, 128, 128, 128});
if (VDSelected(tile_rect_.x, tile_rect_.y, {x, y, w, h}, config)) {
debug_output->DrawRect({x, y, w, h}, Argb32b{255, 255, 255, 255});
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nBlock at %u, %u px\n\n", x, y);
WP2SAppend(str_ptr, "Min bits-per-pixel: %f\n",
block_features.min_bpp / 128.);
for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
WP2SAppend(str_ptr, "Residual density (%s): %f\n", kChannelStr[c],
block_features.res_den[c] / 255.);
}
}
}
}
}
//------------------------------------------------------------------------------
void DeblockingFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff")) {
SavePixelsForVDebugDiff(*blocks_.pixels_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
} else if (VDMatch(config_, "strength")) {
if (num_rows > 0 && num_debug_rows_ == 0) {
// Registered pixels won't fill the whole canvas so fill it with black.
config_.info->debug_output.Fill(
blocks_.tile_rect_, Argb32b{0xff, 0x00, 0x00, 0x00});
}
num_debug_rows_ = num_rows;
}
}
static bool IsHorizontal(const DecoderConfig& config) {
if (VDMatch(config, "horizontal")) return true;
assert(VDMatch(config, "vertical"));
return false;
}
void DeblockingFilter::RegisterPixelsForVDebug(
const DecoderConfig& config, uint32_t q0_x, uint32_t q0_y,
uint32_t max_half, uint32_t filtered_half, bool horizontal_filtering,
Channel channel, uint32_t strength, uint32_t sharpness,
bool before_deblocking, bool deblocked, uint32_t num_bits,
const int16_t* const q0, uint32_t step) {
if (VDMatch(config, "strength") && VDChannel(config) == channel &&
IsHorizontal(config) == horizontal_filtering) {
ArgbBuffer* const debug_output = &config.info->debug_output;
if (!before_deblocking) {
// Strength heatmap on R, inverted sharpness heatmap on G.
const uint32_t r = DivRound(strength * 255, kDblkMaxStrength);
const uint32_t g =
DivRound((kDblkMaxSharpness - sharpness) * 255, kDblkMaxSharpness);
// Display gradients in filtered direction.
for (uint32_t i = filtered_half; i >= 1; --i) {
debug_output->Fill(
horizontal_filtering ? Rectangle{q0_x - i, q0_y, i * 2, 1}
: Rectangle{q0_x, q0_y - i, 1, i * 2},
deblocked ? Argb32b{255, (uint8_t)(r / i), (uint8_t)(g / i), 0}
: Argb32b{255, (uint8_t)(200 / i), (uint8_t)(200 / i),
(uint8_t)(255 / i)});
}
}
const Rectangle rect =
horizontal_filtering
? Rectangle{q0_x - filtered_half, q0_y, 2 * filtered_half, 1}
: Rectangle{q0_x, q0_y - filtered_half, 1, 2 * filtered_half};
if (VDSelected(rect, config)) {
debug_output->DrawRect(rect, Argb32b{255, 255, 255, 255});
std::string* const str_ptr = &config.info->selection_info;
if (before_deblocking) {
WP2SAppend(str_ptr, "\nEdge near %u, %u px (%s)\n\n", q0_x, q0_y,
kChannelStr[channel]);
WP2SAppend(str_ptr, " Half-length: %u\n", filtered_half);
WP2SAppend(str_ptr, " Strength: %u\n", strength);
WP2SAppend(str_ptr, " Sharpness: %u\n", sharpness);
WP2SAppend(str_ptr, " Deblock threshold: %d\n\n",
DeblockThresholdFromSharpness(sharpness, num_bits));
for (int i = -(int32_t)max_half; i < (int32_t)max_half; ++i) {
if (i == 0) WP2SAppend(str_ptr, " |");
WP2SAppend(str_ptr, " %4d", q0[i * (int32_t)step]);
}
WP2SAppend(str_ptr, "\n");
} else { // !before_deblocking
for (int i = -(int32_t)max_half; i < (int32_t)max_half; ++i) {
if (i == 0) WP2SAppend(str_ptr, " |");
WP2SAppend(str_ptr, " %4d", q0[i * (int32_t)step]);
}
WP2SAppend(str_ptr, "\n\n");
WP2SAppend(str_ptr, " Deblocked: %s\n", deblocked ? "yes" : "no");
}
}
}
}
void DeblockingFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) {
if (VDMatch(config_, "a")) {
ApplyVDebugAlphaDiff(unfiltered_pixels_, *blocks_.pixels_,
blocks_.tile_rect_, &config_.info->debug_output);
} else {
const int32_t yuv_max_value =
(1 << (blocks_.num_precision_bits_ - 1)) - 1;
ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, yuv_max_value, 16,
blocks_.tile_rect_, &config_.info->debug_output);
}
}
}
//------------------------------------------------------------------------------
void GrainFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff")) {
SavePixelsForVDebugDiff(*blocks_.pixels_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
}
}
void GrainFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) {
const int32_t yuv_max_value = (1 << (blocks_.num_precision_bits_ - 1)) - 1;
ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, yuv_max_value, 2,
blocks_.tile_rect_, &config_.info->debug_output);
}
}
//------------------------------------------------------------------------------
void DirectionalFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff")) {
SavePixelsForVDebugDiff(*blocks_.pixels_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
}
}
void DirectionalFilter::RegisterPixelsForVDebug(uint32_t from_x, uint32_t to_x,
uint32_t from_y, uint32_t to_y,
uint32_t primary_strength,
uint32_t secondary_strength,
uint32_t direction,
uint32_t variance) {
ArgbBuffer* const debug_output = &config_.info->debug_output;
const Rectangle rect{blocks_.tile_rect_.x + from_x,
blocks_.tile_rect_.y + from_y,
to_x - from_x + 1,
to_y - from_y + 1};
if (VDMatch(config_, "strength")) {
const uint8_t primary_color =
(uint8_t)DivRound(primary_strength * 255, kMaxPriStr);
const uint8_t secondary_color =
(uint8_t)DivRound(secondary_strength * 255, kMaxSecStr);
debug_output->Fill(
rect, Argb32b{255, primary_color, secondary_color, 0});
} else if (VDMatch(config_, "variance")) {
const uint8_t color =
(uint8_t)Clamp(DivRound(WP2Log2Floor(variance >> 6) * 255, 16), 0, 255);
debug_output->Fill(rect, Argb32b{255, 0, 0, color});
} else if (VDMatch(config_, "direction")) {
assert(direction < kDrctFltNumDirs);
// Light checkerboard to distinguish areas where the direction is computed.
debug_output->Fill(rect, (((from_x + from_y) / kDrctFltSize) & 1u)
? Argb32b{255, 40, 40, 40}
: Argb32b{255, 0, 0, 0});
// Draw the oriented tap pattern in the middle of the block.
for (int32_t dist = -(int32_t)kDrctFltTapDist;
dist <= (int32_t)kDrctFltTapDist; ++dist) {
const int32_t cx =
blocks_.tile_rect_.x + from_x + (int32_t)kDrctFltSize / 2;
const int32_t cy =
blocks_.tile_rect_.y + from_y + (int32_t)kDrctFltSize / 2;
MarkPixelForVDebug(cx, cy, {255, 255, 255, 255}, debug_output);
if (dist != 0) {
const int32_t s = (dist < 0) ? -1 : 1, d = std::abs(dist) - 1;
// Primary taps.
MarkPixelForVDebug(cx + s * kDrctFltTapPos[direction][d][0],
cy + s * kDrctFltTapPos[direction][d][1],
{255, 255, 255, 127}, debug_output);
// Secondary taps.
MarkPixelForVDebug(
cx + s * kDrctFltTapPos[(direction + 2u) % kDrctFltNumDirs][d][0],
cy + s * kDrctFltTapPos[(direction + 2u) % kDrctFltNumDirs][d][1],
{255, 127, 127, 255}, debug_output);
MarkPixelForVDebug(
cx + s * kDrctFltTapPos[(direction + 6u) % kDrctFltNumDirs][d][0],
cy + s * kDrctFltTapPos[(direction + 6u) % kDrctFltNumDirs][d][1],
{255, 127, 127, 255}, debug_output);
}
}
}
}
void DirectionalFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) {
const int32_t yuv_max_value = (1 << (blocks_.num_precision_bits_ - 1)) - 1;
ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, yuv_max_value, 16,
blocks_.tile_rect_, &config_.info->debug_output);
}
}
//------------------------------------------------------------------------------
void RestorationFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff")) {
SavePixelsForVDebugDiff(*blocks_.pixels_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
}
}
void RestorationFilter::RegisterPixelsForVDebug(uint32_t from_x, uint32_t to_x,
uint32_t from_y,
uint32_t to_y) {
if (VDMatch(config_, "strength")) {
ArgbBuffer* const debug_output = &config_.info->debug_output;
const Rectangle rect{blocks_.tile_rect_.x + from_x,
blocks_.tile_rect_.y + from_y,
to_x - from_x + 1,
to_y - from_y + 1};
debug_output->Fill(rect, Argb32b{255, 128, 128, 128});
if (VDSelected(rect, config_)) {
MinMaxAvg min_max_avg_strength;
for (uint32_t y = from_y; y <= to_y; ++y) {
for (uint32_t x = from_x; x <= to_x; ++x) {
const uint8_t strength =
filter_strength_map_[(y - from_y) * blocks_.tile_rect_.width +
(x - from_x)];
const uint8_t color = (uint8_t)DivRound(strength * 255, 63);
MarkPixelForVDebug(blocks_.tile_rect_.x + x, blocks_.tile_rect_.y + y,
Argb32b{255, color, 0, 0}, debug_output);
min_max_avg_strength.Add(strength / 63.);
}
}
// Highlight the border.
debug_output->Fill(rect, Argb32b{255, 255, 255, 255});
std::string* const str_ptr = &config_.info->selection_info;
WP2SAppend(str_ptr, "\nStrength %s\n\n",
min_max_avg_strength.ToString().c_str());
const uint32_t area = from_y / kWieFltHeight;
for (Channel c : {kYChannel, kUChannel, kVChannel}) {
WP2SAppend(str_ptr, "Channel %s\n", kChannelStr[c]);
for (uint32_t h_or_v : {0, 1}) {
int32_t tap_weights[kWieFltNumTaps];
std::copy(params_.half_tap_weights[c][area][h_or_v],
params_.half_tap_weights[c][area][h_or_v] + kWieFltTapDist,
tap_weights);
WienerHalfToFullWgts(tap_weights, tap_weights);
WP2SAppend(str_ptr, " %s",
(h_or_v == 0) ? "Horizontal:" : "Vertical: ");
for (int32_t weight : tap_weights) {
WP2SAppend(str_ptr, " %6.3f",
(double)weight / (1u << (kWieFltNumBitsTapWgts - 1)));
}
*str_ptr += '\n';
}
}
}
}
}
void RestorationFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) {
const int32_t yuv_max_value = (1 << (blocks_.num_precision_bits_ - 1)) - 1;
ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, yuv_max_value, 16,
blocks_.tile_rect_, &config_.info->debug_output);
}
}
//------------------------------------------------------------------------------
void IntertileFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(*config_, "diff")) {
SavePixelsForVDebugDiff(canvas_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
} else if (VDMatch(*config_, "strength")) {
if (num_rows > 0 && num_debug_rows_ == 0) {
// Registered pixels won't fill the whole canvas so fill it with black.
config_->info->debug_output.Fill(Argb32b{0xff, 0x00, 0x00, 0x00});
}
num_debug_rows_ = num_rows;
}
}
void IntertileFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(*config_, "diff") && (num_rows == canvas_.Y.h_)) {
const FilterBlockMap& blocks = tiles_layout_->GetTileAt(0, 0).block_map;
const int32_t yuv_max_value = (1 << (blocks.num_precision_bits_ - 1)) - 1;
ApplyVDebugDiff(unfiltered_pixels_, canvas_, yuv_max_value, 16,
{0, 0, canvas_.Y.w_, canvas_.Y.h_},
&config_->info->debug_output);
}
}
//------------------------------------------------------------------------------
void AlphaFilter::SavePixelsForVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff")) {
SavePixelsForVDebugDiff(*blocks_.pixels_, num_rows, &unfiltered_pixels_,
&num_debug_rows_);
}
}
void AlphaFilter::ApplyVDebug(uint32_t num_rows) {
if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) {
ApplyVDebugAlphaDiff(unfiltered_pixels_, *blocks_.pixels_,
blocks_.tile_rect_, &config_.info->debug_output);
}
}
//------------------------------------------------------------------------------
static void ApplyVDebugDiff(const ArgbBuffer& unfiltered_pixels,
const ArgbBuffer& filtered_pixels,
int32_t multiplier,
ArgbBuffer* const debug_output) {
assert(unfiltered_pixels.format() == filtered_pixels.format());
assert(unfiltered_pixels.width() == filtered_pixels.width());
assert(unfiltered_pixels.height() == filtered_pixels.height());
assert(unfiltered_pixels.format() == debug_output->format());
assert(unfiltered_pixels.width() == debug_output->width());
assert(unfiltered_pixels.height() == debug_output->height());
const uint32_t num_channels = WP2FormatBpp(debug_output->format());
const uint32_t alpha_channel = WP2AlphaChannelIndex(debug_output->format());
for (uint32_t y = 0; y < unfiltered_pixels.height(); ++y) {
for (uint32_t x = 0; x < unfiltered_pixels.width(); ++x) {
for (uint32_t c = 0; c < num_channels; ++c) {
const int32_t unfiltered =
(unfiltered_pixels.GetRow8(y))[x * num_channels + c];
const int32_t filtered =
(filtered_pixels.GetRow8(y))[x * num_channels + c];
int32_t diff = (filtered - unfiltered) * multiplier;
if (c == alpha_channel) {
diff = 255 - std::abs(diff); // Opaque by default.
} else {
diff = 128 + diff; // Grey by default.
}
uint8_t* const pixel = debug_output->GetRow8(y) + x * num_channels + c;
*pixel = (uint8_t)Clamp(diff, 0, 255);
}
}
}
}
// Sets all R, G and B channels to the value of a single 'channel'.
static void ToGray(const ArgbBuffer& from, uint32_t channel,
ArgbBuffer* const to) {
assert(from.width() == to->width() && from.height() == to->height());
assert(from.format() == WP2_Argb_32 && to->format() == WP2_Argb_32);
uint32_t kAlpha = 0;
for (uint32_t y = 0; y < to->height(); ++y) {
const uint8_t* const from_row = from.GetRow8(y);
uint8_t* const to_row = to->GetRow8(y);
for (uint32_t x = 0; x < to->width(); ++x) {
const uint8_t* const from_pixel = from_row + x * 4;
uint8_t* const to_pixel = to_row + x * 4;
uint8_t gray = from_pixel[channel];
if (channel != kAlpha) { // Unpremultiply by alpha.
uint8_t alpha = from_pixel[kAlpha];
assert(gray <= alpha);
if (alpha < 255) {
gray = (alpha > 0) ? ((gray * 255 + alpha / 2) / alpha) : 0;
}
}
to_pixel[0] = 255;
to_pixel[1] = to_pixel[2] = to_pixel[3] = gray;
}
}
}
// Spreads the values of the 'channel' of 'src' into at most 'histogram_size'
// buckets. Returns the number of buckets used.
static uint32_t GetHistogram(const ArgbBuffer& src, uint32_t channel,
uint32_t max_num_buckets, uint32_t histogram[],
uint8_t* const min_value,
uint8_t* const max_value) {
assert(max_num_buckets > 0);
const uint32_t num_channels = WP2FormatBpp(src.format());
const uint8_t* row = src.GetRow8(0) + channel;
uint8_t min = row[0], max = row[0];
for (uint32_t y = 0; y < src.height(); ++y) {
for (uint32_t x = 0; x < src.width(); ++x) {
const uint8_t value = row[x * num_channels];
min = std::min(min, value);
max = std::max(max, value);
}
row += src.stride();
}
if (min_value != nullptr) *min_value = min;
if (max_value != nullptr) *max_value = max;
std::fill(histogram, histogram + max_num_buckets, 0u);
const uint32_t range = std::max(1, max - min);
max_num_buckets = std::min(range, max_num_buckets);
const uint32_t max_bucket = max_num_buckets - 1;
row = src.GetRow8(0) + channel;
for (uint32_t y = 0; y < src.height(); ++y) {
for (uint32_t x = 0; x < src.width(); ++x) {
const uint8_t value = row[x * num_channels];
const uint32_t bucket =
DivRound((uint32_t)(value - min) * max_bucket, range);
++histogram[bucket];
}
row += src.stride();
}
return max_num_buckets;
}
// Handles VisualDebug for "compressed" and "original". Declared in wp2_dec_i.h.
void ApplyVDebugBeforeAfter(const DecoderConfig& config,
const CSPTransform& csp_tranform,
const Tile& tile,
ArgbBuffer* const debug_output) {
if (VDMatch(config, "original/diff")) {
ArgbBuffer argb;
WP2_ASSERT_STATUS(
argb.Resize(debug_output->width(), debug_output->height()));
YUVPlane non_padded;
WP2_ASSERT_STATUS(non_padded.SetView(
tile.yuv_output, {0, 0, tile.rect.width, tile.rect.height}));
WP2_ASSERT_STATUS(
non_padded.Export(csp_tranform, /*resize_if_needed=*/false, &argb));
ApplyVDebugDiff(*debug_output, argb, 2, debug_output);
} else if (VDMatch(config, "y") || VDMatch(config, "u") ||
VDMatch(config, "v")) {
const uint32_t yuv_bits = csp_tranform.GetYUVPrecisionBits() + 1;
if (VDMatch(config, "original")) {
csp_tranform.Apply(debug_output, yuv_bits, 0, 0, debug_output->width(),
debug_output->height());
ToGray(*debug_output, VDChannel(config) + 1, debug_output);
} else if (VDMatch(config, "compressed")) {
const Plane16& plane = tile.yuv_output.GetChannel(VDChannel(config));
WP2_ASSERT_STATUS(plane.ToGray(debug_output, yuv_bits));
}
} else {
const bool a = VDMatch(config, "a"), r = VDMatch(config, "r");
const bool g = VDMatch(config, "g"), b = VDMatch(config, "b");
if (a || r || g || b) {
const uint32_t channel = a ? 0 : r ? 1 : g ? 2 : 3;
if (VDMatch(config, "original")) {
ToGray(*debug_output, channel, debug_output);
} else if (VDMatch(config, "compressed")) {
ArgbBuffer argb;
WP2_ASSERT_STATUS(
argb.Resize(debug_output->width(), debug_output->height()));
YUVPlane non_padded_yuv;
WP2_ASSERT_STATUS(non_padded_yuv.SetView(
tile.yuv_output, {0, 0, tile.rect.width, tile.rect.height}));
WP2_ASSERT_STATUS(non_padded_yuv.Export(
csp_tranform, /*resize_if_needed=*/false, &argb));
ToGray(argb, channel, debug_output);
}
}
}
if (VDMatch(config, "histogram") && VDSelected(tile.rect, config)) {
// A gray image of either Y,U,V,R,G or B should already be in
// 'debug_output'. The histogram will be based on that.
ArgbBuffer selected_area(debug_output->format());
Rectangle rect = config.info->selection.ClipWith(tile.rect);
assert(rect.GetArea() != 0);
rect.x -= tile.rect.x;
rect.y -= tile.rect.y;
WP2_ASSERT_STATUS(selected_area.SetView(*debug_output, rect));
std::array<uint32_t, 32> histogram{0};
uint8_t min_value, max_value;
const uint32_t num_buckets =
GetHistogram(selected_area, /*channel=*/1, histogram.size(),
histogram.data(), &min_value, &max_value);
debug_output->DrawRect(rect, Argb32b{255, 255, 128, 128});
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nArea %u x %u at %u, %u\n", rect.width, rect.height,
rect.x + tile.rect.x, rect.y + tile.rect.y);
WP2SAppend(str_ptr, "\nHistogram [%u, %u]\n", min_value, max_value);
for (uint32_t bucket = 0; bucket < num_buckets; ++bucket) {
const float percent = 100.f * histogram[bucket] / rect.GetArea();
WP2SAppend(str_ptr, " Bucket %2u: %5.2f%% (%8u) ", bucket, percent,
histogram[bucket]);
*str_ptr += std::string(std::lround(percent), '|');
WP2SAppend(str_ptr, "\n");
}
std::array<uint32_t, histogram.size()> clusters{0};
std::array<uint32_t, kMaxNumSegments> centers{0};
const uint32_t num_clusters = ClusterHistogram(
histogram.data(), num_buckets, /*max_clusters=*/kMaxNumSegments,
clusters.data(), centers.data());
WP2SAppend(str_ptr, "\nNum clusters: %u\n", num_clusters);
}
}
//------------------------------------------------------------------------------
// Encoder-related
// Fills the background of the current tile with luma and display some info.
WP2Status PartitionScoreFunc::ClearVDebug() const {
if (VDMatch(*config_, "encoder/partition")) {
ArgbBuffer debug_output;
WP2_CHECK_STATUS(
debug_output.SetView(config_->info->debug_output, tile_rect_));
if (VDMatch(*config_, "method")) {
const PartitionMethod pm =
FinalPartitionMethod(*config_, tile_rect_.width, tile_rect_.height);
if (VDSelected(tile_rect_, *config_)) {
debug_output.Fill(Argb32b{255, 255, 255, 255});
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "\nTile at %4u, %4u (%2u x %2u px): %s\n",
tile_rect_.x, tile_rect_.y, tile_rect_.width,
tile_rect_.height, kPartitionMethodString[pm]);
} else {
WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kMaxYuvBits));
}
debug_output.DrawRect(
{0, 0, debug_output.width(), debug_output.height()},
Argb32b{255, (uint8_t)(pm * 255 / NUM_PARTITION_METHODS),
(uint8_t)(100 + pm * 100 / NUM_PARTITION_METHODS),
(uint8_t)(50 + pm * 50 / NUM_PARTITION_METHODS)});
} else if (VDMatch(*config_, "score")) {
WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kMaxYuvBits));
}
}
return WP2_STATUS_OK;
}
// Should be called after ClearVDebug() and for each block score computed.
WP2Status PartitionScoreFunc::RegisterScoreForVDebug(
const Block& block, float score, bool ending_new_line) const {
if (!VDMatch(*config_, "encoder/partition/score") ||
!VDSelected(
tile_rect_.x, tile_rect_.y,
{block.x_pix(), block.y_pix(), kMinBlockSizePix, kMinBlockSizePix},
*config_)) {
return WP2_STATUS_OK;
}
// Highlight the block with a color dependent on its size.
const uint8_t color_w = (uint8_t)(191 + block.w() * 64 / kMaxBlockSize);
const uint8_t color_h = (uint8_t)(191 + block.h() * 64 / kMaxBlockSize);
config_->info->debug_output.Fill(
{tile_rect_.x + block.x_pix() + 1, tile_rect_.y + block.y_pix() + 1,
block.w_pix() - 2, block.h_pix() - 2},
Argb32b{255, color_w, color_h, 0});
if (config_->info != nullptr) {
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "Block at %4u, %4u (%2u x %2u px), score: %9.3f",
tile_rect_.x + block.x_pix(), tile_rect_.y + block.y_pix(),
block.w_pix(), block.h_pix(), score);
if (ending_new_line) WP2SAppend(str_ptr, "\n");
}
return WP2_STATUS_OK;
}
// Should be called after ClearVDebug() and for each block actually encoded.
WP2Status Partitioner::RegisterOrderForVDebug(
uint32_t pass, uint32_t pass_index,
const Block& block, uint32_t block_index, uint32_t max_num_blocks) const {
if (VDMatch(*config_, "encoder/partition")) {
ArgbBuffer* const debug_output = &config_->info->debug_output;
Rectangle rect;
rect.x = tile_rect_.x + block.x_pix();
rect.y = tile_rect_.y + block.y_pix();
rect.width = std::min(block.w_pix(), debug_output->width() - rect.x);
rect.height = std::min(block.h_pix(), debug_output->height() - rect.y);
if (VDMatch(*config_, "pass") || VDMatch(*config_, "order")) {
Argb32b color;
if (VDMatch(*config_, "pass")) {
color.a = 255;
color.r = color.g = color.b =
(uint8_t)(255 - Clamp(pass_index * 12u, 0u, 255u));
} else {
color.a = 255;
color.r = color.g = color.b =
(uint8_t)(55u + DivRound(200u * block_index, max_num_blocks));
}
debug_output->Fill(rect, color);
uint32_t selected_pass_index;
if (VDMatch(*config_, "pass")) {
if (VDIndex(config_->info->visual_debug, &selected_pass_index) &&
pass_index == selected_pass_index) {
debug_output->DrawRect(rect, Argb32b{255, 0, 0, 255});
} else if (VDMatch(*config_, "all")) {
const Argb32b kPassColor[] = {{255, 141, 202, 175},
{255, 140, 73, 192},
{255, 141, 208, 85},
{255, 205, 104, 150},
{255, 133, 148, 196}};
STATIC_ASSERT_ARRAY_SIZE(kPassColor,
(int)MultiScoreFunc::Pass::Any + 1);
debug_output->DrawRect(rect, kPassColor[pass]);
}
}
if (VDSelected(rect, *config_)) {
// Highlight the border.
debug_output->DrawRect(rect, Argb32b{255, 255, 0, 0});
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(
str_ptr, "Block at %u, %u (%u x %u px), pass: %u, order: %u\n",
rect.x, rect.y, rect.width, rect.height, pass_index, block_index);
}
} else if (VDMatch(*config_, "score")) {
// To have a reference, borders of actual encoded blocks are drawn.
// Selected blocks are colored in RegisterScoreForVDebug().
debug_output->DrawRect(rect, Argb32b{255, 64, 128, 64});
}
}
return WP2_STATUS_OK;
}
WP2Status BlockScoreFunc::RegisterScoreForVDebug(
const Block blocks[], uint32_t num_blocks, const float rate[4],
const float disto[4], float total_rate, float total_disto, float score) {
Rectangle hull = blocks[0].rect(); // Print surrounding block if possible.
for (uint32_t i = 1; i < num_blocks; ++i) {
hull = hull.MergeWith(blocks[i].rect());
}
const Block hull_block(hull.x, hull.y, GetBlockSize(hull.width, hull.height));
WP2_CHECK_STATUS(PartitionScoreFunc::RegisterScoreForVDebug(
hull_block, score, /*ending_new_line=*/false));
if (config_->info != nullptr) {
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, " rate %8.2f disto %8.2f", total_rate, total_disto);
if (num_blocks > 1) { // Print sub-blocks stats.
WP2SAppend(str_ptr, " %u blks: ", num_blocks);
for (uint32_t i = 0; i < num_blocks; ++i) {
WP2SAppend(str_ptr, " %ux%u(r%.2f,d%.2f)", blocks[i].w_pix(),
blocks[i].h_pix(), rate[i], disto[i]);
}
}
WP2SAppend(str_ptr, "\n");
}
return WP2_STATUS_OK;
}
WP2Status AreaScoreFunc::RegisterScoreForVDebug(
BlockSize grid_size, const Vector<CodedBlock>& area_blocks, float score,
float disto, float rate) const {
// Only display something when the current 'area_' is selected.
if (!VDMatch(*config_, "encoder/partition/score") ||
!VDSelected(tile_rect_.x, tile_rect_.y, area_, *config_)) {
return WP2_STATUS_OK;
}
std::string* const str_ptr = &config_->info->selection_info;
if (grid_size == BLK_LAST) { // Meaning default partitioning (happens once).
WP2SAppend(str_ptr, "\nArea at %4u, %4u (%2u x %2u px)\n",
tile_rect_.x + area_.x, tile_rect_.y + area_.y,
area_.width, area_.height);
WP2SAppend(str_ptr, "\nscore: %9.3f = disto %7.3f + rate %7.3f\n", score,
disto, rate);
WP2SAppend(str_ptr, " (default)\n");
ArgbBuffer debug_output;
WP2_ASSERT_STATUS(
debug_output.SetView(config_->info->debug_output, tile_rect_));
WP2_ASSERT_STATUS(buffer_.Export(
gparams_->transf_, /*resize_if_needed=*/false, &debug_output));
debug_output.DrawRect(area_, Argb32b{255, 255, 0, 0});
for (const CodedBlock& cb : area_blocks) {
debug_output.DrawRect(cb.blk().rect_pix(), Argb32b{255, 128, 255, 0});
}
} else { // Grid partitioning (happens for each of several block sizes).
WP2SAppend(str_ptr, "\nscore: %9.3f = disto %7.3f + rate %7.3f\n", score,
disto, rate);
WP2SAppend(str_ptr, " (grid of %2u x %2u px)\n", BlockWidthPix(grid_size),
BlockHeightPix(grid_size));
}
return WP2_STATUS_OK;
}
WP2Status SubAreaScoreFunc::RegisterScoreForVDebug(
const Block& block, const Vector<CodedBlock>& area_remaining_blocks,
float score, float disto, float rate) const {
WP2_CHECK_STATUS(PartitionScoreFunc::RegisterScoreForVDebug(block, score));
if (config_->info != nullptr) {
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, " disto %7.3f + rate %7.3f%s\n", disto, rate,
(default_block_.dim() == BLK_LAST) ? " (default)" : "");
}
return WP2_STATUS_OK;
}
WP2Status TileScoreFunc::RegisterScoreForVDebug(
const char label[], const Block& block, float score) const {
if (VDMatch(*config_, "encoder/partition/score")) {
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "\n%s: score %.6f, %4u blocks, %4u-byte tile", label,
score, (uint32_t)blocks_.size(),
enc_tiles_layout_.tiles.front().enc.BufferSize());
if (block.dim() != BLK_LAST) {
WP2SAppend(str_ptr, ", on block at %3u,%3u (%2ux%2u)", block.x_pix(),
block.y_pix(), block.w_pix(), block.h_pix());
}
WP2SAppend(str_ptr, ", tile distortion %.4f dB", distortion_[4]);
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status ExhaustivePartitioner::RegisterScoreForVDebug(
float best_partition_score, uint64_t best_partition_size,
uint64_t num_iterations) const {
if (VDMatch(*config_, "encoder/partition/score")) {
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "\n\nNum iterations: %u\n", (uint32_t)num_iterations);
WP2SAppend(str_ptr, "Best partition score: %8.3f size: %4u\n",
best_partition_score, (uint32_t)best_partition_size);
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status MultiPassPartitioner::RegisterPassForVDebug(
MultiScoreFunc::Pass pass, BlockSize block_size,
uint32_t num_chosen_blocks) const {
if (!VDMatch(*config_, "encoder/partition/pass")) return WP2_STATUS_OK;
if (!VDSelected(tile_rect_, *config_)) return WP2_STATUS_OK;
const char* const kPassStr[] = {"FlatLumaAlpha", "NarrowStdDev",
"GoodQuantDCT", "Direction", "Any"};
STATIC_ASSERT_ARRAY_SIZE(kPassStr, (int)MultiScoreFunc::Pass::Any + 1);
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "Pass: %2u - %s - %2ux%2u - %4u blocks\n", pass_index_,
kPassStr[(int)pass], BlockWidthPix(block_size),
BlockHeightPix(block_size), num_chosen_blocks);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
// Displays the normalized value of the selected VDebug menu.
void MultiScoreFunc::DrawValueThresholdColor(
uint32_t cell_width, uint32_t cell_height, const Block& block,
ArgbBuffer* const debug_output) const {
float value = 0.f;
if (VDMatch(*config_, "luma-alpha-gradient")) {
value = GetLumaAlphaGradient(block) / GetLumaAlphaGradientThreshold(block);
} else if (VDMatch(*config_, "narrow-std-dev")) {
value = GetStdDevRange(block) / GetStdDevRangeThreshold(block);
} else if (VDMatch(*config_, "quant-dct")) {
value = GetQuantDCT(block) / GetQuantDCTThreshold(block);
} else if (VDMatch(*config_, "direction")) {
value = GetDirection(block) / GetDirectionThreshold(block);
} else {
assert(false);
}
// Display as blue if passing threshold, otherwise as red. Dark blue means
// close to the threshold, dark red means far from it.
const float color[4] = {255.f, (value <= 1.f) ? 0.f : 55.f + 200.f / value,
0.f,
(value <= 1.f) ? 55.f + 200.f * (1.f - value) : 0.f};
for (uint32_t sub_y = 0; sub_y < cell_height; ++sub_y) {
if (block.y_pix() + sub_y >= debug_output->height()) break;
uint8_t* const row = debug_output->GetRow8(block.y_pix() + sub_y);
for (uint32_t sub_x = 0; sub_x < cell_width; ++sub_x) {
if (block.x_pix() + sub_x >= debug_output->width()) break;
uint8_t* const pixel = &row[(block.x_pix() + sub_x) * 4];
for (uint32_t c : {1, 2, 3}) {
pixel[c] = (uint8_t)std::lround((pixel[c] + color[c]) * 0.5f);
}
}
}
}
// Displays the lossy luma.
WP2Status MultiScoreFunc::DrawLossyLuma(const Block& block,
const Rectangle& block_rect,
bool draw_side,
ArgbBuffer* const debug_output) const {
Plane16 p;
WP2_CHECK_STATUS(p.Resize(block.w_pix(), block.h_pix()));
int32_t coeffs[kMaxBlockSizePix2];
QuantizeCoeffs(kYChannel, block, coeffs);
std::copy(coeffs, coeffs + block.w_pix() * block.h_pix(), p.Row(0));
ArgbBuffer block_debug_output;
WP2_CHECK_STATUS(block_debug_output.SetView(*debug_output, block_rect));
WP2_CHECK_STATUS(p.ToGray(&block_debug_output, kMaxYuvBits + 1u));
if (draw_side) {
// If there is enough space, also display the original luma.
if (block.x_pix() >= block.w_pix()) {
GetCoeffs(kYChannel, block, coeffs);
std::copy(coeffs, coeffs + block.w_pix() * block.h_pix(), p.Row(0));
WP2_CHECK_STATUS(block_debug_output.SetView(
*debug_output, {block.x_pix() - block.w_pix(), block.y_pix(),
block.w_pix(), block.h_pix()}));
WP2_CHECK_STATUS(p.ToGray(&block_debug_output, kMaxYuvBits + 1u));
}
// If there is enough space, also display the quantized luma for the
// sub-blocks, for comparison during the merging pass.
if (VDMatch(*config_, "merge-flat") &&
block.x_pix() + 2 * block.w_pix() <= debug_output->width() &&
block.y_pix() + block.h_pix() <= debug_output->height()) {
QuantizeCoeffs(kYChannel, block, BLK_8x8, coeffs);
std::copy(coeffs, coeffs + block.w_pix() * block.h_pix(), p.Row(0));
WP2_CHECK_STATUS(block_debug_output.SetView(
*debug_output, {block.x_pix() + block.w_pix(), block.y_pix(),
block.w_pix(), block.h_pix()}));
WP2_CHECK_STATUS(p.ToGray(&block_debug_output, kMaxYuvBits + 1u));
}
}
return WP2_STATUS_OK;
}
// Displays the selected block border and info.
WP2Status MultiScoreFunc::DrawSelection(const Block& block,
const Rectangle& block_rect,
ArgbBuffer* const debug_output) const {
Rectangle border = {SafeSub(block_rect.x, 1u), SafeSub(block_rect.y, 1u), 0,
0};
border.width =
std::min(block_rect.x + block_rect.width + 1u, debug_output->width()) -
border.x;
border.height =
std::min(block_rect.y + block_rect.height + 1u, debug_output->height()) -
border.y;
debug_output->DrawRect(border, Argb32b{255, 255, 255, 0});
if (config_->info == nullptr) return WP2_STATUS_OK;
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "\nBlock at %4u, %4u (%2u x %2u px)\n",
tile_rect_.x + block.x_pix(), tile_rect_.y + block.y_pix(),
block.w_pix(), block.h_pix());
const uint8_t segment_id = AssignSegmentId(
*config_, *gparams_, {tile_rect_.x, tile_rect_.y, src_->Y.w_, src_->Y.h_},
block);
WP2SAppend(str_ptr, "Segment id: %u\n", segment_id);
WP2SAppend(str_ptr, "YA gradient: %5.3f = %5.2f / %5.2f\n",
GetLumaAlphaGradient(block) / GetLumaAlphaGradientThreshold(block),
GetLumaAlphaGradient(block), GetLumaAlphaGradientThreshold(block));
WP2SAppend(str_ptr, "Std dev: %5.3f = %5.2f / %5.2f\n",
GetStdDevRange(block) / GetStdDevRangeThreshold(block),
GetStdDevRange(block), GetStdDevRangeThreshold(block));
WP2SAppend(str_ptr, "Quant DCT: %5.3f = %5.2f / %5.2f\n",
GetQuantDCT(block) / GetQuantDCTThreshold(block),
GetQuantDCT(block), GetQuantDCTThreshold(block));
WP2SAppend(str_ptr, "Direction: %5.3f = %6.1f / %6.1f\n",
GetDirection(block) / GetDirectionThreshold(block),
GetDirection(block), GetDirectionThreshold(block));
return WP2_STATUS_OK;
}
// Displays the orientations computed by MultiScoreFunc::ComputeDirection().
static void DrawDirection(const Rectangle& rect, uint32_t direction,
Argb32b color, ArgbBuffer* const debug_output) {
// 4x4 mask per direction
constexpr uint8_t kPatterns[kDrctFltNumDirs][4][4] = {
{{0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}, {1, 0, 0, 0}},
{{0, 0, 0, 0}, {0, 0, 1, 1}, {1, 1, 0, 0}, {0, 0, 0, 0}},
{{0, 0, 0, 0}, {1, 1, 1, 1}, {1, 1, 1, 1}, {0, 0, 0, 0}},
{{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 1, 1}, {0, 0, 0, 0}},
{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}},
{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}},
{{0, 1, 1, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}},
{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}}};
assert(direction < 8 && rect.width <= 4 && rect.height <= 4);
for (uint32_t y = 0; y < rect.height; ++y) {
for (uint32_t x = 0; x < rect.width; ++x) {
if (kPatterns[direction][y][x] == 1) {
ToUInt8(color, debug_output->GetRow8(rect.y + y) +
(rect.x + x) * WP2FormatBpp(debug_output->format()));
}
}
}
}
WP2Status MultiScoreFunc::DrawVDebug() const {
if (!VDMatch(*config_, "encoder/partition/multi")) return WP2_STATUS_OK;
ArgbBuffer debug_output;
WP2_CHECK_STATUS(
debug_output.SetView(config_->info->debug_output, tile_rect_));
// Image processing output.
if (VDMatch(*config_, "spread-5x5")) {
WP2_CHECK_STATUS(spread_.Y.ToGray(&debug_output, kMaxYuvBits + 1u));
if (VDSelected(0, 0, tile_rect_, *config_)) {
const uint32_t x = config_->info->selection.x - tile_rect_.x;
const uint32_t y = config_->info->selection.y - tile_rect_.y;
debug_output.DrawRect({x, y, 1, 1}, Argb32b{255, 255, 0, 0});
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "\nspread-5x5 at %4u, %4u: %4d\n",
config_->info->selection.x, config_->info->selection.y,
spread_.Y.At(x, y));
}
return WP2_STATUS_OK;
}
// Display the general orientation of all 4x4 blocks in the grid.
if (VDMatch(*config_, "direction-4x4")) {
// Display the luma source as background.
WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kMaxYuvBits + 1u));
for (uint32_t y = 0; y + 4 <= debug_output.height(); y += 4) {
for (uint32_t x = 0; x + 4 <= debug_output.width(); x += 4) {
const Rectangle block_rect = {x, y,
std::min(4u, debug_output.width() - x),
std::min(4u, debug_output.height() - y)};
const uint32_t i = x / 4 + y / 4 * num_block_cols_;
if (direction_certainty_[i] > 0) { // Do not draw random directions.
const uint8_t c =
Clamp(direction_certainty_[i] * 255u / 3u, 0u, 255u);
const Argb32b color = {255, c, (uint8_t)(c / 2), (uint8_t)(255 - c)};
DrawDirection(block_rect, direction_[i], color, &debug_output);
}
if (VDSelected(tile_rect_.x, tile_rect_.y, block_rect, *config_)) {
debug_output.DrawRect(block_rect, Argb32b{255, 0, 255, 0});
const uint32_t angle_deg =
(10 - (int32_t)direction_[i]) * 180 / kDrctFltNumDirs;
std::string* const str_ptr = &config_->info->selection_info;
WP2SAppend(str_ptr, "Block at %4u, %4u: \n", x, y);
WP2SAppend(str_ptr, " angle %u degrees\n", angle_deg);
WP2SAppend(str_ptr, " certainty %u/3\n", direction_certainty_[i]);
}
}
}
return WP2_STATUS_OK;
}
// Original luma.
WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kMaxYuvBits + 1u));
// clang-format off
Block block(0, 0, VDMatch(*config_, "32x32") ? BLK_32x32 :
VDMatch(*config_, "16x16") ? BLK_16x16 :
VDMatch(*config_, "8x8") ? BLK_8x8 : BLK_4x4);
// clang-format on
const bool snapped =
(VDMatch(*config_, "raw-quant-dct") || config_->partition_snapping);
const uint32_t cell_width = snapped ? block.w_pix() : kMinBlockSizePix;
const uint32_t cell_height = snapped ? block.h_pix() : kMinBlockSizePix;
Block selected_block = Block();
// Display all blocks.
for (uint32_t y = 0; y + block.h_pix() <= debug_output.height();
y += cell_height) {
for (uint32_t x = 0; x + block.w_pix() <= debug_output.width();
x += cell_width) {
block.SetXY(x / kMinBlockSizePix, y / kMinBlockSizePix);
const uint32_t max_width = debug_output.width() - block.x_pix();
const uint32_t max_height = debug_output.height() - block.y_pix();
const Rectangle cell_rect = {block.x_pix(), block.y_pix(),
std::min(cell_width, max_width),
std::min(cell_height, max_height)};
const Rectangle block_rect = {block.x_pix(), block.y_pix(),
std::min(block.w_pix(), max_width),
std::min(block.h_pix(), max_height)};
if (VDMatch(*config_, "raw-quant-dct")) {
WP2_CHECK_STATUS(DrawLossyLuma(block, block_rect, /*draw_side=*/false,
&debug_output));
} else {
DrawValueThresholdColor(cell_width, cell_height, block, &debug_output);
}
if (VDSelected(
tile_rect_.x, tile_rect_.y,
VDMatch(*config_, "raw-quant-dct") ? block_rect : cell_rect,
*config_)) {
selected_block = block;
}
}
}
// Display selected block on top.
if (selected_block.dim() != BLK_LAST) {
const uint32_t max_width = debug_output.width() - selected_block.x_pix();
const uint32_t max_height = debug_output.height() - selected_block.y_pix();
const Rectangle block_rect = {selected_block.x_pix(),
selected_block.y_pix(),
std::min(selected_block.w_pix(), max_width),
std::min(selected_block.h_pix(), max_height)};
if (VDMatch(*config_, "quant-dct") || VDMatch(*config_, "merge-flat")) {
WP2_CHECK_STATUS(DrawLossyLuma(selected_block, block_rect,
/*draw_side=*/true, &debug_output));
}
WP2_CHECK_STATUS(DrawSelection(selected_block, block_rect, &debug_output));
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
void SegmentationGridVDebug(const EncoderConfig& config, const Rectangle& rect,
uint32_t tile_pos_x, uint32_t tile_pos_y,
float value) {
const uint8_t gray = (uint8_t)std::lround(value * 10);
ArgbBuffer* const debug_output = &config.info->debug_output;
config.info->debug_output.Fill(
{tile_pos_x + rect.x, tile_pos_y + rect.y,
std::min(rect.width, debug_output->width() - tile_pos_x - rect.x),
std::min(rect.height, debug_output->height() - tile_pos_y - rect.y)},
Argb32b{255, gray, gray, gray});
if (VDSelected(tile_pos_x, tile_pos_y, rect, config)) {
config.info->debug_output.Fill(
{tile_pos_x + rect.x, tile_pos_y + rect.y, rect.width, rect.height},
Argb32b{255, 128, 128, 0});
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "\nRect at %4u, %4u (%2u x %2u px), value: %9.3f\n",
tile_pos_x + rect.x, tile_pos_y + rect.y, rect.width,
rect.height, value);
}
}
void SegmentationBlockVDebug(const EncoderConfig& config, const CodedBlock& cb,
uint32_t tile_pos_x, uint32_t tile_pos_y,
float score, const Segment& segment) {
SegmentationGridVDebug(config,
{cb.x_pix(), cb.y_pix(), cb.w_pix(), cb.h_pix()},
tile_pos_x, tile_pos_y, score);
config.info->debug_output.Fill(
{tile_pos_x + cb.x_pix() + cb.w_pix() - 1,
tile_pos_y + cb.y_pix() + cb.h_pix() - 1, 1, 1},
kSegmentColors[cb.id_]);
const Rectangle rect{cb.x_pix(), cb.y_pix(), cb.w_pix(), cb.h_pix()};
if (VDSelected(tile_pos_x, tile_pos_y, rect, config)) {
std::string* const str_ptr = &config.info->selection_info;
WP2SAppend(str_ptr, "Segment %d risk %f\n", cb.id_, segment.risk_);
}
}
//------------------------------------------------------------------------------
} // namespace WP2
//------------------------------------------------------------------------------
// Lossless decoder
namespace WP2L {
static bool VDSelectedLossless(uint32_t pos, uint32_t length,
const WP2::DecoderInfo& decoder_info,
const WP2::Rectangle& tile_rect) {
const WP2::Rectangle& selection = decoder_info.selection;
if (selection.width == 0 || selection.height == 0) return false;
const uint32_t x = selection.x;
const uint32_t y = selection.y;
if (!tile_rect.Contains(x, y)) return false;
const uint32_t selected_pos =
(y - tile_rect.y) * tile_rect.width + (x - tile_rect.x);
return (selected_pos >= pos && selected_pos < pos + length);
}
static void Fill(uint32_t pos, uint32_t length, const WP2::Rectangle& tile_rect,
WP2::Argb32b color, WP2::ArgbBuffer* out) {
const uint32_t tile_width = tile_rect.width;
const uint32_t start_x = tile_rect.x + pos % tile_width;
const uint32_t start_y = tile_rect.y + pos / tile_width;
const uint32_t end_y = tile_rect.y + (pos + length) / tile_width;
if (start_y == end_y) {
out->Fill({start_x, start_y, length, 1}, color);
return;
}
out->Fill({start_x, start_y, tile_width - pos % tile_width, 1}, color);
if (end_y - start_y > 1) {
out->Fill({tile_rect.x, start_y + 1, tile_width, end_y - start_y - 1},
color);
}
out->Fill({tile_rect.x, end_y, (pos + length) % tile_width, 1}, color);
}
static const char* const kSymbolTypeNames[] = {"Literal", "Copy", "Cache"};
STATIC_ASSERT_ARRAY_SIZE(kSymbolTypeNames, kSymbolTypeNum);
static const char* const kGroup4ModeNames[] = {
"Group4 Vertical", "Group4 Horizontal", "Group4 Pass"};
static const WP2::Argb32b kSymbolColors[2][3] = {
{{255, 255, 0, 0}, {255, 0, 255, 0}, {255, 0, 0, 255}}, // SymbolType
{{255, 255, 255, 0}, {255, 0, 255, 255}, {255, 255, 0, 255}} // Group4
};
WP2Status Decoder::RegisterSymbolForVDebug(
int symbol_type, bool is_group4, uint32_t pos, uint32_t length,
double cost, WP2::DecoderInfo* const decoder_info) {
assert(length > 0);
if (decoder_info == nullptr) return WP2_STATUS_OK;
if (!WP2::VDMatch(decoder_info->visual_debug, "bits-per-pixel/overall") &&
!WP2::VDMatch(decoder_info->visual_debug, "lossless/symbols")) {
return WP2_STATUS_OK;
}
if (WP2::VDMatch(decoder_info->visual_debug, "lossless/symbols")) {
// Only show once per tile.
if (pos == 0 && VDSelected(0, 0, tile_->rect, config_)) {
std::string* const str_ptr = &decoder_info->selection_info;
WP2SAppend(str_ptr, "Tile at (%d, %d)\n", tile_->rect.x, tile_->rect.y);
if (use_group4_) {
WP2SAppend(str_ptr, "Group 4\n");
WP2SAppend(str_ptr, "Move-to-front cache: %s\n",
mtf_.enabled() ? "enabled" : "disabled");
} else {
uint32_t cache_bits = hdr_.cache_config_.cache_bits;
if (cache_bits > 0) {
WP2SAppend(str_ptr, "Color cache: %d bits\n", cache_bits);
} else {
WP2SAppend(str_ptr, "No color cache\n");
}
}
}
Fill(pos, length, tile_->rect, kSymbolColors[is_group4][symbol_type],
&decoder_info->debug_output);
}
const uint32_t tile_width = tile_->rect.width;
if (VDSelectedLossless(pos, length, *decoder_info, tile_->rect)) {
if (WP2::VDMatch(decoder_info->visual_debug, "lossless/symbols")) {
Fill(pos, length, tile_->rect, {255, 255, 255, 255},
&decoder_info->debug_output);
}
std::string* const str_ptr = &decoder_info->selection_info;
WP2SAppend(str_ptr, "Pixel (%d, %d) Symbol at (%u, %u)\n",
decoder_info->selection.x, decoder_info->selection.y,
tile_->rect.x + pos % tile_width,
tile_->rect.y + pos / tile_width);
WP2SAppend(str_ptr, "Type %s length %d cost %f bpp %f\n",
is_group4 ? kGroup4ModeNames[symbol_type]
: kSymbolTypeNames[symbol_type],
length, cost, cost / length);
}
return WP2_STATUS_OK;
}
static const char* const kTransformNames[] = {
"Predictor", "CrossColor", "SubstractGreen", "ColorIndexing", "Group4"};
STATIC_ASSERT_ARRAY_SIZE(kTransformNames, NUM_TRANSFORMS);
WP2Status Decoder::RegisterUnprocessedRowForVDebug(
uint32_t num_rows, uint32_t num_bits, const int16_t* const rows,
WP2::DecoderInfo* const decoder_info) {
if (decoder_info == nullptr) return WP2_STATUS_OK;
if (!WP2::VDMatch(decoder_info->visual_debug, "lossless/transformed")) {
return WP2_STATUS_OK;
}
// Only show once per tile.
if (last_row_ == 0 && VDSelected(0, 0, tile_->rect, config_)) {
std::string* const str_ptr = &decoder_info->selection_info;
WP2SAppend(str_ptr, "Tile at (%d, %d): %d transform(s)\n", tile_->rect.x,
tile_->rect.y, next_transform_);
for (uint32_t i = 0; i < next_transform_; ++i) {
const Transform& transform = transforms_[i];
WP2SAppend(str_ptr, "Transform #%d: %s", i,
kTransformNames[transform.type_]);
if (transform.type_ == CROSS_COLOR_TRANSFORM ||
transform.type_ == PREDICTOR_TRANSFORM) {
WP2SAppend(str_ptr, " Bits: %d", transform.bits_);
} else if (transform.type_ == COLOR_INDEXING_TRANSFORM ||
transform.type_ == GROUP4) {
WP2SAppend(str_ptr, " Num colors: %d", num_colors_);
}
WP2SAppend(str_ptr, "\n");
}
}
const bool opaque = WP2::VDMatch(decoder_info->visual_debug, "force-opaque");
for (uint32_t y = 0; y < num_rows; ++y) {
const uint32_t abs_y = tile_->rect.y + last_row_ + y;
uint8_t* const row = (uint8_t*)decoder_info->debug_output.GetRow(abs_y);
for (uint32_t x = 0; x < tile_->rect.width; ++x) {
const uint32_t abs_x = tile_->rect.x + x;
const uint32_t offset = (y * tile_->rect.width + x) * 4;
if (decoder_info->selection.Contains(abs_x, abs_y)) {
std::string* const str_ptr = &decoder_info->selection_info;
WP2SAppend(str_ptr, "Pixel (%d, %d) argb (%u, %u, %u, %u)\n",
decoder_info->selection.x, decoder_info->selection.y,
rows[offset + 0], rows[offset + 1], rows[offset + 2],
rows[offset + 3]);
}
for (uint32_t c = 0; c < 4; ++c) {
row[abs_x * 4 + c] =
WP2::RightShiftRound(rows[offset + c], num_bits - 8);
}
if (opaque) row[abs_x * 4 + 0] = 255;
}
}
if (VDSelected(0, 0, tile_->rect, config_)) {
decoder_info->debug_output.DrawRect(tile_->rect,
WP2::Argb32b{255, 255, 0, 0});
}
return WP2_STATUS_OK;
}
WP2Status Decoder::HeaderVDebug() {
WP2::DecoderInfo* const decoder_info = config_.info;
if (decoder_info == nullptr) return WP2_STATUS_OK;
if (!WP2::VDMatch(decoder_info->visual_debug, "lossless/clusters")) {
return WP2_STATUS_OK;
}
const uint32_t bits = hdr_.histogram_subsample_bits_;
std::string* const str_ptr = &decoder_info->selection_info;
const uint32_t num_clusters = hdr_.sr_.symbols_info().NumClusters(0);
const WP2::Rectangle& tile_rect = tile_->rect;
if (VDSelected(0, 0, tile_rect, config_)) {
WP2SAppend(str_ptr, "Tile at (%d, %d): %d cluster(s) %d histo_bits\n",
tile_rect.x, tile_rect.y, num_clusters, bits);
}
WP2::ArgbBuffer* const debug_output = &decoder_info->debug_output;
if (num_clusters == 1) {
debug_output->Fill(tile_rect, WP2::Argb32b{255, 0, 0, 0});
} else {
const uint32_t num_blocks_x = SubSampleSize(tile_rect.width, bits);
const uint32_t num_blocks_y = SubSampleSize(tile_rect.height, bits);
for (uint32_t y = 0; y < num_blocks_y; ++y) {
for (uint32_t x = 0; x < num_blocks_x; ++x) {
const WP2::Rectangle rect{
tile_rect.x + (x << bits), tile_rect.y + (y << bits),
std::min(1u << bits, tile_rect.width - (x << bits)),
std::min(1u << bits, tile_rect.height - (y << bits))};
const uint32_t cluster =
hdr_.histogram_image_[4 * (num_blocks_x * y + x) + 2];
const uint8_t gray = WP2::ToPixel8(cluster, num_clusters);
debug_output->Fill(rect, WP2::Argb32b{255, gray, gray, gray});
if (VDSelected(0, 0, rect, config_)) {
WP2SAppend(str_ptr, "Rect (%d, %d) %d x %d : cluster %d\n", rect.x,
rect.y, rect.width, rect.height, cluster);
debug_output->DrawRect(rect, WP2::Argb32b{255, 255, 0, 0});
}
}
}
}
if (VDSelected(0, 0, tile_rect, config_)) {
debug_output->DrawRect(tile_rect, WP2::Argb32b{255, 0, 255, 0});
}
return WP2_STATUS_OK;
}
} // namespace WP2L
#endif // WP2_REDUCE_BINARY_SIZE