// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
size_t num_streams =
ModularStreamId::Num(frame_dim_, frame_header.passes.num_passes); if (cparams_.ModularPartIsLossless()) { switch (cparams_.decoding_speed_tier) { case 0: break; case 1:
cparams_.options.wp_tree_mode = ModularOptions::TreeMode::kWPOnly; break; case 2: {
cparams_.options.wp_tree_mode = ModularOptions::TreeMode::kGradientOnly;
cparams_.options.predictor = Predictor::Gradient; break;
} case 3: { // LZ77, no Gradient.
cparams_.options.nb_repeats = 0;
cparams_.options.predictor = Predictor::Gradient; break;
} default: { // LZ77, no predictor.
cparams_.options.nb_repeats = 0;
cparams_.options.predictor = Predictor::Zero; break;
}
}
} if (cparams_.decoding_speed_tier >= 1 && cparams_.responsive &&
cparams_.ModularPartIsLossless()) {
cparams_.options.tree_kind =
ModularOptions::TreeKind::kTrivialTreeNoPredictor;
cparams_.options.nb_repeats = 0;
} for (size_t i = 0; i < num_streams; ++i) {
stream_images_.emplace_back(memory_manager_);
}
// use a sensible default if nothing explicit is specified: // Squeeze for lossy, no squeeze for lossless if (cparams_.responsive < 0) { if (cparams_.ModularPartIsLossless()) {
cparams_.responsive = 0;
} else {
cparams_.responsive = 1;
}
}
int level_max_bitdepth = (cparams_.level == 5 ? 16 : 32); if (max_bitdepth > level_max_bitdepth) { return JXL_FAILURE( "Bitdepth too high for level %i (need %i bits, have only %i in this " "level)",
cparams_.level, max_bitdepth, level_max_bitdepth);
}
// Set options and apply transformations if (!cparams_.ModularPartIsLossless()) { if (cparams_.palette_colors != 0) {
JXL_DEBUG_V(3, "Lossy encode, not doing palette transforms");
} if (cparams_.color_transform == ColorTransform::kXYB) {
cparams_.channel_colors_pre_transform_percent = 0;
}
cparams_.channel_colors_percent = 0;
cparams_.palette_colors = 0;
cparams_.lossy_palette = false;
}
// don't do squeeze if we don't have some spare bits if (!groupwise && cparams_.responsive && !gi.channel.empty() &&
max_bitdepth + 2 < level_max_bitdepth) {
Transform t(TransformId::kSqueeze);
do_transform(gi, t, weighted::Header(), pool);
max_bitdepth += 2;
}
if (max_bitdepth + 1 > level_max_bitdepth) { // force no group RCTs if we don't have a spare bit
cparams_.colorspace = 0;
}
JXL_ENSURE(max_bitdepth <= level_max_bitdepth);
if (!cparams_.ModularPartIsLossless()) {
quants_.resize(gi.channel.size(), 1); float quantizer = 0.25f; if (!cparams_.responsive) {
JXL_DEBUG_V(1, "Warning: lossy compression without Squeeze " "transform is just color quantization.");
quantizer *= 0.1f;
} float bitdepth_correction = 1.f; if (cparams_.color_transform != ColorTransform::kXYB) {
bitdepth_correction = maxval / 255.f;
}
std::vector<float> quantizers; for (size_t i = 0; i < 3; i++) { float dist = cparams_.butteraugli_distance;
quantizers.push_back(quantizer * dist * bitdepth_correction);
} for (size_t i = 0; i < extra_channels.size(); i++) { int ec_bitdepth =
metadata.extra_channel_info[i].bit_depth.bits_per_sample;
pixel_type ec_maxval = ec_bitdepth < 32 ? (1u << ec_bitdepth) - 1 : 0;
bitdepth_correction = ec_maxval / 255.f; float dist = 0; if (i < cparams_.ec_distance.size()) dist = cparams_.ec_distance[i]; if (dist < 0) dist = cparams_.butteraugli_distance;
quantizers.push_back(quantizer * dist * bitdepth_correction);
} if (cparams_.options.nb_repeats == 0) { return JXL_FAILURE("nb_repeats = 0 not supported with modular lossy!");
} for (uint32_t i = gi.nb_meta_channels; i < gi.channel.size(); i++) {
Channel& ch = gi.channel[i]; int shift = ch.hshift + ch.vshift; // number of pixel halvings if (shift > 16) shift = 16; if (shift > 0) shift--; int q; // assuming default Squeeze here int component =
(do_color ? 0 : 3) + ((i - gi.nb_meta_channels) % nb_chans); // last 4 channels are final chroma residuals if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams_.responsive) {
component = 1;
} if (cparams_.color_transform == ColorTransform::kXYB && component < 3) {
q = quantizers[component] * squeeze_quality_factor_xyb *
squeeze_xyb_qtable[component][shift];
} else { if (cparams_.colorspace != 0 && component > 0 && component < 3) {
q = quantizers[component] * squeeze_quality_factor *
squeeze_chroma_qtable[shift];
} else {
q = quantizers[component] * squeeze_quality_factor *
squeeze_luma_factor * squeeze_luma_qtable[shift];
}
} if (q < 1) q = 1;
QuantizeChannel(gi.channel[i], q);
quants_[i] = q;
}
}
// Fill other groups. // DC for (size_t group_id = 0; group_id < patch_dim.num_dc_groups; group_id++) { const size_t rgx = group_id % patch_dim.xsize_dc_groups; const size_t rgy = group_id / patch_dim.xsize_dc_groups; const Rect rect(rgx * patch_dim.dc_group_dim, rgy * patch_dim.dc_group_dim,
patch_dim.dc_group_dim, patch_dim.dc_group_dim);
size_t gx = rgx + frame_area_rect.x0() / 2048;
size_t gy = rgy + frame_area_rect.y0() / 2048;
size_t real_group_id = gy * frame_dim_.xsize_dc_groups + gx; // minShift==3 because (frame_dim.dc_group_dim >> 3) == frame_dim.group_dim // maxShift==1000 is infinity
stream_params_.push_back(
GroupParams{rect, 3, 1000, ModularStreamId::ModularDC(real_group_id)});
} // AC global -> nothing. // AC for (size_t group_id = 0; group_id < patch_dim.num_groups; group_id++) { const size_t rgx = group_id % patch_dim.xsize_groups; const size_t rgy = group_id / patch_dim.xsize_groups; const Rect mrect(rgx * patch_dim.group_dim, rgy * patch_dim.group_dim,
patch_dim.group_dim, patch_dim.group_dim);
size_t gx = rgx + frame_area_rect.x0() / (frame_dim_.group_dim);
size_t gy = rgy + frame_area_rect.y0() / (frame_dim_.group_dim);
size_t real_group_id = gy * frame_dim_.xsize_groups + gx; for (size_t i = 0; i < enc_state->progressive_splitter.GetNumPasses();
i++) { int maxShift; int minShift;
frame_header.passes.GetDownsamplingBracket(i, minShift, maxShift);
stream_params_.push_back(
GroupParams{mrect, minShift, maxShift,
ModularStreamId::ModularAC(real_group_id, i)});
}
} // if there's only one group, everything ends up in GlobalModular // in that case, also try RCTs/WP params for the one group if (stream_params_.size() == 2) {
stream_params_.push_back(GroupParams{Rect(0, 0, xsize, ysize), 0, 1000,
ModularStreamId::Global()});
}
gi_channel_.resize(stream_images_.size());
constauto process_row = [&](const uint32_t i,
size_t /* thread */) -> Status {
size_t stream = stream_params_[i].id.ID(frame_dim_); if (stream != 0) {
stream_options_[stream] = stream_options_[0];
}
JXL_RETURN_IF_ERROR(PrepareStreamParams(
stream_params_[i].rect, cparams_, stream_params_[i].minShift,
stream_params_[i].maxShift, stream_params_[i].id, do_color, groupwise)); returntrue;
};
JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, stream_params_.size(),
ThreadPool::NoInit, process_row, "ChooseParams"));
{ // Clear out channels that have been copied to groups.
Image& full_image = stream_images_[0];
size_t c = full_image.nb_meta_channels; for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c]; if (fc.w > frame_dim_.group_dim || fc.h > frame_dim_.group_dim) break;
} for (; c < full_image.channel.size(); c++) {
full_image.channel[c].plane = ImageI();
}
}
Status ModularFrameEncoder::ComputeTree(ThreadPool* pool) {
std::vector<ModularMultiplierInfo> multiplier_info; if (!quants_.empty()) { for (uint32_t stream_id = 0; stream_id < stream_images_.size();
stream_id++) { // skip non-modular stream_ids if (stream_id > 0 && gi_channel_[stream_id].empty()) continue; const Image& image = stream_images_[stream_id]; const ModularOptions& options = stream_options_[stream_id]; for (uint32_t i = image.nb_meta_channels; i < image.channel.size(); i++) { if (i >= image.nb_meta_channels &&
(image.channel[i].w > options.max_chan_size ||
image.channel[i].h > options.max_chan_size)) { continue;
} if (stream_id > 0 && gi_channel_[stream_id].empty()) continue;
size_t ch_id = stream_id == 0
? i
: gi_channel_[stream_id][i - image.nb_meta_channels];
uint32_t q = quants_[ch_id]; // Inform the tree splitting heuristics that each channel in each group // used this quantization factor. This will produce a tree with the // given multipliers. if (multiplier_info.empty() ||
multiplier_info.back().range[1][0] != stream_id ||
multiplier_info.back().multiplier != q) {
StaticPropRange range;
range[0] = {{i, i + 1}};
range[1] = {{stream_id, stream_id + 1}};
multiplier_info.push_back({range, static_cast<uint32_t>(q)});
} else { // Previous channel in the same group had the same quantization // factor. Don't provide two different ranges, as that creates // unnecessary nodes.
multiplier_info.back().range[0][1] = i + 1;
}
}
} // Merge group+channel settings that have the same channels and quantization // factors, to avoid unnecessary nodes.
std::sort(multiplier_info.begin(), multiplier_info.end(),
[](ModularMultiplierInfo a, ModularMultiplierInfo b) { return std::make_tuple(a.range, a.multiplier) <
std::make_tuple(b.range, b.multiplier);
});
size_t new_num = 1; for (size_t i = 1; i < multiplier_info.size(); i++) {
ModularMultiplierInfo& prev = multiplier_info[new_num - 1];
ModularMultiplierInfo& cur = multiplier_info[i]; if (prev.range[0] == cur.range[0] && prev.multiplier == cur.multiplier &&
prev.range[1][1] == cur.range[1][0]) {
prev.range[1][1] = cur.range[1][1];
} else {
multiplier_info[new_num++] = multiplier_info[i];
}
}
multiplier_info.resize(new_num);
}
if (!cparams_.custom_fixed_tree.empty()) {
tree_ = cparams_.custom_fixed_tree;
} elseif (cparams_.speed_tier < SpeedTier::kFalcon ||
!cparams_.modular_mode) { // Avoid creating a tree with leaves that don't correspond to any pixels.
std::vector<size_t> useful_splits;
useful_splits.reserve(tree_splits_.size()); for (size_t chunk = 0; chunk < tree_splits_.size() - 1; chunk++) { bool has_pixels = false;
size_t start = tree_splits_[chunk];
size_t stop = tree_splits_[chunk + 1]; for (size_t i = start; i < stop; i++) { if (!stream_images_[i].empty()) has_pixels = true;
} if (has_pixels) {
useful_splits.push_back(tree_splits_[chunk]);
}
} // Don't do anything if modular mode does not have any pixels in this image if (useful_splits.empty()) returntrue;
useful_splits.push_back(tree_splits_.back());
Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect, const CompressParams& cparams_, int minShift, int maxShift, const ModularStreamId& stream, bool do_color, bool groupwise) {
size_t stream_id = stream.ID(frame_dim_); if (stream_id == 0 && frame_dim_.num_groups != 1) { // If we have multiple groups, then the stream with ID 0 holds the full // image and we do not want to apply transforms or in general change the // pixel values. returntrue;
}
Image& full_image = stream_images_[0];
JxlMemoryManager* memory_manager = full_image.memory_manager(); const size_t xsize = rect.xsize(); const size_t ysize = rect.ysize();
Image& gi = stream_images_[stream_id]; if (stream_id > 0) {
JXL_ASSIGN_OR_RETURN(gi, Image::Create(memory_manager, xsize, ysize,
full_image.bitdepth, 0)); // start at the first bigger-than-frame_dim.group_dim non-metachannel
size_t c = full_image.nb_meta_channels; if (!groupwise) { for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c]; if (fc.w > frame_dim_.group_dim || fc.h > frame_dim_.group_dim) break;
}
} for (; c < full_image.channel.size(); c++) {
Channel& fc = full_image.channel[c]; int shift = std::min(fc.hshift, fc.vshift); if (shift > maxShift) continue; if (shift < minShift) continue;
Rect r(rect.x0() >> fc.hshift, rect.y0() >> fc.vshift,
rect.xsize() >> fc.hshift, rect.ysize() >> fc.vshift, fc.w, fc.h); if (r.xsize() == 0 || r.ysize() == 0) continue;
gi_channel_[stream_id].push_back(c);
JXL_ASSIGN_OR_RETURN(
Channel gc, Channel::Create(memory_manager, r.xsize(), r.ysize()));
gc.hshift = fc.hshift;
gc.vshift = fc.vshift; for (size_t y = 0; y < r.ysize(); ++y) {
memcpy(gc.Row(y), r.ConstRow(fc.plane, y),
r.xsize() * sizeof(pixel_type));
}
gi.channel.emplace_back(std::move(gc));
}
if (gi.channel.empty()) returntrue; // Do some per-group transforms
// Local palette transforms // TODO(veluca): make this work with quantize-after-prediction in lossy // mode. if (cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette &&
cparams_.speed_tier < SpeedTier::kCheetah) { int max_bitdepth = 0, maxval = 0; // don't care about that here float channel_color_percent = 0; if (!(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) {
channel_color_percent = cparams_.channel_colors_percent;
}
try_palettes(gi, max_bitdepth, maxval, cparams_, channel_color_percent);
}
}
// lossless and no specific color transform specified: try Nothing, YCoCg, // and 17 RCTs if (cparams_.color_transform == ColorTransform::kNone &&
cparams_.IsLossless() && cparams_.colorspace < 0 &&
gi.channel.size() - gi.nb_meta_channels >= 3 &&
cparams_.responsive == JXL_FALSE && do_color &&
cparams_.speed_tier <= SpeedTier::kHare) {
Transform sg(TransformId::kRCT);
sg.begin_c = gi.nb_meta_channels;
size_t nb_rcts_to_try = 0; switch (cparams_.speed_tier) { case SpeedTier::kLightning: case SpeedTier::kThunder: case SpeedTier::kFalcon: case SpeedTier::kCheetah:
nb_rcts_to_try = 0; // Just do global YCoCg break; case SpeedTier::kHare:
nb_rcts_to_try = 4; break; case SpeedTier::kWombat:
nb_rcts_to_try = 5; break; case SpeedTier::kSquirrel:
nb_rcts_to_try = 7; break; case SpeedTier::kKitten:
nb_rcts_to_try = 9; break; case SpeedTier::kTectonicPlate: case SpeedTier::kGlacier: case SpeedTier::kTortoise:
nb_rcts_to_try = 19; break;
} float best_cost = std::numeric_limits<float>::max();
size_t best_rct = 0; // These should be 19 actually different transforms; the remaining ones // are equivalent to one of these (note that the first two are do-nothing // and YCoCg) modulo channel reordering (which only matters in the case of // MA-with-prev-channels-properties) and/or sign (e.g. RmG vs GmR) for (int i : {0 * 7 + 0, 0 * 7 + 6, 0 * 7 + 5, 1 * 7 + 3, 3 * 7 + 5,
5 * 7 + 5, 1 * 7 + 5, 2 * 7 + 5, 1 * 7 + 1, 0 * 7 + 4,
1 * 7 + 2, 2 * 7 + 1, 2 * 7 + 2, 2 * 7 + 3, 4 * 7 + 4,
4 * 7 + 5, 0 * 7 + 2, 0 * 7 + 1, 0 * 7 + 3}) { if (nb_rcts_to_try == 0) break;
sg.rct_type = i;
nb_rcts_to_try--; if (do_transform(gi, sg, weighted::Header())) { float cost = EstimateCost(gi); if (cost < best_cost) {
best_rct = i;
best_cost = cost;
}
Transform t = gi.transform.back();
JXL_RETURN_IF_ERROR(t.Inverse(gi, weighted::Header(), nullptr));
gi.transform.pop_back();
}
} // Apply the best RCT to the image for future encoding.
sg.rct_type = best_rct;
do_transform(gi, sg, weighted::Header());
} else { // No need to try anything, just use the default options.
}
size_t nb_wp_modes = 1; if (cparams_.speed_tier <= SpeedTier::kTortoise) {
nb_wp_modes = 5;
} elseif (cparams_.speed_tier <= SpeedTier::kKitten) {
nb_wp_modes = 2;
} if (nb_wp_modes > 1 &&
(stream_options_[stream_id].predictor == Predictor::Weighted ||
stream_options_[stream_id].predictor == Predictor::Best ||
stream_options_[stream_id].predictor == Predictor::Variable)) { float best_cost = std::numeric_limits<float>::max();
stream_options_[stream_id].wp_mode = 0; for (size_t i = 0; i < nb_wp_modes; i++) { float cost = EstimateWPCost(gi, i); if (cost < best_cost) {
best_cost = cost;
stream_options_[stream_id].wp_mode = i;
}
}
} returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.