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 · 13 comments · May be fixed by #8506
Open

EMF files: OSError: cannot render metafile #6980

petsuter opened this issue Feb 28, 2023 · 13 comments · May be fixed by #8506
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.

@radarhere
Copy link
Member

radarhere commented Oct 9, 2024

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

Do you have examples of such files? I thought you might be referring to attachment 75058 at 2013-02-18 16:41:51 UTC in https://bugs.documentfoundation.org/show_bug.cgi?id=55058, but I was able to open load that file with Pillow without a problem.

@petsuter
Copy link
Author

petsuter commented Oct 9, 2024

Right, those are different EMF files. I have to check if I can find a public one.

@petsuter
Copy link
Author

petsuter commented Oct 9, 2024

libUEMF-0.2.8.tar.gz from https://sourceforge.net/projects/libuemf/ contains more EMF test files. Almost none of them work with Pillow.

@petsuter
Copy link
Author

there are EMR_POLYBEZIER16 records with count 0 which also fails to load in Pillow

Here is a test file test_20241010.zip
As described above it can be opened for example in Windows Paint and IrfanView, but not in Pillow (v10.4.0)

@radarhere
Copy link
Member

Thanks. Is that image one that can be included in our test suite and distributed under the Pillow license?

@radarhere
Copy link
Member

I've created #8506 to resolve this. With it, I'm able to render test_20241010.emf and test_libuemf_ref.emf.

Here are the results, saved as PNGs.

test_20241010

test_libuemf_ref

@petsuter
Copy link
Author

Sounds great, thanks!

@petsuter
Copy link
Author

petsuter commented Oct 26, 2024

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

(Apparently they moved this in the meantime. In version 7 it was here I guess:
https://github.com/dotnet/runtime/blob/v7.0.20/src/libraries/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.cs#L525

In version 9 it's now here:
https://github.com/dotnet/winforms/blob/release/9.0/src/System.Drawing.Common/src/System/Drawing/Imaging/Metafile.cs#L541
)

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

Successfully merging a pull request may close this issue.

2 participants