# -*- coding: utf-8 -*-
"""把课件"广金化"：替换同济校园背景图 + 蓝色系设计元素与文字改为广金红/金。

输入：课件PPT-加校徽/Ch1-引论-STMT-2022.pptx
输出：课件PPT-GDUF版/Ch1-引论-GDUF版.pptx（原文件保留不动）

处理内容：
  1. image1.jpg —— 版式"封面2"（结束页）整页背景的校园照片 → 用广金官网横幅图
     （www.gduf.edu.cn/images/47.jpg）合成为同尺寸背景：横幅置于上方，下方用底部像素
     模糊延展补齐，并加一层柔和暗角保证叠字可读；
  2. image3.png / image12.png / image10.png —— 蓝色线条带、蓝色科技网、浅蓝网格底纹
     → 色相整体转到广金红/金；
  3. theme1.xml 的 accent1/2/3/5、hlink → 广金红 #9E1F24、金 #C8A15A、深红 #7A1A1E；
  4. 幻灯片/版式/母版里的蓝色文字色（#435CC8、#3366FF、#4472C4、#5B9BD5）→ 广金红/深红。
"""
import colorsys
import os
import re
import shutil
import sys
import zipfile

from PIL import Image, ImageFilter

BASE = os.path.dirname(os.path.abspath(__file__))
PROJ = os.path.dirname(BASE)
SRC = os.path.join(PROJ, '课件PPT-加校徽', 'Ch1-引论-STMT-2022.pptx')
OUTDIR = os.path.join(PROJ, '课件PPT-GDUF版')
OUT = os.path.join(OUTDIR, 'Ch1-引论-GDUF版.pptx')
BANNER = os.path.join(BASE, 'gduf-47.jpg')

GDUF_RED = '9E1F24'
GDUF_GOLD = 'C8A15A'
GDUF_DARK = '7A1A1E'
# 蓝色区间（度）→ 广金红/金区间；饱和度与明度保持不变，因此对比度关系不丢
BLUE_LO, BLUE_HI = 190.0, 265.0
RED_LO, RED_SPAN = 345.0, 30.0


def rotate_blue_hex(hexval):
    """把蓝色系颜色旋到红/金区间，其他颜色原样返回。"""
    try:
        r, g, b = (int(hexval[i:i + 2], 16) / 255.0 for i in (0, 2, 4))
    except Exception:
        return hexval
    h, s, v = colorsys.rgb_to_hsv(r, g, b)
    deg = h * 360.0
    if not (BLUE_LO <= deg <= BLUE_HI) or s < 0.12:
        return hexval
    nd = (RED_LO + (deg - BLUE_LO) * RED_SPAN / (BLUE_HI - BLUE_LO)) % 360.0
    nr, ng, nb = colorsys.hsv_to_rgb(nd / 360.0, s, v)
    return '%02X%02X%02X' % (round(nr * 255), round(ng * 255), round(nb * 255))


def rotate_blue_xml(txt):
    cnt = [0]

    def repl(m):
        old = m.group(1)
        new = rotate_blue_hex(old)
        if new.upper() != old.upper():
            cnt[0] += 1
        return 'val="%s"' % new

    out = re.sub(r'val="([0-9A-Fa-f]{6})"', repl, txt)
    return out, cnt[0]


def build_background(size=(1780, 1000)):
    """用广金横幅合成整页背景：上方真实照片 + 下方像素延展，外加柔和暗角。"""
    W, H = size
    banner = Image.open(BANNER).convert('RGB')
    bh = int(W * banner.height / banner.width)
    banner = banner.resize((W, bh), Image.LANCZOS)
    canvas = Image.new('RGB', (W, H), (255, 255, 255))
    canvas.paste(banner, (0, 0))
    # 底部延展：取横幅最后 90 行，纵向拉伸 + 高斯模糊
    if bh < H:
        tail = banner.crop((0, max(0, bh - 90), W, bh)).resize((W, H - bh), Image.LANCZOS)
        tail = tail.filter(ImageFilter.GaussianBlur(18))
        canvas.paste(tail, (0, bh))
    # 顶部轻微压暗（叠字对比），左上更明显
    grad = Image.new('L', (W, H), 0)
    gp = grad.load()
    for y in range(H):
        for x in range(0, W, 4):
            v = int(max(0, 108 - 108 * (y / float(H)) ** 0.7) * (1.0 - 0.35 * x / float(W)))
            for k in range(4):
                if x + k < W:
                    gp[x + k, y] = v
    dark = Image.new('RGB', (W, H), (0, 0, 0))
    canvas = Image.composite(dark, canvas, grad)
    return canvas


def warm_shift(im, sat=1.0, val=1.0):
    """蓝色系设计图 → 广金红/金：只把蓝色相旋到红区，其他颜色不动。"""
    im = im.convert('RGB')
    px = im.load()
    W, H = im.size
    for y in range(H):
        for x in range(W):
            r, g, b = px[x, y]
            h, s, v = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
            deg = h * 360.0
            if BLUE_LO <= deg <= BLUE_HI and s >= 0.12:
                nd = (RED_LO + (deg - BLUE_LO) * RED_SPAN / (BLUE_HI - BLUE_LO)) % 360.0
                nr, ng, nb = colorsys.hsv_to_rgb(nd / 360.0, min(1.0, s * sat), min(1.0, v * val))
                px[x, y] = (round(nr * 255), round(ng * 255), round(nb * 255))
            elif s < 0.12 and sat != 1.0:
                px[x, y] = (r, g, b)
    return im


def main():
    if not os.path.exists(SRC):
        raise SystemExit('缺少源文件：%s' % SRC)
    os.makedirs(OUTDIR, exist_ok=True)
    bg = build_background()
    tmp_bg = os.path.join(BASE, '_bg-gduf.jpg')
    bg.save(tmp_bg, quality=92)
    print('背景合成完成 %s' % (bg.size,))

    media_ops = {'image1.jpg': open(tmp_bg, 'rb').read()}
    for name, opt in (('image3.png', dict(sat=0.95, val=0.98)),      # 线条带
                      ('image12.png', dict(sat=0.95, val=0.95)),     # 科技网
                      ('image10.png', dict(sat=0.55, val=1.0))):     # 浅色网格底纹（降饱和，避免抢眼）
        zin = zipfile.ZipFile(SRC)
        raw = zin.read('ppt/media/' + name)
        zin.close()
        tmp = os.path.join(BASE, '_tmp_' + name)
        open(tmp, 'wb').write(raw)
        out = warm_shift(Image.open(tmp).convert('RGB'), **opt)
        tmp2 = os.path.join(BASE, '_out_' + name)
        out.save(tmp2)
        media_ops[name] = open(tmp2, 'rb').read()
        print('配色处理 %s -> 暖色' % name)

    zin = zipfile.ZipFile(SRC)
    zout = zipfile.ZipFile(OUT, 'w', zipfile.ZIP_DEFLATED)
    n_theme = n_text = 0
    for item in zin.infolist():
        data = zin.read(item.filename)
        name = os.path.basename(item.filename)
        if item.filename.startswith('ppt/media/') and name in media_ops:
            data = media_ops[name]
        elif item.filename.startswith('ppt/theme/') and item.filename.endswith('.xml'):
            txt, k = rotate_blue_xml(data.decode('utf-8'))
            data = txt.encode('utf-8'); n_theme += k
        elif re.match(r'ppt/(slides/slide\d+|slideLayouts/slideLayout\d+|slideMasters/slideMaster\d+)\.xml$',
                      item.filename):
            txt, k = rotate_blue_xml(data.decode('utf-8'))
            data = txt.encode('utf-8'); n_text += k
        zout.writestr(item, data)
    zout.close(); zin.close()
    for f in ('_bg-gduf.jpg', '_tmp_image3.png', '_out_image3.png', '_tmp_image12.png', '_out_image12.png',
              '_tmp_image10.png', '_out_image10.png'):
        p = os.path.join(BASE, f)
        if os.path.exists(p):
            os.remove(p)
    print('已生成 %s' % OUT)
    print('主题配色旋转 %d 处 ｜ 页面/版式/母版蓝色旋转 %d 处' % (n_theme, n_text))
    return 0


if __name__ == '__main__':
    sys.exit(main())
