gen_trace.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import zlib, struct, os, re
  2. PNG = r"E:/cfc/docs/品牌资产/assets/cf_logo_primary.png"
  3. with open(PNG,"rb") as f: data=f.read()
  4. pos=8; idat=b""
  5. while pos<len(data):
  6. ln=struct.unpack(">I",data[pos:pos+4])[0]; ctype=data[pos+4:pos+8]; chunk=data[pos+8:pos+8+ln]
  7. if ctype==b"IHDR": width,height,bit_depth,color_type=struct.unpack(">IIBB",chunk[:10])
  8. elif ctype==b"IDAT": idat+=chunk
  9. elif ctype==b"IEND": break
  10. pos+=12+ln
  11. channels=4; raw=zlib.decompress(idat); stride=width*channels
  12. out=bytearray(); prev=bytearray(stride); i=0
  13. def paeth(a,b,c):
  14. p=a+b-c; pa,pb,pc=abs(p-a),abs(p-b),abs(p-c)
  15. if pa<=pb and pa<=pc: return a
  16. if pb<=pc: return b
  17. return c
  18. for y in range(height):
  19. ft=raw[i]; i+=1; line=bytearray(raw[i:i+stride]); i+=stride
  20. for x in range(stride):
  21. a=line[x-channels] if x>=channels else 0; b=prev[x]; c=prev[x-channels] if x>=channels else 0
  22. if ft==1: line[x]=(line[x]+a)&255
  23. elif ft==2: line[x]=(line[x]+b)&255
  24. elif ft==3: line[x]=(line[x]+((a+b)>>1))&255
  25. elif ft==4: line[x]=(line[x]+paeth(a,b,c))&255
  26. out+=line; prev=line
  27. def px(x,y):
  28. o=y*stride+x*channels; return (out[o],out[o+1],out[o+2],out[o+3])
  29. def is_logo(r,g,b,a):
  30. if a<128: return False
  31. if abs(r-220)<24 and abs(g-240)<24 and abs(b-249)<24: return False
  32. return True
  33. mask=[[is_logo(*px(x,y)) for x in range(width)] for y in range(height)]
  34. def run_rects(y0,y1,x0,x1,exclude=None):
  35. rects=[]
  36. for y in range(max(0,y0), min(height,y1)):
  37. if exclude and exclude[0]<=y<exclude[1]: continue
  38. x=max(0,x0)
  39. while x<min(width,x1):
  40. if mask[y][x]:
  41. x0r=x
  42. while x<min(width,x1) and mask[y][x]:
  43. x+=1
  44. rects.append((x0r,y,x-x0r))
  45. else:
  46. x+=1
  47. return rects
  48. def build_svg(name, y0,y1,x0,x1, with_bg=False, exclude=None, text=None):
  49. rects=run_rects(y0,y1,x0,x1,exclude)
  50. ys=[y for (_,y,_) in rects]
  51. if text: ys += [text["y"]-text["size"], text["y"]]
  52. loy,hiy=(min(ys),max(ys)) if ys else (0,height)
  53. grad=(f'<linearGradient id="cfBlue" x1="0" y1="{loy}" x2="0" y2="{hiy}" gradientUnits="userSpaceOnUse">\n'
  54. f' <stop offset="0" stop-color="#6FB6E3"/>\n'
  55. f' <stop offset="1" stop-color="#3A8FCC"/>\n </linearGradient>')
  56. body="".join(f'<rect x="{x}" y="{y}" width="{w}" height="1" fill="url(#cfBlue)"/>' for (x,y,w) in rects)
  57. bgrect='' if not with_bg else f'<rect x="0" y="0" width="{width}" height="{height}" rx="28" fill="#DCF0F9"/>'
  58. txt=''
  59. if text:
  60. txt=(f'<text x="{text["x"]}" y="{text["y"]}" text-anchor="middle" '
  61. f'font-family="\'Segoe UI\',\'Helvetica Neue\',Arial,\'Noto Sans SC\',sans-serif" '
  62. f'font-size="{text["size"]}" font-weight="600" letter-spacing="0.5" fill="url(#cfBlue)">{text["content"]}</text>')
  63. svg=(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" width="{width}" height="{height}">\n'
  64. f' <defs>{grad}</defs>\n {bgrect}\n {body}\n {txt}\n</svg>\n')
  65. return svg
  66. outdir=r"E:/cfc/docs/品牌资产/assets"
  67. # wordmark bbox in 144 canvas: x[42,103] y[112,120]; baseline ~121, size ~13, center x=72
  68. WM={"x":72,"y":121,"size":13,"content":"Care Family"}
  69. # primary: monogram pixel-trace (exclude wordmark rows) + smooth vector wordmark text
  70. prim=build_svg("primary",0,height,0,width,with_bg=False,exclude=(108,144),text=WM)
  71. icon=build_svg("icon",36,107,25,119)
  72. primbg=build_svg("primary_bg",0,height,0,width,with_bg=True,exclude=(108,144),text=WM)
  73. # standalone wordmark: smooth vector text on its own clean canvas
  74. wm_svg=(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 44" width="220" height="44">\n'
  75. f' <defs><linearGradient id="cfBlue" x1="0" y1="6" x2="0" y2="38" gradientUnits="userSpaceOnUse">\n'
  76. f' <stop offset="0" stop-color="#6FB6E3"/>\n <stop offset="1" stop-color="#3A8FCC"/>\n </linearGradient></defs>\n'
  77. f' <text x="110" y="32" text-anchor="middle" '
  78. f'font-family="\'Segoe UI\',\'Helvetica Neue\',Arial,\'Noto Sans SC\',sans-serif" '
  79. f'font-size="28" font-weight="600" letter-spacing="1" fill="url(#cfBlue)">Care Family</text>\n</svg>\n')
  80. files={"cf_logo_primary.svg":prim,"cf_logo_icon.svg":icon,"cf_logo_wordmark.svg":wm_svg,"cf_logo_primary_bg.svg":primbg}
  81. for nm,content in files.items():
  82. with open(os.path.join(outdir,nm),"w",encoding="utf-8") as f:
  83. f.write(content)
  84. print("wrote",nm,len(content),"bytes")
  85. # verify primary monogram region still pixel-exact vs original
  86. svg=open(os.path.join(outdir,"cf_logo_primary.svg"),encoding="utf-8").read()
  87. rects=[(int(x),int(y),int(w)) for (x,y,w) in re.findall(r'<rect x="(\d+)" y="(\d+)" width="(\d+)"',svg)]
  88. recon=[[False]*width for _ in range(height)]
  89. for (x,y,w) in rects:
  90. for xx in range(x,min(width,x+w)): recon[y][xx]=True
  91. diff=sum(1 for y in range(36,107) for x in range(25,119) if mask[y][x]!=recon[y][x])
  92. print("primary monogram-region diff vs original:",diff,"(0 = pixel-exact)")
  93. print("done")