Skip to content

Commit

Permalink
Merge pull request #19 from Minecraft-TA/imgur-gallery
Browse files Browse the repository at this point in the history
Add imgur gallery/album background support
  • Loading branch information
Dream-Master authored Jul 30, 2023
2 parents 3c48495 + f32d123 commit 1181905
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 13 deletions.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
63 changes: 51 additions & 12 deletions src/main/java/alexiil/mods/load/MinecraftDisplayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.client.resources.LanguageManager;
Expand All @@ -41,7 +42,11 @@
import org.lwjgl.opengl.SharedDrawable;

import alexiil.mods.load.ProgressDisplayer.IDisplayer;
import alexiil.mods.load.json.*;
import alexiil.mods.load.imgur.ImgurCacheManager;
import alexiil.mods.load.json.Area;
import alexiil.mods.load.json.EPosition;
import alexiil.mods.load.json.EType;
import alexiil.mods.load.json.ImageRender;
import cpw.mods.fml.client.FMLFileResourcePack;
import cpw.mods.fml.client.FMLFolderResourcePack;
import cpw.mods.fml.client.SplashProgress;
Expand Down Expand Up @@ -133,8 +138,7 @@ public class MinecraftDisplayer implements IDisplayer {
private String loadingBarsColor = "fdf900";
private float[] lbRGB = new float[] { 1, 1, 0 };
private float loadingBarsAlpha = 0.5F;
private boolean useImgur = true;
public static String imgurGalleryLink = "https://imgur.com/gallery/Ks0TrYE";
private boolean useImgur = false;

private boolean saltBGhasBeenRendered = false;

Expand All @@ -149,6 +153,8 @@ public class MinecraftDisplayer implements IDisplayer {
private static String newBlendImage = "none";
private static int nonStaticElementsToGo;

private ImgurCacheManager imgurCacheManager = null;

private ScheduledExecutorService backgroundExec = null;
private boolean scheduledTipExecSet = false;

Expand Down Expand Up @@ -639,10 +645,8 @@ public void open(Configuration cfg) {
salt = cfg.getBoolean("salt", "skepticism", salt, comment29);

// imgur
String comment30 = "Set to true if you want to load images from an imgur gallery and use them as backgrounds. WIP, not working yet";
String comment30 = "Set to true if you want to load images from an imgur gallery and use them as backgrounds.";
useImgur = cfg.getBoolean("useImgur", "imgur", useImgur, comment30);
String comment31 = "Link to the imgur gallery";
imgurGalleryLink = cfg.getString("imgurGalleryLink", "imgur", imgurGalleryLink, comment31);

// tips
String comment32 = "Set to true if you want to display random tips. Tips are stored in a separate file";
Expand Down Expand Up @@ -726,6 +730,22 @@ public void run() {
}
}
}, changeFrequency, changeFrequency, TimeUnit.SECONDS);

if (useImgur) {
imgurCacheManager = new ImgurCacheManager();
imgurCacheManager.loadConfig(cfg);

List<String> imgurBackgrounds = new ArrayList<>();
imgurCacheManager.setupImgurGallery(res -> {
// Override the default background with the first image we get, otherwise the image will only
// be visible after the first blend occurs
if (imgurBackgrounds.isEmpty()) background = res.toString();

// Progressively add each image to the list of random backgrounds
imgurBackgrounds.add(res.toString());
randomBackgroundArray = imgurBackgrounds.toArray(new String[0]);
});
}
}
}

Expand Down Expand Up @@ -1332,8 +1352,7 @@ public void drawImageRender(ImageRender render, String text, double percent) {
}

GL11.glColor4f(render.getRed(), render.getGreen(), render.getBlue(), blendAlpha);
ResourceLocation res = new ResourceLocation(render.resourceLocation);
textureManager.bindTexture(res);
bindTexture(render.resourceLocation);
drawRect(
startX,
startY,
Expand All @@ -1351,8 +1370,7 @@ public void drawImageRender(ImageRender render, String text, double percent) {
new Area(0, 0, 256, 256),
new Area(0, 0, 0, 0));
GL11.glColor4f(render2.getRed(), render2.getGreen(), render2.getBlue(), 1.f - blendAlpha);
ResourceLocation res2 = new ResourceLocation(render2.resourceLocation);
textureManager.bindTexture(res2);
bindTexture(render2.resourceLocation);
drawRect(
startX,
startY,
Expand All @@ -1365,8 +1383,7 @@ public void drawImageRender(ImageRender render, String text, double percent) {
break;
} else {
GL11.glColor4f(render.getRed(), render.getGreen(), render.getBlue(), 1F);
ResourceLocation res = new ResourceLocation(render.resourceLocation);
textureManager.bindTexture(res);
bindTexture(render.resourceLocation);
drawRect(
startX,
startY,
Expand All @@ -1386,6 +1403,23 @@ public void drawImageRender(ImageRender render, String text, double percent) {
}
}

private void bindTexture(String resourceLocation) {
ResourceLocation res = new ResourceLocation(resourceLocation);

// We cannot go through the default texture loader, because it can't load from the file system
AbstractTexture texture = imgurCacheManager != null ? imgurCacheManager.getCachedTexture(res) : null;
if (texture != null) {
// Add the texture to TextureManager's cache to disable the loading logic in bindTexture
try {
textureManager.loadTexture(res, texture);
} catch (Exception e) {
BetterLoadingScreen.log.error("Failed to load imgur texture: " + res.getResourcePath(), e);
}
}

textureManager.bindTexture(res);
}

public void drawString(FontRenderer font, String text, int x, int y, int colour) {
font.drawString(text, x, y, colour);
GL11.glColor4f(1, 1, 1, 1);
Expand Down Expand Up @@ -1491,5 +1525,10 @@ public void close() {
backgroundExec.shutdown();
}
getOnlyList().remove(myPack);

if (imgurCacheManager != null) {
imgurCacheManager.cleanUp();
imgurCacheManager = null;
}
}
}
199 changes: 199 additions & 0 deletions src/main/java/alexiil/mods/load/imgur/ImgurCacheManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package alexiil.mods.load.imgur;

import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.config.Configuration;

import alexiil.mods.load.BetterLoadingScreen;

public class ImgurCacheManager {

private static final boolean OFFLINE_MODE = Boolean.getBoolean("bls.offlineMode");
private static final String IMGUR_CACHE_DIR = "bls-imgur-cache";

private final Map<String, AbstractTexture> textureCache = new ConcurrentHashMap<>();

private String appClientId;
private String galleryId;
private int requestTimeout;

private volatile boolean cancelSetup;

public void loadConfig(Configuration config) {
appClientId = config.getString(
"imgurAppClientId",
"imgur",
"",
"The client ID of your imgur application. Required to access the imgur api.");
galleryId = config
.getString("imgurGalleryId", "imgur", "", "ID of the imgur gallery/album. For example: Ks0TrYE");
requestTimeout = config.getInt(
"imgurRequestTimeout",
"imgur",
5000,
100,
Integer.MAX_VALUE,
"Request timeout (ms) for imgur requests");
}

public AbstractTexture getCachedTexture(ResourceLocation location) {
if (!location.getResourceDomain().equals(IMGUR_CACHE_DIR)) return null;

return textureCache.get(location.getResourcePath());
}

public void cleanUp() {
textureCache.values().forEach(AbstractTexture::deleteGlTexture);
textureCache.clear();

cancelSetup = true;
}

public void setupImgurGallery(Consumer<ResourceLocation> textureLocationConsumer) {
Path cacheFolder = Paths.get(IMGUR_CACHE_DIR);
if (Files.notExists(cacheFolder)) {
try {
Files.createDirectory(cacheFolder);
} catch (IOException e) {
BetterLoadingScreen.log.error("Error while creating imgur cache directory", e);
return;
}
}

List<String> cachedImageIDs = getCachedImageIDs();
if (cachedImageIDs == null) return;

// Load any image that is already cached. This avoids waiting for the imgur api call to finish to get something
// rendering
loadAnyImageFromDisk(cachedImageIDs, textureLocationConsumer);

CompletableFuture.runAsync(() -> {
try (ImgurClient client = OFFLINE_MODE ? null : new ImgurClient(appClientId, requestTimeout)) {
Consumer<String> imageHandler = imageID -> {
// This will leave behind cached images that are no longer in the gallery
synchronized (cachedImageIDs) {
cachedImageIDs.remove(imageID);
}

if (cancelSetup) return;

// Should only be the image that might have been loaded in loadAnyImageFromDisk()
if (textureCache.containsKey(imageID)) return;

Path imageFile = getCachedImagePath(imageID);

try {
if (Files.exists(getCachedImagePath(imageID))) {
// Read from disk
readAndCacheImageFromStream(
imageID,
new BufferedInputStream(Files.newInputStream(imageFile), 1024 * 1024),
false);
} else {
if (OFFLINE_MODE) return;

readAndCacheImageFromStream(
imageID,
new ByteArrayInputStream(client.fetchImage(imageID)),
true);
}
} catch (IOException e) {
BetterLoadingScreen.log.error("Error while loading imgur image", e);
return;
}

synchronized (textureLocationConsumer) {
textureLocationConsumer.accept(new ResourceLocation(IMGUR_CACHE_DIR, imageID));
}
};

if (OFFLINE_MODE) {
cachedImageIDs.stream().parallel().forEach(imageHandler);
} else {
client.fetchGalleryImageIDs(galleryId, true).stream().parallel().forEach(imageHandler);
}
} catch (Exception e) {
BetterLoadingScreen.log.error("Error while fetching imgur gallery", e);
}
}).thenRunAsync(() -> {
if (OFFLINE_MODE) return;

// Delete cached images that are no longer in the gallery
try {
for (String id : cachedImageIDs) {
Files.deleteIfExists(getCachedImagePath(id));
}
} catch (IOException e) {
BetterLoadingScreen.log.error("Error while deleting unused cached imgur images", e);
}
});
}

private void loadAnyImageFromDisk(List<String> cachedImageIDs, Consumer<ResourceLocation> textureLocationConsumer) {
if (cachedImageIDs.isEmpty()) return;

String imageID = cachedImageIDs.get(ThreadLocalRandom.current().nextInt(cachedImageIDs.size()));
try {
readAndCacheImageFromDisk(imageID);
} catch (IOException e) {
BetterLoadingScreen.log.error("Error while loading first cached imgur image", e);
return;
}

synchronized (textureLocationConsumer) {
textureLocationConsumer.accept(new ResourceLocation(IMGUR_CACHE_DIR, imageID));
}
}

private void readAndCacheImageFromStream(String imageID, InputStream imageStream, boolean saveToDisk)
throws IOException {
BufferedImage image = ImageIO.read(imageStream);
textureCache.put(imageID, new LateInitDynamicTexture(image, image.getWidth(), image.getHeight()));

if (saveToDisk && Files.notExists(getCachedImagePath(imageID))) writeImageToCache(imageID, image);
}

private void readAndCacheImageFromDisk(String imageID) throws IOException {
readAndCacheImageFromStream(
imageID,
new BufferedInputStream(Files.newInputStream(getCachedImagePath(imageID)), 1024 * 1024),
false);
}

private void writeImageToCache(String imageID, BufferedImage image) throws IOException {
ImageIO.write(
image,
"png",
new BufferedOutputStream(Files.newOutputStream(getCachedImagePath(imageID)), 1024 * 1024));
}

private static Path getCachedImagePath(String imageID) {
return Paths.get(IMGUR_CACHE_DIR).resolve(imageID + ".png");
}

private List<String> getCachedImageIDs() {
try (Stream<Path> cacheFolderStream = Files.list(Paths.get(IMGUR_CACHE_DIR))) {
return cacheFolderStream.map(path -> path.getFileName().toString().replace(".png", ""))
.collect(Collectors.toList());
} catch (IOException e) {
BetterLoadingScreen.log.error("Error while iterating imgur cache folder", e);
return null;
}
}
}
Loading

0 comments on commit 1181905

Please sign in to comment.