blob: dc20f7c97c1fa8cea8681db4dbb66dae45727f2e [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// WP2 lossy encoding of the alpha plane.
//
// Author: Maryla (maryla@google.com)
#include "src/enc/lossless/losslessi_enc.h"
#include "src/enc/wp2_enc_i.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
WP2Status AlphaWriter::Init(const EncoderConfig& config,
const GlobalParams& gparams,
const BlockContext& context, const YUVPlane& yuv,
const Rectangle& tile_rect,
const ProgressRange& progress) {
assert(gparams.has_alpha_);
tile_rect_ = tile_rect;
context_ = &context;
config_ = config;
gparams_ = &gparams;
lossless_enc_.AddDebugPrefix("Alpha");
alpha_mode_ =
(gparams.maybe_use_lossy_alpha_) ? kAlphaModeLossy : kAlphaModeLossless;
if (alpha_mode_ == kAlphaModeLossy) {
WP2_CHECK_STATUS(mode_predictor_.Init(tile_rect_.width, tile_rect_.height));
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
} else {
const ProgressRange near_lossless_progress(progress, 0.5);
const ProgressRange encode_image_progress(progress, 0.5);
// Convert alpha plane to a grayscale image (not padded).
WP2_CHECK_STATUS(alpha_.Resize(tile_rect_.width, tile_rect_.height));
for (uint32_t y = 0; y < tile_rect_.height; ++y) {
const int16_t* const src_row = yuv.A.Row(y);
uint8_t* const dst = alpha_.GetRow8(y);
for (uint32_t x = 0; x < tile_rect_.width; ++x) {
const uint8_t alpha = Clamp<int16_t>(src_row[x], 0, kAlphaMax);
dst[4 * x + 0] = kAlphaMax;
dst[4 * x + 1] = dst[4 * x + 2] = dst[4 * x + 3] = alpha;
}
}
ArgbBuffer* buffer = &alpha_;
ArgbBuffer pre_processed_buffer;
if (config.alpha_quality > kMaxLossyQuality &&
config.alpha_quality < kMaxQuality) {
WP2_CHECK_STATUS(PreprocessNearLossless(alpha_, config, /*is_alpha=*/true,
&pre_processed_buffer));
buffer = &pre_processed_buffer;
}
WP2_CHECK_STATUS(near_lossless_progress.AdvanceBy(1.));
// Encode the whole alpha losslessly.
WP2_CHECK_ALLOC_OK(lossless_encode_info_.bits_per_pixel.resize(
tile_rect_.width * tile_rect_.height));
WP2_CHECK_ALLOC_OK(
lossless_encode_info_.line_tokens.resize(tile_rect_.height));
WP2_CHECK_STATUS(WP2L::EncodeImage(config_, *buffer, /*has_alpha=*/true,
encode_image_progress, &lossless_enc_,
&lossless_encode_info_));
assert(lossless_encode_info_.line_tokens[alpha_.height() - 1] ==
lossless_enc_.NumTokens());
}
return WP2_STATUS_OK;
}
WP2Status AlphaWriter::CopyFrom(const AlphaWriter& other,
const BlockContext& context) {
gparams_ = other.gparams_;
tile_rect_ = other.tile_rect_;
config_ = other.config_;
gparams_ = other.gparams_;
alpha_mode_ = other.alpha_mode_;
if (alpha_mode_ == kAlphaModeLossless) {
WP2_CHECK_OK(!other.alpha_.IsView(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_STATUS(alpha_.CopyFrom(other.alpha_));
WP2_CHECK_STATUS(lossless_enc_.Clone(other.lossless_enc_));
WP2_CHECK_ALLOC_OK(
lossless_encode_info_.CopyFrom(other.lossless_encode_info_));
next_line_ = other.next_line_;
} else {
WP2_CHECK_STATUS(mode_predictor_.CopyFrom(other.mode_predictor_));
}
WP2_CHECK_OK(&context != other.context_, WP2_STATUS_INVALID_PARAMETER);
context_ = &context;
return WP2_STATUS_OK;
}
WP2Status AlphaWriter::WriteHeader(uint32_t num_coeffs_max,
ANSEncBase* const enc) {
ANSDebugPrefix prefix(enc, "Alpha");
if (gparams_->maybe_use_lossy_alpha_) {
enc->PutRValue(alpha_mode_, kAlphaModeNum, "alpha_mode");
} else {
assert(alpha_mode_ == kAlphaModeLossless);
}
return WP2_STATUS_OK;
}
WP2Status AlphaWriter::DecideAlpha(CodedBlock* const cb,
const ResidualWriter& residual_writer,
Counters* const counters) {
if (alpha_mode_ == kAlphaModeLossless) {
cb->alpha_mode_ = kBlockAlphaLossless;
} else {
WP2_CHECK_STATUS(ProcessLossy(
cb, residual_writer, lossless_encode_info_.bits_per_pixel, counters));
}
return WP2_STATUS_OK;
}
WP2Status AlphaWriter::Record(const CodedBlock& cb) { return WP2_STATUS_OK; }
void AlphaWriter::WriteBlockBeforeCoeffs(const CodedBlock& cb,
SymbolManager* const sm,
ANSEncBase* const enc) {
if (alpha_mode_ == kAlphaModeLossy) {
mode_predictor_.Write(cb, enc, sm);
}
}
WP2Status AlphaWriter::ResetRecord() { return WP2_STATUS_OK; }
WP2Status AlphaWriter::Write(const CodedBlock& cb, ANSEnc* const enc) {
ANSDebugPrefix prefix(enc, "Alpha");
if (alpha_mode_ == kAlphaModeLossless) {
WP2_CHECK_STATUS(WriteLossless(cb, enc));
}
return enc->GetStatus();
}
WP2Status AlphaWriter::WriteLossless(const CodedBlock& cb, ANSEnc* const enc) {
const uint32_t max_line = std::min(cb.y_pix() + cb.h_pix(), alpha_.height());
if (next_line_ < max_line) {
// Write as many new lines as we need for this block.
const uint32_t lines_to_write = max_line - next_line_;
const uint32_t start_token =
(next_line_ > 0) ? lossless_encode_info_.line_tokens[next_line_ - 1]
: 0;
const uint32_t n_tokens =
lossless_encode_info_.line_tokens[next_line_ + lines_to_write - 1] -
start_token;
if (n_tokens > 0) {
WP2_CHECK_STATUS(enc->AppendTokens(lossless_enc_, start_token, n_tokens));
}
next_line_ = max_line;
}
return WP2_STATUS_OK;
}
BlockAlphaMode AlphaWriter::GetBlockAlphaMode(const CodedBlock& cb,
const Plane16& alpha) const {
bool all_opaque = true;
bool all_transp = true;
for (uint32_t y = 0, y_offset = cb.y_pix();
y < cb.h_pix() && y_offset < tile_rect_.height; ++y, ++y_offset) {
const int16_t* const row = alpha.Row(y);
for (uint32_t x = 0, x_offset = cb.x_pix();
x < cb.w_pix() && x_offset < tile_rect_.width; ++x, ++x_offset) {
const int16_t a = row[x];
all_opaque = all_opaque && (a == kAlphaMax);
all_transp = all_transp && (a == 0);
if (!all_opaque && !all_transp) return kBlockAlphaLossy;
}
}
assert(all_opaque || all_transp);
return all_opaque ? kBlockAlphaFullOpaque : kBlockAlphaFullTransp;
}
WP2Status AlphaWriter::ProcessLossy(CodedBlock* const cb,
const ResidualWriter& residual_writer,
const Vector_f& bits_per_pixel,
Counters* const counters) {
cb->alpha_mode_ = GetBlockAlphaMode(*cb, cb->in_.A);
if (cb->alpha_mode_ != kBlockAlphaLossy) {
assert(cb->alpha_mode_ == kBlockAlphaFullOpaque ||
cb->alpha_mode_ == kBlockAlphaFullTransp);
cb->out_.A.Fill(cb->alpha_mode_ == kBlockAlphaFullTransp ? 0 : kAlphaMax);
return WP2_STATUS_OK;
}
constexpr uint32_t kNumChannels = 4; // Otherwise we would not be here.
const APredictors& preds = gparams_->a_preds_;
const Segment& segment = gparams_->segments_[0];
CodedBlockBase::CodingParams& params = *cb->GetCodingParams(kAChannel);
params.tf = kDctDct;
for (const Predictor* const pred : preds) {
if (!pred->ComputeParams(cb, kAChannel)) {
// TODO(skal): deal with useless preds
}
}
if ((params.pred = cb->GetForcedPredictor(config_, tile_rect_, preds,
kAChannel)) != nullptr) {
// Predictor is set by EncoderConfig::info (debug).
cb->pred_scores_[kAChannel] = 1.f;
} else if (config_.effort == 1) {
// Select the first predictor to save time.
params.pred = preds[0];
cb->pred_scores_[kAChannel] = 1.f;
} else if (config_.effort == 2) {
// Quickly select the best predictor.
cb->FindBestPred(preds, kAChannel);
} else {
Vector<TransformPair> transforms;
WP2_CHECK_ALLOC_OK(transforms.push_back(kDctDct));
// Spend more time to select the best predictor.
WP2_CHECK_STATUS(cb->FindBestPredTf(
config_, tile_rect_, preds, segment, *context_, kAChannel, kNumChannels,
/*reduced=*/false, transforms, counters));
}
// Quantizes and stores reconstructed pixels in cb->out_.
cb->Quantize(config_, *context_, segment, kAChannel, /*tf_i=*/0,
/*reduced_transform=*/false, counters);
// Update alpha_mode in case the reconstructed pixels are fully opaque or
// fully transparent, even if the input pixels are not.
cb->alpha_mode_ = GetBlockAlphaMode(*cb, cb->out_.A);
if (cb->alpha_mode_ != kBlockAlphaLossy) return WP2_STATUS_OK;
for (uint32_t tf_i = 0; tf_i < cb->GetNumTransforms(kAChannel); ++tf_i) {
WP2_CHECK_STATUS(residual_writer.FindBestEncodingMethod(
cb->tdim(kAChannel), cb->coeffs_[kAChannel][tf_i],
cb->num_coeffs_[kAChannel][tf_i], cb->IsFirstCoeffDC(kAChannel),
kAChannel, kNumChannels, counters->residuals(),
&cb->method_[kAChannel][tf_i]));
}
return WP2_STATUS_OK;
}
} // namespace WP2