diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
commit | fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch) | |
tree | 4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc | |
parent | Releasing progress-linux version 124.0.1-1~progress7.99u1. (diff) | |
download | firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc')
-rw-r--r-- | third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc | 227 |
1 files changed, 133 insertions, 94 deletions
diff --git a/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc b/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc index ae4cd3bd3b..4c2ddba95b 100644 --- a/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc +++ b/third_party/jpeg-xl/lib/jxl/enc_adaptive_quantization.cc @@ -9,6 +9,7 @@ #include <stdlib.h> #include <algorithm> +#include <atomic> #include <cmath> #include <string> #include <vector> @@ -26,8 +27,6 @@ #include "lib/jxl/base/status.h" #include "lib/jxl/butteraugli/butteraugli.h" #include "lib/jxl/cms/opsin_params.h" -#include "lib/jxl/coeff_order_fwd.h" -#include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/convolve.h" #include "lib/jxl/dec_cache.h" #include "lib/jxl/dec_group.h" @@ -102,12 +101,12 @@ V ComputeMask(const D d, const V out_val) { } // mul and mul2 represent a scaling difference between jxl and butteraugli. -static const float kSGmul = 226.77216153508914f; -static const float kSGmul2 = 1.0f / 73.377132366608819f; -static const float kLog2 = 0.693147181f; +const float kSGmul = 226.77216153508914f; +const float kSGmul2 = 1.0f / 73.377132366608819f; +const float kLog2 = 0.693147181f; // Includes correction factor for std::log -> log2. -static const float kSGRetMul = kSGmul2 * 18.6580932135f * kLog2; -static const float kSGVOffset = 7.7825991679894591f; +const float kSGRetMul = kSGmul2 * 18.6580932135f * kLog2; +const float kSGVOffset = 7.7825991679894591f; template <bool invert, typename D, typename V> V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) { @@ -131,7 +130,7 @@ V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) { } template <bool invert = false> -static float RatioOfDerivativesOfCubicRootToSimpleGamma(float v) { +float RatioOfDerivativesOfCubicRootToSimpleGamma(float v) { using DScalar = HWY_CAPPED(float, 1); auto vscalar = Load(DScalar(), &v); return GetLane( @@ -396,12 +395,16 @@ void FuzzyErosion(const float butteraugli_target, const Rect& from_rect, } struct AdaptiveQuantizationImpl { - void PrepareBuffers(size_t num_threads) { - diff_buffer = ImageF(kEncTileDim + 8, num_threads); + Status PrepareBuffers(size_t num_threads) { + JXL_ASSIGN_OR_RETURN(diff_buffer, + ImageF::Create(kEncTileDim + 8, num_threads)); for (size_t i = pre_erosion.size(); i < num_threads; i++) { - pre_erosion.emplace_back(kEncTileDimInBlocks * 2 + 2, - kEncTileDimInBlocks * 2 + 2); + JXL_ASSIGN_OR_RETURN(ImageF tmp, + ImageF::Create(kEncTileDimInBlocks * 2 + 2, + kEncTileDimInBlocks * 2 + 2)); + pre_erosion.emplace_back(std::move(tmp)); } + return true; } void ComputeTile(float butteraugli_target, float scale, const Image3F& xyb, @@ -568,8 +571,7 @@ struct AdaptiveQuantizationImpl { ImageF diff_buffer; }; -static void Blur1x1Masking(ThreadPool* pool, ImageF* mask1x1, - const Rect& rect) { +Status Blur1x1Masking(ThreadPool* pool, ImageF* mask1x1, const Rect& rect) { // Blur the mask1x1 to obtain the masking image. // Before blurring it contains an image of absolute value of the // Laplacian of the intensity channel. @@ -595,30 +597,30 @@ static void Blur1x1Masking(ThreadPool* pool, ImageF* mask1x1, {HWY_REP4(normalize_mul * kFilterMask1x1[1])}, {HWY_REP4(normalize_mul * kFilterMask1x1[4])}, {HWY_REP4(normalize_mul * kFilterMask1x1[3])}}; - ImageF temp(rect.xsize(), rect.ysize()); + JXL_ASSIGN_OR_RETURN(ImageF temp, ImageF::Create(rect.xsize(), rect.ysize())); Symmetric5(*mask1x1, rect, weights, pool, &temp); *mask1x1 = std::move(temp); + return true; } -ImageF AdaptiveQuantizationMap(const float butteraugli_target, - const Image3F& xyb, const Rect& rect, - float scale, ThreadPool* pool, ImageF* mask, - ImageF* mask1x1) { +StatusOr<ImageF> AdaptiveQuantizationMap(const float butteraugli_target, + const Image3F& xyb, const Rect& rect, + float scale, ThreadPool* pool, + ImageF* mask, ImageF* mask1x1) { JXL_DASSERT(rect.xsize() % kBlockDim == 0); JXL_DASSERT(rect.ysize() % kBlockDim == 0); AdaptiveQuantizationImpl impl; const size_t xsize_blocks = rect.xsize() / kBlockDim; const size_t ysize_blocks = rect.ysize() / kBlockDim; - impl.aq_map = ImageF(xsize_blocks, ysize_blocks); - *mask = ImageF(xsize_blocks, ysize_blocks); - *mask1x1 = ImageF(xyb.xsize(), xyb.ysize()); + JXL_ASSIGN_OR_RETURN(impl.aq_map, ImageF::Create(xsize_blocks, ysize_blocks)); + JXL_ASSIGN_OR_RETURN(*mask, ImageF::Create(xsize_blocks, ysize_blocks)); + JXL_ASSIGN_OR_RETURN(*mask1x1, ImageF::Create(xyb.xsize(), xyb.ysize())); JXL_CHECK(RunOnPool( pool, 0, DivCeil(xsize_blocks, kEncTileDimInBlocks) * DivCeil(ysize_blocks, kEncTileDimInBlocks), [&](const size_t num_threads) { - impl.PrepareBuffers(num_threads); - return true; + return !!impl.PrepareBuffers(num_threads); }, [&](const uint32_t tid, const size_t thread) { size_t n_enc_tiles = DivCeil(xsize_blocks, kEncTileDimInBlocks); @@ -634,7 +636,7 @@ ImageF AdaptiveQuantizationMap(const float butteraugli_target, }, "AQ DiffPrecompute")); - Blur1x1Masking(pool, mask1x1, rect); + JXL_RETURN_IF_ERROR(Blur1x1Masking(pool, mask1x1, rect)); return std::move(impl).aq_map; } @@ -654,24 +656,28 @@ namespace { // If true, prints the quantization maps at each iteration. constexpr bool FLAGS_dump_quant_state = false; -void DumpHeatmap(const CompressParams& cparams, const AuxOut* aux_out, - const std::string& label, const ImageF& image, - float good_threshold, float bad_threshold) { +Status DumpHeatmap(const CompressParams& cparams, const AuxOut* aux_out, + const std::string& label, const ImageF& image, + float good_threshold, float bad_threshold) { if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) { - Image3F heatmap = CreateHeatMapImage(image, good_threshold, bad_threshold); + JXL_ASSIGN_OR_RETURN( + Image3F heatmap, + CreateHeatMapImage(image, good_threshold, bad_threshold)); char filename[200]; snprintf(filename, sizeof(filename), "%s%05d", label.c_str(), aux_out->num_butteraugli_iters); - DumpImage(cparams, filename, heatmap); + JXL_RETURN_IF_ERROR(DumpImage(cparams, filename, heatmap)); } + return true; } -void DumpHeatmaps(const CompressParams& cparams, const AuxOut* aux_out, - float ba_target, const ImageF& quant_field, - const ImageF& tile_heatmap, const ImageF& bt_diffmap) { +Status DumpHeatmaps(const CompressParams& cparams, const AuxOut* aux_out, + float ba_target, const ImageF& quant_field, + const ImageF& tile_heatmap, const ImageF& bt_diffmap) { if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) { - if (!WantDebugOutput(cparams)) return; - ImageF inv_qmap(quant_field.xsize(), quant_field.ysize()); + if (!WantDebugOutput(cparams)) return true; + JXL_ASSIGN_OR_RETURN(ImageF inv_qmap, ImageF::Create(quant_field.xsize(), + quant_field.ysize())); for (size_t y = 0; y < quant_field.ysize(); ++y) { const float* JXL_RESTRICT row_q = quant_field.ConstRow(y); float* JXL_RESTRICT row_inv_q = inv_qmap.Row(y); @@ -679,21 +685,24 @@ void DumpHeatmaps(const CompressParams& cparams, const AuxOut* aux_out, row_inv_q[x] = 1.0f / row_q[x]; // never zero } } - DumpHeatmap(cparams, aux_out, "quant_heatmap", inv_qmap, 4.0f * ba_target, - 6.0f * ba_target); - DumpHeatmap(cparams, aux_out, "tile_heatmap", tile_heatmap, ba_target, - 1.5f * ba_target); + JXL_RETURN_IF_ERROR(DumpHeatmap(cparams, aux_out, "quant_heatmap", inv_qmap, + 4.0f * ba_target, 6.0f * ba_target)); + JXL_RETURN_IF_ERROR(DumpHeatmap(cparams, aux_out, "tile_heatmap", + tile_heatmap, ba_target, 1.5f * ba_target)); // matches heat maps produced by the command line tool. - DumpHeatmap(cparams, aux_out, "bt_diffmap", bt_diffmap, - ButteraugliFuzzyInverse(1.5), ButteraugliFuzzyInverse(0.5)); + JXL_RETURN_IF_ERROR(DumpHeatmap(cparams, aux_out, "bt_diffmap", bt_diffmap, + ButteraugliFuzzyInverse(1.5), + ButteraugliFuzzyInverse(0.5))); } + return true; } -ImageF TileDistMap(const ImageF& distmap, int tile_size, int margin, - const AcStrategyImage& ac_strategy) { +StatusOr<ImageF> TileDistMap(const ImageF& distmap, int tile_size, int margin, + const AcStrategyImage& ac_strategy) { const int tile_xsize = (distmap.xsize() + tile_size - 1) / tile_size; const int tile_ysize = (distmap.ysize() + tile_size - 1) / tile_size; - ImageF tile_distmap(tile_xsize, tile_ysize); + JXL_ASSIGN_OR_RETURN(ImageF tile_distmap, + ImageF::Create(tile_xsize, tile_ysize)); size_t distmap_stride = tile_distmap.PixelsPerRow(); for (int tile_y = 0; tile_y < tile_ysize; ++tile_y) { AcStrategyRow ac_strategy_row = ac_strategy.ConstRow(tile_y); @@ -754,14 +763,16 @@ ImageF TileDistMap(const ImageF& distmap, int tile_size, int margin, return tile_distmap; } -static const float kDcQuantPow = 0.83f; -static const float kDcQuant = 1.095924047623553f; -static const float kAcQuant = 0.7381485255235064f; +const float kDcQuantPow = 0.83f; +const float kDcQuant = 1.095924047623553f; +const float kAcQuant = 0.7381485255235064f; // Computes the decoded image for a given set of compression parameters. -ImageBundle RoundtripImage(const FrameHeader& frame_header, - const Image3F& opsin, PassesEncoderState* enc_state, - const JxlCmsInterface& cms, ThreadPool* pool) { +StatusOr<ImageBundle> RoundtripImage(const FrameHeader& frame_header, + const Image3F& opsin, + PassesEncoderState* enc_state, + const JxlCmsInterface& cms, + ThreadPool* pool) { std::unique_ptr<PassesDecoderState> dec_state = jxl::make_unique<PassesDecoderState>(); JXL_CHECK(dec_state->output_encoding_info.SetFromMetadata( @@ -775,7 +786,8 @@ ImageBundle RoundtripImage(const FrameHeader& frame_header, size_t num_special_frames = enc_state->special_frames.size(); size_t num_passes = enc_state->progressive_splitter.GetNumPasses(); - ModularFrameEncoder modular_frame_encoder(frame_header, enc_state->cparams); + ModularFrameEncoder modular_frame_encoder(frame_header, enc_state->cparams, + false); JXL_CHECK(InitializePassesEncoder(frame_header, opsin, Rect(opsin), cms, pool, enc_state, &modular_frame_encoder, nullptr)); @@ -784,7 +796,9 @@ ImageBundle RoundtripImage(const FrameHeader& frame_header, ImageBundle decoded(&enc_state->shared.metadata->m); decoded.origin = frame_header.frame_origin; - decoded.SetFromImage(Image3F(opsin.xsize(), opsin.ysize()), + JXL_ASSIGN_OR_RETURN(Image3F tmp, + Image3F::Create(opsin.xsize(), opsin.ysize())); + decoded.SetFromImage(std::move(tmp), dec_state->output_encoding_info.color_encoding); PassesDecoderState::PipelineOptions options; @@ -806,8 +820,10 @@ ImageBundle RoundtripImage(const FrameHeader& frame_header, group_dec_caches = hwy::MakeUniqueAlignedArray<GroupDecCache>(num_threads); return true; }; + std::atomic<bool> has_error{false}; const auto process_group = [&](const uint32_t group_index, const size_t thread) { + if (has_error) return; if (frame_header.loop_filter.epf_iters > 0) { ComputeSigma(frame_header.loop_filter, dec_state->shared->frame_dim.BlockGroupRect(group_index), @@ -822,10 +838,14 @@ ImageBundle RoundtripImage(const FrameHeader& frame_header, std::pair<ImageF*, Rect> ri = input.GetBuffer(3 + c); FillPlane(0.0f, ri.first, ri.second); } - input.Done(); + if (!input.Done()) { + has_error = true; + return; + } }; JXL_CHECK(RunOnPool(pool, 0, num_groups, allocate_storage, process_group, "AQ loop")); + if (has_error) return JXL_FAILURE("AQ loop failure"); // Ensure we don't create any new special frames. enc_state->special_frames.resize(num_special_frames); @@ -835,18 +855,18 @@ ImageBundle RoundtripImage(const FrameHeader& frame_header, constexpr int kMaxButteraugliIters = 4; -void FindBestQuantization(const FrameHeader& frame_header, - const Image3F& linear, const Image3F& opsin, - ImageF& quant_field, PassesEncoderState* enc_state, - const JxlCmsInterface& cms, ThreadPool* pool, - AuxOut* aux_out) { +Status FindBestQuantization(const FrameHeader& frame_header, + const Image3F& linear, const Image3F& opsin, + ImageF& quant_field, PassesEncoderState* enc_state, + const JxlCmsInterface& cms, ThreadPool* pool, + AuxOut* aux_out) { const CompressParams& cparams = enc_state->cparams; if (cparams.resampling > 1 && cparams.original_butteraugli_distance <= 4.0 * cparams.resampling) { // For downsampled opsin image, the butteraugli based adaptive quantization // loop would only make the size bigger without improving the distance much, // so in this case we enable it only for very high butteraugli targets. - return; + return true; } Quantizer& quantizer = enc_state->shared.quantizer; ImageI& raw_quant_field = enc_state->shared.raw_quant_field; @@ -863,10 +883,13 @@ void FindBestQuantization(const FrameHeader& frame_header, AdjustQuantField(enc_state->shared.ac_strategy, Rect(quant_field), original_butteraugli, &quant_field); ImageF tile_distmap; - ImageF initial_quant_field(quant_field.xsize(), quant_field.ysize()); + JXL_ASSIGN_OR_RETURN( + ImageF initial_quant_field, + ImageF::Create(quant_field.xsize(), quant_field.ysize())); CopyImageTo(quant_field, &initial_quant_field); - float initial_qf_min, initial_qf_max; + float initial_qf_min; + float initial_qf_max; ImageMinMax(initial_quant_field, &initial_qf_min, &initial_qf_max); float initial_qf_ratio = initial_qf_max / initial_qf_min; float qf_max_deviation_low = std::sqrt(250 / initial_qf_ratio); @@ -893,8 +916,9 @@ void FindBestQuantization(const FrameHeader& frame_header, } } quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field); - ImageBundle dec_linear = - RoundtripImage(frame_header, opsin, enc_state, cms, pool); + JXL_ASSIGN_OR_RETURN( + ImageBundle dec_linear, + RoundtripImage(frame_header, opsin, enc_state, cms, pool)); float score; ImageF diffmap; JXL_CHECK(comparator.CompareWith(dec_linear, &diffmap, &score)); @@ -902,16 +926,19 @@ void FindBestQuantization(const FrameHeader& frame_header, score = -score; ScaleImage(-1.0f, &diffmap); } - tile_distmap = TileDistMap(diffmap, 8 * cparams.resampling, 0, - enc_state->shared.ac_strategy); + JXL_ASSIGN_OR_RETURN(tile_distmap, + TileDistMap(diffmap, 8 * cparams.resampling, 0, + enc_state->shared.ac_strategy)); if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && WantDebugOutput(cparams)) { - DumpImage(cparams, ("dec" + ToString(i)).c_str(), *dec_linear.color()); - DumpHeatmaps(cparams, aux_out, butteraugli_target, quant_field, - tile_distmap, diffmap); + JXL_RETURN_IF_ERROR(DumpImage(cparams, ("dec" + ToString(i)).c_str(), + *dec_linear.color())); + JXL_RETURN_IF_ERROR(DumpHeatmaps(cparams, aux_out, butteraugli_target, + quant_field, tile_distmap, diffmap)); } if (aux_out != nullptr) ++aux_out->num_butteraugli_iters; if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) { - float minval, maxval; + float minval; + float maxval; ImageMinMax(quant_field, &minval, &maxval); printf("\nButteraugli iter: %d/%d\n", i, kMaxButteraugliIters); printf("Butteraugli distance: %f (target = %f)\n", score, @@ -967,8 +994,10 @@ void FindBestQuantization(const FrameHeader& frame_header, if (diff > 1.0f) { float old = row_q[x]; row_q[x] *= diff; - int qf_old = old * quantizer.InvGlobalScale() + 0.5; - int qf_new = row_q[x] * quantizer.InvGlobalScale() + 0.5; + int qf_old = + static_cast<int>(std::lround(old * quantizer.InvGlobalScale())); + int qf_new = static_cast<int>( + std::lround(row_q[x] * quantizer.InvGlobalScale())); if (qf_old == qf_new) { row_q[x] = old + quantizer.Scale(); } @@ -988,8 +1017,10 @@ void FindBestQuantization(const FrameHeader& frame_header, } else { float old = row_q[x]; row_q[x] *= diff; - int qf_old = old * quantizer.InvGlobalScale() + 0.5; - int qf_new = row_q[x] * quantizer.InvGlobalScale() + 0.5; + int qf_old = + static_cast<int>(std::lround(old * quantizer.InvGlobalScale())); + int qf_new = static_cast<int>( + std::lround(row_q[x] * quantizer.InvGlobalScale())); if (qf_old == qf_new) { row_q[x] = old + quantizer.Scale(); } @@ -1001,13 +1032,14 @@ void FindBestQuantization(const FrameHeader& frame_header, } } quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field); + return true; } -void FindBestQuantizationMaxError(const FrameHeader& frame_header, - const Image3F& opsin, ImageF& quant_field, - PassesEncoderState* enc_state, - const JxlCmsInterface& cms, ThreadPool* pool, - AuxOut* aux_out) { +Status FindBestQuantizationMaxError(const FrameHeader& frame_header, + const Image3F& opsin, ImageF& quant_field, + PassesEncoderState* enc_state, + const JxlCmsInterface& cms, + ThreadPool* pool, AuxOut* aux_out) { // TODO(szabadka): Make this work for non-opsin color spaces. const CompressParams& cparams = enc_state->cparams; Quantizer& quantizer = enc_state->shared.quantizer; @@ -1026,12 +1058,15 @@ void FindBestQuantizationMaxError(const FrameHeader& frame_header, for (int i = 0; i < kMaxButteraugliIters + 1; ++i) { quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field); if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && aux_out) { - DumpXybImage(cparams, ("ops" + ToString(i)).c_str(), opsin); + JXL_RETURN_IF_ERROR( + DumpXybImage(cparams, ("ops" + ToString(i)).c_str(), opsin)); } - ImageBundle decoded = - RoundtripImage(frame_header, opsin, enc_state, cms, pool); + JXL_ASSIGN_OR_RETURN( + ImageBundle decoded, + RoundtripImage(frame_header, opsin, enc_state, cms, pool)); if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && aux_out) { - DumpXybImage(cparams, ("dec" + ToString(i)).c_str(), *decoded.color()); + JXL_RETURN_IF_ERROR(DumpXybImage(cparams, ("dec" + ToString(i)).c_str(), + *decoded.color())); } for (size_t by = 0; by < enc_state->shared.frame_dim.ysize_blocks; by++) { AcStrategyRow ac_strategy_row = @@ -1073,6 +1108,7 @@ void FindBestQuantizationMaxError(const FrameHeader& frame_header, } } quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field); + return true; } } // namespace @@ -1142,28 +1178,31 @@ float InitialQuantDC(float butteraugli_target) { return std::min(kDcQuant / butteraugli_target_dc, 50.f); } -ImageF InitialQuantField(const float butteraugli_target, const Image3F& opsin, - const Rect& rect, ThreadPool* pool, float rescale, - ImageF* mask, ImageF* mask1x1) { +StatusOr<ImageF> InitialQuantField(const float butteraugli_target, + const Image3F& opsin, const Rect& rect, + ThreadPool* pool, float rescale, + ImageF* mask, ImageF* mask1x1) { const float quant_ac = kAcQuant / butteraugli_target; return HWY_DYNAMIC_DISPATCH(AdaptiveQuantizationMap)( butteraugli_target, opsin, rect, quant_ac * rescale, pool, mask, mask1x1); } -void FindBestQuantizer(const FrameHeader& frame_header, const Image3F* linear, - const Image3F& opsin, ImageF& quant_field, - PassesEncoderState* enc_state, - const JxlCmsInterface& cms, ThreadPool* pool, - AuxOut* aux_out, double rescale) { +Status FindBestQuantizer(const FrameHeader& frame_header, const Image3F* linear, + const Image3F& opsin, ImageF& quant_field, + PassesEncoderState* enc_state, + const JxlCmsInterface& cms, ThreadPool* pool, + AuxOut* aux_out, double rescale) { const CompressParams& cparams = enc_state->cparams; if (cparams.max_error_mode) { - FindBestQuantizationMaxError(frame_header, opsin, quant_field, enc_state, - cms, pool, aux_out); + JXL_RETURN_IF_ERROR(FindBestQuantizationMaxError( + frame_header, opsin, quant_field, enc_state, cms, pool, aux_out)); } else if (linear && cparams.speed_tier <= SpeedTier::kKitten) { // Normal encoding to a butteraugli score. - FindBestQuantization(frame_header, *linear, opsin, quant_field, enc_state, - cms, pool, aux_out); + JXL_RETURN_IF_ERROR(FindBestQuantization(frame_header, *linear, opsin, + quant_field, enc_state, cms, pool, + aux_out)); } + return true; } } // namespace jxl |