Skip to content

Commit 0ecf660

Browse files
committed
Fix affine resampling when using nearest-neighbor interpolation
The built-in Agg interpolator for determining the nearest-neighbor pixels under an affine transform uses integer-based math, but that results in slight inaccuracies that can become visible at even a minor level of magnification. A custom interpolator that uses floating-point-based math is now used instead.
1 parent 89bbd1c commit 0ecf660

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

src/_image_resample.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ void resample(
713713
using span_alloc_t = agg::span_allocator<color_type>;
714714
using span_conv_alpha_t = span_conv_alpha<color_type>;
715715

716+
using nn_affine_interpolator_t = accurate_interpolator_affine_nn<>;
716717
using affine_interpolator_t = agg::span_interpolator_linear<>;
717718
using arbitrary_interpolator_t =
718719
agg::span_interpolator_adaptor<agg::span_interpolator_linear<>, lookup_distortion>;
@@ -774,10 +775,10 @@ void resample(
774775

775776
if (params.interpolation == NEAREST) {
776777
if (params.is_affine) {
777-
using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, affine_interpolator_t>;
778+
using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, nn_affine_interpolator_t>;
778779
using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
779780
using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
780-
affine_interpolator_t interpolator(inverted);
781+
nn_affine_interpolator_t interpolator(inverted);
781782
span_gen_t span_gen(input_accessor, interpolator);
782783
span_conv_t span_conv(span_gen, conv_alpha);
783784
nn_renderer_t nn_renderer(renderer, span_alloc, span_conv);

src/_image_wrapper.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,12 @@ image_resample(py::array input_array,
167167

168168
if (is_affine) {
169169
convert_trans_affine(transform, params.affine);
170-
// If affine parameters will make subpixels visible, treat as nonaffine instead
171-
if (params.affine.sx >= agg::image_subpixel_scale / 2 || params.affine.sy >= agg::image_subpixel_scale / 2) {
172-
is_affine = false;
173-
params.affine = agg::trans_affine(); // reset to identity affine parameters
174-
}
175-
}
176-
if (!is_affine) {
170+
params.is_affine = is_affine;
171+
} else {
177172
transform_mesh = _get_transform_mesh(transform, output_array.shape());
178173
params.transform_mesh = transform_mesh.data();
174+
params.is_affine = false;
179175
}
180-
params.is_affine = is_affine;
181176
}
182177

183178
if (auto resampler =

src/agg_workaround.h

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define MPL_AGG_WORKAROUND_H
33

44
#include "agg_pixfmt_rgba.h"
5+
#include "agg_trans_affine.h"
56

67
/**********************************************************************
78
WORKAROUND: This class is to workaround a bug in Agg SVN where the
@@ -42,4 +43,87 @@ struct fixed_blender_rgba_plain : agg::conv_rgba_plain<ColorT, Order>
4243
}
4344
};
4445

46+
47+
/**********************************************************************
48+
This class provides higher-accuracy nearest-neighbor interpolation for
49+
affine transforms than span_interpolator_linear by using
50+
floating-point-based interpolation instead of integer-based
51+
*/
52+
53+
template<class Transformer = agg::trans_affine, unsigned SubpixelShift = 8>
54+
class accurate_interpolator_affine_nn
55+
{
56+
public:
57+
typedef Transformer trans_type;
58+
59+
enum subpixel_scale_e
60+
{
61+
subpixel_shift = SubpixelShift,
62+
subpixel_scale = 1 << subpixel_shift
63+
};
64+
65+
//--------------------------------------------------------------------
66+
accurate_interpolator_affine_nn() {}
67+
accurate_interpolator_affine_nn(trans_type& trans) : m_trans(&trans) {}
68+
accurate_interpolator_affine_nn(trans_type& trans,
69+
double x, double y, unsigned len) :
70+
m_trans(&trans)
71+
{
72+
begin(x, y, len);
73+
}
74+
75+
//----------------------------------------------------------------
76+
const trans_type& transformer() const { return *m_trans; }
77+
void transformer(trans_type& trans) { m_trans = &trans; }
78+
79+
//----------------------------------------------------------------
80+
void begin(double x, double y, unsigned len)
81+
{
82+
m_len = len - 1;
83+
84+
m_stx1 = x;
85+
m_sty1 = y;
86+
m_trans->transform(&m_stx1, &m_sty1);
87+
m_stx1 *= subpixel_scale;
88+
m_sty1 *= subpixel_scale;
89+
90+
m_stx2 = x + m_len;
91+
m_sty2 = y;
92+
m_trans->transform(&m_stx2, &m_sty2);
93+
m_stx2 *= subpixel_scale;
94+
m_sty2 *= subpixel_scale;
95+
}
96+
97+
//----------------------------------------------------------------
98+
void resynchronize(double xe, double ye, unsigned len)
99+
{
100+
m_len = len - 1;
101+
102+
m_trans->transform(&xe, &ye);
103+
m_stx2 = xe * subpixel_scale;
104+
m_sty2 = ye * subpixel_scale;
105+
}
106+
107+
//----------------------------------------------------------------
108+
void operator++()
109+
{
110+
m_stx1 += (m_stx2 - m_stx1) / m_len;
111+
m_sty1 += (m_sty2 - m_sty1) / m_len;
112+
m_len--;
113+
}
114+
115+
//----------------------------------------------------------------
116+
void coordinates(int* x, int* y) const
117+
{
118+
// Truncate instead of round because this interpolator needs to
119+
// match the definitions for nearest-neighbor interpolation
120+
*x = int(m_stx1);
121+
*y = int(m_sty1);
122+
}
123+
124+
private:
125+
trans_type* m_trans;
126+
unsigned m_len;
127+
double m_stx1, m_sty1, m_stx2, m_sty2;
128+
};
45129
#endif

0 commit comments

Comments
 (0)