Pillow/Python 在 Retina 显示屏上默认为 72 DPI - 看起来很糟糕。如何获得更高的 DPI?

问题描述 投票:0回答:1

我在 Retina 显示屏上使用 Pillow 显示图像时遇到问题。我有一个 150x150 像素的图像文件夹,当我尝试使用圆形蒙版显示它们时,由于默认的 72 DPI,它们显得像素化且质量低。 如果我尝试使用更大的图像,它只会在我的屏幕上占据太多空间,并且也不利用其视网膜显示屏。

我想要实现的是控制 DPI 或正确缩放这些图像以匹配我的 Retina 显示屏(具有更高的 DPI)。这是相关代码片段和屏幕截图:

在此输入图片描述


# Constants for general settings
WINDOW_MIN_WIDTH = 700
WINDOW_MIN_HEIGHT = 350
GRID_COLUMNS = 9  # Number of columns for the grid layout
IMAGE_DIAMETER = 132  # Diameter for the circular images
border_width = 4
DEFAULT_THEME = "blue"
APPEARANCE_MODE = "dark"
IMAGE_BRIGHTNESS = 0.5

#.... Omitted a bunch of code which is more about how to find the images using os pointless for this dpi issue

    def create_circular_image(self, image_path, diameter, border_width):
        """
        Create a circular image with a border and anti-aliasing for smoother edges.
        """
        try:
            # Load the image and ensure it's square
            img = Image.open(image_path)
            square_size = min(img.width, img.height)
            img = img.crop((0, 0, square_size, square_size))

            # Resize with high-quality resampling to reduce pixelation
            img = img.resize((diameter, diameter), Image.LANCZOS)

            # Apply a circular mask with anti-aliasing
            mask = Image.new("L", img.size, 0)
            draw = ImageDraw.Draw(mask)
            draw.ellipse((0, 0, diameter, diameter), fill=255)
            img.putalpha(mask)

            # Determine the group (subdirectory) and border color
            group = os.path.basename(os.path.dirname(image_path))
            ring_color = GROUP_COLORS.get(group, "white")  # Default to white if not found

            # Create a new border with anti-aliasing
            new_diameter = diameter + 2 * border_width
            bordered_img = Image.new("RGBA", (new_diameter, new_diameter), ring_color)
            border_mask = Image.new("L", (new_diameter, new_diameter), 0)
            border_draw = ImageDraw.Draw(border_mask)
            border_draw.ellipse((0, 0, new_diameter, new_diameter), fill=255, outline=0)

            # Calculate the position to center the circular image within the bordered frame
            img_pos = ((new_diameter - img.width) // 2, (new_diameter - img.height) // 2)

            # Apply the circular mask to ensure smooth edges
            bordered_img.paste(img, img_pos, mask)
            bordered_img.putalpha(border_mask)

            # Convert to CTkImage for high DPI support
            ctk_image = CTkImage(light_image=bordered_img, dark_image=bordered_img, size=(new_diameter, new_diameter))

            return ctk_image



#... Omitted more until I reference the actual creation for the buttons which calls that function

    def create_phenotype_controls(self, image_paths):
        """
        Create controls for images and sliders.
        """
        phenotype_names = self.extract_phenotypes_from_paths(image_paths)

        for idx, (image_path, phenotype_name) in enumerate(itertools.zip_longest(image_paths, phenotype_names)):
            frame = ctk.CTkFrame(self.grid_frame)
            frame.grid(row=idx // GRID_COLUMNS, column=idx % GRID_COLUMNS, padx=2.5, pady=2.5)


            # Create circular image
            image = self.create_circular_image(image_path, IMAGE_DIAMETER, border_width)
            image_widget = ctk.CTkButton(frame, image=image, text="",command=lambda idx=idx: self.toggle_phenotype(idx),fg_color="gray12")  # Ensure no text
            image_widget.pack(padx=0,pady=0, fill="both")  # Fill the available space

            self.buttons.append(image_widget)  # Store the button reference

#... Omitted last bit of code here

我似乎无法找到一种方法来正确调整 Pillow 中的 DPI 以与我的 Retina 显示屏配合使用,并避免应用圆形蒙版时出现像素化。是否有解决方案或解决方法可以使用 Pillow 在 Retina 显示屏上实现更高 DPI 的图像渲染?

这是我迄今为止尝试过的:

  • 增加图像尺寸:我放大了图像并应用了各种重采样技术(如
    Image.LANCZOS
    )来提高清晰度,但这并没有产生太大的影响。
  • 手动设置 DPI:我在使用 Pillow 保存图像时调整了 DPI 设置,但它仍然默认为 72 DPI,导致我的 Retina 显示屏上出现像素化、低质量的图像。
  • 应用圆形蒙版:我创建了具有抗锯齿功能的圆形蒙版来平滑边缘,但它们看起来仍然粗糙且像素化。
  • CustomTkinter:我测试了 CustomTkinter,它提供了灵活性,并且使用圆形对象效果更好,但是当使用 Pillow 处理图像时,我无法克服 72 DPI 的限制。
  • Mac API(Quartz 和 pyobjc):我什至通过
    pyobjc
    包探索了像 Quartz 这样的 macOS API,希望它能帮助我直接管理高 DPI 渲染,但图像看起来仍然不正确。

尽管进行了所有这些尝试,我仍无法以 Mac M2 Retina 显示屏的质量显示图像。感觉 Pillow 无法正确处理高 DPI 设置。有没有办法绕过这个限制,或者有没有更好的方法在 Retina 显示屏上使用 Python 显示高 DPI 图像?

python image tkinter python-imaging-library customtkinter
1个回答
0
投票

对于DPI问题,你只能为tkinter设置DPI,但这没有帮助。

IMO,您并没有真正创建抗锯齿圆,并且不能通过简单的 Pillow 方法来完成。

示例代码

from math import sqrt
from io import BytesIO
from copy import deepcopy
from PIL import Image, ImageDraw, ImageTk
import tkinter as tk

def move_to_center():
    root.update()
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    win_width = root.winfo_width()
    win_height = root.winfo_height()
    x, y = (screen_width - win_width) // 2, (screen_height - win_height) // 2
    root.geometry(f"+{x}+{y}")

def circle(radius=10, color=(0, 0, 255)):
    r0 = radius
    r_square = r0**2
    x, y = 0, r0-1  # left-botton of point, measure right-top point
    r, g, b = color
    div = 4
    im = Image.new("RGBA", (2*r0, 2*r0), (0, 0, 0, 0))

    while y>=x:
        total = 0
        for dy in range(1, div+1):
            for dx in range(1, div+1):
                if r_square >= (x+dx/4)**2+(y+dy/4)**2:
                    total += 1
        a = int(255*total/div**2)
        c = (r, g, b, a)
        im.putpixel((r0+x,   r0+y),   c)
        im.putpixel((r0+y,   r0+x),   c)
        im.putpixel((r0+x,   r0-y-1), c)
        im.putpixel((r0+y,   r0-x-1), c)
        im.putpixel((r0-x-1, r0+y),   c)
        im.putpixel((r0-y-1, r0+x),   c)
        im.putpixel((r0-x-1, r0-y-1), c)
        im.putpixel((r0-y-1, r0-x-1), c)

        x += 1
        if (x+1)**2+y**2 >= r_square:
            y -= 1

    c = color + (255, )
    draw = ImageDraw.Draw(im)
    draw.ellipse((2, 2, 2*r0-3, 2*r0-3), fill=c, width=0)
    # fill color
    points = [(1, r0)]
    checked = []
    while points:
        temp = []
        for point in points:
            checked.append(point)
            r, g, b, a = im.getpixel(point)
            if (r, g, b) != color:
                im.putpixel(point, c)
                x0, y0 = point
                for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
                    p = (x0+dx, y0+dy)
                    if p not in checked and p not in temp:
                        temp.append(p)
        points = deepcopy(temp)
    return ImageTk.PhotoImage(im)

root = tk.Tk()

radius = 150
font = ("Courier New", 16)
image1 = circle(radius, (0, 0, 255))

frame1 = tk.Frame(root)
frame1.pack(side=tk.LEFT)
canvas1 = tk.Canvas(frame1, width=radius*2+10, height=radius*2+10, background='white')
canvas1.pack()
label1 = tk.Label(frame1, text="Anti-Aliasing Circle", font=font)
label1.pack()
canvas1.create_image(radius+5, radius+5, image=image1)

im = Image.new("RGBA", (radius*2, radius*2), (255, 255, 255, 0))
draw = ImageDraw.Draw(im)
draw.ellipse((0, 0, radius*2, radius*2), fill=(0, 0, 255, 255))
image2 = ImageTk.PhotoImage(im)

frame2 = tk.Frame(root)
frame2.pack(side=tk.LEFT)
canvas2 = tk.Canvas(frame2, width=radius*2+10, height=radius*2+10, background='white')
canvas2.pack()
label2 = tk.Label(frame2, text="Pillow Circle", font=font)
label2.pack()
canvas2.create_image(radius+5, radius+5, image=image2)

frame3 = tk.Frame(root)
frame3.pack(side=tk.LEFT)
canvas3 = tk.Canvas(frame3, width=radius*2+10, height=radius*2+10, background='white')
canvas3.pack()
label3 = tk.Label(frame3, text="Tkinter Circle", font=font)
label3.pack()
canvas3.create_oval(5, 5, 2*radius+5, 2*radius+5, fill="#0000FF", outline="#0000FF")

move_to_center()
root.mainloop()

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.