| // 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. |
| // ----------------------------------------------------------------------------- |
| // |
| // Segment |
| // |
| // Author: Skal (pascal.massimino@gmail.com) |
| |
| #include "src/common/lossy/segment.h" |
| |
| #include "src/common/constants.h" |
| #include "src/common/lossy/block.h" |
| #include "src/dec/symbols_dec.h" |
| #include "src/enc/symbols_enc.h" |
| #include "src/utils/ans_utils.h" |
| #include "src/utils/utils.h" |
| |
| namespace WP2 { |
| |
| //------------------------------------------------------------------------------ |
| |
| uint32_t GetMaxNumSegments(bool explicit_segment_ids, uint32_t quality_hint, |
| PartitionSet set) { |
| assert(quality_hint <= kMaxLossyQualityHint); |
| const uint32_t max_num_segments = |
| (explicit_segment_ids ? kMaxNumSegments : kMaxNumSegments / 2); |
| // Use fewer segments as quality decreases. |
| const uint32_t res = 1u + DivRound((max_num_segments - 1) * quality_hint, |
| kMaxLossyQualityHint); |
| // TODO(vrabaud) Try for other partition sets, only tested on the default one. |
| if (set == SOME_RECTS) { |
| return std::max(2u, res); |
| } else { |
| return res; |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Grain read/write |
| |
| bool GrainParams::operator==(const GrainParams& other) const { |
| return (y_ == other.y_) && |
| (uv_ == other.uv_) && |
| (cut_y_ == other.cut_y_) && |
| (cut_uv_ == other.cut_uv_); |
| } |
| |
| void GrainParams::Write(ANSEnc* const enc) const { |
| enc->PutUValue(y_, 4, "grain"); |
| enc->PutUValue(uv_, 4, "grain"); |
| enc->PutUValue(cut_y_, 3, "grain"); |
| enc->PutUValue(cut_uv_, 3, "grain"); |
| } |
| |
| void GrainParams::Read(ANSDec* const dec) { |
| y_ = dec->ReadUValue(4, "grain"); |
| uv_ = dec->ReadUValue(4, "grain"); |
| cut_y_ = dec->ReadUValue(3, "grain"); |
| cut_uv_ = dec->ReadUValue(3, "grain"); |
| } |
| |
| void GrainParams::Reset() { |
| y_ = uv_ = 0; |
| cut_y_ = cut_uv_ = 0; |
| } |
| |
| void GrainParams::Print() const { |
| printf("[y=%d uv=%d, cut y=%d uv=%d]\n", y_, uv_, cut_y_, cut_uv_); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Segment |
| |
| bool Segment::IsMergeableWith(const Segment& other) const { |
| const bool same = |
| (q_scale_ == other.q_scale_) && |
| (use_quality_factor_ == other.use_quality_factor_) && |
| (use_grain_ == other.use_grain_) && |
| (!use_grain_ || grain_ == other.grain_); |
| if (!same) return false; |
| for (uint32_t c = 0; c < 4; ++c) { |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| if (quant_steps_[c][i] != other.quant_steps_[c][i]) return false; |
| } |
| } |
| return true; |
| } |
| |
| WP2Status Segment::WriteHeader(ANSEnc* const enc, bool write_grain) const { |
| enc->PutBool(use_quality_factor_, "quant-factor"); |
| |
| if (use_quality_factor_) { |
| enc->PutRValue(quality_factor_, kQFactorMax + 1, "quant-factor"); |
| } else { |
| // TODO(skal): use better delta-coding. |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| for (auto c : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| enc->PutRange(quant_steps_[c][i], 0, kQFactorMax, "quant-factor"); |
| } |
| } |
| } |
| |
| if (write_grain && enc->PutUValue(use_grain_, 1, "grain")) grain_.Write(enc); |
| return enc->GetStatus(); |
| } |
| |
| void Segment::StoreEncodingMethods(const CodedBlock& cb, |
| SymbolManager* const sm, |
| ANSEncBase* const enc) const { |
| for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| if (channel == kAChannel && !cb.HasLossyAlpha()) continue; |
| for (uint32_t tf_i = 0; tf_i < cb.GetNumTransforms(channel); ++tf_i) { |
| if (cb.num_coeffs_[channel][tf_i] == 0) { |
| // TODO(yguyon): At least one method != kAllZero in block if split |
| assert(cb.method_[channel][tf_i] == EncodingMethod::kAllZero); |
| } else { |
| const uint32_t cluster = |
| (channel == kAChannel) ? 0 : (3 * cb.id_ + (uint32_t)channel); |
| const Symbol symbol = (channel == kAChannel) ? kSymbolEncodingMethodA |
| : kSymbolEncodingMethod; |
| if (cb.IsFirstCoeffDC(channel)) { |
| sm->Process(cluster, symbol, (uint32_t)cb.method_[channel][tf_i], |
| kChannelStr[channel], enc); |
| } else { |
| // Use 'max_value' to remove the EncodingMethod::kDCOnly possibility. |
| sm->Process(cluster, symbol, (uint32_t)cb.method_[channel][tf_i], |
| /*max_value=*/(uint32_t)EncodingMethod::kMethod1, |
| kChannelStr[channel], enc); |
| } |
| } |
| } |
| } |
| } |
| |
| WP2Status Segment::ReadHeader(PartitionSet partition_set, uint32_t quality_hint, |
| uint32_t u_quant_multiplier, |
| uint32_t v_quant_multiplier, bool read_grain, |
| ANSDec* const dec) { |
| partition_set_ = partition_set; |
| const bool use_quality_factor = dec->ReadBool("quant-factor"); |
| if (use_quality_factor) { |
| const uint32_t quality_factor = |
| dec->ReadRValue(kQFactorMax + 1, "quant-factor"); |
| SetQuality(quality_hint, quality_factor, |
| u_quant_multiplier, v_quant_multiplier); |
| } else { |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| for (auto c : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| quant_steps_[c][i] = dec->ReadRange(0, kQFactorMax, "quant-factor"); |
| SetQuantSteps(quant_steps_[c], c); |
| } |
| } |
| } |
| FinalizeQuant(); |
| |
| if (read_grain) { |
| use_grain_ = dec->ReadUValue(1, "grain"); |
| if (use_grain_) grain_.Read(dec); |
| } else { |
| use_grain_ = false; |
| } |
| return dec->GetStatus(); |
| } |
| |
| WP2Status Segment::ReadEncodingMethods(SymbolReader* const sr, |
| CodedBlock* const cb) const { |
| for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) { |
| if (channel == kAChannel && !cb->HasLossyAlpha()) continue; |
| for (uint32_t tf_i = 0; tf_i < cb->GetNumTransforms(channel); ++tf_i) { |
| if (cb->num_coeffs_[channel][tf_i] == 0) { |
| cb->method_[channel][tf_i] = EncodingMethod::kAllZero; |
| } else { |
| const uint32_t cluster = |
| (channel == kAChannel) ? 0 : (3 * cb->id_ + (uint32_t)channel); |
| const Symbol symbol = (channel == kAChannel) ? kSymbolEncodingMethodA |
| : kSymbolEncodingMethod; |
| if (cb->IsFirstCoeffDC(channel)) { |
| cb->method_[channel][tf_i] = |
| (EncodingMethod)sr->Read(cluster, symbol, kChannelStr[channel]); |
| } else { |
| // Use 'max_value' to remove the EncodingMethod::kDCOnly possibility. |
| int32_t method; |
| WP2_CHECK_STATUS( |
| sr->TryRead(cluster, symbol, |
| /*max_value=*/(uint32_t)EncodingMethod::kMethod1, |
| kChannelStr[channel], &method)); |
| cb->method_[channel][tf_i] = (EncodingMethod)method; |
| } |
| } |
| } |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| uint16_t Segment::GetMaxAbsDC(Channel channel) const { |
| const QuantMtx& mtx = GetQuant(channel); |
| uint16_t max_abs_dc = 0; |
| // TODO(vrabaud) only use the TrfSizes defined by -ps. |
| for (const TrfSize tdim : kAllTrfSizes) { |
| max_abs_dc = std::max(max_abs_dc, mtx.GetMaxAbsResDC(tdim)); |
| } |
| return max_abs_dc; |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| static uint32_t InterpolateQuant(int32_t qf, float mult) { |
| const int32_t q = std::lround(qf * mult); |
| return (uint32_t)Clamp(q, 0, (int32_t)kQFactorMax); |
| } |
| |
| void Segment::SetQuality(uint32_t quality_hint, uint32_t quality_factor, |
| uint32_t u_quant_multiplier, |
| uint32_t v_quant_multiplier) { |
| // The following ad-hoc formulae maps the [0-kQFactorMax] range of quality |
| // factor to approximately 0.05 bpp - 3 bpp usable range. Note that |
| // quality_factor 0 is the highest quality (= smaller quant-step). |
| quality_factor = std::min(quality_factor, (uint32_t)kQFactorMax); |
| const int32_t q_y = quality_factor; |
| const int32_t q_u = quality_factor + u_quant_multiplier - kNeutralMultiplier; |
| const int32_t q_v = quality_factor + v_quant_multiplier - kNeutralMultiplier; |
| const int32_t q_a = quality_factor; |
| // Quantize DC less than AC for UV, especially at lower qualities. |
| constexpr float kMinScale = 0.25f; |
| constexpr float kMaxScale = 0.60f; |
| const float qf = |
| 1.f * std::min(quality_hint, kMaxLossyQualityHint) / kMaxLossyQualityHint; |
| const float uv_dc_scale = kMinScale + (kMaxScale - kMinScale) * qf; |
| const float kUVScales[kNumQuantZones] = {uv_dc_scale, 1.0, 1.0, 1.0}; |
| |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| quant_steps_[kYChannel][i] = InterpolateQuant(q_y, 1.0f); |
| quant_steps_[kUChannel][i] = InterpolateQuant(q_u, kUVScales[i]); |
| quant_steps_[kVChannel][i] = InterpolateQuant(q_v, kUVScales[i]); |
| quant_steps_[kAChannel][i] = InterpolateQuant(q_a, 0.5f); |
| } |
| quality_factor_ = quality_factor; |
| use_quality_factor_ = true; |
| } |
| |
| void Segment::SetQuantSteps(uint16_t quants[kNumQuantZones], Channel channel) { |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| if (quants[i]) { |
| quant_steps_[channel][i] = std::min(quants[i], kQFactorMax); |
| } |
| } |
| use_quality_factor_ = false; |
| } |
| |
| void Segment::GetQuantSteps(uint16_t quants[kNumQuantZones], |
| Channel channel) const { |
| for (uint32_t i = 0; i < kNumQuantZones; ++i) { |
| quants[i] = quant_steps_[channel][i]; |
| } |
| } |
| |
| WP2Status Segment::AllocateForEncoder() { |
| WP2_CHECK_STATUS(quant_y_.Allocate()); |
| WP2_CHECK_STATUS(quant_u_.Allocate()); |
| WP2_CHECK_STATUS(quant_v_.Allocate()); |
| WP2_CHECK_STATUS(quant_a_.Allocate()); |
| return WP2_STATUS_OK; |
| } |
| |
| void Segment::SetYUVBounds(int16_t yuv_min, int16_t yuv_max) { |
| max_residual_ = 1 + yuv_max - yuv_min; |
| // Adjust quantization based on the actual yuv/alpha range, compared to |
| // the max yuv range which is what the quantizer is tuned for by default. |
| q_scale_ = (float)(1 << (kMaxYuvBits + 1)) / max_residual_; |
| } |
| |
| void Segment::FinalizeQuant(float lambda) { |
| assert(q_scale_ > 0.f); // must have been set prior to calling this |
| quant_y_.Init(max_residual_, q_scale_, partition_set_, |
| quant_steps_[kYChannel]); |
| quant_u_.Init(max_residual_, q_scale_, partition_set_, |
| quant_steps_[kUChannel]); |
| quant_v_.Init(max_residual_, q_scale_, partition_set_, |
| quant_steps_[kVChannel]); |
| // Max residual value is different for alpha since it doesn't go through the |
| // CSP transform. Alpha is between 0 and kAlphaMax. After subtracting |
| // predicted values from actual values, the residuals are between -kAlphaMax |
| // and kAlphaMax. So the max absolute value is still kAlphaMax. |
| quant_a_.Init(/*max_residual=*/kAlphaMax, /*q_scale=*/1.0, partition_set_, |
| quant_steps_[kAChannel]); |
| if (lambda > 0) { |
| lambda_f_ = lambda; |
| quant_y_.SetLambda(lambda / q_scale_); |
| quant_u_.SetLambda(lambda / q_scale_); |
| quant_v_.SetLambda(lambda / q_scale_); |
| const float q_scale_a = (float)(1 << (kMaxYuvBits + 1)) / (1 + kAlphaMax); |
| quant_a_.SetLambda(lambda / q_scale_a); |
| } |
| } |
| |
| void Segment::AdjustQuantSteps() { |
| if (quant_y_.qstats != nullptr) { |
| uint16_t quant_steps[kNumQuantZones]; |
| GetQuantSteps(quant_steps, kYChannel); |
| quant_y_.qstats->AdjustQuantSteps(quant_steps); |
| SetQuantSteps(quant_steps, kYChannel); |
| } |
| // TODO(skal): quant_uv_ |
| |
| FinalizeQuant(); |
| } |
| |
| WP2Status GlobalParams::AssignQuantizations(const EncoderConfig& config) { |
| const uint32_t num_segments = segments_.size(); |
| assert(num_segments <= (uint32_t)config.segments); |
| assert(num_segments <= kMaxNumSegments); |
| uint32_t qualities[kMaxNumSegments]; |
| const float quant_factor0 = |
| kQFactorMax * (kMaxLossyQuality - config.quality) / kMaxLossyQuality; |
| float min_risk = 1.0f; |
| float max_risk = 0.0f; |
| for (const auto& s : segments_) min_risk = std::min(min_risk, s.risk_); |
| for (const auto& s : segments_) max_risk = std::max(max_risk, s.risk_); |
| min_risk -= 0.01f; // to avoid divide-by-zero |
| const float amp = 1.4f * Clamp(config.sns / 100.f, 0.f, 1.f); |
| const float adjust_amp = |
| std::min(1.f, num_segments / 4.f) / (max_risk - min_risk); |
| // Exponent for 'adjust' (1 = linear). |
| const float exponent = 2.0f; |
| for (uint32_t idx = 0; idx < num_segments; ++idx) { |
| const Segment& s = segments_[idx]; |
| float quant = config.segment_factors[idx]; |
| if (quant == 0.f) { |
| const float adjust = adjust_amp * (s.risk_ - min_risk); |
| const float displaced = amp * pow(adjust, exponent); |
| quant = quant_factor0 * (1.0f - displaced); |
| } else { |
| quant = kQFactorMax * (kMaxLossyQuality - quant) / kMaxLossyQuality; |
| } |
| qualities[idx] = Clamp<int32_t>(std::lround(quant), 0, kQFactorMax); |
| } |
| for (uint32_t idx = 0; idx < num_segments; ++idx) { |
| WP2_CHECK_STATUS(segments_[idx].AllocateForEncoder()); |
| segments_[idx].SetQuantizationFactor( |
| transf_, GetQualityHint(config.quality), u_quant_multiplier_, |
| v_quant_multiplier_, qualities[idx], 1.f, config.partition_set); |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| WP2Status GlobalParams::AssignAlphaQuantizations(const YUVPlane& yuv, |
| const EncoderConfig& config) { |
| WP2AlphaInit(); |
| has_alpha_ = false; |
| if (!yuv.A.IsEmpty()) { |
| for (uint32_t y = 0; y < yuv.A.h_; ++y) { |
| const int16_t* const row = yuv.A.Row(y); |
| has_alpha_ = has_alpha_ || WP2HasOtherValue16b(row, yuv.A.w_, kAlphaMax); |
| if (has_alpha_) break; |
| } |
| } |
| maybe_use_lossy_alpha_ = |
| has_alpha_ && (config.alpha_quality <= kMaxLossyQuality); |
| if (maybe_use_lossy_alpha_) { |
| // TODO(maryla): we could detect automatically cases where the alpha |
| // filter should not be applied. |
| enable_alpha_filter_ = config.enable_alpha_filter; |
| WP2_CHECK_ALLOC_OK(a_segments_.resize(1)); |
| const uint32_t max_quality = kMaxLossyQuality; |
| const uint32_t quant_factor0 = |
| (uint32_t)std::lround( |
| kQFactorMax * (max_quality - config.alpha_quality) / max_quality); |
| for (Segment& s : a_segments_) { |
| WP2_CHECK_STATUS(s.AllocateForEncoder()); |
| s.SetQuantizationFactor( |
| transf_, GetQualityHint(config.alpha_quality), |
| /*u_quant_multiplier=*/0, /*v_quant_multiplier=*/0, |
| quant_factor0, /*lambda_mult=*/1.f, config.partition_set); |
| } |
| } |
| return WP2_STATUS_OK; |
| } |
| |
| void Segment::SetQuantizationFactor(const CSPTransform& transform, |
| uint32_t quality_hint, |
| uint32_t u_quant_multiplier, |
| uint32_t v_quant_multiplier, |
| uint32_t qfactor, float lambda_mult, |
| PartitionSet partition_set) { |
| partition_set_ = partition_set; |
| SetYUVBounds(transform.GetYUVMin(), transform.GetYUVMax()); |
| SetQuality(quality_hint, qfactor, u_quant_multiplier, v_quant_multiplier); |
| FinalizeQuant(lambda_mult); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| } // namespace WP2 |