Image loses alpha channel after copying to & from the clipboard

If I create a wx.Image with all the pixels set to a fully transparent colour (alpha=0) and call its HasAlpha() method, it returns True (as I would expect).

If I then convert the image to a bitmap, copy the bitmap to the clipboard, get the bitmap back from the clipboard and convert it back to a wx.Image, then its HasAlpha() method returns False!

The same thing happens if I set some of the pixels in the image to any fully opaque colour (alpha=255) and perform the same operations.

However, if I set at least one of the pixels in the original image to any semi-transparent colour (alpha=1 through 254) and perform the same operations, then the final image’s HasAlpha() does return True.

I have evaluated this issue on the following setups with the same results:

  1. Python 3.6.9, wxPython 4.0.1 gtk3 (phoenix), Linux Mint 19.3
  2. Python 3.8.2, wxPython 4.0.7 gtk3 (phoenix) wxWidgets 3.0.4, Linux Mint 20.

The code below produces the following output:

incl_transparent_pixel = False
start_image.HasAlpha() = True
clipboard_image.HasAlpha() = False

incl_transparent_pixel = True
start_image.HasAlpha() = True
clipboard_image.HasAlpha() = True

Why does the clipboard discard the alpha channel for images that don’t contain any partially transparent pixels? Is this a bug or intended behaviour or am I doing something wrong?

import wx

def copyImageToClipboard(image):
    if not wx.TheClipboard.IsOpened():
        wx.TheClipboard.Open()
        bitmap = wx.Bitmap(image)
        data_object = wx.BitmapDataObject()
        data_object.SetBitmap(bitmap)
        success = wx.TheClipboard.SetData(data_object)
        wx.TheClipboard.Close()
    else:
        success = False
    return success


def getBlankImage(width, height, colour):
    r, g, b, a = colour.Get(includeAlpha=True)
    data = bytearray((r, g, b) * width * height)
    alpha = bytearray((a,) * height * width)
    image = wx.Image(width, height, data, alpha)
    return image


def getClipboardImage():
    bitmap = None
    if not wx.TheClipboard.IsOpened():
        data_obj = wx.BitmapDataObject()
        wx.TheClipboard.Open()
        success = wx.TheClipboard.GetData(data_obj)
        wx.TheClipboard.Close()
        if success:
            bitmap = data_obj.GetBitmap()
    if bitmap is not None:
        return bitmap.ConvertToImage()
    return None


def runTest(incl_transparent_pixel=False):
    print("incl_transparent_pixel =", incl_transparent_pixel)
    bg_colour = wx.Colour(255, 255, 255, 0)
    start_image = getBlankImage(16, 16, bg_colour)
    if incl_transparent_pixel:
        # Set one pixel to any transparent colour
        x, y = 0, 0
        start_image.SetRGB(x, y, 255, 0, 0)
        start_image.SetAlpha(x, y, 128)
    print("start_image.HasAlpha() =", start_image.HasAlpha())
    success = copyImageToClipboard(start_image)
    assert success
    clipboard_image = getClipboardImage()
    assert clipboard_image.IsOk()
    print("clipboard_image.HasAlpha() =", clipboard_image.HasAlpha())


def main():
    _app = wx.App()
    runTest()
    print()
    runTest(True)


if __name__ == '__main__':

    main()

I suspect that this could be a wxWidgets (wxGTK) bug/limitation. Please check https://trac.wxwidgets.org and see if there is already a ticket about this, and create one if not.

Thanks for your reply, Robin. I have raised ticket #18928.

I received a reply to my ticket which mentioned that the fully transparent alpha channel is replaced with a mask.

I have since found that if I add the lines:

if not clipboard_image.HasAlpha():
    clipboard_image.InitAlpha()

it initialises an alpha channel for the image and, if the image has a mask colour, all mask pixels will be set to completely transparent.

My application now functions as expected.

Thanks for the update.