Search code examples
pythonpython-3.xwxpython

Load image on python CheckListCtrl with transparency


I'm starting to use WX GUI on python, and on my "Hello Word" project I'm trying to create a program with the ability to read any image and show it as icon into a CheckListCtrl column. I've done the first part (read the image and draw it into the CheckListCtrl), but I'm not able to load a PNG image and keep the transparency on that icon.

My code is the following:

'''
17 June 2018
@autor: Daniel Carrasco
'''

import wx
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
import sys
from pathlib import Path

BACKGROUNDCOLOR = (240, 240, 240, 255);

class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
  def __init__(self, parent):
    wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER,
              size=wx.Size(395, 467), pos=wx.Point(10, 20));
    CheckListCtrlMixin.__init__(self);
    ListCtrlAutoWidthMixin.__init__(self);

#====================================================================
class MainFrame(wx.Frame):
  def __init__(self, *args, **kwargs):
    self.dataFolder = {
      "images": Path("images/")
    }

    wx.Frame.__init__(self, *args, **kwargs);

    icon = wx.Icon("icons.ico", wx.BITMAP_TYPE_ICO)
    self.SetIcon(icon);

    self.createWidgets();
    self.createButtons();
    self.Show();


  #----------------------------------------------------------
  def exitGUI(self, event):       # callback
    self.Destroy();

  #----------------------------------------------------------
  def createWidgets(self):
    self.CreateStatusBar();      # wxPython built-in method
    self.createMenu();

    # Creamos el panel
    boxSizer = wx.BoxSizer();

    panel = wx.Panel(self);
    panel.SetBackgroundColour(BACKGROUNDCOLOR);
    panel.SetSizerAndFit(boxSizer);

    staticBox = wx.StaticBox( panel, -1, "Listado de Saves", size=(415, 500),
              pos=wx.Point(5, 0) )   
    self.statBoxSizerV = wx.StaticBoxSizer(staticBox, wx.VERTICAL)

    # Lista de items
    self.itemList = CheckListCtrl(staticBox);
    self.itemList.InsertColumn(0, '', width=32);
    self.itemList.InsertColumn(1, 'Icono', width=52);
    self.itemList.InsertColumn(2, 'Título', width=140);

    self.il_Small = self.itemList.GetImageList(wx.IMAGE_LIST_SMALL);
    self.il = wx.ImageList(48, 48, wx.IMAGE_LIST_SMALL);
    self.itemList.SetImageList(self.il, wx.IMAGE_LIST_SMALL);

    image = wx.Image(str(self.dataFolder["images"] / "tick_1.png"), wx.BITMAP_TYPE_ANY);
    self.il.Add(wx.Bitmap(image));

    image = wx.Image(str(self.dataFolder["images"] / 'tick_2.png'), wx.BITMAP_TYPE_ANY);
    self.il.Add(wx.Bitmap(image));

    image = wx.Image(str(self.dataFolder["images"] / 'exit.png'), wx.BITMAP_TYPE_ANY );
    for x in range(0, 4):
      for y in range(0, 4):
        image.SetAlpha(x, y, 0);
    image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
    image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
#    image.ClearAlpha();
    self.il.Add(wx.Bitmap(image));

    image = wx.Image(str(self.dataFolder["images"] / 'test.png'), wx.BITMAP_TYPE_ANY );
    image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
    image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
    self.il.Add(image.ConvertToBitmap());

    index = self.itemList.InsertItem(sys.maxsize, "test");
    self.itemList.SetItemColumnImage(0, 1, 3)
    #self.itemList.Append("Prueba");

  #----------------------------------------------------------
  def createButtons(self):
    pass

  #----------------------------------------------------------
  def createMenu(self):
    # Menú Archivo
    APP_EXIT = 1;
    mArchivo = wx.Menu();
    qmi = wx.MenuItem(mArchivo, APP_EXIT, '&Salir\tCtrl+Q');
    image = wx.Image(str(self.dataFolder["images"] / 'exit.png'),wx.BITMAP_TYPE_PNG);
    image = image.Scale(16, 16, wx.IMAGE_QUALITY_HIGH);
    qmi.SetBitmap(image.ConvertToBitmap());
    mArchivo.Append(qmi);
    self.Bind(wx.EVT_MENU, self.exitGUI, id=APP_EXIT);

    # Barra de menús
    menuBar = wx.MenuBar();

    menuBar.Append(mArchivo, "&Archivo");

    # Seteamos la barra de menús
    self.SetMenuBar(menuBar);


#======================
# Start GUI
#======================
app = wx.App()
MainFrame(None, style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX, title="Savegame Linker", size=(485,587))
app.MainLoop()

This code just read the image into and Image object, then scale the image and add a border resizing. The problem is that PNG transparency is not kept and only the border is transparent:

Transparency Wrong

If I remove the image transparency with Photoshop (adding white background), then the image is showed with the transparency I want:

transparency OK

Is there any way to keep the PNG transparency on CheckListCtrl, or at least add a white background to the image (that looks like an alternative solution). I want to do it if posible using only WX, because I think that use pillow module for example, just for remove transparency, is not an optimal solution.

Thanks!!


Solution

  • I think that the right way is converting the transparency into mask. I've already tested it, but looks like I've used the function after other that made it fail.

    image = wx.Image(str(self.dataFolder["images"] / 'test.png'), wx.BITMAP_TYPE_ANY );
    image = image.Scale(40, 40, wx.IMAGE_QUALITY_HIGH);
    image.ConvertAlphaToMask(threshold=50);
    image = image.Size(wx.Size(48,48), wx.Point(4,4), 255, 255, 255);
    self.il.Add(image.ConvertToBitmap());
    

    The last time I'd tried the function was after the image.Size function, and then it fails (maybe the Size function removes the transparency), but if is done before then works.

    Thanks again and greetings!!

    EDIT:

    A few months later I've continued the project and I've found another way to make the background transparent: Remove the transparency converting it to solid white:

    def remove_transparency(im, bg_colour=(255, 255, 255)):
        # Only process if image has transparency (http://stackoverflow.com/a/1963146)
        if im.mode in ('RGBA', 'LA') or (im.mode == 'P' and 'transparency' in im.info):
                # Need to convert to RGBA if LA format due to a bug in PIL (http://stackoverflow.com/a/1963146)
                alpha = im.convert('RGBA').split()[-1]
                # Create a new background image of our matt color.
                # Must be RGBA because paste requires both images have the same format
                # (http://stackoverflow.com/a/8720632    and    http://stackoverflow.com/a/9459208)
                bg = Image.new("RGBA", im.size, bg_colour + (255,))
                bg.paste(im, mask=alpha)
                return bg
    
        else:
                return im
    
    # Open the image
    sbuf = BytesIO(campo[4])
    im = Image.open(sbuf)
    
    # Remove transparency (white background will be transparent on ImageList)
    im2 = remove_transparency(im).convert("RGB")
    im.close()
    
    # Create an wx.Image from image
    width, height = im2.size
    image = wx.Image(width, height, im2.tobytes())
    image = image.Size(wx.Size(48,48), wx.Point(2,2), 255, 255, 255)
    
    # Convert it to Bitmap and add it to ImageList
    image = image.ConvertToBitmap()
    icon_image = self.il.Add(image)
    sbuf.close()
    

    I've changed slightly the way I store the images, and now they comes from a PNG stored on an SQLite DB BLOB (campo[4]).

    Greetings!!