Skip to content

Commit 769f7a4

Browse files
committed
Avoid another copy when RGBA is resampled as RGB
In the case of RGBA, the RGB and A channels are resampled separately, but they are created as a view on the original to pass to the C++ code. The C++ code then copies it to a contiguous buffer, but Agg's RGB resampler supports manually stepping the RGB input by a custom stride. As this step is a template parameter, we can't handle any arbitraray array, but can special case steps of 3 or 4 units, which should cover the common cases of RGB or RGBA-viewed-as-RGB input.
1 parent 2b73e7e commit 769f7a4

File tree

2 files changed

+37
-14
lines changed

2 files changed

+37
-14
lines changed

src/_image_resample.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@ template<typename T> struct is_grayscale<T, std::void_t<decltype(T::r)>> : std::
504504
template<typename T> constexpr bool is_grayscale_v = is_grayscale<T>::value;
505505

506506

507-
template<typename color_type, bool input_has_alpha>
507+
// rgb_step is only used if input_has_alpha=false.
508+
template<typename color_type, bool input_has_alpha, int rgb_step=3>
508509
struct type_mapping
509510
{
510511
using input_blender_type = std::conditional_t<
@@ -526,7 +527,7 @@ struct type_mapping
526527
std::conditional_t<
527528
input_has_alpha,
528529
agg::pixfmt_alpha_blend_rgba<input_blender_type, agg::rendering_buffer>,
529-
agg::pixfmt_alpha_blend_rgb<input_blender_type, agg::rendering_buffer, 3>
530+
agg::pixfmt_alpha_blend_rgb<input_blender_type, agg::rendering_buffer, rgb_step>
530531
>
531532
>;
532533
using output_blender_type = std::conditional_t<
@@ -722,13 +723,14 @@ static void get_filter(const resample_params_t &params,
722723
}
723724

724725

725-
template<typename color_type, bool input_has_alpha = true>
726+
// rgb_step is only used if input_has_alpha=false.
727+
template<typename color_type, bool input_has_alpha = true, int rgb_step = 3>
726728
void resample(
727729
const void *input, int in_width, int in_height, int in_stride,
728730
void *output, int out_width, int out_height, int out_stride,
729731
resample_params_t &params)
730732
{
731-
using type_mapping_t = type_mapping<color_type, input_has_alpha>;
733+
using type_mapping_t = type_mapping<color_type, input_has_alpha, rgb_step>;
732734

733735
using input_pixfmt_t = typename type_mapping_t::input_pixfmt_type;
734736
using output_pixfmt_t = typename type_mapping_t::output_pixfmt_type;

src/_image_wrapper.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,29 @@ image_resample(py::array input_array,
107107
}
108108

109109
py::ssize_t ncomponents = 0;
110+
int rgb_step = 0;
110111
if (ndim == 3) {
111112
ncomponents = input_array.shape(2);
112-
if (ncomponents != 3 && ncomponents != 4) {
113+
if (ncomponents == 3) {
114+
// We special-case a few options in order to avoid copying in the common case.
115+
auto rgb_stride = input_array.strides(1);
116+
auto item_stride = input_array.strides(2);
117+
if (rgb_stride == 3 * item_stride) {
118+
rgb_step = 3;
119+
} else if (rgb_stride == 4 * item_stride) {
120+
rgb_step = 4;
121+
}
122+
} else if (ncomponents != 4) {
113123
throw std::invalid_argument(
114124
"3D input array must be RGB with shape (M, N, 3) or RGBA with shape (M, N, 4), "
115125
"has trailing dimension of {}"_s.format(ncomponents));
116126
}
117127
}
118128

119-
// Ensure input array is contiguous, regardless of dtype
120-
input_array = py::array::ensure(input_array, py::array::c_style);
129+
if (rgb_step == 0) {
130+
// Ensure input array is contiguous, regardless of dtype
131+
input_array = py::array::ensure(input_array, py::array::c_style);
132+
}
121133

122134
// Validate output array
123135
auto out_ndim = output_array.ndim();
@@ -194,13 +206,22 @@ image_resample(py::array input_array,
194206
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, true> :
195207
nullptr
196208
) : (
197-
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false> :
198-
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false> :
199-
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false> :
200-
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false> :
201-
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false> :
202-
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false> :
203-
nullptr)))
209+
(rgb_step == 4) ? (
210+
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false, 4> :
211+
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false, 4> :
212+
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false, 4> :
213+
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false, 4> :
214+
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false, 4> :
215+
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false, 4> :
216+
nullptr
217+
) : (
218+
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false, 3> :
219+
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false, 3> :
220+
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false, 3> :
221+
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false, 3> :
222+
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false, 3> :
223+
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false, 3> :
224+
nullptr))))
204225
{
205226
Py_BEGIN_ALLOW_THREADS
206227
resampler(

0 commit comments

Comments
 (0)