| // 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 "src/common/vdebug.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <cmath> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstring> |
| #include <limits> |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include "src/common/color_precision.h" |
| #include "src/common/constants.h" |
| #include "src/common/lossy/block.h" |
| #include "src/common/lossy/block_size.h" |
| #include "src/common/lossy/predictor.h" |
| #include "src/common/lossy/quant_mtx.h" |
| #include "src/common/lossy/segment.h" |
| #include "src/common/lossy/transforms.h" |
| #include "src/common/symbols.h" |
| #include "src/dec/filters/alpha_filter.h" |
| #include "src/dec/filters/block_map_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/lossless/losslessi_dec.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/analysis.h" |
| #include "src/enc/block_enc.h" |
| #include "src/enc/partitioning/partition_score_func.h" |
| #include "src/enc/partitioning/partition_score_func_area.h" |
| #include "src/enc/partitioning/partition_score_func_block.h" |
| #include "src/enc/partitioning/partition_score_func_multi.h" |
| #include "src/enc/partitioning/partition_score_func_tile.h" |
| #include "src/enc/partitioning/partitioner.h" |
| #include "src/enc/partitioning/partitioner_exhaustive.h" |
| #include "src/enc/partitioning/partitioner_multi.h" |
| #include "src/enc/partitioning/partitioner_split_recurse.h" |
| #include "src/enc/wp2_enc_i.h" |
| #include "src/utils/ans.h" |
| #include "src/utils/ans_enc.h" |
| #include "src/utils/csp.h" |
| #include "src/utils/plane.h" |
| #include "src/utils/utils.h" |
| #include "src/utils/vector.h" |
| #include "src/wp2/base.h" |
| #include "src/wp2/debug.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 { |
| |
| //------------------------------------------------------------------------------ |
| |
| void SyntaxWriter::PutRawPixels(const CodedBlockBase& cb, |
| const YUVPlane& pixels, ANSEnc* const enc) { |
| assert(gparams_->transf_.GetYuvDepth().is_signed); |
| const Block& block = cb.blk(); |
| 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; |
| const bool split_tf = enc->PutBool(cb.GetCodingParams(channel).split_tf, |
| "DEEP_MATCH_SPLIT_TF"); |
| const uint32_t num_transforms = |
| GetNumTransformsInBlock(block.dim(), split_tf); |
| for (uint32_t tf_i = 0; tf_i < num_transforms; ++tf_i) { |
| const Rectangle rect = block.LocalTfRect(split_tf, tf_i); |
| for (uint32_t y = rect.y; y < rect.y + rect.height; ++y) { |
| for (uint32_t x = rect.x; x < rect.x + rect.width; ++x) { |
| enc->PutSUValue(pixels.GetChannel(channel).At(x, y), |
| gparams_->transf_.GetYuvDepth().num_bits, |
| kChannelStr[channel]); |
| } |
| } |
| } |
| } |
| } |
| |
| void SyntaxReader::ReadAndCompareRawPixels(const CodedBlockBase& cb, |
| const YUVPlane& pixels, |
| ANSDec* const dec) { |
| assert(gparams_->transf_.GetYuvDepth().is_signed); |
| 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"); |
| const Block& block = cb.blk(); |
| 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; |
| const bool split_tf = dec->ReadBool("DEEP_MATCH_SPLIT_TF"); |
| const uint32_t num_transforms = |
| GetNumTransformsInBlock(block.dim(), split_tf); |
| for (uint32_t tf_i = 0; tf_i < num_transforms; ++tf_i) { |
| const Rectangle rect = block.LocalTfRect(split_tf, tf_i); |
| for (uint32_t y = rect.y; y < rect.y + rect.height; ++y) { |
| for (uint32_t x = rect.x; x < rect.x + rect.width; ++x) { |
| const int16_t expected = dec->ReadSUValue( |
| gparams_->transf_.GetYuvDepth().num_bits, kChannelStr[channel]); |
| const int16_t actual = pixels.GetChannel(channel).At(x, y); |
| if (expected != actual) 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 CodedBlockBase::ToBlockInfo(bool use_aom_coeffs, |
| BlockInfo* const blk) const { |
| const CodingParams& y_params = GetCodingParams(kYChannel); |
| |
| blk->rect = AsRect(); |
| 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::memset(blk->coeffs, 0, sizeof(blk->coeffs)); |
| std::fill(blk->split_tf, blk->split_tf + 4, false); |
| std::fill(&blk->encoding_method[0][0], &blk->encoding_method[0][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] = use_aom_coeffs |
| ? (int)EncodingMethod::kNumMethod + 1 |
| : (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 |
| |
| // encoder part is reset: |
| std::memset(blk->pred_scores, 0, sizeof(blk->pred_scores)); |
| } |
| |
| void CodedBlock::ToBlockInfo(bool use_aom_coeffs, BlockInfo* const blk) const { |
| CodedBlockBase::ToBlockInfo(use_aom_coeffs, blk); |
| |
| // copy the encoder-specific part |
| std::memcpy(blk->pred_scores, pred_scores_, sizeof(pred_scores_)); |
| } |
| |
| #endif // WP2_BITTRACE |
| |
| CodedBlock::SplitTf CodedBlock::GetForcedSplitTf( |
| const EncoderConfig& config, const Rectangle& tile_rect) const { |
| EncoderInfo* const info = config.info; |
| if (info == nullptr) return SplitTf::kUnknown; |
| if (info->disable_split_tf) return SplitTf::kForcedUnsplit; |
| |
| for (const auto& forced : info->force_param) { |
| if (forced.type != EncoderInfo::ForcedParam::Type::kSplitTf) continue; |
| if (forced.x != (x_pix() + tile_rect.x) || |
| forced.y != (y_pix() + tile_rect.y)) { |
| continue; |
| } |
| |
| return forced.value ? SplitTf::kForcedSplit : SplitTf::kForcedUnsplit; |
| } |
| return SplitTf::kUnknown; |
| } |
| |
| TransformPair CodedBlock::GetForcedTransform(const EncoderConfig& config, |
| const Rectangle& tile_rect) const { |
| EncoderInfo* const info = config.info; |
| if (info == nullptr) return kUnknownTf; |
| if (info->disable_transforms) return kDctDct; |
| |
| for (const auto& forced : info->force_param) { |
| if (forced.type != EncoderInfo::ForcedParam::Type::kTransform) continue; |
| if (forced.x != (x_pix() + tile_rect.x) || |
| forced.y != (y_pix() + tile_rect.y)) { |
| continue; |
| } |
| |
| if (forced.value >= kNumTransformPairs) return kUnknownTf; |
| return (TransformPair)forced.value; |
| } |
| return kUnknownTf; |
| } |
| |
| const Predictor* CodedBlock::GetForcedPredictor(const EncoderConfig& config, |
| const Rectangle& tile_rect, |
| const Predictors& preds, |
| Channel channel) const { |
| EncoderInfo* const info = config.info; |
| if (info == nullptr) return nullptr; |
| if (info->disable_preds) return preds[0]; |
| |
| for (const auto& forced : info->force_param) { |
| if (forced.type != EncoderInfo::ForcedParam::Type::kPredictor) continue; |
| if (forced.channel != channel || forced.x != (x_pix() + tile_rect.x) || |
| forced.y != (y_pix() + tile_rect.y)) { |
| continue; |
| } |
| |
| assert(!preds.empty()); |
| const Predictor* const pred = |
| preds[std::min(forced.value, (uint32_t)(preds.size() - 1))]; |
| return pred; |
| } |
| return nullptr; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| static const int kFontW = 8, kFontH = 8; // font size |
| static constexpr uint64_t kFonts[96] = { // Bitmapped 8x8 font packed as u64 |
| 0x0000000000000000, 0x0008000808080800, 0x0000000000242400, |
| 0x00247e24247e2400, 0x107c507c147c1000, 0x0062640810264600, |
| 0x005c225408140800, 0x0000000000081000, 0x0020101010102000, |
| 0x0004080808080400, 0x0028107c10280000, 0x0010107c10100000, |
| 0x0810100000000000, 0x0000007c00000000, 0x0018180000000000, |
| 0x0004081020400000, 0x003c464a52623c00, 0x007c101010141800, |
| 0x007e023c40423c00, 0x003c424030423c00, 0x00107e1214181000, |
| 0x003c42403e027e00, 0x003c42423e023c00, 0x0008081020407e00, |
| 0x003c42423c423c00, 0x003c407c42423c00, 0x0008000008000000, |
| 0x0408080000080000, 0x0020100810200000, 0x00007c007c000000, |
| 0x0008102010080000, 0x0010001020423c00, 0x003c027a6a523c00, |
| 0x0042427e42423c00, 0x003e42423e423e00, 0x003c420202423c00, |
| 0x001e224242221e00, 0x007e02023e027e00, 0x000202023e027e00, |
| 0x003c427202423c00, 0x004242427e424200, 0x007c101010107c00, |
| 0x003c424240404000, 0x004222120e122200, 0x007e020202020200, |
| 0x004242425a664200, 0x004262524a464200, 0x003c424242423c00, |
| 0x0002023e42423e00, 0x003c524a42423c00, 0x0042223e42423e00, |
| 0x003c42403c023c00, 0x0008080808087f00, 0x003c424242424200, |
| 0x0018244242424200, 0x00245a4242424200, 0x0042241818244200, |
| 0x0008080814224100, 0x007e040810207e00, 0x0070101010107000, |
| 0x0020100804020000, 0x000e080808080e00, 0x000808082a1c0800, |
| 0xff00000000000000, 0x007e04041e443800, 0x003c223c201c0000, |
| 0x003c44443c040400, 0x0038040404380000, 0x003c22223c202000, |
| 0x003c021e221c0000, 0x0008080818083000, 0x1c203c22223c0000, |
| 0x002222221e020200, 0x001c08080c000800, 0x1824202020002000, |
| 0x0024140c0c140400, 0x0030080808080800, 0x002a2a2a2a160000, |
| 0x00222222221e0000, 0x001c2222221c0000, 0x02021e22221e0000, |
| 0x60203c22223c0000, 0x0004040404380000, 0x001e201c021c0000, |
| 0x00300808081c0800, 0x001c222222220000, 0x0008141422220000, |
| 0x00142a2a2a220000, 0x0022140814220000, 0x1c203c2222220000, |
| 0x003e0408103e0000, 0x007010100c107000, 0x0010101010101000, |
| 0x000e080830080e00, 0x0000000000142800, 0x3c4299858599423c}; |
| |
| static constexpr uint8_t kFontsSize[96] = { |
| 5, 4, 6, 7, 7, 7, 7, 5, 6, 4, 7, 7, 5, 7, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
| 7, 7, 4, 4, 6, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, |
| 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 4, 6, 8, 7, 6, 7, 6, 6, 6, 6, 6, |
| 6, 5, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 5, 6, 6, 8}; |
| |
| void Print(const std::string& msg, int x, int y, Argb32b color, |
| ArgbBuffer* const out) { |
| assert(WP2FormatBpp(out->format()) == 4); |
| const int x_start = x; // for '\n' |
| for (uint8_t c : msg) { |
| if (c == '\n') { |
| x = x_start; |
| y += kFontH; |
| continue; |
| } |
| // translate to printable character |
| c = ((c >= 32 && c < 96 + 32) ? c : '?') - 32; |
| uint64_t bits = kFonts[c]; |
| const int w = kFontsSize[c]; |
| const uint64_t mask = (1u << w) - 1; |
| const int max_Y = std::min(y + kFontH, (int)out->height()); |
| for (int Y = y; bits && (Y < max_Y); ++Y, bits >>= kFontW) { |
| if (Y >= 0 && (bits & mask)) { |
| for (int i = 0; i < w; ++i) { |
| const int X = x + i; |
| if (X < 0 || X >= (int)out->width()) continue; |
| if (bits & (1u << i)) ToUInt8(color, out->GetRow8(Y) + X * 4); |
| } |
| } |
| } |
| x += w; |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| // 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 'rect', for non-space characters in |
| // 'mask' (or respectively spaces). |
| static void DrawPixelArray(const Rectangle& rect, Argb32b color, |
| Argb32b bg_color, |
| const std::vector<std::string>& mask, uint32_t scale, |
| ArgbBuffer* const debug_output) { |
| for (uint32_t l = 0; l < mask.size(); ++l) { |
| for (uint32_t ls = 0; ls < scale; ++ls) { |
| const uint32_t final_y = rect.y + l * scale + ls; |
| if (final_y >= rect.y + rect.height) break; |
| |
| for (uint32_t c = 0; mask[l][c] != '\0'; ++c) { |
| for (uint32_t cs = 0; cs < scale; ++cs) { |
| const uint32_t final_x = rect.x + c * scale + cs; |
| if (final_x >= rect.x + rect.width) 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 '/'. |
| template <> |
| bool VDMatch<const char*>(const char* const& in, const char token[]) { |
| if (in != nullptr && token != nullptr) { |
| const uint32_t token_length = std::strlen(token); |
| if (token_length == 0) return true; |
| |
| for (const char* pos = in; (pos = std::strstr(pos, token)) != nullptr; |
| pos += token_length) { |
| if ((pos == in || pos[-1] == '/') && // Beginning of a token. |
| (pos[token_length] == '\0' || pos[token_length] == '/')) { // End. |
| return true; // A complete token is matched. |
| } |
| } |
| } |
| return false; |
| } |
| template <> |
| bool VDMatch<std::string>(const std::string& in, const char token[]) { |
| return VDMatch<const char*>(in.c_str(), token); |
| } |
| |
| // 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; |
| } |
| |
| 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; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| namespace { |
| |
| // Maps a value in [0; num_values) to the [-512;512] range. |
| 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. |
| 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); |
| } |
| |
| Argb32b ColorMult(Argb32b color, float multiplier) { |
| return {color.a, (uint8_t)(color.r * multiplier), |
| (uint8_t)(color.g * multiplier), (uint8_t)(color.b * multiplier)}; |
| } |
| |
| } // namespace |
| |
| //------------------------------------------------------------------------------ |
| |
| void CodedBlockBase::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 Rectangle r_px = AsRect().ClipWith(debug_output->AsRect()); |
| |
| const uint32_t w = r_px.width; |
| const uint32_t h = r_px.height; |
| 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& str = config.info->selection_info; |
| str += WP2SPrint("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) str += "All zero\n"; |
| str += WP2SPrint("Transform x: %s\n", WP2TransformNames[params.tf_x()]); |
| str += WP2SPrint("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 CodedBlockBase& cb, |
| const Plane16& pixels) { |
| const Channel channel = VDChannel(config); |
| const bool split_tf = cb.GetCodingParams(channel).split_tf; |
| for (uint32_t tf_i = 0; tf_i < cb.GetNumTransforms(channel); ++tf_i) { |
| const Rectangle rect = cb.blk().TfRect(split_tf, tf_i); |
| if (VDSelected(tile_x, tile_y, rect, config)) { |
| // Print pixels with the same formatting as "prediction/modes". |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("Block at %u, %u px (w %u, h %u) tf %d\n", |
| tile_x + cb.x_pix(), tile_y + cb.y_pix(), cb.w_pix(), |
| cb.h_pix(), tf_i); |
| // To align with output from DisplayPredictionMode(). |
| str += WP2SPrint("\n\n"); |
| const int16_t* const context = |
| cb.GetContext(channel, split_tf, tf_i, kContextSmall); |
| const int16_t* const context_right = |
| cb.GetContext(channel, split_tf, tf_i, kContextExtendRight); |
| const int16_t* const context_left = |
| cb.GetContext(channel, split_tf, tf_i, kContextExtendLeft); |
| str += GetContextAndBlockPixelsStr( |
| context, context_right, context_left, rect.width, rect.height, |
| pixels.Row(rect.y - cb.y_pix()) + (rect.x - cb.x_pix()), |
| pixels.Step()); |
| } |
| } |
| } |
| |
| void CodedBlockBase::StoreResiduals(const DecoderConfig& config, |
| uint32_t tile_x, uint32_t tile_y, |
| const QuantMtx& quant, Channel channel, |
| Plane16* const dst_plane) const { |
| const CodedBlockBase::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); |
| uint32_t tf_i = 0; |
| std::string& str = 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 int16_t* const res = coeffs_[channel][tf_i]; |
| 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) str += WP2SPrint("%4d ", res[x + split_w * y]); |
| } |
| if (selected) str += "\n"; |
| } |
| ++tf_i; |
| } |
| } |
| } |
| |
| void CodedBlock::StoreOriginalResiduals( |
| const EncoderConfig& config, uint32_t tile_pos_x, uint32_t tile_pos_y, |
| int16_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& str = 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) { |
| if (y_pix() + split_y + y >= dst_plane->h_) break; |
| for (uint32_t x = 0; x < split_w; ++x, ++k) { |
| if (x_pix() + split_x + x >= dst_plane->w_) break; |
| const int16_t value = original_res[tf_i][k]; |
| dst_plane->At(x_pix() + split_x + x, y_pix() + split_y + y) = value; |
| if (selected) str += WP2SPrint("%4d ", value); |
| } |
| if (selected) str += "\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 CodedBlockBase& cb, Channel channel, |
| bool split_tf, uint32_t tf_i, |
| bool draw_red_border, |
| ArgbBuffer* const debug_output) { |
| const Rectangle raw_rect = cb.blk().TfRect(split_tf, tf_i); |
| const uint32_t x = raw_rect.x, y = raw_rect.y, w = raw_rect.width, |
| h = raw_rect.height; |
| const Rectangle rect = |
| cb.blk().TfRect(split_tf, tf_i).ClipWith(debug_output->AsRect()); |
| |
| 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, split_tf, tf_i, 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, split_tf, tf_i, 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, split_tf, tf_i, 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])); |
| } |
| } |
| |
| void CodedBlock::StorePredictionScore(const EncoderConfig& config, |
| const Rectangle& tile_rect, |
| Channel channel, const Predictor& pred, |
| TransformPair tf, uint32_t segment_id, |
| const BlockRates& results, |
| bool is_best) 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 = AsRect().ClipWith(debug_output.AsRect()); |
| 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}); |
| |
| std::string line = |
| WP2SPrint("Score %9.1f = dist %9u + lambda %9.1f * (", |
| results.GetScore(), results.distortion, results.lambda); |
| if (results.predictor_rate.is_defined) { |
| line += WP2SPrint("pred %7.4f + ", results.predictor_rate.rate); |
| } |
| if (results.transform_rate.is_defined) { |
| line += WP2SPrint("tf %7.4f + ", results.transform_rate.rate); |
| } |
| if (results.segment_id_rate.is_defined) { |
| line += WP2SPrint("segid %7.4f + ", results.segment_id_rate.rate); |
| } |
| if (results.residuals_rate.is_defined) { |
| line += WP2SPrint("res %7.4f", results.residuals_rate.rate); |
| } |
| line += WP2SPrint(") - %s (%s - %s/%s x%u - %u)", is_best ? "best" : " ", |
| pred.GetName().c_str(), WP2TransformNames[kTfX[tf]], |
| WP2TransformNames[kTfY[tf]], GetNumTransforms(channel), |
| segment_id); |
| |
| // Print at least all better predictors. Skip worse ones if too many. |
| std::string& str = config.info->selection_info; |
| if (is_best || std::count(str.begin(), str.end(), '\n') < 30) { |
| str += line; |
| str += '\n'; |
| } else { |
| // Print in console instead. |
| printf("Block at (%d %d) %s\n", x_pix(), y_pix(), line.c_str()); |
| } |
| } |
| |
| for (uint32_t tf_i = 0; tf_i < GetNumTransforms(channel); ++tf_i) { |
| const bool split_tf = GetCodingParams(channel).split_tf; |
| const Rectangle raw_tf_rect = blk().TfRect(split_tf, tf_i); |
| const Rectangle tf_rect = raw_tf_rect.ClipWith(debug_output.AsRect()); |
| const bool tf_selected = |
| VDSelected(tile_rect.x, tile_rect.y, tf_rect, config); |
| |
| DisplayPredContext(*this, channel, split_tf, tf_i, |
| /*draw_red_border=*/tf_selected, &debug_output); |
| } |
| } |
| |
| static void DrawAnglePredictor(const WP2::Rectangle& rect, float angle_deg, |
| WP2::ArgbBuffer* const debug_output) { |
| if (angle_deg == std::numeric_limits<float>::max()) return; |
| const float angle = (M_PI / 180.f) * angle_deg; |
| const float cos = std::cos(angle); |
| const float sin = std::sin(angle); |
| const uint32_t pixel_depth = WP2FormatBpp(debug_output->format()); |
| const uint32_t length = |
| std::sqrt(rect.width * rect.width + rect.height * rect.height) / 2; |
| for (float i = 0.f; i < length; i += 0.5f) { |
| const int y = std::round(rect.y + (rect.height - 1) / 2.f - i * sin); |
| const int x = std::round(rect.x + (rect.width - 1) / 2.f + i * cos); |
| if (!rect.Contains(x, y)) break; |
| const uint8_t color[4] = {255, 255, 0, 0}; |
| std::memcpy(debug_output->GetPosition(x, y), color, pixel_depth); |
| } |
| } |
| |
| static constexpr Argb32b kNonAnglePredictorColors[] = { |
| {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 |
| {0xff, 0x20, 0x6b, 0x00}, // Dark green |
| {0xff, 0x19, 0x19, 0x70}, // Dark blue |
| {0xff, 0xe8, 0xd0, 0xb3}, // Desert |
| {0xff, 0xfb, 0xbe, 0xf4}, // Light pink |
| }; |
| // If this fails, add some more colors to the array above. |
| static_assert(ArraySize(kNonAnglePredictorColors) >= kYBasePredNum, |
| "Not enough colors"); |
| static_assert(ArraySize(kNonAnglePredictorColors) >= kABasePredNum, |
| "Not enough colors"); |
| static_assert(ArraySize(kNonAnglePredictorColors) >= kUVBasePredNum, |
| "Not enough colors"); |
| |
| // 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 CodedBlockBase& cb, const Predictors& preds, const Predictor& p, |
| Channel channel, uint32_t tf_i, const Rectangle& block_rect, bool selected, |
| Plane16* const raw_prediction, ArgbBuffer* const debug_output) { |
| const bool split_tf = cb.GetCodingParams(channel).split_tf; |
| if (raw_prediction != nullptr) { |
| Plane16 dst_view; |
| WP2_ASSERT_STATUS( |
| dst_view.SetView(*raw_prediction, cb.blk().TfRect(split_tf, tf_i))); |
| const Predictor& pred = *cb.GetCodingParams(channel).pred; |
| pred.Predict(cb, channel, split_tf, tf_i, dst_view.Row(0), dst_view.Step()); |
| } else { |
| assert(debug_output != nullptr); |
| float angle; |
| if (p.IsAngle(&angle)) { |
| // Map angle to light gray value in [128; 255] |
| const uint8_t gray = ToPixel8(p.mode(), kAnglePredNum) / 2 + 128; |
| debug_output->Fill(block_rect, Argb32b{0xff, gray, gray, gray}); |
| DrawAnglePredictor(block_rect, angle, debug_output); |
| } else { |
| // Find this index of this predictor out of the non-angle predictors. |
| uint32_t i = 0; |
| bool found = false; |
| for (const Predictor* pred : preds) { |
| if (pred->IsAngle(nullptr)) continue; |
| if (pred->mode() == p.mode()) { |
| found = true; |
| break; |
| } |
| ++i; |
| } |
| (void)found; |
| assert(found); |
| assert(i < ArraySize(kNonAnglePredictorColors)); |
| debug_output->Fill(block_rect, kNonAnglePredictorColors[i]); |
| } |
| if (selected) { |
| debug_output->DrawRect(block_rect, Argb32b{0xff, 0xff, 0x33, 0x33}); |
| } |
| // draw split borders |
| if (split_tf) { |
| const BlockSize split_size = GetSplitSize(cb.dim(), true); |
| const uint32_t split_w = BlockWidthPix(split_size); |
| const uint32_t split_h = BlockHeightPix(split_size); |
| for (uint32_t split_y = 0; split_y < cb.h_pix(); split_y += split_h) { |
| for (uint32_t split_x = 0; split_x < cb.w_pix(); split_x += split_w) { |
| constexpr Argb32b kBlack = Argb32b{0xff, 0x00, 0x00, 0x00}; |
| debug_output->Fill( |
| {block_rect.x + split_x, block_rect.y + split_y, 1, split_h}, |
| kBlack); |
| debug_output->Fill( |
| {block_rect.x + split_x, block_rect.y + split_y, split_w, 1}, |
| kBlack); |
| } |
| } |
| } |
| } |
| |
| if (selected) { |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("Prediction block at %u, %u px (w %u, h %u)", |
| block_rect.x + tile_rect.x, block_rect.y + tile_rect.y, |
| block_rect.width, block_rect.height, tf_i); |
| if (split_tf) { |
| str += WP2SPrint(" tf %d", tf_i); |
| } |
| str += "\n"; |
| |
| str += WP2SPrint(" %u: %s\n", p.mode(), p.GetName().c_str()); |
| |
| if (!VDMatch(config, "short")) { |
| const std::string prediction = p.GetPredStr(cb, channel, split_tf, tf_i); |
| str += "Prediction:\n" + prediction; |
| } |
| } |
| } |
| |
| void CodedBlockBase::StorePredictionModes( |
| const DecoderConfig& config, const Rectangle& tile_rect, Channel channel, |
| uint32_t tf_i, const Predictors& preds, Plane16* const raw_prediction, |
| ArgbBuffer* const debug_output) const { |
| const bool split_tf = GetCodingParams(channel).split_tf; |
| |
| const Rectangle raw_rect = blk().TfRect(split_tf, tf_i); |
| const Rectangle r_px{ |
| raw_rect.x, raw_rect.y, |
| std::min(raw_rect.width, tile_rect.width - raw_rect.x), |
| std::min(raw_rect.height, tile_rect.height - raw_rect.y)}; |
| |
| const bool selected = VDSelected(tile_rect.x, tile_rect.y, r_px, config); |
| |
| const CodingParams& params = GetCodingParams(channel); |
| DisplayPredictionMode(config, tile_rect, *this, preds, *params.pred, channel, |
| tf_i, r_px, selected, raw_prediction, debug_output); |
| |
| if (selected && debug_output != nullptr) { |
| DisplayPredContext(*this, channel, split_tf, tf_i, /*draw_red_border=*/true, |
| debug_output); |
| } |
| } |
| |
| void CodedBlockBase::AppendOriginalPixels( |
| const DecoderConfig& config, uint32_t tile_x, uint32_t tile_y, |
| const CSPTransform& csp_transform, ArgbBuffer* const debug_output) const { |
| if (!(VDMatch(config, "y") || VDMatch(config, "u") || VDMatch(config, "v") || |
| VDMatch(config, "a"))) { |
| return; |
| } |
| if (VDSelected(AsRect(tile_x, tile_y), 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_transform, |
| /*resize_if_needed=*/true, /*pad=*/kPredWidth)); |
| // Use a dummy CodedBlockBase to get the 'context' out of 'original_yuv'. |
| CodedBlockBase 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, AsRect())); |
| OutputPixelValuesWithContext(config, tile_x, tile_y, cb, |
| view.GetChannel(channel)); |
| } |
| } |
| |
| void CodedBlockBase::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; |
| } |
| const Channel channel = VDChannel(config); |
| if (channel == kAChannel && !HasLossyAlpha()) return; |
| |
| if (VDSelected(AsRect(tile_x, tile_y), config)) { |
| OutputPixelValuesWithContext(config, tile_x, tile_y, *this, |
| out_.GetChannel(channel)); |
| 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.AsRect(); |
| 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& str = config.info->selection_info; |
| str += WP2SPrint("\nBlock at %u, %u px\n\n", rect.x, rect.y); |
| str += WP2SPrint("%f bits over %u pixels\n", num_bits, |
| (uint32_t)rect.GetArea()); |
| str += WP2SPrint(" = %f bpp\n", num_bits / rect.GetArea()); |
| } |
| #endif // defined(WP2_BITTRACE) |
| } |
| |
| void CodedBlockBase::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 + 1)); |
| ++tf_i; |
| } |
| } |
| } |
| |
| void CodedBlockBase::StoreHasCoeffs(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)num_coeffs_[channel][tf_i] > 0, 2)); |
| ++tf_i; |
| } |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Cfl |
| |
| static constexpr float CflNorm = 1.f / (1 << kCflFracBits); |
| |
| void CodedBlockBase::StoreCflSlope(Channel channel, int16_t yuv_min, |
| int16_t yuv_max, Plane16* const dst_plane, |
| std::string* const debug_str) const { |
| int32_t a, b; |
| CflPredictor cfl_predictor(yuv_min, yuv_max); |
| cfl_predictor.ContextLinearRegression(channel, *this, &a, &b); |
| dst_plane->Fill(blk_.AsRect(), (int16_t)std::lround(a * 100.f * CflNorm)); |
| |
| if (debug_str != nullptr) { |
| *debug_str += |
| WP2SPrint("Chroma = %.2f * luma + %.2f\n", CflNorm * a, CflNorm * b); |
| } |
| } |
| |
| void CodedBlockBase::StoreCflIntercept(Channel channel, int16_t yuv_min, |
| int16_t yuv_max, |
| Plane16* const dst_plane, |
| std::string* const debug_str) const { |
| int32_t a, b; |
| CflPredictor cfl_predictor(yuv_min, yuv_max); |
| cfl_predictor.ContextLinearRegression(channel, *this, &a, &b); |
| dst_plane->Fill(blk_.AsRect(), (int16_t)std::lround(0.5f * CflNorm * b)); |
| |
| if (debug_str != nullptr) { |
| *debug_str = |
| WP2SPrint("Chroma = %.2f * luma + %.2f\n", CflNorm * a, CflNorm * b); |
| } |
| } |
| |
| // 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 { |
| int32_t a_int, b_int; |
| CflPredictor cfl_predictor(yuv_min, yuv_max); |
| cfl_predictor.BlockLinearRegression(channel, *this, &a_int, &b_int); |
| *a = CflNorm * a_int; |
| *b = CflNorm * b_int; |
| |
| if (debug_str != nullptr) { |
| *debug_str += WP2SPrint("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(AsRect(), (int16_t)std::lround(a * 100.f)); |
| } |
| |
| 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(AsRect(), (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); |
| const Rectangle rect = AsRect().ClipWith(dst_plane->AsRect()); |
| |
| float a, b; |
| CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str); |
| |
| for (uint32_t y = 0; y < rect.height; ++y) { |
| for (uint32_t x = 0; x < rect.width; ++x) { |
| const int32_t v = std::lround(a * luma.At(x, y) + b); |
| dst_plane->At(rect.x + x, rect.y + 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); |
| const Rectangle rect = AsRect().ClipWith(dst_plane->AsRect()); |
| |
| float a, b; |
| CflLinearRegression(channel, yuv_min, yuv_max, &a, &b, debug_str); |
| |
| for (uint32_t y = 0; y < rect.height; ++y) { |
| for (uint32_t x = 0; x < rect.width; ++x) { |
| const int32_t v = std::lround(a * luma.At(x, y) + b); |
| dst_plane->At(rect.x + x, rect.y + 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(AsRect(tile_x, tile_y), config)) { |
| config.info->selection_info += |
| WP2SPrint("Block at %4u, %4u (%2u x %2u px)\n", tile_x + x_pix(), |
| tile_y + y_pix(), w_pix(), h_pix()); |
| config.info->selection_info += |
| WP2SPrint("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(AsRect(), 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; |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| static std::string PrintQuantSteps(const uint16_t steps[4], const char name[]) { |
| return WP2SPrint(" %s quant_steps, DC: %d AC: %d %d %d %d\n", name, steps[0], |
| steps[1], steps[2], steps[3], steps[4]); |
| } |
| |
| static std::string PrintSegmentInfo(const Segment& segment, bool is_alpha) { |
| std::string str; |
| if (segment.use_quality_factor_) { |
| const float q = 1.f * segment.quality_factor_ / kQFactorMax; |
| str += WP2SPrint(" quality_factor_: %d (q ~= %.1f)\n", |
| segment.quality_factor_, kMaxLossyQuality * (1.f - q)); |
| } |
| if (is_alpha) { |
| str += PrintQuantSteps(segment.quant_steps_[kAChannel], "A"); |
| } else { |
| str += PrintQuantSteps(segment.quant_steps_[kYChannel], "Y"); |
| str += PrintQuantSteps(segment.quant_steps_[kUChannel], "U"); |
| str += PrintQuantSteps(segment.quant_steps_[kVChannel], "V"); |
| } |
| return str; |
| } |
| |
| WP2Status CodedBlockBase::Draw(const DecoderConfig& config, const Tile& tile, |
| const GlobalParams& gparams, |
| ArgbBuffer* const debug_output) const { |
| assert(VDMatch(config, "blocks")); |
| const Rectangle rect = AsRect().ClipWith(debug_output->AsRect()); |
| const uint32_t x = rect.x; |
| const uint32_t y = rect.y; |
| const uint32_t w = rect.width; // Border |
| const uint32_t h = rect.height; |
| const uint32_t mid_w = std::min(w - 2, debug_output->width() - (x + 1)); |
| const uint32_t mid_h = std::min(h - 2, debug_output->height() - (y + 1)); |
| const Rectangle sub_rect = {x + 1, y + 1, mid_w, mid_h}; |
| |
| { // Copy the decoded content in the block's sub-area. |
| YUVPlane tmp_yuv; |
| WP2_CHECK_STATUS(tmp_yuv.SetView(tile.yuv_output, rect)); |
| ArgbBuffer tmp_argb; |
| WP2_CHECK_STATUS(tmp_argb.SetView(*debug_output, rect)); |
| WP2_CHECK_STATUS( |
| tmp_yuv.Export(gparams.transf_, /*resize_if_needed=*/false, &tmp_argb)); |
| } |
| |
| 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->DrawRect(rect, Argb32b{0xff, 0xff, 0xff, 0x30}, 0x03); |
| } 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(rect, 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_]; |
| debug_output->Fill(sub_rect, 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{255u, (uint8_t)std::min((uint32_t)(red * mult), 255u), |
| (uint8_t)std::min((uint32_t)(green * mult), 255u), 0u}; |
| debug_output->Fill(rect, color); |
| } |
| |
| if (VDSelected(tile.rect.x, tile.rect.y, rect, config)) { |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("\nSegment id: %u\n", id_); |
| str += PrintSegmentInfo(segment, is_alpha); |
| |
| debug_output->Fill(rect, Argb32b{255, 0, 0, 0}); |
| debug_output->Fill(sub_rect, color); |
| } |
| } else if (VDMatch(config, "is420")) { |
| debug_output->DrawRect(rect, is420_ ? Argb32b{0xff, 0x11, 0xff, 0x11} |
| : Argb32b{0xff, 0xdd, 0xdd, 0xdd}); |
| if (is420_) { |
| DrawPixelArray(sub_rect, 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(AsRect(pos_x, pos_y), config)) { |
| config.info->selection_info += WP2SPrint( |
| "%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(AsRect(pos_x, pos_y), config)) { |
| 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; |
| const Argb32b kColors[3] = {Argb32b{0xff, 0xff, 0x11, 0xff}, |
| Argb32b{0xff, 0xff, 0x11, 0x11}, |
| Argb32b{0xff, 0x11, 0xff, 0x11}}; |
| const Rectangle rect = AsRect().ClipWith(debug_output.AsRect()); |
| debug_output.Fill(rect, kColors[(int)decision]); |
| debug_output.DrawRect(rect, Argb32b{0xff, 0x00, 0x00, 0x00}, 0x03); |
| 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(AsRect(pos_x, pos_y), config); |
| |
| if (selected) { |
| config.info->selection_info += WP2SPrint("lambda-mult: %.3f", lambda_mult_); |
| } |
| |
| ArgbBuffer& debug_output = config.info->debug_output; |
| const Rectangle rect = AsRect(pos_x, pos_y).ClipWith(debug_output.AsRect()); |
| 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& str = config.info->selection_info; |
| str += WP2SPrint("\nTile at %u, %u px (%u x %u)\n", rect.x, rect.y, |
| rect.width, rect.height); |
| assert(chunk_size_is_known); |
| str += WP2SPrint("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 { |
| return WP2SPrint("min: %.3f, avg: %.3f, avg: %.3f (%u)", min_, GetAvg(), |
| max_, num_values_); |
| } |
| |
| 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 Rectangle rect = |
| Rectangle(bx * kMinBlockSizePix, by * kMinBlockSizePix, |
| BlockWidthPix(block_features.size), |
| BlockHeightPix(block_features.size)) |
| .ClipWith(debug_output->AsRect()); |
| |
| const uint8_t color = |
| (vd == -1) ? block_features.min_bpp : block_features.res_den[vd]; |
| debug_output->Fill(rect, Argb32b{255, color, 0, 0}); |
| debug_output->DrawRect(rect, Argb32b{255, 128, 128, 128}, 0x03); |
| |
| if (VDSelected(tile_rect_.x, tile_rect_.y, rect, config)) { |
| debug_output->DrawRect(rect, Argb32b{255, 255, 255, 255}); |
| |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("\nBlock at %u, %u px\n\n", rect.x, rect.y); |
| |
| str += WP2SPrint("Min bits-per-pixel: %f\n", |
| block_features.min_bpp / 128.); |
| for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| str += WP2SPrint("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& str = config.info->selection_info; |
| if (before_deblocking) { |
| str += WP2SPrint("\nEdge near %u, %u px (%s)\n\n", q0_x, q0_y, |
| kChannelStr[channel]); |
| |
| str += WP2SPrint(" Half-length: %u\n", filtered_half); |
| str += WP2SPrint(" Strength: %u\n", strength); |
| str += WP2SPrint(" Sharpness: %u\n", sharpness); |
| str += WP2SPrint(" 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) str += " |"; |
| str += WP2SPrint(" %4d", q0[i * (int32_t)step]); |
| } |
| str += "\n"; |
| } else { // !before_deblocking |
| for (int i = -(int32_t)max_half; i < (int32_t)max_half; ++i) { |
| if (i == 0) str += " |"; |
| str += WP2SPrint(" %4d", q0[i * (int32_t)step]); |
| } |
| str += "\n\n"; |
| str += WP2SPrint(" 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 { |
| ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, |
| blocks_.bit_depth_.max(), 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) { |
| ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, |
| blocks_.bit_depth_.max(), 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) { |
| ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, |
| blocks_.bit_depth_.max(), 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& str = config_.info->selection_info; |
| str += WP2SPrint("\nStrength %s\n\n", |
| min_max_avg_strength.ToString().c_str()); |
| |
| const uint32_t area = from_y / kWieFltHeight; |
| for (Channel c : {kYChannel, kUChannel, kVChannel}) { |
| str += WP2SPrint("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); |
| str += (h_or_v == 0) ? " Horizontal:" : " Vertical: "; |
| for (int32_t weight : tap_weights) { |
| str += WP2SPrint( |
| " %6.3f", (double)weight / (1u << (kWieFltNumBitsTapWgts - 1))); |
| } |
| str += '\n'; |
| } |
| } |
| } |
| } |
| } |
| |
| void RestorationFilter::ApplyVDebug(uint32_t num_rows) { |
| if (VDMatch(config_, "diff") && num_rows == blocks_.tile_rect_.height) { |
| ApplyVDebugDiff(unfiltered_pixels_, *blocks_.pixels_, |
| blocks_.bit_depth_.max(), 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; |
| ApplyVDebugDiff(unfiltered_pixels_, canvas_, blocks.bit_depth_.max(), 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()); |
| uint32_t alpha_channel; |
| if (!WP2FormatHasAlpha(debug_output->format(), &alpha_channel)) { |
| alpha_channel = num_channels; |
| } |
| |
| 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_transform, 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_transform, /*resize_if_needed=*/false, &argb)); |
| ApplyVDebugDiff(*debug_output, argb, 2, debug_output); |
| } else if (VDMatch(config, "y") || VDMatch(config, "u") || |
| VDMatch(config, "v")) { |
| if (VDMatch(config, "original")) { |
| csp_transform.Apply(debug_output, csp_transform.GetYuvDepth(), 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, csp_transform.GetYuvDepth())); |
| } |
| } 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_transform, /*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& str = config.info->selection_info; |
| str += WP2SPrint("\nArea %u x %u at %u, %u\n", rect.width, rect.height, |
| rect.x + tile.rect.x, rect.y + tile.rect.y); |
| str += WP2SPrint("\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(); |
| str += WP2SPrint(" Bucket %2u: %5.2f%% (%8u) ", bucket, percent, |
| histogram[bucket]); |
| str += std::string(std::lround(percent), '|'); |
| str += "\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()); |
| str += WP2SPrint("\nNum clusters: %u\n", num_clusters); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Encoder-related |
| |
| static constexpr BitDepth kYuvMaxBitDepth = {kYuvMaxPrec + 1, |
| /*is_signed=*/true}; |
| |
| // 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& str = config_->info->selection_info; |
| str += WP2SPrint("\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, kYuvMaxBitDepth)); |
| } |
| debug_output.DrawRect( |
| debug_output.AsRect(), |
| 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)}, |
| 0x03); |
| } else if (VDMatch(*config_, "score")) { |
| WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kYuvMaxBitDepth)); |
| } |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| bool PartitionScoreFunc::VDebugBlockSelected(const Block& block) const { |
| // Top left corner of the block must be selected. |
| return VDSelected( |
| tile_rect_.x, tile_rect_.y, |
| {block.x_pix(), block.y_pix(), kMinBlockSizePix, kMinBlockSizePix}, |
| *config_); |
| } |
| |
| // Should be called after ClearVDebug() and for each block score computed. |
| WP2Status PartitionScoreFunc::RegisterScoreForVDebug( |
| const Block& block, float score, bool force_selected, |
| bool ending_new_line) const { |
| if (!VDMatch(*config_, "encoder/partition/score") || |
| (!force_selected && !VDebugBlockSelected(block))) { |
| return WP2_STATUS_OK; |
| } |
| if (config_->info != nullptr) { |
| // 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}); |
| |
| std::string& str = config_->info->selection_info; |
| str += WP2SPrint("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) str += "\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; |
| const Rectangle rect = block.AsRect(tile_rect_.x, tile_rect_.y) |
| .ClipWith(debug_output->AsRect()); |
| |
| 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}, 0x03); |
| } else if (VDMatch(*config_, "all")) { |
| const Argb32b kPassColor[] = {{255, 141, 202, 175}, |
| {255, 140, 73, 192}, |
| {255, 205, 104, 150}, |
| {255, 133, 148, 196}}; |
| STATIC_ASSERT_ARRAY_SIZE(kPassColor, |
| (int)MultiScoreFunc::Pass::Any + 1); |
| debug_output->DrawRect(rect, kPassColor[pass], 0x03); |
| } |
| } |
| |
| if (VDSelected(rect, *config_)) { |
| // Highlight the border. |
| debug_output->DrawRect(rect, Argb32b{255, 255, 0, 0}); |
| |
| std::string& str = config_->info->selection_info; |
| str += WP2SPrint("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; |
| } |
| |
| bool SplitRecursePartitioner::VDebugBlockSelected(const Block& block) const { |
| // Top left corner of the block must be selected. |
| return VDSelected( |
| tile_rect_.x, tile_rect_.y, |
| {block.x_pix(), block.y_pix(), kMinBlockSizePix, kMinBlockSizePix}, |
| *config_); |
| } |
| |
| WP2Status SplitRecursePartitioner::VDebugRecursionLevel( |
| const Block& block, uint32_t recursion_level) const { |
| if (!VDMatch(*config_, "encoder/partition/score")) { |
| return WP2_STATUS_OK; |
| } |
| |
| if (config_->info != nullptr) { |
| std::string& str = config_->info->selection_info; |
| for (uint32_t i = 0; i < recursion_level; ++i) { |
| str += WP2SPrint(" "); |
| } |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status SplitRecursePartitioner::RegisterScoreForVDebug( |
| const Block& block, uint32_t recursion_level, const float scores[4], |
| float extra_rate, float total_score, bool is_best) const { |
| if (!VDMatch(*config_, "encoder/partition/score")) { |
| return WP2_STATUS_OK; |
| } |
| |
| if (config_->info != nullptr) { |
| WP2_CHECK_STATUS(VDebugRecursionLevel(block, recursion_level)); |
| |
| std::string& str = config_->info->selection_info; |
| str += WP2SPrint("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(), total_score); |
| |
| str += WP2SPrint(" %s ", is_best ? "best" : " "); |
| str += WP2SPrint(" 4 blks, recursive scores: "); |
| for (uint32_t i = 0; i < 4; ++i) { |
| str += WP2SPrint(" %.3f ", scores[i]); |
| } |
| str += WP2SPrint(" +r%.3f", extra_rate); |
| str += "\n"; |
| } |
| |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status BlockScoreFunc::RegisterScoreForVDebug( |
| const Block blocks[], uint32_t num_blocks, const float rate[4], |
| const float disto[4], float extra_rate, float total_rate, float total_disto, |
| float score, bool is_best, bool force_selected) { |
| 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)); |
| if (!VDMatch(*config_, "encoder/partition/score") || |
| (!force_selected && !VDebugBlockSelected(hull_block))) { |
| return WP2_STATUS_OK; |
| } |
| |
| WP2_CHECK_STATUS(PartitionScoreFunc::RegisterScoreForVDebug( |
| hull_block, score, force_selected, /*ending_new_line=*/false)); |
| |
| if (config_->info != nullptr) { |
| std::string& str = config_->info->selection_info; |
| str += WP2SPrint(" %s rate %3.2f disto %6.2f", is_best ? "best" : " ", |
| total_rate, total_disto); |
| if (num_blocks > 1) { // Print sub-blocks stats. |
| str += WP2SPrint(" %u blks: ", num_blocks); |
| for (uint32_t i = 0; i < num_blocks; ++i) { |
| str += WP2SPrint(" %ux%u(r%.2f,d%.2f)", blocks[i].w_pix(), |
| blocks[i].h_pix(), rate[i], disto[i]); |
| } |
| } |
| str += WP2SPrint(" +r%.3f", extra_rate); |
| str += "\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& str = config_->info->selection_info; |
| if (grid_size == BLK_LAST) { // Meaning default partitioning (happens once). |
| str += |
| WP2SPrint("\nArea at %4u, %4u (%2u x %2u px)\n", tile_rect_.x + area_.x, |
| tile_rect_.y + area_.y, area_.width, area_.height); |
| str += WP2SPrint("\nscore: %9.3f = disto %7.3f + rate %7.3f\n", score, |
| disto, rate); |
| str += " (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.AsRect(), Argb32b{255, 128, 255, 0}, 0x03); |
| } |
| } else { // Grid partitioning (happens for each of several block sizes). |
| str += WP2SPrint("\nscore: %9.3f = disto %7.3f + rate %7.3f\n", score, |
| disto, rate); |
| str += WP2SPrint(" (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& str = config_->info->selection_info; |
| str += WP2SPrint(" disto %7.3f + rate %7.3f", disto, rate); |
| if (default_block_.dim() == BLK_LAST) str += " (default)"; |
| str += "\n"; |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status TileScoreFunc::RegisterScoreForVDebug(const char label[], |
| const Block& block, |
| float score) const { |
| if (VDMatch(*config_, "encoder/partition/score")) { |
| std::string& str = config_->info->selection_info; |
| str += |
| WP2SPrint("\n%s: score %.6f, %4u blocks, %4u-byte tile", label, score, |
| (uint32_t)blocks_.size(), |
| (enc_tiles_layout_.tiles.front().data.size > 0) |
| ? enc_tiles_layout_.tiles.front().data.size |
| : enc_tiles_layout_.tiles.front().enc.GetBitstreamSize()); |
| if (block.dim() != BLK_LAST) { |
| str += WP2SPrint(", on block at %3u,%3u (%2ux%2u)", block.x_pix(), |
| block.y_pix(), block.w_pix(), block.h_pix()); |
| } |
| str += WP2SPrint(", 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& str = config_->info->selection_info; |
| str += WP2SPrint("\n\nNum iterations: %u\n", (uint32_t)num_iterations); |
| str += WP2SPrint("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", "Direction", |
| "Any"}; |
| STATIC_ASSERT_ARRAY_SIZE(kPassStr, (int)MultiScoreFunc::Pass::Any + 1); |
| std::string& str = config_->info->selection_info; |
| str += WP2SPrint("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_, "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 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& str = config_->info->selection_info; |
| str += WP2SPrint("\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); |
| str += WP2SPrint("Segment id: %u\n", segment_id); |
| str += WP2SPrint( |
| "YA gradient: %5.3f = %5.2f / %5.2f\n", |
| GetLumaAlphaGradient(block) / GetLumaAlphaGradientThreshold(block), |
| GetLumaAlphaGradient(block), GetLumaAlphaGradientThreshold(block)); |
| str += WP2SPrint("Std dev: %5.3f = %5.2f / %5.2f\n", |
| GetStdDevRange(block) / GetStdDevRangeThreshold(block), |
| GetStdDevRange(block), GetStdDevRangeThreshold(block)); |
| str += WP2SPrint("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_)); |
| |
| // 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, kYuvMaxBitDepth)); |
| 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& str = config_->info->selection_info; |
| str += WP2SPrint("Block at %4u, %4u: \n", x, y); |
| str += WP2SPrint(" angle %u degrees\n", angle_deg); |
| str += WP2SPrint(" certainty %u/3\n", direction_certainty_[i]); |
| } |
| } |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| // Original luma. |
| WP2_CHECK_STATUS(src_->Y.ToGray(&debug_output, kYuvMaxBitDepth)); |
| |
| Block block(0, 0, |
| VDMatch(*config_, "32x32") ? BLK_32x32 |
| : VDMatch(*config_, "16x16") ? BLK_16x16 |
| : VDMatch(*config_, "8x8") ? BLK_8x8 |
| : BLK_4x4); |
| const uint32_t cell_width = |
| config_->partition_snapping ? block.w_pix() : kMinBlockSizePix; |
| const uint32_t cell_height = |
| config_->partition_snapping ? 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 Rectangle cell_rect = |
| Rectangle(block.x_pix(), block.y_pix(), cell_width, cell_height) |
| .ClipWith(debug_output.AsRect()); |
| DrawValueThresholdColor(cell_width, cell_height, block, &debug_output); |
| if (VDSelected(tile_rect_.x, tile_rect_.y, cell_rect, *config_)) { |
| selected_block = block; |
| } |
| } |
| } |
| |
| // Display selected block on top. |
| if (selected_block.dim() != BLK_LAST) { |
| const Rectangle block_rect = |
| selected_block.AsRect().ClipWith(debug_output.AsRect()); |
| WP2_CHECK_STATUS(DrawSelection(selected_block, block_rect, &debug_output)); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| void GrainVDebug(const EncoderConfig& config, uint32_t x_pix, uint32_t y_pix, |
| Channel channel, FeatureMap::NoiseStr& noise_info, |
| int32_t* const coeffs) { |
| if (!VDMatch(config, "encoder/grain") || VDMatch(config, "segment_y") || |
| VDMatch(config, "segment_uv")) { |
| return; |
| } |
| if (channel != VDChannel(config)) return; |
| const bool is_strength = VDMatch(config, "strength"); |
| ArgbBuffer* const debug_output = &config.info->debug_output; |
| const Rectangle rect = |
| Rectangle(x_pix, y_pix, kMinBlockSizePix, kMinBlockSizePix) |
| .ClipWith(debug_output->AsRect()); |
| const uint16_t v = is_strength ? noise_info.strength[(int)channel] |
| : noise_info.freq[(int)channel]; |
| const uint16_t num_values = is_strength ? 101 : 7; // [0-100] or [0-6] |
| const uint8_t gray = ToPixel8(v, num_values); |
| debug_output->Fill(rect, Argb32b{255, gray, gray, gray}); |
| |
| if (VDSelected(rect, config)) { |
| std::string& str = config.info->selection_info; |
| |
| str += WP2SPrint("%u, %u px strength: %d freq: %d\n", x_pix, y_pix, |
| noise_info.strength[(int)channel], |
| noise_info.freq[(int)channel]); |
| |
| uint32_t k = 0; |
| for (uint32_t j = 0; j < kMinBlockSizePix; ++j) { |
| for (uint32_t i = 0; i < kMinBlockSizePix; ++i, ++k) { |
| if (i == 0 && j == 0) { |
| str += WP2SPrint(" X "); |
| } else { |
| str += WP2SPrint("%3d ", coeffs[k]); |
| } |
| } |
| str += WP2SPrint("\n"); |
| } |
| debug_output->DrawRect(rect, Argb32b{255, 255, 0, 0}); |
| } |
| } |
| |
| void SegmentGrainVDebug(const EncoderConfig& config, const CodedBlock& cb, |
| uint32_t tile_pos_x, uint32_t tile_pos_y, |
| const Segment& segment) { |
| if (!VDMatch(config, "encoder/grain/segments_y") && |
| !VDMatch(config, "encoder/grain/segments_uv")) { |
| return; |
| } |
| |
| ArgbBuffer* const debug_output = &config.info->debug_output; |
| const bool is_strength = VDMatch(config, "strength"); |
| const bool is_y = VDMatch(config, "segments_y"); |
| const GrainParams& grain = segment.grain_; |
| const uint16_t v = is_strength ? (is_y ? grain.y_ : grain.uv_) |
| : (is_y ? grain.freq_y_ : grain.freq_uv_); |
| const uint16_t num_values = is_strength ? 16 : 7; // [0-15] or [0-6] |
| const uint8_t gray = ToPixel8(v, num_values); |
| const Rectangle& rect = |
| cb.AsRect(tile_pos_x, tile_pos_y).ClipWith(debug_output->AsRect()); |
| debug_output->Fill(rect, Argb32b{255, gray, gray, gray}); |
| 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_]); |
| |
| if (VDSelected(cb.AsRect(tile_pos_x, tile_pos_y), config)) { |
| std::string& str = config.info->selection_info; |
| str += |
| WP2SPrint("Segment %d Y strength %d feq %d | UV strength %d feq %d\n", |
| cb.id_, grain.y_, grain.freq_y_, grain.uv_, grain.freq_uv_); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| 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; |
| const Rectangle rect2 = {tile_pos_x + rect.x, tile_pos_y + rect.y, rect.width, |
| rect.height}; |
| if (VDSelected(rect2, config)) { |
| config.info->debug_output.Fill(rect2, Argb32b{255, 128, 128, 0}); |
| |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("\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); |
| } else { |
| config.info->debug_output.Fill(rect2.ClipWith(debug_output->AsRect()), |
| Argb32b{255, gray, gray, gray}); |
| } |
| } |
| |
| 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.AsRect(), 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_]); |
| |
| if (VDSelected(cb.AsRect(tile_pos_x, tile_pos_y), config)) { |
| std::string& str = config.info->selection_info; |
| str += WP2SPrint("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); |
| } |
| |
| // Fills a copy-segment starting at 'pos' with length 'length' with 'color'. |
| 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", "ColorCache", |
| "SegmentCache"}; |
| STATIC_ASSERT_ARRAY_SIZE(kSymbolTypeNames, kSymbolTypeNum); |
| static const char* const kGroup4ModeNames[] = { |
| "Group4 Vertical", "Group4 Horizontal", "Group4 Pass"}; |
| static const char* const kLZWTypeName = "LZW"; |
| static const WP2::Argb32b kSymbolColors[3][4] = { |
| {{255, 255, 0, 0}, |
| {255, 0, 255, 0}, |
| {255, 0, 0, 255}, |
| {255, 128, 64, 200}}, // SymbolType |
| {{255, 255, 255, 0}, {255, 0, 255, 255}, {255, 255, 0, 255}}, // Group4 |
| {{255, 255, 128, 64}} // LZW |
| }; |
| |
| WP2Status Decoder::RegisterSymbolForVDebug( |
| int symbol_type, uint32_t pos, uint32_t distance, uint32_t length, |
| float 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") && |
| !WP2::VDMatch(decoder_info->visual_debug, "lossless/transformed")) { |
| return WP2_STATUS_OK; |
| } |
| |
| if (WP2::VDMatch(decoder_info->visual_debug, "lossless/symbols")) { |
| // Only show once per tile. |
| if (pos == 0 && VDSelected(tile_->rect, config_)) { |
| std::string& str = decoder_info->selection_info; |
| str += WP2SPrint("Tile at (%d, %d)\n", tile_->rect.x, tile_->rect.y); |
| if (encoding_algorithm_ == EncodingAlgorithm::kGroup4) { |
| str += "Group 4\n"; |
| str += WP2SPrint("Move-to-front cache: %s\n", |
| mtf_.enabled() ? "enabled" : "disabled"); |
| } else if (encoding_algorithm_ == EncodingAlgorithm::kLZW) { |
| str += "LZW\n"; |
| str += WP2SPrint("Number of colors: %d\n", num_colors_); |
| } else if (encoding_algorithm_ == EncodingAlgorithm::kWebP) { |
| uint32_t cache_bits = hdr_.cache_config_.cache_bits; |
| if (cache_bits > 0) { |
| str += WP2SPrint("Color cache: %d bits\n", cache_bits); |
| } else { |
| str += "No color cache\n"; |
| } |
| } else { |
| return WP2_STATUS_INVALID_PARAMETER; |
| } |
| } |
| if (encoding_algorithm_ == EncodingAlgorithm::kLZW) { |
| // Display only the first pixel of a segment. |
| Fill(pos, 1, tile_->rect, |
| kSymbolColors[static_cast<int>(encoding_algorithm_)][symbol_type], |
| &decoder_info->debug_output); |
| if (length > 1) { |
| Fill(pos + 1, length - 1, tile_->rect, {255, 0, 0, 0}, |
| &decoder_info->debug_output); |
| } |
| } else { |
| Fill(pos, length, tile_->rect, |
| kSymbolColors[static_cast<int>(encoding_algorithm_)][symbol_type], |
| &decoder_info->debug_output); |
| } |
| } |
| |
| 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); |
| } |
| const uint32_t tile_width = tile_->rect.width; |
| std::string& str = decoder_info->selection_info; |
| str += WP2SPrint("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); |
| str += WP2SPrint("Type %s ", |
| (encoding_algorithm_ == EncodingAlgorithm::kGroup4) |
| ? kGroup4ModeNames[symbol_type] |
| : (encoding_algorithm_ == EncodingAlgorithm::kLZW) |
| ? kLZWTypeName |
| : kSymbolTypeNames[symbol_type]); |
| if (encoding_algorithm_ == EncodingAlgorithm::kGroup4) { |
| str += WP2SPrint("length %d ", length); |
| } else if (symbol_type == kSymbolTypeCopy) { |
| str += WP2SPrint("from (%d, %d) length %d ", |
| tile_->rect.x + (pos - distance) % tile_width, |
| tile_->rect.y + (pos - distance) / tile_width, length); |
| // Do not display the length for single pixels. |
| } else if (encoding_algorithm_ == EncodingAlgorithm::kLZW) { |
| str += WP2SPrint("segment length %d\n", length); |
| } |
| str += WP2SPrint("cost %f bpp %f\n", cost, cost / length); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| static const char* const kTransformNames[] = { |
| "Predictor", "Predictor with sub", "CrossColor", "CrossColorGlobal", |
| "YCoCgR", "SubstractGreen", "ColorIndexing", "NormalizeChannels"}; |
| STATIC_ASSERT_ARRAY_SIZE(kTransformNames, (uint32_t)TransformType::kNum); |
| static const char* const kPredictorNames[] = {"Black", |
| "180", |
| "90", |
| "45", |
| "135", |
| "Avg L,T,TR", |
| "157", |
| "Avg L,T", |
| "113", |
| "67", |
| "Avg all", |
| "Select L,TL,T", |
| "L+T-TL", |
| "(L+T)/2+((L+T)/2-TL)/2", |
| "Clamped L+T-TL"}; |
| STATIC_ASSERT_ARRAY_SIZE(kPredictorNames, (uint32_t)kNumPredictors); |
| static constexpr int kPredictorAngle[] = {0, 180, 90, 45, 135, 0, 157, 0, |
| 113, 67, 0, 0, 0, 0, 0}; |
| STATIC_ASSERT_ARRAY_SIZE(kPredictorAngle, (uint32_t)kNumPredictors); |
| |
| static void DisplayTileInfo(const WP2::DecoderConfig& config, uint32_t last_row, |
| const WP2::Rectangle& tile_rect, |
| WP2::DecoderInfo* const decoder_info) { |
| if (last_row == 0 && VDSelected(tile_rect, config)) { |
| std::string& str = decoder_info->selection_info; |
| str += WP2SPrint("Tile at (%d, %d)\n", tile_rect.x, tile_rect.y); |
| } |
| } |
| |
| // Display an image buffer by modifying it according to the parameters (scale, |
| // channel shuffling). |
| static void DisplayBuffer(uint32_t last_row, uint32_t num_rows, |
| const WP2::Rectangle& tile, |
| const Transform& transform, const int16_t* const rows, |
| uint32_t num_bits, float offset, float scale, |
| bool discard_alpha, bool duplicate_green, |
| WP2::DecoderInfo* const decoder_info) { |
| for (uint32_t y = 0; y < num_rows; ++y) { |
| const uint32_t abs_y = tile.y + last_row + y; |
| uint8_t* const row = decoder_info->debug_output.GetRow8(abs_y); |
| for (uint32_t x = 0; x < tile.width; ++x) { |
| const uint32_t abs_x = tile.x + x; |
| const uint32_t offset_coord = (y * tile.width + x) * 4; |
| if (decoder_info->selection.Contains(abs_x, abs_y)) { |
| std::string& str = decoder_info->selection_info; |
| // Display block info. |
| if (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub) { |
| const uint32_t x_sub = x >> transform.bits_; |
| const uint32_t y_sub = y >> transform.bits_; |
| const uint32_t transform_width = transform.width_; |
| const uint16_t predictor = |
| transform.data_[y_sub * 4 * transform_width + 4 * x_sub + 2]; |
| assert(predictor < kNumPredictors); |
| str += WP2SPrint("Block Predictor: %d %s\n", predictor, |
| kPredictorNames[predictor]); |
| } |
| |
| str += WP2SPrint("Pixel (%d, %d) argb (%d, %d, %d, %d)\n", |
| decoder_info->selection.x, decoder_info->selection.y, |
| rows[offset_coord + 0], rows[offset_coord + 1], |
| rows[offset_coord + 2], rows[offset_coord + 3]); |
| } |
| // Display the pixel by scaling it according to the type. |
| uint32_t c_min = 0, c_max = 3; |
| if (discard_alpha) { |
| row[abs_x * 4 + 0] = 255; |
| c_min = 1; |
| } |
| if (duplicate_green) c_min = c_max = 2; |
| for (uint32_t c = c_min; c <= c_max; ++c) { |
| row[abs_x * 4 + c] = WP2::Clamp( |
| (int)std::round(offset + |
| scale * WP2::RightShiftRound(rows[offset_coord + c], |
| num_bits - 8)), |
| 0, 255); |
| } |
| if (duplicate_green) { |
| row[abs_x * 4 + 1] = row[abs_x * 4 + 3] = row[abs_x * 4 + 2]; |
| } |
| } |
| } |
| } |
| |
| WP2Status Decoder::RegisterTransformedRowForVDebug( |
| int index, 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; |
| assert(index >= -1 && index < (int)kPossibleTransformCombinationSize); |
| const bool next_is_predictor = |
| (index + 1 < (int)kPossibleTransformCombinationSize && |
| (transforms_[index + 1].header_.type == TransformType::kPredictor || |
| transforms_[index + 1].header_.type == TransformType::kPredictorWSub)); |
| if (next_is_predictor && WP2::VDMatch(decoder_info->visual_debug, |
| "lossless/prediction/reference")) { |
| // This case is when displaying the reference image before a transform. |
| } else if (index >= 0) { |
| const std::string menu = WP2SPrint("lossless/transformed/%d", index); |
| if (!WP2::VDMatch(decoder_info->visual_debug, menu.c_str())) { |
| return WP2_STATUS_OK; |
| } |
| } else { |
| return WP2_STATUS_OK; |
| } |
| const Transform& transform = transforms_[index]; |
| |
| // Only show once per tile. |
| DisplayTileInfo(config_, last_row_, tile_->rect, decoder_info); |
| if (last_row_ == 0 && VDSelected(tile_->rect, config_)) { |
| std::string& str = decoder_info->selection_info; |
| for (uint32_t i = 0; i < kPossibleTransformCombinationSize; ++i) { |
| const Transform& transform_i = transforms_[i]; |
| if (transform_i.header_.type == TransformType::kNum) break; |
| // Highlight the used transform. |
| if (i == (uint32_t)index) str += "=> "; |
| str += WP2SPrint("Transform #%d: %s", i, |
| kTransformNames[(uint32_t)transform_i.header_.type]); |
| if (transform_i.header_.type == TransformType::kCrossColor || |
| transform_i.header_.type == TransformType::kPredictor || |
| transform_i.header_.type == TransformType::kPredictorWSub) { |
| str += WP2SPrint(" Bits: %d", transform_i.bits_); |
| } else if (transform_i.header_.type == TransformType::kColorIndexing) { |
| str += WP2SPrint(" Num colors: %d", num_colors_); |
| } |
| str += "\n"; |
| } |
| str += "\n"; |
| } |
| |
| // Display the result of the transform. |
| if (transform.header_.type == TransformType::kSubtractGreen) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/0, /*scale=*/1.f, /*discard_alpha=*/false, |
| /*duplicate_green=*/false, decoder_info); |
| } else if (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub || |
| transform.header_.type == TransformType::kCrossColor) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/128.f, /*scale=*/10.f, /*discard_alpha=*/true, |
| /*duplicate_green=*/false, decoder_info); |
| } else if (transform.header_.type == TransformType::kColorIndexing) { |
| if (num_colors_ <= 200) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/55.f, /*scale=*/200.f / (num_colors_ - 1), |
| /*discard_alpha=*/true, /*duplicate_green=*/true, |
| decoder_info); |
| } else if (num_colors_ <= 256) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/256 - num_colors_, /*scale=*/1.f, |
| /*discard_alpha=*/true, /*duplicate_green=*/true, |
| decoder_info); |
| } else { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/0.f, /*scale=*/1.f, |
| /*discard_alpha=*/true, /*duplicate_green=*/true, |
| decoder_info); |
| } |
| } else if (transform.header_.type == TransformType::kYCoCgR) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/128.f, /*scale=*/1.f, |
| /*discard_alpha=*/false, /*duplicate_green=*/false, |
| decoder_info); |
| } |
| |
| if (VDSelected(tile_->rect, config_)) { |
| decoder_info->debug_output.DrawRect(tile_->rect, |
| WP2::Argb32b{255, 255, 0, 0}); |
| } |
| |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status Decoder::RegisterPredictedRowForVDebug( |
| uint32_t index, uint32_t num_rows, uint32_t num_bits, |
| const int16_t* const rows, WP2::DecoderInfo* const decoder_info) { |
| if (decoder_info == nullptr || rows == nullptr) return WP2_STATUS_OK; |
| assert(index >= 0 && index < kPossibleTransformCombinationSize); |
| if (!WP2::VDMatch(decoder_info->visual_debug, "lossless/prediction/raw") && |
| !WP2::VDMatch(decoder_info->visual_debug, "lossless/cross-color/raw")) { |
| return WP2_STATUS_OK; |
| } |
| const Transform& transform = transforms_[index]; |
| |
| // Only show once per tile. |
| DisplayTileInfo(config_, last_row_, tile_->rect, decoder_info); |
| |
| // Display the prediction of the transform. |
| if (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub) { |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/0, /*scale=*/1.f, /*discard_alpha=*/false, |
| /*duplicate_green=*/false, decoder_info); |
| } else { |
| // Offset because it is easier to visualize but keep the original scale. |
| DisplayBuffer(last_row_, num_rows, tile_->rect, transform, rows, num_bits, |
| /*offset=*/128, /*scale=*/1.f, /*discard_alpha=*/true, |
| /*duplicate_green=*/false, decoder_info); |
| } |
| |
| if (VDSelected(tile_->rect, config_)) { |
| decoder_info->debug_output.DrawRect(tile_->rect, |
| WP2::Argb32b{255, 255, 0, 0}); |
| } |
| |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status Decoder::RegisterTransformForVDebug( |
| const Transform& transform, WP2::DecoderInfo* const decoder_info) { |
| if (decoder_info == nullptr) return WP2_STATUS_OK; |
| if (!(WP2::VDMatch(decoder_info->visual_debug, "lossless/prediction/modes") && |
| (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub)) && |
| !(WP2::VDMatch(decoder_info->visual_debug, |
| "lossless/cross-color/transform") && |
| transform.header_.type == TransformType::kCrossColor)) { |
| return WP2_STATUS_OK; |
| } |
| |
| DisplayTileInfo(config_, last_row_, tile_->rect, decoder_info); |
| |
| // Display the transform. |
| for (uint32_t y_sub = 0; y_sub < transform.height_; ++y_sub) { |
| const uint32_t tsize = (1 << transform.bits_); |
| const uint32_t block_y = tile_->rect.y + y_sub * tsize; |
| for (uint32_t x_sub = 0; x_sub < transform.width_; ++x_sub) { |
| const uint32_t block_x = tile_->rect.x + x_sub * tsize; |
| const int16_t* const data = |
| &transform.data_[y_sub * 4 * transform.width_ + 4 * x_sub]; |
| const WP2::Rectangle block_rect = |
| WP2::Rectangle(block_x, block_y, tsize, tsize).ClipWith(tile_->rect); |
| WP2::Argb32b color; |
| if (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub) { |
| // Display predictors. |
| const uint16_t predictor = data[2]; |
| const uint8_t c = WP2::ToPixel8(predictor, kNumPredictors); |
| color = {255, c, c, c}; |
| decoder_info->debug_output.Fill(block_rect, color); |
| |
| if (kPredictorAngle[predictor] != 0) { |
| DrawAnglePredictor(block_rect, kPredictorAngle[predictor], |
| &decoder_info->debug_output); |
| } |
| } else if (transform.header_.type == TransformType::kCrossColor) { |
| // Display cross-color. |
| color = {255, (uint8_t)WP2::Clamp(128 + data[1], 0, 255), |
| (uint8_t)WP2::Clamp(128 + data[2], 0, 255), |
| (uint8_t)WP2::Clamp(128 + data[3], 0, 255)}; |
| decoder_info->debug_output.Fill(block_rect, color); |
| } |
| |
| // Display block info. |
| if (decoder_info->selection.Intersects(block_rect)) { |
| std::string& str = decoder_info->selection_info; |
| if (transform.header_.type == TransformType::kPredictor || |
| transform.header_.type == TransformType::kPredictorWSub) { |
| str += WP2SPrint("Block Predictor: %d %s\n", data[2], |
| kPredictorNames[data[2]]); |
| } else if (transform.header_.type == TransformType::kCrossColor) { |
| str += WP2SPrint("Block cross-color: GtoR: %d GtoB: %d RtoB: %d\n", |
| data[1], data[2], data[3]); |
| } |
| } |
| } |
| } |
| |
| if (VDSelected(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& str = decoder_info->selection_info; |
| const uint32_t num_clusters = hdr_.sr_.symbols_info().NumClusters(0); |
| const WP2::Rectangle& tile_rect = tile_->rect; |
| if (VDSelected(tile_rect, config_)) { |
| str += WP2SPrint("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(rect, config_)) { |
| str += WP2SPrint("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(tile_rect, config_)) { |
| debug_output->DrawRect(tile_rect, WP2::Argb32b{255, 0, 255, 0}); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| } // namespace WP2L |
| |
| #else |
| |
| //------------------------------------------------------------------------------ |
| // Code defined to compile: |
| |
| namespace WP2 { |
| |
| CodedBlock::SplitTf CodedBlock::GetForcedSplitTf(const EncoderConfig&, uint32_t, |
| uint32_t) const { |
| return SplitTf::kUnknown; |
| } |
| |
| TransformPair CodedBlock::GetForcedTransform(const EncoderConfig&, uint32_t, |
| uint32_t) const { |
| return kUnknownTf; |
| } |
| |
| const Predictor* CodedBlock::GetForcedPredictor(const EncoderConfig&, uint32_t, |
| uint32_t, const Predictors&, |
| Channel) const { |
| return nullptr; |
| } |
| |
| } // namespace WP2 |
| |
| #endif // WP2_REDUCE_BINARY_SIZE |