Pillow
Pillow copied to clipboard
EMF files: OSError: cannot render metafile
What did you do?
-
Download example EMF file
test_libuemf_ref.emf
from https://bugs.documentfoundation.org/attachment.cgi?id=135791 (via https://bugs.documentfoundation.org/show_bug.cgi?id=55058) -
Run:
import PIL.Image
with PIL.Image.open("test_libuemf_ref.emf") as im:
im.save("out.png")
What did you expect to happen?
out.png
should be created.
What actually happened?
C:\Python311\Lib\site-packages\PIL\Image.py:3167: DecompressionBombWarning: Image size (139177600 pixels) exceeds limit of 89478485 pixels, could be decompression bomb DOS attack.
warnings.warn(
{'wmf_bbox': (0, 0, 14030, 9920), 'dpi': (1199.9124549648136, 1199.9047573693986)}
Traceback (most recent call last):
File "test.py", line 3, in <module>
im.save("test_libuemf_ref.emf")
File "C:\Python311\Lib\site-packages\PIL\Image.py", line 2394, in save
self._ensure_mutable()
File "C:\Python311\Lib\site-packages\PIL\Image.py", line 611, in _ensure_mutable
self._copy()
File "C:\Python311\Lib\site-packages\PIL\Image.py", line 604, in _copy
self.load()
File "C:\Python311\Lib\site-packages\PIL\WmfImagePlugin.py", line 162, in load
return super().load()
^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\PIL\ImageFile.py", line 345, in load
image = loader.load(self)
^^^^^^^^^^^^^^^^^
File "C:\Python311\Lib\site-packages\PIL\WmfImagePlugin.py", line 53, in load
Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
OSError: cannot render metafile
What are your OS, Python and Pillow versions?
- OS: Windows 10
- Python: 3.11
- Pillow: 9.4.0
(It seems Pillow calls the GDI routine PlayEnhMetaFile
which fails.
The same EMF file works with this C# code:
var emf_filename = @"test_libuemf_ref.emf";
var png_filename = @"out.png";
var metafile = new Metafile(emf_filename);
var metafileHeader = metafile.GetMetafileHeader();
var bitmap = new Bitmap(metafile.Width, metafile.Height);
using (var gfx = Graphics.FromImage(bitmap))
{
gfx.Clear(Color.White);
float sx = metafileHeader.DpiX / gfx.DpiX;
float sy = metafileHeader.DpiY / gfx.DpiY;
gfx.ScaleTransform(sx, sy);
gfx.DrawImage(metafile, 0, 0);
}
bitmap.Save(png_filename);
C# seems to also uses GDI (but PlayMetafileRecord
instead of PlayEnhMetaFile
? not sure) here I think: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.cs
Just in case this helps.)
I think the first record that fails record ~13 is a EMR_STROKEANDFILLPATH with "bounds" arguments 0,0,29699,20999 which is probably rejected because it is larger than the EMR_HEADER
bounds 0,0,14030,9920.
In other files there are EMR_POLYBEZIER16
records with count 0 which also fails to load in Pillow. These empty records could just be dropped e.g. like this:
import pathlib
import struct
input_path = pathlib.Path("test.emf")
output_path = pathlib.Path("filtered.emf")
input_bytes = input_path.read_bytes()
output_records = []
offset = 0
while offset < len(input_bytes):
rec_type, rec_size = struct.unpack_from('<ii', input_bytes, offset)
if rec_type == 88: # EMR_POLYBEZIER16
count = struct.unpack_from('<i', input_bytes, offset + 24)[0]
if count == 0:
offset += rec_size
continue
rec_bytes = input_bytes[offset:offset+rec_size]
output_records.append(rec_bytes)
offset += rec_size
output_bytes = b''.join(output_records)
output_path.write_bytes(
output_bytes[:0x30] +
struct.pack('<i', len(output_bytes)) +
struct.pack('<i', len(output_records)) +
output_bytes[0x38:]
)
I think the first record that fails record ~13 is a EMR_STROKEANDFILLPATH with "bounds" arguments 0,0,29699,20999 which is probably rejected because it is larger than the
EMR_HEADER
bounds 0,0,14030,9920.
So, if I understand you correctly, the image actually has bugs in it? You're requesting that Pillow be more flexible when reading?
Other programs can open and display the image without a problem, including Windows Paint and IrfanView and the C# reader above. I don't know if the image can be considered to have bugs, but maybe "unusual edge cases"?
A few days ago I didn't know anything about EMF files. I wanted to load some seemingly valid files. They were rejected for unknown reasons. If you can make Pillow more flexible to allow reading these files, I think that would be valuable.