Skip to content

Commit 48bf244

Browse files
committed
chore: optimize pwa icon generation
Do not collect all generated icons into memory, but instead write each icon to disc flushing the cache to not use so much memory.
1 parent daf13bc commit 48bf244

File tree

1 file changed

+45
-16
lines changed

1 file changed

+45
-16
lines changed

flow-build-tools/src/main/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIcons.java

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import javax.imageio.ImageIO;
1919

20+
import java.awt.Color;
21+
import java.awt.Graphics2D;
22+
import java.awt.Image;
2023
import java.awt.image.BufferedImage;
2124
import java.io.File;
2225
import java.io.IOException;
@@ -45,6 +48,11 @@
4548
/**
4649
* Generates necessary PWA icons.
4750
* <p>
51+
* Icons are processed in parallel but each thread draws, writes the PNG
52+
* directly to disk, and immediately flushes the scaled image. This avoids
53+
* accumulating all icon data in memory while still benefiting from concurrent
54+
* I/O and image scaling.
55+
* <p>
4856
* For internal use only. May be renamed or removed in a future release.
4957
*/
5058
public class TaskGeneratePWAIcons implements FallibleCommand {
@@ -94,8 +102,7 @@ public void execute() throws ExecutionFailedException {
94102
ExecutorService executor = Executors.newFixedThreadPool(4);
95103
CompletableFuture<?>[] iconsGenerators = PwaRegistry
96104
.getIconTemplates(pwaConfiguration.getIconPath()).stream()
97-
.map(icon -> new InternalPwaIcon(icon, baseImage))
98-
.map(this::generateIcon)
105+
.map(icon -> generateIconTask(icon, baseImage))
99106
.map(task -> CompletableFuture.runAsync(task, executor))
100107
.toArray(CompletableFuture[]::new);
101108
try {
@@ -115,6 +122,7 @@ public void execute() throws ExecutionFailedException {
115122
} finally {
116123
executor.shutdown();
117124
}
125+
baseImage.flush();
118126
} finally {
119127
if (headless == null) {
120128
System.clearProperty(HEADLESS_PROPERTY);
@@ -173,30 +181,51 @@ private URL findIcon(PwaConfiguration pwaConfiguration) {
173181
return iconURL;
174182
}
175183

176-
private Runnable generateIcon(InternalPwaIcon icon) {
177-
Path iconPath = generatedIconsPath.resolve(icon.getRelHref()
178-
.substring(1).replace('/', File.separatorChar));
184+
private Runnable generateIconTask(PwaIcon icon, BufferedImage baseImage) {
185+
String relHref = "/" + icon.getHref().split("\\?")[0];
186+
Path iconPath = generatedIconsPath
187+
.resolve(relHref.substring(1).replace('/', File.separatorChar));
188+
int targetWidth = icon.getWidth();
189+
int targetHeight = icon.getHeight();
179190
return () -> {
191+
BufferedImage scaled = drawIconImage(baseImage, targetWidth,
192+
targetHeight);
180193
try (OutputStream os = Files.newOutputStream(iconPath)) {
181-
icon.write(os);
194+
ImageIO.write(scaled, "png", os);
182195
} catch (IOException e) {
183196
throw new UncheckedIOException(e);
197+
} finally {
198+
scaled.flush();
184199
}
185200
};
186201
}
187202

188-
private static class InternalPwaIcon extends PwaIcon {
189-
private final BufferedImage baseImage;
203+
private static BufferedImage drawIconImage(BufferedImage baseImage,
204+
int targetWidth, int targetHeight) {
205+
int bgColor = baseImage.getRGB(0, 0);
190206

191-
public InternalPwaIcon(PwaIcon icon, BufferedImage baseImage) {
192-
super(icon);
193-
this.baseImage = baseImage;
194-
}
207+
BufferedImage bimage = new BufferedImage(targetWidth, targetHeight,
208+
BufferedImage.TYPE_INT_ARGB);
209+
Graphics2D graphics = bimage.createGraphics();
210+
try {
211+
graphics.setBackground(new Color(bgColor, true));
212+
graphics.clearRect(0, 0, targetWidth, targetHeight);
195213

196-
@Override
197-
protected BufferedImage getBaseImage() {
198-
return baseImage;
199-
}
214+
float ratio = Math.max((float) baseImage.getWidth() / targetWidth,
215+
(float) baseImage.getHeight() / targetHeight);
216+
ratio = Math.max(ratio, 1.0f);
217+
218+
int newWidth = Math.round(baseImage.getHeight() / ratio);
219+
int newHeight = Math.round(baseImage.getWidth() / ratio);
200220

221+
graphics.drawImage(
222+
baseImage.getScaledInstance(newWidth, newHeight,
223+
Image.SCALE_SMOOTH),
224+
(targetWidth - newWidth) / 2,
225+
(targetHeight - newHeight) / 2, null);
226+
} finally {
227+
graphics.dispose();
228+
}
229+
return bimage;
201230
}
202231
}

0 commit comments

Comments
 (0)