|
8 | 8 |
|
9 | 9 | import base64 |
10 | 10 | import inspect |
| 11 | +import io |
11 | 12 | import mimetypes |
12 | 13 | import os |
| 14 | +import re |
13 | 15 | import sys |
14 | 16 | import tkinter as tk |
15 | 17 | from pathlib import Path |
16 | 18 | from urllib.parse import urlparse |
17 | 19 |
|
| 20 | +from PIL import Image |
| 21 | + |
18 | 22 |
|
19 | 23 | # 文字をエスケープする |
20 | 24 | def escape_js_string(s: str): |
@@ -90,3 +94,60 @@ def get_screen_size(window_handle=None) -> tuple[int, int]: |
90 | 94 | except Exception: |
91 | 95 | # 取得できない場合はデフォルトサイズを返す |
92 | 96 | return 800, 600 |
| 97 | + |
| 98 | +# data URL 形式の文字列を解析して (media_type, subtype, base64_data) を返す |
| 99 | +# 想定外の形式の場合は ('image/png', 'png', data_url) を返す |
| 100 | +def parse_data_url(data_url: str) -> tuple[str, str, str]: |
| 101 | + if not isinstance(data_url, str): |
| 102 | + return "image/png", "png", "" |
| 103 | + |
| 104 | + match = re.match(r"^data:(.*?);base64,(.*)$", data_url) |
| 105 | + if match: |
| 106 | + media_type = match.group(1).strip() or "image/png" |
| 107 | + b64_data = match.group(2).strip() |
| 108 | + |
| 109 | + # 画像タイプ部分だけ抽出する |
| 110 | + subtype_match = re.match(r"^image/(\w+)$", media_type) |
| 111 | + subtype = subtype_match.group(1) if subtype_match else "png" |
| 112 | + |
| 113 | + return media_type, subtype, b64_data |
| 114 | + else: |
| 115 | + # 想定外の場合はPNG扱い |
| 116 | + return "image/png", "png", data_url.strip() |
| 117 | + |
| 118 | +# Base64エンコードされた画像データを指定サイズ以下に圧縮する |
| 119 | +def resize_base64_image(b64_data: str, max_size_mb: float, output_format="JPEG", quality_step=5) -> str: |
| 120 | + img_bytes = base64.b64decode(b64_data) |
| 121 | + image = Image.open(io.BytesIO(img_bytes)) |
| 122 | + size_mb = len(img_bytes) / (1024 * 1024) |
| 123 | + if size_mb <= max_size_mb: |
| 124 | + return b64_data |
| 125 | + |
| 126 | + buffer = io.BytesIO() |
| 127 | + if output_format.upper() == "JPEG": |
| 128 | + # JPEGは画質を下げながら圧縮 |
| 129 | + quality = 95 |
| 130 | + while True: |
| 131 | + buffer = io.BytesIO() |
| 132 | + image.save(buffer, format="JPEG", quality=quality) |
| 133 | + new_data = buffer.getvalue() |
| 134 | + new_size = len(new_data) / (1024 * 1024) |
| 135 | + if new_size <= max_size_mb or quality <= 10: |
| 136 | + break |
| 137 | + quality -= quality_step |
| 138 | + |
| 139 | + else: |
| 140 | + # PNGなどはリサイズ主体で対応 |
| 141 | + width, height = image.size |
| 142 | + while True: |
| 143 | + buffer = io.BytesIO() |
| 144 | + image.save(buffer, format=output_format, optimize=True, compress_level=9) |
| 145 | + new_data = buffer.getvalue() |
| 146 | + new_size = len(new_data) / (1024 * 1024) |
| 147 | + if new_size <= max_size_mb or (width < 100 or height < 100): |
| 148 | + break |
| 149 | + # サイズがまだ大きいなら10%ずつ縮小 |
| 150 | + width, height = int(width * 0.9), int(height * 0.9) |
| 151 | + image = image.resize((width, height), Image.LANCZOS) |
| 152 | + |
| 153 | + return base64.b64encode(buffer.getvalue()).decode("utf-8") |
0 commit comments