diff --git a/README.adoc b/README.adoc index b236782a..02feda5a 100644 --- a/README.adoc +++ b/README.adoc @@ -244,8 +244,7 @@ For the direct rolls it can be configured with the `channel_config` command. The images will only be shown if the following conditions are met: * The `answer_format` is set to `full` or `without_expression` -* No set of dice with more than 15 dice -* Not more the 10 sets of dice +* No set of dice with more than 30 dice * No multi line result There are the following options: diff --git a/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java b/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java index 0338b1ab..70c84a20 100644 --- a/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java +++ b/bot/src/main/java/de/janno/discord/bot/dice/image/ImageResultCreator.java @@ -2,6 +2,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; import de.janno.discord.bot.BotMetrics; import de.janno.evaluator.dice.Roll; @@ -36,6 +40,22 @@ public class ImageResultCreator { private static final String CACHE_FOLDER = "imageCache"; private static final String CACHE_INDEX_FILE = "imageCacheName.csv"; private final BigInteger MAX_ROLL_COMBINATION_TO_CACHE; + private final LoadingCache separatorImage = CacheBuilder.newBuilder() + .maximumSize(10) + .build(new CacheLoader<>() { + @Override + public @NonNull BufferedImage load(@NonNull Integer high) { + BufferedImage combined = new BufferedImage(15, high, BufferedImage.TYPE_INT_ARGB_PRE); + + Graphics g = combined.getGraphics(); + g.setColor(Color.lightGray); + g.fillRect(1, (high / 2) - 2, 13, 4); + g.setColor(Color.black); + g.drawRect(1, (high / 2) - 2, 13, 4); + g.dispose(); + return combined; + } + }); public ImageResultCreator() { this(1000); @@ -101,9 +121,7 @@ String createRollCacheName(Roll roll, DiceStyleAndColor diceStyleAndColor) { } if (rolls.size() != 1 || rolls.get(0).getRandomElementsInRoll().getRandomElements().isEmpty() || - rolls.get(0).getRandomElementsInRoll().getRandomElements().size() > 10 || - rolls.get(0).getRandomElementsInRoll().getRandomElements().stream().anyMatch(r -> r.getRandomElements().size() > 15) || - rolls.get(0).getRandomElementsInRoll().getRandomElements().stream().anyMatch(r -> r.getRandomElements().isEmpty()) || + rolls.get(0).getRandomElementsInRoll().getRandomElements().stream().mapToInt(r -> r.getRandomElements().size()).sum() > 30 || rolls.get(0).getRandomElementsInRoll().getRandomElements().stream() .flatMap(r -> r.getRandomElements().stream()) .anyMatch(r -> diceStyleAndColor.getImageFor(r.getMaxInc(), r.getRollElement().asInteger().orElse(null), r.getRollElement().getColor()).isEmpty()) @@ -136,34 +154,50 @@ String createRollCacheName(Roll roll, DiceStyleAndColor diceStyleAndColor) { private Supplier createNewFileForRoll(Roll roll, File file, String name, DiceStyleAndColor diceStyleAndColor) { Stopwatch stopwatch = Stopwatch.createStarted(); - List> images = roll.getRandomElementsInRoll().getRandomElements().stream() + final List> images = roll.getRandomElementsInRoll().getRandomElements().stream() .map(r -> r.getRandomElements().stream() .flatMap(re -> diceStyleAndColor.getImageFor(re.getMaxInc(), re.getRollElement().asInteger().orElse(null), re.getRollElement().getColor()).stream()) .toList() ) .filter(l -> !l.isEmpty()) .toList(); + final int singleDiceSize = diceStyleAndColor.getDieHighAndWith(); + + BufferedImage separator = separatorImage.getUnchecked(singleDiceSize); + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < images.size() - 1; i++) { + builder.addAll(images.get(i)); + builder.add(separator); + } + builder.addAll(images.get(images.size() - 1)); + final List imagesWithSeparators = builder.build(); - int maxInnerSize = images.stream() - .mapToInt(List::size) - .max().orElseThrow(); + final int maxLineWidth = 640; - int singleDiceSize = diceStyleAndColor.getDieHighAndWith(); - int w = singleDiceSize * (maxInnerSize); - int h = singleDiceSize * (images.size()); + final int allInOneLineWidth = imagesWithSeparators.stream().mapToInt(BufferedImage::getWidth).sum(); + final int numberOfImageLines = (int) Math.ceil(((double) allInOneLineWidth) / ((double) maxLineWidth)); + + final int w = Math.min(allInOneLineWidth, maxLineWidth); + final int h = singleDiceSize * numberOfImageLines; BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); Graphics g = combined.getGraphics(); - for (int i = 0; i < images.size(); i++) { - List line = images.get(i); - for (int j = 0; j < line.size(); j++) { - g.drawImage(line.get(j), singleDiceSize * j, singleDiceSize * i, singleDiceSize, singleDiceSize, null); + int currentLine = 0; + int currentLineWidth = 0; + for (BufferedImage image : imagesWithSeparators) { + if (image.getWidth() + currentLineWidth > maxLineWidth) { + currentLine++; + currentLineWidth = image.getWidth(); + } else { + currentLineWidth += image.getWidth(); } + g.drawImage(image, currentLineWidth - image.getWidth(), singleDiceSize * currentLine, image.getWidth(), singleDiceSize, null); } + g.dispose(); - String indexPath = "%s/%s/%s".formatted(CACHE_FOLDER, diceStyleAndColor.toString(), CACHE_INDEX_FILE); + final String indexPath = "%s/%s/%s".formatted(CACHE_FOLDER, diceStyleAndColor.toString(), CACHE_INDEX_FILE); BigInteger combinations = roll.getRandomElementsInRoll().getRandomElements() .stream().flatMap(r -> r.getRandomElements().stream()) .map(r -> { @@ -178,6 +212,7 @@ private Supplier createNewFileForRoll(Roll roll, File fil ) .map(BigInteger::valueOf) .reduce(BigInteger.ONE, BigInteger::multiply); + //don't cache images that unlikely to ever get generated again if (MAX_ROLL_COMBINATION_TO_CACHE.compareTo(combinations) < 0) { BotMetrics.incrementImageResultMetricCounter(BotMetrics.CacheTag.CACHE_SKIP); diff --git a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java index 4a8d61a7..62992a73 100644 --- a/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java +++ b/bot/src/test/java/de/janno/discord/bot/dice/image/ImageResultCreatorTest.java @@ -10,6 +10,7 @@ import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -75,7 +76,7 @@ static Stream generateDiceStyleDataPerColor() { @BeforeEach void setup() throws IOException { File cacheDirectory = new File("imageCache/"); - if(cacheDirectory.exists()){ + if (cacheDirectory.exists()) { FileUtils.cleanDirectory(cacheDirectory); } } @@ -83,7 +84,7 @@ void setup() throws IOException { @AfterEach void cleanUp() throws IOException { File cacheDirectory = new File("imageCache/"); - if(cacheDirectory.exists()){ + if (cacheDirectory.exists()) { FileUtils.cleanDirectory(cacheDirectory); } } @@ -178,7 +179,7 @@ void getImageForRoll_blackGold() throws ExpressionException, IOException { Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_alies_v1, "black_and_gold")); assertThat(res).isNotNull(); - assertThat(getDataHash(res)).isEqualTo("704e741a90a7ca38dacd7e571863ab55bb9ae47120d0b16e7278993ac7f33b82"); + assertThat(getDataHash(res)).isEqualTo("a2c774609da21cd6982d3fad969a7ec87db430c1cb83cf7dadb61b3374cf5589"); } @Test @@ -271,14 +272,14 @@ void getImageForRoll_noCacheForLargeDiceSets() throws ExpressionException, IOExc assertThat(res1).isNotNull(); File cacheFolder = new File("imageCache/"); assertThat(cacheFolder).isEmptyDirectory(); - assertThat(getDataHash(res1)).isEqualTo("5672d47a89d51f0098bc154b22c72f1ea2059ba8d77a589adca8bd720c7ace2b"); + assertThat(getDataHash(res1)).isEqualTo("c6ea9275d2ab8391ff4978a4fd8e3f36fa0ef0ab4dc6fa85074a175e2bd307b0"); List rolls2 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("7d10"); Supplier res2 = underTest.getImageForRoll(rolls2, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, "red_and_white")); assertThat(cacheFolder).isEmptyDirectory(); assertThat(res2).isNotNull(); - assertThat(getDataHash(res2)).isEqualTo("5672d47a89d51f0098bc154b22c72f1ea2059ba8d77a589adca8bd720c7ace2b"); + assertThat(getDataHash(res2)).isEqualTo("c6ea9275d2ab8391ff4978a4fd8e3f36fa0ef0ab4dc6fa85074a175e2bd307b0"); } @Test @@ -349,7 +350,7 @@ void getImageForRoll_3dRedWhite() throws ExpressionException, IOException { Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_3d, "red_and_white")); assertThat(res).isNotNull(); - assertThat(getDataHash(res)).isEqualTo("2956ce00e097a7332f6769e37c69c7120074918316102b614758d65486c8cbfb"); + assertThat(getDataHash(res)).isEqualTo("5bb342eaacea0ef00fab10901e786a3b5f3b38b287f7c7961b9cf7f117694f6e"); } @Test @@ -369,12 +370,10 @@ void getImageForRoll_d6DotsBlackAndGold() throws ExpressionException, IOExceptio Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.d6_dots, "black_and_gold")); assertThat(res).isNotNull(); - assertThat(getDataHash(res)).isEqualTo("bd8246680e77103a186bd6eae3cdc230b8847c92ff55fffdef1f3fbb74aed45c"); + assertThat(getDataHash(res)).isEqualTo("d2cbedfb456d593a51fd5339c95b802ef1dc6157279083e4f9e947b86744226f"); } - - @Test void getImageForRoll_noDie_3dRedWhite() throws ExpressionException { List rolls = new DiceEvaluator(new GivenNumberSupplier(), 1000).evaluate("5"); @@ -491,15 +490,28 @@ void getImageForRoll_polyhedral_RdD() throws ExpressionException, IOException { Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, DiceImageStyle.polyhedral_RdD.getDefaultColor())); assertThat(res).isNotNull(); - assertThat(getDataHash(res)).isEqualTo("de01222cf4ea85e2fe2eea6539ebdfe9809bfe88f6dfb8ece5736df9eeb6603d"); + assertThat(getDataHash(res)).isEqualTo("134795311673058eac57eaeecb83010b9323b0f9056bd8d80af3355d81d3e674"); } @Test void getImageForRoll_polyhedral_RdD_specialColor() throws ExpressionException { - List rolls = new DiceEvaluator(new GivenNumberSupplier( 99), 1000).evaluate("1d100"); + List rolls = new DiceEvaluator(new GivenNumberSupplier(99), 1000).evaluate("1d100"); Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, "special")); assertThat(res).isNull(); } + + @Test + @Disabled + void debug() throws ExpressionException, IOException { + List rolls = new DiceEvaluator(new GivenNumberSupplier(4, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 20, 99, 8, 12), 1000).evaluate("1d4 + 6d6 + 1d7 + 1d8 + 1d10 +1d12 + 1d20 + 1d100 + 1d8 col 'special' + 1d12 col 'special'"); + + Supplier res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, DiceImageStyle.polyhedral_RdD.getDefaultColor())); + + + File out = new File("out.png"); + assertThat(res).isNotNull(); + FileUtils.copyInputStreamToFile(res.get(), out); + } } \ No newline at end of file