Page 1 of 1

## High quality sprites with color gradient and anti-aliasing

### #1 DK3250

• Pythonian

Reputation: 347
• Posts: 1,156
• Joined: 27-December 13

Posted 16 November 2016 - 02:22 PM

High quality sprites with color gradient and anti-aliasing

This tutorial requires pygame and python 3.x

In an earlier tutorial, I've shown how to anti-alias a simple circle/ball figure. http://www.dreaminco...-anti-aliasing/
With such simple figures the anti-aliasing mathematics can be boiled down to work even if the sprite moves on a multicolored background.

In this tutorial, we will make anti-aliasing of more complicated figures, but only on monochrome backgrounds.

If you look on a professional game like Candy Chrush, the sprites (the figures) are smooth and soft in their appearance with little artificial reflections giving the impression of 3D surfaces. If the sprites in your games are much more dull and flat, this tutorial may be just what you are looking for.

The strategy for making a nice sprite is:
1. Draw the sprite in monochrome (one color) - or import a monochrome figure drawn elsewhere. The figure must be oversized compared to the final result by a factor of 2 or 4 on each side.
If your final sprite is 50X50 pixels, your figure must be 100X100 or 200X200 pixels.
I prefer a quite large start picture, simply because I find it easier to make nice, precise drawings on a large canvas.
In this tutorial I use a color gradient as it can be applied to almost any figure using fairly simple mathematics.
3. Anti-alias the figure by averaging the color values of all 2X2 picture elements and use the average as color in a picture of half the dimension (or quarter the size).
Point 3 may be repeated to get to sufficient small final sprites.

Let's look at the individual steps.

Drawing a monochrome sprite

The drawing part is a challenge to me; I am really not very artistic, so I tend to draw using basic shapes like circles, lines and polygons; figures I can get from the pygame.draw.<figure>() functions.

If you are comfortable using a more free-style drawing tool, you may be able to make even more impressive sprites, and to combine step 1 and 2 in the procedure mentioned above.

Anyway, let me show how I've made a heart using basic pygame figures. I use five graphic parts to generate a heart. In the first picture below the five parts are shown in five different colors; this is for demonstration only, in the final code all parts should be of the same color. The second picture show the final result.
The code for making the five picture elements is shown in the function ”draw_figure()” in the code below.

If you want to make more advanced drawings, I recommend a free tool: Piskel http://www.piskelapp.com/. With Piskel, and an artistic nerve, you can do really nice sprites - and skip parts of this tutorial.

First a center for our gradient is chosen.
The color gradient is made by pixel for pixel inspection of the surface holding the monochrome figure. If the color is the monochrome, it is changed to the gradient color which in turn is dependent of the pixel's distance to the gradient center.

When I originally made the gradient functions, I used a circle with diameter 255. To ensure a steep color gradient in small figures (such that the visual impression is the same no matter the size), the distance to gradient center is normalized by multiplication with (255/size) - size is the 'dimension' of your surface; typically the average of the two axis lengths.

The gradient function itself just returns a color depending of the normalized distance. I normally use an almost white color near the center, and then reduce the color intensity as the distance grows. The color elements (r, g, b ) are not reduced equally, but such that the gradients ”color target” are favored. As the color elements are reduced, we need to ensure they don't become negative; for this I use the max() function.

Try to play with the individual values of the gradient function, the possibilities are endless.

Resize and anti-alias

Now we only need to resize and anti-alias. To this we evaluate four pixels in a 2X2 grid and calculate the average of each color element (r, g, b ). This average is then used to color one pixel in a surface with half the dimension length. Then on to the next four pixels, and so on.

This procedure may be repeated to get to sufficient small sprites.

The anti-aliasing effect occurs when four pixels include both background and monochrome. You will get a 25%, 50%, 75% saturation depending of the situation. If you run two resizes the smoothening becomes even better.

This anti aliasing technique can be applied to all sprites and patterns; striped, dotted or otherwise multicolored. The anti aliasing will work just as well on the internal color boundaries as on the boundary to the background.

Closing words

This whole procedure is a bit time consuming so you don't want to do it in the middle of a game. But a few seconds during game initialization is normally acceptable.
During game initialization you make all the sprites your game needs, and later you just show/move them around.
```"""
Tutorial, demonstrating how a color gradient and anti aliasing
can be applied to a 'home made' sprite.

By DK3250, November 2016
"""
import pygame, sys, math, random
pygame.init()

# DECLARATIONS

X = 400
Y = 400

BLACK = (0, 0, 0)
RED = (225, 50, 50)
WHITE = (255, 255, 255)
MONO = RED

size = 320

screen = pygame.display.set_mode((X, Y))
screen.fill(WHITE)

# FUNCTIONS

def red_1(d2):
""" A gradient function - values can be modified for change of effect """
return [max(255-int(d2*0.5), 0), max(200-int(d2*2), 0), max(200-int(d2*2), 0)]

def magenta_1(d2):
return [max(255-int(d2*0.6), 0), max(200-int(d2*1.5), 0), max(255-int(d2*0.6), 0)]

def draw_figure():
"""
Drawing of a monochrome figure
Here by use of basic pygame.draw.<figure> functions

Could also be an import of a monochrome picture
"""
surf = pygame.surface.Surface((size, size))
surf.fill(BLACK)

help_surf = pygame.surface.Surface((size, size))
help_surf.set_colorkey(BLACK)
help_surf.fill(BLACK)

pygame.draw.circle(surf, MONO, (38, 47), 265, 0)
pygame.draw.polygon(surf, BLACK, ((size-1, 78), (129, size-1), (0, size-1), (0, 0), (size-1, 0)), 0)

pygame.draw.circle(help_surf, MONO, (282, 47), 265, 0)
pygame.draw.polygon(help_surf, BLACK, ((0, 78), (191, size-1), (size-1, size-1), (size-1, 0), (0, 0)), 0)
surf.blit(help_surf, (0, 0))

pygame.draw.circle(surf, MONO, (96, 100), 72, 0)
pygame.draw.circle(surf, MONO, (224, 100), 72, 0)
pygame.draw.polygon(surf, MONO, ((28, 118), (160, 280), (288, 118)), 0)

return surf

"""
In-place conversion of a monochrome figure to a gradiented one.
Normalized distance from center of gradient is calculated
and used as input for the gradient function.

The gradient function defines the modified color range.
"""
x1, y1 = 200, 120  # center of gradient

for j in range(size):
for i in range(size):
if surf.get_at((i, j)) == MONO:
d2 = ((i-x1)**2 + (j-y1)**2)**0.5 * 255 / size
surf.set_at((i,j), color)

def resize(surf, size):
"""
Anti-aliasing by average of color code in four pixels
with subsequent use of the average in a smaller surface
"""
new_surf = pygame.surface.Surface((size//2, size//2))

for j in range(0, size, 2):
for i in range(0, size, 2):
r1, g1, b1, a1 = surf.get_at((i, j))
r2, g2, b2, a2 = surf.get_at((i+1, j))
r3, g3, b3, a3 = surf.get_at((i, j+1))
r4, g4, b4, a4 = surf.get_at((i+1, j+1))

r = (r1 + r2 + r3 + r4) / 4
g = (g1 + g2 + g3 + g4) / 4
b = (b1 + b2 + b3 + b4) / 4

new_surf.set_at((i//2, j//2), (r, g, b, 255))

new_size = size // 2
return new_surf, new_size

# MAIN CODE

surf = draw_figure()
surf, size = resize(surf, size)
surf, size = resize(surf, size)

screen.blit(surf, (10, 10))
pygame.display.flip()

```

Is This A Good Question/Topic? 1

## Replies To: High quality sprites with color gradient and anti-aliasing

### #2 noles123

Reputation: 0
• Posts: 12
• Joined: 19-October 17

Posted 19 October 2017 - 11:52 AM

hi, i am a little bit confused
why in this code you must use 'size = 720'? and in the draw figure you also use size at 'surface' and 'draw', i wonder to know why
thanks

### #3 DK3250

• Pythonian

Reputation: 347
• Posts: 1,156
• Joined: 27-December 13

Posted 19 October 2017 - 02:33 PM

In line 20 I use 'size = 320'. This value is chosen such that it divides nicely with 2, 4, (and 8, 16) as explained in the text.

This variable named 'size' is then used in various functions as you mention. In this way the function becomes generic i.e. able to handle all sizes and the variable only need to be defined in one single line.

### #4 noles123

Reputation: 0
• Posts: 12
• Joined: 19-October 17

Posted 26 November 2017 - 09:12 AM

hey i want to ask again

r1, g1, b1, a1 = surf.get_at((i, j))

what is a1 for?

and I still don't get what is this for?

```new_surf.set_at((i//2, j//2), (r, g, b, 255))

new_size = size // 2

```

thanks for your help, it really helps, I start to understand AA in python

### #5 DK3250

• Pythonian

Reputation: 347
• Posts: 1,156
• Joined: 27-December 13

Posted 26 November 2017 - 09:36 AM

The color has 4 components: Red, Green, Blue and Alpha.
Alpha is the 'transparency' of the color; from 0 (fully transparent) to 255 (fully opaque).
To utilize the transparency, a Pygame surface must be declared with the SRCALPHA flag.
I include an example that I hope you will find illustrative.

```import pygame
pygame.init()

screen = pygame.display.set_mode((200, 200))

WHITE = (255, 255, 255)
GREY = (126, 126, 126, 126)
BLACK = (0, 0, 0)

screen.fill(WHITE)
pygame.draw.rect(screen, BLACK, (50, 50, 100, 100), 0)
surf_1 = pygame.surface.Surface((100, 100))
surf_1.fill(GREY)
surf_2 = pygame.surface.Surface((100, 100), pygame.SRCALPHA)
surf_2.fill(GREY)

screen.blit(surf_1, (0, 0))
screen.blit(surf_2, (100, 100))

pygame.display.flip()

```

Use of the surface.get_at() function will always return all four color components; thus the line
```r1, g1, b1, a1 = surf.get_at((i, j))

```

EDIT: Minor correction

This post has been edited by DK3250: 01 December 2017 - 02:23 PM