blob: 50b4d41eb042ec72a48e8a421c29a6bf089a1473 [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.
// -----------------------------------------------------------------------------
//
// main entry for the lossless encoder.
//
// Author: Vincent Rabaud (vrabaud@google.com)
#include "src/enc/lossless/losslessi_enc.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include "src/common/color_precision.h"
#include "src/common/constants.h"
#include "src/common/lossless/color_cache.h"
#include "src/common/lossless/transforms.h"
#include "src/common/progress_watcher.h"
#include "src/common/symbols.h"
#include "src/dsp/lossless/dspl.h"
#include "src/dsp/lossless/encl_dsp.h"
#include "src/dsp/math.h"
#include "src/enc/lossless/backward_references_enc.h"
#include "src/enc/lossless/histogram_enc.h"
#include "src/enc/symbols_enc.h"
#include "src/utils/ans.h"
#include "src/utils/ans_enc.h"
#include "src/utils/ans_utils.h"
#include "src/utils/utils.h"
#include "src/utils/vector.h"
#include "src/wp2/base.h"
#include "src/wp2/encode.h"
#include "src/wp2/format_constants.h"
namespace WP2L {
using WP2::ProgressRange;
// -----------------------------------------------------------------------------
namespace {
void swap(EncodeInfo& a, EncodeInfo& b) {
swap(a.line_tokens, b.line_tokens);
swap(a.bits_per_pixel, b.bits_per_pixel);
}
// Writes a 'value' with prefix coding where the prefix is stored as symbol
// 'sym' in cluster 'cluster'.
void WriteLZ77PrefixCode(uint32_t sym, uint32_t cluster, int32_t value,
uint32_t min, uint32_t max, WP2_OPT_LABEL,
WP2::SymbolManager* const sw,
WP2::ANSEncBase* const enc, float* const cost) {
// Write information about the prefix code representation.
const WP2::PrefixCode prefix_code(value - min, kLZ77PrefixCodeSize);
const WP2::PrefixCode prefix_code_max(max - min, kLZ77PrefixCodeSize);
sw->ProcessWithCost(sym, cluster, prefix_code.prefix, prefix_code_max.prefix,
label, enc, cost);
if (prefix_code.extra_bits_num == 0) return;
// Write the rest of the representation.
const std::string label_bits = WP2SPrint("%s_bits", label);
if (prefix_code.prefix == prefix_code_max.prefix) {
const uint32_t new_range =
max - min + 1 -
WP2::PrefixCode::Merge(prefix_code.prefix, kLZ77PrefixCodeSize, 0);
if (new_range <= WP2::kANSMaxRange) {
enc->PutRValue(prefix_code.extra_bits_value, new_range,
label_bits.c_str());
if (cost != nullptr) *cost += WP2Log2(new_range);
} else {
// Go recursively.
WriteLZ77PrefixCode(sym, cluster, prefix_code.extra_bits_value,
/*min=*/0, /*max=*/new_range - 1, label, sw, enc,
cost);
}
} else {
if (prefix_code.extra_bits_num <= WP2::kANSMaxUniformBits) {
enc->PutUValue(prefix_code.extra_bits_value, prefix_code.extra_bits_num,
label_bits.c_str());
} else {
enc->PutUValue(
prefix_code.extra_bits_value & ((1u << WP2::kANSMaxUniformBits) - 1),
WP2::kANSMaxUniformBits, label_bits.c_str());
enc->PutUValue(prefix_code.extra_bits_value >> WP2::kANSMaxUniformBits,
prefix_code.extra_bits_num - WP2::kANSMaxUniformBits,
label_bits.c_str());
}
if (cost != nullptr) *cost += prefix_code.extra_bits_num;
}
}
} // namespace
} // namespace WP2L
namespace WP2 {
// Counts the number of transforms for a combination of transforms from the
// official table.
uint32_t EncoderConfig::LosslessCrunchConfig::GetNumTransforms() const {
uint32_t num_transforms = 0;
while (num_transforms < WP2L::kPossibleTransformCombinationSize &&
transforms[num_transforms].type != WP2L::TransformType::kNum) {
++num_transforms;
}
return num_transforms;
}
bool EncoderConfig::LosslessCrunchConfig::ShouldBeCachedFor(
const EncoderConfig::LosslessCrunchConfig& config) const {
// First, make sure the encoding algorithm and pre-multiplication are the same
// and that the set of transforms is a subset.
if (algorithm != config.algorithm ||
use_premultiplied != config.use_premultiplied ||
!std::equal(transforms.data(), transforms.data() + GetNumTransforms(),
config.transforms.data())) {
return false;
}
// If we have a palette, make sure the type is the same.
for (uint32_t i = 0; i < GetNumTransforms(); ++i) {
if (transforms[i].type == WP2L::TransformType::kColorIndexing &&
palette_sorting_type != config.palette_sorting_type) {
return false;
}
}
return true;
}
} // namespace WP2
// -----------------------------------------------------------------------------
namespace WP2L {
// Writes an image in a raw manner.
static WP2Status WriteRawImage(const int16_t* const argb, uint32_t width,
uint32_t height,
const LosslessSymbolsInfo& symbols_info,
const ProgressRange& progress,
WP2::ANSEncBase* const enc,
EncodeInfo* const encode_info = nullptr) {
ProgressRange progress_local(progress, 1.);
const bool get_cost =
encode_info != nullptr && !encode_info->bits_per_pixel.empty();
// Cache the extrema.
int16_t mins[4], maxs[4];
for (Symbol sym : {kSymbolA, kSymbolR, kSymbolG, kSymbolB}) {
if (symbols_info.IsUnused(sym)) continue;
const uint32_t c = (int)sym - (int)kSymbolA;
mins[c] = symbols_info.Min(sym, /*cluster=*/0);
maxs[c] = symbols_info.Max(sym, /*cluster=*/0);
}
// Write the raw pixel values.
for (uint32_t y = 0; y < height; ++y) {
const int16_t* const row = &argb[4 * width * y];
for (uint32_t x = 0; x < width; ++x) {
// Deal with alpha.
float cost;
int16_t alpha;
if (symbols_info.IsUnused(kSymbolA)) {
cost = 0.f;
alpha = WP2::kAlphaMax;
} else {
alpha = row[4 * x + 0];
enc->PutRange(alpha, mins[0], maxs[0], kSymbolNames[(int)kSymbolA]);
if (get_cost) cost = WP2Log2(maxs[0] - mins[0] + 1);
}
// Pre-multiply.
for (uint32_t c = 1; c < 4; ++c) {
if (symbols_info.IsUnused((int)kSymbolA + c)) continue;
const int16_t min = WP2::DivBy255(mins[c] * alpha);
const int16_t max = WP2::DivBy255(maxs[c] * alpha);
enc->PutRange(WP2::DivBy255(row[4 * x + c] * alpha), min, max,
kSymbolNames[(int)kSymbolA + c]);
if (get_cost) cost += WP2Log2(max - min + 1);
}
if (get_cost) encode_info->bits_per_pixel[y * width + x] = cost;
}
if (encode_info != nullptr && !encode_info->line_tokens.empty()) {
encode_info->line_tokens[y] = enc->NumTokens();
}
}
WP2_CHECK_STATUS(progress_local.AdvanceBy(1.));
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
WP2Status StorePixels(uint32_t width, uint32_t height, uint32_t histo_bits,
const BackwardRefs& refs, const ProgressRange& progress,
const int16_t* const segments, WP2::ANSEncBase* const enc,
WP2::SymbolManager* const sw,
EncodeInfo* const encode_info) {
const uint32_t histo_xsize =
histo_bits ? SubSampleSize(width, histo_bits) : 1;
const uint32_t tile_mask = (histo_bits == 0) ? 0 : ~((1u << histo_bits) - 1);
// x and y trace the position in the image.
uint32_t x = 0;
uint32_t y = 0;
uint32_t tile_x = x & tile_mask;
uint32_t tile_y = y & tile_mask;
uint32_t histogram_ix = (segments == nullptr) ? 0 : segments[4 * 0 + 2];
const WP2::ProgressScale row_progress(progress, 1. / height);
if (encode_info != nullptr) {
assert(encode_info->line_tokens.empty() ||
encode_info->line_tokens.size() == height);
assert(encode_info->bits_per_pixel.empty() ||
encode_info->bits_per_pixel.size() == width * height);
}
const bool get_cost =
encode_info != nullptr && !encode_info->bits_per_pixel.empty();
// Define unused symbols.
const bool has_alpha = LosslessSymbolsInfo::HasAlpha(sw->symbols_info());
const bool is_label_image =
LosslessSymbolsInfo::IsLabelImage(sw->symbols_info());
float cost;
float* const cost_ptr = get_cost ? &cost : nullptr;
for (RefsCursor c(refs); c.Ok(); c.Next()) {
const PixelMode& v = *c.cur_pos_;
if (v.GetMode() == kSymbolTypeDiscarded) continue;
cost = 0.;
if (histo_bits != 0u) {
if ((tile_x != (x & tile_mask)) || (tile_y != (y & tile_mask))) {
tile_x = x & tile_mask;
tile_y = y & tile_mask;
const uint32_t ind =
(y >> histo_bits) * histo_xsize + (x >> histo_bits);
histogram_ix = (segments == nullptr) ? ind : segments[4 * ind + 2];
}
}
sw->ProcessWithCost(kSymbolType, histogram_ix, v.GetMode(), "lossless_mode",
enc, cost_ptr);
switch (v.GetMode()) {
case kSymbolTypeLiteral: {
WP2::ANSDebugPrefix prefix(enc, "literal");
if (has_alpha) {
sw->ProcessWithCost(kSymbolA, histogram_ix, v.GetLiteral(0), "A", enc,
cost_ptr);
}
if (!is_label_image) {
sw->ProcessWithCost(kSymbolR, histogram_ix, v.GetLiteral(1), "R", enc,
cost_ptr);
sw->ProcessWithCost(kSymbolB, histogram_ix, v.GetLiteral(3), "B", enc,
cost_ptr);
}
sw->ProcessWithCost(kSymbolG, histogram_ix, v.GetLiteral(2), "G", enc,
cost_ptr);
break;
}
case kSymbolTypeColorCacheIdx: {
const uint32_t code = v.GetColorCacheIdx();
sw->ProcessWithCost(kSymbolColorCache, histogram_ix, code,
v.GetColorCacheIdxRange() - 1, "color_cache_idx",
enc, cost_ptr);
break;
}
case kSymbolTypeSegmentCacheIdx: {
const uint32_t code = v.GetSegmentCacheIdx();
sw->ProcessWithCost(kSymbolSegmentCache, histogram_ix, code,
v.GetSegmentCacheIdxRange() - 1,
"segment_cache_idx", enc, cost_ptr);
break;
}
case kSymbolTypeCopy: {
WP2::ANSDebugPrefix prefix(enc, "copy");
const uint32_t pixel_ind = x + y * width;
const uint32_t plane_code = OffsetToPlaneCode(width, v.GetOffset());
const uint32_t max_plane_code = pixel_ind + kCodeToPlaneCodes - 1;
WriteLZ77PrefixCode(kSymbolDist, histogram_ix, plane_code,
/*min=*/0, max_plane_code, "dist", sw, enc, &cost);
uint32_t length_left = v.GetLength();
uint32_t length_written = 0u;
do {
// Write in chunks of size at most kLZ77LengthEscape.
// The first chunk has a size of at least kMinLZ77Length (otherwise
// 0).
const uint32_t min_length =
(length_written == 0) ? kLZ77LengthMin : 0u;
const uint32_t max_length = std::min(
width * height - (pixel_ind + length_written), kLZ77LengthEscape);
const uint32_t length_to_write = (length_left > kLZ77LengthEscape)
? kLZ77LengthEscape
: length_left;
WriteLZ77PrefixCode(kSymbolLen, histogram_ix, length_to_write,
min_length, max_length, "len", sw, enc, &cost);
if (length_to_write < kLZ77LengthEscape) break;
length_left -= length_to_write;
length_written += length_to_write;
} while (true);
break;
}
default:
assert(false);
}
if (get_cost) {
const float bits_per_pixel = cost / v.GetLength();
for (uint32_t i = 0; i < v.GetLength(); ++i) {
encode_info->bits_per_pixel[y * width + x + i] = bits_per_pixel;
}
}
x += v.GetLength();
while (x >= width) {
x -= width;
if (encode_info != nullptr && !encode_info->line_tokens.empty()) {
encode_info->line_tokens[y] = enc->NumTokens();
}
++y;
WP2_CHECK_STATUS(row_progress.AdvanceBy(1.));
}
}
WP2_CHECK_STATUS(enc->GetStatus());
return WP2_STATUS_OK;
}
WP2Status StorePixels(uint32_t width, uint32_t height, const BackwardRefs& refs,
WP2::ANSEncBase* const enc,
WP2::SymbolManager* const sw) {
WP2_CHECK_STATUS(StorePixels(width, height, /*histo_bits=*/0, refs,
ProgressRange(), /*segments=*/nullptr, enc, sw));
return WP2_STATUS_OK;
}
// Store the ANS statistics in the bitstream, so that they can be re-used
// when decoding the symbols.
// n_pixels is the number of pixels in the image, and use_palette a flag
// indicating whether the image uses a palette and therefore has no information
// in the A,R,B channels.
WP2Status WriteHeaders(const WP2::SymbolRecorder& recorder,
const LosslessSymbolsInfo& symbols_info,
uint32_t num_pixels, int effort,
WP2::ANSEncBase* const enc, WP2::ANSDictionaries* dicts,
WP2::SymbolWriter* const sw, float* const cost) {
WP2::ANSDebugPrefix prefix(enc, "symbols");
WP2_CHECK_STATUS(sw->Init(symbols_info, effort));
WP2_CHECK_STATUS(sw->Allocate());
float cost_tmp = 0.f;
float* cost_tmp_ptr;
if (cost == nullptr) {
cost_tmp_ptr = nullptr;
} else {
*cost = 0.f;
cost_tmp_ptr = &cost_tmp;
}
WP2::ANSDictionaries dict_tmp;
WP2::ANSDictionaries* dict_tmp_ptr;
if (dicts == nullptr) {
// Even if dicts are not asked for, we need to compute them for kSymbolNames
// so that they can be used in GetPotentialUsage.
dicts = &dict_tmp;
dict_tmp_ptr = nullptr;
} else {
dict_tmp_ptr = dicts;
}
// Iterate over all histograms and get the aggregate number of codes used.
for (uint32_t i = 0; i < symbols_info.NumClusters(kSymbolType); ++i) {
// Deal with the first symbol type.
WP2_CHECK_STATUS(sw->WriteHeader(kSymbolType, i, num_pixels, recorder,
kSymbolNames[kSymbolType], enc, dicts,
cost_tmp_ptr));
if (cost != nullptr) *cost += cost_tmp;
bool is_maybe_used[kSymbolTypeNum];
// TODO(vrabaud) For kSymbolType, it's either a dictionary, or a trivial
// symbol (not a range) so we could limit the storage to those two methods.
// We would get exact counts when decoding and we would therefore know
// exactly what is used or not. That would lead to a very small gain in
// compressibility.
sw->GetPotentialUsage(kSymbolType, i, is_maybe_used, kSymbolTypeNum);
// Figure out the number of elements and the max values in bits.
for (int s = 1; s < kSymbolNum; ++s) {
if (symbols_info.IsUnused(s)) continue;
// Ignore symbols we don't even use.
Symbol sym = (Symbol)s;
if (!IsSymbolUsed(sym, is_maybe_used)) continue;
WP2_CHECK_STATUS(sw->WriteHeader(sym, i, num_pixels, recorder,
kSymbolNames[s], enc, dict_tmp_ptr,
cost_tmp_ptr));
if (cost != nullptr) *cost += cost_tmp;
}
}
return WP2_STATUS_OK;
}
WP2Status EncodeHelperImage(WP2::ANSEncBase* const enc,
WP2::ANSDictionaries* const dicts,
const int16_t* const argb,
HashChain* const hash_chain,
BackwardRefsPool* const ref_pool, uint32_t width,
uint32_t height,
const LosslessSymbolsInfo& symbols_info, int effort,
const ProgressRange& progress) {
if (width * height == 1) {
WP2_CHECK_STATUS(
WriteRawImage(argb, width, height, symbols_info, progress, enc));
return WP2_STATUS_OK;
}
// Calculate backward references from ARGB image.
WP2_CHECK_STATUS(hash_chain->Fill(effort, argb, width, height));
BackwardRefsPool::RefsPtr refs = BackwardRefsPool::GetEmptyBackwardRefs();
CacheConfig cache_config;
WP2::SymbolRecorder symbol_recorder;
WP2_CHECK_STATUS(symbol_recorder.Allocate(symbols_info, /*num_records=*/0));
WP2_CHECK_STATUS(GetBackwardReferences(
width, height, argb, effort, kLZ77Standard | kLZ77RLE,
/*cache_bits_max=*/0, *hash_chain, symbols_info, &symbol_recorder,
&cache_config, ref_pool, &refs));
// Record symbol statistics.
WP2::SymbolRecorder recorder;
WP2::ANSEncNoop noop;
WP2_CHECK_STATUS(recorder.Allocate(symbols_info, /*num_records=*/0));
WP2_CHECK_STATUS(StorePixels(width, height, *refs, &noop, &recorder));
// Store headers.
WP2::SymbolWriter sw;
WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info, width * height, effort,
enc, dicts, &sw));
// Store actual literals.
WP2_CHECK_STATUS(StorePixels(width, height, /*histo_bits=*/0, *refs, progress,
/*segments=*/nullptr, enc, &sw));
return WP2_STATUS_OK;
}
WP2Status FindEncodingRecipe(const CrunchConfig& config, uint32_t& index) {
for (uint32_t i = 0; i < kPossibleEncodingRecipesNum; ++i) {
if (config.algorithm != kPossibleEncodingRecipes[i].algorithm) {
continue;
}
bool config_exists = true;
for (uint32_t j = 0; j < kPossibleTransformCombinationSize; ++j) {
config_exists &= config.transforms[j].type ==
kPossibleEncodingRecipes[i].transforms[j];
}
if (config_exists) {
index = i;
return WP2_STATUS_OK;
}
}
return WP2_STATUS_INVALID_CONFIGURATION;
}
WP2Status Encoder::EncodeImageInternal(
const CrunchConfig& config, const ProgressRange& progress,
const LosslessSymbolsInfo& symbols_info_init, float* const cost_best,
WP2::ANSEnc* const enc_init, EncodeInfo* const encode_info,
WP2::ANSDictionaries* const dicts_init) {
// First, check whether Group4 is used or anything else.
if (config.algorithm == EncodingAlgorithm::kGroup4) {
WP2_CHECK_STATUS(Group4Encode(
argb_buffer_, palette_.Size(), config.group4_use_move_to_front,
config_.effort, progress, enc_init, encode_info));
return WP2_STATUS_OK;
}
// Check if LZW is used.
if (config.algorithm == EncodingAlgorithm::kLZW) {
WP2_CHECK_STATUS(LZWEncode(argb_buffer_, palette_, config_.effort, enc_init,
encode_info));
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
// Check if SCP is used.
std::array<int32_t, 4> minima_range, maxima_range;
if (config.algorithm == EncodingAlgorithm::kSCP) {
WP2_CHECK_STATUS(GetARGBRanges(config.transforms,
symbols_info_init.SampleFormat(),
minima_range, maxima_range));
WP2_CHECK_STATUS(ScpEncode(argb_buffer_, config_.effort, minima_range,
maxima_range, *enc_init, encode_info));
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
if (config.algorithm == EncodingAlgorithm::kCALIC) {
WP2_CHECK_STATUS(GetARGBRanges(config.transforms,
symbols_info_init.SampleFormat(),
minima_range, maxima_range));
WP2_CHECK_STATUS(CalicEncode(argb_buffer_, config_.effort, minima_range,
maxima_range, *enc_init, encode_info));
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
// If using a color cache, do not have it bigger than the number of
// colors.
const bool use_palette =
(std::find_if(config.transforms.begin(), config.transforms.end(),
[](const TransformHeader& t) {
return t.type == TransformType::kColorIndexing;
}) != config.transforms.end());
const uint32_t width = argb_buffer_.width, height = argb_buffer_.height;
const bool color_cache_is_disabled = (width * height == 1);
const bool segment_cache_is_disabled = (width * height < 3);
const uint32_t cache_bits_max =
color_cache_is_disabled ? 0
: use_palette ? std::min(1u + (uint32_t)WP2Log2Floor(palette_.Size()),
kMaxColorCacheBits)
: kMaxColorCacheBits;
const int16_t* const argb = argb_buffer_.data;
const uint32_t histogram_bits = config.histo_bits;
const uint32_t histogram_width = SubSampleSize(width, histogram_bits);
const uint32_t histogram_height = SubSampleSize(height, histogram_bits);
const uint32_t num_segments_max = histogram_width * histogram_height;
WP2::ANSEnc enc_best_tmp, enc_tmp;
WP2::ANSDictionaries dicts_tmp;
WP2::SymbolWriter sw;
assert(width * height >= 1 && num_segments_max >= 1);
assert(histogram_bits >= kHistogramBitsMin);
assert(histogram_bits <= kHistogramBitsMax);
LosslessSymbolsInfo symbols_info;
WP2_CHECK_STATUS(symbols_info.CopyFrom(symbols_info_init));
// Get the maximum range for the color cache symbol.
uint32_t max_range = 0;
for (uint32_t i = 0; i <= cache_bits_max; ++i) {
const CacheConfig cache_config_tmp = {
(i > 0) ? CacheType::kHash : CacheType::kNone, i, false};
max_range = std::max(max_range, GetColorCacheRange(cache_config_tmp));
}
symbols_info.SetCacheRange(max_range, /*has_segment_cache=*/max_range != 0);
symbols_info.SetNumClusters(1);
// Create a SymbolRecorder that can record the maximum number of cache
// symbols.
WP2::SymbolRecorder symbol_recorder_one_segment;
WP2_CHECK_STATUS(
symbol_recorder_one_segment.Allocate(symbols_info, /*num_records=*/0));
// Label image where only the green channel is filled.
WP2::Vector_s16 segments;
WP2_CHECK_ALLOC_OK(segments.resize(4 * num_segments_max));
std::fill(segments.begin(), segments.end(), 0);
WP2_CHECK_STATUS(hash_chain_.Fill(config_.effort, argb, width, height));
for (uint32_t lz77s_idx = 0; lz77s_idx < config.lz77s_types_to_try_size;
++lz77s_idx) {
const ProgressRange idx_progress(progress,
1. / config.lz77s_types_to_try_size);
const ProgressRange histo_symbols_progress(idx_progress, 0.2);
const ProgressRange histo_image_progress(idx_progress, 0.2);
const ProgressRange symbol_stats_progress(idx_progress, 0.2);
const ProgressRange header_progress(idx_progress, 0.2);
const ProgressRange literals_progress(idx_progress, 0.2);
// Reset the bit writer or take the input one if we are at the last
// configuration.
WP2::ANSDictionaries* dicts;
WP2::ANSEnc* enc;
if (lz77s_idx + 1 < config.lz77s_types_to_try_size) {
WP2_CHECK_STATUS(enc_tmp.Clone(*enc_init));
enc = &enc_tmp;
WP2_CHECK_STATUS(dicts_tmp.CopyFrom(*dicts_init));
dicts = &dicts_tmp;
} else {
enc = enc_init;
dicts = dicts_init;
}
BackwardRefsPool::RefsPtr refs_best =
BackwardRefsPool::GetEmptyBackwardRefs();
CacheConfig cache_config;
WP2_CHECK_STATUS(GetBackwardReferences(
width, height, argb, config_.effort,
config.lz77s_types_to_try[lz77s_idx], cache_bits_max, hash_chain_,
symbols_info, &symbol_recorder_one_segment, &cache_config, &ref_pool_,
&refs_best));
symbols_info.SetCacheRange(GetColorCacheRange(cache_config),
cache_config.use_segment_cache);
// Build histogram image and symbols from backward references.
uint32_t num_segments;
WP2_CHECK_STATUS(GetHistoImageSymbols(
width, height, *refs_best, config_.effort, histo_symbols_progress,
histogram_bits, symbols_info, &num_segments, segments.data()));
// Write the global header
enc->AddDebugPrefix("GlobalHeader");
// Color Cache parameters.
if (!color_cache_is_disabled &&
enc->PutBool(cache_config.type != CacheType::kNone, "color_cache")) {
assert(cache_config.cache_bits > 0);
enc->PutRange(cache_config.cache_bits, 1, kMaxColorCacheBits,
"color_cache_bits");
}
// Segment Cache parameters.
if (!segment_cache_is_disabled && cache_config.type != CacheType::kNone) {
enc->PutBool(cache_config.use_segment_cache, "segment_cache");
}
// Find the maximum subsampling that would result in >= 2 histograms.
uint32_t max_sampling;
bool more_than_one_hist = false;
for (max_sampling = kHistogramBitsMax + 1;
!more_than_one_hist && max_sampling-- > kHistogramBitsMin;) {
more_than_one_hist = (SubSampleSize(width, max_sampling) > 1 ||
SubSampleSize(height, max_sampling) > 1);
}
// Histogram image.
if (num_segments > 1) assert(more_than_one_hist);
const bool write_histogram_image =
(more_than_one_hist &&
enc->PutBool(num_segments > 1, "write_histogram_image"));
if (write_histogram_image) {
WP2::ANSDebugPrefix prefix_histogram_image(enc, "histogram_image");
enc->PutRange(histogram_bits, kHistogramBitsMin, max_sampling,
"histogram_bits");
enc->PutRange(num_segments, 2,
std::min(kMaxHistogramImageSize, num_segments_max),
"num_histograms_m2");
LosslessSymbolsInfo symbols_info_tmp;
symbols_info_tmp.InitAsLabelImage(histogram_width * histogram_height,
num_segments);
WP2_CHECK_STATUS(EncodeHelperImage(
enc, dicts, segments.data(), &hash_chain_, &ref_pool_,
histogram_width, histogram_height, symbols_info_tmp, config_.effort,
histo_image_progress));
} else {
WP2_CHECK_STATUS(histo_image_progress.AdvanceBy(1.));
}
// Get the symbols stats.
const uint32_t num_pixels = width * height;
WP2::ANSEncNoop enc_noop;
WP2::SymbolRecorder recorder;
symbols_info.SetNumClusters(num_segments);
WP2_CHECK_STATUS(recorder.Allocate(symbols_info, /*num_records=*/0));
WP2_CHECK_STATUS(StorePixels(width, height, histogram_bits, *refs_best,
symbol_stats_progress, segments.data(),
&enc_noop, &recorder));
// Store symbol headers.
WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info, num_pixels,
config_.effort, enc, dicts, &sw));
WP2_CHECK_STATUS(header_progress.AdvanceBy(1.));
enc->PopDebugPrefix();
// Store actual literals.
EncodeInfo current_encode_info;
if (encode_info != nullptr) {
WP2_CHECK_ALLOC_OK(current_encode_info.line_tokens.resize(
encode_info->line_tokens.size()));
WP2_CHECK_ALLOC_OK(current_encode_info.bits_per_pixel.resize(
encode_info->bits_per_pixel.size()));
}
WP2_CHECK_STATUS(StorePixels(width, height, histogram_bits, *refs_best,
literals_progress, segments.data(), enc, &sw,
&current_encode_info));
// Keep track of the smallest image so far.
const float cost_tmp = enc->GetCost(*dicts);
if (cost_tmp < *cost_best) {
*cost_best = cost_tmp;
WP2::swap(*enc, enc_best_tmp);
if (encode_info != nullptr) {
swap(*encode_info, current_encode_info);
}
}
}
// enc_best can be empty if we have not found anything better.
WP2::swap(*enc_init, enc_best_tmp);
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
// Transforms
void Encoder::ApplySubtractGreen() {
SubtractGreenFromBlueAndRed(argb_buffer_.data,
argb_buffer_.width * argb_buffer_.height);
}
// Finds the best spatial predictor for each prediction block (of size
// 2^(enc->transform_bits_)), saves the residuals in enc->argb_ and writes the
// transform for each block in the form of an image.
WP2Status Encoder::ApplyPredictor(const std::array<int32_t, 4>& min_values,
const std::array<int32_t, 4>& max_values,
bool can_use_sub_modes,
const ProgressRange& progress,
uint32_t pred_bits, WP2::ANSEnc* const enc,
WP2::ANSDictionaries* const dicts) {
const ProgressRange residual_image_progress(progress, 0.5);
const ProgressRange helper_image_progress(progress, 0.5);
WP2::Vector_s8 sub_modes;
WP2_CHECK_STATUS(CreateResidualImage(
pred_bits, config_.keep_unmultiplied, can_use_sub_modes, min_values,
max_values, config_.effort, residual_image_progress, &argb_buffer_,
argb_scratch_, transform_data_, &sub_modes));
const WP2::ANSDebugPrefix prefix(enc, "predictor");
enc->PutRange(pred_bits, kTransformBitsMin, kTransformBitsMax,
"transform_bits");
const uint32_t transform_width = SubSampleSize(argb_buffer_.width, pred_bits);
const uint32_t transform_height =
SubSampleSize(argb_buffer_.height, pred_bits);
LosslessSymbolsInfo symbols_info;
symbols_info.InitAsPredictorImage(transform_width * transform_height);
const WP2Status status = EncodeHelperImage(
enc, dicts, transform_data_, (HashChain*)&hash_chain_,
(BackwardRefsPool*)&ref_pool_, transform_width, transform_height,
symbols_info, config_.effort, helper_image_progress);
if (can_use_sub_modes && enc->PutBool(!sub_modes.empty(), "use_sub_modes")) {
const WP2::ANSDebugPrefix prefix_sub_modes(enc, "sub_modes");
// Also encode the sub-modes.
WP2_CHECK_STATUS(WriteSubModes(transform_data_, /*needs_shift=*/false,
sub_modes, config_.effort, enc, dicts));
}
return status;
}
// Finds the best cross channel transform for each prediction block (of size
// 2^(enc->transform_bits_)), saves the residuals in enc->argb_ and writes the
// transform for each block in the form of an image.
WP2Status Encoder::ApplyCrossColor(const ProgressRange& progress,
uint32_t ccolor_transform_bits,
WP2::ANSEnc* const enc,
WP2::ANSDictionaries* const dicts) {
WP2_CHECK_STATUS(ColorSpaceTransform(ccolor_transform_bits, config_.effort,
&argb_buffer_, transform_data_));
const WP2::ANSDebugPrefix prefix(enc, "cross_color");
enc->PutRange(ccolor_transform_bits, kTransformBitsMin, kTransformBitsMax,
"transform_bits");
const uint32_t transform_width =
SubSampleSize(argb_buffer_.width, ccolor_transform_bits);
const uint32_t transform_height =
SubSampleSize(argb_buffer_.height, ccolor_transform_bits);
LosslessSymbolsInfo symbols_info;
symbols_info.InitAsCrossColorImage(transform_width * transform_height);
WP2_CHECK_STATUS(EncodeHelperImage(
enc, dicts, transform_data_, (HashChain*)&hash_chain_,
(BackwardRefsPool*)&ref_pool_, transform_width, transform_height,
symbols_info, config_.effort, progress));
return WP2_STATUS_OK;
}
WP2Status Encoder::ApplyCrossColorGlobal(const ProgressRange& progress,
uint32_t y_index, uint32_t uv_index,
WP2::ANSEnc* const enc) {
const WP2::ANSDebugPrefix prefix(enc, "cross_color_global");
size_t i = 0;
for (i = 0; i < kCommonCrossColorIndices.size(); ++i) {
if (y_index == kCommonCrossColorIndices[i].first &&
uv_index == kCommonCrossColorIndices[i].second) {
enc->PutRValue(i, 7, "pre_defined");
break;
}
}
if (i == kCommonCrossColorIndices.size()) {
enc->PutRValue(6, 7, "pre_defined");
enc->PutRange(y_index, 1, 9, "y_index");
enc->PutRange(uv_index, 1, 12, "uv_index");
}
CrossColorGlobal(argb_buffer_.GetRow(0),
argb_buffer_.width * argb_buffer_.height, y_index, uv_index);
WP2_CHECK_STATUS(progress.AdvanceBy(1.));
return WP2_STATUS_OK;
}
// Note: Expects "enc->palette_" to be set properly.
WP2Status Encoder::ApplyColorIndexing(const CrunchConfig& config) {
const WP2Status status = palette_.Apply(pic_, argb_buffer_.data);
argb_buffer_.has_alpha = has_alpha_;
argb_buffer_.channel_bits = WP2Formatbpc(pic_.format());
return status;
}
WP2Status Encoder::ApplyNormalizeChannels(
const std::array<int32_t, 4>& minima_range,
const std::array<int32_t, 4>& maxima_range, TransformHeader& header,
WP2::ANSEnc& enc) {
const WP2::ANSDebugPrefix prefix(&enc, "normalize_channels");
std::array<int16_t, 4> min, max;
const uint32_t width = argb_buffer_.width;
const uint32_t height = argb_buffer_.height;
const int first_channel = has_alpha_ ? 0 : 1;
FindExtrema(argb_buffer_.GetRow(0), width, height, has_alpha_, min, max);
// Store the minimum.
for (int c = first_channel; c < 4; ++c) {
header.normalize_channels_offset[c] = min[c];
enc.PutRange(min[c], minima_range[c], maxima_range[c], "offset");
max[c] -= min[c];
}
// TODO(vrabaud) This is a basic heuristic for now.
constexpr uint32_t kMinSizeForPalette = 256;
header.normalize_channels_has_palette = width * height >= kMinSizeForPalette;
if (!enc.PutBool(header.normalize_channels_has_palette, "has_palette")) {
// Remove the min value to only have positive values.
for (uint32_t y = 0; y < height; ++y) {
int16_t* const row = argb_buffer_.GetRow(y);
for (uint32_t x = 0; x < width; ++x) {
for (int c = first_channel; c < 4; c++) row[4 * x + c] -= min[c];
}
}
// Store the maximum.
for (int c = first_channel; c < 4; ++c) {
enc.PutRValue(max[c], maxima_range[c] - min[c] + 1, "max");
}
return WP2_STATUS_OK;
}
// Compute whether a value is present.
std::array<WP2::Vector_u8, 4> present;
for (int c = first_channel; c < 4; ++c) {
WP2_CHECK_ALLOC_OK(present[c].resize(max[c] + 1));
std::fill(present[c].begin(), present[c].end(), 0);
for (uint32_t y = 0; y < height; ++y) {
int16_t* const row = argb_buffer_.GetRow(y);
for (uint32_t x = 0; x < width; ++x) {
row[4 * x + c] -= min[c];
present[c][row[4 * x + c]] = 1;
}
}
}
// Convert to palette.
WP2::Vector_u16 palette;
WP2::Vector_u16 mappings;
for (int c = first_channel; c < 4; ++c) {
WP2_CHECK_ALLOC_OK(palette.resize(max[c] + 1));
WP2_CHECK_ALLOC_OK(mappings.resize(max[c] + 1));
uint32_t mapping_size = 0;
for (uint32_t i = 0; i < present[c].size(); ++i) {
if (present[c][i]) {
palette[i] = mapping_size;
mappings[mapping_size] = i;
++mapping_size;
} else {
palette[i] = std::numeric_limits<uint16_t>::max();
}
}
// Store the mapping.
const uint32_t range = 1 + maxima_range[c] - min[c];
enc.PutRange(mapping_size, 1, range, "size");
header.normalize_channels_max[c] = mapping_size - 1;
WP2::VectorNoCtor<WP2::OptimizeArrayStorageStat> stats;
WP2_CHECK_ALLOC_OK(stats.resize(mapping_size));
StoreMapping(mappings.data(), mapping_size, range, config_.effort,
stats.data(), &enc);
// Apply the mapping.
for (uint32_t y = 0; y < height; ++y) {
int16_t* const row = argb_buffer_.GetRow(y);
for (uint32_t x = c; x < 4 * width; x += 4) {
row[x] = palette[row[x]];
}
}
}
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
// Allocates the memory for argb (W x H) buffer, 2 rows of context for
// prediction and transform data.
// Flags influencing the memory allocated:
// transform_bits_
// use_predict_, use_cross_color_
WP2Status Encoder::AllocateTransformBuffer(
const WP2::Vector<CrunchConfig>& configs) {
const uint32_t width = pic_.width();
const uint32_t height = pic_.height();
const uint64_t image_size = width * height;
// Figure out whether we use predict or cross color at least once over all the
// crunch configurations.
bool use_predict = false, use_cross_color = false;
for (const CrunchConfig& config : configs) {
use_predict |=
std::find_if(config.transforms.begin(), config.transforms.end(),
[](const TransformHeader& header) {
return header.type == TransformType::kPredictor ||
header.type == TransformType::kPredictorWSub;
}) != config.transforms.end();
use_cross_color |=
std::find_if(config.transforms.begin(), config.transforms.end(),
[](const TransformHeader& header) {
return header.type == TransformType::kCrossColor;
}) != config.transforms.end();
}
// ResidualImage needs room for 2 scanlines of uint32 pixels with an extra
// pixel in each, plus 2 regular scanlines of bytes.
const uint64_t argb_scratch_size =
use_predict ? (width + 1) * 2 +
(width * 2 + sizeof(uint32_t) - 1) / sizeof(uint32_t)
: 0;
uint64_t transform_data_size = 0u;
if (use_predict || use_cross_color) {
// Find the maximal transform size needed.
for (const CrunchConfig& c : configs) {
transform_data_size =
std::max(transform_data_size,
(uint64_t)SubSampleSize(width, c.transform_bits) *
SubSampleSize(height, c.transform_bits));
}
}
const uint64_t mem_size =
image_size + argb_scratch_size + transform_data_size;
int16_t* mem;
if (4 * mem_size > transform_mem_.size()) {
WP2_CHECK_ALLOC_OK(transform_mem_.resize(4 * mem_size));
mem = transform_mem_.data();
} else {
mem = transform_mem_.data();
}
argb_buffer_.width = width;
argb_buffer_.height = height;
argb_buffer_.data = mem;
mem += 4 * image_size;
argb_scratch_ = mem;
mem += 4 * argb_scratch_size;
transform_data_ = mem;
return WP2_STATUS_OK;
}
WP2Status Encoder::MakeInputImageCopy(const CrunchConfig& config) {
const uint32_t width = pic_.width();
const uint32_t height = pic_.height();
const bool can_premultiply = has_alpha_ &&
!WP2IsPremultiplied(pic_.format()) &&
!config_.keep_unmultiplied;
if (!can_premultiply) assert(!config.use_premultiplied);
argb_buffer_.has_alpha = has_alpha_;
argb_buffer_.channel_bits = WP2Formatbpc(pic_.format());
const uint32_t max_value = WP2::FormatMax(pic_.format(), 1);
for (uint32_t y = 0; y < height; ++y) {
int16_t* const dst_row = argb_buffer_.GetRow(y);
if (WP2Formatbpc(pic_.format()) <= 8) {
const uint8_t* const src_row = pic_.GetRow8(y);
if (can_premultiply) {
for (uint32_t x = 0; x < width; ++x) {
const uint8_t a = src_row[4 * x + 0];
if (a == 0) {
// Zero out fully transparent pixels.
std::fill(dst_row + 4 * x, dst_row + 4 * (x + 1), 0u);
} else if (config.use_premultiplied) {
dst_row[4 * x + 0] = a;
for (uint32_t c = 1; c < 4; ++c) {
dst_row[4 * x + c] = static_cast<int16_t>(
std::min((src_row[4 * x + c] * a + 127u) / 255u, max_value));
}
} else {
std::copy(src_row + 4 * x, src_row + 4 * (x + 1), dst_row + 4 * x);
}
}
} else {
// Simply copy pixels over.
std::copy(src_row, src_row + 4 * width, dst_row);
}
} else {
std::copy(pic_.GetRow16(y), pic_.GetRow16(y) + 4 * width, dst_row);
}
}
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
WP2Status Encoder::ApplyAndWriteTransformsImpl(
const CrunchConfig* const config_prev, const ProgressRange progress,
CrunchConfig* const config, LosslessSymbolsInfo* const symbols_info,
WP2::ANSEnc* const enc, WP2::ANSDictionaries* const dicts) {
// We only need to copy the original image if we are not building upon a
// previous transformed image.
bool need_copy = (config_prev == nullptr);
// Get the number of transforms and previous transforms.
const uint32_t num_transforms = config->GetNumTransforms();
const uint32_t num_transforms_prev =
(config_prev == nullptr) ? 0 : config_prev->GetNumTransforms();
assert(config_prev == nullptr || num_transforms_prev < num_transforms);
for (uint32_t i = 0; i < num_transforms; ++i) {
TransformHeader& transform = config->transforms[i];
if (config_prev != nullptr && i < num_transforms_prev) {
// Skip transforms that are the same in the previous configuration.
assert(config_prev->transforms[i].type == transform.type);
// Make sure to pass along the parameters that have been computed in the
// previous pass.
transform = config_prev->transforms[i];
continue;
}
const TransformType type = transform.type;
ProgressRange progress_local(progress,
1. / (num_transforms - num_transforms_prev));
if (type == TransformType::kColorIndexing) {
// Encode palette.
transform.indexing_num_colors = palette_.Size();
ProgressRange palette_find(progress_local, 0.5);
ProgressRange palette_write(progress_local, 0.5);
WP2_CHECK_STATUS(palette_.FindBestMethod(pic_, palette_find,
config->palette_sorting_type,
config_.effort, this));
WP2_CHECK_STATUS(palette_.Write(palette_write, enc, dicts, this));
WP2_CHECK_STATUS(ApplyColorIndexing(*config));
need_copy = false;
continue;
}
if (need_copy) {
WP2_CHECK_STATUS(MakeInputImageCopy(*config));
need_copy = false;
}
switch (type) {
case TransformType::kSubtractGreen:
ApplySubtractGreen();
WP2_CHECK_STATUS(progress_local.AdvanceBy(1.));
break;
case TransformType::kPredictor:
case TransformType::kPredictorWSub: {
// Define the minimum/maximum values before applying the predictors.
std::array<int32_t, 4> minima_range, maxima_range;
WP2_CHECK_STATUS(
GetARGBRanges(config->transforms, symbols_info_.SampleFormat(),
minima_range, maxima_range, /*num_transforms=*/i));
WP2_CHECK_STATUS(ApplyPredictor(minima_range, maxima_range,
config->HasSubModes(), progress_local,
config->transform_bits, enc, dicts));
break;
}
case TransformType::kCrossColor:
WP2_CHECK_STATUS(ApplyCrossColor(progress_local, config->transform_bits,
enc, dicts));
break;
case TransformType::kCrossColorGlobal:
WP2_CHECK_STATUS(
ApplyCrossColorGlobal(progress_local, transform.cc_global_y_index,
transform.cc_global_uv_index, enc));
break;
case TransformType::kNormalizeChannels: {
std::array<int32_t, 4> minima_range, maxima_range;
WP2_CHECK_STATUS(
GetARGBRanges(config->transforms, symbols_info_.SampleFormat(),
minima_range, maxima_range, /*num_transforms=*/i));
WP2_CHECK_STATUS(ApplyNormalizeChannels(minima_range, maxima_range,
transform, *enc));
WP2_CHECK_STATUS(progress_local.AdvanceBy(1.));
break;
}
case TransformType::kColorIndexing:
case TransformType::kNum:
assert(false);
return WP2_STATUS_INVALID_PARAMETER;
}
}
if (need_copy) WP2_CHECK_STATUS(MakeInputImageCopy(*config));
return WP2_STATUS_OK;
}
// Applies the different transforms decided by the cruncher and writes them to
// the header.
WP2Status Encoder::ApplyAndWriteTransforms(
const CrunchConfig* const config_prev, bool cache_data,
const WP2::ProgressRange progress, CrunchConfig* const config,
LosslessSymbolsInfo* const symbols_info, WP2::ANSEnc* const enc,
WP2::ANSDictionaries* const dicts) {
WP2_CHECK_STATUS(symbols_info->CopyFrom(symbols_info_));
symbols_info->SetCacheRange(0, /*has_segment_cache=*/false);
const WP2::ANSDebugPrefix prefix1(enc, "GlobalHeader");
const WP2::ANSDebugPrefix prefix2(enc, "transforms");
// Find the transform combination in the official set and store its index.
uint32_t index;
WP2_CHECK_STATUS(FindEncodingRecipe(*config, index));
enc->PutRValue(index, kPossibleEncodingRecipesNum, "index");
if (config_prev != nullptr) {
// If we have already computed transforms before, re-use cached data.
WP2_CHECK_STATUS(enc->AppendTokens(cached_transforms_enc_, 0,
std::numeric_limits<uint32_t>::max()));
WP2::swap(cached_transforms_dict_, *dicts);
}
if (cache_data) {
// First store to cached_transforms_enc_ for future use.
cached_transforms_enc_.Reset();
cached_transforms_enc_.CopyDebugPrefix(*enc);
WP2_CHECK_STATUS(
ApplyAndWriteTransformsImpl(config_prev, progress, config, symbols_info,
&cached_transforms_enc_, dicts));
WP2_CHECK_STATUS(cached_transforms_dict_.CopyFrom(*dicts));
// Then append cached_transforms_enc_ to the output encoder.
WP2_CHECK_STATUS(enc->AppendTokens(cached_transforms_enc_, 0,
std::numeric_limits<uint32_t>::max()));
} else {
WP2_CHECK_STATUS(ApplyAndWriteTransformsImpl(config_prev, progress, config,
symbols_info, enc, dicts));
}
if (config->algorithm == EncodingAlgorithm::kGroup4) {
// Green channel of first pixel.
const int16_t first_color = argb_buffer_.data[2];
assert(first_color >= 0);
enc->PutRValue(first_color, palette_.Size(), "first_color");
if (palette_.Size() > 3) {
// MoveToFront is only relevant when there are more than 3 colors.
// With 2 colors, any color change is necessarily the other color
// (different from the current one). With 3 colors, we use a boolean
// to encode whether the new color is the one expected (from the row
// above), or the other one, and that is enough.
enc->PutBool(config->group4_use_move_to_front, "use_move_to_front");
} else {
assert(!config->group4_use_move_to_front);
}
return WP2_STATUS_OK;
}
if (config->algorithm == EncodingAlgorithm::kLZW) {
WP2_CHECK_STATUS(MakeInputImageCopy(*config));
ProgressRange progress_local(progress, 1.);
WP2_CHECK_STATUS(palette_.Write(progress_local, enc, dicts, this));
// 'lzw_capacity' must be at least number of colors.
const uint32_t lzw_capacity = palette_.Size() + kLZWCapacity;
assert(lzw_capacity <= kLZWCapacityMax);
enc->PutRange(lzw_capacity, palette_.Size() + 1, kLZWCapacityMax,
"capacity");
return WP2_STATUS_OK;
}
if (config->algorithm == EncodingAlgorithm::kSCP ||
config->algorithm == EncodingAlgorithm::kCALIC) {
return WP2_STATUS_OK;
}
if (has_alpha_ && image_is_premultiplied_) {
// The lossless encoder may use unmultiplied samples for better compression,
// even if the image is signaled as premultiplied overall.
enc->PutBool(WP2IsPremultiplied(pic_.format()) || config->use_premultiplied,
"is_tile_premultiplied");
} else {
// If it is signaled that the color information in the translucent pixels is
// kept, no lossless compression algorithm can encode premultiplied samples.
assert(!config->use_premultiplied);
}
if (config->transforms[0].type == TransformType::kNum) {
ProgressRange progress_local(progress, 1.);
WP2_CHECK_STATUS(progress_local.AdvanceBy(1.));
} else if (config->transforms[0].type == TransformType::kColorIndexing &&
config->transforms[1].type == TransformType::kNum) {
symbols_info->InitAsLabelImage(pic_.width() * pic_.height(),
palette_.Size());
} else {
// Find the ranges of the ARGB values in the image.
std::array<int16_t, 4> minima, maxima;
FindExtrema(argb_buffer_.GetRow(0), pic_.width(), pic_.height(), has_alpha_,
minima, maxima);
// Find the ranges in which they should belong according to the transforms.
std::array<int32_t, 4> minima_range, maxima_range;
WP2_CHECK_STATUS(GetARGBRanges(config->transforms,
symbols_info->SampleFormat(), minima_range,
maxima_range));
for (uint32_t c = has_alpha_ ? 0 : 1; c < 4; ++c) {
assert(minima[c] <= maxima[c]);
if (minima[c] < minima_range[c] || maxima[c] > maxima_range[c]) {
// Invalidate the encoding because it does not fit within the range.
enc->Reset();
return WP2_STATUS_OK;
}
}
// Write the ARGB ranges.
for (Symbol sym : {kSymbolA, kSymbolR, kSymbolG, kSymbolB}) {
if (symbols_info->IsUnused(sym)) continue;
const uint32_t channel = (int)sym - (int)kSymbolA;
if (minima_range[channel] < 0 &&
enc->PutBool(minima[channel] < 0, "min_is_negative")) {
WritePrefixCode(minima[channel], minima_range[channel], -1,
kARGBRangePrefixSize, enc, "minimum");
if (enc->PutBool(maxima[channel] < 0, "max_is_negative")) {
WritePrefixCode(maxima[channel], minima[channel], -1,
kARGBRangePrefixSize, enc, "maximum");
} else {
WritePrefixCode(maxima[channel], 0, maxima_range[channel],
kARGBRangePrefixSize, enc, "maximum");
}
} else {
WritePrefixCode(minima[channel], 0, maxima_range[channel],
kARGBRangePrefixSize, enc, "minimum");
WritePrefixCode(maxima[channel], minima[channel], maxima_range[channel],
kARGBRangePrefixSize, enc, "maximum");
}
symbols_info->SetMinMax(sym, minima[channel], maxima[channel]);
}
}
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
// Encoder
Encoder::Encoder(const WP2::EncoderConfig& config,
const WP2::ArgbBuffer& picture, bool has_alpha,
bool is_premultiplied)
: config_(config),
pic_(picture),
has_alpha_(has_alpha),
image_is_premultiplied_(is_premultiplied) {
argb_scratch_ = nullptr;
transform_data_ = nullptr;
symbols_info_.Init(picture.width() * picture.height(), has_alpha_,
pic_.format());
EncLDspInit();
}
WP2Status Encoder::Allocate() {
const uint32_t num_pixels = pic_.width() * pic_.height();
WP2_CHECK_ALLOC_OK(hash_chain_.Allocate(num_pixels));
palette_.Init(symbols_info_.SampleFormat(), has_alpha_,
config_.keep_unmultiplied);
ref_pool_.Init(num_pixels);
return WP2_STATUS_OK;
}
// -----------------------------------------------------------------------------
// Main call
WP2Status EncodeImage(const WP2::EncoderConfig& encoder_config,
const WP2::ArgbBuffer& picture, bool has_alpha,
bool image_is_premultiplied,
const ProgressRange& progress,
WP2::ANSEnc* const enc_init,
EncodeInfo* const encode_info) {
assert(!picture.IsEmpty());
assert(enc_init != nullptr);
const ProgressRange analysis_progress(progress, 0.5);
const ProgressRange full_encode_progress(progress, 0.5);
WP2::Vector<CrunchConfig> crunch_configs;
// Analyze image (entropy, num_palettes etc)
Encoder encoder(encoder_config, picture, has_alpha, image_is_premultiplied);
WP2_CHECK_STATUS(encoder.Allocate());
WP2_CHECK_STATUS(
EncoderAnalyze(analysis_progress, &encoder, &crunch_configs));
WP2_CHECK_STATUS(encoder.AllocateTransformBuffer(crunch_configs));
const uint32_t width = encoder.pic_.width();
const uint32_t height = encoder.pic_.height();
uint32_t best_size = std::numeric_limits<uint32_t>::max();
float best_cost = std::numeric_limits<float>::max();
WP2::ANSEnc enc, enc_best;
bool use_previous_transforms = false;
for (uint32_t i_config = 0; i_config < crunch_configs.size(); ++i_config) {
const CrunchConfig& config = crunch_configs[i_config];
const ProgressRange config_progress(full_encode_progress,
1. / crunch_configs.size());
if (config.is_skippable && best_cost < std::numeric_limits<float>::max()) {
WP2_CHECK_STATUS(config_progress.AdvanceBy(1.));
continue;
}
const ProgressRange transforms_progress(config_progress, 0.5);
const ProgressRange encode_progress(config_progress, 0.5);
// Reset the bit writer.
WP2_CHECK_STATUS(enc.Clone(*enc_init));
WP2::ANSDictionaries dicts;
// Reset any parameter in the encoder that is set in the previous iteration.
LosslessSymbolsInfo symbols_info;
encoder.ref_pool_.Reset();
// Check whether we need to cache data by checking whether the next
// configuration has the same first transforms.
bool cache_transforms = false;
if (i_config + 1 < crunch_configs.size() &&
config.algorithm == EncodingAlgorithm::kWebP) {
cache_transforms = config.ShouldBeCachedFor(crunch_configs[i_config + 1]);
}
WP2_CHECK_STATUS(encoder.ApplyAndWriteTransforms(
use_previous_transforms ? &crunch_configs[i_config - 1] : nullptr,
cache_transforms, transforms_progress, &crunch_configs[i_config],
&symbols_info, &enc, &dicts));
// If enc has been emptied, encoding could not be performed.
if (enc.NumTokens() == 0) {
use_previous_transforms = false;
WP2_CHECK_STATUS(encode_progress.AdvanceBy(1.));
continue;
}
use_previous_transforms = cache_transforms;
// -------------------------------------------------------------------------
// Encode and write the transformed image.
EncodeInfo current_encode_info;
if (encode_info != nullptr) {
WP2_CHECK_ALLOC_OK(current_encode_info.line_tokens.resize(height));
WP2_CHECK_ALLOC_OK(
current_encode_info.bits_per_pixel.resize(width * height));
}
WP2_CHECK_STATUS(encoder.EncodeImageInternal(config, encode_progress,
symbols_info, &best_cost, &enc,
&current_encode_info, &dicts));
// If enc has been emptied, nothing better could be found.
if (enc.NumTokens() == 0) continue;
// If we are better than what we already have.
WP2_CHECK_STATUS(enc.AssembleToBitstream());
if (enc.GetBitstreamSize() < best_size) {
best_size = enc.GetBitstreamSize();
// Store the BitWriter.
WP2::swap(enc, enc_best);
if (encode_info != nullptr) {
swap(*encode_info, current_encode_info);
}
}
}
// In case no encoding could be performed (this can happen in tests if a
// configuration is imposed and it does not work out).
WP2_CHECK_OK(best_size < std::numeric_limits<uint32_t>::max(),
WP2_STATUS_INVALID_PARAMETER);
WP2::swap(*enc_init, enc_best);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
bool EncodeInfo::CopyFrom(const EncodeInfo& other) {
return line_tokens.copy_from(other.line_tokens) &&
bits_per_pixel.copy_from(other.bits_per_pixel);
}
//------------------------------------------------------------------------------
} // namespace WP2L