#!/usr/bin/env python # coding: shift_jis import struct import array from PIL import Image import os, sys, re, glob #---------------------------------------------------------- def putInt16(f, v): f.write(struct.pack('>h', v)) def putUInt16(f, v): f.write(struct.pack('>H', v)) def putInt32(f, v): f.write(struct.pack('>i', v)) def putUInt32(f, v): f.write(struct.pack('>I', v)) def putByte(f, v): f.write(struct.pack('>B', v)) def putBytes(f, bs): array.array('B',bs).tofile(f) def putStr(f, s): f.write(s) def putPascalStr(f, s): l = len(s) f.write(struct.pack('B', l)) f.write(s) l = (l+1) % 4 putBytes(f, [0]*((4-l)%4)) def putPSD_header(f, width, height): putStr(f,'8BPS') putBytes(f, [0,1, 0,0,0,0,0,0]) putInt16(f, 3) # チャネル数 putInt32(f, height) # 画像の高さ putInt32(f, width) # 画像の幅 putInt16(f, 8) # 色深度(1,8,16) putInt16(f, 3) # カラーモード: RGBカラー def putPSD_dummy_block(f): putUInt32(f,0) def putPSD_layer_info(f, layer, layer_no): # layer は RGBAモードのImage w,h = layer.size putUInt32(f, 0) putUInt32(f, 0) putUInt32(f, h) putUInt32(f, w) putUInt16(f, 4) # 各チャンネルは無圧縮で格納するからサイズ=面積 + 圧縮フラグ(2byte) cs = h * w + 2 for i in range(4): putInt16(f,i-1) putUInt32(f,cs) putStr(f, '8BIMnorm') # ブレンドモードは標準 putBytes(f, [255,0,0,0]) # Opacity, Clipping, Flag, dummy block_top = f.tell() putUInt32(f,0) # 一旦ダミーのブロックサイズを記入 putPSD_dummy_block(f) # レイヤーマスクは作らない putPSD_dummy_block(f) # layer blending rangeも作らない putPascalStr(f, "layer%d" % (layer_no+1)) x = f.tell() - block_top - 4 f.seek(block_top) putUInt32(f, x) # 正式なブロックサイズを記入 f.seek(0,2) def putPSD_layer_channels(f, layer): r, g, b, a = layer.split() for ch in [a, r, g, b]: putInt16(f,0) # 圧縮フラグ putBytes(f,[x for x in ch.getdata()]) def putPSD_layer_block(f, layers): block_top = f.tell() putUInt32(f,0) # 一旦ダミーのブロックサイズを記入 putInt16(f, len(layers)) # レイヤー数 for i in range(len(layers)): putPSD_layer_info(f,layers[i],i) # チャンネルデータの書き出し for layer in layers: putPSD_layer_channels(f, layer) x = f.tell() - block_top + 4 if x % 2 == 1: # 偶数サイズになるようにパディング putByte(f,0) x = x + 1 f.seek(block_top) putUInt32(f, x) # 正式なブロックサイズを記入 f.seek(0,2) def putPSD_misc_block(f, layers): # 引数layersはputPSD_layer_blockに丸投げ block_top = f.tell() putUInt32(f,0) # 一旦ダミーのブロックサイズを記入 putPSD_layer_block(f,layers) x = f.tell() - block_top + 4 f.seek(block_top) putUInt32(f, x) # 正式なブロックサイズを記入 f.seek(0,2) #---------------------------------------------------------- def getImage(fn): return Image.open(fn).convert("RGBA") # モードはRGBA def bind_psd(file_list, out_name): layers = [ getImage(x) for x in file_list ] width = 0 height= 0 for im in layers: w,h = im.size if width < w: width = w if height < h: height = h # ファイル名本体の最後が +x(\d+)y(\d+) にマッチするなら # それぞれの数字をオフセット値と見なしてレイヤーを平行移動する pos = re.compile(r'^.*\+x(\d+)y(\d+)\.') for i in range(len(file_list)): o = pos.match(file_list[i]) if o != None: im = Image.new("RGBA", (width,height)) (x1,y1,x2,y2) = layers[i].getbbox() dx, dy = int(o.group(1)), int(o.group(2)) im.paste(layers[i], (x1+dx,y1+dy,x2+dx,y2+dy)) layers[i] = im f = open(out_name,'wb') putPSD_header(f, width, height) putPSD_dummy_block(f) # カラーモードブロック putPSD_dummy_block(f) # イメージリソースブロック putPSD_misc_block(f, layers) # レイヤーブロック等 # putPSD_image_block # とりあえずダミーで埋める putInt16(f,0) # 圧縮フラグ putBytes(f, [0]*width*height*3) f.close() #---------------------------------------------------------- def usage(): print "Usage: python psd_bind.py src1 src2 .. out.psd" print "Sources are .jpg, .png or .bmp files." def main(): ext = re.compile(r'^.*\.(jpg|png|bmp)$', re.IGNORECASE) psd = re.compile(r'^.*\.psd$', re.IGNORECASE) def chk_src(fn): if ext.match(x) == None: return False return os.path.exists(x) arg = [] for x in sys.argv[1:]: tmp = glob.glob(x) if len(tmp) == 0: arg.append(x) else: for f in glob.glob(x): arg.append(f) srcs = [ x for x in arg if chk_src(x) ] tmp = [ x for x in arg if psd.match(x) != None ] if len(srcs) == 0 or len(tmp) == 0: usage() else: bind_psd(srcs, tmp[0]) main()