Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better listing of the dice images #352

Merged
merged 1 commit into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Integer, BufferedImage> 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);
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -136,34 +154,50 @@ String createRollCacheName(Roll roll, DiceStyleAndColor diceStyleAndColor) {
private Supplier<? extends InputStream> createNewFileForRoll(Roll roll, File file, String name, DiceStyleAndColor diceStyleAndColor) {
Stopwatch stopwatch = Stopwatch.createStarted();

List<List<BufferedImage>> images = roll.getRandomElementsInRoll().getRandomElements().stream()
final List<List<BufferedImage>> 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<BufferedImage> 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<BufferedImage> 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<BufferedImage> 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 -> {
Expand All @@ -178,6 +212,7 @@ private Supplier<? extends InputStream> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,15 +76,15 @@ static Stream<Arguments> generateDiceStyleDataPerColor() {
@BeforeEach
void setup() throws IOException {
File cacheDirectory = new File("imageCache/");
if(cacheDirectory.exists()){
if (cacheDirectory.exists()) {
FileUtils.cleanDirectory(cacheDirectory);
}
}

@AfterEach
void cleanUp() throws IOException {
File cacheDirectory = new File("imageCache/");
if(cacheDirectory.exists()){
if (cacheDirectory.exists()) {
FileUtils.cleanDirectory(cacheDirectory);
}
}
Expand Down Expand Up @@ -178,7 +179,7 @@ void getImageForRoll_blackGold() throws ExpressionException, IOException {
Supplier<? extends InputStream> 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
Expand Down Expand Up @@ -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<Roll> rolls2 = new DiceEvaluator(new GivenNumberSupplier(1), 1000).evaluate("7d10");
Supplier<? extends InputStream> 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
Expand Down Expand Up @@ -349,7 +350,7 @@ void getImageForRoll_3dRedWhite() throws ExpressionException, IOException {
Supplier<? extends InputStream> 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
Expand All @@ -369,12 +370,10 @@ void getImageForRoll_d6DotsBlackAndGold() throws ExpressionException, IOExceptio
Supplier<? extends InputStream> 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<Roll> rolls = new DiceEvaluator(new GivenNumberSupplier(), 1000).evaluate("5");
Expand Down Expand Up @@ -491,15 +490,28 @@ void getImageForRoll_polyhedral_RdD() throws ExpressionException, IOException {
Supplier<? extends InputStream> 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<Roll> rolls = new DiceEvaluator(new GivenNumberSupplier( 99), 1000).evaluate("1d100");
List<Roll> rolls = new DiceEvaluator(new GivenNumberSupplier(99), 1000).evaluate("1d100");

Supplier<? extends InputStream> res = underTest.getImageForRoll(rolls, new DiceStyleAndColor(DiceImageStyle.polyhedral_RdD, "special"));

assertThat(res).isNull();
}

@Test
@Disabled
void debug() throws ExpressionException, IOException {
List<Roll> 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<? extends InputStream> 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);
}
}