image_ops.h (9876B)
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_OPS_H_ 7 #define LIB_JXL_IMAGE_OPS_H_ 8 9 // Operations on images. 10 11 #include <algorithm> 12 #include <cstddef> 13 #include <cstdint> 14 #include <limits> 15 16 #include "lib/jxl/base/compiler_specific.h" 17 #include "lib/jxl/base/status.h" 18 #include "lib/jxl/frame_dimensions.h" 19 #include "lib/jxl/image.h" 20 21 namespace jxl { 22 23 // Works for mixed image-like argument types. 24 template <class Image1, class Image2> 25 bool SameSize(const Image1& image1, const Image2& image2) { 26 return image1.xsize() == image2.xsize() && image1.ysize() == image2.ysize(); 27 } 28 29 template <typename T> 30 void CopyImageTo(const Plane<T>& from, Plane<T>* JXL_RESTRICT to) { 31 JXL_ASSERT(SameSize(from, *to)); 32 if (from.ysize() == 0 || from.xsize() == 0) return; 33 for (size_t y = 0; y < from.ysize(); ++y) { 34 const T* JXL_RESTRICT row_from = from.ConstRow(y); 35 T* JXL_RESTRICT row_to = to->Row(y); 36 memcpy(row_to, row_from, from.xsize() * sizeof(T)); 37 } 38 } 39 40 // Copies `from:rect_from` to `to:rect_to`. 41 template <typename T> 42 void CopyImageTo(const Rect& rect_from, const Plane<T>& from, 43 const Rect& rect_to, Plane<T>* JXL_RESTRICT to) { 44 JXL_DASSERT(SameSize(rect_from, rect_to)); 45 JXL_DASSERT(rect_from.IsInside(from)); 46 JXL_DASSERT(rect_to.IsInside(*to)); 47 if (rect_from.xsize() == 0) return; 48 for (size_t y = 0; y < rect_from.ysize(); ++y) { 49 const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y); 50 T* JXL_RESTRICT row_to = rect_to.Row(to, y); 51 memcpy(row_to, row_from, rect_from.xsize() * sizeof(T)); 52 } 53 } 54 55 // Copies `from:rect_from` to `to:rect_to`. 56 template <typename T> 57 void CopyImageTo(const Rect& rect_from, const Image3<T>& from, 58 const Rect& rect_to, Image3<T>* JXL_RESTRICT to) { 59 JXL_ASSERT(SameSize(rect_from, rect_to)); 60 for (size_t c = 0; c < 3; c++) { 61 CopyImageTo(rect_from, from.Plane(c), rect_to, &to->Plane(c)); 62 } 63 } 64 65 template <typename T, typename U> 66 void ConvertPlaneAndClamp(const Rect& rect_from, const Plane<T>& from, 67 const Rect& rect_to, Plane<U>* JXL_RESTRICT to) { 68 JXL_ASSERT(SameSize(rect_from, rect_to)); 69 using M = decltype(T() + U()); 70 for (size_t y = 0; y < rect_to.ysize(); ++y) { 71 const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y); 72 U* JXL_RESTRICT row_to = rect_to.Row(to, y); 73 for (size_t x = 0; x < rect_to.xsize(); ++x) { 74 row_to[x] = 75 std::min<M>(std::max<M>(row_from[x], std::numeric_limits<U>::min()), 76 std::numeric_limits<U>::max()); 77 } 78 } 79 } 80 81 // Copies `from` to `to`. 82 template <typename T> 83 void CopyImageTo(const T& from, T* JXL_RESTRICT to) { 84 return CopyImageTo(Rect(from), from, Rect(*to), to); 85 } 86 87 // Copies `from:rect_from` to `to:rect_to`; also copies `padding` pixels of 88 // border around `from:rect_from`, in all directions, whenever they are inside 89 // the first image. 90 template <typename T> 91 void CopyImageToWithPadding(const Rect& from_rect, const T& from, 92 size_t padding, const Rect& to_rect, T* to) { 93 size_t xextra0 = std::min(padding, from_rect.x0()); 94 size_t xextra1 = 95 std::min(padding, from.xsize() - from_rect.x0() - from_rect.xsize()); 96 size_t yextra0 = std::min(padding, from_rect.y0()); 97 size_t yextra1 = 98 std::min(padding, from.ysize() - from_rect.y0() - from_rect.ysize()); 99 JXL_DASSERT(to_rect.x0() >= xextra0); 100 JXL_DASSERT(to_rect.y0() >= yextra0); 101 102 return CopyImageTo(Rect(from_rect.x0() - xextra0, from_rect.y0() - yextra0, 103 from_rect.xsize() + xextra0 + xextra1, 104 from_rect.ysize() + yextra0 + yextra1), 105 from, 106 Rect(to_rect.x0() - xextra0, to_rect.y0() - yextra0, 107 to_rect.xsize() + xextra0 + xextra1, 108 to_rect.ysize() + yextra0 + yextra1), 109 to); 110 } 111 112 // Returns linear combination of two grayscale images. 113 template <typename T> 114 StatusOr<Plane<T>> LinComb(const T lambda1, const Plane<T>& image1, 115 const T lambda2, const Plane<T>& image2) { 116 const size_t xsize = image1.xsize(); 117 const size_t ysize = image1.ysize(); 118 JXL_CHECK(xsize == image2.xsize()); 119 JXL_CHECK(ysize == image2.ysize()); 120 JXL_ASSIGN_OR_RETURN(Plane<T> out, Plane<T>::Create(xsize, ysize)); 121 for (size_t y = 0; y < ysize; ++y) { 122 const T* const JXL_RESTRICT row1 = image1.Row(y); 123 const T* const JXL_RESTRICT row2 = image2.Row(y); 124 T* const JXL_RESTRICT row_out = out.Row(y); 125 for (size_t x = 0; x < xsize; ++x) { 126 row_out[x] = lambda1 * row1[x] + lambda2 * row2[x]; 127 } 128 } 129 return out; 130 } 131 132 // Multiplies image by lambda in-place 133 template <typename T> 134 void ScaleImage(const T lambda, Plane<T>* image) { 135 for (size_t y = 0; y < image->ysize(); ++y) { 136 T* const JXL_RESTRICT row = image->Row(y); 137 for (size_t x = 0; x < image->xsize(); ++x) { 138 row[x] = lambda * row[x]; 139 } 140 } 141 } 142 143 // Multiplies image by lambda in-place 144 template <typename T> 145 void ScaleImage(const T lambda, Image3<T>* image) { 146 for (size_t c = 0; c < 3; ++c) { 147 ScaleImage(lambda, &image->Plane(c)); 148 } 149 } 150 151 template <typename T> 152 void FillImage(const T value, Plane<T>* image) { 153 for (size_t y = 0; y < image->ysize(); ++y) { 154 T* const JXL_RESTRICT row = image->Row(y); 155 for (size_t x = 0; x < image->xsize(); ++x) { 156 row[x] = value; 157 } 158 } 159 } 160 161 template <typename T> 162 void ZeroFillImage(Plane<T>* image) { 163 if (image->xsize() == 0) return; 164 for (size_t y = 0; y < image->ysize(); ++y) { 165 T* const JXL_RESTRICT row = image->Row(y); 166 memset(row, 0, image->xsize() * sizeof(T)); 167 } 168 } 169 170 // Mirrors out of bounds coordinates and returns valid coordinates unchanged. 171 // We assume the radius (distance outside the image) is small compared to the 172 // image size, otherwise this might not terminate. 173 // The mirror is outside the last column (border pixel is also replicated). 174 static inline int64_t Mirror(int64_t x, const int64_t xsize) { 175 JXL_DASSERT(xsize != 0); 176 177 // TODO(janwas): replace with branchless version 178 while (x < 0 || x >= xsize) { 179 if (x < 0) { 180 x = -x - 1; 181 } else { 182 x = 2 * xsize - 1 - x; 183 } 184 } 185 return x; 186 } 187 188 // Wrap modes for ensuring X/Y coordinates are in the valid range [0, size): 189 190 // Mirrors (repeating the edge pixel once). Useful for convolutions. 191 struct WrapMirror { 192 JXL_INLINE int64_t operator()(const int64_t coord, const int64_t size) const { 193 return Mirror(coord, size); 194 } 195 }; 196 197 // Returns the same coordinate: required for TFNode with Border(), or useful 198 // when we know "coord" is already valid (e.g. interior of an image). 199 struct WrapUnchanged { 200 JXL_INLINE int64_t operator()(const int64_t coord, int64_t /*size*/) const { 201 return coord; 202 } 203 }; 204 205 // Similar to Wrap* but for row pointers (reduces Row() multiplications). 206 207 class WrapRowMirror { 208 public: 209 template <class ImageOrView> 210 WrapRowMirror(const ImageOrView& image, size_t ysize) 211 : first_row_(image.ConstRow(0)), last_row_(image.ConstRow(ysize - 1)) {} 212 213 const float* operator()(const float* const JXL_RESTRICT row, 214 const int64_t stride) const { 215 if (row < first_row_) { 216 const int64_t num_before = first_row_ - row; 217 // Mirrored; one row before => row 0, two before = row 1, ... 218 return first_row_ + num_before - stride; 219 } 220 if (row > last_row_) { 221 const int64_t num_after = row - last_row_; 222 // Mirrored; one row after => last row, two after = last - 1, ... 223 return last_row_ - num_after + stride; 224 } 225 return row; 226 } 227 228 private: 229 const float* const JXL_RESTRICT first_row_; 230 const float* const JXL_RESTRICT last_row_; 231 }; 232 233 struct WrapRowUnchanged { 234 JXL_INLINE const float* operator()(const float* const JXL_RESTRICT row, 235 int64_t /*stride*/) const { 236 return row; 237 } 238 }; 239 240 // Computes the minimum and maximum pixel value. 241 template <typename T> 242 void ImageMinMax(const Plane<T>& image, T* const JXL_RESTRICT min, 243 T* const JXL_RESTRICT max) { 244 *min = std::numeric_limits<T>::max(); 245 *max = std::numeric_limits<T>::lowest(); 246 for (size_t y = 0; y < image.ysize(); ++y) { 247 const T* const JXL_RESTRICT row = image.Row(y); 248 for (size_t x = 0; x < image.xsize(); ++x) { 249 *min = std::min(*min, row[x]); 250 *max = std::max(*max, row[x]); 251 } 252 } 253 } 254 255 // Initializes all planes to the same "value". 256 template <typename T> 257 void FillImage(const T value, Image3<T>* image) { 258 for (size_t c = 0; c < 3; ++c) { 259 for (size_t y = 0; y < image->ysize(); ++y) { 260 T* JXL_RESTRICT row = image->PlaneRow(c, y); 261 for (size_t x = 0; x < image->xsize(); ++x) { 262 row[x] = value; 263 } 264 } 265 } 266 } 267 268 template <typename T> 269 void FillPlane(const T value, Plane<T>* image, Rect rect) { 270 for (size_t y = 0; y < rect.ysize(); ++y) { 271 T* JXL_RESTRICT row = rect.Row(image, y); 272 for (size_t x = 0; x < rect.xsize(); ++x) { 273 row[x] = value; 274 } 275 } 276 } 277 278 template <typename T> 279 void ZeroFillImage(Image3<T>* image) { 280 for (size_t c = 0; c < 3; ++c) { 281 for (size_t y = 0; y < image->ysize(); ++y) { 282 T* JXL_RESTRICT row = image->PlaneRow(c, y); 283 if (image->xsize() != 0) memset(row, 0, image->xsize() * sizeof(T)); 284 } 285 } 286 } 287 288 // Same as above, but operates in-place. Assumes that the `in` image was 289 // allocated large enough. 290 void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in, 291 size_t block_dim = kBlockDim); 292 293 // Downsamples an image by a given factor. 294 StatusOr<Image3F> DownsampleImage(const Image3F& opsin, size_t factor); 295 StatusOr<ImageF> DownsampleImage(const ImageF& image, size_t factor); 296 297 } // namespace jxl 298 299 #endif // LIB_JXL_IMAGE_OPS_H_