Skip to content

Commit

Permalink
Avoid another copy when RGBA is resampled as RGB
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
QuLogic committed Jan 17, 2025
1 parent 2b73e7e commit 769f7a4
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 14 deletions.
10 changes: 6 additions & 4 deletions src/_image_resample.h
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,8 @@ template<typename T> struct is_grayscale<T, std::void_t<decltype(T::r)>> : std::
template<typename T> constexpr bool is_grayscale_v = is_grayscale<T>::value;


template<typename color_type, bool input_has_alpha>
// rgb_step is only used if input_has_alpha=false.
template<typename color_type, bool input_has_alpha, int rgb_step=3>
struct type_mapping
{
using input_blender_type = std::conditional_t<
Expand All @@ -526,7 +527,7 @@ struct type_mapping
std::conditional_t<
input_has_alpha,
agg::pixfmt_alpha_blend_rgba<input_blender_type, agg::rendering_buffer>,
agg::pixfmt_alpha_blend_rgb<input_blender_type, agg::rendering_buffer, 3>
agg::pixfmt_alpha_blend_rgb<input_blender_type, agg::rendering_buffer, rgb_step>
>
>;
using output_blender_type = std::conditional_t<
Expand Down Expand Up @@ -722,13 +723,14 @@ static void get_filter(const resample_params_t &params,
}


template<typename color_type, bool input_has_alpha = true>
// rgb_step is only used if input_has_alpha=false.
template<typename color_type, bool input_has_alpha = true, int rgb_step = 3>
void resample(
const void *input, int in_width, int in_height, int in_stride,
void *output, int out_width, int out_height, int out_stride,
resample_params_t &params)
{
using type_mapping_t = type_mapping<color_type, input_has_alpha>;
using type_mapping_t = type_mapping<color_type, input_has_alpha, rgb_step>;

using input_pixfmt_t = typename type_mapping_t::input_pixfmt_type;
using output_pixfmt_t = typename type_mapping_t::output_pixfmt_type;
Expand Down
41 changes: 31 additions & 10 deletions src/_image_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,29 @@ image_resample(py::array input_array,
}

py::ssize_t ncomponents = 0;
int rgb_step = 0;
if (ndim == 3) {
ncomponents = input_array.shape(2);
if (ncomponents != 3 && ncomponents != 4) {
if (ncomponents == 3) {
// We special-case a few options in order to avoid copying in the common case.
auto rgb_stride = input_array.strides(1);
auto item_stride = input_array.strides(2);
if (rgb_stride == 3 * item_stride) {
rgb_step = 3;
} else if (rgb_stride == 4 * item_stride) {
rgb_step = 4;
}
} else if (ncomponents != 4) {
throw std::invalid_argument(
"3D input array must be RGB with shape (M, N, 3) or RGBA with shape (M, N, 4), "
"has trailing dimension of {}"_s.format(ncomponents));
}
}

// Ensure input array is contiguous, regardless of dtype
input_array = py::array::ensure(input_array, py::array::c_style);
if (rgb_step == 0) {
// Ensure input array is contiguous, regardless of dtype
input_array = py::array::ensure(input_array, py::array::c_style);
}

// Validate output array
auto out_ndim = output_array.ndim();
Expand Down Expand Up @@ -194,13 +206,22 @@ image_resample(py::array input_array,
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, true> :
nullptr
) : (
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false> :
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false> :
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false> :
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false> :
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false> :
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false> :
nullptr)))
(rgb_step == 4) ? (
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false, 4> :
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false, 4> :
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false, 4> :
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false, 4> :
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false, 4> :
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false, 4> :
nullptr
) : (
dtype.equal(py::dtype::of<std::uint8_t>()) ? resample<agg::rgba8, false, 3> :
dtype.equal(py::dtype::of<std::int8_t>()) ? resample<agg::rgba8, false, 3> :
dtype.equal(py::dtype::of<std::uint16_t>()) ? resample<agg::rgba16, false, 3> :
dtype.equal(py::dtype::of<std::int16_t>()) ? resample<agg::rgba16, false, 3> :
dtype.equal(py::dtype::of<float>()) ? resample<agg::rgba32, false, 3> :
dtype.equal(py::dtype::of<double>()) ? resample<agg::rgba64, false, 3> :
nullptr))))
{
Py_BEGIN_ALLOW_THREADS
resampler(
Expand Down

0 comments on commit 769f7a4

Please sign in to comment.