Pythonista-Issues icon indicating copy to clipboard operation
Pythonista-Issues copied to clipboard

Pillow open black image

Open jbellanca opened this issue 10 months ago • 5 comments

This code works perfect on my Mac, but does not work in Pythonista. It loads an image from a website, saves it as a PNG, and does some image manipulation. When run in Pythonista, it loads the image ok, but the save to PNG and any use of the image comes out completely black. (It's downloading a standard 8-bit RGB jpeg). Same code on run on my Mac works perfect, with the correct output images.

Is there a bug somewhere, or am I misunderstanding something fundamentally different in how Pythonista works on iOS vs Python on a Mac?

import requests
from bs4 import BeautifulSoup
import re
from PIL import Image, ImageDraw, ImageFile
	
def get_page_source(url):
    """Fetch the page source from a given URL."""
    headers = {'User-Agent': 'Mozilla/5.0'}  # Set user-agent to avoid blocking
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    return response.text

def find_a1_link(source):
    """Find the link containing 'Page A1' text."""
    soup = BeautifulSoup(source, 'html.parser')
    a_tag = soup.find('a', string=re.compile("Page A1", re.IGNORECASE))
    return a_tag['href'] if a_tag else None

def find_magnified_image(source):
    """Find the URL of the image ending in '.magnified.jpg'."""
    match = re.search(r'https?://[^\s"]+\.magnified\.jpg', source)
    return match.group(0) if match else None

def save_image(image_url, filename='page_a1.jpg'):
    """Download and save the image from the given URL."""
    response = requests.get(image_url, stream=True)
    response.raise_for_status()
    with open(filename, 'wb') as file:
        for chunk in response.iter_content(1024):
            file.write(chunk)
    print(f"Image saved as {filename}")

def process_image():
    # Open the original image
    original_image = Image.open("page_a1.jpg")
    original_image.save("page_a1.png", "PNG")
    
    # Calculate new height to maintain aspect ratio
    new_width = 391
    aspect_ratio = original_image.height / original_image.width
    new_height = int(new_width * aspect_ratio)
    
    # Resize the image proportionally
    resized_image = original_image.resize((new_width, new_height), Image.LANCZOS)
    
    # Create a new blank image with white background
    new_image = Image.new("RGB", (800, 480), "white")
    
    # Calculate position to center the halves vertically
    top_half = resized_image.crop((0, 0, new_width, new_height // 2))
    bottom_half = resized_image.crop((0, new_height // 2, new_width, new_height))
    
    top_y = (480 - (new_height // 2)) // 2
    bottom_y = (480 - (new_height // 2)) // 2
    
    # Paste the halves onto the new image
    new_image.paste(top_half, (5, top_y))
    new_image.paste(bottom_half, (405, bottom_y))
    
    # Draw the vertical line
    draw = ImageDraw.Draw(new_image)
    draw.line([(399, 0), (399, 480)], fill=(128, 128, 128), width=2)
    
    # Save the new image
    new_image.save("page_a1_resized.jpg", "JPEG")
    
if __name__ == "__main__":
    main_url = "https://buffalonews.com/eedition/"
    main_source = get_page_source(main_url)
    
    a1_link = find_a1_link(main_source)
    if not a1_link:
        print("Could not find 'Page A1' link.")
    else:
        if not a1_link.startswith("http"):
            a1_link = main_url.rstrip('/') + '/' + a1_link  # Handle relative links
        
        a1_source = get_page_source(a1_link)
        image_url = find_magnified_image(a1_source)
        
        if not image_url:
            print("Could not find magnified image URL.")
        else:
            save_image(image_url)
        
        process_image()

jbellanca avatar Feb 18 '25 17:02 jbellanca

It worked for me on the new Pythonista beta on my old iPad. Just after you write the image, call console.quicklook(file_path) to see it. The image looked good to me.

Reminded me of days gone by because I was born and raised in Buffalo 🦬 but now live in Switzerland 🇨🇭.

cclauss avatar Feb 18 '25 18:02 cclauss

Huh. I was using Pythonista v3.4 (3400012) on my iPhone 16 Pro Max (iOS 18.3.1) and get the black screen problem. I tried on my iPad Air and got the same results. Maybe it's a bug that's fixed in the beta you're using?

It saves the original jpg from the web perfectly fine, but the png and edited jpg (page_a1_resized.jpg) have the problem. It's like PIL can't read the image after it's saved. Actually the code above (which works on the Mac fine) also throws an error in Pythonista for me (OSError broken data stream when reading image file) as is, unless I add a line to load truncated images after it imports PIL. ("ImageFile.LOAD_TRUNCATED_IMAGES = True" as a new line 5.)

I thought maybe there's something weird with the jpeg it downloads, but it works on the Mac, sooo? idk.

Link to the two files it produces for me: https://imgur.com/a/nBMEVNh

(Cool that you're from here! Been researching a ski trip to Switzerland for next season. I'll drop some wings off at the airport for you hahaha)

jbellanca avatar Feb 18 '25 20:02 jbellanca

@jbellanca I see what you mean. The processed image is black for me as well (even in beta) and I need to set ImageFile.LOAD_TRUNCATED_IMAGES to make it work at all. Not sure what's going wrong there right now.

omz avatar Feb 19 '25 11:02 omz

It seems to have something to do with the handling of incomplete(?) jpeg files in Pillow.

I haven't looked into it very much, but here's at least a workaround you can try (add this at the beginning of process_image:

import ui
ui_img = ui.Image('page_a1.jpg')
with open('page_a1.jpg', 'wb') as f:
    f.write(ui_img.to_jpeg())

This will open and re-encode the original image using the ui module, and then the rest seems to work as is.

omz avatar Feb 19 '25 13:02 omz

Thanks for the workaround - totally appreciate it!

jbellanca avatar Feb 19 '25 18:02 jbellanca