Skip to content

Commit 41cdab1

Browse files
authored
Support colorized unit images in unit stats table. (#10421)
* Support colorized unit images in unit stats table. Fixes #7256.
1 parent 6c9d008 commit 41cdab1

File tree

2 files changed

+53
-16
lines changed

2 files changed

+53
-16
lines changed

game-app/game-core/src/main/java/games/strategy/triplea/image/UnitImageFactory.java

+49-11
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,21 @@
2121
import java.awt.Image;
2222
import java.awt.Toolkit;
2323
import java.awt.image.BufferedImage;
24+
import java.io.File;
25+
import java.io.IOException;
2426
import java.net.URL;
27+
import java.nio.file.Files;
2528
import java.util.HashMap;
2629
import java.util.Map;
2730
import java.util.Optional;
31+
import javax.imageio.ImageIO;
2832
import javax.swing.ImageIcon;
2933
import lombok.Builder;
3034
import lombok.Value;
35+
import lombok.extern.slf4j.Slf4j;
3136

3237
/** A factory with an image cache for creating unit images. */
38+
@Slf4j
3339
public class UnitImageFactory {
3440
public static final int DEFAULT_UNIT_ICON_SIZE = 48;
3541
private static final String FILE_NAME_BASE = "units/";
@@ -51,6 +57,8 @@ public class UnitImageFactory {
5157
private final Map<ImageKey, Image> images = new HashMap<>();
5258
// maps Point -> Icon
5359
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<>();
5462
// Scaling factor for unit images
5563
private final double scaleFactor;
5664
private final ResourceLoader resourceLoader;
@@ -216,9 +224,7 @@ public Image getImage(final ImageKey imageKey) {
216224
baseImage -> {
217225
// We want to scale units according to the given scale factor.
218226
// 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.
222228
// Image observer is null, since the image should have been guaranteed to
223229
// be loaded.
224230
final int width = (int) (baseImage.getWidth(null) * scaleFactor);
@@ -256,6 +262,34 @@ public Optional<URL> getBaseImageUrl(final ImageKey imageKey) {
256262
return Optional.ofNullable(url);
257263
}
258264

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+
259293
private Optional<Image> getTransformedImage(final ImageKey imageKey) {
260294
return getBaseImageUrl(imageKey)
261295
.map(imageLocation -> loadImageAndTransform(imageLocation, imageKey));
@@ -268,28 +302,32 @@ private Image loadImageAndTransform(URL imageLocation, ImageKey imageKey) {
268302
}
269303

270304
private Image transformImageIfNeeded(Image image, ImageKey imageKey) {
271-
if (mapData.ignoreTransformingUnit(imageKey.getType().getName())) {
305+
if (!shouldTransformImage(imageKey)) {
272306
return image;
273307
}
274308
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-
}
280309
// Create an image copy so we don't modify the one returned by the toolkit which may be cached.
281310
image = createImageCopy(image);
311+
Optional<Color> unitColor = mapData.getUnitColor(playerName);
282312
if (unitColor.isPresent()) {
283313
final int brightness = mapData.getUnitBrightness(playerName);
284314
ImageTransformer.colorize(unitColor.get(), brightness, image);
285315
}
286-
if (shouldFlip) {
316+
if (mapData.shouldFlipUnit(playerName)) {
287317
ImageTransformer.flipHorizontally(image);
288318
}
289319
return image;
290320
}
291321

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) {
293331
final var copy =
294332
new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
295333
Graphics g = copy.createGraphics();

game-app/game-core/src/main/java/games/strategy/triplea/ui/menubar/help/UnitStatsTable.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ private static List<UnitType> getPlayerUnitsWithImages(
130130
// this next part is purely to allow people to "add" neutral (null player) units to
131131
// territories.
132132
// This is because the null player does not have a production frontier, and we also do not
133-
// know what units we have
134-
// art for, so only use the units on a map.
133+
// know what units we have art for, so only use the units on a map.
135134
for (final Territory t : data.getMap()) {
136135
for (final Unit u : t.getUnitCollection()) {
137136
if (u.isOwnedBy(player)) {
@@ -145,8 +144,7 @@ private static List<UnitType> getPlayerUnitsWithImages(
145144
if (!unitTypes.contains(ut)) {
146145
try {
147146
final UnitImageFactory imageFactory = uiContext.getUnitImageFactory();
148-
if (imageFactory != null
149-
&& imageFactory.hasImage(ImageKey.builder().player(player).type(ut).build())) {
147+
if (imageFactory.hasImage(ImageKey.builder().player(player).type(ut).build())) {
150148
unitTypes.add(ut);
151149
}
152150
} catch (final Exception e) {
@@ -167,7 +165,8 @@ private static String getUnitImageUrl(
167165
}
168166
final String imageLocation =
169167
unitImageFactory
170-
.getBaseImageUrl(ImageKey.builder().type(unitType).player(player).build())
168+
.getPossiblyTransformedImageUrl(
169+
ImageKey.builder().type(unitType).player(player).build())
171170
.map(Object::toString)
172171
.orElse("");
173172
return "<img src=\"" + imageLocation + "\" border=\"0\"/>";

0 commit comments

Comments
 (0)