Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GIFs saving with unexpected transparency (or perhaps undefined pixels?) #8003

Closed
gbeales opened this issue Apr 23, 2024 · 5 comments
Closed

Comments

@gbeales
Copy link

gbeales commented Apr 23, 2024

What did you do?

Saved a gif using a library I built a while ago. Since updating Pillow to 10.2.0 (or newer) parts of the gifs would often randomly be transparent. Or so I thought: while writing this bug post it appears that undefined may be a better way to describe them.

What did you expect to happen?

None of the gif should be transparent: it was created using only a 3-channel RGB image. In this example (made using 10.1.0), the background should be black with random noise moving over it from left to right:

test_10_1

What actually happened?

The background is black before the random noise passes over it. After the noise has cleared, the background is sometimes black, sometimes it is not black. The colour that it is when it is not black seems to depend on the viewer. I have seen at least three different behaviours:

  • In this post on Edge or Chrome it displays correctly (black).
  • In the Windows Photos app, each black pixel is shown as the last colour it was defined. The same is true for viewing this post in Firefox.
  • In PowerPoint, it displays as transparent.

Some notes:

  • Simpler gifs, for example one using only two colours, do not seem to show this behaviour.
  • Similarly, which pixels come out black versus transparent/undefined varies with run-to-run of my test code due to its randomness, I believe.
  • This does not appear to happen if we colour the background a different colour. For example, the below right image shows a red background at all times, from what I can tell.
Black Background Red Background
test_10_2 test_10_2_red
Functions Erratically Functions Expectedly

Minimally working example:

import numpy as np
from PIL import Image

# define a gif that has a black background, with random noise that passes over it from left to right
# We will use numpy to create the image, and PIL to save it as a gif.

# define a black background image
base_image = np.zeros((100, 100, 3), dtype=np.uint8)
# base_image[...] = np.array((255, 0, 0), dtype=np.uint8)  # to set the background red

frames = []
for i in range(0, 200):

    img = base_image.copy()
    if i < 100:
        img[:, :i] = np.random.randint(0, 255, (100, i, 3), dtype=np.uint8)
    else:
        img[:, i-100:] = np.random.randint(0, 255, (100, 200-i, 3), dtype=np.uint8)

    # convert the numpy array to a PIL image, save in the frames list
    frames.append(Image.fromarray(img))

# save the frames as a gif
frames[0].save('test.gif', save_all=True, append_images=frames[1:], loop=0)

What are your OS, Python and Pillow versions?

Test machine 1:

  • OS: Windows 11 23H2
  • Python: 3.11.9
  • Pillow: 10.2.0, 10.3.0 (tested both)

Test machine 2:

  • OS: Ubuntu 22.04 (VM running on WSL)
  • Python: 3.8
  • Pillow: 10.2.0

Pillow Report Output

Details

--------------------------------------------------------------------
Pillow 10.2.0
Python 3.11.9 (tags/v3.11.9:de54cf5, Apr  2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]
--------------------------------------------------------------------
Python modules loaded from C:\Users\gbeales\Python Envs\facet_decoupling\Lib\site-packages\PIL
Binary modules loaded from C:\Users\gbeales\Python Envs\facet_decoupling\Lib\site-packages\PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 10.2.0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.13.2
--- LITTLECMS2 support ok, loaded 2.16
--- WEBP support ok, loaded 1.3.2
--- WEBP Transparency support ok
--- WEBPMUX support ok
--- WEBP Animation support ok
--- JPEG support ok, compiled for libjpeg-turbo 3.0.1
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.0
--- ZLIB (PNG/ZIP) support ok, loaded 1.3
--- LIBTIFF support ok, loaded 4.6.0
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
*** XCB (X protocol) support not installed
--------------------------------------------------------------------
BLP
Extensions: .blp
Features: open, save, encode
--------------------------------------------------------------------
BMP image/bmp
Extensions: .bmp
Features: open, save
--------------------------------------------------------------------
BUFR
Extensions: .bufr
Features: open, save
--------------------------------------------------------------------
CUR
Extensions: .cur
Features: open
--------------------------------------------------------------------
DCX
Extensions: .dcx
Features: open
--------------------------------------------------------------------
DDS
Extensions: .dds
Features: open, save
--------------------------------------------------------------------
DIB image/bmp
Extensions: .dib
Features: open, save
--------------------------------------------------------------------
EPS application/postscript
Extensions: .eps, .ps
Features: open, save
--------------------------------------------------------------------
FITS
Extensions: .fit, .fits
Features: open
--------------------------------------------------------------------
FLI
Extensions: .flc, .fli
Features: open
--------------------------------------------------------------------
FTEX
Extensions: .ftc, .ftu
Features: open
--------------------------------------------------------------------
GBR
Extensions: .gbr
Features: open
--------------------------------------------------------------------
GIF image/gif
Extensions: .gif
Features: open, save, save_all
--------------------------------------------------------------------
GRIB
Extensions: .grib
Features: open, save
--------------------------------------------------------------------
HDF5
Extensions: .h5, .hdf
Features: open, save
--------------------------------------------------------------------
ICNS image/icns
Extensions: .icns
Features: open, save
--------------------------------------------------------------------
ICO image/x-icon
Extensions: .ico
Features: open, save
--------------------------------------------------------------------
IM
Extensions: .im
Features: open, save
--------------------------------------------------------------------
IMT
Features: open
--------------------------------------------------------------------
IPTC
Extensions: .iim
Features: open
--------------------------------------------------------------------
JPEG image/jpeg
Extensions: .jfif, .jpe, .jpeg, .jpg
Features: open, save
--------------------------------------------------------------------
JPEG2000 image/jp2
Extensions: .j2c, .j2k, .jp2, .jpc, .jpf, .jpx
Features: open, save
--------------------------------------------------------------------
MCIDAS
Features: open
--------------------------------------------------------------------
MPEG video/mpeg
Extensions: .mpeg, .mpg
Features: open
--------------------------------------------------------------------
MSP
Extensions: .msp
Features: open, save, decode
--------------------------------------------------------------------
PCD
Extensions: .pcd
Features: open
--------------------------------------------------------------------
PCX image/x-pcx
Extensions: .pcx
Features: open, save
--------------------------------------------------------------------
PIXAR
Extensions: .pxr
Features: open
--------------------------------------------------------------------
PNG image/png
Extensions: .apng, .png
Features: open, save, save_all
--------------------------------------------------------------------
PPM image/x-portable-anymap
Extensions: .pbm, .pgm, .pnm, .ppm
Features: open, save
--------------------------------------------------------------------
PSD image/vnd.adobe.photoshop
Extensions: .psd
Features: open
--------------------------------------------------------------------
QOI
Extensions: .qoi
Features: open
--------------------------------------------------------------------
SGI image/sgi
Extensions: .bw, .rgb, .rgba, .sgi
Features: open, save
--------------------------------------------------------------------
SPIDER
Features: open, save
--------------------------------------------------------------------
SUN
Extensions: .ras
Features: open
--------------------------------------------------------------------
TGA image/x-tga
Extensions: .icb, .tga, .vda, .vst
Features: open, save
--------------------------------------------------------------------
TIFF image/tiff
Extensions: .tif, .tiff
Features: open, save, save_all
--------------------------------------------------------------------
WEBP image/webp
Extensions: .webp
Features: open, save, save_all
--------------------------------------------------------------------
WMF
Extensions: .emf, .wmf
Features: open, save
--------------------------------------------------------------------
XBM image/xbm
Extensions: .xbm
Features: open, save
--------------------------------------------------------------------
XPM image/xpm
Extensions: .xpm
Features: open
--------------------------------------------------------------------
XVTHUMB
Features: open
--------------------------------------------------------------------

@radarhere radarhere changed the title GIFs saving with unexpected transparency (or perhaps undefined pixels?). GIFs saving with unexpected transparency (or perhaps undefined pixels?) Apr 23, 2024
@radarhere radarhere added the GIF label Apr 23, 2024
@radarhere
Copy link
Member

radarhere commented Apr 23, 2024

Testing in Firefox on my Mac, I find this is due to #7568

If you would like an immediate fix, just add optimize=False to your save() command

frames[0].save('test.gif', save_all=True, append_images=frames[1:], loop=0, optimize=False)

@radarhere
Copy link
Member

radarhere commented Apr 23, 2024

I also find that setting the disposal to 1 fixes it.

frames[0].save('test.gif', save_all=True, append_images=frames[1:], loop=0, disposal=1)

The GIF specification describes disposal of 1 as "Do not dispose. The graphic is to be left in place", and our default disposal of 0 as "No disposal specified. The decoder is not required to take any action".

Testing further, the key part of changing the disposal is that Pillow adds the Graphic Control Extension. It is an optional block, and I think you could argue that according to the specification, that shouldn't change the image at all with the values we're giving it - meaning the bug is in Firefox, and I don't think Pillow is incorrect.

Is changing the disposal in your code an acceptable solution?

@radarhere
Copy link
Member

radarhere commented Apr 24, 2024

From my reading of the specification, the following Graphic Control Extension block

\x21\xf9\x04\x00\x00\x00\x00\x00

means that

  • the disposal method is 0, "No disposal specified",
  • "User input is not expected"
  • "Transparent Index is not given"
  • the delay time should not be considered because it is 0.

Because the specification states that "This block is OPTIONAL", I would expect an image to render the same way if this empty block is present or not.

However, while Google Chrome does behave according to my expectations, Firefox does not.

To demonstrate, unless I'm mistaken, the only difference between these two GIFs should be that one contains instances of this empty block, and one does not.

With Empty
with_empty

Without Empty
without_empty

Copy link

github-actions bot commented May 6, 2024

Closing this issue as no feedback has been received.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale May 6, 2024
@radarhere
Copy link
Member

I reported the problem to Firefox in webcompat/web-bugs#136378, but they couldn't see a problem - which is not at all what I expected.

The obvious fix for this would be to add the empty GCE blocks to Pillow GIFs, but given that

  • Firefox can't replicate it
  • judging by the lack of response, there's no strong interest here
  • there's a workaround for those who come across this
  • it would increase the file size for everyone by a small amount. You may think this is inconsequential, but I do think we've received requests to optimise by such small amounts before.

I'm reluctant to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants