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