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, &copy);
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, &copy);
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