GWの宿題的な何か

pypyのベンチマーク的な何か with Pillow(PIL)

# -*- coding:utf-8 -*-

import os
import re
import threading

from PIL import Image
from bs4 import BeautifulSoup

def transHue(img, huetype):
	img1 = img.copy()
	for hue in huetype.find_all(u"map"):
		c_from = [int(n.replace(u"*", u"-1")) for n in hue.get(u"from").split(u",")]
		c_to = [int(n.replace(u"*", u"-1")) for n in hue.get(u"to").split(u",")] + [255] # RGB + A
		try:
			c_idx = c_from.index(-1)
		except ValueError:
			c_idx = -1
		img2 = Image.new(u"RGBA", img1.size, (255, 0, 255, 0))
		for i in xrange(img1.size[0]):
			for j in xrange(img1.size[1]):
				img1_pixel = img1.getpixel((i,j))
				if ((c_from[0] == -1 and img1_pixel[0] > 0) or c_from[0] == img1_pixel[0]) \
					and ((c_from[1] == -1 and img1_pixel[1] > 0) or c_from[1] == img1_pixel[1]) \
					and ((c_from[2] == -1  and img1_pixel[2] > 0) or c_from[2] == img1_pixel[2]):
					if c_idx == -1:
	 					img2.putpixel((i, j), c_to)
					else:
						img2.putpixel((i, j), map(lambda n: ((n * img1_pixel[c_idx]) >> 8) + 1,  c_to))
				else:
					img2.putpixel((i, j), img1_pixel)
		img1.close()
		img1 = img2
	return img2

def decodeXml(filepath):
	re_ent = re.compile(u"<!ENTITY (.*?) '(.*?)'>", re.S)
	xml_raw = open(filepath).read().decode(u"cp932")
	xml_re = re_ent.findall(xml_raw)
	for group in xml_re or []:
		xml_raw = xml_raw.replace(u"&" + group[0] + u";", group[1])
	return xml_raw.lower()

class scrap:
	def __init__(self):
		self.pic_dic = {}
		self.img_list = []
		self.soup = None
		self.lock = threading.RLock()
		self.semaphore = threading.BoundedSemaphore(32)

	def load(self, xml_raw):
		self.soup = BeautifulSoup(xml_raw)
		for picture in self.soup.find_all(u"picture"):
			pic_path = picture.get(u"src")
			if pic_path:
				contribution = picture.find_parent(u"contribution")
				if contribution:
					self.pic_dic[contribution.get('id')] = os.path.join(path, pic_path.replace(u"/", u"\\"))

	def basename(self, sprite, path):
		picture = sprite.find(u"picture")
		file = picture.get(u"src")
		file = os.path.join(path, file) if file else self.pic_dic[picture.get(u"ref")]
		return file

	def crop(self, sprite, size, filepath, opposite):
		org_x, org_y = [int(n) for n in sprite.get(u"origin").split(u",")]
		size_x, size_y = [int(n) for n in size.text.split(u",")]
		if opposite: size_x, size_y = size_y, size_x
		offset = int(sprite.get(u"offset"))
		img = Image.open(filepath).convert(u"RGB")
		base = Image.new(u"RGBA", img.size, (255, 0, 255, 0))
		base.paste(img, (0, 0))
		img.close()
		return base.crop((org_x, org_y, org_x + (size_x + size_y) * 16, org_y + offset + (1 + size_y) * 8))

	def color(self, contribution, sprite, img, file):
		huetype = sprite.find(u"spritetype")
		if huetype:
			img_hue = transHue(img, huetype)
			img.close()
			self.lock.acquire()
			print "|>",
			self.img_list.append((img_hue, file))
			self.lock.release()
		else:
			huetypes = contribution.find_all(u"spritetype")
			if huetypes:
				for huetype in huetypes:
					img_orig = img.copy()
					img_hue = transHue(img_orig, huetype)
					img_orig.close()
					self.lock.acquire()
					print "/>",
					self.img_list.append((img_hue, file))
					self.lock.release()
				img.close()
			else:
				self.lock.acquire()
				print "->",
				self.img_list.append((img, file))
				self.lock.release()

	def parse(self, path):
		threads = []
		event = threading.Event()
		for contribution in self.soup.find_all(u"contribution"):
			thread = threading.Thread(target=self.encode, args=(path, contribution))
			thread.start()
			threads.append(thread)
		for thread in threads:
			thread.join()

	def alpha(self, img1):
		img2 = Image.new(u"RGBA", img1.size, (255, 0, 255, 0))
		for i in xrange(img1.size[0]):
			for j in xrange(img1.size[1]):
				img1_pixel = img1.getpixel((i, j))
				if 255 == img1_pixel[0] and 0 == img1_pixel[1] and 255 == img1_pixel[2]:
					img2.putpixel((i, j), (255, 0, 255, 0))
				else:
					img2.putpixel((i, j), img1_pixel + (255,))
		img1.close()
		return img2

	def tower(self, stack, size, path, opposite, floor_max):
		imgs = [self.alpha(self.crop(sprite, size, self.basename(sprite, path), opposite)) for sprite in stack]
		# MAX(MIDDLE_HEIGHT + BOTTOM_HEIGT + TOP_HEIGHT)
		height_max = max([imgs[1].size[1] + 16 * floor for floor in xrange(1, floor_max + 1)] \
							+ [imgs[2].size[1], imgs[0].size[1] + 16 * (floor_max + 1)])
		background = Image.new(u"RGBA", (imgs[2].size[0], height_max), (255, 0, 255, 0))
		# BOTTOM
		background.paste(imgs[2], (0, background.size[1] - imgs[2].size[1]), mask=imgs[2].split()[3])
		# MIDDLE
		for floor in xrange(1, floor_max + 1):
			background.paste(imgs[1], (0, background.size[1] - imgs[1].size[1] - 16 * floor), mask=imgs[1].split()[3])
		# TOP
		background.paste(imgs[0], (0, background.size[1] - imgs[0].size[1] - 16 * (floor_max + 1)), mask=imgs[0].split()[3])
		return background, self.basename(stack[-1], path)

	def encode(self, path, contribution):
		self.semaphore.acquire()
		try:
			size = contribution.find(u"size")
			if size:
				sprite_list = contribution.find_all(u"sprite")
				if sprite_list:
					for sprite in sprite_list:
						opposite = sprite.get(u"opposite") != None
						filepath = self.basename(sprite, path)
						self.color(contribution, sprite, self.crop(sprite, size, filepath, opposite), filepath)
				else:
					height_min = contribution.find(u"minheight")
					height_min = int(height_min.text) if height_min else 2
					height_max =  contribution.find(u"maxheight")
					height_max = int(height_max.text) if height_max else 5
					pictures_list = contribution.find_all(u"pictures")
					if pictures_list:
						for pictures in pictures_list:
							opposite = pictures.get(u"opposite") != None
							stack = [pictures.find(key) for key in [u"top", u"middle", u"bottom"]]
							if not None in stack:
								img, file = self.tower(stack, size, path, opposite, height_max - height_min)
								self.color(contribution, pictures, img, file)
		except Exception as e:
			self.lock.acquire()
			print "Error:", str(type(e)), e.message, contribution
			self.lock.release()
		self.semaphore.release()

def makeTile(img_list, prefix):
	class img_set:
		def __init__(self, img1, size1):
			self.img = img1
			self.x, self.y = size1
	img_size = {}
	for img, file in img_list:
		if file not in img_size: img_size[file] = (0, 0)
		if img_size[file][1] < img.size[1]: img_size[file] = img.size

	img_type = {}
	for img, file in img_list:
		key = "_".join([str(s) for s in img_size[file]])
		if key not in img_type: img_type[key] = []
		img_type[key].append(img_set(img, img_size[file]))

	for filename, val1 in img_type.items():
		width = val1[0].x
		base = Image.new(u"RGB", (width * len(val1), val1[0].y), (255, 0, 255))
		for idx, val2 in enumerate(val1):
			base.paste(val2.img, (width * idx, val1[0].y - val2.img.size[1]))
		if width > 0: base.save(prefix + filename + u".bmp")
		base.close()

def dirs(directory):
	for root, dirs, files in os.walk(directory):
		yield root, None
		for file in files:
			yield root, file

if __name__ == '__main__':
	for path, file in dirs(u"."):
		if file == "plugin.xml":
			preload = scrap()
			preload.load(decodeXml(os.path.join(path, file)))
	for path, file in dirs(u"."):
		if file == "plugin.xml":
			imgs = scrap()
			imgs.pic_dic = preload.pic_dic
			imgs.load(decodeXml(os.path.join(path, file)))
			imgs.parse(path)
			makeTile(imgs.img_list, path.lower().replace(u"\\", u"_").replace(u".", u"_").replace(u"__", u"") + u"_")

内容的には、FreeTrainのplugin/assetをマップチップ化するスクリプトです。