image.h (12584B)
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 <cinttypes> // PRIu64 14 #endif 15 16 #include <jxl/memory_manager.h> 17 18 #include <algorithm> 19 #include <cstddef> 20 #include <cstdint> 21 #include <cstring> 22 #include <utility> // std::move 23 24 #include "lib/jxl/base/compiler_specific.h" 25 #include "lib/jxl/base/status.h" 26 #include "lib/jxl/memory_manager_internal.h" 27 28 namespace jxl { 29 30 // DO NOT use PlaneBase outside of image.{h|cc} 31 namespace detail { 32 33 // Type-independent parts of Plane<> - reduces code duplication and facilitates 34 // moving member function implementations to cc file. 35 struct PlaneBase { 36 PlaneBase() 37 : xsize_(0), 38 ysize_(0), 39 orig_xsize_(0), 40 orig_ysize_(0), 41 bytes_per_row_(0), 42 sizeof_t_(0) {} 43 44 // Copy construction/assignment is forbidden to avoid inadvertent copies, 45 // which can be very expensive. Use CopyImageTo() instead. 46 PlaneBase(const PlaneBase& other) = delete; 47 PlaneBase& operator=(const PlaneBase& other) = delete; 48 49 // Move constructor (required for returning Image from function) 50 PlaneBase(PlaneBase&& other) noexcept = default; 51 52 // Move assignment (required for std::vector) 53 PlaneBase& operator=(PlaneBase&& other) noexcept = default; 54 55 ~PlaneBase() = 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 Status ShrinkTo(const size_t xsize, const size_t ysize) { 64 JXL_ENSURE(xsize <= orig_xsize_); 65 JXL_ENSURE(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 return true; 71 } 72 73 // How many pixels. 74 JXL_INLINE size_t xsize() const { return xsize_; } 75 JXL_INLINE size_t ysize() const { return ysize_; } 76 77 // NOTE: do not use this for copying rows - the valid xsize may be much less. 78 JXL_INLINE size_t bytes_per_row() const { return bytes_per_row_; } 79 80 JXL_INLINE JxlMemoryManager* memory_manager() const { 81 return bytes_.memory_manager(); 82 } 83 84 // Raw access to byte contents, for interfacing with other libraries. 85 // Unsigned char instead of char to avoid surprises (sign extension). 86 JXL_INLINE uint8_t* bytes() { 87 uint8_t* p = bytes_.address<uint8_t>(); 88 return static_cast<uint8_t * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(p, 64)); 89 } 90 JXL_INLINE const uint8_t* bytes() const { 91 const uint8_t* p = bytes_.address<uint8_t>(); 92 return static_cast<const uint8_t * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(p, 64)); 93 } 94 95 protected: 96 PlaneBase(uint32_t xsize, uint32_t ysize, size_t sizeof_t); 97 Status Allocate(JxlMemoryManager* memory_manager, size_t pre_padding); 98 99 // Returns pointer to the start of a row. 100 JXL_INLINE void* VoidRow(const size_t y) const { 101 JXL_DASSERT(y < ysize_); 102 uint8_t* row = bytes_.address<uint8_t>() + y * bytes_per_row_; 103 return JXL_ASSUME_ALIGNED(row, 64); 104 } 105 106 // (Members are non-const to enable assignment during move-assignment.) 107 uint32_t xsize_; // In valid pixels, not including any padding. 108 uint32_t ysize_; 109 uint32_t orig_xsize_; 110 uint32_t orig_ysize_; 111 size_t bytes_per_row_; // Includes padding. 112 AlignedMemory bytes_; 113 size_t sizeof_t_; 114 }; 115 116 } // namespace detail 117 118 // Single channel, aligned rows separated by padding. T must be POD. 119 // 120 // 'Single channel' (one 2D array per channel) simplifies vectorization 121 // (repeating the same operation on multiple adjacent components) without the 122 // complexity of a hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients 123 // can easily iterate over all components in a row and Image requires no 124 // knowledge of the pixel format beyond the component type "T". 125 // 126 // 'Aligned' means each row is aligned to the L1 cache line size. This prevents 127 // false sharing between two threads operating on adjacent rows. 128 // 129 // 'Padding' is still relevant because vectors could potentially be larger than 130 // a cache line. By rounding up row sizes to the vector size, we allow 131 // reading/writing ALIGNED vectors whose first lane is a valid sample. This 132 // avoids needing a separate loop to handle remaining unaligned lanes. 133 // 134 // This image layout could also be achieved with a vector and a row accessor 135 // function, but a class wrapper with support for "deleter" allows wrapping 136 // existing memory allocated by clients without copying the pixels. It also 137 // provides convenient accessors for xsize/ysize, which shortens function 138 // argument lists. Supports move-construction so it can be stored in containers. 139 template <typename ComponentType> 140 class Plane : public detail::PlaneBase { 141 public: 142 using T = ComponentType; 143 static constexpr size_t kNumPlanes = 1; 144 145 Plane() = default; 146 147 static StatusOr<Plane> Create(JxlMemoryManager* memory_manager, 148 const size_t xsize, const size_t ysize, 149 const size_t pre_padding = 0) { 150 static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || 151 sizeof(T) == 8); 152 uint32_t xsize32 = static_cast<uint32_t>(xsize); 153 uint32_t ysize32 = static_cast<uint32_t>(ysize); 154 JXL_ENSURE(xsize32 == xsize); 155 JXL_ENSURE(ysize32 == ysize); 156 Plane plane(xsize32, ysize32, sizeof(T)); 157 JXL_RETURN_IF_ERROR(plane.Allocate(memory_manager, pre_padding)); 158 return plane; 159 } 160 161 JXL_INLINE T* Row(const size_t y) { return static_cast<T*>(VoidRow(y)); } 162 163 // Returns pointer to const (see above). 164 JXL_INLINE const T* Row(const size_t y) const { 165 return static_cast<const T*>(VoidRow(y)); 166 } 167 168 // Documents that the access is const. 169 JXL_INLINE const T* ConstRow(const size_t y) const { 170 return static_cast<const T*>(VoidRow(y)); 171 } 172 173 // Returns number of pixels (some of which are padding) per row. Useful for 174 // computing other rows via pointer arithmetic. WARNING: this must 175 // NOT be used to determine xsize. 176 JXL_INLINE intptr_t PixelsPerRow() const { 177 return static_cast<intptr_t>(bytes_per_row_ / sizeof(T)); 178 } 179 180 private: 181 Plane(uint32_t xsize, uint32_t ysize, size_t sizeof_t) 182 : detail::PlaneBase(xsize, ysize, sizeof_t) {} 183 }; 184 185 using ImageSB = Plane<int8_t>; 186 using ImageB = Plane<uint8_t>; 187 using ImageS = Plane<int16_t>; // signed integer or half-float 188 using ImageU = Plane<uint16_t>; 189 using ImageI = Plane<int32_t>; 190 using ImageF = Plane<float>; 191 using ImageD = Plane<double>; 192 193 // Currently, we abuse Image to either refer to an image that owns its storage 194 // or one that doesn't. In similar vein, we abuse Image* function parameters to 195 // either mean "assign to me" or "fill the provided image with data". 196 // Hopefully, the "assign to me" meaning will go away and most images in the 197 // codebase will not be backed by own storage. When this happens we can redesign 198 // Image to be a non-storage-holding view class and introduce BackedImage in 199 // those places that actually need it. 200 201 // NOTE: we can't use Image as a view because invariants are violated 202 // (alignment and the presence of padding before/after each "row"). 203 204 // A bundle of 3 same-sized images. Typically constructed by moving from three 205 // rvalue references to Image. To overwrite an existing Image3 using 206 // single-channel producers, we also need access to Image*. Constructing 207 // temporary non-owning Image pointing to one plane of an existing Image3 risks 208 // dangling references, especially if the wrapper is moved. Therefore, we 209 // store an array of Image (which are compact enough that size is not a concern) 210 // and provide Plane+Row accessors. 211 template <typename ComponentType> 212 class Image3 { 213 public: 214 using T = ComponentType; 215 using PlaneT = jxl::Plane<T>; 216 static constexpr size_t kNumPlanes = 3; 217 218 Image3() : planes_{PlaneT(), PlaneT(), PlaneT()} {} 219 220 // Copy construction/assignment is forbidden to avoid inadvertent copies, 221 // which can be very expensive. Use CopyImageTo instead. 222 Image3(const Image3& other) = delete; 223 Image3& operator=(const Image3& other) = delete; 224 225 Image3(Image3&& other) noexcept { 226 for (size_t i = 0; i < kNumPlanes; i++) { 227 planes_[i] = std::move(other.planes_[i]); 228 } 229 } 230 Image3& operator=(Image3&& other) noexcept { 231 for (size_t i = 0; i < kNumPlanes; i++) { 232 planes_[i] = std::move(other.planes_[i]); 233 } 234 return *this; 235 } 236 237 static StatusOr<Image3> Create(JxlMemoryManager* memory_manager, 238 const size_t xsize, const size_t ysize) { 239 JXL_ASSIGN_OR_RETURN(PlaneT plane0, 240 PlaneT::Create(memory_manager, xsize, ysize)); 241 JXL_ASSIGN_OR_RETURN(PlaneT plane1, 242 PlaneT::Create(memory_manager, xsize, ysize)); 243 JXL_ASSIGN_OR_RETURN(PlaneT plane2, 244 PlaneT::Create(memory_manager, xsize, ysize)); 245 return Image3(std::move(plane0), std::move(plane1), std::move(plane2)); 246 } 247 248 // Returns row pointer; usage: PlaneRow(idx_plane, y)[x] = val. 249 JXL_INLINE T* PlaneRow(const size_t c, const size_t y) { 250 // Custom implementation instead of calling planes_[c].Row ensures only a 251 // single multiplication is needed for PlaneRow(0..2, y). 252 PlaneRowBoundsCheck(c, y); 253 const size_t row_offset = y * planes_[0].bytes_per_row(); 254 void* row = planes_[c].bytes() + row_offset; 255 return static_cast<T * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(row, 64)); 256 } 257 258 // Returns const row pointer; usage: val = PlaneRow(idx_plane, y)[x]. 259 JXL_INLINE const T* PlaneRow(const size_t c, const size_t y) const { 260 PlaneRowBoundsCheck(c, y); 261 const size_t row_offset = y * planes_[0].bytes_per_row(); 262 const void* row = planes_[c].bytes() + row_offset; 263 return static_cast<const T * JXL_RESTRICT>(JXL_ASSUME_ALIGNED(row, 64)); 264 } 265 266 // Returns const row pointer, even if called from a non-const Image3. 267 JXL_INLINE const T* ConstPlaneRow(const size_t c, const size_t y) const { 268 PlaneRowBoundsCheck(c, y); 269 return PlaneRow(c, y); 270 } 271 272 JXL_INLINE const PlaneT& Plane(size_t idx) const { return planes_[idx]; } 273 274 JXL_INLINE PlaneT& Plane(size_t idx) { return planes_[idx]; } 275 276 void Swap(Image3& other) { 277 for (size_t c = 0; c < 3; ++c) { 278 other.planes_[c].Swap(planes_[c]); 279 } 280 } 281 282 // Useful for pre-allocating image with some padding for alignment purposes 283 // and later reporting the actual valid dimensions. May also be used to 284 // un-shrink the image. Caller is responsible for ensuring xsize/ysize are <= 285 // the original dimensions. 286 Status ShrinkTo(const size_t xsize, const size_t ysize) { 287 for (PlaneT& plane : planes_) { 288 JXL_RETURN_IF_ERROR(plane.ShrinkTo(xsize, ysize)); 289 } 290 return true; 291 } 292 293 // Sizes of all three images are guaranteed to be equal. 294 JXL_INLINE JxlMemoryManager* memory_manager() const { 295 return planes_[0].memory_manager(); 296 } 297 JXL_INLINE size_t xsize() const { return planes_[0].xsize(); } 298 JXL_INLINE size_t ysize() const { return planes_[0].ysize(); } 299 // Returns offset [bytes] from one row to the next row of the same plane. 300 // WARNING: this must NOT be used to determine xsize, nor for copying rows - 301 // the valid xsize may be much less. 302 JXL_INLINE size_t bytes_per_row() const { return planes_[0].bytes_per_row(); } 303 // Returns number of pixels (some of which are padding) per row. Useful for 304 // computing other rows via pointer arithmetic. WARNING: this must NOT be used 305 // to determine xsize. 306 JXL_INLINE intptr_t PixelsPerRow() const { return planes_[0].PixelsPerRow(); } 307 308 private: 309 Image3(PlaneT&& plane0, PlaneT&& plane1, PlaneT&& plane2) { 310 planes_[0] = std::move(plane0); 311 planes_[1] = std::move(plane1); 312 planes_[2] = std::move(plane2); 313 } 314 315 void PlaneRowBoundsCheck(const size_t c, const size_t y) const { 316 JXL_DASSERT(c < kNumPlanes && y < ysize()); 317 } 318 319 PlaneT planes_[kNumPlanes]; 320 }; 321 322 using Image3B = Image3<uint8_t>; 323 using Image3S = Image3<int16_t>; 324 using Image3U = Image3<uint16_t>; 325 using Image3I = Image3<int32_t>; 326 using Image3F = Image3<float>; 327 using Image3D = Image3<double>; 328 329 } // namespace jxl 330 331 #endif // LIB_JXL_IMAGE_H_