// 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/headers.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/common.h" #include "lib/jxl/fields.h" namespace jxl { namespace { struct Rational { constexpr explicit Rational(uint32_t num, uint32_t den) : num(num), den(den) {} // Returns floor(multiplicand * rational). constexpr uint32_t MulTruncate(uint32_t multiplicand) const { return uint64_t(multiplicand) * num / den; } uint32_t num; uint32_t den; }; Rational FixedAspectRatios(uint32_t ratio) { JXL_ASSERT(0 != ratio && ratio < 8); // Other candidates: 5/4, 7/5, 14/9, 16/10, 5/3, 21/9, 12/5 constexpr Rational kRatios[7] = {Rational(1, 1), // square Rational(12, 10), // Rational(4, 3), // camera Rational(3, 2), // mobile camera Rational(16, 9), // camera/display Rational(5, 4), // Rational(2, 1)}; // return kRatios[ratio - 1]; } uint32_t FindAspectRatio(uint32_t xsize, uint32_t ysize) { for (uint32_t r = 1; r < 8; ++r) { if (xsize == FixedAspectRatios(r).MulTruncate(ysize)) { return r; } } return 0; // Must send xsize instead } } // namespace size_t SizeHeader::xsize() const { if (ratio_ != 0) { return FixedAspectRatios(ratio_).MulTruncate( static_cast(ysize())); } return small_ ? ((xsize_div8_minus_1_ + 1) * 8) : xsize_; } Status SizeHeader::Set(size_t xsize64, size_t ysize64) { if (xsize64 > 0xFFFFFFFFull || ysize64 > 0xFFFFFFFFull) { return JXL_FAILURE("Image too large"); } const uint32_t xsize32 = static_cast(xsize64); const uint32_t ysize32 = static_cast(ysize64); if (xsize64 == 0 || ysize64 == 0) return JXL_FAILURE("Empty image"); ratio_ = FindAspectRatio(xsize32, ysize32); small_ = ysize64 <= 256 && (ysize64 % kBlockDim) == 0 && (ratio_ != 0 || (xsize64 <= 256 && (xsize64 % kBlockDim) == 0)); if (small_) { ysize_div8_minus_1_ = ysize32 / 8 - 1; } else { ysize_ = ysize32; } if (ratio_ == 0) { if (small_) { xsize_div8_minus_1_ = xsize32 / 8 - 1; } else { xsize_ = xsize32; } } JXL_ASSERT(xsize() == xsize64); JXL_ASSERT(ysize() == ysize64); return true; } Status PreviewHeader::Set(size_t xsize64, size_t ysize64) { const uint32_t xsize32 = static_cast(xsize64); const uint32_t ysize32 = static_cast(ysize64); if (xsize64 == 0 || ysize64 == 0) return JXL_FAILURE("Empty preview"); div8_ = (xsize64 % kBlockDim) == 0 && (ysize64 % kBlockDim) == 0; if (div8_) { ysize_div8_ = ysize32 / 8; } else { ysize_ = ysize32; } ratio_ = FindAspectRatio(xsize32, ysize32); if (ratio_ == 0) { if (div8_) { xsize_div8_ = xsize32 / 8; } else { xsize_ = xsize32; } } JXL_ASSERT(xsize() == xsize64); JXL_ASSERT(ysize() == ysize64); return true; } size_t PreviewHeader::xsize() const { if (ratio_ != 0) { return FixedAspectRatios(ratio_).MulTruncate( static_cast(ysize())); } return div8_ ? (xsize_div8_ * 8) : xsize_; } SizeHeader::SizeHeader() { Bundle::Init(this); } Status SizeHeader::VisitFields(Visitor* JXL_RESTRICT visitor) { JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &small_)); if (visitor->Conditional(small_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(5, 0, &ysize_div8_minus_1_)); } if (visitor->Conditional(!small_)) { // (Could still be small, but non-multiple of 8.) JXL_QUIET_RETURN_IF_ERROR(visitor->U32(BitsOffset(9, 1), BitsOffset(13, 1), BitsOffset(18, 1), BitsOffset(30, 1), 1, &ysize_)); } JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(3, 0, &ratio_)); if (visitor->Conditional(ratio_ == 0 && small_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(5, 0, &xsize_div8_minus_1_)); } if (visitor->Conditional(ratio_ == 0 && !small_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(BitsOffset(9, 1), BitsOffset(13, 1), BitsOffset(18, 1), BitsOffset(30, 1), 1, &xsize_)); } return true; } PreviewHeader::PreviewHeader() { Bundle::Init(this); } Status PreviewHeader::VisitFields(Visitor* JXL_RESTRICT visitor) { JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &div8_)); if (visitor->Conditional(div8_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(Val(16), Val(32), BitsOffset(5, 1), BitsOffset(9, 33), 1, &ysize_div8_)); } if (visitor->Conditional(!div8_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(BitsOffset(6, 1), BitsOffset(8, 65), BitsOffset(10, 321), BitsOffset(12, 1345), 1, &ysize_)); } JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(3, 0, &ratio_)); if (visitor->Conditional(ratio_ == 0 && div8_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(Val(16), Val(32), BitsOffset(5, 1), BitsOffset(9, 33), 1, &xsize_div8_)); } if (visitor->Conditional(ratio_ == 0 && !div8_)) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(BitsOffset(6, 1), BitsOffset(8, 65), BitsOffset(10, 321), BitsOffset(12, 1345), 1, &xsize_)); } return true; } AnimationHeader::AnimationHeader() { Bundle::Init(this); } Status AnimationHeader::VisitFields(Visitor* JXL_RESTRICT visitor) { JXL_QUIET_RETURN_IF_ERROR(visitor->U32(Val(100), Val(1000), BitsOffset(10, 1), BitsOffset(30, 1), 1, &tps_numerator)); JXL_QUIET_RETURN_IF_ERROR(visitor->U32(Val(1), Val(1001), BitsOffset(8, 1), BitsOffset(10, 1), 1, &tps_denominator)); JXL_QUIET_RETURN_IF_ERROR( visitor->U32(Val(0), Bits(3), Bits(16), Bits(32), 0, &num_loops)); JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &have_timecodes)); return true; } Status ReadSizeHeader(BitReader* JXL_RESTRICT reader, SizeHeader* JXL_RESTRICT size) { return Bundle::Read(reader, size); } Status WriteSizeHeader(const SizeHeader& size, BitWriter* JXL_RESTRICT writer, size_t layer, AuxOut* aux_out) { const size_t max_bits = Bundle::MaxBits(size); if (max_bits != SizeHeader::kMaxBits) { JXL_ABORT("Please update SizeHeader::kMaxBits from %" PRIuS " to %" PRIuS "\n", SizeHeader::kMaxBits, max_bits); } // Only check the number of non-extension bits (extensions are unbounded). // (Bundle::Write will call CanEncode again, but it is fast because SizeHeader // is tiny.) size_t extension_bits, total_bits; JXL_RETURN_IF_ERROR(Bundle::CanEncode(size, &extension_bits, &total_bits)); JXL_ASSERT(total_bits - extension_bits < SizeHeader::kMaxBits); return Bundle::Write(size, writer, layer, aux_out); } } // namespace jxl