Basic pixel graphics in Python helper (SDL)

For Python users.
Sometimes one would like call draw(x, y, color) and see the pixel of color at x, y within a window. That is exactly what pixeldisplay.py helper has been written for.

pixeldisplay.py wraps pygame-ce interface to SDL. To give it a try

  • install pip3 install pygame-ce, probably within Python virtual environment
  • save script below into new file named pixeldisplay.py
  • enjoy
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023  Eugene V.S.
#
# *** MIT LICENSE **************************************************************
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# *** Author and contributors **************************************************
# Eugene V.S. aka ugnvs
# *** Tested in the environment ************************************************
# Oct 2023 -- Ubuntu MATE 22.04 64-bit Desktop
#          -- Python 3.10.12
#          -- pygame-ce 2.3.2 (SDL 2.26.5)
# Apr 2026 -- Ubuntu MATE 24.04 64-bit Desktop
#          -- Python 3.12.3
#          -- pygame-ce 2.5.7 (SDL 2.32.10)
#
# *** History & Milestones *****************************************************
# 2023-10-23 -- started & done
# ******************************************************************************

"""
Simple pixel display module for playing with pixel graphics.
Services: automatic and manual pausing, image saving

Usage example:

import pixeldisplay, random

# override defaults as needed
pixeldisplay.defaults['width'] = 400
pixeldisplay.defaults['height'] = 300
pixeldisplay.defaults['timeout'] = 4000
pixeldisplay.defaults['caption'] = 'White Noise'

# define a function to generate pixels of interest
def whitenoise():
    while True:
        x = random.randrange(pixeldisplay.defaults['width'])
        y = random.randrange(pixeldisplay.defaults['height'])
        tint = random.randrange(256)
        # N.B. 'yield [x,y, (r,g,b)]' is used instead of 'draw(x, y, color)' call
        yield [x, y, (tint, tint, tint)]


# Run calculation and drawing
pixeldisplay.run(whitenoise)

"""

import pygame, datetime

# Defaults
defaults = {'width' :800,
            'height':600,
            'background' : (0, 0, 0),
            'foreground' : (255, 255, 255),
            'fps' : 15, # frames per second
            'timeout' : 0, # milliseconds, 0 is no timeout
            'caption' : 'Pixel Display' # Window's caption & filename
}

# ==== Functions ===============================================================
def data():
    '''
    Default pixel data generator fills an area with default colour.
    Generally, a generator may provide as finite set of pixels,
    as an infinite (random) pixel sequence.
    '''
    for i in range(defaults['width']):
        for j in range(defaults['height']):
            yield [i, j, defaults['foreground']]
# ------------------------------------------------------------------------------

def run(generator=None):
    print( '''
    *****************************************
    *    When graphics window is active:    *
    *                                       *
    * <SPACE> toggles pause on calculations *
    * <ENTER> saves image to disk           *
    * <ESC>   quits                         *
    *                                       *
    *****************************************
''')
    if generator is None:
        print('No pixel generator provided. Using default white filling.')
        pixels = data()
    else:
        pixels = generator()
    
    pygame.init()
    screen = pygame.display.set_mode((defaults['width'], defaults['height']))
    pygame.display.set_caption(defaults['caption'])
    screen.fill(defaults['background'])
    pygame.display.flip()
    
    clock  = pygame.time.Clock()
    
    PAUSEEVENT = pygame.USEREVENT+1
    FRAMEEVENT = pygame.USEREVENT+2
    
    pygame.time.set_timer(PAUSEEVENT, defaults['timeout'])
    pygame.time.set_timer(FRAMEEVENT, 1000 // defaults['fps'])
    
    pause = False
    
    while True:
        if not pause:
            try:
                pixel = next(pixels)
            except StopIteration:
                pygame.time.set_timer(PAUSEEVENT, 0)
                pause = True
                print('Paused. No more pixel data available.')
                
            screen.set_at((pixel[0], pixel[1]), pixel[2])
            
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                print( 'Shutting down.')
                pygame.quit()
                return
            elif event.type == FRAMEEVENT:
                pygame.display.flip()
                pygame.time.set_timer(FRAMEEVENT, 1000 // defaults['fps'])
            elif event.type == PAUSEEVENT:
                pygame.time.set_timer(PAUSEEVENT, 0)
                print( 'Paused due to timeout')
                pause = True
                pygame.time.set_timer(PAUSEEVENT, 0) # timer event cancelled
            elif event.type == pygame.KEYDOWN:
                keys_pressed = pygame.key.get_pressed()
                if keys_pressed[pygame.K_SPACE]:
                    if pause:
                        # restart the computation
                        print( 'Pause cleared')
                        pause = False
                        pygame.time.set_timer(PAUSEEVENT, defaults['timeout'])
                    else: 
                        # shut down computation
                        pygame.time.set_timer(PAUSEEVENT, 0) # cancel pause timer
                        print( 'Pause invoked')
                        pause = True
                elif keys_pressed[pygame.K_RETURN] or keys_pressed[pygame.K_KP_ENTER]:
                    fname = defaults['caption'] + ' ' + datetime.datetime.today().isoformat("_").replace(':','-')[:19] + '.png'
                    print( 'Saving image into ' + fname)
                    pygame.image.save(screen, fname)
                elif keys_pressed[pygame.K_ESCAPE]:
                    print( 'Shutting down.')
                    pygame.quit()
                    return
                else:
                    pass

# ==== End of definitions ======================================================

if __name__ == '__main__':
    run()

4 Likes

very nice, well written and understood.

3 Likes