platform-assistant/scripts/process_image.py
Sisyphus afac18e843 feat(F002): W26 + W29.x + W30 + W31 平台助手全部改动首次 commit
- W26: NestJS API + Next.js Web + Prisma + SQLite scaffold
- W26: Lead 模型 + Crawler + 询价发布助手 (线索池 + 一键发布)
- W29.x: MCP 集成 (Integration 模型 + connectMcp + publish_rfq 5 工具端到端跑通)
- W30: 3 字段补充 (notes / quotationType / quotationDemand)
- W31 P1: 自动填 enterpriseId / userId (Prisma companyUserId 字段 + get_app_identity tool + lead.service.ts publishArgs)
- W31: detail 详情页 + admin UI 集成管理 (OpenAppListView + AuditLogListView)

关联: ~/Documents/02-平台开发/产品开发/F002-W31-交接文档.md
GIT-AUDIT-REPORT-2026-06-24.md
2026-06-24 11:07:11 +08:00

242 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
图片处理脚本
裁剪、缩放、合成背景
用法:
python3 process_image.py input.jpg output.jpg --ratio 1:1 --style white
python3 process_image.py input.jpg output.jpg --ratio 4:3 --style minimal
"""
import argparse
import json
import os
import sys
from PIL import Image, ImageFilter, ImageDraw
# 目标尺寸(长边)
TARGET_SIZE = 800
# 背景色
BG_COLORS = {
"white": (255, 255, 255),
"light_gray": (245, 245, 245),
"transparent": None,
}
# 比例映射
RATIO_MAP = {
"1:1": (1, 1),
"4:3": (4, 3),
"3:4": (3, 4),
"16:9": (16, 9),
}
# 水印检测阈值:像素亮度 > 此值且 < 255 的区域可能是水印
WATERMARK_BRIGHT_MIN = 200
WATERMARK_BRIGHT_MAX = 254
# 边缘裁剪比例(去除角标水印)
EDGE_CROP_RATIO = 0.03
def crop_edges(img, ratio=EDGE_CROP_RATIO):
"""裁剪边缘,去除角落水印/标识"""
w, h = img.size
cx = int(w * ratio)
cy = int(h * ratio)
return img.crop((cx, cy, w - cx, h - cy))
def remove_semi_transparent_watermark(img):
"""去除半透明文字水印(常见于素材网站)"""
if img.mode == "RGB":
img = img.convert("RGBA")
pixels = img.load()
w, h = img.size
# 扫描检测亮度异常高的像素(接近白色但不是纯白 → 可能是半透明白色水印)
for y in range(h):
for x in range(w):
r, g, b, a = pixels[x, y]
# 计算亮度
brightness = (r + g + b) / 3
# 如果亮度很高但不是纯白,且 RGB 三通道接近(灰白色),可能是水印
if WATERMARK_BRIGHT_MIN <= brightness <= WATERMARK_BRIGHT_MAX:
if abs(r - g) < 15 and abs(g - b) < 15 and abs(r - b) < 15:
# 取周围像素的平均值来替换
pixels[x, y] = _get_neighbor_avg(pixels, x, y, w, h)
return img.convert("RGB")
def _get_neighbor_avg(pixels, x, y, w, h, radius=3):
"""获取周围非水印像素的平均颜色"""
total_r, total_g, total_b, count = 0, 0, 0, 0
for dy in range(-radius, radius + 1):
for dx in range(-radius, radius + 1):
nx, ny = x + dx, y + dy
if 0 <= nx < w and 0 <= ny < h and (dx != 0 or dy != 0):
r, g, b, a = pixels[nx, ny]
brightness = (r + g + b) / 3
# 只统计不是水印的像素
if brightness < WATERMARK_BRIGHT_MIN or brightness > WATERMARK_BRIGHT_MAX:
total_r += r
total_g += g
total_b += b
count += 1
if count > 0:
return (total_r // count, total_g // count, total_b // count, 255)
# 如果周围都是水印,用白色填充
return (255, 255, 255, 255)
def remove_watermark(img):
"""综合水印去除:边缘裁剪 + 半透明水印清理"""
# 1. 裁剪边缘(去除角落 logo/水印)
img = crop_edges(img)
# 2. 去除半透明文字水印
img = remove_semi_transparent_watermark(img)
return img
def crop_to_ratio(img, ratio_str):
"""按比例居中裁剪"""
w, h = img.size
rw, rh = RATIO_MAP.get(ratio_str, (1, 1))
target_ratio = rw / rh
current_ratio = w / h
if current_ratio > target_ratio:
# 图片太宽,裁剪左右
new_w = int(h * target_ratio)
left = (w - new_w) // 2
img = img.crop((left, 0, left + new_w, h))
elif current_ratio < target_ratio:
# 图片太高,裁剪上下
new_h = int(w / target_ratio)
top = (h - new_h) // 2
img = img.crop((0, top, w, top + new_h))
return img
def resize_to_target(img, target_size=TARGET_SIZE):
"""缩放到目标尺寸(长边为 target_size)"""
w, h = img.size
if w >= h:
ratio = target_size / w
else:
ratio = target_size / h
new_w = int(w * ratio)
new_h = int(h * ratio)
return img.resize((new_w, new_h), Image.LANCZOS)
def add_white_bg(img):
"""添加白色背景(处理透明通道)"""
if img.mode in ("RGBA", "LA", "PA"):
bg = Image.new("RGB", img.size, (255, 255, 255))
if img.mode == "RGBA":
bg.paste(img, mask=img.split()[3])
else:
bg.paste(img)
return bg
elif img.mode != "RGB":
return img.convert("RGB")
return img
def add_colored_bg(img, color):
"""添加纯色背景"""
bg = Image.new("RGB", img.size, color)
if img.mode in ("RGBA", "LA", "PA"):
if img.mode == "RGBA":
bg.paste(img, mask=img.split()[3])
else:
bg.paste(img)
else:
bg.paste(img)
return bg
def sharpen(img):
"""轻微锐化"""
return img.filter(ImageFilter.SHARPEN)
def process_image(input_path, output_path, ratio="1:1", style="white"):
"""完整处理流程"""
try:
img = Image.open(input_path)
# 1. 转为 RGB(如果是 CMYK 等)
if img.mode not in ("RGB", "RGBA", "LA", "PA"):
img = img.convert("RGB")
# 2. 去水印
img = remove_watermark(img)
# 3. 按比例裁剪
img = crop_to_ratio(img, ratio)
# 4. 缩放
img = resize_to_target(img, TARGET_SIZE)
# 5. 合成背景
if style == "white":
img = add_white_bg(img)
elif style == "minimal":
img = add_white_bg(img) # minimal 也用白底
elif style == "scene":
# 场景图保留原背景,只做裁剪缩放
if img.mode != "RGB":
img = add_white_bg(img)
elif style == "realistic":
if img.mode != "RGB":
img = add_white_bg(img)
else:
img = add_white_bg(img)
# 6. 锐化
img = sharpen(img)
# 7. 保存
os.makedirs(os.path.dirname(output_path), exist_ok=True)
img.save(output_path, "JPEG", quality=90)
return output_path
except Exception as e:
print(json.dumps({"error": str(e)}), file=sys.stderr)
return None
def main():
parser = argparse.ArgumentParser(description="图片处理")
parser.add_argument("input", help="输入图片路径")
parser.add_argument("output", help="输出图片路径")
parser.add_argument("--ratio", default="1:1", choices=RATIO_MAP.keys(), help="目标比例")
parser.add_argument("--style", default="white", choices=BG_COLORS.keys(), help="背景风格")
args = parser.parse_args()
result = process_image(args.input, args.output, args.ratio, args.style)
output = {
"input": args.input,
"output": result,
"ratio": args.ratio,
"style": args.style,
"success": result is not None,
}
print(json.dumps(output, ensure_ascii=False))
if __name__ == "__main__":
main()