Best way to optimise thumbnail creation?

I've added a feature to my drawing program where creating a drawing (pen stroke, rectangle, circle etc.) will update its thumbnail, a wx.BitmapButton placed on a ScrollPanel. I'm doing the updating using the method below:

def redraw(self, _id):
   board = self.gui.tabs.GetPage(_id) # currently selected tab, a ScrolledWindow
   context = wx.ClientDC(board)
   memory = wx.MemoryDC()
   x, y = board.GetClientSizeTuple()
   bitmap = wx.EmptyBitmap(x, y)
   memory.SelectObject(bitmap)
   memory.Blit(0, 0, x, y, context, 0, 0)
   memory.SelectObject(wx.NullBitmap)

   img = wx.ImageFromBitmap(bitmap)
   img.Rescale(150, 150) # thumbnail
   bmp = wx.BitmapFromImage(img)
   return bmp

my concern is the overhead of doing the screen grab->bmp->img->resize->bmp process. It's only done once, on a button_up event when the user finishes the current drawing, and seems to work decently on my machine (fast dual core), but I'm not sure if it's too computationally heavy.

Using pycallgraph, a time spent in function/time function was called graph plotter, I can see that the 27 calls to this method took 0.98 seconds when I drew 27 shapes at a reasonably fast speed.

The left mouse motion which actually draws each shape was called 1,100 times and took 0.6 seconds, for comparison.

Is there a better way I could be doing this?

Regards,
Steven Sproat.

Steven Sproat wrote:

I've added a feature to my drawing program where creating a drawing (pen stroke, rectangle, circle etc.) will update its thumbnail, a wx.BitmapButton placed on a ScrollPanel. I'm doing the updating using the method below:

def redraw(self, _id):
  board = self.gui.tabs.GetPage(_id) # currently selected tab, a ScrolledWindow
  context = wx.ClientDC(board)
  memory = wx.MemoryDC()
  x, y = board.GetClientSizeTuple()
  bitmap = wx.EmptyBitmap(x, y)
  memory.SelectObject(bitmap)
  memory.Blit(0, 0, x, y, context, 0, 0)
  memory.SelectObject(wx.NullBitmap)

  img = wx.ImageFromBitmap(bitmap)
  img.Rescale(150, 150) # thumbnail
  bmp = wx.BitmapFromImage(img)
  return bmp

my concern is the overhead of doing the screen grab->bmp->img->resize->bmp process. It's only done once, on a button_up event when the user finishes the current drawing, and seems to work decently on my machine (fast dual core), but I'm not sure if it's too computationally heavy.

Using pycallgraph, a time spent in function/time function was called graph plotter, I can see that the 27 calls to this method took 0.98 seconds when I drew 27 shapes at a reasonably fast speed.

The left mouse motion which actually draws each shape was called 1,100 times and took 0.6 seconds, for comparison.

Is there a better way I could be doing this?

I've never needed to do something like this myself but I think you can do the scaling in the process of the Blit by first setting the scale on the memory dc. That will save the round-trip to/from wx.Image.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

Steven Sproat wrote:

I've added a feature to my drawing program where creating a drawing (pen stroke, rectangle, circle etc.) will update its thumbnail, a wx.BitmapButton placed on a ScrollPanel. I'm doing the updating using the method below:

def redraw(self, _id):
  board = self.gui.tabs.GetPage(_id) # currently selected tab, a ScrolledWindow
  context = wx.ClientDC(board)
  memory = wx.MemoryDC()
  x, y = board.GetClientSizeTuple()
  bitmap = wx.EmptyBitmap(x, y)
  memory.SelectObject(bitmap)
  memory.Blit(0, 0, x, y, context, 0, 0)
  memory.SelectObject(wx.NullBitmap)

  img = wx.ImageFromBitmap(bitmap)
  img.Rescale(150, 150) # thumbnail
  bmp = wx.BitmapFromImage(img)
  return bmp

my concern is the overhead of doing the screen grab->bmp->img->resize->bmp process. It's only done once, on a button_up event when the user finishes the current drawing, and seems to work decently on my machine (fast dual core), but I'm not sure if it's too computationally heavy.

Using pycallgraph, a time spent in function/time function was called graph plotter, I can see that the 27 calls to this method took 0.98 seconds when I drew 27 shapes at a reasonably fast speed.

The left mouse motion which actually draws each shape was called 1,100 times and took 0.6 seconds, for comparison.

Is there a better way I could be doing this?

I've never needed to do something like this myself but I think you can do the scaling in the process of the Blit by first setting the scale on the memory dc. That will save the round-trip to/from wx.Image.

Although the quality of the scale from wx.Image will likely be much better.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

Steven Sproat wrote:

I've added a feature to my drawing program where creating a drawing (pen stroke, rectangle, circle etc.) will update its thumbnail, a wx.BitmapButton placed on a ScrollPanel. I'm doing the updating using the method below:

def redraw(self, _id):
  board = self.gui.tabs.GetPage(_id) # currently selected tab, a ScrolledWindow
  context = wx.ClientDC(board)
  memory = wx.MemoryDC()
  x, y = board.GetClientSizeTuple()
  bitmap = wx.EmptyBitmap(x, y)
  memory.SelectObject(bitmap)
  memory.Blit(0, 0, x, y, context, 0, 0)
  memory.SelectObject(wx.NullBitmap)

  img = wx.ImageFromBitmap(bitmap)
  img.Rescale(150, 150) # thumbnail
  bmp = wx.BitmapFromImage(img)
  return bmp

my concern is the overhead of doing the screen grab->bmp->img->resize->bmp process. It's only done once, on a button_up event when the user finishes the current drawing, and seems to work decently on my machine (fast dual core), but I'm not sure if it's too computationally heavy.

Using pycallgraph, a time spent in function/time function was called graph plotter, I can see that the 27 calls to this method took 0.98 seconds when I drew 27 shapes at a reasonably fast speed.

The left mouse motion which actually draws each shape was called 1,100 times and took 0.6 seconds, for comparison.

Is there a better way I could be doing this?

I've never needed to do something like this myself but I think you can do the scaling in the process of the Blit by first setting the scale on the memory dc. That will save the round-trip to/from wx.Image.

Hi Robin, thanks for the suggestion, but I'm not too sure if it's working correctly.

    def redraw(self, _id):
        board = self.gui.tabs.GetPage(_id)
        context = wx.BufferedDC(None, board.buffer)
        memory = wx.MemoryDC()
        x, y = board.GetClientSizeTuple()
        bitmap = wx.EmptyBitmap(x, y)
        memory.SetUserScale(0.99, 0.99)
        memory.SelectObject(bitmap)
        memory.Blit(0, 0, x, y, context, 0, 0)
        memory.SelectObject(wx.NullBitmap)

        return bitmap

I've been playing with different values for the userscale, but it seems to affect the zoom-in factor on the thumbnail, so it's only showing a section of the DC instead of a resized thumbnail of the entire DC (if that makes sense)

i.e. - ImageShack - Best place for all of your image hosting and image sharing needs
when it should look like: ImageShack - Best place for all of your image hosting and image sharing needs

another alternative I can think of is to save the screen as a file, load it with PIL, apply a size transformation there then load it back into the thumnnail. I'm guessing this would be a slower option though due to file IO rather than doing it all in memory, plus it's a larger operation. The way I'm doing it now does seem a little slow but across the 3 different computers I've tested it on, it seems to work well.

Actually, looking at the data I posted above, 0.98 seconds /27 method calls = 0.036s per call, which, while slower than most other methods in the program, is an acceptable tradeoff in terms of neat functionality vs performance.

Steven Sproat wrote:

Robin Dunn wrote:

Steven Sproat wrote:

I've added a feature to my drawing program where creating a drawing (pen stroke, rectangle, circle etc.) will update its thumbnail, a wx.BitmapButton placed on a ScrollPanel. I'm doing the updating using the method below:

def redraw(self, _id):
  board = self.gui.tabs.GetPage(_id) # currently selected tab, a ScrolledWindow
  context = wx.ClientDC(board)
  memory = wx.MemoryDC()
  x, y = board.GetClientSizeTuple()
  bitmap = wx.EmptyBitmap(x, y)
  memory.SelectObject(bitmap)
  memory.Blit(0, 0, x, y, context, 0, 0)
  memory.SelectObject(wx.NullBitmap)

  img = wx.ImageFromBitmap(bitmap)
  img.Rescale(150, 150) # thumbnail
  bmp = wx.BitmapFromImage(img)
  return bmp

my concern is the overhead of doing the screen grab->bmp->img->resize->bmp process. It's only done once, on a button_up event when the user finishes the current drawing, and seems to work decently on my machine (fast dual core), but I'm not sure if it's too computationally heavy.

Using pycallgraph, a time spent in function/time function was called graph plotter, I can see that the 27 calls to this method took 0.98 seconds when I drew 27 shapes at a reasonably fast speed.

The left mouse motion which actually draws each shape was called 1,100 times and took 0.6 seconds, for comparison.

Is there a better way I could be doing this?

I've never needed to do something like this myself but I think you can do the scaling in the process of the Blit by first setting the scale on the memory dc. That will save the round-trip to/from wx.Image.

Hi Robin, thanks for the suggestion, but I'm not too sure if it's working correctly.

   def redraw(self, _id):
       board = self.gui.tabs.GetPage(_id)
       context = wx.BufferedDC(None, board.buffer)
       memory = wx.MemoryDC()
       x, y = board.GetClientSizeTuple()
       bitmap = wx.EmptyBitmap(x, y)
       memory.SetUserScale(0.99, 0.99)
       memory.SelectObject(bitmap)
       memory.Blit(0, 0, x, y, context, 0, 0)
       memory.SelectObject(wx.NullBitmap)

       return bitmap

I've been playing with different values for the userscale, but it seems to affect the zoom-in factor on the thumbnail, so it's only showing a section of the DC instead of a resized thumbnail of the entire DC (if that makes sense)

Hmm... I must have been thinking of something else.

another alternative I can think of is to save the screen as a file, load it with PIL, apply a size transformation there then load it back into the thumnnail. I'm guessing this would be a slower option though due to file IO rather than doing it all in memory, plus it's a larger operation.

Yes, that would be even slower than what you are doing now.

···

The way I'm doing it now does seem a little slow but across the 3 different computers I've tested it on, it seems to work well.

Actually, looking at the data I posted above, 0.98 seconds /27 method calls = 0.036s per call, which, while slower than most other methods in the program, is an acceptable tradeoff in terms of neat functionality vs performance.

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!