libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

image.h (18162B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #ifndef LIB_JXL_IMAGE_H_
      7 #define LIB_JXL_IMAGE_H_
      8 
      9 // SIMD/multicore-friendly planar image representation with row accessors.
     10 
     11 #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
     12     defined(THREAD_SANITIZER)
     13 #include <inttypes.h>
     14 #endif
     15 
     16 #include <stddef.h>
     17 #include <stdint.h>
     18 #include <string.h>
     19 
     20 #include <algorithm>
     21 #include <sstream>
     22 #include <string>
     23 #include <utility>  // std::move
     24 
     25 #include "lib/jxl/base/compiler_specific.h"
     26 #include "lib/jxl/base/status.h"
     27 #include "lib/jxl/cache_aligned.h"
     28 
     29 namespace jxl {
     30 
     31 // DO NOT use PlaneBase outside of image.{h|cc}
     32 namespace detail {
     33 
     34 // Type-independent parts of Plane<> - reduces code duplication and facilitates
     35 // moving member function implementations to cc file.
     36 struct PlaneBase {
     37   PlaneBase()
     38       : xsize_(0),
     39         ysize_(0),
     40         orig_xsize_(0),
     41         orig_ysize_(0),
     42         bytes_per_row_(0),
     43         bytes_(nullptr),
     44         sizeof_t_(0) {}
     45 
     46   // Copy construction/assignment is forbidden to avoid inadvertent copies,
     47   // which can be very expensive. Use CopyImageTo() instead.
     48   PlaneBase(const PlaneBase& other) = delete;
     49   PlaneBase& operator=(const PlaneBase& other) = delete;
     50 
     51   // Move constructor (required for returning Image from function)
     52   PlaneBase(PlaneBase&& other) noexcept = default;
     53 
     54   // Move assignment (required for std::vector)
     55   PlaneBase& operator=(PlaneBase&& other) noexcept = default;
     56 
     57   void Swap(PlaneBase& other);
     58 
     59   // Useful for pre-allocating image with some padding for alignment purposes
     60   // and later reporting the actual valid dimensions. May also be used to
     61   // un-shrink the image. Caller is responsible for ensuring xsize/ysize are <=
     62   // the original dimensions.
     63   void ShrinkTo(const size_t xsize, const size_t ysize) {
     64     JXL_CHECK(xsize <= orig_xsize_);
     65     JXL_CHECK(ysize <= orig_ysize_);
     66     xsize_ = static_cast<uint32_t>(xsize);
     67     ysize_ = static_cast<uint32_t>(ysize);
     68     // NOTE: we can't recompute bytes_per_row for more compact storage and
     69     // better locality because that would invalidate the image contents.
     70   }
     71 
     72   // How many pixels.
     73   JXL_INLINE size_t xsize() const { return xsize_; }
     74   JXL_INLINE size_t ysize() const { return ysize_; }
     75 
     76   // NOTE: do not use this for copying rows - the valid xsize may be much less.
     77   JXL_INLINE size_t bytes_per_row() const { return bytes_per_row_; }
     78 
     79   // Raw access to byte contents, for interfacing with other libraries.
     80   // Unsigned char instead of char to avoid surprises (sign extension).
     81   JXL_INLINE uint8_t* bytes() {
     82     void* p = bytes_.get();
     83     return static_cast<uint8_t * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(p, 64));
     84   }
     85   JXL_INLINE const uint8_t* bytes() const {
     86     const void* p = bytes_.get();
     87     return static_cast<const uint8_t * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(p, 64));
     88   }
     89 
     90  protected:
     91   PlaneBase(size_t xsize, size_t ysize, size_t sizeof_t);
     92   Status Allocate();
     93 
     94   // Returns pointer to the start of a row.
     95   JXL_INLINE void* VoidRow(const size_t y) const {
     96 #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
     97     defined(THREAD_SANITIZER)
     98     if (y >= ysize_) {
     99       JXL_ABORT("Row(%" PRIu64 ") in (%u x %u) image\n",
    100                 static_cast<uint64_t>(y), xsize_, ysize_);
    101     }
    102 #endif
    103 
    104     void* row = bytes_.get() + y * bytes_per_row_;
    105     return JXL_ASSUME_ALIGNED(row, 64);
    106   }
    107 
    108   // (Members are non-const to enable assignment during move-assignment.)
    109   uint32_t xsize_;  // In valid pixels, not including any padding.
    110   uint32_t ysize_;
    111   uint32_t orig_xsize_;
    112   uint32_t orig_ysize_;
    113   size_t bytes_per_row_;  // Includes padding.
    114   CacheAlignedUniquePtr bytes_;
    115   size_t sizeof_t_;
    116 };
    117 
    118 }  // namespace detail
    119 
    120 // Single channel, aligned rows separated by padding. T must be POD.
    121 //
    122 // 'Single channel' (one 2D array per channel) simplifies vectorization
    123 // (repeating the same operation on multiple adjacent components) without the
    124 // complexity of a hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients
    125 // can easily iterate over all components in a row and Image requires no
    126 // knowledge of the pixel format beyond the component type "T".
    127 //
    128 // 'Aligned' means each row is aligned to the L1 cache line size. This prevents
    129 // false sharing between two threads operating on adjacent rows.
    130 //
    131 // 'Padding' is still relevant because vectors could potentially be larger than
    132 // a cache line. By rounding up row sizes to the vector size, we allow
    133 // reading/writing ALIGNED vectors whose first lane is a valid sample. This
    134 // avoids needing a separate loop to handle remaining unaligned lanes.
    135 //
    136 // This image layout could also be achieved with a vector and a row accessor
    137 // function, but a class wrapper with support for "deleter" allows wrapping
    138 // existing memory allocated by clients without copying the pixels. It also
    139 // provides convenient accessors for xsize/ysize, which shortens function
    140 // argument lists. Supports move-construction so it can be stored in containers.
    141 template <typename ComponentType>
    142 class Plane : public detail::PlaneBase {
    143  public:
    144   using T = ComponentType;
    145   static constexpr size_t kNumPlanes = 1;
    146 
    147   Plane() = default;
    148 
    149   static StatusOr<Plane> Create(const size_t xsize, const size_t ysize) {
    150     Plane plane(xsize, ysize, sizeof(T));
    151     JXL_RETURN_IF_ERROR(plane.Allocate());
    152     return plane;
    153   }
    154 
    155   JXL_INLINE T* Row(const size_t y) { return static_cast<T*>(VoidRow(y)); }
    156 
    157   // Returns pointer to const (see above).
    158   JXL_INLINE const T* Row(const size_t y) const {
    159     return static_cast<const T*>(VoidRow(y));
    160   }
    161 
    162   // Documents that the access is const.
    163   JXL_INLINE const T* ConstRow(const size_t y) const {
    164     return static_cast<const T*>(VoidRow(y));
    165   }
    166 
    167   // Returns number of pixels (some of which are padding) per row. Useful for
    168   // computing other rows via pointer arithmetic. WARNING: this must
    169   // NOT be used to determine xsize.
    170   JXL_INLINE intptr_t PixelsPerRow() const {
    171     return static_cast<intptr_t>(bytes_per_row_ / sizeof(T));
    172   }
    173 
    174  private:
    175   Plane(size_t xsize, size_t ysize, size_t sizeof_t)
    176       : detail::PlaneBase(xsize, ysize, sizeof_t) {}
    177 };
    178 
    179 using ImageSB = Plane<int8_t>;
    180 using ImageB = Plane<uint8_t>;
    181 using ImageS = Plane<int16_t>;  // signed integer or half-float
    182 using ImageU = Plane<uint16_t>;
    183 using ImageI = Plane<int32_t>;
    184 using ImageF = Plane<float>;
    185 using ImageD = Plane<double>;
    186 
    187 template <typename T>
    188 class Image3;
    189 
    190 // Rectangular region in image(s). Factoring this out of Image instead of
    191 // shifting the pointer by x0/y0 allows this to apply to multiple images with
    192 // different resolutions (e.g. color transform and quantization field).
    193 // Can compare using SameSize(rect1, rect2).
    194 template <typename T>
    195 class RectT {
    196  public:
    197   // Most windows are xsize_max * ysize_max, except those on the borders where
    198   // begin + size_max > end.
    199   constexpr RectT(T xbegin, T ybegin, size_t xsize_max, size_t ysize_max,
    200                   T xend, T yend)
    201       : x0_(xbegin),
    202         y0_(ybegin),
    203         xsize_(ClampedSize(xbegin, xsize_max, xend)),
    204         ysize_(ClampedSize(ybegin, ysize_max, yend)) {}
    205 
    206   // Construct with origin and known size (typically from another Rect).
    207   constexpr RectT(T xbegin, T ybegin, size_t xsize, size_t ysize)
    208       : x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {}
    209 
    210   // Construct a rect that covers a whole image/plane/ImageBundle etc.
    211   template <typename ImageT>
    212   explicit RectT(const ImageT& image)
    213       : RectT(0, 0, image.xsize(), image.ysize()) {}
    214 
    215   RectT() : RectT(0, 0, 0, 0) {}
    216 
    217   RectT(const RectT&) = default;
    218   RectT& operator=(const RectT&) = default;
    219 
    220   // Construct a subrect that resides in an image/plane/ImageBundle etc.
    221   template <typename ImageT>
    222   RectT Crop(const ImageT& image) const {
    223     return Intersection(RectT(image));
    224   }
    225 
    226   // Construct a subrect that resides in the [0, ysize) x [0, xsize) region of
    227   // the current rect.
    228   RectT Crop(size_t area_xsize, size_t area_ysize) const {
    229     return Intersection(RectT(0, 0, area_xsize, area_ysize));
    230   }
    231 
    232   // Returns a rect that only contains `num` lines with offset `y` from `y0()`.
    233   RectT Lines(size_t y, size_t num) const {
    234     JXL_DASSERT(y + num <= ysize_);
    235     return RectT(x0_, y0_ + y, xsize_, num);
    236   }
    237 
    238   RectT Line(size_t y) const { return Lines(y, 1); }
    239 
    240   JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const {
    241     return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_,
    242                  ysize_, std::min(x1(), other.x1()),
    243                  std::min(y1(), other.y1()));
    244   }
    245 
    246   JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset,
    247                                       int64_t y_offset) const {
    248     return RectT(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_);
    249   }
    250 
    251   template <typename V>
    252   V* Row(Plane<V>* image, size_t y) const {
    253     JXL_DASSERT(y + y0_ >= 0);
    254     return image->Row(y + y0_) + x0_;
    255   }
    256 
    257   template <typename V>
    258   const V* Row(const Plane<V>* image, size_t y) const {
    259     JXL_DASSERT(y + y0_ >= 0);
    260     return image->Row(y + y0_) + x0_;
    261   }
    262 
    263   template <typename V>
    264   V* PlaneRow(Image3<V>* image, const size_t c, size_t y) const {
    265     JXL_DASSERT(y + y0_ >= 0);
    266     return image->PlaneRow(c, y + y0_) + x0_;
    267   }
    268 
    269   template <typename V>
    270   const V* ConstRow(const Plane<V>& image, size_t y) const {
    271     JXL_DASSERT(y + y0_ >= 0);
    272     return image.ConstRow(y + y0_) + x0_;
    273   }
    274 
    275   template <typename V>
    276   const V* ConstPlaneRow(const Image3<V>& image, size_t c, size_t y) const {
    277     JXL_DASSERT(y + y0_ >= 0);
    278     return image.ConstPlaneRow(c, y + y0_) + x0_;
    279   }
    280 
    281   bool IsInside(const RectT& other) const {
    282     return x0_ >= other.x0() && x1() <= other.x1() && y0_ >= other.y0() &&
    283            y1() <= other.y1();
    284   }
    285 
    286   // Returns true if this Rect fully resides in the given image. ImageT could be
    287   // Plane<T> or Image3<T>; however if ImageT is Rect, results are nonsensical.
    288   template <class ImageT>
    289   bool IsInside(const ImageT& image) const {
    290     return IsInside(RectT(image));
    291   }
    292 
    293   T x0() const { return x0_; }
    294   T y0() const { return y0_; }
    295   size_t xsize() const { return xsize_; }
    296   size_t ysize() const { return ysize_; }
    297   T x1() const { return x0_ + xsize_; }
    298   T y1() const { return y0_ + ysize_; }
    299 
    300   RectT<T> ShiftLeft(size_t shiftx, size_t shifty) const {
    301     return RectT<T>(x0_ * (1 << shiftx), y0_ * (1 << shifty), xsize_ << shiftx,
    302                     ysize_ << shifty);
    303   }
    304   RectT<T> ShiftLeft(size_t shift) const { return ShiftLeft(shift, shift); }
    305 
    306   // Requires x0(), y0() to be multiples of 1<<shiftx, 1<<shifty.
    307   RectT<T> CeilShiftRight(size_t shiftx, size_t shifty) const {
    308     JXL_ASSERT(x0_ % (1 << shiftx) == 0);
    309     JXL_ASSERT(y0_ % (1 << shifty) == 0);
    310     return RectT<T>(x0_ / (1 << shiftx), y0_ / (1 << shifty),
    311                     DivCeil(xsize_, T{1} << shiftx),
    312                     DivCeil(ysize_, T{1} << shifty));
    313   }
    314   RectT<T> CeilShiftRight(std::pair<size_t, size_t> shift) const {
    315     return CeilShiftRight(shift.first, shift.second);
    316   }
    317   RectT<T> CeilShiftRight(size_t shift) const {
    318     return CeilShiftRight(shift, shift);
    319   }
    320 
    321   RectT<T> Extend(T border, RectT<T> parent) const {
    322     T new_x0 = x0() > parent.x0() + border ? x0() - border : parent.x0();
    323     T new_y0 = y0() > parent.y0() + border ? y0() - border : parent.y0();
    324     T new_x1 = x1() + border > parent.x1() ? parent.x1() : x1() + border;
    325     T new_y1 = y1() + border > parent.y1() ? parent.y1() : y1() + border;
    326     return RectT<T>(new_x0, new_y0, new_x1 - new_x0, new_y1 - new_y0);
    327   }
    328 
    329   template <typename U>
    330   RectT<U> As() const {
    331     return RectT<U>(static_cast<U>(x0_), static_cast<U>(y0_),
    332                     static_cast<U>(xsize_), static_cast<U>(ysize_));
    333   }
    334 
    335  private:
    336   // Returns size_max, or whatever is left in [begin, end).
    337   static constexpr size_t ClampedSize(T begin, size_t size_max, T end) {
    338     return (static_cast<T>(begin + size_max) <= end)
    339                ? size_max
    340                : (end > begin ? end - begin : 0);
    341   }
    342 
    343   T x0_;
    344   T y0_;
    345 
    346   size_t xsize_;
    347   size_t ysize_;
    348 };
    349 
    350 template <typename T>
    351 std::string Description(RectT<T> r) {
    352   std::ostringstream os;
    353   os << "[" << r.x0() << ".." << r.x1() << ")x"
    354      << "[" << r.y0() << ".." << r.y1() << ")";
    355   return os.str();
    356 }
    357 
    358 using Rect = RectT<size_t>;
    359 
    360 // Currently, we abuse Image to either refer to an image that owns its storage
    361 // or one that doesn't. In similar vein, we abuse Image* function parameters to
    362 // either mean "assign to me" or "fill the provided image with data".
    363 // Hopefully, the "assign to me" meaning will go away and most images in the
    364 // codebase will not be backed by own storage. When this happens we can redesign
    365 // Image to be a non-storage-holding view class and introduce BackedImage in
    366 // those places that actually need it.
    367 
    368 // NOTE: we can't use Image as a view because invariants are violated
    369 // (alignment and the presence of padding before/after each "row").
    370 
    371 // A bundle of 3 same-sized images. Typically constructed by moving from three
    372 // rvalue references to Image. To overwrite an existing Image3 using
    373 // single-channel producers, we also need access to Image*. Constructing
    374 // temporary non-owning Image pointing to one plane of an existing Image3 risks
    375 // dangling references, especially if the wrapper is moved. Therefore, we
    376 // store an array of Image (which are compact enough that size is not a concern)
    377 // and provide Plane+Row accessors.
    378 template <typename ComponentType>
    379 class Image3 {
    380  public:
    381   using T = ComponentType;
    382   using PlaneT = jxl::Plane<T>;
    383   static constexpr size_t kNumPlanes = 3;
    384 
    385   Image3() : planes_{PlaneT(), PlaneT(), PlaneT()} {}
    386 
    387   Image3(Image3&& other) noexcept {
    388     for (size_t i = 0; i < kNumPlanes; i++) {
    389       planes_[i] = std::move(other.planes_[i]);
    390     }
    391   }
    392 
    393   // Copy construction/assignment is forbidden to avoid inadvertent copies,
    394   // which can be very expensive. Use CopyImageTo instead.
    395   Image3(const Image3& other) = delete;
    396   Image3& operator=(const Image3& other) = delete;
    397 
    398   Image3& operator=(Image3&& other) noexcept {
    399     for (size_t i = 0; i < kNumPlanes; i++) {
    400       planes_[i] = std::move(other.planes_[i]);
    401     }
    402     return *this;
    403   }
    404 
    405   static StatusOr<Image3> Create(const size_t xsize, const size_t ysize) {
    406     StatusOr<PlaneT> plane0 = PlaneT::Create(xsize, ysize);
    407     JXL_RETURN_IF_ERROR(plane0.status());
    408     StatusOr<PlaneT> plane1 = PlaneT::Create(xsize, ysize);
    409     JXL_RETURN_IF_ERROR(plane1.status());
    410     StatusOr<PlaneT> plane2 = PlaneT::Create(xsize, ysize);
    411     JXL_RETURN_IF_ERROR(plane2.status());
    412     return Image3(std::move(plane0).value(), std::move(plane1).value(),
    413                   std::move(plane2).value());
    414   }
    415 
    416   // Returns row pointer; usage: PlaneRow(idx_plane, y)[x] = val.
    417   JXL_INLINE T* PlaneRow(const size_t c, const size_t y) {
    418     // Custom implementation instead of calling planes_[c].Row ensures only a
    419     // single multiplication is needed for PlaneRow(0..2, y).
    420     PlaneRowBoundsCheck(c, y);
    421     const size_t row_offset = y * planes_[0].bytes_per_row();
    422     void* row = planes_[c].bytes() + row_offset;
    423     return static_cast<T * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(row, 64));
    424   }
    425 
    426   // Returns const row pointer; usage: val = PlaneRow(idx_plane, y)[x].
    427   JXL_INLINE const T* PlaneRow(const size_t c, const size_t y) const {
    428     PlaneRowBoundsCheck(c, y);
    429     const size_t row_offset = y * planes_[0].bytes_per_row();
    430     const void* row = planes_[c].bytes() + row_offset;
    431     return static_cast<const T * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(row, 64));
    432   }
    433 
    434   // Returns const row pointer, even if called from a non-const Image3.
    435   JXL_INLINE const T* ConstPlaneRow(const size_t c, const size_t y) const {
    436     PlaneRowBoundsCheck(c, y);
    437     return PlaneRow(c, y);
    438   }
    439 
    440   JXL_INLINE const PlaneT& Plane(size_t idx) const { return planes_[idx]; }
    441 
    442   JXL_INLINE PlaneT& Plane(size_t idx) { return planes_[idx]; }
    443 
    444   void Swap(Image3& other) {
    445     for (size_t c = 0; c < 3; ++c) {
    446       other.planes_[c].Swap(planes_[c]);
    447     }
    448   }
    449 
    450   // Useful for pre-allocating image with some padding for alignment purposes
    451   // and later reporting the actual valid dimensions. May also be used to
    452   // un-shrink the image. Caller is responsible for ensuring xsize/ysize are <=
    453   // the original dimensions.
    454   void ShrinkTo(const size_t xsize, const size_t ysize) {
    455     for (PlaneT& plane : planes_) {
    456       plane.ShrinkTo(xsize, ysize);
    457     }
    458   }
    459 
    460   // Sizes of all three images are guaranteed to be equal.
    461   JXL_INLINE size_t xsize() const { return planes_[0].xsize(); }
    462   JXL_INLINE size_t ysize() const { return planes_[0].ysize(); }
    463   // Returns offset [bytes] from one row to the next row of the same plane.
    464   // WARNING: this must NOT be used to determine xsize, nor for copying rows -
    465   // the valid xsize may be much less.
    466   JXL_INLINE size_t bytes_per_row() const { return planes_[0].bytes_per_row(); }
    467   // Returns number of pixels (some of which are padding) per row. Useful for
    468   // computing other rows via pointer arithmetic. WARNING: this must NOT be used
    469   // to determine xsize.
    470   JXL_INLINE intptr_t PixelsPerRow() const { return planes_[0].PixelsPerRow(); }
    471 
    472  private:
    473   Image3(PlaneT&& plane0, PlaneT&& plane1, PlaneT&& plane2) {
    474     planes_[0] = std::move(plane0);
    475     planes_[1] = std::move(plane1);
    476     planes_[2] = std::move(plane2);
    477   }
    478 
    479   void PlaneRowBoundsCheck(const size_t c, const size_t y) const {
    480 #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
    481     defined(THREAD_SANITIZER)
    482     if (c >= kNumPlanes || y >= ysize()) {
    483       JXL_ABORT("PlaneRow(%" PRIu64 ", %" PRIu64 ") in (%" PRIu64 " x %" PRIu64
    484                 ") image\n",
    485                 static_cast<uint64_t>(c), static_cast<uint64_t>(y),
    486                 static_cast<uint64_t>(xsize()), static_cast<uint64_t>(ysize()));
    487     }
    488 #endif
    489   }
    490 
    491   PlaneT planes_[kNumPlanes];
    492 };
    493 
    494 using Image3B = Image3<uint8_t>;
    495 using Image3S = Image3<int16_t>;
    496 using Image3U = Image3<uint16_t>;
    497 using Image3I = Image3<int32_t>;
    498 using Image3F = Image3<float>;
    499 using Image3D = Image3<double>;
    500 
    501 }  // namespace jxl
    502 
    503 #endif  // LIB_JXL_IMAGE_H_