21
21
import java .awt .Image ;
22
22
import java .awt .Toolkit ;
23
23
import java .awt .image .BufferedImage ;
24
+ import java .io .File ;
25
+ import java .io .IOException ;
24
26
import java .net .URL ;
27
+ import java .nio .file .Files ;
25
28
import java .util .HashMap ;
26
29
import java .util .Map ;
27
30
import java .util .Optional ;
31
+ import javax .imageio .ImageIO ;
28
32
import javax .swing .ImageIcon ;
29
33
import lombok .Builder ;
30
34
import lombok .Value ;
35
+ import lombok .extern .slf4j .Slf4j ;
31
36
32
37
/** A factory with an image cache for creating unit images. */
38
+ @ Slf4j
33
39
public class UnitImageFactory {
34
40
public static final int DEFAULT_UNIT_ICON_SIZE = 48 ;
35
41
private static final String FILE_NAME_BASE = "units/" ;
@@ -51,6 +57,8 @@ public class UnitImageFactory {
51
57
private final Map <ImageKey , Image > images = new HashMap <>();
52
58
// maps Point -> Icon
53
59
private final Map <String , ImageIcon > icons = new HashMap <>();
60
+ // Temporary colorized image files used for URLs for html views (e.g. unit stats table).
61
+ private final Map <ImageKey , URL > colorizedTempFiles = new HashMap <>();
54
62
// Scaling factor for unit images
55
63
private final double scaleFactor ;
56
64
private final ResourceLoader resourceLoader ;
@@ -216,9 +224,7 @@ public Image getImage(final ImageKey imageKey) {
216
224
baseImage -> {
217
225
// We want to scale units according to the given scale factor.
218
226
// We use smooth scaling since the images are cached to allow to take our
219
- // time in
220
- // doing the
221
- // scaling.
227
+ // time in doing the scaling.
222
228
// Image observer is null, since the image should have been guaranteed to
223
229
// be loaded.
224
230
final int width = (int ) (baseImage .getWidth (null ) * scaleFactor );
@@ -256,6 +262,34 @@ public Optional<URL> getBaseImageUrl(final ImageKey imageKey) {
256
262
return Optional .ofNullable (url );
257
263
}
258
264
265
+ public Optional <URL > getPossiblyTransformedImageUrl (final ImageKey imageKey ) {
266
+ final Optional <URL > url = getBaseImageUrl (imageKey );
267
+ if (url .isEmpty () || !shouldTransformImage (imageKey )) {
268
+ return url ;
269
+ }
270
+ return Optional .of (
271
+ colorizedTempFiles .computeIfAbsent (
272
+ imageKey ,
273
+ key -> {
274
+ // The cast is safe because we use BufferedImage when transforming images.
275
+ BufferedImage bufferedImage = (BufferedImage ) loadImageAndTransform (url .get (), key );
276
+ try {
277
+ // Create a temp file that can be used in URLs. Note: JEditorPane doesn't support
278
+ // base64-encoded data: URLs, so we need to actually have a file on disk. We use
279
+ // a cache so that we don't create the same files multiple times.
280
+ File file = Files .createTempFile (key .getFullName (), ".png" ).toFile ();
281
+ // Delete the file on exit.
282
+ file .deleteOnExit ();
283
+ ImageIO .write (bufferedImage , "PNG" , file );
284
+ return file .toURI ().toURL ();
285
+ } catch (IOException e ) {
286
+ log .error ("Failed to create temp file: " , e );
287
+ }
288
+ // Return the non-colorized URL on error.
289
+ return url .get ();
290
+ }));
291
+ }
292
+
259
293
private Optional <Image > getTransformedImage (final ImageKey imageKey ) {
260
294
return getBaseImageUrl (imageKey )
261
295
.map (imageLocation -> loadImageAndTransform (imageLocation , imageKey ));
@@ -268,28 +302,32 @@ private Image loadImageAndTransform(URL imageLocation, ImageKey imageKey) {
268
302
}
269
303
270
304
private Image transformImageIfNeeded (Image image , ImageKey imageKey ) {
271
- if (mapData . ignoreTransformingUnit (imageKey . getType (). getName () )) {
305
+ if (! shouldTransformImage (imageKey )) {
272
306
return image ;
273
307
}
274
308
final String playerName = imageKey .getPlayer ().getName ();
275
- Optional <Color > unitColor = mapData .getUnitColor (playerName );
276
- boolean shouldFlip = mapData .shouldFlipUnit (playerName );
277
- if (unitColor .isEmpty () && !shouldFlip ) {
278
- return image ;
279
- }
280
309
// Create an image copy so we don't modify the one returned by the toolkit which may be cached.
281
310
image = createImageCopy (image );
311
+ Optional <Color > unitColor = mapData .getUnitColor (playerName );
282
312
if (unitColor .isPresent ()) {
283
313
final int brightness = mapData .getUnitBrightness (playerName );
284
314
ImageTransformer .colorize (unitColor .get (), brightness , image );
285
315
}
286
- if (shouldFlip ) {
316
+ if (mapData . shouldFlipUnit ( playerName ) ) {
287
317
ImageTransformer .flipHorizontally (image );
288
318
}
289
319
return image ;
290
320
}
291
321
292
- private static Image createImageCopy (Image image ) {
322
+ private boolean shouldTransformImage (ImageKey imageKey ) {
323
+ if (mapData .ignoreTransformingUnit (imageKey .getType ().getName ())) {
324
+ return false ;
325
+ }
326
+ final String playerName = imageKey .getPlayer ().getName ();
327
+ return mapData .getUnitColor (playerName ).isPresent () || mapData .shouldFlipUnit (playerName );
328
+ }
329
+
330
+ private static BufferedImage createImageCopy (Image image ) {
293
331
final var copy =
294
332
new BufferedImage (image .getWidth (null ), image .getHeight (null ), BufferedImage .TYPE_INT_ARGB );
295
333
Graphics g = copy .createGraphics ();
0 commit comments