Triangle-kernel filtering problems at non-integer ratios I'm trying to implement image downscaling using a triangle filter (it's narrow therefore fast, and goes hand in hand with linear interpolation when upscaling), and the triangle function used for filtering has a span proportional to the downscaling ratio.



Alas when the ratio is non-integer, grid artifacts appear (as shown above) on the downscaled image. The following plot shows what happens with the way triangles for each pixel of the input image overlap and add up in those cases:



I don't know what I should do to fix/work around this, but I'm sure this problem must have been encountered by others before (Google was no help).
I went with Jason R's idea of adjusting the linear filtering, except I didn't like the idea of doing a division on every output pixel, using more than just a simple kernel or how normalising that way would ruin the smooth borders. So instead I modified the kernel itself, which it turns out is done by flattening the peak of the triangle.



It seems like a pretty good solution, and you can see how the sum of the triangles progresses into something resembling a Gaussian function.

Here's how it's done, first of all there's a 'knee' joining the flat top with the rest of the triangle. It ping-pongs on the horizontal axis between 0 and 0.5 like so. Note: $n$ is the scale multiplier for the width of the kernel and the size of the downscaling, it's always 1 or lower, it's the reciprocal of the number displayed at the top of the animation. The smaller $n$, the smaller the output image, the wider the kernel over the original image.

$knee = 0.5 - |(n^{-1} \mod 1) - 0.5|$

Next we need to calculate the height of the top of the flattened triangle, which is done like this:

$m = \lfloor n^{-1} + 0.5\rfloor$

$top = \frac{1 - knee \cdot n}{2m - nm^2}$

And finally we turn it into an affine function which we can use directly when calculating weights:

$c_1 = \frac{-top}{n^{-1}-knee}$

$c_0 = \frac{-c_1}{n}$

The above is calculated only once and $knee$, $top$, $c_1$ and $c_0$ are stored and used when computing weights like this:

$$f(x) =
  \begin{cases} 
      \hfill top   \hfill &amp; \text{ if $|x| \leq knee$ } \\
      \hfill c_1 x + c_0 \hfill &amp; \text{ if $knee &lt; |x| \leq n^{-1}$ } \\
      \hfill 0   \hfill &amp; \text{ otherwise } \\
  \end{cases}$$