Image.set_data blit bug?

106 views
Skip to first unread message

Brian Will

unread,
Dec 17, 2012, 5:41:12 AM12/17/12
to pyglet...@googlegroups.com
For pedagogical purposes, I'm trying to set up a simple drawing canvas on which to draw and display a single image with control of the individual pixels. Efficiency is no concern. To this end, I have a MutableImage class with getPixel and setPixel methods. Everything seems to work fine, except when blitting the generated image, pixels from columns on the left side for some reason also get displayed in columns about 2/3 to the right, e.g. in a 500 pixel wide image, the content of columns 0-100 gets erroneously drawn also in columns ~300-400 (overwriting whatever should be in those columns). My hack around this is to save my generated image to file, then load it again with pyglet.image.load. When I then blit my generated image newly loaded from the file, it displays properly. So if my data is getting saved properly, does this mean there's a bug in blitting images after set_data?

p.s. I also get strange behavior in the saved .png. The generated image displays correctly in my window, but the saved file doesn't match, almost like a whole color channel is missing. Seems like something is screwy with the channels.

Here's my code:

import pyglet
    
class MutableImage(object):
    FORMAT = 'RGB'
    
    def __init__(self, image):
        self.image = image
        self.imageData = image.get_data(MutableImage.FORMAT, image.width * len(MutableImage.FORMAT))
        self.dataList = list(self.imageData)
    
    def getPixel(self, x, y):
        '''Returns tuple of (r, g, b)'''
        pixelIdx = x + (y * self.image.width)
        byteIdx = pixelIdx * len(MutableImage.FORMAT)
        return (
            ord(self.dataList[byteIdx]),
            ord(self.dataList[byteIdx + 1]),
            ord(self.dataList[byteIdx + 2])
        )
        
    def setPixel(self, x, y, color):
        ''' color is tuple of (R, G, B) as integers 0-255 '''
        # ignore requests to draw out of bounds
        if self.inBounds(x, y):
            pixelIdx = x + (y * self.image.width)
            byteIdx = pixelIdx * len(MutableImage.FORMAT)
            r, g, b = color
            self.dataList[byteIdx] = chr(r)
            self.dataList[byteIdx + 1] = chr(g)
            self.dataList[byteIdx + 2] = chr(b)
        
    def getImage(self):
        ''' return the current state of data '''
        data = ''.join(self.dataList)
        self.image.set_data(MutableImage.FORMAT, self.image.width * len(MutableImage.FORMAT), data)
        
        # without hack of saving as file then loading as file, a fraction of left side
        # gets redrawn on right side for no apparent reason when we blit the image
        self.image.save('hack-around.png')
        return pyglet.image.load('hack-around.png')
    
    def inBounds(self, x, y):
        if x < self.image.width and y < self.image.height:
            return True
        return False
        
    def clamp(self, x, y):
        if x >= self.image.width:
            x = self.image.width - 1
        if y >= self.image.height:
            y = self.image.height - 1
        return (x, y)
    
    def paintRectangle(self, lowerLeftCorner, upperRightCorner, color):
        ''' coords in (x, y) tuple  '''
        x1, y1 = lowerLeftCorner
        x2, y2 = upperRightCorner
        x1, y1 = self.clamp(x1, y1)
        x2, y2 = self.clamp(x2, y2)
        for x in range(x1, x2 + 1):
            for y in range(y1, y2 + 1):
                self.setPixel(x, y, color)
                
    def copyRegion(self, src, dest, width, height):
        ''' src and dest (x, y) tuples of lower left corner of regions '''
        pass
        
    def paintImage(self, dest, srcImage):
        ''' dest specifies lower left corner where to draw the srcImage '''
        mutableSrcImage = MutableImage(srcImage)
        destX, destY = dest
        for x in range(srcImage.width):
            for y in range(srcImage.height):
                color = mutableSrcImage.getPixel(x, y)
                self.setPixel(x + destX, y + destY, color)
    

def displayWindow(image):
    window = pyglet.window.Window(image.width, image.height, caption='image render')
    
    @window.event
    def on_draw():
        image.blit(0, 0)
       
    pyglet.app.run()



mi = MutableImage(pyglet.image.create(500, 900))
 
mi.paintRectangle((100, 0), (500, 900), (0, 255, 0))

for x in range(50, 500):
    mi.setPixel(x, 850, (255, 0, 0))
for x in range(50, 500):
    mi.setPixel(x, 450, (255, 0, 0))
for x in range(50, 500):
    mi.setPixel(x, 250, (255, 0, 0))    
mi.paintRectangle((0, 0), (100, 100), (0, 255, 255))
mi.paintRectangle((450, 850), (500, 900), (0, 255, 255))

displayWindow(mi.getImage())

Adam Bark

unread,
Dec 17, 2012, 3:50:37 PM12/17/12
to pyglet...@googlegroups.com
On 17/12/12 10:41, Brian Will wrote:
For pedagogical purposes, I'm trying to set up a simple drawing canvas on which to draw and display a single image with control of the individual pixels. Efficiency is no concern. To this end, I have a MutableImage class with getPixel and setPixel methods. Everything seems to work fine, except when blitting the generated image, pixels from columns on the left side for some reason also get displayed in columns about 2/3 to the right, e.g. in a 500 pixel wide image, the content of columns 0-100 gets erroneously drawn also in columns ~300-400 (overwriting whatever should be in those columns). My hack around this is to save my generated image to file, then load it again with pyglet.image.load. When I then blit my generated image newly loaded from the file, it displays properly. So if my data is getting saved properly, does this mean there's a bug in blitting images after set_data?

p.s. I also get strange behavior in the saved .png. The generated image displays correctly in my window, but the saved file doesn't match, almost like a whole color channel is missing. Seems like something is screwy with the channels.
Hi,
Just so you get this fix as quickly as possible, changing FORMAT to "RGBA" works for me. I think there might be some assumption about RGB == RGBA but I haven't checked yet.
HTH,
Adam.

Adam Bark

unread,
Dec 17, 2012, 4:01:55 PM12/17/12
to pyglet...@googlegroups.com
On 17/12/12 10:41, Brian Will wrote:
Ah ok, I solved it. As the documentation states "Note: You can make no assumptions about the return type; usually it will be ImageData or CompressedImageData, but patterns are free to return any subclass of AbstractImage." Therefore you must interrogate the returned image for it's format and pitch ie. on my system:
>>> print image.pitch, image.format
... 2000 RGBA

Brian Will

unread,
Dec 18, 2012, 9:59:22 AM12/18/12
to pyglet...@googlegroups.com
Great! But I'm not entirely sure about the erroneous results I was seeing. I would think that erroneously assuming 3 bytes per pixel when there are really 4 would result in color distortion, not a repetition of pixels. Nor do I understand why saving/loading as a file fixed it, nor why the .png output is still borked (though .jpg comes out fine).

--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To post to this group, send email to pyglet...@googlegroups.com.
To unsubscribe from this group, send email to pyglet-users...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/pyglet-users?hl=en.

Mitchell Ludwig

unread,
Mar 10, 2019, 5:21:25 AM3/10/19
to pyglet-users
It is 7 years later, and this fixed this for me. Thank you, internet stanger for your continued contributions to the future of civilization. Best.
Reply all
Reply to author
Forward
0 new messages