From aa8f0e4a7ef027a73b04559b56b1e72a616d0747 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 15:53:24 +0800 Subject: [PATCH 01/16] Create BgraPreCanvas --- .../apng/argb8888/BgraPreBitmapDirector.java | 111 ++++++++++++++++++ .../hmcl/ui/image/internal/BgraPreCanvas.java | 90 ++++++++++++++ .../hmcl/ui/image/internal/BgraPreFrame.java | 39 ++++++ 3 files changed, 240 insertions(+) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java new file mode 100644 index 0000000000..7137fca898 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -0,0 +1,111 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.image.apng.argb8888; + +import org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer; +import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; +import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl; +import org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader; +import org.jackhuang.hmcl.ui.image.apng.error.PngException; +import org.jackhuang.hmcl.ui.image.internal.BgraPreFrame; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Glavo + */ +public final class BgraPreBitmapDirector extends BasicArgb8888Director { + private PngHeader header; + private Argb8888Bitmap defaultImage; + private byte[] canvas; + + private PngFrameControl currentFrame = null; + private PngAnimationControl animationControl; + private List animationFrames; + + @Override + public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws PngException { + if (this.header != null) + throw new IllegalStateException("Png header has already been set"); + + this.header = header; + this.defaultImage = new Argb8888Bitmap(header.width, header.height); + this.scanlineProcessor = Argb8888Processors.from(header, buffer, defaultImage); + this.canvas = new byte[header.width * header.height * 4]; + } + + @Override + public boolean wantDefaultImage() { + return true; + } + + @Override + public boolean wantAnimationFrames() { + return true; + } + + @Override + public Argb8888ScanlineProcessor beforeDefaultImage() { + return scanlineProcessor; + } + + @Override + public void receiveDefaultImage(Argb8888Bitmap bitmap) { + // this.bitmapSequence.receiveDefaultImage(bitmap); + } + + @Override + public void receiveAnimationControl(PngAnimationControl control) { + this.animationControl = animationControl; + this.animationFrames = new ArrayList<>(animationControl.numFrames); + } + + @Override + public Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control) { + //throw new IllegalStateException("TODO up to here"); + //return null; + currentFrame = control; + + //System.out.println("Frame: "+control); + + return scanlineProcessor.cloneWithNewBitmap(header.adjustFor(control)); // TODO: is this going to be a problem? + } + + @Override + public void receiveFrameImage(Argb8888Bitmap bitmap) { + if (null == currentFrame) { + throw new IllegalStateException("Received a frame image with no frame control in place"); + } + if (null == animationFrames) { + throw new IllegalStateException("Received a frame image without animation control (or frame list?) in place"); + } + + + + // TODO + // animationFrames.add(new Argb8888BitmapSequence.Frame(currentFrame, bitmap)); + currentFrame = null; + } + + @Override + public Argb8888BitmapSequence getResult() { + return bitmapSequence; + } +} + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java new file mode 100644 index 0000000000..3ded971b02 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -0,0 +1,90 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.image.internal; + +import org.jackhuang.hmcl.util.ByteArray; + +public class BgraPreCanvas { + private final byte[] pixels; + private final int width; + private final int height; + + public BgraPreCanvas(byte[] pixels, int width, int height) { + if (pixels.length != 4 * width * height) + throw new IllegalArgumentException("Pixel array length missmatch"); + + this.pixels = pixels; + this.width = width; + this.height = height; + } + + public BgraPreCanvas(int width, int height) { + this.pixels = new byte[4 * width * height]; + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public void setArgb(int row, int col, int argb) { + if (row < 0 || row >= width || col < 0 || col >= height) { + throw new IndexOutOfBoundsException("row or col out of bounds"); + } + + int targetIndex = (row * width + col) * 4; + + int a = argb >> 24 & 0xff; + int r = argb >> 16 & 0xFF; + int g = argb >> 8 & 0xFF; + int b = argb & 0xFF; + + if (a == 0) { + r = g = b = 0; + } else if (a < 255) { + r = (r * a + 127) / 0xff; + g = (g * a + 127) / 0xff; + b = (b * a + 127) / 0xff; + } + + //noinspection PointlessArithmeticExpression + pixels[targetIndex + 0] = (byte) b; + pixels[targetIndex + 1] = (byte) g; + pixels[targetIndex + 2] = (byte) r; + pixels[targetIndex + 3] = (byte) a; + } + + public void setArgbPre(int row, int col, int argbPre) { + if (row < 0 || row >= width || col < 0 || col >= height) { + throw new IndexOutOfBoundsException("row or col out of bounds"); + } + + int targetIndex = (row * width + col) * 4; + ByteArray.setIntLE(pixels, targetIndex, argbPre); + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java new file mode 100644 index 0000000000..9ac3bdff0c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java @@ -0,0 +1,39 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.image.internal; + +public final class BgraPreFrame { + final byte[] pixels; + final int xOffset; + final int yOffset; + final int width; + final int height; + final long duration; + + public BgraPreFrame(byte[] pixels, int xOffset, int yOffset, int width, int height, long duration) { + this.duration = duration; + if (pixels.length != width * height * 4) + throw new IllegalArgumentException("Invalid pixel array length"); + + this.pixels = pixels; + this.xOffset = xOffset; + this.yOffset = yOffset; + this.width = width; + this.height = height; + } +} From 88db57b0fab6ca21f8fb7a602d89022592ea1edf Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 16:10:06 +0800 Subject: [PATCH 02/16] update --- .../hmcl/ui/image/internal/BgraPreCanvas.java | 23 ++++++++++++++++--- .../hmcl/ui/image/internal/BgraPreFrame.java | 15 ++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 3ded971b02..318d47c611 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -19,10 +19,12 @@ import org.jackhuang.hmcl.util.ByteArray; +import java.util.Objects; + public class BgraPreCanvas { - private final byte[] pixels; - private final int width; - private final int height; + protected final byte[] pixels; + protected final int width; + protected final int height; public BgraPreCanvas(byte[] pixels, int width, int height) { if (pixels.length != 4 * width * height) @@ -47,6 +49,21 @@ public int getHeight() { return height; } + public byte[] getPixels(int x, int y, int w, int h) { + Objects.checkFromIndexSize(x, w, width); + Objects.checkFromIndexSize(y, h, height); + + final int bytesForRow = 4 * w; + + byte[] pixels = new byte[4 * w * h]; + for (int row = 0; row < h; row++) { + int sourceOffset = 4 * ((y + row) * width + x); + int targetOffset = 4 * (row * w); + System.arraycopy(this.pixels, sourceOffset, pixels, targetOffset, bytesForRow); + } + return pixels; + } + public void setArgb(int row, int col, int argb) { if (row < 0 || row >= width || col < 0 || col >= height) { throw new IndexOutOfBoundsException("row or col out of bounds"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java index 9ac3bdff0c..8e211a04cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java @@ -17,23 +17,16 @@ */ package org.jackhuang.hmcl.ui.image.internal; -public final class BgraPreFrame { - final byte[] pixels; +public class BgraPreFrame extends BgraPreCanvas { final int xOffset; final int yOffset; - final int width; - final int height; final long duration; - public BgraPreFrame(byte[] pixels, int xOffset, int yOffset, int width, int height, long duration) { - this.duration = duration; - if (pixels.length != width * height * 4) - throw new IllegalArgumentException("Invalid pixel array length"); + public BgraPreFrame(byte[] pixels, int width, int height, int xOffset, int yOffset, long duration) { + super(pixels, width, height); - this.pixels = pixels; + this.duration = duration; this.xOffset = xOffset; this.yOffset = yOffset; - this.width = width; - this.height = height; } } From 8b3605dd4c4f5fb28e4e6ab5fce12762e3d0475f Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 20:08:44 +0800 Subject: [PATCH 03/16] update BgraPreCanvas --- .../hmcl/ui/image/AnimationImage.java | 7 +- .../apng/argb8888/BgraPreBitmapDirector.java | 41 ++++-- .../ui/image/internal/AnimationImageImpl.java | 2 +- .../hmcl/ui/image/internal/BgraPreCanvas.java | 122 ++++++++++++++---- .../hmcl/ui/image/internal/BgraPreFrame.java | 8 ++ 5 files changed, 141 insertions(+), 39 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java index e22deccf7f..a3905a5a33 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java @@ -17,8 +17,13 @@ */ package org.jackhuang.hmcl.ui.image; +import javafx.scene.image.WritableImage; + /** * @author Glavo */ -public interface AnimationImage { +public abstract class AnimationImage extends WritableImage { + public AnimationImage(int width, int height) { + super(width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index 7137fca898..3bb645924d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -17,11 +17,14 @@ */ package org.jackhuang.hmcl.ui.image.apng.argb8888; +import javafx.scene.image.Image; import org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer; import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl; import org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader; import org.jackhuang.hmcl.ui.image.apng.error.PngException; +import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException; +import org.jackhuang.hmcl.ui.image.internal.BgraPreCanvas; import org.jackhuang.hmcl.ui.image.internal.BgraPreFrame; import java.util.ArrayList; @@ -30,10 +33,10 @@ /** * @author Glavo */ -public final class BgraPreBitmapDirector extends BasicArgb8888Director { +public final class BgraPreBitmapDirector extends BasicArgb8888Director { private PngHeader header; private Argb8888Bitmap defaultImage; - private byte[] canvas; + private BgraPreCanvas canvas; private PngFrameControl currentFrame = null; private PngAnimationControl animationControl; @@ -47,7 +50,7 @@ public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws Png this.header = header; this.defaultImage = new Argb8888Bitmap(header.width, header.height); this.scanlineProcessor = Argb8888Processors.from(header, buffer, defaultImage); - this.canvas = new byte[header.width * header.height * 4]; + this.canvas = new BgraPreCanvas(header.width, header.height); } @Override @@ -89,23 +92,41 @@ public Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control) { @Override public void receiveFrameImage(Argb8888Bitmap bitmap) { - if (null == currentFrame) { + if (currentFrame == null) throw new IllegalStateException("Received a frame image with no frame control in place"); - } - if (null == animationFrames) { + if (animationFrames == null) throw new IllegalStateException("Received a frame image without animation control (or frame list?) in place"); + + final long duration; + if (currentFrame.delayNumerator == 0) { + duration = 10; + } else { + int durationsMills = 1000 * currentFrame.delayNumerator; + if (currentFrame.delayDenominator == 0) + durationsMills /= 100; + else + durationsMills /= currentFrame.delayDenominator; + duration = durationsMills; } + final var frame = new BgraPreFrame( + canvas, + currentFrame.width, currentFrame.height, currentFrame.xOffset, currentFrame.yOffset, + duration + ); + + if (currentFrame.blendOp == 0) { + } else if (currentFrame.blendOp == 1) { // APNG_BLEND_OP_OVER - Alpha blending + + } - // TODO - // animationFrames.add(new Argb8888BitmapSequence.Frame(currentFrame, bitmap)); currentFrame = null; } @Override - public Argb8888BitmapSequence getResult() { - return bitmapSequence; + public Image getResult() { + return null; // TODO } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java index 260c59d0ab..f9560cb706 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java @@ -32,7 +32,7 @@ /** * @author Glavo */ -public final class AnimationImageImpl extends WritableImage implements AnimationImage { +public final class AnimationImageImpl extends AnimationImage { private Animation animation; private final int[][] frames; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 318d47c611..b264fdd170 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -22,6 +22,24 @@ import java.util.Objects; public class BgraPreCanvas { + protected static int argbToPre(int argb) { + int a = argb >> 24 & 0xff; + int r = argb >> 16 & 0xFF; + int g = argb >> 8 & 0xFF; + int b = argb & 0xFF; + + if (a == 0) { + r = g = b = 0; + } else if (a < 255) { + r = (r * a + 127) / 0xff; + g = (g * a + 127) / 0xff; + b = (b * a + 127) / 0xff; + } + + return (a << 24) | (r << 16) | (g << 8) | b; + } + + protected final byte[] pixels; protected final int width; protected final int height; @@ -64,40 +82,90 @@ public byte[] getPixels(int x, int y, int w, int h) { return pixels; } - public void setArgb(int row, int col, int argb) { - if (row < 0 || row >= width || col < 0 || col >= height) { - throw new IndexOutOfBoundsException("row or col out of bounds"); - } + public void setArgb(int x, int y, int argb) { + Objects.checkIndex(x, width); + Objects.checkIndex(y, height); - int targetIndex = (row * width + col) * 4; - - int a = argb >> 24 & 0xff; - int r = argb >> 16 & 0xFF; - int g = argb >> 8 & 0xFF; - int b = argb & 0xFF; + int targetIndex = (y * width + x) * 4; + ByteArray.setIntLE(pixels, targetIndex, argbToPre(argb)); + } - if (a == 0) { - r = g = b = 0; - } else if (a < 255) { - r = (r * a + 127) / 0xff; - g = (g * a + 127) / 0xff; - b = (b * a + 127) / 0xff; - } + public void setArgbPre(int x, int y, int argbPre) { + Objects.checkIndex(x, width); + Objects.checkIndex(y, height); - //noinspection PointlessArithmeticExpression - pixels[targetIndex + 0] = (byte) b; - pixels[targetIndex + 1] = (byte) g; - pixels[targetIndex + 2] = (byte) r; - pixels[targetIndex + 3] = (byte) a; + int targetIndex = (y * width + x) * 4; + ByteArray.setIntLE(pixels, targetIndex, argbPre); } - public void setArgbPre(int row, int col, int argbPre) { - if (row < 0 || row >= width || col < 0 || col >= height) { - throw new IndexOutOfBoundsException("row or col out of bounds"); + public void setArgb(int x, int y, int w, int h, + int[] buffer, int offset, int scanlineStride) { + Objects.checkFromIndexSize(x, w, width); + Objects.checkFromIndexSize(y, h, width); + + for (int row = 0; row < h; row++) { + for (int col = 0; col < w; col++) { + int sourceIndex = offset + (row * scanlineStride + col); + int targetIndex = 4 * ((row + y) * width + x + col); + ByteArray.setIntLE(pixels, targetIndex, argbToPre(buffer[sourceIndex])); + } } + } - int targetIndex = (row * width + col) * 4; - ByteArray.setIntLE(pixels, targetIndex, argbPre); + public void blendingWithArgb(int x, int y, int w, int h, + int[] buffer, int offset, int scanlineStride) { + Objects.checkFromIndexSize(x, w, width); + Objects.checkFromIndexSize(y, h, width); + + for (int row = 0; row < h; row++) { + for (int col = 0; col < w; col++) { + int sourceIndex = offset + (row * scanlineStride + col); + + int resultArgbPre; + int srcArgb = buffer[sourceIndex]; + + int srcA = (srcArgb >> 24) & 0xff; + if (srcA == 0) { + continue; + } + + int targetIndex = 4 * ((row + y) * width + x + col); + if (srcA == 255) { + resultArgbPre = argbToPre(srcArgb); + } else { + int srcArgbPre = argbToPre(srcArgb); + + int srcRPre = (srcArgbPre >> 16) & 0xff; + int srcGPre = (srcArgbPre >> 8) & 0xff; + int srcBPre = (srcArgbPre) & 0xff; + + int dstArgbPre = ByteArray.getIntLE(pixels, targetIndex); + + int dstA = (dstArgbPre >> 24) & 0xff; + int dstRPre = (dstArgbPre >> 16) & 0xff; + int dstGPre = (dstArgbPre >> 8) & 0xff; + int dstBPre = (dstArgbPre) & 0xff; + + int invSrcA = 255 - srcA; + + int outAlpha = srcA + (dstA * invSrcA + 127) / 255; + + if (outAlpha == 0) { + resultArgbPre = 0; + } else { + int outR, outG, outB; + + outR = srcRPre + (dstRPre * invSrcA + 127) / 255; + outG = srcGPre + (dstGPre * invSrcA + 127) / 255; + outB = srcBPre + (dstBPre * invSrcA + 127) / 255; + + resultArgbPre = (outAlpha << 24) | (outR << 16) | (outG << 8) | outB; + } + } + + ByteArray.setIntLE(pixels, targetIndex, resultArgbPre); + } + } } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java index 8e211a04cb..52d2f761ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java @@ -29,4 +29,12 @@ public BgraPreFrame(byte[] pixels, int width, int height, int xOffset, int yOffs this.xOffset = xOffset; this.yOffset = yOffset; } + + public BgraPreFrame(int width, int height, int xOffset, int yOffset, long duration) { + this(new byte[4 * width * height], width, height, xOffset, yOffset, duration); + } + + public BgraPreFrame(BgraPreCanvas canvas, int width, int height, int xOffset, int yOffset, long duration) { + this(canvas.getPixels(xOffset, yOffset, width, height), width, height, xOffset, yOffset, duration); + } } From e03d855f213caa0c74157b65205f5ccc30a4343f Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 20:46:16 +0800 Subject: [PATCH 04/16] update --- .../hmcl/ui/image/AnimationImage.java | 73 ++++++++++++++++++- .../image/apng/argb8888/Argb8888Director.java | 3 +- .../apng/argb8888/BgraPreBitmapDirector.java | 23 +++++- .../ui/image/internal/AnimationImageImpl.java | 73 +++---------------- .../hmcl/ui/image/internal/BgraPreCanvas.java | 26 +++++++ .../hmcl/ui/image/internal/BgraPreFrame.java | 9 ++- 6 files changed, 139 insertions(+), 68 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java index a3905a5a33..84fb78034f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationImage.java @@ -17,13 +17,84 @@ */ package org.jackhuang.hmcl.ui.image; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.value.WritableValue; import javafx.scene.image.WritableImage; +import javafx.util.Duration; + +import java.lang.ref.WeakReference; /** * @author Glavo */ public abstract class AnimationImage extends WritableImage { - public AnimationImage(int width, int height) { + private Animation animation; + protected final int cycleCount; + protected final int width; + protected final int height; + + public AnimationImage(int width, int height, int cycleCount) { super(width, height); + this.cycleCount = cycleCount; + this.width = width; + this.height = height; + } + + public void play() { + if (animation == null) { + animation = new Animation(this); + animation.timeline.play(); + } + } + + public abstract int getFramesCount(); + + public abstract long getDuration(int index); + + protected abstract void updateImage(int frameIndex); + + private static final class Animation implements WritableValue { + private final Timeline timeline = new Timeline(); + private final WeakReference imageRef; + + private Integer value; + + private Animation(AnimationImage image) { + this.imageRef = new WeakReference<>(image); + timeline.setCycleCount(image.cycleCount); + + long duration = 0; + + int frames = image.getFramesCount(); + for (int i = 0; i < frames; ++i) { + timeline.getKeyFrames().add( + new KeyFrame(Duration.millis(duration), + new KeyValue(this, i, Interpolator.DISCRETE))); + + duration = duration + image.getDuration(i); + } + + timeline.getKeyFrames().add(new KeyFrame(Duration.millis(duration))); + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public void setValue(Integer value) { + this.value = value; + + AnimationImage image = imageRef.get(); + if (image == null) { + timeline.stop(); + return; + } + image.updateImage(value); + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Director.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Director.java index a7308f4106..59f0e42834 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Director.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/Argb8888Director.java @@ -3,6 +3,7 @@ package org.jackhuang.hmcl.ui.image.apng.argb8888; +import org.jackhuang.hmcl.ui.image.apng.Png; import org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer; import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl; @@ -41,7 +42,7 @@ public interface Argb8888Director { Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control); - void receiveFrameImage(Argb8888Bitmap bitmap); + void receiveFrameImage(Argb8888Bitmap bitmap) throws PngException; ResultT getResult(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index 3bb645924d..4504a5813b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.ui.image.apng.chunks.PngHeader; import org.jackhuang.hmcl.ui.image.apng.error.PngException; import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException; +import org.jackhuang.hmcl.ui.image.internal.BgraPreAnimationImage; import org.jackhuang.hmcl.ui.image.internal.BgraPreCanvas; import org.jackhuang.hmcl.ui.image.internal.BgraPreFrame; @@ -91,7 +92,7 @@ public Argb8888ScanlineProcessor receiveFrameControl(PngFrameControl control) { } @Override - public void receiveFrameImage(Argb8888Bitmap bitmap) { + public void receiveFrameImage(Argb8888Bitmap bitmap) throws PngException { if (currentFrame == null) throw new IllegalStateException("Received a frame image with no frame control in place"); if (animationFrames == null) @@ -116,9 +117,27 @@ public void receiveFrameImage(Argb8888Bitmap bitmap) { ); if (currentFrame.blendOp == 0) { - + frame.setArgb(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, + bitmap.array, 0, currentFrame.width); } else if (currentFrame.blendOp == 1) { // APNG_BLEND_OP_OVER - Alpha blending + frame.blendingWithArgb(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, + bitmap.array, 0, currentFrame.width); + } else { + throw new PngIntegrityException("Unsupported blendOp " + currentFrame.blendOp); + } + switch (currentFrame.disposeOp) { + case 0: // APNG_DISPOST_OP_NONE + canvas.setBgraPre(currentFrame.xOffset, currentFrame.yOffset, frame); + break; + case 1: // APNG_DISPOSE_OP_BACKGROUND + canvas.clear(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height); + break; + case 2: // APNG_DISPOSE_OP_PREVIOUS + // Do nothing, keep the previous frame. + break; + default: + throw new PngIntegrityException("Unsupported disposeOp " + currentFrame.disposeOp); } currentFrame = null; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java index f9560cb706..a8791fdfe4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/AnimationImageImpl.java @@ -17,51 +17,39 @@ */ package org.jackhuang.hmcl.ui.image.internal; -import javafx.animation.Interpolator; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.value.WritableValue; import javafx.scene.image.PixelFormat; -import javafx.scene.image.WritableImage; -import javafx.util.Duration; import org.jackhuang.hmcl.ui.image.AnimationImage; -import java.lang.ref.WeakReference; - /** * @author Glavo */ public final class AnimationImageImpl extends AnimationImage { - - private Animation animation; private final int[][] frames; private final int[] durations; - private final int cycleCount; public AnimationImageImpl(int width, int height, int[][] frames, int[] durations, int cycleCount) { - super(width, height); - + super(width, height, cycleCount); if (frames.length != durations.length) { throw new IllegalArgumentException("frames.length != durations.length"); } this.frames = frames; this.durations = durations; - this.cycleCount = cycleCount; - play(); } - public void play() { - if (animation == null) { - animation = new Animation(this); - animation.timeline.play(); - } + @Override + public int getFramesCount() { + return frames.length; } - private void updateImage(int frameIndex) { + @Override + public long getDuration(int index) { + return durations[index]; + } + + protected void updateImage(int frameIndex) { final int width = (int) getWidth(); final int height = (int) getHeight(); final int[] frame = frames[frameIndex]; @@ -71,45 +59,4 @@ private void updateImage(int frameIndex) { frame, 0, width ); } - - private static final class Animation implements WritableValue { - private final Timeline timeline = new Timeline(); - private final WeakReference imageRef; - - private Integer value; - - private Animation(AnimationImageImpl image) { - this.imageRef = new WeakReference<>(image); - timeline.setCycleCount(image.cycleCount); - - int duration = 0; - - for (int i = 0; i < image.frames.length; ++i) { - timeline.getKeyFrames().add( - new KeyFrame(Duration.millis(duration), - new KeyValue(this, i, Interpolator.DISCRETE))); - - duration = duration + image.durations[i]; - } - - timeline.getKeyFrames().add(new KeyFrame(Duration.millis(duration))); - } - - @Override - public Integer getValue() { - return value; - } - - @Override - public void setValue(Integer value) { - this.value = value; - - AnimationImageImpl image = imageRef.get(); - if (image == null) { - timeline.stop(); - return; - } - image.updateImage(value); - } - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index b264fdd170..0bbe546881 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -19,6 +19,7 @@ import org.jackhuang.hmcl.util.ByteArray; +import java.util.Arrays; import java.util.Objects; public class BgraPreCanvas { @@ -82,6 +83,31 @@ public byte[] getPixels(int x, int y, int w, int h) { return pixels; } + public void clear(int x, int y, int w, int h) { + Objects.checkFromIndexSize(x, w, width); + Objects.checkFromIndexSize(y, h, width); + + final int bytesForRow = 4 * w; + + for (int row = 0; row < h; row++) { + int targetIndex = 4 * ((y + row) * width + x); + Arrays.fill(pixels, targetIndex, targetIndex + bytesForRow, (byte) 0); + } + } + + public void setBgraPre(int x, int y, BgraPreCanvas canvas) { + Objects.checkFromIndexSize(x, canvas.width, width); + Objects.checkFromIndexSize(y, canvas.height, width); + + for (int row = 0; row < canvas.height; row++) { + for (int col = 0; col < canvas.width; col++) { + int sourceIndex = 4 * (row * canvas.width + col); + int targetIndex = 4 * ((row + y) * width + x + col); + ByteArray.setIntLE(pixels, targetIndex, ByteArray.getIntLE(canvas.pixels, sourceIndex)); + } + } + } + public void setArgb(int x, int y, int argb) { Objects.checkIndex(x, width); Objects.checkIndex(y, height); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java index 52d2f761ca..82b342d588 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreFrame.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.ui.image.internal; -public class BgraPreFrame extends BgraPreCanvas { +import org.jackhuang.hmcl.ui.image.AnimationFrame; + +public class BgraPreFrame extends BgraPreCanvas implements AnimationFrame { final int xOffset; final int yOffset; final long duration; @@ -37,4 +39,9 @@ public BgraPreFrame(int width, int height, int xOffset, int yOffset, long durati public BgraPreFrame(BgraPreCanvas canvas, int width, int height, int xOffset, int yOffset, long duration) { this(canvas.getPixels(xOffset, yOffset, width, height), width, height, xOffset, yOffset, duration); } + + @Override + public long getDuration() { + return duration; + } } From 5830d7c351e000d22baf51f27d5c51afcceefbd0 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 21:03:44 +0800 Subject: [PATCH 05/16] update --- .../apng/argb8888/BgraPreBitmapDirector.java | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index 4504a5813b..1b380f3411 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.ui.image.apng.argb8888; +import javafx.animation.Timeline; import javafx.scene.image.Image; import org.jackhuang.hmcl.ui.image.apng.PngScanlineBuffer; import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; @@ -43,6 +44,22 @@ public final class BgraPreBitmapDirector extends BasicArgb8888Director { private PngAnimationControl animationControl; private List animationFrames; + private boolean doScale; + private int targetWidth; + private int targetHeight; + + private final int requestedWidth; + private final int requestedHeight; + private final boolean preserveRatio; + private final boolean smooth; + + public BgraPreBitmapDirector(int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) { + this.requestedWidth = requestedWidth; + this.requestedHeight = requestedHeight; + this.preserveRatio = preserveRatio; + this.smooth = smooth; + } + @Override public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws PngException { if (this.header != null) @@ -52,6 +69,27 @@ public void receiveHeader(PngHeader header, PngScanlineBuffer buffer) throws Png this.defaultImage = new Argb8888Bitmap(header.width, header.height); this.scanlineProcessor = Argb8888Processors.from(header, buffer, defaultImage); this.canvas = new BgraPreCanvas(header.width, header.height); + + if (requestedWidth > 0 && requestedHeight > 0 + && (requestedWidth != header.width || requestedHeight != header.height)) { + doScale = true; + + if (preserveRatio) { + double scaleX = (double) requestedWidth / header.width; + double scaleY = (double) requestedHeight / header.height; + double scale = Math.min(scaleX, scaleY); + + targetWidth = (int) (header.width * scale); + targetHeight = (int) (header.height * scale); + } else { + targetWidth = requestedWidth; + targetHeight = requestedHeight; + } + } else { + doScale = false; + targetWidth = header.width; + targetHeight = header.height; + } } @Override @@ -145,7 +183,19 @@ public void receiveFrameImage(Argb8888Bitmap bitmap) throws PngException { @Override public Image getResult() { - return null; // TODO + if (doScale) { + throw new UnsupportedOperationException("TODO"); // TODO + } + + int cycleCount; + if (animationControl != null) { + cycleCount = animationControl.numPlays; + if (cycleCount == 0) + cycleCount = Timeline.INDEFINITE; + } else + cycleCount = Timeline.INDEFINITE; + + return new BgraPreAnimationImage(targetWidth, targetHeight, cycleCount, List.copyOf(animationFrames)); } } From a2ff22b603fe6c13e6997e610258d2b7e0c1c012 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 21:08:02 +0800 Subject: [PATCH 06/16] update --- .../jackhuang/hmcl/ui/image/ImageUtils.java | 63 +++---------------- .../apng/argb8888/BgraPreBitmapDirector.java | 2 +- 2 files changed, 8 insertions(+), 57 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java index 261ab519cb..128446bc7a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java @@ -22,13 +22,13 @@ import javafx.scene.image.Image; import javafx.scene.image.PixelFormat; import javafx.scene.image.WritableImage; -import org.jackhuang.hmcl.ui.image.apng.Png; -import org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888Bitmap; -import org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888BitmapSequence; +import org.jackhuang.hmcl.ui.image.apng.argb8888.*; import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl; import org.jackhuang.hmcl.ui.image.apng.error.PngException; import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException; +import org.jackhuang.hmcl.ui.image.apng.reader.DefaultPngChunkReader; +import org.jackhuang.hmcl.ui.image.apng.reader.PngReadHelper; import org.jackhuang.hmcl.ui.image.internal.AnimationImageImpl; import org.jackhuang.hmcl.util.SwingFXUtils; import org.jetbrains.annotations.Nullable; @@ -42,8 +42,6 @@ import java.util.*; import java.util.regex.Pattern; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; - /** * @author Glavo */ @@ -78,57 +76,10 @@ public final class ImageUtils { return DEFAULT.load(input, requestedWidth, requestedHeight, preserveRatio, smooth); try { - var sequence = Png.readArgb8888BitmapSequence(input); - - final int width = sequence.header.width; - final int height = sequence.header.height; - - boolean doScale; - if (requestedWidth > 0 && requestedHeight > 0 - && (requestedWidth != width || requestedHeight != height)) { - doScale = true; - - if (preserveRatio) { - double scaleX = (double) requestedWidth / width; - double scaleY = (double) requestedHeight / height; - double scale = Math.min(scaleX, scaleY); - - requestedWidth = (int) (width * scale); - requestedHeight = (int) (height * scale); - } - } else { - doScale = false; - } - - if (sequence.isAnimated()) { - try { - return toImage(sequence, doScale, requestedWidth, requestedHeight); - } catch (Throwable e) { - LOG.warning("Failed to load animated image", e); - } - } - - Argb8888Bitmap defaultImage = sequence.defaultImage; - int targetWidth; - int targetHeight; - int[] pixels; - if (doScale) { - targetWidth = requestedWidth; - targetHeight = requestedHeight; - pixels = scale(defaultImage.array, - defaultImage.width, defaultImage.height, - targetWidth, targetHeight); - } else { - targetWidth = defaultImage.width; - targetHeight = defaultImage.height; - pixels = defaultImage.array; - } - - WritableImage image = new WritableImage(targetWidth, targetHeight); - image.getPixelWriter().setPixels(0, 0, targetWidth, targetHeight, - PixelFormat.getIntArgbInstance(), pixels, - 0, targetWidth); - return image; + return PngReadHelper.read(input, new DefaultPngChunkReader<>( + new Argb8888Processor<>( + new BgraPreBitmapDirector( + requestedWidth, requestedHeight, preserveRatio, smooth)))); } catch (PngException e) { throw new IOException(e); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index 1b380f3411..95506e696a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -114,7 +114,7 @@ public void receiveDefaultImage(Argb8888Bitmap bitmap) { @Override public void receiveAnimationControl(PngAnimationControl control) { - this.animationControl = animationControl; + this.animationControl = control; this.animationFrames = new ArrayList<>(animationControl.numFrames); } From dae4d3c84bfa8bdc330adb7606d0c9fac244c370 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 21:10:48 +0800 Subject: [PATCH 07/16] update --- .../hmcl/ui/image/AnimationFrame.java | 22 +++ .../jackhuang/hmcl/ui/image/ImageUtils.java | 151 ------------------ .../apng/argb8888/BgraPreBitmapDirector.java | 1 + .../image/internal/BgraPreAnimationImage.java | 56 +++++++ 4 files changed, 79 insertions(+), 151 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationFrame.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationFrame.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationFrame.java new file mode 100644 index 0000000000..976e9a4ac5 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/AnimationFrame.java @@ -0,0 +1,22 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.image; + +public interface AnimationFrame { + long getDuration(); +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java index 128446bc7a..dd0df2e564 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java @@ -18,18 +18,11 @@ package org.jackhuang.hmcl.ui.image; import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi; -import javafx.animation.Timeline; import javafx.scene.image.Image; -import javafx.scene.image.PixelFormat; -import javafx.scene.image.WritableImage; import org.jackhuang.hmcl.ui.image.apng.argb8888.*; -import org.jackhuang.hmcl.ui.image.apng.chunks.PngAnimationControl; -import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl; import org.jackhuang.hmcl.ui.image.apng.error.PngException; -import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException; import org.jackhuang.hmcl.ui.image.apng.reader.DefaultPngChunkReader; import org.jackhuang.hmcl.ui.image.apng.reader.PngReadHelper; -import org.jackhuang.hmcl.ui.image.internal.AnimationImageImpl; import org.jackhuang.hmcl.util.SwingFXUtils; import org.jetbrains.annotations.Nullable; @@ -213,150 +206,6 @@ private static int[] scale(int[] pixels, return result; } - private static Image toImage(Argb8888BitmapSequence sequence, - boolean doScale, - int targetWidth, int targetHeight) throws PngException { - final int width = sequence.header.width; - final int height = sequence.header.height; - - List frames = sequence.getAnimationFrames(); - - var framePixels = new int[frames.size()][]; - var durations = new int[framePixels.length]; - - int[] buffer = new int[Math.multiplyExact(width, height)]; - for (int frameIndex = 0; frameIndex < frames.size(); frameIndex++) { - var frame = frames.get(frameIndex); - PngFrameControl control = frame.control; - - if (frameIndex == 0 && ( - control.xOffset != 0 || control.yOffset != 0 - || control.width != width || control.height != height)) { - throw new PngIntegrityException("Invalid first frame: " + control); - } - - if (control.xOffset < 0 || control.yOffset < 0 - || width < 0 || height < 0 - || control.xOffset + control.width > width - || control.yOffset + control.height > height - || control.delayNumerator < 0 || control.delayDenominator < 0 - ) { - throw new PngIntegrityException("Invalid frame control: " + control); - } - - int[] currentFrameBuffer = buffer.clone(); - if (control.blendOp == 0) { - for (int row = 0; row < control.height; row++) { - System.arraycopy(frame.bitmap.array, - row * control.width, - currentFrameBuffer, - (control.yOffset + row) * width + control.xOffset, - control.width); - } - } else if (control.blendOp == 1) { - // APNG_BLEND_OP_OVER - Alpha blending - for (int row = 0; row < control.height; row++) { - for (int col = 0; col < control.width; col++) { - int srcIndex = row * control.width + col; - int dstIndex = (control.yOffset + row) * width + control.xOffset + col; - - int srcPixel = frame.bitmap.array[srcIndex]; - int dstPixel = currentFrameBuffer[dstIndex]; - - int srcAlpha = (srcPixel >>> 24) & 0xFF; - if (srcAlpha == 0) { - continue; - } else if (srcAlpha == 255) { - currentFrameBuffer[dstIndex] = srcPixel; - } else { - int srcR = (srcPixel >>> 16) & 0xFF; - int srcG = (srcPixel >>> 8) & 0xFF; - int srcB = srcPixel & 0xFF; - - int dstAlpha = (dstPixel >>> 24) & 0xFF; - int dstR = (dstPixel >>> 16) & 0xFF; - int dstG = (dstPixel >>> 8) & 0xFF; - int dstB = dstPixel & 0xFF; - - int invSrcAlpha = 255 - srcAlpha; - - int outAlpha = srcAlpha + (dstAlpha * invSrcAlpha + 127) / 255; - int outR, outG, outB; - - if (outAlpha == 0) { - outR = outG = outB = 0; - } else { - outR = (srcR * srcAlpha + dstR * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha; - outG = (srcG * srcAlpha + dstG * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha; - outB = (srcB * srcAlpha + dstB * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha; - } - - outAlpha = Math.min(outAlpha, 255); - outR = Math.min(outR, 255); - outG = Math.min(outG, 255); - outB = Math.min(outB, 255); - - currentFrameBuffer[dstIndex] = (outAlpha << 24) | (outR << 16) | (outG << 8) | outB; - } - } - } - } else { - throw new PngIntegrityException("Unsupported blendOp " + control.blendOp + " at frame " + frameIndex); - } - - if (doScale) - framePixels[frameIndex] = scale(currentFrameBuffer, - width, height, - targetWidth, targetHeight); - else - framePixels[frameIndex] = currentFrameBuffer; - - if (control.delayNumerator == 0) { - durations[frameIndex] = 10; - } else { - int durationsMills = 1000 * control.delayNumerator; - if (control.delayDenominator == 0) - durationsMills /= 100; - else - durationsMills /= control.delayDenominator; - - durations[frameIndex] = durationsMills; - } - - switch (control.disposeOp) { - case 0: // APNG_DISPOST_OP_NONE - System.arraycopy(currentFrameBuffer, 0, buffer, 0, currentFrameBuffer.length); - break; - case 1: // APNG_DISPOSE_OP_BACKGROUND - for (int row = 0; row < control.height; row++) { - int fromIndex = (control.yOffset + row) * width + control.xOffset; - Arrays.fill(buffer, fromIndex, fromIndex + control.width, 0); - } - break; - case 2: // APNG_DISPOSE_OP_PREVIOUS - // Do nothing, keep the previous frame. - break; - default: - throw new PngIntegrityException("Unsupported disposeOp " + control.disposeOp + " at frame " + frameIndex); - } - } - - PngAnimationControl animationControl = sequence.getAnimationControl(); - int cycleCount; - if (animationControl != null) { - cycleCount = animationControl.numPlays; - if (cycleCount == 0) - cycleCount = Timeline.INDEFINITE; - } else { - cycleCount = Timeline.INDEFINITE; - } - - if (doScale) - return new AnimationImageImpl(targetWidth, targetHeight, framePixels, durations, cycleCount); - else - return new AnimationImageImpl(width, height, framePixels, durations, cycleCount); - } - private ImageUtils() { } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index 95506e696a..cc03c43b46 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -178,6 +178,7 @@ public void receiveFrameImage(Argb8888Bitmap bitmap) throws PngException { throw new PngIntegrityException("Unsupported disposeOp " + currentFrame.disposeOp); } + animationFrames.add(frame); currentFrame = null; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java new file mode 100644 index 0000000000..e4c8a56735 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.image.internal; + +import javafx.scene.image.PixelFormat; +import org.jackhuang.hmcl.ui.image.AnimationImage; + +import java.util.List; + +/** + * @author Glavo + */ +public final class BgraPreAnimationImage extends AnimationImage { + private final List frames; + + public BgraPreAnimationImage(int width, int height, int cycleCount, List frames) { + super(width, height, cycleCount); + this.frames = frames; + + play(); + } + + @Override + public int getFramesCount() { + return frames.size(); + } + + @Override + public long getDuration(int index) { + return frames.get(index).getDuration(); + } + + @Override + protected void updateImage(int frameIndex) { + BgraPreFrame frame = frames.get(frameIndex); + getPixelWriter().setPixels(frame.xOffset, frame.yOffset, frame.width, frame.width, + PixelFormat.getByteBgraPreInstance(), + frame.pixels, 0, 4 * frame.width + ); + } +} From eb514491031033479ccd13bf48b65e436b918d16 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 9 Aug 2025 21:14:49 +0800 Subject: [PATCH 08/16] update --- .../jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java index e4c8a56735..68a6f01ea7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java @@ -50,7 +50,7 @@ protected void updateImage(int frameIndex) { BgraPreFrame frame = frames.get(frameIndex); getPixelWriter().setPixels(frame.xOffset, frame.yOffset, frame.width, frame.width, PixelFormat.getByteBgraPreInstance(), - frame.pixels, 0, 4 * frame.width + frame.pixels, 0, frame.width ); } } From fe0dd17232a94bde5fd5e96a41eeb6f35c111e4b Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:04:44 +0800 Subject: [PATCH 09/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 0bbe546881..26a746cc32 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -47,7 +47,7 @@ protected static int argbToPre(int argb) { public BgraPreCanvas(byte[] pixels, int width, int height) { if (pixels.length != 4 * width * height) - throw new IllegalArgumentException("Pixel array length missmatch"); + throw new IllegalArgumentException("Pixel array length mismatch"); this.pixels = pixels; this.width = width; From d0e9fcf435a2c2e293c3fd9ded3715d496a82353 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:04:55 +0800 Subject: [PATCH 10/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 26a746cc32..84de579c98 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -85,7 +85,7 @@ public byte[] getPixels(int x, int y, int w, int h) { public void clear(int x, int y, int w, int h) { Objects.checkFromIndexSize(x, w, width); - Objects.checkFromIndexSize(y, h, width); + Objects.checkFromIndexSize(y, h, height); final int bytesForRow = 4 * w; From 1c7d44e1ef2440c82c6701ab6d1800408a358392 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:05:10 +0800 Subject: [PATCH 11/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 84de579c98..3b0d2971c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -97,7 +97,7 @@ public void clear(int x, int y, int w, int h) { public void setBgraPre(int x, int y, BgraPreCanvas canvas) { Objects.checkFromIndexSize(x, canvas.width, width); - Objects.checkFromIndexSize(y, canvas.height, width); + Objects.checkFromIndexSize(y, canvas.height, height); for (int row = 0; row < canvas.height; row++) { for (int col = 0; col < canvas.width; col++) { From d80555858eadc33685a23ffa3624b59c092cdc9d Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:05:21 +0800 Subject: [PATCH 12/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 3b0d2971c1..4edf021509 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -127,7 +127,7 @@ public void setArgbPre(int x, int y, int argbPre) { public void setArgb(int x, int y, int w, int h, int[] buffer, int offset, int scanlineStride) { Objects.checkFromIndexSize(x, w, width); - Objects.checkFromIndexSize(y, h, width); + Objects.checkFromIndexSize(y, h, height); for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { From ec5ac5ac828d7e50ccb6c6199e98745182fc1f1f Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:05:36 +0800 Subject: [PATCH 13/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java index 68a6f01ea7..77bd6b3516 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java @@ -48,7 +48,7 @@ public long getDuration(int index) { @Override protected void updateImage(int frameIndex) { BgraPreFrame frame = frames.get(frameIndex); - getPixelWriter().setPixels(frame.xOffset, frame.yOffset, frame.width, frame.width, + getPixelWriter().setPixels(frame.xOffset, frame.yOffset, frame.width, frame.height, PixelFormat.getByteBgraPreInstance(), frame.pixels, 0, frame.width ); From 46f4f8fe82b3a7f034505240465266de3db368a1 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:05:49 +0800 Subject: [PATCH 14/16] Update HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java index 4edf021509..8602e443fa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreCanvas.java @@ -141,7 +141,7 @@ public void setArgb(int x, int y, int w, int h, public void blendingWithArgb(int x, int y, int w, int h, int[] buffer, int offset, int scanlineStride) { Objects.checkFromIndexSize(x, w, width); - Objects.checkFromIndexSize(y, h, width); + Objects.checkFromIndexSize(y, h, height); for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { From cc86e07869c4289e54601d66ca2acb3ce6656bb4 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:19:54 +0800 Subject: [PATCH 15/16] update --- .../hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java index cc03c43b46..916612226a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/argb8888/BgraPreBitmapDirector.java @@ -155,10 +155,10 @@ public void receiveFrameImage(Argb8888Bitmap bitmap) throws PngException { ); if (currentFrame.blendOp == 0) { - frame.setArgb(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, + frame.setArgb(0, 0, currentFrame.width, currentFrame.height, bitmap.array, 0, currentFrame.width); } else if (currentFrame.blendOp == 1) { // APNG_BLEND_OP_OVER - Alpha blending - frame.blendingWithArgb(currentFrame.xOffset, currentFrame.yOffset, currentFrame.width, currentFrame.height, + frame.blendingWithArgb(0, 0, currentFrame.width, currentFrame.height, bitmap.array, 0, currentFrame.width); } else { throw new PngIntegrityException("Unsupported blendOp " + currentFrame.blendOp); From 6651dcb158de8cd8ad09b23d800a72ef76c40a3c Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 14 Aug 2025 21:28:19 +0800 Subject: [PATCH 16/16] fix --- .../jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java index 77bd6b3516..1094fb4b56 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/internal/BgraPreAnimationImage.java @@ -50,7 +50,7 @@ protected void updateImage(int frameIndex) { BgraPreFrame frame = frames.get(frameIndex); getPixelWriter().setPixels(frame.xOffset, frame.yOffset, frame.width, frame.height, PixelFormat.getByteBgraPreInstance(), - frame.pixels, 0, frame.width + frame.pixels, 0, 4 * frame.width ); } }