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

Tkinter窗口/全屏切换时图片动态缩放及布局适配问题

问题解决:Tkinter全屏模式下封面图缩放与布局适配

核心问题分析

  1. 你之前用ImageTk.PhotoImage.zoom的方式错误,zoom仅支持整数倍缩放,且应该基于原始PIL Image而非已转换的PhotoImage对象操作。
  2. 布局行权重设置不合理,三行权重均为1导致文本区域挤占封面图空间,且固定像素间距在全屏模式下比例失调。
  3. 缩放时未保持图片宽高比,直接按窗口75%宽高计算会导致图片变形。

修改后的完整代码

import wave
import tkinter as tk
import asyncio
from shazamio import Shazam
import requests
from PIL import Image, ImageTk, ImageColor
import io
import numpy as np
import colorsys
import math
import os
import pyaudio  # 补上缺失的导入

# Constants
RECORD_SECS = 4
MAX_RETRIES = 5
CHUNK = 8192

original_width = 0
original_height = 0
current_image = None
original_image = None  # 新增:保存原始PIL Image对象,用于后续缩放

async def record_and_recognize(audio):
    print("Recording started...")
    form_1 = pyaudio.paInt16  
    chans = 1  
    samp_rate = 48000  
    dev_index = 2  

    script_dir = os.path.dirname(os.path.abspath(__file__))
    wav_output_filename = os.path.join(script_dir, 'shazam.wav')  

    stream = audio.open(format=form_1, rate=samp_rate, channels=chans, 
                      input_device_index=dev_index, input=True, 
                       frames_per_buffer=CHUNK)

    frames = []
    for _ in range(0, int((samp_rate / CHUNK) * RECORD_SECS)):
        data = stream.read(CHUNK)
        frames.append(data)

    stream.stop_stream()
    stream.close()

    wavefile = wave.open(wav_output_filename, 'wb')
    wavefile.setnchannels(chans)
    wavefile.setsampwidth(audio.get_sample_size(form_1))
    wavefile.setframerate(samp_rate)
    wavefile.writeframes(b''.join(frames))
    wavefile.close()

    print("Recording completed...")

    shazam = Shazam()
    retry_count = 0
    result = None

    while retry_count < MAX_RETRIES:
        try:
            print("Recognizing the song...")
            result = await shazam.recognize(wav_output_filename)
            print("Recognition completed.")
            break
        except (requests.RequestException, Exception) as e:
            print(f"Failed to recognize the song. Retrying... (Attempt {retry_count + 1}/{MAX_RETRIES})")
            retry_count += 1
            await asyncio.sleep(2)

    if retry_count == MAX_RETRIES:
        print("Max retry attempts reached. Could not recognize the song.")
        return

    if 'track' in result:
        track = result['track']
        track_title = track['title']
        artist_name = track['subtitle']

        if 'images' in track:
            cover_art_url = track['images']['coverarthq']
            response = requests.get(cover_art_url)
            image_data = response.content
            global original_image, original_width, original_height, current_image
            
            original_image = Image.open(io.BytesIO(image_data))
            original_width, original_height = original_image.size
            
            # 初始显示原始大小图片
            current_image = ImageTk.PhotoImage(original_image)
            dominant_color = find_dominant_color(original_image)
            lighter_color = lighten_color(dominant_color, 1.2)  # 调整亮度因子,避免过暗
            
            root.configure(bg=lighter_color)
            cover_art_label.config(image=current_image, bg=lighter_color)
            cover_art_label.image = current_image  

            text_color = get_text_color(lighter_color)
            title_label.config(text=track_title, fg=text_color, bg=lighter_color, font=("Arial", 16, "bold"))
            artist_label.config(text=artist_name, fg=text_color, bg=lighter_color, font=("Arial", 16, "italic"))
            adjust_layout()  # 识别完成后立即适配布局
        else:
            print("No cover art available for this track.")
    else:
        print("Could not recognize the song.")

def update_song_information(audio):
    print("Updating song information...")
    asyncio.run(record_and_recognize(audio))
    root.after(10000, update_song_information, audio)

def find_dominant_color(image):
    img_array = np.array(image)
    flat_array = img_array.reshape(-1, img_array.shape[-1])
    unique_colors, counts = np.unique(flat_array, axis=0, return_counts=True)
    dominant_index = np.argmax(counts)
    dominant_color = tuple(unique_colors[dominant_index])
    hex_color = '#{0:02x}{1:02x}{2:02x}'.format(*dominant_color)
    return hex_color

def lighten_color(color, factor=0.5):
    r, g, b = ImageColor.getcolor(color, "RGB")
    h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
    l = min(1.0, l * factor)
    r, g, b = colorsys.hls_to_rgb(h, l, s)
    return '#{0:02x}{1:02x}{2:02x}'.format(int(r * 255), int(g * 255), int(b * 255))

def get_text_color(bg_color):
    r, g, b = ImageColor.getcolor(bg_color, "RGB")
    brightness = math.sqrt(0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2)
    return "white" if brightness < 128 else "black"

def adjust_layout(event=None):
    if not original_image:
        return  # 无图片时跳过
    
    if root.attributes("-fullscreen"):
        window_width = root.winfo_width()
        window_height = root.winfo_height()
        
        # 计算缩放比例:取窗口75%宽高与原始图片宽高的最小比例,保持宽高比
        scale_width = (window_width * 0.75) / original_width
        scale_height = (window_height * 0.7) / original_height  # 留更多空间给文本
        scale_factor = min(scale_width, scale_height)
        
        # 计算新尺寸
        new_width = int(original_width * scale_factor)
        new_height = int(original_height * scale_factor)
        
        # 缩放图片
        resized_img = original_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
        resized_photo = ImageTk.PhotoImage(resized_img)
        
        cover_art_label.config(image=resized_photo)
        cover_art_label.image = resized_photo
        
        # 自适应间距:用窗口高度的百分比
        cover_pady = int(window_height * 0.05)
        title_pady = int(window_height * 0.03)
        artist_pady = int(window_height * 0.02)
        
        cover_art_label.grid_configure(pady=(cover_pady, 0))
        title_label.grid_configure(pady=(title_pady, 0))
        artist_label.grid_configure(pady=(artist_pady, cover_pady))
    else:
        # 窗口模式恢复原始图片
        cover_art_label.config(image=current_image)
        cover_art_label.image = current_image
        
        # 恢复固定间距
        cover_art_label.grid_configure(pady=(30, 0))
        title_label.grid_configure(pady=(10, 0))
        artist_label.grid_configure(pady=(10, 20))

root = tk.Tk()
root.title("Song Recognition")
root.attributes("-fullscreen", True)

# 设置窗口最小尺寸:原始图片尺寸+文本区域高度
root.minsize(400, 600)  # 可根据实际情况调整

def toggle_fullscreen(event=None):
    root.attributes("-fullscreen", not root.attributes("-fullscreen"))
    adjust_layout()

root.bind("<Escape>", toggle_fullscreen)

cover_art_label = tk.Label(root)
title_label = tk.Label(root, text="", font=("Arial", 16, "bold"))
artist_label = tk.Label(root, text="", font=("Arial", 16, "italic"))

# 调整布局权重:封面图行占主要空间,文本行占小权重
root.grid_rowconfigure(0, weight=8)
root.grid_rowconfigure(1, weight=1)
root.grid_rowconfigure(2, weight=1)
root.grid_columnconfigure(0, weight=1)

cover_art_label.grid(row=0, column=0, padx=10, pady=(30, 0), sticky="n")
title_label.grid(row=1, column=0, pady=(10, 0), sticky="n")
artist_label.grid(row=2, column=0, pady=(10, 20), sticky="n")

audio = pyaudio.PyAudio()
update_song_information(audio)

root.bind("<Configure>", adjust_layout)
root.mainloop()

关键修改说明

  1. 新增original_image变量:保存原始PIL Image对象,后续缩放直接基于该对象操作,避免PhotoImage缩放的限制。
  2. 正确的缩放逻辑:计算窗口可用空间与原始图片的比例,取最小比例保证宽高比不变,用Image.Resampling.LANCZOS算法缩放,保证图片清晰度。
  3. 布局权重优化:将封面图行的权重设为8,文本行各设为1,确保封面图占据大部分窗口空间,文本区域保持合适占比。
  4. 自适应间距:全屏模式下用窗口高度的百分比设置间距,保证不同分辨率下的布局比例一致;窗口模式恢复固定像素间距。
  5. 修复细节问题:补上缺失的pyaudio导入,调整亮度因子避免背景过暗,识别完成后立即调用布局适配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 07:48:13