我在 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
)来提高清晰度,但这并没有产生太大的影响。pyobjc
包探索了像 Quartz 这样的 macOS API,希望它能帮助我直接管理高 DPI 渲染,但图像看起来仍然不正确。尽管进行了所有这些尝试,我仍无法以 Mac M2 Retina 显示屏的质量显示图像。感觉 Pillow 无法正确处理高 DPI 设置。有没有办法绕过这个限制,或者有没有更好的方法在 Retina 显示屏上使用 Python 显示高 DPI 图像?
对于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()