NoRt9001
NoRt9001

Reputation: 100

More Complex Color Gradients in Pygame

In Python 3.8 with the Pygame module:

I'm trying to make color gradients for a color game I'm making. I know how to make a one-dimensional gradient, like this:

enter image description here

But I want to be able to create an image with more than two colors within the gradient. An example from the internet is this: (ignore the fancy text)

enter image description here

How can I create a function to generate a gradient of multiple colors on a surface? It's important that I can choose the colors in the corners, or at least be able to influence them in a way that I can understand.

An idea that I found online is this:

for i in range(900):
   for j in range(900):
      surface.set_at((i,j),(i/4,j/4,i/4))

My issue with this is that it is difficult to choose the colors that it generates. I can't piece together how to make colors blend into each other, especially with multiple of them.

Upvotes: 4

Views: 1754

Answers (2)

jsbueno
jsbueno

Reputation: 110208

There is no magic involved. Pygame does not have an API for gradients and, although you had not post an code of yours, you probably made the interpolation just fine.

So, for a "2 dimensional square gradient", you have to continue to break your problem in smaller parts, until you get what you want - For this, it could be: have a gradient that runs on the top of your interest areaat y == 0, another gradient that runs at the bottom of your area, at y = height, and for each column, you interpolate a new gradient, starting at the color on the top gradient and running to the color at the bottom gradient.

The only bad news is that this would be very slow in pygame - but you could draw it once and save the resulting image, and just load it at game time.

There are other libraries and frameworks suitable to draw a linear gradient, and do it fast, like Cairo, GEGL, and OpenGL itself - but none of those have a ready-made way to transition one full gradient into another.

[continuing...]

Upvotes: 3

NoRt9001
NoRt9001

Reputation: 100

So, for a "2 dimensional square gradient", you have to continue to break your problem in smaller parts, until you get what you want - For this, it could be: have a gradient that runs on the top of your interest area at y = 0, another gradient that runs at the bottom of your area, at y = height, and for each column, you interpolate a new gradient, starting at the color on the top gradient and running to the color at the bottom gradient.

This worked great! I was able to come up with three functions that together create awesome 4-color rectangular gradients, with each color at a different corner. Here's my code if you'd like to use it:

windowSurface = pygame.display.set_mode((1500,900))
s = pygame.Surface(size, pygame.SRCALPHA)
gradientStorage = pygame.Surface((1500,1500))
colorGridStorage = pygame. Surface((1500,1500))


def colorMix(color1, color2, paletteSize):    # creates a list of colors that gradually fade from color1 to color2, inclusive. paletteSize is the amount of values that will be generated.
               # The smallest useful paletteSize is 3, as it will return [color1, color1MixedWithColor2, color2]
        palette = [color1]
        colorDifference = [ color1[0] - color2[0], color1[1] - color2[1], color1[2] - color2[2] ]

        Rstep = (color1[0] - color2[0]) / (paletteSize -1)
        Gstep = (color1[1] - color2[1]) / (paletteSize -1)
        Bstep = (color1[2] - color2[2]) / (paletteSize -1)

        for i in range(1,paletteSize):
            palette.append((color1[0] - Rstep*i, color1[1] - Gstep*i, color1[2] - Bstep*i))

        palette.append(color2)

        return palette

def createColorGrid(resolution, color1, color2, color3, color4):        # build a new colorGrid using a different process than above. colors are RGB format. For a 1D color fade set pairs of colors
                                      # like (255,0,0) (255,0,0) (0,255,255) (0,255,255). Colors are ordered from top left corner and follow corners clockwise.
        colorArray = [resolution]   # the first value in colorGrid is always a tuple stating the resolution.
        leftColumn = colorMix(color1,color4,resolution[1])
        rightColumn = colorMix(color2,color3,resolution[1])

        for i in range(0,resolution[1]):                                                                  # color processing goes from top left to top right, then down a row and repeat
                colorArray.append(colorMix(leftColumn[i],rightColumn[i],resolution[0]))
        return colorArray


def drawColorGrid(colorGrid, rect):    # input a colorGrid array. This will draw the set of color tiles decided by the colorGrid you pass into it
    colorGridStorage.fill((255,255,255))
    iCounter = 0
    for i in colorGrid:

        jCounter = 0
        if isinstance(i[0], int):   # the first value in colorGrid is the [x,y] resolution. we need to ignore it and move on to the rest
            continue

        for j in i:
            rectX = (rect[0] + round( jCounter * (rect[2]/colorGrid[0][0])))
            rectY = rect[1] + round(iCounter * rect[3]/colorGrid[0][1])
            rectWidth = round(rect[2]/colorGrid[0][0])
            rectHeight = round(rect[3]/colorGrid[0][1])
            pygame.draw.rect(colorGridStorage, j, (rectX,  rectY, rectWidth, rectHeight))
            jCounter += 1
        iCounter +=1
    windowSurface.blit(colorGridStorage, (rect[0], rect[1]))

To draw a new gradient, first use createColorGrid(resolution, color1, color2, color3, color4) to build an array of blocks of colors at a certain resolution. Low resolutions will look just like something from the game I Love Hue. Once you have a colorGrid variable, plug that into drawColorGrid(colorGrid, rect). This will take the colorGrid array and blit it to your screen within a given rect. The screen surface is named windowSurface in this case.

I'm a fairly new programmer, so there could definitely be some optimizations in the code above. Point them out to me if you like, but this code works great for the applications I'm doing. It seems to be smooth enough to generate and blit at least 20 random gradients to a full screen per second, which is much more than I need.

Upvotes: 1

Related Questions