diff options
Diffstat (limited to 'common/src/main/java/dev/isxander/yacl')
4 files changed, 55 insertions, 17 deletions
diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java b/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java index 3b28a65..22eebc9 100644 --- a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java +++ b/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java @@ -28,12 +28,14 @@ public interface OptionDescription { Builder image(ResourceLocation image, int width, int height); Builder image(Path path, ResourceLocation uniqueLocation); + Builder webpImage(ResourceLocation image); + Builder webpImage(Path path, ResourceLocation uniqueLocation); + + @Deprecated Builder gifImage(ResourceLocation image); + @Deprecated Builder gifImage(Path path, ResourceLocation uniqueLocation); - Builder webpImage(ResourceLocation image, int frameDelayMS); - Builder webpImage(Path path, ResourceLocation uniqueLocation, int frameDelayMS); - OptionDescription build(); } } diff --git a/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java b/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java index 8ea8ba3..1376a27 100644 --- a/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java +++ b/common/src/main/java/dev/isxander/yacl/gui/ImageRenderer.java @@ -149,11 +149,11 @@ public interface ImageRenderer extends AutoCloseable { return createGIF(resource.open(), textureLocation); } - public static AnimatedNativeImageBacked createWEBPFromTexture(ResourceLocation textureLocation, int frameDelayMS) throws IOException { + public static AnimatedNativeImageBacked createWEBPFromTexture(ResourceLocation textureLocation) throws IOException { ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); Resource resource = resourceManager.getResource(textureLocation).orElseThrow(); - return createWEBP(resource.open(), textureLocation, frameDelayMS); + return createWEBP(resource.open(), textureLocation); } public static AnimatedNativeImageBacked createGIF(InputStream is, ResourceLocation uniqueLocation) { @@ -173,10 +173,30 @@ public interface ImageRenderer extends AutoCloseable { } } - public static AnimatedNativeImageBacked createWEBP(InputStream is, ResourceLocation uniqueLocation, int frameDelayMS) { + public static AnimatedNativeImageBacked createWEBP(InputStream is, ResourceLocation uniqueLocation) { try (is) { - ImageReader reader = ImageIO.getImageReadersBySuffix("webp").next(); + ImageReader reader = new WebPImageReaderSpi().createReaderInstance(); reader.setInput(ImageIO.createImageInputStream(is)); + + // WebP reader does not expose frame delay, prepare for reflection hell + int frameDelayMS; + reader.getNumImages(true); // Force reading of all frames + try { + Class<?> webpReaderClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.WebPImageReader"); + Field framesField = webpReaderClass.getDeclaredField("frames"); + framesField.setAccessible(true); + List<?> frames = (List<?>) framesField.get(reader); + Object firstFrame = frames.get(0); + + Class<?> animationFrameClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.AnimationFrame"); + Field durationField = animationFrameClass.getDeclaredField("duration"); + durationField.setAccessible(true); + frameDelayMS = (int) durationField.get(firstFrame); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + // that was fun + return createFromImageReader(reader, frameDelayMS, uniqueLocation); } catch (IOException e) { throw new RuntimeException(e); diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java index f57a410..c866b43 100644 --- a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java +++ b/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java @@ -38,7 +38,7 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip Validate.isTrue(width > 0, "Width must be greater than 0!"); Validate.isTrue(height > 0, "Height must be greater than 0!"); - this.image = CompletableFuture.completedFuture(Optional.of(new ImageRenderer.TextureBacked(image, width, height))); + this.image = ImageRenderer.getOrMakeSync(image, () -> Optional.of(new ImageRenderer.TextureBacked(image, width, height))); imageUnset = false; return this; } @@ -46,7 +46,7 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip @Override public Builder image(Path path, ResourceLocation uniqueLocation) { Validate.isTrue(imageUnset, "Image already set!"); - this.image = CompletableFuture.supplyAsync(() -> ImageRenderer.NativeImageBacked.createFromPath(path, uniqueLocation)); + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> ImageRenderer.NativeImageBacked.createFromPath(path, uniqueLocation)); imageUnset = false; return this; } @@ -54,7 +54,7 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip @Override public Builder gifImage(ResourceLocation image) { Validate.isTrue(imageUnset, "Image already set!"); - this.image = CompletableFuture.supplyAsync(() -> { + this.image = ImageRenderer.getOrMakeAsync(image, () -> { try { return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIFFromTexture(image)); } catch (IOException e) { @@ -69,7 +69,7 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip @Override public Builder gifImage(Path path, ResourceLocation uniqueLocation) { Validate.isTrue(imageUnset, "Image already set!"); - this.image = CompletableFuture.supplyAsync(() -> { + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { try { return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createGIF(new FileInputStream(path.toFile()), uniqueLocation)); } catch (IOException e) { @@ -82,11 +82,11 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip } @Override - public Builder webpImage(ResourceLocation image, int frameDelayMS) { + public Builder webpImage(ResourceLocation image) { Validate.isTrue(imageUnset, "Image already set!"); - this.image = CompletableFuture.supplyAsync(() -> { + this.image = ImageRenderer.getOrMakeAsync(image, () -> { try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBPFromTexture(image, frameDelayMS)); + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBPFromTexture(image)); } catch (IOException e) { e.printStackTrace(); return Optional.empty(); @@ -97,11 +97,11 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip } @Override - public Builder webpImage(Path path, ResourceLocation uniqueLocation, int frameDelayMS) { + public Builder webpImage(Path path, ResourceLocation uniqueLocation) { Validate.isTrue(imageUnset, "Image already set!"); - this.image = CompletableFuture.supplyAsync(() -> { + this.image = ImageRenderer.getOrMakeAsync(uniqueLocation, () -> { try { - return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBP(new FileInputStream(path.toFile()), uniqueLocation, frameDelayMS)); + return Optional.of(ImageRenderer.AnimatedNativeImageBacked.createWEBP(new FileInputStream(path.toFile()), uniqueLocation)); } catch (IOException e) { e.printStackTrace(); return Optional.empty(); diff --git a/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java b/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java new file mode 100644 index 0000000..c33eed7 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/mixin/MinecraftMixin.java @@ -0,0 +1,16 @@ +package dev.isxander.yacl.mixin; + +import dev.isxander.yacl.gui.ImageRenderer; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + @Inject(method = "close", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/telemetry/ClientTelemetryManager;close()V")) + private void closeImages(CallbackInfo ci) { + ImageRenderer.closeAll(); + } +} |