Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import os
2import time
3import math
4import yaml
5import tarfile
6import zipfile
7from PIL import Image, ImageChops, ImageFilter, ImageEnhance, ImageFont, ImageDraw, ImageColor, ImageOps
10def inout_fname(param):
11 input_filename = param.get("input")
12 output_filename = param.get("output", input_filename)
13 if input_filename is None or output_filename is None:
14 raise Exception("please set input and output: %s" % (param))
15 return input_filename, output_filename
18image_crop_schema = yaml.safe_load("""
19allOf:
20 - "$ref": "#/definitions/common/inout"
21 - type: object
22 properties:
23 size:
24 oneOf:
25 - type: string
26 enum: [auto]
27 - type: array
28 items: {type: integer}
29 minItems: 4
30 maxItems: 4
31""")
34def Base_image_crop(self, param):
35 """
36 - name: crop local image (auto)
37 image_crop:
38 input: filename.png
39 size: auto
40 - name: crop local image (manual)
41 image_crop:
42 input: filename.png
43 size: [100, 100, 200, 200] # left, upper, right, lower
44 """
45 input_filename, filename = inout_fname(param)
46 if filename is None:
47 # generate filename
48 ts = time.time()
49 msec = math.modf(ts)[0] * 1000
50 filename = param.get("prefix", "")
51 filename += time.strftime("%Y%m%d_%H%M%S", time.localtime(ts))
52 filename += "_%03d.png" % (msec)
53 self.log.debug("filename generated %s", filename)
54 size = param.get("size", "auto")
55 if size == "auto":
56 img = Image.open(input_filename)
57 bg = Image.new(img.mode, img.size, img.getpixel((0, 0)))
58 diff = ImageChops.difference(img, bg)
59 diff = ImageChops.add(diff, diff, 2.0, -100)
60 box = diff.getbbox()
61 self.log.info("auto crop: %s", box)
62 crop = img.crop(box)
63 crop.save(filename)
64 elif isinstance(size, (tuple, list)):
65 img = Image.open(input_filename)
66 self.log.info("manual crop: %s", size)
67 crop = img.crop(size)
68 crop.save(filename)
69 else:
70 raise Exception("not implemented yet: crop %s %s" % (filename, size))
73image_optimize_schema = yaml.safe_load("""
74allOf:
75 - "$ref": "#/definitions/common/inout"
76 - type: object
77 properties:
78 command: {type: string}
79""")
82def Base_image_optimize(self, param):
83 """
84 - name: optimize local png using optipng
85 image_optimize:
86 input: filename.png
87 """
88 input_filename, filename = inout_fname(param)
89 command = param.get("command", "optipng")
90 self.log.info("optimize image: %s -> %s", input_filename, filename)
91 before = os.stat(input_filename)
92 if before.st_size == 0:
93 raise Exception("image size is zero: %s" % (input_filename))
94 if filename != input_filename:
95 cmd = [command, "-o9", "-out", filename, input_filename]
96 else:
97 cmd = [command, "-o9", filename]
98 try:
99 sout = self.runcmd(cmd)
100 self.log.debug("result: %s", sout)
101 after = os.stat(filename)
102 self.log.info("%s: before=%d, after=%d, reduce %d bytes (%.1f %%)", filename,
103 before.st_size, after.st_size, before.st_size - after.st_size,
104 100.0 * (before.st_size - after.st_size) / before.st_size)
105 except FileNotFoundError as e:
106 self.log.info("cannot exec %s: %s", cmd, e)
107 if filename != input_filename:
108 os.rename(input_filename, filename)
111image_resize_schema = yaml.safe_load("""
112allOf:
113 - "$ref": "#/definitions/common/inout"
114 - type: object
115 properties:
116 size:
117 type: array
118 items: {type: integer}
119 minItems: 2
120 maxItems: 2
121 percent:
122 oneOf:
123 - type: array
124 items: {type: number}
125 minItems: 2
126 maxItems: 2
127 - type: number
128 algorithm:
129 type: string
130 enum: [NEAREST, BOX, BILINEAR, HAMMING, BICUBIC, LANCZOS]
131""")
134def Base_image_resize(self, param):
135 """
136 - name: resize local image
137 image_resize:
138 input: filename.png
139 size: [100, 200] # width, height
140 algorithm: LANCZOS
141 """
142 input_filename, filename = inout_fname(param)
143 self.log.info("resize image: %s %s -> %s", input_filename, param, filename)
144 img = Image.open(input_filename)
145 size = param.get("size")
146 if size is None:
147 pct = param.get("percent")
148 if pct is None:
149 raise Exception("missing size: [width, height]")
150 if isinstance(pct, (tuple, list)):
151 pctX = pct[0]
152 pctY = pct[1]
153 else:
154 pctX = pctY = float(pct)
155 size = (int(img.width * pctX / 100), int(img.height * pctY / 100))
156 algostr = param.get("algorithm", "NEAREST")
157 if not hasattr(Image, algostr):
158 raise Exception("algorighm not found: %s" % (algostr))
159 rst = img.resize(tuple(size), getattr(Image, algostr))
160 rst.save(filename)
163def Base_image_writetext(self, param):
164 """
165 - name: write text to local image
166 image_writetext:
167 input: filename.png
168 font: /path/to/file.ttc
169 text: hello world
170 color: blue
171 position: [100, 200] # x, y
172 """
173 text = self.getvalue(param)
174 input_filename, filename = inout_fname(param)
175 pos = param.get("position", (0, 0))
176 fontname = param.get("font")
177 fontsize = param.get("size", 10)
178 color = param.get("color", "red")
179 if fontname is None:
180 font = ImageFont.load_default()
181 else:
182 font = ImageFont.truetype(fontname, size=fontsize)
183 img = Image.open(input_filename)
184 draw = ImageDraw.Draw(img)
185 fillcolor = ImageColor.getcolor(color)
186 draw.text(tuple(pos), text, fill=fillcolor, font=font)
187 del draw
188 img.save(filename)
191def Base_image_filter(self, param):
192 """
193 - name: image filter
194 image_filter:
195 input: input.png
196 output: output.png
197 filter:
198 - ModeFilter: 12
199 - GaussianBlur: 5
200 - ModeFilter: 12
201 - GaussianBlur: 1
202 """
203 input_filename, filename = inout_fname(param)
204 img = Image.open(input_filename)
205 for f in param.get("filter", []):
206 if not isinstance(f, dict):
207 raise Exception("invalid parameter: %s" % (f))
208 for k, v in f.items():
209 fn = getattr(ImageFilter, k)
210 if not callable(fn):
211 raise Exception("filter %s(%s) not found" % (k, v))
212 self.log.debug("filter %s %s", k, v)
213 if v is None:
214 img = img.filter(fn)
215 elif isinstance(v, (tuple, list)):
216 img = img.filter(fn(*v))
217 elif isinstance(v, dict):
218 img = img.filter(fn(**v))
219 else:
220 img = img.filter(fn(v))
221 img.save(filename)
224image_convert_schema = {
225 "allOf": [
226 {"$ref": "#/definitions/common/inout"},
227 {
228 "type": "object",
229 "properties": {
230 "mode": {
231 "type": "string",
232 "enum": Image.MODES,
233 }
234 }
235 }
236 ]
237}
240def Base_image_convert(self, param):
241 """
242 - name: grayscale
243 image_convert:
244 input: filename.png
245 mode: L
246 """
247 input_filename, filename = inout_fname(param)
248 img = Image.open(input_filename)
249 mode = param.get("mode")
250 if mode is None:
251 raise Exception("invalid parameter: %s" % (param))
252 img = img.convert(mode)
253 img.save(filename)
256def Base_image_chops(self, param):
257 """
258 - name: blend image
259 image_chops:
260 input: filename.png
261 filter:
262 - blend: [addimage.png, 0.5]
263 - darker: darklimit.png
264 """
265 input_filename, filename = inout_fname(param)
266 img = Image.open(input_filename)
267 for f in param.get("filter", []):
268 if not isinstance(f, dict):
269 raise Exception("invalid parameter: %s" % (param))
270 for k, v in f.items():
271 fn = getattr(ImageChops, k)
272 if not callable(fn):
273 raise Exception("chop %s(%s) not found" % (k, v))
274 self.log.debug("chop %s %s", k, v)
275 if isinstance(v, (list, tuple)):
276 fname = v[0]
277 args = v[1:]
278 else:
279 fname = v
280 args = []
281 img2 = Image.open(fname)
282 img = fn(img, img2, *args)
283 img.save(filename)
286def Base_image_enhance(self, param):
287 """
288 - name: enhance image
289 image_enhance:
290 input: filename.png
291 filter:
292 - Sharpness: 0.5
293 - Brightness: 1.2
294 """
295 input_filename, filename = inout_fname(param)
296 img = Image.open(input_filename)
297 for f in param.get("filter", []):
298 if not isinstance(f, dict):
299 raise Exception("invalid parameter: %s" % (param))
300 for k, v in f.items():
301 fn = getattr(ImageEnhance, k)
302 if not callable(fn):
303 raise Exception("enhance %s(%s) not found" % (k, v))
304 self.log.debug("enhance %s %s", k, v)
305 enhancer = fn(img)
306 img = enhancer.enhance(v)
307 img.save(filename)
310def Base_image_ops(self, param):
311 """
312 - name: image op
313 image_ops:
314 input: filename.png
315 filter:
316 - autocontrast: []
317 - crop: [0]
318 - mirror: []
319 """
320 input_filename, filename = inout_fname(param)
321 img = Image.open(input_filename)
322 for f in param.get("filter", []):
323 if not isinstance(f, dict):
324 raise Exception("invalid parameter: %s" % (param))
325 for k, v in f.items():
326 fn = getattr(ImageOps, k)
327 if not callable(fn):
328 raise Exception("ops %s(%s) not found" % (k, v))
329 self.log.debug("ops %s %s", k, v)
330 img = fn(img, *v)
331 img.save(filename)
334def Base_image_archive(self, param):
335 input_filename, filename = inout_fname(param)
336 delflag = param.get("delete", True)
337 assert input_filename != filename
338 base, ext = os.path.splitext(filename)
339 if ext in (".zip", ".cbz"):
340 with zipfile.ZipFile(filename, 'a') as zf:
341 self.log.debug("zip %s %s", filename, input_filename)
342 zf.write(input_filename)
343 if delflag:
344 os.unlink(input_filename)
345 elif ext in (".tar"):
346 with tarfile.open(filename, 'a') as tf:
347 self.log.debug("tar %s %s", filename, input_filename)
348 tf.add(input_filename)
349 if delflag:
350 os.unlink(input_filename)
351 else:
352 raise Exception("not implemented yet: archive %s" % (param,))