pythondjangonginximagemagickwand

Python Wand: MagickReadImage returns false, but did not raise ImageMagick exception


I've got some long-standing code in a Django code base that reads in a PDF and uses Wand to take a screenshot of the first page of the PDF, which is then displayed on the website. We recently migrated servers (an upgrade from Ubuntu 22 LTS to 24 LTS), and something broke, and I can't for the life of me figure it out.

First, some potentially useful information:

The PDF-to-PNG code is on the admin side of the web app. Here's the heart of it:

with Image(filename=pdf_location) as pdf:
  with Image(pdf.sequence[0]) as first_page_pdf:
    with first_page_pdf.convert('png') as first_page_png:
      first_page_png.background_color = Color('white')
      first_page_png.alpha_channel = 'remove'
      return first_page_png.make_blob()

When I upload a PDF to the admin site for processing, I'm getting this error:

MagickReadImage returns false, but did not raise ImageMagick exception. This can occur when a delegate is missing, or returns EXIT_SUCCESS without generating a raster.

I have tried everything I can think of after a ton of searching, but nothing is working:

$ gs --version
10.02.1
$ which gs
/usr/bin/gs
$ identify -list policy

Path: /etc/ImageMagick-6/policy.xml
  Policy: Resource
    name: disk
    value: 2GiB
  Policy: Resource
    name: map
    value: 2048MiB
  Policy: Resource
    name: memory
    value: 1024MiB
  Policy: Resource
    name: area
    value: 256MP
  Policy: Resource
    name: height
    value: 32KP
  Policy: Resource
    name: width
    value: 32KP
  Policy: Undefined
    rights: None
  Policy: Path
    rights: None
    pattern: @*
  Policy: Delegate
    rights: None
    pattern: URL
  Policy: Delegate
    rights: None
    pattern: HTTPS
  Policy: Delegate
    rights: None
    pattern: HTTP
  Policy: Coder
    rights: Read Write
    pattern: PDF

Path: [built-in]
  Policy: Undefined
    rights: None
$ gs -sDEVICE=pngalpha -o page-%03d.png -r120 pdf-test.pdf
GPL Ghostscript 10.02.1 (2023-11-01)
Copyright (C) 2023 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
Processing pages 1 through 1.
Page 1
Loading font ArialMT (or substitute) from /usr/share/ghostscript/10.02.1/Resource/Font/NimbusSans-Regular

and

$ convert -density 120 pdf-test.pdf page-%03d.png

Both correctly create page-001.png when using this test PDF.

$ ./manage_dev.py shell
19 objects imported automatically (use -v 2 for details).

Python 3.12.3 (main, Jun 18 2025, 17:59:45) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from wand.image import Image, Color
>>> with Image(filename='pdf-test.pdf') as pdf:
...   with Image(pdf.sequence[0]) as first_page_pdf:
...     with first_page_pdf.convert('png') as first_page_png:
...       first_page_png.background_color = Color('white')
...       first_page_png.alpha_channel = 'remove'
...       blob = first_page_png.make_blob()
...       with open('screenshot.png', 'wb') as png:
...         png.write(blob)
...
24420
$ convert -list configure | grep DELEGATES
DELEGATES      bzlib djvu fftw fontconfig freetype heic jbig jng jpeg lcms lqr lzma openexr openjp2 pango png ps raw tiff webp wmf x xml zlib zstd
DELEGATES      bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png raw tiff webp wmf x xml zlib

Note that this is not a typo; there are 2 DELEGATES lines here, and neither contains gs.

To reiterate, this code has worked perfectly for many years prior to this server migration/upgrade, so all this leads me to believe that it must be some configuration file (ImageMagick, nginx?) somewhere outside of my code, but I just can't nail it down. I'm really hoping that one of you might have some insights.

Thanks in advance!

[edit]

Here are some responses to comments below:

$ convert -version
Version: ImageMagick 6.9.12-98 Q16 x86_64 18038 https://legacy.imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP(4.5)
Delegates (built-in): bzlib djvu fftw fontconfig freetype heic jbig jng jp2 jpeg lcms lqr ltdl lzma openexr pangocairo png raw tiff webp wmf x xml zlib

Solution

  • Wand delegates some tasks to ImageMagick, which delegates PDF tasks to GhostScript and return again. So it is faster and easier to avoid the delegates problem by Wand ask Ghostscript direct to return the desired ImageBlob.

    Thus all you need to resolve the issue, is to swap out the code block entry but ensure the return is as expected.

    Here is a suitable emulation of a direct call and perfect return.

    Set gs to your own installation and set the required options as shown.

    enter image description here

    # ---- PERSONAL Preamble for testing the function DO NOT ADD TO function LEAVE THIS OUT
    import pymupdf, os, subprocess, tempfile, base64
    gs = os.environ.get("GS_EXE") or r"C:\Users\WDAGUtilityAccount\Desktop\Apps\PDF\gs\10.05.1\bin\gs.exe"
    def create_hello_world_pdf():
        fd, pdf_location = tempfile.mkstemp(suffix=".pdf"); os.close(fd)  # Close the file descriptor immediately
        doc = pymupdf.open(); page = doc.new_page(); page.insert_text((200, 72), "HELLO WORLD", fontsize=20)
        doc.save(pdf_location); doc.close()
        return pdf_location
    # ---- PERSONAL Preamble for testing the function DO NOT ADD TO function LEAVE THIS OUT
    
    
    
    # ---- Replacement Function that returns dotted call: first_page_png.make_blob()  
    # ---- Substitute lines from "with Image(filename=pdf_location) as pdf:"
    
    # OP required white background, no alpha
    gs_opts = "-dSAFER -sDEVICE=png16m -r120 -dFirstPage=1 -dLastPage=1 -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -o"
    
    def first_page_png_make_blob(pdf_location):
        with tempfile.TemporaryDirectory() as tmpdir:
            png_path = os.path.join(tmpdir, "page.png")
            cmd = [gs] + gs_opts.split() + [png_path, pdf_location]
            subprocess.run(cmd, check=True)
            class FirstPagePNG:
                def make_blob(self):
                    with open(png_path, "rb") as f:
                        return f.read()
            first_page_png = FirstPagePNG()
            return first_page_png.make_blob()
    
    # ---- TEST: Prove first_page_png.make_blob() returns valid PNG
    
    pdf_location = create_hello_world_pdf()
    blob = first_page_png_make_blob(pdf_location)
    encoded = base64.b64encode(blob).decode("utf-8")
    html = f"""
    <html><body><h2>Proof: PNG from dotted call first_page_png.make_blob()</h2><img src="data:image/png;base64,{encoded}" /></body></html>
    """
    with open("proof.html", "w", encoding="utf-8") as f:
        f.write(html)
    print("PNG proof written to proof.html — open it in your browser to verify.")