// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #ifndef LIB_JXL_IMAGE_OPS_H_ #define LIB_JXL_IMAGE_OPS_H_ // Operations on images. #include #include #include #include #include "lib/jxl/base/profiler.h" #include "lib/jxl/base/status.h" #include "lib/jxl/common.h" #include "lib/jxl/image.h" namespace jxl { template void CopyImageTo(const Plane& from, Plane* JXL_RESTRICT to) { PROFILER_ZONE("CopyImage1"); JXL_ASSERT(SameSize(from, *to)); if (from.ysize() == 0 || from.xsize() == 0) return; for (size_t y = 0; y < from.ysize(); ++y) { const T* JXL_RESTRICT row_from = from.ConstRow(y); T* JXL_RESTRICT row_to = to->Row(y); memcpy(row_to, row_from, from.xsize() * sizeof(T)); } } // DEPRECATED - prefer to preallocate result. template Plane CopyImage(const Plane& from) { Plane to(from.xsize(), from.ysize()); CopyImageTo(from, &to); return to; } // Copies `from:rect_from` to `to:rect_to`. template void CopyImageTo(const Rect& rect_from, const Plane& from, const Rect& rect_to, Plane* JXL_RESTRICT to) { PROFILER_ZONE("CopyImageR"); JXL_DASSERT(SameSize(rect_from, rect_to)); JXL_DASSERT(rect_from.IsInside(from)); JXL_DASSERT(rect_to.IsInside(*to)); if (rect_from.xsize() == 0) return; for (size_t y = 0; y < rect_from.ysize(); ++y) { const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y); T* JXL_RESTRICT row_to = rect_to.Row(to, y); memcpy(row_to, row_from, rect_from.xsize() * sizeof(T)); } } // DEPRECATED - Returns a copy of the "image" pixels that lie in "rect". template Plane CopyImage(const Rect& rect, const Plane& image) { Plane copy(rect.xsize(), rect.ysize()); CopyImageTo(rect, image, ©); return copy; } // Copies `from:rect_from` to `to:rect_to`. template void CopyImageTo(const Rect& rect_from, const Image3& from, const Rect& rect_to, Image3* JXL_RESTRICT to) { PROFILER_ZONE("CopyImageR"); JXL_ASSERT(SameSize(rect_from, rect_to)); for (size_t c = 0; c < 3; c++) { CopyImageTo(rect_from, from.Plane(c), rect_to, &to->Plane(c)); } } template void ConvertPlaneAndClamp(const Rect& rect_from, const Plane& from, const Rect& rect_to, Plane* JXL_RESTRICT to) { PROFILER_ZONE("ConvertPlane"); JXL_ASSERT(SameSize(rect_from, rect_to)); using M = decltype(T() + U()); for (size_t y = 0; y < rect_to.ysize(); ++y) { const T* JXL_RESTRICT row_from = rect_from.ConstRow(from, y); U* JXL_RESTRICT row_to = rect_to.Row(to, y); for (size_t x = 0; x < rect_to.xsize(); ++x) { row_to[x] = std::min(std::max(row_from[x], std::numeric_limits::min()), std::numeric_limits::max()); } } } // Copies `from` to `to`. template void CopyImageTo(const T& from, T* JXL_RESTRICT to) { return CopyImageTo(Rect(from), from, Rect(*to), to); } // Copies `from:rect_from` to `to`. template void CopyImageTo(const Rect& rect_from, const T& from, T* JXL_RESTRICT to) { return CopyImageTo(rect_from, from, Rect(*to), to); } // Copies `from` to `to:rect_to`. template void CopyImageTo(const T& from, const Rect& rect_to, T* JXL_RESTRICT to) { return CopyImageTo(Rect(from), from, rect_to, to); } // Copies `from:rect_from` to `to:rect_to`; also copies `padding` pixels of // border around `from:rect_from`, in all directions, whenever they are inside // the first image. template void CopyImageToWithPadding(const Rect& from_rect, const T& from, size_t padding, const Rect& to_rect, T* to) { size_t xextra0 = std::min(padding, from_rect.x0()); size_t xextra1 = std::min(padding, from.xsize() - from_rect.x0() - from_rect.xsize()); size_t yextra0 = std::min(padding, from_rect.y0()); size_t yextra1 = std::min(padding, from.ysize() - from_rect.y0() - from_rect.ysize()); JXL_DASSERT(to_rect.x0() >= xextra0); JXL_DASSERT(to_rect.y0() >= yextra0); return CopyImageTo(Rect(from_rect.x0() - xextra0, from_rect.y0() - yextra0, from_rect.xsize() + xextra0 + xextra1, from_rect.ysize() + yextra0 + yextra1), from, Rect(to_rect.x0() - xextra0, to_rect.y0() - yextra0, to_rect.xsize() + xextra0 + xextra1, to_rect.ysize() + yextra0 + yextra1), to); } // DEPRECATED - prefer to preallocate result. template Image3 CopyImage(const Image3& from) { Image3 copy(from.xsize(), from.ysize()); CopyImageTo(from, ©); return copy; } // DEPRECATED - prefer to preallocate result. template Image3 CopyImage(const Rect& rect, const Image3& from) { Image3 to(rect.xsize(), rect.ysize()); CopyImageTo(rect, from.Plane(0), to.Plane(0)); CopyImageTo(rect, from.Plane(1), to.Plane(1)); CopyImageTo(rect, from.Plane(2), to.Plane(2)); return to; } // Sets "thickness" pixels on each border to "value". This is faster than // initializing the entire image and overwriting valid/interior pixels. template void SetBorder(const size_t thickness, const T value, Image3* image) { const size_t xsize = image->xsize(); const size_t ysize = image->ysize(); // Top: fill entire row for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < std::min(thickness, ysize); ++y) { T* JXL_RESTRICT row = image->PlaneRow(c, y); std::fill(row, row + xsize, value); } // Bottom: fill entire row for (size_t y = ysize - thickness; y < ysize; ++y) { T* JXL_RESTRICT row = image->PlaneRow(c, y); std::fill(row, row + xsize, value); } // Left/right: fill the 'columns' on either side, but only if the image is // big enough that they don't already belong to the top/bottom rows. if (ysize >= 2 * thickness) { for (size_t y = thickness; y < ysize - thickness; ++y) { T* JXL_RESTRICT row = image->PlaneRow(c, y); std::fill(row, row + thickness, value); std::fill(row + xsize - thickness, row + xsize, value); } } } } template void Subtract(const ImageIn& image1, const ImageIn& image2, ImageOut* out) { using T = typename ImageIn::T; const size_t xsize = image1.xsize(); const size_t ysize = image1.ysize(); JXL_CHECK(xsize == image2.xsize()); JXL_CHECK(ysize == image2.ysize()); for (size_t y = 0; y < ysize; ++y) { const T* const JXL_RESTRICT row1 = image1.Row(y); const T* const JXL_RESTRICT row2 = image2.Row(y); T* const JXL_RESTRICT row_out = out->Row(y); for (size_t x = 0; x < xsize; ++x) { row_out[x] = row1[x] - row2[x]; } } } // In-place. template void SubtractFrom(const Plane& what, Plane* to) { const size_t xsize = what.xsize(); const size_t ysize = what.ysize(); for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = what.ConstRow(y); Tout* JXL_RESTRICT row_to = to->Row(y); for (size_t x = 0; x < xsize; ++x) { row_to[x] -= row_what[x]; } } } // In-place. template void AddTo(const Plane& what, Plane* to) { const size_t xsize = what.xsize(); const size_t ysize = what.ysize(); for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = what.ConstRow(y); Tout* JXL_RESTRICT row_to = to->Row(y); for (size_t x = 0; x < xsize; ++x) { row_to[x] += row_what[x]; } } } template void AddTo(Rect rectFrom, const Plane& what, Rect rectTo, Plane* to) { JXL_ASSERT(SameSize(rectFrom, rectTo)); const size_t xsize = rectTo.xsize(); const size_t ysize = rectTo.ysize(); for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = rectFrom.ConstRow(what, y); Tout* JXL_RESTRICT row_to = rectTo.Row(to, y); for (size_t x = 0; x < xsize; ++x) { row_to[x] += row_what[x]; } } } // Returns linear combination of two grayscale images. template Plane LinComb(const T lambda1, const Plane& image1, const T lambda2, const Plane& image2) { const size_t xsize = image1.xsize(); const size_t ysize = image1.ysize(); JXL_CHECK(xsize == image2.xsize()); JXL_CHECK(ysize == image2.ysize()); Plane out(xsize, ysize); for (size_t y = 0; y < ysize; ++y) { const T* const JXL_RESTRICT row1 = image1.Row(y); const T* const JXL_RESTRICT row2 = image2.Row(y); T* const JXL_RESTRICT row_out = out.Row(y); for (size_t x = 0; x < xsize; ++x) { row_out[x] = lambda1 * row1[x] + lambda2 * row2[x]; } } return out; } // Returns a pixel-by-pixel multiplication of image by lambda. template Plane ScaleImage(const T lambda, const Plane& image) { Plane out(image.xsize(), image.ysize()); for (size_t y = 0; y < image.ysize(); ++y) { const T* const JXL_RESTRICT row = image.Row(y); T* const JXL_RESTRICT row_out = out.Row(y); for (size_t x = 0; x < image.xsize(); ++x) { row_out[x] = lambda * row[x]; } } return out; } // Multiplies image by lambda in-place template void ScaleImage(const T lambda, Plane* image) { for (size_t y = 0; y < image->ysize(); ++y) { T* const JXL_RESTRICT row = image->Row(y); for (size_t x = 0; x < image->xsize(); ++x) { row[x] = lambda * row[x]; } } } template Plane Product(const Plane& a, const Plane& b) { Plane c(a.xsize(), a.ysize()); for (size_t y = 0; y < a.ysize(); ++y) { const T* const JXL_RESTRICT row_a = a.Row(y); const T* const JXL_RESTRICT row_b = b.Row(y); T* const JXL_RESTRICT row_c = c.Row(y); for (size_t x = 0; x < a.xsize(); ++x) { row_c[x] = row_a[x] * row_b[x]; } } return c; } template void FillImage(const T value, Plane* image) { for (size_t y = 0; y < image->ysize(); ++y) { T* const JXL_RESTRICT row = image->Row(y); for (size_t x = 0; x < image->xsize(); ++x) { row[x] = value; } } } template void ZeroFillImage(Plane* image) { if (image->xsize() == 0) return; for (size_t y = 0; y < image->ysize(); ++y) { T* const JXL_RESTRICT row = image->Row(y); memset(row, 0, image->xsize() * sizeof(T)); } } // Mirrors out of bounds coordinates and returns valid coordinates unchanged. // We assume the radius (distance outside the image) is small compared to the // image size, otherwise this might not terminate. // The mirror is outside the last column (border pixel is also replicated). static inline int64_t Mirror(int64_t x, const int64_t xsize) { JXL_DASSERT(xsize != 0); // TODO(janwas): replace with branchless version while (x < 0 || x >= xsize) { if (x < 0) { x = -x - 1; } else { x = 2 * xsize - 1 - x; } } return x; } // Wrap modes for ensuring X/Y coordinates are in the valid range [0, size): // Mirrors (repeating the edge pixel once). Useful for convolutions. struct WrapMirror { JXL_INLINE int64_t operator()(const int64_t coord, const int64_t size) const { return Mirror(coord, size); } }; // Returns the same coordinate: required for TFNode with Border(), or useful // when we know "coord" is already valid (e.g. interior of an image). struct WrapUnchanged { JXL_INLINE int64_t operator()(const int64_t coord, int64_t /*size*/) const { return coord; } }; // Similar to Wrap* but for row pointers (reduces Row() multiplications). class WrapRowMirror { public: template WrapRowMirror(const ImageOrView& image, size_t ysize) : first_row_(image.ConstRow(0)), last_row_(image.ConstRow(ysize - 1)) {} const float* operator()(const float* const JXL_RESTRICT row, const int64_t stride) const { if (row < first_row_) { const int64_t num_before = first_row_ - row; // Mirrored; one row before => row 0, two before = row 1, ... return first_row_ + num_before - stride; } if (row > last_row_) { const int64_t num_after = row - last_row_; // Mirrored; one row after => last row, two after = last - 1, ... return last_row_ - num_after + stride; } return row; } private: const float* const JXL_RESTRICT first_row_; const float* const JXL_RESTRICT last_row_; }; struct WrapRowUnchanged { JXL_INLINE const float* operator()(const float* const JXL_RESTRICT row, int64_t /*stride*/) const { return row; } }; // Sets "thickness" pixels on each border to "value". This is faster than // initializing the entire image and overwriting valid/interior pixels. template void SetBorder(const size_t thickness, const T value, Plane* image) { const size_t xsize = image->xsize(); const size_t ysize = image->ysize(); // Top: fill entire row for (size_t y = 0; y < std::min(thickness, ysize); ++y) { T* const JXL_RESTRICT row = image->Row(y); std::fill(row, row + xsize, value); } // Bottom: fill entire row for (size_t y = ysize - thickness; y < ysize; ++y) { T* const JXL_RESTRICT row = image->Row(y); std::fill(row, row + xsize, value); } // Left/right: fill the 'columns' on either side, but only if the image is // big enough that they don't already belong to the top/bottom rows. if (ysize >= 2 * thickness) { for (size_t y = thickness; y < ysize - thickness; ++y) { T* const JXL_RESTRICT row = image->Row(y); std::fill(row, row + thickness, value); std::fill(row + xsize - thickness, row + xsize, value); } } } // Computes the minimum and maximum pixel value. template void ImageMinMax(const Plane& image, T* const JXL_RESTRICT min, T* const JXL_RESTRICT max) { *min = std::numeric_limits::max(); *max = std::numeric_limits::lowest(); for (size_t y = 0; y < image.ysize(); ++y) { const T* const JXL_RESTRICT row = image.Row(y); for (size_t x = 0; x < image.xsize(); ++x) { *min = std::min(*min, row[x]); *max = std::max(*max, row[x]); } } } // Copies pixels, scaling their value relative to the "from" min/max by // "to_range". Example: U8 [0, 255] := [0.0, 1.0], to_range = 1.0 => // outputs [0.0, 1.0]. template void ImageConvert(const Plane& from, const float to_range, Plane* const JXL_RESTRICT to) { JXL_ASSERT(SameSize(from, *to)); FromType min_from, max_from; ImageMinMax(from, &min_from, &max_from); const float scale = to_range / (max_from - min_from); for (size_t y = 0; y < from.ysize(); ++y) { const FromType* const JXL_RESTRICT row_from = from.Row(y); ToType* const JXL_RESTRICT row_to = to->Row(y); for (size_t x = 0; x < from.xsize(); ++x) { row_to[x] = static_cast((row_from[x] - min_from) * scale); } } } template Plane ConvertToFloat(const Plane& from) { float factor = 1.0f / std::numeric_limits::max(); if (std::is_same::value || std::is_same::value) { factor = 1.0f; } Plane to(from.xsize(), from.ysize()); for (size_t y = 0; y < from.ysize(); ++y) { const From* const JXL_RESTRICT row_from = from.Row(y); float* const JXL_RESTRICT row_to = to.Row(y); for (size_t x = 0; x < from.xsize(); ++x) { row_to[x] = row_from[x] * factor; } } return to; } template Plane ImageFromPacked(const std::vector& packed, const size_t xsize, const size_t ysize) { Plane out(xsize, ysize); for (size_t y = 0; y < ysize; ++y) { T* const JXL_RESTRICT row = out.Row(y); const T* const JXL_RESTRICT packed_row = &packed[y * xsize]; memcpy(row, packed_row, xsize * sizeof(T)); } return out; } // Computes independent minimum and maximum values for each plane. template void Image3MinMax(const Image3& image, const Rect& rect, std::array* out_min, std::array* out_max) { for (size_t c = 0; c < 3; ++c) { T min = std::numeric_limits::max(); T max = std::numeric_limits::min(); for (size_t y = 0; y < rect.ysize(); ++y) { const T* JXL_RESTRICT row = rect.ConstPlaneRow(image, c, y); for (size_t x = 0; x < rect.xsize(); ++x) { min = std::min(min, row[x]); max = std::max(max, row[x]); } } (*out_min)[c] = min; (*out_max)[c] = max; } } // Computes independent minimum and maximum values for each plane. template void Image3MinMax(const Image3& image, std::array* out_min, std::array* out_max) { Image3MinMax(image, Rect(image), out_min, out_max); } template void Image3Max(const Image3& image, std::array* out_max) { for (size_t c = 0; c < 3; ++c) { T max = std::numeric_limits::min(); for (size_t y = 0; y < image.ysize(); ++y) { const T* JXL_RESTRICT row = image.ConstPlaneRow(c, y); for (size_t x = 0; x < image.xsize(); ++x) { max = std::max(max, row[x]); } } (*out_max)[c] = max; } } // Computes the sum of the pixels in `rect`. template T ImageSum(const Plane& image, const Rect& rect) { T result = 0; for (size_t y = 0; y < rect.ysize(); ++y) { const T* JXL_RESTRICT row = rect.ConstRow(image, y); for (size_t x = 0; x < rect.xsize(); ++x) { result += row[x]; } } return result; } template T ImageSum(const Plane& image) { return ImageSum(image, Rect(image)); } template std::array Image3Sum(const Image3& image, const Rect& rect) { std::array out_sum = 0; for (size_t c = 0; c < 3; ++c) { (out_sum)[c] = ImageSum(image.Plane(c), rect); } return out_sum; } template std::array Image3Sum(const Image3& image) { return Image3Sum(image, Rect(image)); } template std::vector PackedFromImage(const Plane& image, const Rect& rect) { const size_t xsize = rect.xsize(); const size_t ysize = rect.ysize(); std::vector packed(xsize * ysize); for (size_t y = 0; y < rect.ysize(); ++y) { memcpy(&packed[y * xsize], rect.ConstRow(image, y), xsize * sizeof(T)); } return packed; } template std::vector PackedFromImage(const Plane& image) { return PackedFromImage(image, Rect(image)); } // Computes the median pixel value. template T ImageMedian(const Plane& image, const Rect& rect) { std::vector pixels = PackedFromImage(image, rect); return Median(&pixels); } template T ImageMedian(const Plane& image) { return ImageMedian(image, Rect(image)); } template std::array Image3Median(const Image3& image, const Rect& rect) { std::array out_median; for (size_t c = 0; c < 3; ++c) { (out_median)[c] = ImageMedian(image.Plane(c), rect); } return out_median; } template std::array Image3Median(const Image3& image) { return Image3Median(image, Rect(image)); } template void Image3Convert(const Image3& from, const float to_range, Image3* const JXL_RESTRICT to) { JXL_ASSERT(SameSize(from, *to)); std::array min_from, max_from; Image3MinMax(from, &min_from, &max_from); float scales[3]; for (size_t c = 0; c < 3; ++c) { scales[c] = to_range / (max_from[c] - min_from[c]); } float scale = std::min(scales[0], std::min(scales[1], scales[2])); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < from.ysize(); ++y) { const FromType* JXL_RESTRICT row_from = from.ConstPlaneRow(c, y); ToType* JXL_RESTRICT row_to = to->PlaneRow(c, y); for (size_t x = 0; x < from.xsize(); ++x) { const float to = (row_from[x] - min_from[c]) * scale; row_to[x] = static_cast(to); } } } } template Image3F ConvertToFloat(const Image3& from) { return Image3F(ConvertToFloat(from.Plane(0)), ConvertToFloat(from.Plane(1)), ConvertToFloat(from.Plane(2))); } template void Subtract(const Image3& image1, const Image3& image2, Image3* out) { const size_t xsize = image1.xsize(); const size_t ysize = image1.ysize(); JXL_CHECK(xsize == image2.xsize()); JXL_CHECK(ysize == image2.ysize()); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < ysize; ++y) { const Tin* const JXL_RESTRICT row1 = image1.ConstPlaneRow(c, y); const Tin* const JXL_RESTRICT row2 = image2.ConstPlaneRow(c, y); Tout* const JXL_RESTRICT row_out = out->PlaneRow(c, y); for (size_t x = 0; x < xsize; ++x) { row_out[x] = row1[x] - row2[x]; } } } } template void SubtractFrom(const Image3& what, Image3* to) { const size_t xsize = what.xsize(); const size_t ysize = what.ysize(); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y); Tout* JXL_RESTRICT row_to = to->PlaneRow(c, y); for (size_t x = 0; x < xsize; ++x) { row_to[x] -= row_what[x]; } } } } template void AddTo(const Image3& what, Image3* to) { const size_t xsize = what.xsize(); const size_t ysize = what.ysize(); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y); Tout* JXL_RESTRICT row_to = to->PlaneRow(c, y); for (size_t x = 0; x < xsize; ++x) { row_to[x] += row_what[x]; } } } } // Adds `what` of the size of `rect` to `to` in the position of `rect`. template void AddTo(const Rect& rect, const Image3& what, Image3* to) { const size_t xsize = what.xsize(); const size_t ysize = what.ysize(); JXL_ASSERT(xsize == rect.xsize()); JXL_ASSERT(ysize == rect.ysize()); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < ysize; ++y) { const Tin* JXL_RESTRICT row_what = what.ConstPlaneRow(c, y); Tout* JXL_RESTRICT row_to = rect.PlaneRow(to, c, y); for (size_t x = 0; x < xsize; ++x) { row_to[x] += row_what[x]; } } } } template Image3 ScaleImage(const T lambda, const Image3& image) { Image3 out(image.xsize(), image.ysize()); for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < image.ysize(); ++y) { const T* JXL_RESTRICT row = image.ConstPlaneRow(c, y); T* JXL_RESTRICT row_out = out.PlaneRow(c, y); for (size_t x = 0; x < image.xsize(); ++x) { row_out[x] = lambda * row[x]; } } } return out; } // Multiplies image by lambda in-place template void ScaleImage(const T lambda, Image3* image) { for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < image->ysize(); ++y) { T* const JXL_RESTRICT row = image->PlaneRow(c, y); for (size_t x = 0; x < image->xsize(); ++x) { row[x] = lambda * row[x]; } } } } // Initializes all planes to the same "value". template void FillImage(const T value, Image3* image) { for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < image->ysize(); ++y) { T* JXL_RESTRICT row = image->PlaneRow(c, y); for (size_t x = 0; x < image->xsize(); ++x) { row[x] = value; } } } } template void FillPlane(const T value, Plane* image) { for (size_t y = 0; y < image->ysize(); ++y) { T* JXL_RESTRICT row = image->Row(y); for (size_t x = 0; x < image->xsize(); ++x) { row[x] = value; } } } template void FillImage(const T value, Image3* image, Rect rect) { for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < rect.ysize(); ++y) { T* JXL_RESTRICT row = rect.PlaneRow(image, c, y); for (size_t x = 0; x < rect.xsize(); ++x) { row[x] = value; } } } } template void FillPlane(const T value, Plane* image, Rect rect) { for (size_t y = 0; y < rect.ysize(); ++y) { T* JXL_RESTRICT row = rect.Row(image, y); for (size_t x = 0; x < rect.xsize(); ++x) { row[x] = value; } } } template void ZeroFillImage(Image3* image) { for (size_t c = 0; c < 3; ++c) { for (size_t y = 0; y < image->ysize(); ++y) { T* JXL_RESTRICT row = image->PlaneRow(c, y); if (image->xsize() != 0) memset(row, 0, image->xsize() * sizeof(T)); } } } template void ZeroFillPlane(Plane* image, Rect rect) { for (size_t y = 0; y < rect.ysize(); ++y) { T* JXL_RESTRICT row = rect.Row(image, y); memset(row, 0, rect.xsize() * sizeof(T)); } } // Pad an image with xborder columns on each vertical side and yboder rows // above and below, mirroring the image. Image3F PadImageMirror(const Image3F& in, size_t xborder, size_t yborder); // Same as above, but operates in-place. Assumes that the `in` image was // allocated large enough. void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in, size_t block_dim = kBlockDim); // Downsamples an image by a given factor. void DownsampleImage(Image3F* opsin, size_t factor); void DownsampleImage(ImageF* image, size_t factor); } // namespace jxl #endif // LIB_JXL_IMAGE_OPS_H_