From c75cbe0107ab89df7eaed8d27421676cec25d3b0 Mon Sep 17 00:00:00 2001 From: KSSJW <165921128+KSSJW@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:15:36 +0800 Subject: [PATCH 1/3] Perf: Optimize background opacity calculation for high-res images --- .../java/org/jackhuang/hmcl/theme/Themes.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java index ced817d7c0..5dc26bdef4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -31,9 +31,10 @@ import javafx.collections.SetChangeListener; import javafx.event.EventHandler; import javafx.geometry.Insets; +import javafx.scene.SnapshotParameters; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; -import javafx.scene.image.PixelReader; -import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; @@ -941,10 +942,11 @@ private static Background createThemeColorBackground(double opacity) { /// Creates a JavaFX image background with the requested opacity. private static Background createBackgroundWithOpacity(Image image, double opacity) { - PixelReader pixelReader = image.getPixelReader(); + if (image == null) return null; + if (opacity <= 0) { return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY)); - } else if (opacity >= 1. || pixelReader == null) { + } else if (opacity >= 1.0) { return new Background(new BackgroundImage( image, BackgroundRepeat.NO_REPEAT, @@ -953,15 +955,19 @@ private static Background createBackgroundWithOpacity(Image image, double opacit new BackgroundSize(800, 480, false, false, true, true) )); } else { - WritableImage tempImage = new WritableImage((int) image.getWidth(), (int) image.getHeight()); - PixelWriter pixelWriter = tempImage.getPixelWriter(); - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - Color color = pixelReader.getColor(x, y); - Color newColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getOpacity() * opacity); - pixelWriter.setColor(x, y, newColor); - } - } + Canvas canvas = new Canvas(800, 480); + GraphicsContext gc = canvas.getGraphicsContext2D(); + + double imageWidth = image.getWidth(); + double imageHeight = image.getHeight(); + double scale = Math.max(800 / imageWidth, 480 / imageHeight); + + gc.setGlobalAlpha(opacity); + gc.drawImage(image, 0, 0, imageWidth * scale, imageHeight * scale); + + SnapshotParameters params = new SnapshotParameters(); + params.setFill(Color.TRANSPARENT); + WritableImage tempImage = canvas.snapshot(params, null); return new Background(new BackgroundImage( tempImage, From bfb66c4183ab1f78cb5cf8f31aed1a5478dee0f0 Mon Sep 17 00:00:00 2001 From: KSSJW <165921128+KSSJW@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:39:30 +0800 Subject: [PATCH 2/3] Refactor: Null Safety --- HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java index 5dc26bdef4..b087eea8ab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -946,7 +946,7 @@ private static Background createBackgroundWithOpacity(Image image, double opacit if (opacity <= 0) { return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY)); - } else if (opacity >= 1.0) { + } else if (opacity >= 1.0 || image.getPixelReader() == null) { return new Background(new BackgroundImage( image, BackgroundRepeat.NO_REPEAT, From 6febe50f616fa0bed90f4ccc974e777f7821f90c Mon Sep 17 00:00:00 2001 From: KSSJW <165921128+KSSJW@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:02:34 +0800 Subject: [PATCH 3/3] Fix: Resolve thread-safety issue in background image generation --- .../java/org/jackhuang/hmcl/theme/Themes.java | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java index b087eea8ab..36ff2f2594 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/theme/Themes.java @@ -29,12 +29,11 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.SetChangeListener; +import javafx.embed.swing.SwingFXUtils; import javafx.event.EventHandler; import javafx.geometry.Insets; -import javafx.scene.SnapshotParameters; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; +import javafx.scene.image.PixelFormat; import javafx.scene.image.WritableImage; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; @@ -79,6 +78,9 @@ import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -955,26 +957,43 @@ private static Background createBackgroundWithOpacity(Image image, double opacit new BackgroundSize(800, 480, false, false, true, true) )); } else { - Canvas canvas = new Canvas(800, 480); - GraphicsContext gc = canvas.getGraphicsContext2D(); + int targetWidth = 800; + int targetHeight = 480; double imageWidth = image.getWidth(); double imageHeight = image.getHeight(); - double scale = Math.max(800 / imageWidth, 480 / imageHeight); + + double scale = Math.max(targetWidth / imageWidth, targetHeight / imageHeight); + + BufferedImage srcBuffered = SwingFXUtils.fromFXImage(image, null); + + BufferedImage destBuffered = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = destBuffered.createGraphics(); - gc.setGlobalAlpha(opacity); - gc.drawImage(image, 0, 0, imageWidth * scale, imageHeight * scale); + AffineTransform at = new AffineTransform(); + at.scale(scale, scale); + g2d.drawImage(srcBuffered, at, null); + g2d.dispose(); + + WritableImage tempImage = SwingFXUtils.toFXImage(destBuffered, null); + + int[] buffer = new int[targetWidth * targetHeight]; + tempImage.getPixelReader().getPixels(0, 0, targetWidth, targetHeight, PixelFormat.getIntArgbInstance(), buffer, 0, targetWidth); - SnapshotParameters params = new SnapshotParameters(); - params.setFill(Color.TRANSPARENT); - WritableImage tempImage = canvas.snapshot(params, null); + int alphaMultiplier = (int) (opacity * 255); + for (int i = 0; i < buffer.length; i++) { + int argb = buffer[i]; + int a = ((argb >>> 24) * alphaMultiplier) / 255; + buffer[i] = (a << 24) | (argb & 0x00FFFFFF); + } + + tempImage.getPixelWriter().setPixels(0, 0, targetWidth, targetHeight, PixelFormat.getIntArgbInstance(), buffer, 0, targetWidth); return new Background(new BackgroundImage( tempImage, - BackgroundRepeat.NO_REPEAT, - BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, - new BackgroundSize(800, 480, false, false, true, true) + new BackgroundSize(targetWidth, targetHeight, false, false, true, true) )); } }