Source code
Revision control
Copy as Markdown
Other Tools
// 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.
#include "lib/jxl/dec_cache.h"
#include <jxl/memory_manager.h>
#include <algorithm>
#include "lib/jxl/ac_strategy.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/blending.h"
#include "lib/jxl/coeff_order.h"
#include "lib/jxl/common.h" // JXL_HIGH_PRECISION
#include "lib/jxl/memory_manager_internal.h"
#include "lib/jxl/render_pipeline/stage_blending.h"
#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h"
#include "lib/jxl/render_pipeline/stage_cms.h"
#include "lib/jxl/render_pipeline/stage_epf.h"
#include "lib/jxl/render_pipeline/stage_from_linear.h"
#include "lib/jxl/render_pipeline/stage_gaborish.h"
#include "lib/jxl/render_pipeline/stage_noise.h"
#include "lib/jxl/render_pipeline/stage_patches.h"
#include "lib/jxl/render_pipeline/stage_splines.h"
#include "lib/jxl/render_pipeline/stage_spot.h"
#include "lib/jxl/render_pipeline/stage_to_linear.h"
#include "lib/jxl/render_pipeline/stage_tone_mapping.h"
#include "lib/jxl/render_pipeline/stage_upsampling.h"
#include "lib/jxl/render_pipeline/stage_write.h"
#include "lib/jxl/render_pipeline/stage_xyb.h"
#include "lib/jxl/render_pipeline/stage_ycbcr.h"
namespace jxl {
Status GroupDecCache::InitOnce(JxlMemoryManager* memory_manager,
size_t num_passes, size_t used_acs) {
for (size_t i = 0; i < num_passes; i++) {
if (num_nzeroes[i].xsize() == 0) {
// Allocate enough for a whole group - partial groups on the
// right/bottom border just use a subset. The valid size is passed via
// Rect.
JXL_ASSIGN_OR_RETURN(num_nzeroes[i],
Image3I::Create(memory_manager, kGroupDimInBlocks,
kGroupDimInBlocks));
}
}
size_t max_block_area = 0;
for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
AcStrategy acs = AcStrategy::FromRawStrategy(o);
if ((used_acs & (1 << o)) == 0) continue;
size_t area =
acs.covered_blocks_x() * acs.covered_blocks_y() * kDCTBlockSize;
max_block_area = std::max(area, max_block_area);
}
if (max_block_area > max_block_area_) {
max_block_area_ = max_block_area;
// We need 3x float blocks for dequantized coefficients and 1x for scratch
// space for transforms.
JXL_ASSIGN_OR_RETURN(
float_memory_,
AlignedMemory::Create(memory_manager,
max_block_area_ * 7 * sizeof(float)));
// We need 3x int32 or int16 blocks for quantized coefficients.
JXL_ASSIGN_OR_RETURN(
int32_memory_,
AlignedMemory::Create(memory_manager,
max_block_area_ * 3 * sizeof(int32_t)));
JXL_ASSIGN_OR_RETURN(
int16_memory_,
AlignedMemory::Create(memory_manager,
max_block_area_ * 3 * sizeof(int16_t)));
}
dec_group_block = float_memory_.address<float>();
scratch_space = dec_group_block + max_block_area_ * 3;
dec_group_qblock = int32_memory_.address<int32_t>();
dec_group_qblock16 = int16_memory_.address<int16_t>();
return true;
}
// Initialize the decoder state after all of DC is decoded.
Status PassesDecoderState::InitForAC(size_t num_passes, ThreadPool* pool) {
shared_storage.coeff_order_size = 0;
for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
if (((1 << o) & used_acs) == 0) continue;
uint8_t ord = kStrategyOrder[o];
shared_storage.coeff_order_size =
std::max(kCoeffOrderOffset[3 * (ord + 1)] * kDCTBlockSize,
shared_storage.coeff_order_size);
}
size_t sz = num_passes * shared_storage.coeff_order_size;
if (sz > shared_storage.coeff_orders.size()) {
shared_storage.coeff_orders.resize(sz);
}
return true;
}
Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
const ImageMetadata* metadata,
ImageBundle* decoded,
PipelineOptions options) {
JxlMemoryManager* memory_manager = this->memory_manager();
size_t num_c = 3 + frame_header.nonserialized_metadata->m.num_extra_channels;
bool render_noise =
(options.render_noise && (frame_header.flags & FrameHeader::kNoise) != 0);
size_t num_tmp_c = render_noise ? 3 : 0;
if (frame_header.CanBeReferenced()) {
// Necessary so that SetInputSizes() can allocate output buffers as needed.
frame_storage_for_referencing = ImageBundle(memory_manager, metadata);
}
RenderPipeline::Builder builder(memory_manager, num_c + num_tmp_c);
if (options.use_slow_render_pipeline) {
builder.UseSimpleImplementation();
}
if (!frame_header.chroma_subsampling.Is444()) {
for (size_t c = 0; c < 3; c++) {
if (frame_header.chroma_subsampling.HShift(c) != 0) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true)));
}
if (frame_header.chroma_subsampling.VShift(c) != 0) {
JXL_RETURN_IF_ERROR(builder.AddStage(
GetChromaUpsamplingStage(c, /*horizontal=*/false)));
}
}
}
if (frame_header.loop_filter.gab) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetGaborishStage(frame_header.loop_filter)));
}
{
const LoopFilter& lf = frame_header.loop_filter;
if (lf.epf_iters >= 3) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Zero)));
}
if (lf.epf_iters >= 1) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetEPFStage(lf, sigma, EpfStage::One)));
}
if (lf.epf_iters >= 2) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Two)));
}
}
bool late_ec_upsample = frame_header.upsampling != 1;
for (auto ecups : frame_header.extra_channel_upsampling) {
if (ecups != frame_header.upsampling) {
// If patches are applied, either frame_header.upsampling == 1 or
// late_ec_upsample is true.
late_ec_upsample = false;
}
}
if (!late_ec_upsample) {
for (size_t ec = 0; ec < frame_header.extra_channel_upsampling.size();
ec++) {
if (frame_header.extra_channel_upsampling[ec] != 1) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
frame_header.nonserialized_metadata->transform_data, 3 + ec,
CeilLog2Nonzero(frame_header.extra_channel_upsampling[ec]))));
}
}
}
if ((frame_header.flags & FrameHeader::kPatches) != 0) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetPatchesStage(
&shared->image_features.patches,
&frame_header.nonserialized_metadata->m.extra_channel_info)));
}
if ((frame_header.flags & FrameHeader::kSplines) != 0) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetSplineStage(&shared->image_features.splines)));
}
if (frame_header.upsampling != 1) {
size_t nb_channels =
3 +
(late_ec_upsample ? frame_header.extra_channel_upsampling.size() : 0);
for (size_t c = 0; c < nb_channels; c++) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
frame_header.nonserialized_metadata->transform_data, c,
CeilLog2Nonzero(frame_header.upsampling))));
}
}
if (render_noise) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetConvolveNoiseStage(num_c)));
JXL_RETURN_IF_ERROR(builder.AddStage(GetAddNoiseStage(
shared->image_features.noise_params, shared->cmap.base(), num_c)));
}
if (frame_header.dc_level != 0) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImage3FStage(
memory_manager, &shared_storage.dc_frames[frame_header.dc_level - 1])));
}
if (frame_header.CanBeReferenced() &&
frame_header.save_before_color_transform) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
&frame_storage_for_referencing, output_encoding_info)));
}
bool has_alpha = false;
size_t alpha_c = 0;
for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
if (metadata->extra_channel_info[i].type == ExtraChannel::kAlpha) {
has_alpha = true;
alpha_c = 3 + i;
break;
}
}
if (fast_xyb_srgb8_conversion) {
#if !JXL_HIGH_PRECISION
JXL_ENSURE(!NeedsBlending(frame_header));
JXL_ENSURE(!frame_header.CanBeReferenced() ||
frame_header.save_before_color_transform);
JXL_ENSURE(!options.render_spotcolors ||
!metadata->Find(ExtraChannel::kSpotColor));
bool is_rgba = (main_output.format.num_channels == 4);
uint8_t* rgb_output = reinterpret_cast<uint8_t*>(main_output.buffer);
JXL_RETURN_IF_ERROR(builder.AddStage(
GetFastXYBTosRGB8Stage(rgb_output, main_output.stride, width, height,
is_rgba, has_alpha, alpha_c)));
#endif
} else {
bool linear = false;
if (frame_header.color_transform == ColorTransform::kYCbCr) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetYCbCrStage()));
} else if (frame_header.color_transform == ColorTransform::kXYB) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetXYBStage(output_encoding_info)));
if (output_encoding_info.color_encoding.GetColorSpace() !=
ColorSpace::kXYB) {
linear = true;
}
} // Nothing to do for kNone.
if (options.coalescing && NeedsBlending(frame_header)) {
if (linear) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetFromLinearStage(output_encoding_info)));
linear = false;
}
JXL_RETURN_IF_ERROR(builder.AddStage(GetBlendingStage(
frame_header, this, output_encoding_info.color_encoding)));
}
if (options.coalescing && frame_header.CanBeReferenced() &&
!frame_header.save_before_color_transform) {
if (linear) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetFromLinearStage(output_encoding_info)));
linear = false;
}
JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
&frame_storage_for_referencing, output_encoding_info)));
}
if (options.render_spotcolors &&
frame_header.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) {
for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
// Don't use Find() because there may be multiple spot color channels.
const ExtraChannelInfo& eci = metadata->extra_channel_info[i];
if (eci.type == ExtraChannel::kSpotColor) {
JXL_RETURN_IF_ERROR(
builder.AddStage(GetSpotColorStage(i, eci.spot_color)));
}
}
}
auto tone_mapping_stage = GetToneMappingStage(output_encoding_info);
if (tone_mapping_stage) {
if (!linear) {
auto to_linear_stage = GetToLinearStage(output_encoding_info);
if (!to_linear_stage) {
if (!output_encoding_info.cms_set) {
return JXL_FAILURE("Cannot tonemap this colorspace without a CMS");
}
auto cms_stage = GetCmsStage(output_encoding_info);
if (cms_stage) {
JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
}
} else {
JXL_RETURN_IF_ERROR(builder.AddStage(std::move(to_linear_stage)));
}
linear = true;
}
JXL_RETURN_IF_ERROR(builder.AddStage(std::move(tone_mapping_stage)));
}
if (linear) {
const size_t channels_src =
(output_encoding_info.orig_color_encoding.IsCMYK()
? 4
: output_encoding_info.orig_color_encoding.Channels());
const size_t channels_dst =
output_encoding_info.color_encoding.Channels();
bool mixing_color_and_grey = (channels_dst != channels_src);
if ((output_encoding_info.color_encoding_is_original) ||
(!output_encoding_info.cms_set) || mixing_color_and_grey) {
// in those cases we only need a linear stage in other cases we attempt
// to obtain a cms stage: the cases are
// - output_encoding_info.color_encoding_is_original: no cms stage
// needed because it would be a no-op
// - !output_encoding_info.cms_set: can't use the cms, so no point in
// trying to add a cms stage
// - mixing_color_and_grey: cms stage can't handle that
// TODO(firsching): remove "mixing_color_and_grey" condition after
// adding support for greyscale to cms stage.
JXL_RETURN_IF_ERROR(
builder.AddStage(GetFromLinearStage(output_encoding_info)));
} else {
if (!output_encoding_info.linear_color_encoding.CreateICC()) {
return JXL_FAILURE("Failed to create ICC");
}
auto cms_stage = GetCmsStage(output_encoding_info);
if (cms_stage) {
JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
}
}
linear = false;
}
(void)linear;
if (main_output.callback.IsPresent() || main_output.buffer) {
JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToOutputStage(
main_output, width, height, has_alpha, unpremul_alpha, alpha_c,
undo_orientation, extra_output, memory_manager)));
} else {
JXL_RETURN_IF_ERROR(builder.AddStage(
GetWriteToImageBundleStage(decoded, output_encoding_info)));
}
}
JXL_ASSIGN_OR_RETURN(render_pipeline,
std::move(builder).Finalize(shared->frame_dim));
return render_pipeline->IsInitialized();
}
} // namespace jxl