1 package org.sentrysoftware.maven.skin;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.awt.Image;
24 import java.awt.image.BufferedImage;
25 import java.io.File;
26 import java.io.IOException;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.regex.Pattern;
32 import java.util.stream.Collectors;
33
34 import javax.imageio.IIOImage;
35 import javax.imageio.ImageIO;
36 import javax.imageio.ImageWriteParam;
37 import javax.imageio.ImageWriter;
38 import javax.imageio.spi.IIORegistry;
39 import javax.imageio.stream.FileImageOutputStream;
40
41 import org.apache.velocity.tools.config.DefaultKey;
42 import org.jsoup.nodes.Element;
43
44 import com.luciad.imageio.webp.WebPImageReaderSpi;
45 import com.luciad.imageio.webp.WebPImageWriterSpi;
46 import com.luciad.imageio.webp.WebPWriteParam;
47
48
49
50
51 @DefaultKey("imageTool")
52 public class ImageTool {
53
54 static {
55
56
57
58 IIORegistry iioRegistry = IIORegistry.getDefaultInstance();
59 iioRegistry.registerServiceProvider(new WebPImageWriterSpi());
60 iioRegistry.registerServiceProvider(new WebPImageReaderSpi());
61
62 }
63
64
65
66
67
68
69
70
71 private static final Pattern ABSOLUTE_URL_PATTERN = Pattern.compile("^(?:[a-z]+:)?//", Pattern.CASE_INSENSITIVE);
72
73
74
75
76 public ImageTool() {
77
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91 protected static boolean isAbsoluteUrl(final String path) {
92 return ABSOLUTE_URL_PATTERN.matcher(path).find();
93 }
94
95
96
97
98
99
100
101
102
103
104
105
106 public Element checkImageLinks(
107 final Element body,
108 final String basedir,
109 final String currentDocument)
110 throws IOException {
111
112
113 List<String> errorList = new ArrayList<String>();
114
115
116 Path basedirPath = Paths.get(basedir).toAbsolutePath();
117
118
119 Path documentPath = Paths.get(basedir, currentDocument);
120
121 Path parentPath = documentPath.getParent();
122 if (parentPath == null) {
123 throw new IOException("Couldn't get the parent path of " + currentDocument);
124 }
125
126
127 List<Element> elements = body.select("img");
128
129
130 for (Element element : elements) {
131
132
133 String imageSrc = element.attr("src");
134 if (imageSrc.isEmpty()) {
135 continue;
136 }
137
138
139 if (isAbsoluteUrl(imageSrc)) {
140 continue;
141 }
142
143
144 Path sourcePath = documentPath.resolveSibling(imageSrc);
145 File sourceFile = sourcePath.toFile();
146
147
148 if (!sourcePath.toAbsolutePath().startsWith(basedirPath)) {
149 continue;
150 }
151
152
153
154 Path recalculatedPath = parentPath.toRealPath().relativize(sourcePath.toRealPath());
155 String sourcePathSlashString = sourcePath.toString().replace('\\', '/');
156 String recalculatedPathSlashString = recalculatedPath.toString().replace('\\', '/');
157 if (!recalculatedPathSlashString.endsWith(sourcePathSlashString)
158 && !sourcePathSlashString.endsWith(recalculatedPathSlashString)) {
159 errorList
160 .add(
161 "Referenced image " + imageSrc + " in " + currentDocument + " doesn't match case of actual file "
162 + recalculatedPath);
163 }
164
165
166 if (!sourceFile.isFile()) {
167 errorList.add("Referenced image " + imageSrc + " in " + currentDocument + " doesn't exist");
168 }
169
170 }
171
172
173 if (!errorList.isEmpty()) {
174 throw new IOException(errorList.stream().collect(Collectors.joining("\n")));
175 }
176
177 return body;
178
179 }
180
181
182
183
184
185
186
187
188 protected static String getExtension(final File file) {
189 String name = file.getName();
190 int dotIndex = name.lastIndexOf('.');
191 if (dotIndex > -1) {
192 return name.substring(dotIndex + 1);
193 }
194 return "";
195 }
196
197
198
199
200
201
202
203
204 protected static String getNameWithoutExtension(final File file) {
205 String name = file.getName();
206 int dotIndex = name.lastIndexOf('.');
207 if (dotIndex > -1) {
208 return name.substring(0, dotIndex);
209 }
210 return name;
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 protected static File createThumbnail(
225 final File sourceFile,
226 final String thumbnailMark,
227 final int maxWidth,
228 final int maxHeight)
229 throws IOException {
230
231
232 if (!sourceFile.isFile()) {
233 throw new IOException(sourceFile.getAbsolutePath() + " does not exist");
234 }
235
236
237 File destination = new File(sourceFile.getParent(), getNameWithoutExtension(sourceFile) + thumbnailMark + ".jpg");
238
239
240 if (Helper.getLastModifiedTime(sourceFile) < Helper.getLastModifiedTime(destination)) {
241 return destination;
242 }
243
244
245 BufferedImage sourceImage = ImageIO.read(sourceFile);
246 String imageType = getExtension(sourceFile).toLowerCase();
247
248
249 int targetWidth = sourceImage.getWidth();
250 int targetHeight = sourceImage.getHeight();
251
252 if (maxWidth > 0 && targetWidth > maxWidth) {
253 targetHeight = targetHeight * maxWidth / targetWidth;
254 targetWidth = maxWidth;
255 }
256 if (maxHeight > 0 && targetHeight > maxHeight) {
257 targetWidth = targetWidth * maxHeight / targetHeight;
258 targetHeight = maxHeight;
259 }
260
261
262 Image resultingImage = sourceImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH);
263 BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
264 outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
265
266
267 ImageIO.write(outputImage, imageType, destination);
268
269 return destination;
270
271 }
272
273
274
275
276
277
278
279
280
281 protected static File saveImageFileAsWebp(final File sourceFile) throws IOException {
282
283
284 if (!sourceFile.isFile()) {
285 throw new IOException(sourceFile.getAbsolutePath() + " does not exist");
286 }
287
288
289 String webpImagePath = getNameWithoutExtension(sourceFile) + ".webp";
290 File webpFile = new File(sourceFile.getParent(), webpImagePath);
291
292
293 if (Helper.getLastModifiedTime(sourceFile) < Helper.getLastModifiedTime(webpFile)) {
294 return webpFile;
295 }
296
297
298 BufferedImage sourceImage = ImageIO.read(sourceFile);
299 if (sourceImage == null) {
300 return null;
301 }
302
303
304 String imageType = getExtension(sourceFile).toLowerCase();
305 if ("webp".equals(imageType)) {
306 return null;
307 }
308
309
310 ImageWriter writer = ImageIO.getImageWritersBySuffix("webp").next();
311
312
313 WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale());
314 writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
315 if ("jpeg".equals(imageType) || "jpg".equals(imageType)) {
316 writeParam.setCompressionType(writeParam.getCompressionTypes()[WebPWriteParam.LOSSY_COMPRESSION]);
317 } else {
318 writeParam.setCompressionType(writeParam.getCompressionTypes()[WebPWriteParam.LOSSLESS_COMPRESSION]);
319 }
320
321
322 writer.setOutput(new FileImageOutputStream(webpFile));
323
324
325 writer.write(null, new IIOImage(sourceImage, null, null), writeParam);
326
327
328 return webpFile;
329
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345 public Element convertImagesToWebp(
346 final Element body,
347 final String selector,
348 final String basedir,
349 final String currentDocument)
350 throws IOException {
351
352
353 Path basedirPath = Paths.get(basedir).toAbsolutePath();
354
355
356 Path documentPath = Paths.get(basedir, currentDocument);
357
358 Path parentPath = documentPath.getParent();
359 if (parentPath == null) {
360 throw new IOException("Couldn't get parent path of " + currentDocument);
361 }
362
363
364 List<Element> elements = body.select(selector);
365
366
367 for (Element element : elements) {
368
369
370 String imageSrc = element.attr("src");
371 if (imageSrc.isEmpty()) {
372 continue;
373 }
374
375
376 if (isAbsoluteUrl(imageSrc)) {
377 continue;
378 }
379
380
381 Path sourcePath = documentPath.resolveSibling(imageSrc);
382 File sourceFile = sourcePath.toFile();
383
384
385 if (!sourcePath.toAbsolutePath().startsWith(basedirPath)) {
386 continue;
387 }
388
389
390 if (!sourceFile.isFile()) {
391 throw new IOException(sourceFile.getAbsolutePath() + " (referenced as " + imageSrc + ") does not exist");
392 }
393
394
395 File webpFile = saveImageFileAsWebp(sourceFile);
396 if (webpFile == null) {
397 continue;
398 }
399
400
401 String webpSrc = parentPath.relativize(webpFile.toPath()).toString().replace('\\', '/');
402
403
404 element
405 .wrap("<picture>")
406 .parent()
407 .prependElement("source")
408 .attr("srcset", webpSrc)
409 .attr("type", "image/webp");
410
411 }
412
413 return body;
414
415 }
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430 public Element explicitImageSize(
431 final Element body,
432 final String selector,
433 final String basedir,
434 final String currentDocument)
435 throws IOException {
436
437
438 Path basedirPath = Paths.get(basedir).toAbsolutePath();
439
440
441 Path documentPath = Paths.get(basedir, currentDocument);
442
443
444 List<Element> elements = body.select(selector);
445
446
447 for (Element element : elements) {
448
449
450 String imageSrc = element.attr("src");
451 if (imageSrc.isEmpty()) {
452 continue;
453 }
454
455
456 if (isAbsoluteUrl(imageSrc)) {
457 continue;
458 }
459
460
461 if (element.attr("style").matches("(^|\\b)(width:|height:)")
462 || !element.attr("height").isEmpty()
463 || !element.attr("width").isEmpty()) {
464 continue;
465 }
466
467
468 Path sourcePath = documentPath.resolveSibling(imageSrc);
469 File sourceFile = sourcePath.toFile();
470
471
472 if (!sourcePath.toAbsolutePath().startsWith(basedirPath)) {
473 continue;
474 }
475
476
477 if (!sourceFile.isFile()) {
478 throw new IOException(sourceFile.getAbsolutePath() + " (referenced as " + imageSrc + ") does not exist");
479 }
480
481
482 BufferedImage sourceImage = ImageIO.read(sourceFile);
483 if (sourceImage == null) {
484 continue;
485 }
486
487
488 element
489 .attr("width", String.valueOf(sourceImage.getWidth()))
490 .attr("height", String.valueOf(sourceImage.getHeight()))
491 .attr(
492 "style",
493 String
494 .format(
495 "width: %dpx; height: %dpx;%s",
496 sourceImage.getWidth(),
497 sourceImage.getHeight(),
498 element.attr("style")));
499
500 }
501
502 return body;
503
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535 public Element convertImagesToThumbnails(
536 final Element body,
537 final String selector,
538 final String basedir,
539 final String currentDocument,
540 final int maxWidth,
541 final int maxHeight,
542 final String wrapTemplate)
543 throws IOException {
544
545
546 Path basedirPath = Paths.get(basedir).toAbsolutePath();
547
548
549 Path documentPath = Paths.get(basedir, currentDocument);
550
551 Path parentPath = documentPath.getParent();
552 if (parentPath == null) {
553 throw new IOException("Couldn't get parent path of " + currentDocument);
554 }
555
556
557 List<Element> elements = body.select(selector);
558
559
560 for (Element element : elements) {
561
562
563 String imageSrc = element.attr("src");
564 if (imageSrc.isEmpty()) {
565 continue;
566 }
567
568
569 if (isAbsoluteUrl(imageSrc)) {
570 continue;
571 }
572
573
574 String imageAlt = element.attr("alt");
575
576
577 Path sourcePath = documentPath.resolveSibling(imageSrc);
578 File sourceFile = sourcePath.toFile();
579
580
581 if (!sourcePath.toAbsolutePath().startsWith(basedirPath)) {
582 continue;
583 }
584
585
586 if (!sourceFile.isFile()) {
587 throw new IOException(sourceFile.getAbsolutePath() + " (referenced as " + imageSrc + ") does not exist");
588 }
589
590
591 BufferedImage sourceImage = ImageIO.read(sourceFile);
592 int sourceWidth = sourceImage.getWidth();
593 int sourceHeight = sourceImage.getHeight();
594
595
596 File thumbnailFile = createThumbnail(sourceFile, "-thumbnail", maxWidth, maxHeight);
597
598
599 BufferedImage thumbnailImage = ImageIO.read(thumbnailFile);
600 int thumbnailWidth = thumbnailImage.getWidth();
601 int thumbnailHeight = thumbnailImage.getHeight();
602
603
604 String thumbnailSrc = parentPath.relativize(thumbnailFile.toPath()).toString().replace('\\', '/');
605
606
607 String wrapHtml = wrapTemplate
608 .replaceAll("%imgWidth%", String.valueOf(sourceWidth))
609 .replaceAll("%imgHeight%", String.valueOf(sourceHeight))
610 .replaceAll("%thumbWidth%", String.valueOf(thumbnailWidth))
611 .replaceAll("%thumbHeight%", String.valueOf(thumbnailHeight))
612 .replaceAll("%thumbSrc%", thumbnailSrc)
613 .replaceAll("%imgAlt%", imageAlt);
614
615
616
617 Element pictureElement = element;
618 if ("PICTURE".equalsIgnoreCase(element.parent().tagName())) {
619 pictureElement = element.parent();
620 }
621 pictureElement.wrap(wrapHtml);
622
623 }
624
625 return body;
626
627 }
628
629 }