community icon indicating copy to clipboard operation
community copied to clipboard

"bad transparency mask" error animated gifs

Open KBungei opened this issue 1 year ago • 6 comments

Software versions

Python: 3.11.6 64bit OS: Windows 11 Kivy: 2.2.1

Description of the problem

"bad transparency mask" error message when attempting to display some animated GIFs using AsyncImage 'KIVY_IMAGE' environment is default

Error message logged is "bad transparency mask"

Are there alternative image rendering options for animated GIFs, apart from pil which I believe to be what's throwing this error when it encounters an image with a bad transparency mask,

Same images work on image viewers and when opened on the browser. I was hoping Kivy has a default work around for this.

Given an image with a "bad transparency mask", this same behavior should be reproducible

minimal code that produces behavior

image_wid = AsyncImage(
    source="path-to-bad-transparency-mask-image.gif",
)

parent_wid.add_widget(image_wid)

Sample images with "bad transparency mask":

SampleGIFImage_40kbmb SampleGIFImage_350kbmb sample installer

Screenshots

Window looks like this, with the gifs in question replaced with the still loading icon: Capture-for-git-5 Capture--for-git Capture-for-git-3

Kivy logs:

[INFO   ] [deps        ] Successfully imported "kivy_deps.angle" 0.3.3
[INFO   ] [deps        ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO   ] [deps        ] Successfully imported "kivy_deps.sdl2" 0.6.0
[INFO   ] [Kivy        ] v2.2.1
[INFO   ] [Kivy        ] Installed at "C:\Python311\Lib\site-packages\kivy\__init__.py"
[INFO   ] [Python      ] v3.11.6 (tags/v3.11.6:8b6ee5b, Oct  2 2023, 14:57:12) [MSC v.1935 64 bit (AMD64)]
[INFO   ] [Python      ] Interpreter at "C:\Python311\python.exe"
[INFO   ] [Logger      ] Purge log fired. Processing...
[INFO   ] [Logger      ] Purge finished!
[INFO   ] [Factory     ] 190 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2
[INFO   ] [Window      ] Provider: sdl2
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] GLEW initialization succeeded
[INFO   ] [GL          ] Backend used <glew>
[INFO   ] [GL          ] OpenGL version <b'4.6.0 - Build 31.0.101.2125'>
[INFO   ] [GL          ] OpenGL vendor <b'Intel'>
[INFO   ] [GL          ] OpenGL renderer <b'Intel(R) HD Graphics 620'>
[INFO   ] [GL          ] OpenGL parsed version: 4, 6
[INFO   ] [GL          ] Shading version <b'4.60 - Build 31.0.101.2125'>
[INFO   ] [GL          ] Texture max size <16384>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
Scanning 1280 x 720 ...
Scanning 1920 x 1080 ...
Scanning 426 x 240 ...
Scanning 480 x 360 ...
Scanning 640 x 480 ...
Scanning bad-transparency-error ...
[DEBUG  ] [tag         ] Orientation (274) - type: short (3) - value: b'\x01\x00'
[DEBUG  ] [tag         ] XResolution (282) - type: rational (5) Tag Location: 34 - Data Location: 86 - value: b'H\x00\x00\x00\x01\x00\x00\x00'
[DEBUG  ] [tag         ] YResolution (283) - type: rational (5) Tag Location: 46 - Data Location: 94 - value: b'H\x00\x00\x00\x01\x00\x00\x00'
[DEBUG  ] [tag         ] ResolutionUnit (296) - type: short (3) - value: b'\x02\x00'
[DEBUG  ] [tag         ] YCbCrPositioning (531) - type: short (3) - value: b'\x01\x00'
[DEBUG  ] [tag         ] ExifIFD (34665) - type: long (4) - value: b'f\x00\x00\x00'
Scanning sample-gif ...
Scanning sample-gif - Copy ...
Scanning sample-gif - Copy (2) ...
['./test-imgs\\1280 x 720\\image-1.jpg', './test-imgs\\1280 x 720\\image-2.jpg', './test-imgs\\1280 x 720\\image-3.jpg', './test-imgs\\1280 x 720\\image-4.jpg']
[INFO   ] [Loader      ] using a thread pool of 2 workers
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
['./test-imgs\\1280 x 720\\image-5.jpg', './test-imgs\\1920 x 1080\\image-1.jpg', './test-imgs\\1920 x 1080\\image-10.jpg', './test-imgs\\1920 x 1080\\image-2.jpg']
['./test-imgs\\1920 x 1080\\image-3.jpg', './test-imgs\\1920 x 1080\\image-4.jpg', './test-imgs\\1920 x 1080\\image-5.jpg', './test-imgs\\1920 x 1080\\image-6.jpg']
['./test-imgs\\1920 x 1080\\image-7.jpg', './test-imgs\\1920 x 1080\\image-8.jpg', './test-imgs\\1920 x 1080\\image-9.jpg', './test-imgs\\200w.gif']
[DEBUG  ] Importing BlpImagePlugin
[DEBUG  ] Importing BmpImagePlugin
[DEBUG  ] Importing BufrStubImagePlugin
[DEBUG  ] Importing CurImagePlugin
[DEBUG  ] Importing DcxImagePlugin
[DEBUG  ] Importing DdsImagePlugin
[DEBUG  ] Importing EpsImagePlugin
[DEBUG  ] Importing FitsImagePlugin
[DEBUG  ] Importing FitsStubImagePlugin
[DEBUG  ] Importing FliImagePlugin
[DEBUG  ] Importing FpxImagePlugin
[DEBUG  ] [Image       ] failed to import FpxImagePlugin: No module named 'olefile'
[DEBUG  ] Importing FtexImagePlugin
[DEBUG  ] Importing GbrImagePlugin
[DEBUG  ] Importing GifImagePlugin
[DEBUG  ] Importing GribStubImagePlugin
[DEBUG  ] Importing Hdf5StubImagePlugin
[DEBUG  ] Importing IcnsImagePlugin
[DEBUG  ] Importing IcoImagePlugin
[DEBUG  ] Importing ImImagePlugin
[DEBUG  ] Importing ImtImagePlugin
[DEBUG  ] Importing IptcImagePlugin
[DEBUG  ] Importing JpegImagePlugin
[DEBUG  ] Importing Jpeg2KImagePlugin
[DEBUG  ] Importing McIdasImagePlugin
[DEBUG  ] Importing MicImagePlugin
[DEBUG  ] [Image       ] failed to import MicImagePlugin: No module named 'olefile'
[DEBUG  ] Importing MpegImagePlugin
[DEBUG  ] Importing MpoImagePlugin
[DEBUG  ] Importing MspImagePlugin
[DEBUG  ] Importing PalmImagePlugin
[DEBUG  ] Importing PcdImagePlugin
[DEBUG  ] Importing PcxImagePlugin
[DEBUG  ] Importing PdfImagePlugin
[DEBUG  ] Importing PixarImagePlugin
[DEBUG  ] Importing PngImagePlugin
[DEBUG  ] Importing PpmImagePlugin
[DEBUG  ] Importing PsdImagePlugin
[DEBUG  ] Importing QoiImagePlugin
[DEBUG  ] Importing SgiImagePlugin
[DEBUG  ] Importing SpiderImagePlugin
[DEBUG  ] Importing SunImagePlugin
[DEBUG  ] Importing TgaImagePlugin
[DEBUG  ] Importing TiffImagePlugin
[DEBUG  ] Importing WebPImagePlugin
[DEBUG  ] Importing WmfImagePlugin
[DEBUG  ] Importing XbmImagePlugin
[DEBUG  ] Importing XpmImagePlugin
[DEBUG  ] Importing XVThumbImagePlugin
['./test-imgs\\426 x 240\\image-1.jpg', './test-imgs\\426 x 240\\image-2.jpg', './test-imgs\\426 x 240\\image-3.jpg', './test-imgs\\426 x 240\\image-4.jpg']
['./test-imgs\\426 x 240\\image-5.jpg', './test-imgs\\480 x 360\\image-1.jpg', './test-imgs\\480 x 360\\image-2.jpg', './test-imgs\\480 x 360\\image-3.jpg']
['./test-imgs\\480 x 360\\image-4.jpg', './test-imgs\\480 x 360\\image-5.jpg', './test-imgs\\640 x 480\\image-1.jpg', './test-imgs\\640 x 480\\image-2.jpg']
['./test-imgs\\640 x 480\\image-3.jpg', './test-imgs\\640 x 480\\image-4.jpg', './test-imgs\\640 x 480\\image-5.jpg', './test-imgs\\bad-transparency-error\\installer.gif']
bad transparency mask
['./test-imgs\\bad-transparency-error\\sample.gif', './test-imgs\\bad-transparency-error\\SampleGIFImage_350kbmb.gif', './test-imgs\\bad-transparency-error\\SampleGIFImage_40kbmb.gif', './test-imgs\\image-1.jpg']
bad transparency mask
bad transparency mask
bad transparency mask
['./test-imgs\\sample-gif\\200w.gif', './test-imgs\\sample-gif\\209736462-a686bb2c-b99d-480a-9798-13e204083281.gif', './test-imgs\\sample-gif\\giphy.gif', './test-imgs\\sample-gif\\installer.gif']
bad transparency mask

KBungei avatar Jan 08 '24 01:01 KBungei

The AsycImage widget is not calling the error callback or throwing an exception with any of the animated gifs above. It does not display with image.

Using the test code below and the gif images posted above. Using any of the images above will cause the message "bad transparency mask" to be printed to the console. It is not a formatted log message. The error callback is not dispatched, and there is no exception thrown.

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import AsyncImage


class CheckBadMaskApp(App):
    def build(self):
        return GridLayout(cols=2)

    def on_start(self):
        try:
            ai = AsyncImage(source='good.jpg', on_load=self.load_callback, on_error=self.error_callback)
            self.root.add_widget(ai)
            ai = AsyncImage(source='installer.gif', on_load=self.load_callback, on_error=self.error_callback)
            self.root.add_widget(ai)
        except Exception as e:
            print(f'Exception {e}')

    def error_callback(self, obj):
        print('error callback')

    def load_callback(self, obj):
        print('load callback')


CheckBadMaskApp().run()

Using a "good" image, the image is visible and the load_callback is executed. Using an 'installer.gif' causes an issue. "bad transparency mask" is printed to the console, it is not a formatted log message. The on_error or on_load callbacks are not called. An exception is not thrown.

`

ElliotGarbus avatar Jan 10 '24 00:01 ElliotGarbus

@ElliotGarbus I really appreciate you taking the time to help me articulate the issue in a more professional and comprehensive way. Your rephrasing makes the problem much clearer and easier for others to understand. I'm especially grateful for the detailed code example you provided, as it effectively demonstrates the specific behavior I'm encountering. Edited to remove all that quote reply

KBungei avatar Jan 10 '24 00:01 KBungei

Thanks @ElliotGarbus: I was just making a similar comment when yours appeared.

There are several issues here, and I want to refocus it.

  1. img_pil.py has a comment:

Support for GIF animation added.

Gif animation has a lot of issues(transparency/color depths... etc). In order to keep it simple, what is implemented here is what is natively supported by the PIL library.

As a general rule, try to use gifs that have no transparency. Gif's with transparency will work but be prepared for some artifacts until transparency support is improved.

So adding transparency support would be nice.

That said, the two GIFs I examined didn't have transparency (image.mode == 'P').

  1. Correctly or not, PIL is raising an exception. However, it is not being caught in the case of AsyncImage and sent to on_error(). If true, this is a bug that should be fixed.

Related: LoaderBase._load() is raising an exception. It should not do this; it should log the error. This is a bug that needs to be fixed.

  1. kivy.loader's _Worker is catching any Exception and printing it, rather than logging it as an exception. This is a bug that should be fixed.

  2. OP is asking for a work-around. That is out of scope for this bug report.

While you are in the area, loader.py has an except: block with no exception named. This means it traps memory errors and abort commands. It should be replaced with the minimal exceptions expected.

Julian-O avatar Jan 10 '24 00:01 Julian-O

Thanks @Julian-O for the analysis and bug identification, This helped me understand the underlying issues. Fixing the exception handling would be fantastic. Edited to remove all that quote reply

KBungei avatar Jan 10 '24 01:01 KBungei

@KBungei If you’d like some help on some possible work arounds, we can resume the discussion on Reddit.

ElliotGarbus avatar Jan 10 '24 02:01 ElliotGarbus

@ElliotGarbus Thank you! I'd be happy to discuss them on Reddit.

KBungei avatar Jan 10 '24 02:01 KBungei