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 <array>
13 #include <limits>
14 #include <vector>
15
16 #include "lib/jxl/base/profiler.h"
17 #include "lib/jxl/base/status.h"
18 #include "lib/jxl/common.h"
19 #include "lib/jxl/image.h"
20
21 namespace jxl {
22
23 template <typename T>
CopyImageTo(const Plane<T> & from,Plane<T> * JXL_RESTRICT to)24 void CopyImageTo(const Plane<T>& from, Plane<T>* JXL_RESTRICT to) {
25 PROFILER_ZONE("CopyImage1");
26 JXL_ASSERT(SameSize(from, *to));
27 if (from.ysize() == 0 || from.xsize() == 0) return;
28 for (size_t y = 0; y < from.ysize(); ++y) {
29 const T* JXL_RESTRICT row_from = from.ConstRow(y);
30 T* JXL_RESTRICT row_to = to->Row(y);
31 memcpy(row_to, row_from, from.xsize() * sizeof(T));
32 }
33 }
34
35 // DEPRECATED - prefer to preallocate result.
36 template <typename T>
CopyImage(const Plane<T> & from)37 Plane<T> CopyImage(const Plane<T>& from) {
38 Plane<T> to(from.xsize(), from.ysize());
39 CopyImageTo(from, &to);
40 return to;
41 }
42
43 // Copies `from:rect_from` to `to:rect_to`.
44 template <typename T>
CopyImageTo(const Rect & rect_from,const Plane<T> & from,const Rect & rect_to,Plane<T> * JXL_RESTRICT to)45 void CopyImageTo(const Rect& rect_from, const Plane<T>& from,
46 const Rect& rect_to, Plane<T>* JXL_RESTRICT to) {
47 PROFILER_ZONE("CopyImageR");
48 JXL_DASSERT(SameSize(rect_from, rect_to));
49 JXL_DASSERT(rect_from.IsInside(from));
50 JXL_DASSERT(rect_to.IsInside(*to));
51 if (rect_from.xsize() == 0) return;
52 for (size_t y = 0; y < rect_from.ysize(); ++y) {
53 const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y);
54 T* JXL_RESTRICT row_to = rect_to.Row(to, y);
55 memcpy(row_to, row_from, rect_from.xsize() * sizeof(T));
56 }
57 }
58
59 // DEPRECATED - Returns a copy of the "image" pixels that lie in "rect".
60 template <typename T>
CopyImage(const Rect & rect,const Plane<T> & image)61 Plane<T> CopyImage(const Rect& rect, const Plane<T>& image) {
62 Plane<T> copy(rect.xsize(), rect.ysize());
63 CopyImageTo(rect, image, ©);
64 return copy;
65 }
66
67 // Copies `from:rect_from` to `to:rect_to`.
68 template <typename T>
CopyImageTo(const Rect & rect_from,const Image3<T> & from,const Rect & rect_to,Image3<T> * JXL_RESTRICT to)69 void CopyImageTo(const Rect& rect_from, const Image3<T>& from,
70 const Rect& rect_to, Image3<T>* JXL_RESTRICT to) {
71 PROFILER_ZONE("CopyImageR");
72 JXL_ASSERT(SameSize(rect_from, rect_to));
73 for (size_t c = 0; c < 3; c++) {
74 CopyImageTo(rect_from, from.Plane(c), rect_to, &to->Plane(c));
75 }
76 }
77
78 template <typename T, typename U>
ConvertPlaneAndClamp(const Rect & rect_from,const Plane<T> & from,const Rect & rect_to,Plane<U> * JXL_RESTRICT to)79 void ConvertPlaneAndClamp(const Rect& rect_from, const Plane<T>& from,
80 const Rect& rect_to, Plane<U>* JXL_RESTRICT to) {
81 PROFILER_ZONE("ConvertPlane");
82 JXL_ASSERT(SameSize(rect_from, rect_to));
83 using M = decltype(T() + U());
84 for (size_t y = 0; y < rect_to.ysize(); ++y) {
85 const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y);
86 U* JXL_RESTRICT row_to = rect_to.Row(to, y);
87 for (size_t x = 0; x < rect_to.xsize(); ++x) {
88 row_to[x] =
89 std::min<M>(std::max<M>(row_from[x], std::numeric_limits<U>::min()),
90 std::numeric_limits<U>::max());
91 }
92 }
93 }
94
95 // Copies `from` to `to`.
96 template <typename T>
CopyImageTo(const T & from,T * JXL_RESTRICT to)97 void CopyImageTo(const T& from, T* JXL_RESTRICT to) {
98 return CopyImageTo(Rect(from), from, Rect(*to), to);
99 }
100
101 // Copies `from:rect_from` to `to`.
102 template <typename T>
CopyImageTo(const Rect & rect_from,const T & from,T * JXL_RESTRICT to)103 void CopyImageTo(const Rect& rect_from, const T& from, T* JXL_RESTRICT to) {
104 return CopyImageTo(rect_from, from, Rect(*to), to);
105 }
106
107 // Copies `from` to `to:rect_to`.
108 template <typename T>
CopyImageTo(const T & from,const Rect & rect_to,T * JXL_RESTRICT to)109 void CopyImageTo(const T& from, const Rect& rect_to, T* JXL_RESTRICT to) {
110 return CopyImageTo(Rect(from), from, rect_to, to);
111 }
112
113 // Copies `from:rect_from` to `to:rect_to`; also copies `padding` pixels of
114 // border around `from:rect_from`, in all directions, whenever they are inside
115 // the first image.
116 template <typename T>
CopyImageToWithPadding(const Rect & from_rect,const T & from,size_t padding,const Rect & to_rect,T * to)117 void CopyImageToWithPadding(const Rect& from_rect, const T& from,
118 size_t padding, const Rect& to_rect, T* to) {
119 size_t xextra0 = std::min(padding, from_rect.x0());
120 size_t xextra1 =
121 std::min(padding, from.xsize() - from_rect.x0() - from_rect.xsize());
122 size_t yextra0 = std::min(padding, from_rect.y0());
123 size_t yextra1 =
124 std::min(padding, from.ysize() - from_rect.y0() - from_rect.ysize());
125 JXL_DASSERT(to_rect.x0() >= xextra0);
126 JXL_DASSERT(to_rect.y0() >= yextra0);
127
128 return CopyImageTo(Rect(from_rect.x0() - xextra0, from_rect.y0() - yextra0,
129 from_rect.xsize() + xextra0 + xextra1,
130 from_rect.ysize() + yextra0 + yextra1),
131 from,
132 Rect(to_rect.x0() - xextra0, to_rect.y0() - yextra0,
133 to_rect.xsize() + xextra0 + xextra1,
134 to_rect.ysize() + yextra0 + yextra1),
135 to);
136 }
137
138 // DEPRECATED - prefer to preallocate result.
139 template <typename T>
CopyImage(const Image3<T> & from)140 Image3<T> CopyImage(const Image3<T>& from) {
141 Image3<T> copy(from.xsize(), from.ysize());
142 CopyImageTo(from, ©);
143 return copy;
144 }
145
146 // DEPRECATED - prefer to preallocate result.
147 template <typename T>
CopyImage(const Rect & rect,const Image3<T> & from)148 Image3<T> CopyImage(const Rect& rect, const Image3<T>& from) {
149 Image3<T> to(rect.xsize(), rect.ysize());
150 CopyImageTo(rect, from.Plane(0), to.Plane(0));
151 CopyImageTo(rect, from.Plane(1), to.Plane(1));
152 CopyImageTo(rect, from.Plane(2), to.Plane(2));
153 return to;
154 }
155
156 // Sets "thickness" pixels on each border to "value". This is faster than
157 // initializing the entire image and overwriting valid/interior pixels.
158 template <typename T>
SetBorder(const size_t thickness,const T value,Image3<T> * image)159 void SetBorder(const size_t thickness, const T value, Image3<T>* image) {
160 const size_t xsize = image->xsize();
161 const size_t ysize = image->ysize();
162 // Top: fill entire row
163 for (size_t c = 0; c < 3; ++c) {
164 for (size_t y = 0; y < std::min(thickness, ysize); ++y) {
165 T* JXL_RESTRICT row = image->PlaneRow(c, y);
166 std::fill(row, row + xsize, value);
167 }
168
169 // Bottom: fill entire row
170 for (size_t y = ysize - thickness; y < ysize; ++y) {
171 T* JXL_RESTRICT row = image->PlaneRow(c, y);
172 std::fill(row, row + xsize, value);
173 }
174
175 // Left/right: fill the 'columns' on either side, but only if the image is
176 // big enough that they don't already belong to the top/bottom rows.
177 if (ysize >= 2 * thickness) {
178 for (size_t y = thickness; y < ysize - thickness; ++y) {
179 T* JXL_RESTRICT row = image->PlaneRow(c, y);
180 std::fill(row, row + thickness, value);
181 std::fill(row + xsize - thickness, row + xsize, value);
182 }
183 }
184 }
185 }
186
187 template <class ImageIn, class ImageOut>
Subtract(const ImageIn & image1,const ImageIn & image2,ImageOut * out)188 void Subtract(const ImageIn& image1, const ImageIn& image2, ImageOut* out) {
189 using T = typename ImageIn::T;
190 const size_t xsize = image1.xsize();
191 const size_t ysize = image1.ysize();
192 JXL_CHECK(xsize == image2.xsize());
193 JXL_CHECK(ysize == image2.ysize());
194
195 for (size_t y = 0; y < ysize; ++y) {
196 const T* const JXL_RESTRICT row1 = image1.Row(y);
197 const T* const JXL_RESTRICT row2 = image2.Row(y);
198 T* const JXL_RESTRICT row_out = out->Row(y);
199 for (size_t x = 0; x < xsize; ++x) {
200 row_out[x] = row1[x] - row2[x];
201 }
202 }
203 }
204
205 // In-place.
206 template <typename Tin, typename Tout>
SubtractFrom(const Plane<Tin> & what,Plane<Tout> * to)207 void SubtractFrom(const Plane<Tin>& what, Plane<Tout>* to) {
208 const size_t xsize = what.xsize();
209 const size_t ysize = what.ysize();
210 for (size_t y = 0; y < ysize; ++y) {
211 const Tin* JXL_RESTRICT row_what = what.ConstRow(y);
212 Tout* JXL_RESTRICT row_to = to->Row(y);
213 for (size_t x = 0; x < xsize; ++x) {
214 row_to[x] -= row_what[x];
215 }
216 }
217 }
218
219 // In-place.
220 template <typename Tin, typename Tout>
AddTo(const Plane<Tin> & what,Plane<Tout> * to)221 void AddTo(const Plane<Tin>& what, Plane<Tout>* to) {
222 const size_t xsize = what.xsize();
223 const size_t ysize = what.ysize();
224 for (size_t y = 0; y < ysize; ++y) {
225 const Tin* JXL_RESTRICT row_what = what.ConstRow(y);
226 Tout* JXL_RESTRICT row_to = to->Row(y);
227 for (size_t x = 0; x < xsize; ++x) {
228 row_to[x] += row_what[x];
229 }
230 }
231 }
232
233 template <typename Tin, typename Tout>
AddTo(Rect rectFrom,const Plane<Tin> & what,Rect rectTo,Plane<Tout> * to)234 void AddTo(Rect rectFrom, const Plane<Tin>& what, Rect rectTo,
235 Plane<Tout>* to) {
236 JXL_ASSERT(SameSize(rectFrom, rectTo));
237 const size_t xsize = rectTo.xsize();
238 const size_t ysize = rectTo.ysize();
239 for (size_t y = 0; y < ysize; ++y) {
240 const Tin* JXL_RESTRICT row_what = rectFrom.ConstRow(what, y);
241 Tout* JXL_RESTRICT row_to = rectTo.Row(to, y);
242 for (size_t x = 0; x < xsize; ++x) {
243 row_to[x] += row_what[x];
244 }
245 }
246 }
247
248 // Returns linear combination of two grayscale images.
249 template <typename T>
LinComb(const T lambda1,const Plane<T> & image1,const T lambda2,const Plane<T> & image2)250 Plane<T> LinComb(const T lambda1, const Plane<T>& image1, const T lambda2,
251 const Plane<T>& image2) {
252 const size_t xsize = image1.xsize();
253 const size_t ysize = image1.ysize();
254 JXL_CHECK(xsize == image2.xsize());
255 JXL_CHECK(ysize == image2.ysize());
256 Plane<T> out(xsize, ysize);
257 for (size_t y = 0; y < ysize; ++y) {
258 const T* const JXL_RESTRICT row1 = image1.Row(y);
259 const T* const JXL_RESTRICT row2 = image2.Row(y);
260 T* const JXL_RESTRICT row_out = out.Row(y);
261 for (size_t x = 0; x < xsize; ++x) {
262 row_out[x] = lambda1 * row1[x] + lambda2 * row2[x];
263 }
264 }
265 return out;
266 }
267
268 // Returns a pixel-by-pixel multiplication of image by lambda.
269 template <typename T>
ScaleImage(const T lambda,const Plane<T> & image)270 Plane<T> ScaleImage(const T lambda, const Plane<T>& image) {
271 Plane<T> out(image.xsize(), image.ysize());
272 for (size_t y = 0; y < image.ysize(); ++y) {
273 const T* const JXL_RESTRICT row = image.Row(y);
274 T* const JXL_RESTRICT row_out = out.Row(y);
275 for (size_t x = 0; x < image.xsize(); ++x) {
276 row_out[x] = lambda * row[x];
277 }
278 }
279 return out;
280 }
281
282 // Multiplies image by lambda in-place
283 template <typename T>
ScaleImage(const T lambda,Plane<T> * image)284 void ScaleImage(const T lambda, Plane<T>* image) {
285 for (size_t y = 0; y < image->ysize(); ++y) {
286 T* const JXL_RESTRICT row = image->Row(y);
287 for (size_t x = 0; x < image->xsize(); ++x) {
288 row[x] = lambda * row[x];
289 }
290 }
291 }
292
293 template <typename T>
Product(const Plane<T> & a,const Plane<T> & b)294 Plane<T> Product(const Plane<T>& a, const Plane<T>& b) {
295 Plane<T> c(a.xsize(), a.ysize());
296 for (size_t y = 0; y < a.ysize(); ++y) {
297 const T* const JXL_RESTRICT row_a = a.Row(y);
298 const T* const JXL_RESTRICT row_b = b.Row(y);
299 T* const JXL_RESTRICT row_c = c.Row(y);
300 for (size_t x = 0; x < a.xsize(); ++x) {
301 row_c[x] = row_a[x] * row_b[x];
302 }
303 }
304 return c;
305 }
306
307 float DotProduct(const ImageF& a, const ImageF& b);
308
309 template <typename T>
FillImage(const T value,Plane<T> * image)310 void FillImage(const T value, Plane<T>* image) {
311 for (size_t y = 0; y < image->ysize(); ++y) {
312 T* const JXL_RESTRICT row = image->Row(y);
313 for (size_t x = 0; x < image->xsize(); ++x) {
314 row[x] = value;
315 }
316 }
317 }
318
319 template <typename T>
ZeroFillImage(Plane<T> * image)320 void ZeroFillImage(Plane<T>* image) {
321 if (image->xsize() == 0) return;
322 for (size_t y = 0; y < image->ysize(); ++y) {
323 T* const JXL_RESTRICT row = image->Row(y);
324 memset(row, 0, image->xsize() * sizeof(T));
325 }
326 }
327
328 // Mirrors out of bounds coordinates and returns valid coordinates unchanged.
329 // We assume the radius (distance outside the image) is small compared to the
330 // image size, otherwise this might not terminate.
331 // The mirror is outside the last column (border pixel is also replicated).
Mirror(int64_t x,const int64_t xsize)332 static inline int64_t Mirror(int64_t x, const int64_t xsize) {
333 JXL_DASSERT(xsize != 0);
334
335 // TODO(janwas): replace with branchless version
336 while (x < 0 || x >= xsize) {
337 if (x < 0) {
338 x = -x - 1;
339 } else {
340 x = 2 * xsize - 1 - x;
341 }
342 }
343 return x;
344 }
345
346 // Wrap modes for ensuring X/Y coordinates are in the valid range [0, size):
347
348 // Mirrors (repeating the edge pixel once). Useful for convolutions.
349 struct WrapMirror {
operatorWrapMirror350 JXL_INLINE int64_t operator()(const int64_t coord, const int64_t size) const {
351 return Mirror(coord, size);
352 }
353 };
354
355 // Returns the same coordinate: required for TFNode with Border(), or useful
356 // when we know "coord" is already valid (e.g. interior of an image).
357 struct WrapUnchanged {
operatorWrapUnchanged358 JXL_INLINE int64_t operator()(const int64_t coord, int64_t /*size*/) const {
359 return coord;
360 }
361 };
362
363 // Similar to Wrap* but for row pointers (reduces Row() multiplications).
364
365 class WrapRowMirror {
366 public:
367 template <class ImageOrView>
WrapRowMirror(const ImageOrView & image,size_t ysize)368 WrapRowMirror(const ImageOrView& image, size_t ysize)
369 : first_row_(image.ConstRow(0)), last_row_(image.ConstRow(ysize - 1)) {}
370
operator()371 const float* operator()(const float* const JXL_RESTRICT row,
372 const int64_t stride) const {
373 if (row < first_row_) {
374 const int64_t num_before = first_row_ - row;
375 // Mirrored; one row before => row 0, two before = row 1, ...
376 return first_row_ + num_before - stride;
377 }
378 if (row > last_row_) {
379 const int64_t num_after = row - last_row_;
380 // Mirrored; one row after => last row, two after = last - 1, ...
381 return last_row_ - num_after + stride;
382 }
383 return row;
384 }
385
386 private:
387 const float* const JXL_RESTRICT first_row_;
388 const float* const JXL_RESTRICT last_row_;
389 };
390
391 struct WrapRowUnchanged {
operatorWrapRowUnchanged392 JXL_INLINE const float* operator()(const float* const JXL_RESTRICT row,
393 int64_t /*stride*/) const {
394 return row;
395 }
396 };
397
398 // Sets "thickness" pixels on each border to "value". This is faster than
399 // initializing the entire image and overwriting valid/interior pixels.
400 template <typename T>
SetBorder(const size_t thickness,const T value,Plane<T> * image)401 void SetBorder(const size_t thickness, const T value, Plane<T>* image) {
402 const size_t xsize = image->xsize();
403 const size_t ysize = image->ysize();
404 // Top: fill entire row
405 for (size_t y = 0; y < std::min(thickness, ysize); ++y) {
406 T* const JXL_RESTRICT row = image->Row(y);
407 std::fill(row, row + xsize, value);
408 }
409
410 // Bottom: fill entire row
411 for (size_t y = ysize - thickness; y < ysize; ++y) {
412 T* const JXL_RESTRICT row = image->Row(y);
413 std::fill(row, row + xsize, value);
414 }
415
416 // Left/right: fill the 'columns' on either side, but only if the image is
417 // big enough that they don't already belong to the top/bottom rows.
418 if (ysize >= 2 * thickness) {
419 for (size_t y = thickness; y < ysize - thickness; ++y) {
420 T* const JXL_RESTRICT row = image->Row(y);
421 std::fill(row, row + thickness, value);
422 std::fill(row + xsize - thickness, row + xsize, value);
423 }
424 }
425 }
426
427 // Computes the minimum and maximum pixel value.
428 template <typename T>
ImageMinMax(const Plane<T> & image,T * const JXL_RESTRICT min,T * const JXL_RESTRICT max)429 void ImageMinMax(const Plane<T>& image, T* const JXL_RESTRICT min,
430 T* const JXL_RESTRICT max) {
431 *min = std::numeric_limits<T>::max();
432 *max = std::numeric_limits<T>::lowest();
433 for (size_t y = 0; y < image.ysize(); ++y) {
434 const T* const JXL_RESTRICT row = image.Row(y);
435 for (size_t x = 0; x < image.xsize(); ++x) {
436 *min = std::min(*min, row[x]);
437 *max = std::max(*max, row[x]);
438 }
439 }
440 }
441
442 // Copies pixels, scaling their value relative to the "from" min/max by
443 // "to_range". Example: U8 [0, 255] := [0.0, 1.0], to_range = 1.0 =>
444 // outputs [0.0, 1.0].
445 template <typename FromType, typename ToType>
ImageConvert(const Plane<FromType> & from,const float to_range,Plane<ToType> * const JXL_RESTRICT to)446 void ImageConvert(const Plane<FromType>& from, const float to_range,
447 Plane<ToType>* const JXL_RESTRICT to) {
448 JXL_ASSERT(SameSize(from, *to));
449 FromType min_from, max_from;
450 ImageMinMax(from, &min_from, &max_from);
451 const float scale = to_range / (max_from - min_from);
452 for (size_t y = 0; y < from.ysize(); ++y) {
453 const FromType* const JXL_RESTRICT row_from = from.Row(y);
454 ToType* const JXL_RESTRICT row_to = to->Row(y);
455 for (size_t x = 0; x < from.xsize(); ++x) {
456 row_to[x] = static_cast<ToType>((row_from[x] - min_from) * scale);
457 }
458 }
459 }
460
461 template <typename From>
ConvertToFloat(const Plane<From> & from)462 Plane<float> ConvertToFloat(const Plane<From>& from) {
463 float factor = 1.0f / std::numeric_limits<From>::max();
464 if (std::is_same<From, double>::value || std::is_same<From, float>::value) {
465 factor = 1.0f;
466 }
467 Plane<float> to(from.xsize(), from.ysize());
468 for (size_t y = 0; y < from.ysize(); ++y) {
469 const From* const JXL_RESTRICT row_from = from.Row(y);
470 float* const JXL_RESTRICT row_to = to.Row(y);
471 for (size_t x = 0; x < from.xsize(); ++x) {
472 row_to[x] = row_from[x] * factor;
473 }
474 }
475 return to;
476 }
477
478 template <typename T>
ImageFromPacked(const std::vector<T> & packed,const size_t xsize,const size_t ysize)479 Plane<T> ImageFromPacked(const std::vector<T>& packed, const size_t xsize,
480 const size_t ysize) {
481 Plane<T> out(xsize, ysize);
482 for (size_t y = 0; y < ysize; ++y) {
483 T* const JXL_RESTRICT row = out.Row(y);
484 const T* const JXL_RESTRICT packed_row = &packed[y * xsize];
485 memcpy(row, packed_row, xsize * sizeof(T));
486 }
487 return out;
488 }
489
490 // Computes independent minimum and maximum values for each plane.
491 template <typename T>
Image3MinMax(const Image3<T> & image,const Rect & rect,std::array<T,3> * out_min,std::array<T,3> * out_max)492 void Image3MinMax(const Image3<T>& image, const Rect& rect,
493 std::array<T, 3>* out_min, std::array<T, 3>* out_max) {
494 for (size_t c = 0; c < 3; ++c) {
495 T min = std::numeric_limits<T>::max();
496 T max = std::numeric_limits<T>::min();
497 for (size_t y = 0; y < rect.ysize(); ++y) {
498 const T* JXL_RESTRICT row = rect.ConstPlaneRow(image, c, y);
499 for (size_t x = 0; x < rect.xsize(); ++x) {
500 min = std::min(min, row[x]);
501 max = std::max(max, row[x]);
502 }
503 }
504 (*out_min)[c] = min;
505 (*out_max)[c] = max;
506 }
507 }
508
509 // Computes independent minimum and maximum values for each plane.
510 template <typename T>
Image3MinMax(const Image3<T> & image,std::array<T,3> * out_min,std::array<T,3> * out_max)511 void Image3MinMax(const Image3<T>& image, std::array<T, 3>* out_min,
512 std::array<T, 3>* out_max) {
513 Image3MinMax(image, Rect(image), out_min, out_max);
514 }
515
516 template <typename T>
Image3Max(const Image3<T> & image,std::array<T,3> * out_max)517 void Image3Max(const Image3<T>& image, std::array<T, 3>* out_max) {
518 for (size_t c = 0; c < 3; ++c) {
519 T max = std::numeric_limits<T>::min();
520 for (size_t y = 0; y < image.ysize(); ++y) {
521 const T* JXL_RESTRICT row = image.ConstPlaneRow(c, y);
522 for (size_t x = 0; x < image.xsize(); ++x) {
523 max = std::max(max, row[x]);
524 }
525 }
526 (*out_max)[c] = max;
527 }
528 }
529
530 // Computes the sum of the pixels in `rect`.
531 template <typename T>
ImageSum(const Plane<T> & image,const Rect & rect)532 T ImageSum(const Plane<T>& image, const Rect& rect) {
533 T result = 0;
534 for (size_t y = 0; y < rect.ysize(); ++y) {
535 const T* JXL_RESTRICT row = rect.ConstRow(image, y);
536 for (size_t x = 0; x < rect.xsize(); ++x) {
537 result += row[x];
538 }
539 }
540 return result;
541 }
542
543 template <typename T>
ImageSum(const Plane<T> & image)544 T ImageSum(const Plane<T>& image) {
545 return ImageSum(image, Rect(image));
546 }
547
548 template <typename T>
Image3Sum(const Image3<T> & image,const Rect & rect)549 std::array<T, 3> Image3Sum(const Image3<T>& image, const Rect& rect) {
550 std::array<T, 3> out_sum = 0;
551 for (size_t c = 0; c < 3; ++c) {
552 (out_sum)[c] = ImageSum(image.Plane(c), rect);
553 }
554 return out_sum;
555 }
556
557 template <typename T>
Image3Sum(const Image3<T> & image)558 std::array<T, 3> Image3Sum(const Image3<T>& image) {
559 return Image3Sum(image, Rect(image));
560 }
561
562 template <typename T>
PackedFromImage(const Plane<T> & image,const Rect & rect)563 std::vector<T> PackedFromImage(const Plane<T>& image, const Rect& rect) {
564 const size_t xsize = rect.xsize();
565 const size_t ysize = rect.ysize();
566 std::vector<T> packed(xsize * ysize);
567 for (size_t y = 0; y < rect.ysize(); ++y) {
568 memcpy(&packed[y * xsize], rect.ConstRow(image, y), xsize * sizeof(T));
569 }
570 return packed;
571 }
572
573 template <typename T>
PackedFromImage(const Plane<T> & image)574 std::vector<T> PackedFromImage(const Plane<T>& image) {
575 return PackedFromImage(image, Rect(image));
576 }
577
578 // Computes the median pixel value.
579 template <typename T>
ImageMedian(const Plane<T> & image,const Rect & rect)580 T ImageMedian(const Plane<T>& image, const Rect& rect) {
581 std::vector<T> pixels = PackedFromImage(image, rect);
582 return Median(&pixels);
583 }
584
585 template <typename T>
ImageMedian(const Plane<T> & image)586 T ImageMedian(const Plane<T>& image) {
587 return ImageMedian(image, Rect(image));
588 }
589
590 template <typename T>
Image3Median(const Image3<T> & image,const Rect & rect)591 std::array<T, 3> Image3Median(const Image3<T>& image, const Rect& rect) {
592 std::array<T, 3> out_median;
593 for (size_t c = 0; c < 3; ++c) {
594 (out_median)[c] = ImageMedian(image.Plane(c), rect);
595 }
596 return out_median;
597 }
598
599 template <typename T>
Image3Median(const Image3<T> & image)600 std::array<T, 3> Image3Median(const Image3<T>& image) {
601 return Image3Median(image, Rect(image));
602 }
603
604 template <typename FromType, typename ToType>
Image3Convert(const Image3<FromType> & from,const float to_range,Image3<ToType> * const JXL_RESTRICT to)605 void Image3Convert(const Image3<FromType>& from, const float to_range,
606 Image3<ToType>* const JXL_RESTRICT to) {
607 JXL_ASSERT(SameSize(from, *to));
608 std::array<FromType, 3> min_from, max_from;
609 Image3MinMax(from, &min_from, &max_from);
610 float scales[3];
611 for (size_t c = 0; c < 3; ++c) {
612 scales[c] = to_range / (max_from[c] - min_from[c]);
613 }
614 float scale = std::min(scales[0], std::min(scales[1], scales[2]));
615 for (size_t c = 0; c < 3; ++c) {
616 for (size_t y = 0; y < from.ysize(); ++y) {
617 const FromType* JXL_RESTRICT row_from = from.ConstPlaneRow(c, y);
618 ToType* JXL_RESTRICT row_to = to->PlaneRow(c, y);
619 for (size_t x = 0; x < from.xsize(); ++x) {
620 const float to = (row_from[x] - min_from[c]) * scale;
621 row_to[x] = static_cast<ToType>(to);
622 }
623 }
624 }
625 }
626
627 template <typename From>
ConvertToFloat(const Image3<From> & from)628 Image3F ConvertToFloat(const Image3<From>& from) {
629 return Image3F(ConvertToFloat(from.Plane(0)), ConvertToFloat(from.Plane(1)),
630 ConvertToFloat(from.Plane(2)));
631 }
632
633 template <typename Tin, typename Tout>
Subtract(const Image3<Tin> & image1,const Image3<Tin> & image2,Image3<Tout> * out)634 void Subtract(const Image3<Tin>& image1, const Image3<Tin>& image2,
635 Image3<Tout>* out) {
636 const size_t xsize = image1.xsize();
637 const size_t ysize = image1.ysize();
638 JXL_CHECK(xsize == image2.xsize());
639 JXL_CHECK(ysize == image2.ysize());
640
641 for (size_t c = 0; c < 3; ++c) {
642 for (size_t y = 0; y < ysize; ++y) {
643 const Tin* const JXL_RESTRICT row1 = image1.ConstPlaneRow(c, y);
644 const Tin* const JXL_RESTRICT row2 = image2.ConstPlaneRow(c, y);
645 Tout* const JXL_RESTRICT row_out = out->PlaneRow(c, y);
646 for (size_t x = 0; x < xsize; ++x) {
647 row_out[x] = row1[x] - row2[x];
648 }
649 }
650 }
651 }
652
653 template <typename Tin, typename Tout>
SubtractFrom(const Image3<Tin> & what,Image3<Tout> * to)654 void SubtractFrom(const Image3<Tin>& what, Image3<Tout>* to) {
655 const size_t xsize = what.xsize();
656 const size_t ysize = what.ysize();
657 for (size_t c = 0; c < 3; ++c) {
658 for (size_t y = 0; y < ysize; ++y) {
659 const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y);
660 Tout* JXL_RESTRICT row_to = to->PlaneRow(c, y);
661 for (size_t x = 0; x < xsize; ++x) {
662 row_to[x] -= row_what[x];
663 }
664 }
665 }
666 }
667
668 template <typename Tin, typename Tout>
AddTo(const Image3<Tin> & what,Image3<Tout> * to)669 void AddTo(const Image3<Tin>& what, Image3<Tout>* to) {
670 const size_t xsize = what.xsize();
671 const size_t ysize = what.ysize();
672 for (size_t c = 0; c < 3; ++c) {
673 for (size_t y = 0; y < ysize; ++y) {
674 const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y);
675 Tout* JXL_RESTRICT row_to = to->PlaneRow(c, y);
676 for (size_t x = 0; x < xsize; ++x) {
677 row_to[x] += row_what[x];
678 }
679 }
680 }
681 }
682
683 // Adds `what` of the size of `rect` to `to` in the position of `rect`.
684 template <typename Tin, typename Tout>
AddTo(const Rect & rect,const Image3<Tin> & what,Image3<Tout> * to)685 void AddTo(const Rect& rect, const Image3<Tin>& what, Image3<Tout>* to) {
686 const size_t xsize = what.xsize();
687 const size_t ysize = what.ysize();
688 JXL_ASSERT(xsize == rect.xsize());
689 JXL_ASSERT(ysize == rect.ysize());
690 for (size_t c = 0; c < 3; ++c) {
691 for (size_t y = 0; y < ysize; ++y) {
692 const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y);
693 Tout* JXL_RESTRICT row_to = rect.PlaneRow(to, c, y);
694 for (size_t x = 0; x < xsize; ++x) {
695 row_to[x] += row_what[x];
696 }
697 }
698 }
699 }
700
701 template <typename T>
ScaleImage(const T lambda,const Image3<T> & image)702 Image3<T> ScaleImage(const T lambda, const Image3<T>& image) {
703 Image3<T> out(image.xsize(), image.ysize());
704 for (size_t c = 0; c < 3; ++c) {
705 for (size_t y = 0; y < image.ysize(); ++y) {
706 const T* JXL_RESTRICT row = image.ConstPlaneRow(c, y);
707 T* JXL_RESTRICT row_out = out.PlaneRow(c, y);
708 for (size_t x = 0; x < image.xsize(); ++x) {
709 row_out[x] = lambda * row[x];
710 }
711 }
712 }
713 return out;
714 }
715
716 // Multiplies image by lambda in-place
717 template <typename T>
ScaleImage(const T lambda,Image3<T> * image)718 void ScaleImage(const T lambda, Image3<T>* image) {
719 for (size_t c = 0; c < 3; ++c) {
720 for (size_t y = 0; y < image->ysize(); ++y) {
721 T* const JXL_RESTRICT row = image->PlaneRow(c, y);
722 for (size_t x = 0; x < image->xsize(); ++x) {
723 row[x] = lambda * row[x];
724 }
725 }
726 }
727 }
728
729 // Initializes all planes to the same "value".
730 template <typename T>
FillImage(const T value,Image3<T> * image)731 void FillImage(const T value, Image3<T>* image) {
732 for (size_t c = 0; c < 3; ++c) {
733 for (size_t y = 0; y < image->ysize(); ++y) {
734 T* JXL_RESTRICT row = image->PlaneRow(c, y);
735 for (size_t x = 0; x < image->xsize(); ++x) {
736 row[x] = value;
737 }
738 }
739 }
740 }
741
742 template <typename T>
FillPlane(const T value,Plane<T> * image)743 void FillPlane(const T value, Plane<T>* image) {
744 for (size_t y = 0; y < image->ysize(); ++y) {
745 T* JXL_RESTRICT row = image->Row(y);
746 for (size_t x = 0; x < image->xsize(); ++x) {
747 row[x] = value;
748 }
749 }
750 }
751
752 template <typename T>
FillImage(const T value,Image3<T> * image,Rect rect)753 void FillImage(const T value, Image3<T>* image, Rect rect) {
754 for (size_t c = 0; c < 3; ++c) {
755 for (size_t y = 0; y < rect.ysize(); ++y) {
756 T* JXL_RESTRICT row = rect.PlaneRow(image, c, y);
757 for (size_t x = 0; x < rect.xsize(); ++x) {
758 row[x] = value;
759 }
760 }
761 }
762 }
763
764 template <typename T>
FillPlane(const T value,Plane<T> * image,Rect rect)765 void FillPlane(const T value, Plane<T>* image, Rect rect) {
766 for (size_t y = 0; y < rect.ysize(); ++y) {
767 T* JXL_RESTRICT row = rect.Row(image, y);
768 for (size_t x = 0; x < rect.xsize(); ++x) {
769 row[x] = value;
770 }
771 }
772 }
773
774 template <typename T>
ZeroFillImage(Image3<T> * image)775 void ZeroFillImage(Image3<T>* image) {
776 for (size_t c = 0; c < 3; ++c) {
777 for (size_t y = 0; y < image->ysize(); ++y) {
778 T* JXL_RESTRICT row = image->PlaneRow(c, y);
779 if (image->xsize() != 0) memset(row, 0, image->xsize() * sizeof(T));
780 }
781 }
782 }
783
784 template <typename T>
ZeroFillPlane(Plane<T> * image,Rect rect)785 void ZeroFillPlane(Plane<T>* image, Rect rect) {
786 for (size_t y = 0; y < rect.ysize(); ++y) {
787 T* JXL_RESTRICT row = rect.Row(image, y);
788 memset(row, 0, rect.xsize() * sizeof(T));
789 }
790 }
791
792 // First, image is padded horizontally, with the rightmost value.
793 // Next, image is padded vertically, by repeating the last line.
794 ImageF PadImage(const ImageF& in, size_t xsize, size_t ysize);
795
796 // Pad an image with xborder columns on each vertical side and yboder rows
797 // above and below, mirroring the image.
798 Image3F PadImageMirror(const Image3F& in, size_t xborder, size_t yborder);
799
800 // First, image is padded horizontally, with the rightmost value.
801 // Next, image is padded vertically, by repeating the last line.
802 // Prefer PadImageToBlockMultipleInPlace if padding to kBlockDim.
803 Image3F PadImageToMultiple(const Image3F& in, size_t N);
804
805 // Same as above, but operates in-place. Assumes that the `in` image was
806 // allocated large enough.
807 void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in);
808
809 // Downsamples an image by a given factor.
810 void DownsampleImage(Image3F* opsin, size_t factor);
811 void DownsampleImage(ImageF* image, size_t factor);
812
813 } // namespace jxl
814
815 #endif // LIB_JXL_IMAGE_OPS_H_
816