Skip to content

Commit

Permalink
gif support
Browse files Browse the repository at this point in the history
  • Loading branch information
vgskye committed Dec 9, 2024
1 parent d07e4b8 commit 378b812
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 10 deletions.
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ subprojects {
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
maven {
url "https://sg.storage.bunnycdn.com/skyeven"
credentials(HttpHeaderCredentials) {
name = "AccessKey"
value = property("skyevenToken")
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
}
}
108 changes: 108 additions & 0 deletions common/src/main/java/vg/skye/AnimatedTextureSprite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package vg.skye;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.samsthenerd.inline.utils.Spritelike;
import com.samsthenerd.inline.utils.TextureSprite;
import net.minecraft.resources.ResourceLocation;

import java.util.Arrays;
import java.util.List;

public class AnimatedTextureSprite extends Spritelike {
private final ResourceLocation id;
private final int frameHeight;
private final int textWidth;
private final int textHeight;
private final int[] delays;
private final int totalLength;

public AnimatedTextureSprite(ResourceLocation id, int textWidth, int textHeight, int frameHeight, List<Integer> delays){
this(id, textWidth, textHeight, frameHeight, delays.stream().mapToInt(Integer::intValue).toArray());
}

public AnimatedTextureSprite(ResourceLocation id, int textWidth, int textHeight, int frameHeight, int[] delays){
this.id = id;
this.textWidth = textWidth;
this.textHeight = textHeight;
this.frameHeight = frameHeight;
this.delays = delays;
int sum = 0;
for (int delay : delays) {
sum += delay;
}
totalLength = sum;
}

@Override
public SpritelikeType getType(){
return AnimatedTextureSpriteType.INSTANCE;
}

public ResourceLocation getTextureId(){
return id;
}

public float getMinU(){
return 0;
}
public float getMinV(){
long currentFrame = (System.nanoTime() / 1000000L) % totalLength;
int delayAcc = 0;
for (int i = 0; i < delays.length; i++) {
delayAcc += delays[i];
if (delayAcc >= currentFrame) {
int topOff = frameHeight * i;
return (float) topOff / textHeight;
}
}
return 0;
}
public float getMaxU(){
return 1;
}
public float getMaxV(){
long currentFrame = (System.nanoTime() / 1000000L) % totalLength;
int delayAcc = 0;
for (int i = 0; i < delays.length; i++) {
delayAcc += delays[i];
if (delayAcc >= currentFrame) {
int botOff = frameHeight * (i + 1);
return (float) botOff / textHeight;
}
}
return 1;
}

public int getTextureWidth(){
return textWidth;
}
public int getTextureHeight(){
return textHeight;
}
public int getFrameHeight() {
return frameHeight;
}
public List<Integer> getDelays() {
return Arrays.stream(delays).boxed().toList();
}

public static class AnimatedTextureSpriteType implements SpritelikeType{
public static final TextureSprite.TextureSpriteType INSTANCE = new TextureSprite.TextureSpriteType();
private static final Codec<AnimatedTextureSprite> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("id").forGetter(AnimatedTextureSprite::getTextureId),
Codec.INT.optionalFieldOf("textWidth", 16).forGetter(AnimatedTextureSprite::getTextureWidth),
Codec.INT.optionalFieldOf("textHeight", 16).forGetter(AnimatedTextureSprite::getTextureHeight),
Codec.INT.optionalFieldOf("frameHeight", 16).forGetter(AnimatedTextureSprite::getFrameHeight),
Codec.list(Codec.INT).optionalFieldOf("delays", List.of()).forGetter(AnimatedTextureSprite::getDelays)
).apply(instance, AnimatedTextureSprite::new));

public Codec<AnimatedTextureSprite> getCodec(){
return CODEC;
}

public String getId(){
return "animated_texture";
}
}
}
4 changes: 1 addition & 3 deletions common/src/main/java/vg/skye/EmojiMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.samsthenerd.inline.api.matching.ContinuousMatcher;
import com.samsthenerd.inline.api.matching.MatchContext;
import com.samsthenerd.inline.api.matching.MatcherInfo;
import com.samsthenerd.inline.utils.TextureSprite;
import net.minecraft.resources.ResourceLocation;

import java.util.regex.MatchResult;
Expand All @@ -25,8 +24,7 @@ public ContinuousMatchResult match(String input, MatchContext matchContext) {
String emoji = mr.group(1);
var tex = EmojilessClient.emojis.get(emoji);
if (tex != null) {
var sprite = new TextureSprite(tex.loc(), 0, 0, 1, 1, tex.w(), tex.h());
result.addMatch(mr.start(), mr.end(), new SpriteInlineData(sprite));
result.addMatch(mr.start(), mr.end(), new SpriteInlineData(tex));
}
}
return result;
Expand Down
2 changes: 2 additions & 0 deletions common/src/main/java/vg/skye/Emojiless.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package vg.skye;

import com.samsthenerd.inline.utils.Spritelike;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -8,5 +9,6 @@ public final class Emojiless {
public static final Logger LOGGER = LoggerFactory.getLogger("emojiless");

public static void init() {
Spritelike.registerType(AnimatedTextureSprite.AnimatedTextureSpriteType.INSTANCE);
}
}
82 changes: 76 additions & 6 deletions common/src/main/java/vg/skye/EmojilessClient.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,112 @@
package vg.skye;

import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.platform.TextureUtil;
import com.samsthenerd.inline.api.client.InlineClientAPI;
import com.samsthenerd.inline.utils.Spritelike;
import com.samsthenerd.inline.utils.TextureSprite;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.lwjgl.PointerBuffer;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import vg.skye.mixin.NativeImageAccessor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;

public final class EmojilessClient {
public record EmojiTexture(ResourceLocation loc, int w, int h) {}

public static Map<String, EmojiTexture> emojis = new HashMap<>();
public static Map<String, Spritelike> emojis = new HashMap<>();

public static void init() {
InlineClientAPI.INSTANCE.addMatcher(EmojiMatcher.INSTANCE);
}

public static AnimatedTextureSprite readGif(ResourceLocation loc, ByteBuffer buf) throws IOException {
NativeImage image;
try (MemoryStack memoryStack = MemoryStack.stackPush()) {
PointerBuffer delayBuf = memoryStack.mallocPointer(1);
IntBuffer wBuf = memoryStack.mallocInt(1);
IntBuffer hBuf = memoryStack.mallocInt(1);
IntBuffer framesBuf = memoryStack.mallocInt(1);
IntBuffer channelsBuf = memoryStack.mallocInt(1);
ByteBuffer imageBuf = STBImage.stbi_load_gif_from_memory(
buf,
delayBuf,
wBuf,
hBuf,
framesBuf,
channelsBuf,
4
);
if (imageBuf == null) {
throw new IOException("Could not load image: " + STBImage.stbi_failure_reason());
}

image = new NativeImage(
wBuf.get(0),
hBuf.get(0) * framesBuf.get(0),
true
);
MemoryUtil.memCopy(
MemoryUtil.memAddress(imageBuf),
((NativeImageAccessor) (Object) image).getPixels(),
(long) wBuf.get(0) * hBuf.get(0) * framesBuf.get(0) * 4
);

var tex = new DynamicTexture(image);
Minecraft.getInstance().execute(() -> {
Minecraft.getInstance().getTextureManager().register(loc, tex);
});
var delays = new int[framesBuf.get(0)];
delayBuf.getIntBuffer(framesBuf.get(0)).get(delays);
return new AnimatedTextureSprite(
loc,
image.getWidth(),
hBuf.get(0) * framesBuf.get(0),
hBuf.get(0),
delays
);
}
}

public static void reloadCallback(ResourceManager manager) {
emojis.clear();
var definitions = manager.listResources("emojis", path -> path.getPath().endsWith(".png"));
for (var entry : definitions.entrySet()) {
for (var entry : manager.listResources("emojis", path -> path.getPath().endsWith(".png")).entrySet()) {
try {
var segments = entry.getKey().getPath().split("/");
var filename = segments[segments.length - 1].split(".png")[0];
var stream = entry.getValue().open();
var img = NativeImage.read(stream);
var tex = new DynamicTexture(img);
var loc = new ResourceLocation("emojiless", "emoji_textures/" + entry.getKey().getNamespace() + "/" + entry.getKey().getPath());
emojis.put(filename, new EmojiTexture(loc, img.getWidth(), img.getHeight()));
emojis.put(filename, new TextureSprite(loc, 0, 0, 1, 1, img.getWidth(), img.getHeight()));
Minecraft.getInstance().execute(() -> {
Minecraft.getInstance().getTextureManager().register(loc, tex);
});
} catch (Exception e) {
Emojiless.LOGGER.warn("Failed to load emoji", e);
}
}
for (var entry : manager.listResources("emojis", path -> path.getPath().endsWith(".gif")).entrySet()) {
try {
var segments = entry.getKey().getPath().split("/");
var filename = segments[segments.length - 1].split(".gif")[0];
var stream = entry.getValue().open();
var buf = TextureUtil.readResource(stream);
buf.rewind();
var loc = new ResourceLocation("emojiless", "emoji_textures/" + entry.getKey().getNamespace() + "/" + entry.getKey().getPath());
var img = readGif(loc, buf);
emojis.put(filename, img);
} catch (Exception e) {
Emojiless.LOGGER.warn("Failed to load animated emoji", e);
}
}
}
}
11 changes: 11 additions & 0 deletions common/src/main/java/vg/skye/mixin/NativeImageAccessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package vg.skye.mixin;

import com.mojang.blaze3d.platform.NativeImage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(NativeImage.class)
public interface NativeImageAccessor {
@Accessor
long getPixels();
}
1 change: 1 addition & 0 deletions common/src/main/resources/emojiless.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_17",
"minVersion": "0.8",
"client": [
"NativeImageAccessor"
],
"mixins": [
],
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true

# Mod properties
mod_version = 1.0.0
mod_version = 1.1.0
maven_group = vg.skye
archives_name = emojiless
enabled_platforms = fabric,forge
Expand Down

0 comments on commit 378b812

Please sign in to comment.