Skip to content

Instantly share code, notes, and snippets.

@isXander
Last active August 10, 2023 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isXander/6f4010c6f32fef304ccb0ec429331bc3 to your computer and use it in GitHub Desktop.
Save isXander/6f4010c6f32fef304ccb0ec429331bc3 to your computer and use it in GitHub Desktop.
private static AnimatedNativeImageBacked createFromImageReader(ImageReader reader, AnimFrameProvider animationProvider, ResourceLocation uniqueLocation) throws Exception {
if (reader.isSeekForwardOnly()) {
throw new RuntimeException("Image reader is not seekable");
}
int frameCount = reader.getNumImages(true);
// Because this is being backed into a texture atlas, we need a maximum dimension
// so you can get the texture atlas size.
// Smaller frames are given black borders
int frameWidth = IntStream.range(0, frameCount).map(i -> {
try {
return reader.getWidth(i);
} catch (IOException e) {
throw new RuntimeException(e);
}
}).max().orElseThrow();
int frameHeight = IntStream.range(0, frameCount).map(i -> {
try {
return reader.getHeight(i);
} catch (IOException e) {
throw new RuntimeException(e);
}
}).max().orElseThrow();
// Packs the frames into an optimal 1:1 texture.
// OpenGL can only have texture axis with a max of 32768 pixels,
// and packing them to that length is not efficient, apparently.
double ratio = frameWidth / (double)frameHeight;
int cols = (int)Math.ceil(Math.sqrt(frameCount) / Math.sqrt(ratio));
int rows = (int)Math.ceil(frameCount / (double)cols);
NativeImage image = new NativeImage(NativeImage.Format.RGBA, frameWidth * cols, frameHeight * rows, true);
// // Fill whole atlas with black, as each frame may have different dimensions
// // that would cause borders of transparent pixels to appear around the frames
// for (int x = 0; x < frameWidth * cols; x++) {
// for (int y = 0; y < frameHeight * rows; y++) {
// image.setPixelRGBA(x, y, 0xFF000000);
// }
// }
BufferedImage bi = null;
Graphics2D graphics = null;
// each frame may have a different delay
double[] frameDelays = new double[frameCount];
for (int i = 0; i < frameCount; i++) {
AnimFrame frame = animationProvider.get(i);
if (frameCount > 1) // frame will be null if not animation
frameDelays[i] = frame.durationMS;
if (bi == null) {
// first frame...
bi = reader.read(i);
graphics = bi.createGraphics();
} else {
// WebP reader sometimes provides delta frames, (only the pixels that changed since the last frame)
// so instead of overwriting the image every frame, we draw delta frames on top of the previous frame
// to keep a complete image.
BufferedImage deltaFrame = reader.read(i);
graphics.drawImage(deltaFrame, frame.xOffset, frame.yOffset, null);
}
// Each frame may have different dimensions, so we need to center them.
int xOffset = (frameWidth - bi.getWidth()) / 2;
int yOffset = (frameHeight - bi.getHeight()) / 2;
for (int w = 0; w < bi.getWidth(); w++) {
for (int h = 0; h < bi.getHeight(); h++) {
int rgb = bi.getRGB(w, h);
int r = FastColor.ARGB32.red(rgb);
int g = FastColor.ARGB32.green(rgb);
int b = FastColor.ARGB32.blue(rgb);
int a = FastColor.ARGB32.alpha(rgb);
int col = i % cols;
int row = (int) Math.floor(i / (double)cols);
image.setPixelRGBA(
frameWidth * col + w + xOffset,
frameHeight * row + h + yOffset,
FastColor.ABGR32.color(a, b, g, r) // NativeImage uses ABGR for some reason
);
}
}
}
// gives the texture to GL for rendering
// usually, you create a native image with NativeImage.create, which sets the pixels and
// runs this function itself. In this case, we need to do it manually.
image.upload(0, 0, 0, false);
if (graphics != null)
graphics.dispose();
reader.dispose();
return new AnimatedNativeImageBacked(image, frameWidth, frameHeight, frameCount, frameDelays, cols, rows, uniqueLocation);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment