You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Retina显示器下Pillow默认72DPI显示模糊,如何实现高DPI适配?

解决Mac M2 Retina显示器上Pillow图片显示像素化问题

问题背景

在Mac M2 Retina显示器上使用Pillow处理150×150像素图片并添加圆形遮罩后,因默认72DPI导致显示像素化;使用更大图片会占用过多屏幕空间,且无法利用Retina的高DPI特性。尝试过放大重采样、手动设置DPI、优化抗锯齿、CustomTkinter、Quartz API等方法均无效,需寻求绕过Pillow限制实现高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

解决方案

1. 基于Retina设备像素比创建2倍分辨率图片

Retina显示器默认采用2x设备像素比(DPR),即1个逻辑像素对应2个物理像素。按目标显示尺寸的2倍创建图片,再通过CTkImage指定实际显示的逻辑尺寸,让系统自动适配高DPI:

def create_circular_image(self, image_path, diameter, border_width):
    # 适配Retina的2x缩放因子
    retina_diameter = diameter * 2
    retina_border_width = border_width * 2
    try:
        img = Image.open(image_path)
        square_size = min(img.width, img.height)
        img = img.crop((0, 0, square_size, square_size))

        # 用2倍尺寸进行高画质重采样
        img = img.resize((retina_diameter, retina_diameter), Image.LANCZOS)

        # 圆形遮罩同步使用2倍尺寸,保证边缘抗锯齿精度
        mask = Image.new("L", img.size, 0)
        draw = ImageDraw.Draw(mask)
        draw.ellipse((0, 0, retina_diameter, retina_diameter), fill=255)
        img.putalpha(mask)

        group = os.path.basename(os.path.dirname(image_path))
        ring_color = GROUP_COLORS.get(group, "white")

        # 边框也使用2倍尺寸创建
        new_retina_diameter = retina_diameter + 2 * retina_border_width
        bordered_img = Image.new("RGBA", (new_retina_diameter, new_retina_diameter), ring_color)
        border_mask = Image.new("L", (new_retina_diameter, new_retina_diameter), 0)
        border_draw = ImageDraw.Draw(border_mask)
        border_draw.ellipse((0, 0, new_retina_diameter, new_retina_diameter), fill=255, outline=0)

        img_pos = ((new_retina_diameter - img.width) // 2, (new_retina_diameter - img.height) // 2)
        bordered_img.paste(img, img_pos, mask)
        bordered_img.putalpha(border_mask)

        # CTkImage指定原逻辑尺寸,系统自动将高分辨率图片渲染到物理像素上
        display_size = (diameter + 2 * border_width, diameter + 2 * border_width)
        ctk_image = CTkImage(light_image=bordered_img, dark_image=bordered_img, size=display_size)

        return ctk_image
    except Exception as e:
        print(f"Error creating circular image: {e}")
        return None

2. 禁用Pillow的DPI元数据干扰

不要手动设置图片DPI元数据,Retina的显示逻辑依赖系统设备像素比,而非图片的DPI信息。确保处理后的图片不添加多余的DPI配置,让CTkImage直接处理尺寸适配。

3. 优化CustomTkinter的高DPI配置

在应用初始化阶段,确保CustomTkinter正确启用高DPI支持:

import customtkinter as ctk

ctk.set_appearance_mode(APPEARANCE_MODE)
ctk.set_default_color_theme(DEFAULT_THEME)
# 若自动缩放存在问题,可尝试手动关闭自动DPI感知,依赖系统原生缩放
ctk.deactivate_automatic_dpi_awareness()

原理说明

通过创建2倍分辨率的图片,让系统将高像素密度的内容渲染到Retina显示器的物理像素上,既保证了显示清晰度,又不会占用额外的屏幕逻辑空间,完美适配Retina的高DPI特性。

内容的提问来源于stack exchange,提问作者Chad Ify

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:04:54