Search code examples
colorsbitmapwxpythonpixel

Pixel information from bitmap or wxImage using wxPython


I have a question concerning getting an individual pixel’s color information from a bitmap. I’ve searched these forums, along with demos and tutorials, and while I believe I understand what I need to do in theory, I’m not able to actually do it.

Here’s an example of my code (I’ve shortened it, but this is a working example):

import os, sys
import wx
import wx.lib.plot as plot
import Image

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(500, 500))

        HolderPanel = wx.Panel(self, wx.ID_ANY)

        panel2 = MyPanel_2(HolderPanel, wx.ID_ANY)

        framesizer = wx.BoxSizer(wx.HORIZONTAL)
        framesizer.Add(panel2, 1, wx.EXPAND | wx.BOTTOM | wx.TOP | wx.RIGHT, 2)

        HolderSizer = wx.BoxSizer(wx.VERTICAL)
        HolderSizer.Add(framesizer, 1, wx.EXPAND)

        HolderPanel.SetSizer(HolderSizer)
        self.Show()


class MyPanel_2(wx.Panel):

    def __init__(self, parent, id):
        wx.Panel.__init__(self, parent, id, style=wx.SIMPLE_BORDER)
        self.SetBackgroundColour('grey')


        # Create main image holder
        self.img_1 = wx.EmptyImage(300,300)        
        self.imageCtrl_1 = wx.StaticBitmap(self, wx.ID_ANY, wx.BitmapFromImage(self.img_1)) # Starting with an EmptyBitmap, the real one will get put there by the call onView
        self.PhotoMaxSize_1 = (300)
        self.imageCtrl_1.Bind(wx.EVT_LEFT_DOWN, self.onMouseClick_img1)

        # Create the browse button
        brwsBtn = wx.Button(self, wx.ID_ANY, 'Browse', (10, 10))
        brwsBtn.Bind(wx.EVT_BUTTON, self.onBrowse)

        # Set up the text box
        self.photoTxt = wx.TextCtrl(self, size=(200,-1))

        # Create the sizers
        vsizer1 = wx.BoxSizer(wx.VERTICAL)
        hsizer3 = wx.BoxSizer(wx.HORIZONTAL)
        vsizer_MAIN = wx.BoxSizer(wx.VERTICAL)

        # -----------------------------------------------------------------------------------------------------------
        vsizer1.Add(self.imageCtrl_1, proportion = 0, flag= wx.ALIGN_CENTRE | wx.ALL, border=10)

        # -----------------------------------------------------------------------------------------------------------
        hsizer3.Add(brwsBtn, proportion = 0, flag = wx.ALIGN_CENTRE_VERTICAL | wx.LEFT | wx.RIGHT, border=10)
        hsizer3.Add(self.photoTxt, proportion = 1, flag = wx.ALIGN_CENTRE_VERTICAL | wx.LEFT | wx.RIGHT, border = 10)

        # -----------------------------------------------------------------------------------------------------------
        vsizer_MAIN.Add(vsizer1, proportion = 1, flag = wx.EXPAND)
        vsizer_MAIN.Add(hsizer3, proportion = 1, flag = wx.EXPAND)

        # -----------------------------------------------------------------------------------------------------------
        self.SetSizer(vsizer_MAIN)

    def onBrowse(self, event):
        """ 
        Browse for file
        """
        wildcard = "pictures (*.jpg, *.jpeg, *.png)|*.jpg;*.jpeg;*.png"
        dialog = wx.FileDialog(None, "Choose a file", wildcard=wildcard, style=wx.OPEN)

        if dialog.ShowModal() == wx.ID_OK:
            self.photoTxt.SetValue(dialog.GetPath())
        dialog.Destroy() 
        self.onView()

    def onView(self):

        self.filepath = self.photoTxt.GetValue()

        self.img_1 = wx.Image(self.filepath, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        W = self.img_1.GetWidth()
        H = self.img_1.GetHeight()
        if W > H:
            NewW = self.PhotoMaxSize_1
            NewH = self.PhotoMaxSize_1 * H / W
        else:
            NewH = self.PhotoMaxSize_1
            NewW = self.PhotoMaxSize_1 * W / H
        self.img_1 = self.img_1.Scale(NewW,NewH)

        self.imageCtrl_1.SetBitmap(wx.BitmapFromImage(self.img_1)) # Converts the scaled image to a wx.Bitmap and put it on the wx.StaticBitmap
        self.Refresh()

    def onMouseClick_img1(self, event):

        im = Image.open(self.filepath)
        pos = event.GetPosition()
        pix = im.load()
        print pix[pos.x, pos.y]

app = wx.App()
MyFrame(None, -1, 'Current Build')
app.MainLoop()

What this allows me to do is browse for and import an image, resize the image, and then select individual pixels to access their color information. However, there is a problem with this code: the color information does not match with the actual image (and in some cases, there’s an error relating to being out of the image’s range). I went back to check this and realized that

        im = Image.open(self.filepath)

is referencing the actual path to the image, and that’s what the event.GetPosition() is reading (since the resized image isn’t the same size, my code isn’t reading what’s presented). I realized that I can get the pixel's color information because I'm looking at a wxImage and not the converted bitmap from the original image. By adjusting the onMouseClick_img1 event to just:

    def onMouseClick_img1(self, event):

        pos = event.GetPosition()

        print pos

I can read the position of any point on my empty StaticBitMap OR on my loaded, resized image that I converted to a bitmap. However, I cannot pull the color information of the selected pixel. After searching, I found this page

and tried both methods, but ended up with errors for both. Since I’d like to use the wx.Image method, I tried this:

    def onMouseClick_img1(self, event):

        img = wx.ImageFromBitmap(self.img_1)

        pos = event.GetPosition()
        print (img.GetRed(pos), img.GetGreen(pos), img.GetBlue(pos))

but I get this error message:

Traceback (most recent call last): File "/Users/Documents/[user name]/Eclipse_workspace/builder/src/GUI/Maybe.py", line 254, in onMouseClick_img1 img = wx.ImageFromBitmap(self.img_1) File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/wx/_core.py", line 3750, in ImageFromBitmap val = core.new_ImageFromBitmap(*args, **kwargs) TypeError: in method 'new_ImageFromBitmap', expected argument 1 of type 'wxBitmap const &'

I thought that all I needed to do was convert the bitmap back to an image once I had resized it and then pull the pixel information from that, but I'm obviously doing something incorrectly somewhere. Any help would be greatly appreciated. This is my first attempt at a real GUI, and I'm new to wxPython, so if you see something more general that's wrong, please feel free to let me know.


Solution

  • So, I found a way to make this work. Perhaps I wasn't fully understanding what I needed to do in the first place (and maybe I'm still missing key ideas here), but what I've done works. It might be the case that the way that I got this working is ugly or inefficient.. but it works. Did I mention that I think this works?

    Instead of trying to pull any information from the displayed bitmap, or convert the bitmap to another format and display that, I display the resized image in a StaticBitmap and use another method to pull the pixel information:

        def onView(self):
        """ 
        This is pulling in the image and resizing it, maintaining its original ratio; if the image is square, this is fine as long as the image boxes are square; the ratios
        are getting messed up depending on the size of the picture; I would imagine that the images we're going to be importing are going to be pretty uniform.
        """        
    
        # Code imports and resizes the image
        self.filepath = self.photoTxt.GetValue()
    
        self.img_1 = wx.Image(self.filepath, wx.BITMAP_TYPE_ANY)
        # scale the image, preserving the aspect ratio
        W = self.img_1.GetWidth()
        H = self.img_1.GetHeight()
        if W > H:
            NewW = self.PhotoMaxSize_1
            NewH = self.PhotoMaxSize_1 * H / W
        else:
            NewH = self.PhotoMaxSize_1
            NewW = self.PhotoMaxSize_1 * W / H
        self.img_1 = self.img_1.Scale(NewW,NewH)
    
        self.imageCtrl_1.SetBitmap(wx.BitmapFromImage(self.img_1)) # Converts the scaled image to a wx.Bitmap and put it on the wx.StaticBitmap
        self.Refresh()
    
        # Code creates an image using wxImage to pull pixel information from
        self.img_1_IMAGE = Image.open(self.filepath)
        self.img_1_IMAGE_r = self.img_1_IMAGE.resize((NewW, NewH))
    
    def onMouseClick_img1(self, event):
    
        im = self.img_1_IMAGE_r
        pos = event.GetPosition()
        pix = im.load()
        print pix[pos.x, pos.y]
    

    The key difference seems to be (at least to me) that I didn't convert this image into a bitmap and assign it to my StaticBitmap. This allows me to access the pixel information of the resized image when onMouseClick_img1 is called. Since I use the same resizing parameters for each image, the dimensions should match up (and they seem to do so), allowing me to see the image in the StaticBitmap that's displayed while pulling pixel information from the resized image.

    If anyone has any input or concerns about this, please feel free to let me know. I just wanted to post what I found that works in case someone else was stuck with a similar issue.