3

I am trying to build an application that requires the user to draw something.

To do so, I create a canvas (a pygame.Surface object) on which the drawing are registered, and then I blit it onto the window. I'd like the canvas to be infinite, so that when the user scrolls he can continue drawing (of course only a small part of the canvas is blited onto the window). But, actually, Surface in pygame requires a finite width and height and, most importantly, it's not that big! (I think that's because it actually locks the space in memory).

So, I tried to create chunks: every chunk has given fixed size (like, twice the screen size), and to each chunk is allocated a certain Surface. Chunks are created dynamically, on demand, and it overall works pretty well.

My problem is that when I try to draw lines that cross onto multiple chunks, it requires a great effort to compute onto which chunks that line should actually be drawn, and in what pieces it should be broken. I didn't even try to draw rectangles because it really was a pain to make the 'draw-a-line' function work.

That's when I thought that what I was doing was fundamentally wrong: instead of trying to rewrite all of pygame.draw and pygame.gfxdraw functions so that they basically do a per-chunk work, I should really overload the pygame.Surface (say, create a MySurface class child of Surface) so whenever a pixel is modified, I internally chose to which chunk it belongs and actually change it on that chunk, and pass that new Surface object to the pygame functions.

I've searched a lot at the pygame doc, but there it isn't explained how to do that. I don't even know what methods of a Surface object are internally called when I blit/draw onto it! I also google it and I didn't find anyone trying to do that kind of stuff (maybe I'm going the wrong way?).

So, my question(s) is: is this the right approach? And, if yes, how should I realize it?

I don't post code because what I need is more an explanation on where to find the doc of what I try to do more than a code review.

jthulhu
  • 7,223
  • 2
  • 16
  • 33

1 Answers1

4

You can't just subclass Surface, because it's not written in python, but in C. Here's the source code; look for yourself.

You could take another approach and instead of calculating where to draw stuff, blit it onto a temporary Surface first and blit that to the chunks relative to the chunk's position.

Here's simple example I hacked together:

 import pygame

class Chunk(pygame.sprite.Sprite):
    def __init__(self, grid_pos, size, color):
        super().__init__()
        self.image = pygame.Surface(size)
        self.rect = self.image.get_rect(
            x = grid_pos[0] * size[0],
            y = grid_pos[1] * size[1]
        )
        self.image.fill(pygame.Color(color))

    def patch(self, surface):
        self.image.blit(surface, (-self.rect.x, -self.rect.y))

def main():
    pygame.init()
    size = 800, 600
    screen = pygame.display.set_mode(size)
    chunks = pygame.sprite.Group(
        Chunk((0,0), size, 'green'),
        Chunk((1,0), size, 'red'),
        Chunk((0,1), size, 'blue'),
        Chunk((1,1), size, 'yellow')
    )
    dragging = None
    drawing = None
    tmp_s = pygame.Surface(size, pygame.SRCALPHA)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 3:
                    dragging = event.pos
                if event.button == 1:
                    drawing = event.pos

            if event.type == pygame.MOUSEBUTTONUP:
                if event.button == 3:
                    dragging = None
                if event.button == 1:
                    drawing = None
                    for chunk in chunks:
                        chunk.patch(tmp_s)

            if event.type == pygame.MOUSEMOTION:
                if dragging:
                    for chunk in chunks:
                        chunk.rect.move_ip(event.rel)

        screen.fill((0, 0, 0))
        chunks.draw(screen)

        tmp_s.fill((0,0,0,0))
        if drawing:
            size = pygame.Vector2(pygame.mouse.get_pos()) - drawing
            pygame.draw.rect(tmp_s, pygame.Color('white'), (*drawing, *size), 10)

        screen.blit(tmp_s, (0, 0))
        chunks.update()
        pygame.display.flip()

main()

enter image description here

As you can see, the canvas consists of 4 chunks. Use the right mouse button to move the canvas and the left button to start drawing a rect.

sloth
  • 99,095
  • 21
  • 171
  • 219
  • Hug... I guess I can see two options: go down to the C-level or simply accept your great solution (I've already made my choice :P). Btw, if I understand well, I don't have to do anything particular to my chunks, but I still have to create wrappers to the standard draw functions? Anyways, thanks for the answer and the reactivity! – jthulhu Apr 24 '20 at 12:14
  • You don't have to wrap the draw functions themselves. Just don't draw directly to the screen surface. – sloth Apr 24 '20 at 12:20
  • ok, I have implemented it but I suffer from a huge loss in performance. I pass from ~300fps at ~30fps, juste because I blit the temporary surface onto the chunks at each frame. – jthulhu Apr 24 '20 at 12:59
  • You only have to blit the temporary surface onto the chunks when you want to permanently alter them. Also, since your chunks are as big or bigger than the screen, there are at most four chunks that have to be altered. You should know which chunks are currently visible and only blit the temp surface to those. – sloth Apr 24 '20 at 18:13
  • that's actually what I tried later to improve efficiency, (require chunks to be bigger than screen, generate coordinates of seen chunks, retrieve these chunks or create them if they don't exist yet, and finally blit onto them), and to that only once, when the user tries to scroll, then reset temp surface. So I think my performance issue comes from a bug in my program, and not from your solution, I'll fix that (sooner or later). Thanks for the help! – jthulhu Apr 24 '20 at 18:59
  • I patched my program (which means, I did `git checkout --' and started over) and now it's working perfectly! No performance issues. Thanks again. – jthulhu Apr 25 '20 at 15:50