Drawing rotated textures appears pixelated

First I want to say I am very new to game dev and monogame.

I am trying to draw a simple rectangle and rotate it. I made a large 150,600 rectangle in paint.net for my texture saved as a png. I then scale is down and rotate it and get the following:

How can I prevent the rectangle from being pixelated when scaling down to small sizes like this? Is it my image file?

This is my image: https://www.dropbox.com/s/s2sn07uwzt7sgv4/rectangle.png?dl=0

I tried the below but it did not help:
graphics.PreferMultiSampling = true;
graphics.GraphicsDevice.PresentationParameters.MultiSampleCount = 8;

Any tips/suggestions would be appreciated!

I don’t understand exactly what you mean.

Is the image you posted actual resolution you use it for in engine? Can you post the spritebatch.draw code you used? Is it only this one that looks wrong? Does it look wrong only when using rotation?

Note taken, well for this thread anyway :slight_smile:

Why are you scaling it down? it would also help if you attached the original graphic image for analysis, you should have turned off any filtering and have full sharpness on the image, but honestly? a rectangle? may as well create it from code…

Oh and look into Anti-Aliasing.

Look at this guys blog and articles such as this one on anti-aliasing.

I tried making the image actual resolution but had the same issue. So I tried making the image large then shrinking so that there would be more detail in my image. The issue is only when rotating (Since the image is a rectangle no jagged edges could occur when vertical or horizontal). In Paint.Net resizing and rotating the rectangle does not show jagged edges so it must be my code.

` Code:

        //Initialize
        graphics.PreferMultiSampling = true;
        graphics.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
        graphics.GraphicsDevice.PresentationParameters.MultiSampleCount = 1024;

        //LoadContent
        spriteBatch = new SpriteBatch(GraphicsDevice);
        rectangleFromImage = Content.Load<Texture2D>("rectangle");
        drawRectangle = new Texture2D(graphics.GraphicsDevice,5, 20);

        Color[] colors = new Color[drawRectangle.Width * drawRectangle.Height];
        for(int i = 0; i < colors.Length; i++)
        {
            colors[i] = Color.Black;
        }

        drawRectangle.SetData(colors);

        //Draw
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearWrap);
        spriteBatch.Draw(rectangleFromImage, new Vector2(50, 50), null, Color.White, MathHelper.ToRadians(15), new Vector2(rectangleFromImage.Width / 2, 0), 0.01f, SpriteEffects.None, 1);
        spriteBatch.Draw(drawRectangle, new Vector2(100, 50), null, Color.White, MathHelper.ToRadians(15), new Vector2(drawRectangle.Width / 2, 0), 1, SpriteEffects.None, 1);
        spriteBatch.End();

`

Why are you scaling it down? it would also help if you attached the original graphic image for analysis, you should have turned off any filtering and have full sharpness on the image, but honestly? a rectangle? may as well create it from code…

The dropbox link in my original post is the rectangle I am trying to draw. I get the same issue drawing a rectangle in code. I set preferMultiSampling, I set sampler state to LinearWrap and set sample count to 1024 (No number here helps).

Look at this guys blog2 and articles such as this one on anti-aliasing2.

Thanks, I will read these.

_spriteBatch.Draw(texture: rectangleFromImage,
position: new Vector2(50, 50),
sourceRectangle: null,
color: Color.White,
rotation: MathHelper.ToRadians(15),
origin: new Vector2(rectangleFromImage.Width / 2, 0),
scale: 0.01f,
effects: SpriteEffects.None,
layerDepth: 1);

So your scale is 0.01f

That means the original resolution divided by 100. Therefore it should be 1.5f x 6 pixels (or 2x6 pixels). That corresponds to the output image.

So the question really is - why do you need a 600x150 pixel image to make a 6x2 pixel one?

EDIT Nevermind. I understand now

The issue lays in the way the texture is read - for each pixel in the final output we check the corresponding pixel at that position in the original image. So we do not “downscale” the image but instead we only check 6x2 = 12 pixels in the original super large file.

To give an example - if your final image is 10 pixels and the original is 1000 pixels wide then we read the 50th, the 150th, the 250th etc. pixel in the original bitmap for our final output.

You chose “SamplerState.LinearWrap” which makes this a bit better in that we also check the neighbouring pixels and interpolate between them, but these are most likely also black.
Downscaling something to 1% of the orginal size doesn’t work very well with the normal graphics pipeline.

You cannot fix this in the code. The only way to fix that is downsample the image in paint.net to a value close to the final output resolution you want.

Small tip:
new Vector2(rectangleFromImage.Width / 2, 0),

this line should be
new Vector2(rectangleFromImage.Width / 2f, 0),

You divide an int (width) by an int (2), so the output must be an int again. Therefore your origin is sometimes a bit off, if the original image resolution is not an even number. For example 3 / 2 would be 1.
If you add the f to the 2 -> 2f it will be treated as a float and we get 3 / 2 = 1.5f

Aspect Ratio, correct?

@SyntaxCheck

If you do not want to change the original texture in size though you can add a bit of a border around it.

Like in this image above.

And here is the result: (Click on the image to enlarge it!)

1 Like

I appreciate the comments, you have been very helpful. Thanks for your patience with my lack of basic knowledge.

Small tip:
new Vector2(rectangleFromImage.Width / 2, 0),

this line should be
new Vector2(rectangleFromImage.Width / 2f, 0),

Thanks for that, I probably would have ran into that issue later down the road!

The issue lays in the way the texture is read - for each pixel in the final output we check the corresponding pixel at that position in the original image. So we do not “downscale” the image but instead we only check 6x2 = 12 pixels in the original super large file.

To give an example - if your final image is 10 pixels and the original is 1000 pixels wide then we read the 50th, the 150th, the 250th etc. pixel in the original bitmap for our final output.

You chose “SamplerState.LinearWrap” which makes this a bit better in that we also check the neighbouring pixels and interpolate between them, but these are most likely also black.
Downscaling something to 1% of the orginal size doesn’t work very well with the normal graphics pipeline.

You cannot fix this in the code. The only way to fix that is downsample the image in paint.net to a value close to the final output resolution you want.

Thanks for that explanation, I never realized it worked that way. My intent was to have a large image and be able to scale it up and down so that is why I started with a large image.

If you do not want to change the original texture in size though you can add a bit of a border around it.

Hmm this really confuses me. Your image rotated does look how I want it to look but I don’t understand why that works or how you got it to look like that. When I try the image you included it looks like your left image, are you doing something different in the draw?

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearWrap); spriteBatch.Draw(rectangleFromImage, new Vector2(50, 50), null, Color.White, MathHelper.ToRadians(15), new Vector2(rectangleFromImage.Width / 2f, 0), 0.1f, SpriteEffects.None, 1);

I will have to do more reading, some of the recommendations I find for XNA do not work in MonoGame so on this topic it is a bit hard to find info. I will definitely have to do more research into Anti aliasing and texture resizing this weekend.

Should I mention ^2 textures here? @kosmonautgames ?

doesn’t matter, I’ve tried it out

No I’m just rendering with scale 0.1 With 0.01 it can’t be done since we only read like 2 pixels and there is no way for the white border to be picked up

1 Like

Figured out how to get your border example to work. I enabled “GenerateMipmap” for the textures and it looks like yours now. I was using 0.1f btw.

So do people simply not rotate images? It seems like a major pain to add a giant border. I tried a transparent border and it appears to still work so I guess I could adjust my position to factor in the border so that my images still line up where I want them.

You chose "SamplerState.LinearWrap" which makes this a bit better in that we also check the neighbouring pixels and interpolate between them, but these are most likely also black.

I think this is the key to my confusion. Why the linear interpolation does not work when all the pixels are black is confusing. Is it simply because my image is at the edges of the file?

Could a Shader help me render my images without a border maybe using other methods of interpolation?

Bilinear filtering only has an effect inside the triangle. To smooth (anti-alias) the edges you need to use a multisampled back buffer or render target.

Except that multisampling doesn’t work, right?

It should do on back buffer. Render targets need some refactoring, though.