Hide keyboard shortcuts

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 

8 

9 

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 

16 

17 

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""") 

32 

33 

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)) 

71 

72 

73image_optimize_schema = yaml.safe_load(""" 

74allOf: 

75 - "$ref": "#/definitions/common/inout" 

76 - type: object 

77 properties: 

78 command: {type: string} 

79""") 

80 

81 

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) 

109 

110 

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""") 

132 

133 

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) 

161 

162 

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) 

189 

190 

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) 

222 

223 

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} 

238 

239 

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) 

254 

255 

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) 

284 

285 

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) 

308 

309 

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) 

332 

333 

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,))