Beckett O'Brien
Beckett O'Brien

Reputation: 196

How to use raw pixel data in pyglet to change image size

I need to be able to change the width or height of an image before loading it into a sprite. I currently am using numpy to get either the center row or column and keep inserting it back into the image until it is the correct size. I do not know if this code even works yet, because I have also been having trouble loading the raw_image back into a sprite and displaying it.

def set_image_size(img, x, y):
    raw_img = img.get_image_data()
    format = 'RGBA'
    pitch = raw_img.width * len(format)
    rgba = np.array(list(img.get_image_data().get_data(format, pitch))).reshape(-1, raw_img.width, len(format))
    
    mid_y = rgba[round(raw_img.height/2),:] # This is needed to stretch along Y
    while rgba.shape[0] < y:
        rgba = np.insert(rgba, round(raw_img.height/2), mid_y, 0)
    mid_x = rgba[:,round(raw_img.width/2)] # This is needed to stretch along X
    while rgba.shape[1] < x:
        rgba = np.insert(rgba, round(raw_img.width/2), mid_x, 1)
    
    raw_img.set_data(format, pitch, ''.join(map(chr, rgba.tostring())))

    return raw_img

When I try blitting this onto an abstract image and blitting that to the screen, I get a weird striped red version of my image. I don't need to worry too much about quality, I just want to "stretch/tile" only the inside of the image so that the edges don't get warped. Also, my images are very small, only 15 by 15 pixels or so.

How can I fix either of these issues?

EDIT:

When I use sprite.scale, it gets stretched in weird ways:

self.sprite = pyglet.sprite.Sprite(self.image, self.x, self.y, batch=batch)
self.sprite.scale_x = self.width / 16

Original Sprite: Original Sprite

Stretched (with sprite.scale):

enter image description here

This isn't as much of a problem for this sprite, because I can use OpenGL Quads to draw it, but I need to start using more complex sprites that need to be scaled in the above way.

Upvotes: 1

Views: 553

Answers (1)

Torxed
Torxed

Reputation: 23500

So the problem lies with the scale of the image resolution you're originally working with, being 16px wide and 7px high. Up-scaling an image by default won't do any clever neighbor interpolation and "fill in the blanks" in the way you expect it to.

To solve this, you can tell OpenGL that each new pixel in your up-scaled image should take "inspiration" from its neighbors. You do this by adding to your render loop the following each time you render the sprite:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

It's important that this is called before every render of the sprites you need to add the filter to. Not sure what the technical jargon is for this, but it has to be setup each render sequence, because the next time you loop over your render function it gets reset. There's a fancy word for this I'm sure of it.

Here's a working example:

from pyglet import *
from pyglet.gl import *

key = pyglet.window.key

class main(pyglet.window.Window):
    def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
        super(main, self).__init__(width, height, *args, **kwargs)
        self.x, self.y = 0, 0
        glEnable(GL_TEXTURE_2D) # Strictly speaking not needed, I think this is default or won't affect this test image in any significant way.

        self.keys = {}

        self.mouse_x = 0
        self.mouse_y = 0

        # The image itself is 800x400 originally
        # Positioned at x=0 and y=50 will place it at the bottom essentially
        self.sprite = pyglet.sprite.Sprite(pyglet.image.load('test.png'), 50, 50, batch=None)
        self.sprite.scale = 20 # Scale the height, not the width

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def on_mouse_motion(self, x, y, dx, dy):
        self.mouse_x = x

    def on_key_release(self, symbol, modifiers):
        try:
            del self.keys[symbol]
        except:
            pass

    def on_key_press(self, symbol, modifiers):
        if symbol == key.ESCAPE: # [ESC]
            self.alive = 0

        self.keys[symbol] = True

    def render(self):
        self.clear()

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        self.sprite.draw()

        self.flip()

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze
            #
            event = self.dispatch_events()

if __name__ == '__main__':
    x = main()
    x.run()

And here's a test-image you can use: enter image description here (<-- Small white square, not zoomed in for effect)

And here's the result:

enter image description here

Upvotes: 2

Related Questions