How does ci::TriangleFilter avoid aliasing?

I tried to do my own implementation of ci::ip::resize, specialized for 2x downscaling. My code is pretty self-explanatory:

Array2D<float> downscale2x(Array2D<float> in) {
	auto res = Array2D<float>(in.Size() / 2);
	forxy(res) {
		res(p) = .25f*(
			in(p * 2)
			+ in(p * 2 + ivec2(1, 0))
			+ in(p * 2 + ivec2(0, 1))
			+ in(p * 2 + ivec2(1, 1))
	return res;

But this results in a strongly (visibly) aliased image.

OTOH, ci::ip::resize (with a default-constructed triangle filter) results in an image with no visible aliasing. How does it do that? I suspect it’s because the triangle filter has larger support than in my implementation.

And another question:

Does FilterBase’s filter support value mean half of the actual support? I’m asking because look:

class FilterTriangle : public FilterBase {
	FilterTriangle( float aSupport = 1.0f ) : FilterBase( aSupport ) {}
	virtual float operator()( float x ) const {
		if ( x < -1.0f ) return 0.0f;
		else if ( x < 0.0f ) return 1.0f + x;
		else if ( x < 1.0f ) return 1.0f - x;
		return 0.0f;

Here, the default aSupport is 1.0f, yet the operator() implementation returns nonzeros for the (-1, 1) range, meaning that the support is actually 2.0f.

TIA :slight_smile:

I also tried 2 other approaches:

  • native GL_LINEAR OpenGL downscaling
  • OpenCV’s cv::resize

They both produce aliasing just like the approach in my original post.

Only the ci:ip::resize approach works fine.

I think I found the reason. Quote from cinder’s Resize.cpp::rescale:

filterParamsX.scale = std::max( 1.0f, 1.0f / );
filterParamsX.supp = std::max( 0.5f, filterParamsX.scale * filter.getSupport() );
filterParamsX.width = (int32_t)ceil( 2.0f * filterParamsX.supp );

Here, filterParamsX.width works out to 4 (in my usecase) whereas in my approach I sample 2 pixels in the x direction.

Ok, I managed to replicate ci::ip::resize’s quality by doing a small-kernel gaussian prefiltering before downsampling.

Still, would like to hear your perspective on this :slight_smile:


Could you clarify what you are trying to achieve? If you need filtering only for the OpenGL rendering, you could try enabling mipmapping?


Sure :slight_smile:

My motivation is to write a much simpler/shorter reimplementation of ci::ip::resize, so I can fully understand my implementation (including edge cases).

ci::ip::resize is, of course, highly optimized code, so it’s hard to read. And it is general-purpose, whereas in my implementation, I just need 2x downscaling.