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

EMF files: OSError: cannot render metafile #6980

Open
petsuter opened this issue Feb 28, 2023 · 5 comments
Open

EMF files: OSError: cannot render metafile #6980

petsuter opened this issue Feb 28, 2023 · 5 comments
Labels

Comments

@petsuter
Copy link

petsuter commented Feb 28, 2023

What did you do?

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
@petsuter
Copy link
Author

test_libuemf_ref.emf

@petsuter
Copy link
Author

petsuter commented Feb 28, 2023

(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.)

@radarhere radarhere changed the title Bug report: EMF files: OSError: cannot render metafile EMF files: OSError: cannot render metafile Mar 1, 2023
@petsuter
Copy link
Author

petsuter commented Mar 1, 2023

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:]
)

@radarhere
Copy link
Member

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?

@petsuter
Copy link
Author

petsuter commented Mar 3, 2023

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.

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

No branches or pull requests

2 participants