支持加载 WebP 格式的背景图片 (#3457)

* 支持加载 WebP 格式的背景图片

* update

* update

* update

* update
This commit is contained in:
Glavo
2024-11-28 00:35:08 +08:00
committed by GitHub
parent 4c242d0c80
commit 36da64f796
16 changed files with 264 additions and 113 deletions

View File

@@ -37,6 +37,7 @@ version = "$versionRoot.$buildNumber"
dependencies { dependencies {
implementation(project(":HMCLCore")) implementation(project(":HMCLCore"))
implementation("libs:JFoenix") implementation("libs:JFoenix")
implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
} }
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes) fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
@@ -110,6 +111,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
exclude("**/package-info.class") exclude("**/package-info.class")
exclude("META-INF/maven/**") exclude("META-INF/maven/**")
exclude("META-INF/services/javax.imageio.spi.ImageReaderSpi")
exclude("META-INF/services/javax.imageio.spi.ImageInputStreamSpi")
minimize { minimize {
exclude(dependency("com.google.code.gson:.*:.*")) exclude(dependency("com.google.code.gson:.*:.*"))
exclude(dependency("libs:JFoenix:.*")) exclude(dependency("libs:JFoenix:.*"))

View File

@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.ProxyManager; import org.jackhuang.hmcl.setting.ProxyManager;
import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -43,9 +44,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
@@ -261,24 +260,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
public Optional<File> getVersionIconFile(String id) { public Optional<File> getVersionIconFile(String id) {
File root = getVersionRoot(id); File root = getVersionRoot(id);
File iconFile = new File(root, "icon.png"); for (String extension : FXUtils.IMAGE_EXTENSIONS) {
if (iconFile.exists()) { File file = new File(root, "icon." + extension);
return Optional.of(iconFile); if (file.exists()) {
return Optional.of(file);
} }
iconFile = new File(root, "icon.jpg");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.bmp");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.gif");
if (iconFile.exists()) {
return Optional.of(iconFile);
} }
return Optional.empty(); return Optional.empty();
@@ -286,7 +272,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void setVersionIconFile(String id, File iconFile) throws IOException { public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT); String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) { if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
throw new IllegalArgumentException("Unsupported icon file: " + ext); throw new IllegalArgumentException("Unsupported icon file: " + ext);
} }
@@ -297,11 +283,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void deleteIconFile(String id) { public void deleteIconFile(String id) {
File root = getVersionRoot(id); File root = getVersionRoot(id);
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
new File(root, "icon.png").delete(); new File(root, "icon." + extension).delete();
new File(root, "icon.jpg").delete(); }
new File(root, "icon.bmp").delete();
new File(root, "icon.gif").delete();
} }
public Image getVersionIconImage(String id) { public Image getVersionIconImage(String id) {
@@ -315,9 +299,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
Version version = getVersion(id).resolve(this); Version version = getVersion(id).resolve(this);
Optional<File> iconFile = getVersionIconFile(id); Optional<File> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) { if (iconFile.isPresent()) {
try (InputStream inputStream = new FileInputStream(iconFile.get())) { try {
return new Image(inputStream); return FXUtils.loadImage(iconFile.get().toPath());
} catch (IOException e) { } catch (Exception e) {
LOG.warning("Failed to load version icon of " + id, e); LOG.warning("Failed to load version icon of " + id, e);
} }
} }

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui; package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*; import com.jfoenix.controls.*;
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
import javafx.animation.*; import javafx.animation.*;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
@@ -44,6 +45,7 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.scene.text.TextFlow; import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Callback; import javafx.util.Callback;
import javafx.util.Duration; import javafx.util.Duration;
@@ -53,11 +55,9 @@ import org.glavo.png.PNGWriter;
import org.glavo.png.javafx.PNGJavaFXUtils; import org.glavo.png.javafx.PNGJavaFXUtils;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -68,13 +68,15 @@ import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import java.io.*; import java.io.*;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.net.URI; import java.net.*;
import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -113,6 +115,10 @@ public final class FXUtils {
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(
"png", "jpg", "jpeg", "bmp", "gif", "webp"
);
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>(); private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>(); private static final Map<String, Path> remoteImageCache = new ConcurrentHashMap<>();
@@ -713,6 +719,50 @@ public final class FXUtils {
stage.getIcons().add(newBuiltinImage(icon)); stage.getIcons().add(newBuiltinImage(icon));
} }
private static Image loadWebPImage(InputStream input) throws IOException {
WebPImageReaderSpi spi = new WebPImageReaderSpi();
ImageReader reader = spi.createReaderInstance(null);
try (ImageInputStream imageInput = ImageIO.createImageInputStream(input)) {
reader.setInput(imageInput, true, true);
return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()), null);
} finally {
reader.dispose();
}
}
public static Image loadImage(Path path) throws Exception {
try (InputStream input = Files.newInputStream(path)) {
if ("webp".equalsIgnoreCase(FileUtils.getExtension(path)))
return loadWebPImage(input);
else {
Image image = new Image(input);
if (image.isError())
throw image.getException();
return image;
}
}
}
public static Image loadImage(URL url) throws Exception {
URLConnection connection = NetworkUtils.createConnection(url);
if (connection instanceof HttpURLConnection) {
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
}
try (InputStream input = connection.getInputStream()) {
String path = url.getPath();
if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.')))
return loadWebPImage(input);
else {
Image image = new Image(input);
if (image.isError())
throw image.getException();
return image;
}
}
}
/** /**
* Suppress IllegalArgumentException since the url is supposed to be correct definitely. * Suppress IllegalArgumentException since the url is supposed to be correct definitely.
* *
@@ -1034,4 +1084,9 @@ public final class FXUtils {
return String.format("#%02x%02x%02x", r, g, b); return String.format("#%02x%02x%02x", r, g, b);
} }
public static FileChooser.ExtensionFilter getImageExtensionFilter() {
return new FileChooser.ExtensionFilter(i18n("extension.png"),
IMAGE_EXTENSIONS.stream().map(ext -> "*." + ext).toArray(String[]::new));
}
} }

View File

@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.base.ValidatorBase; import com.jfoenix.validation.base.ValidatorBase;
import javafx.beans.property.*; import javafx.beans.property.*;
import javafx.collections.ObservableList;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
@@ -114,6 +113,7 @@ public final class MultiFileItem<T> extends VBox {
public static class Option<T> { public static class Option<T> {
protected final String title; protected final String title;
protected String subtitle; protected String subtitle;
protected String tooltip;
protected final T data; protected final T data;
protected final BooleanProperty selected = new SimpleBooleanProperty(); protected final BooleanProperty selected = new SimpleBooleanProperty();
protected final JFXRadioButton left = new JFXRadioButton(); protected final JFXRadioButton left = new JFXRadioButton();
@@ -140,6 +140,11 @@ public final class MultiFileItem<T> extends VBox {
return this; return this;
} }
public Option<T> setTooltip(String tooltip) {
this.tooltip = tooltip;
return this;
}
public boolean isSelected() { public boolean isSelected() {
return left.isSelected(); return left.isSelected();
} }
@@ -161,6 +166,8 @@ public final class MultiFileItem<T> extends VBox {
BorderPane.setAlignment(left, Pos.CENTER_LEFT); BorderPane.setAlignment(left, Pos.CENTER_LEFT);
left.setToggleGroup(group); left.setToggleGroup(group);
left.setUserData(data); left.setUserData(data);
if (StringUtils.isNotBlank(tooltip))
FXUtils.installFastTooltip(left, tooltip);
pane.setLeft(left); pane.setLeft(left);
if (StringUtils.isNotBlank(subtitle)) { if (StringUtils.isNotBlank(subtitle)) {
@@ -268,8 +275,9 @@ public final class MultiFileItem<T> extends VBox {
return this; return this;
} }
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() { public FileOption<T> addExtensionFilter(FileChooser.ExtensionFilter filter) {
return selector.getExtensionFilters(); selector.getExtensionFilters().add(filter);
return this;
} }
@Override @Override

View File

@@ -49,13 +49,10 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.wizard.Refreshable; import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -180,23 +177,13 @@ public class DecoratorController {
case CUSTOM: case CUSTOM:
String backgroundImage = config().getBackgroundImage(); String backgroundImage = config().getBackgroundImage();
if (backgroundImage != null) if (backgroundImage != null)
image = tryLoadImage(Paths.get(backgroundImage)).orElse(null); image = tryLoadImage(Paths.get(backgroundImage));
break; break;
case NETWORK: case NETWORK:
String backgroundImageUrl = config().getBackgroundImageUrl(); String backgroundImageUrl = config().getBackgroundImageUrl();
if (backgroundImageUrl != null) { if (backgroundImageUrl != null) {
try { try {
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl)); image = FXUtils.loadImage(new URL(backgroundImageUrl));
if (connection instanceof HttpURLConnection) {
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
}
try (InputStream input = connection.getInputStream()) {
image = new Image(input);
if (image.isError()) {
throw image.getException();
}
}
} catch (Exception e) { } catch (Exception e) {
LOG.warning("Couldn't load background image", e); LOG.warning("Couldn't load background image", e);
} }
@@ -218,73 +205,57 @@ public class DecoratorController {
* Load background image from bg/, background.png, background.jpg, background.gif * Load background image from bg/, background.png, background.jpg, background.gif
*/ */
private Image loadDefaultBackgroundImage() { private Image loadDefaultBackgroundImage() {
Optional<Image> image = randomImageIn(Paths.get("bg")); Image image = randomImageIn(Paths.get("bg"));
if (!image.isPresent()) { if (image != null)
image = tryLoadImage(Paths.get("background.png")); return image;
}
if (!image.isPresent()) { for (String extension : FXUtils.IMAGE_EXTENSIONS) {
image = tryLoadImage(Paths.get("background.jpg")); image = tryLoadImage(Paths.get("background." + extension));
} if (image != null)
if (!image.isPresent()) { return image;
image = tryLoadImage(Paths.get("background.gif"));
}
return image.orElseGet(() -> newBuiltinImage("/assets/img/background.jpg"));
} }
private Optional<Image> randomImageIn(Path imageDir) { return newBuiltinImage("/assets/img/background.jpg");
}
private @Nullable Image randomImageIn(Path imageDir) {
if (!Files.isDirectory(imageDir)) { if (!Files.isDirectory(imageDir)) {
return Optional.empty(); return null;
} }
List<Path> candidates; List<Path> candidates;
try (Stream<Path> stream = Files.list(imageDir)) { try (Stream<Path> stream = Files.list(imageDir)) {
candidates = stream candidates = stream
.filter(it -> FXUtils.IMAGE_EXTENSIONS.contains(getExtension(it).toLowerCase(Locale.ROOT)))
.filter(Files::isReadable) .filter(Files::isReadable)
.filter(it -> {
String ext = getExtension(it).toLowerCase(Locale.ROOT);
return ext.equals("png") || ext.equals("jpg") || ext.equals("gif");
})
.collect(toList()); .collect(toList());
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Failed to list files in ./bg", e); LOG.warning("Failed to list files in ./bg", e);
return Optional.empty(); return null;
} }
Random rnd = new Random(); Random rnd = new Random();
while (candidates.size() > 0) { while (!candidates.isEmpty()) {
int selected = rnd.nextInt(candidates.size()); int selected = rnd.nextInt(candidates.size());
Optional<Image> loaded = tryLoadImage(candidates.get(selected)); Image loaded = tryLoadImage(candidates.get(selected));
if (loaded.isPresent()) { if (loaded != null)
return loaded; return loaded;
} else { else
candidates.remove(selected); candidates.remove(selected);
} }
} return null;
return Optional.empty();
} }
private Optional<Image> tryLoadImage(Path path) { private @Nullable Image tryLoadImage(Path path) {
if (!Files.isReadable(path)) if (!Files.isReadable(path))
return Optional.empty(); return null;
return tryLoadImage(path.toAbsolutePath().toUri().toString());
}
private Optional<Image> tryLoadImage(String url) {
Image img;
try { try {
img = new Image(url); return FXUtils.loadImage(path);
} catch (IllegalArgumentException e) { } catch (Exception e) {
LOG.warning("Couldn't load background image", e); LOG.warning("Couldn't load background image", e);
return Optional.empty(); return null;
} }
if (img.getException() != null) {
LOG.warning("Couldn't load background image", img.getException());
return Optional.empty();
}
return Optional.of(img);
} }
// ==== Navigation ==== // ==== Navigation ====

View File

@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting; import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
@@ -129,17 +130,13 @@ public final class ExportWizardProvider implements WizardProvider {
if (bg.isDirectory()) if (bg.isDirectory())
zip.putDirectory(bg.toPath(), "bg"); zip.putDirectory(bg.toPath(), "bg");
File background_png = new File("background.png").getAbsoluteFile(); for (String extension : FXUtils.IMAGE_EXTENSIONS) {
if (background_png.isFile()) String fileName = "background." + extension;
zip.putFile(background_png, "background.png");
File background_jpg = new File("background.jpg").getAbsoluteFile(); File background = new File(fileName).getAbsoluteFile();
if (background_jpg.isFile()) if (background.isFile())
zip.putFile(background_jpg, "background.jpg"); zip.putFile(background, "background.png");
}
File background_gif = new File("background.gif").getAbsoluteFile();
if (background_gif.isFile())
zip.putFile(background_gif, "background.gif");
zip.putFile(launcherJar, launcherJar.getFileName().toString()); zip.putFile(launcherJar, launcherJar.getFileName().toString());
} }

View File

@@ -106,11 +106,13 @@ public class PersonalizationPage extends StackPane {
backgroundSublist.setHasSubtitle(true); backgroundSublist.setHasSubtitle(true);
backgroundItem.loadChildren(Arrays.asList( backgroundItem.loadChildren(Arrays.asList(
new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT), new MultiFileItem.Option<>(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
.setTooltip(i18n("launcher.background.default.tooltip")),
new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC), new MultiFileItem.Option<>(i18n("launcher.background.classic"), EnumBackgroundImage.CLASSIC),
new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT), new MultiFileItem.Option<>(i18n("launcher.background.translucent"), EnumBackgroundImage.TRANSLUCENT),
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM) new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumBackgroundImage.CUSTOM)
.setChooserTitle(i18n("launcher.background.choose")) .setChooserTitle(i18n("launcher.background.choose"))
.addExtensionFilter(FXUtils.getImageExtensionFilter())
.bindBidirectional(config().backgroundImageProperty()), .bindBidirectional(config().backgroundImageProperty()),
new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK) new MultiFileItem.StringOption<>(i18n("launcher.background.network"), EnumBackgroundImage.NETWORK)
.setValidators(new URLValidator(true)) .setValidators(new URLValidator(true))

View File

@@ -72,7 +72,7 @@ public class VersionIconDialog extends DialogPane {
private void exploreIcon() { private void exploreIcon() {
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif")); chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());
File selectedFile = chooser.showOpenDialog(Controllers.getStage()); File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) { if (selectedFile != null) {
try { try {

View File

@@ -185,7 +185,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null)) javaCustomOption = new MultiFileItem.FileOption<Pair<JavaVersionType, JavaRuntime>>(i18n("settings.custom"), pair(JavaVersionType.CUSTOM, null))
.setChooserTitle(i18n("settings.game.java_directory.choose")); .setChooserTitle(i18n("settings.game.java_directory.choose"));
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
javaCustomOption.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe")); javaCustomOption.addExtensionFilter(new FileChooser.ExtensionFilter("Java", "java.exe"));
javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> { javaListChangeListener = FXUtils.onWeakChangeAndOperate(JavaManager.getAllJavaProperty(), allJava -> {
List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>(); List<MultiFileItem.Option<Pair<JavaVersionType, JavaRuntime>>> options = new ArrayList<>();

View File

@@ -0,0 +1,124 @@
// Copy from javafx.swing
/*
* Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.jackhuang.hmcl.util;
import javafx.scene.image.*;
import javafx.scene.image.Image;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.nio.IntBuffer;
/**
* This class provides utility methods for converting data types between
* Swing/AWT and JavaFX formats.
*
* @since JavaFX 2.2
*/
public final class SwingFXUtils {
private SwingFXUtils() {
}
/**
* Snapshots the specified {@link BufferedImage} and stores a copy of
* its pixels into a JavaFX {@link Image} object, creating a new
* object if needed.
* The returned {@code Image} will be a static snapshot of the state
* of the pixels in the {@code BufferedImage} at the time the method
* completes. Further changes to the {@code BufferedImage} will not
* be reflected in the {@code Image}.
* <p>
* The optional JavaFX {@link WritableImage} parameter may be reused
* to store the copy of the pixels.
* A new {@code Image} will be created if the supplied object is null,
* is too small or of a type which the image pixels cannot be easily
* converted into.
*
* @param bimg the {@code BufferedImage} object to be converted
* @param wimg an optional {@code WritableImage} object that can be
* used to store the returned pixel data
* @return an {@code Image} object representing a snapshot of the
* current pixels in the {@code BufferedImage}.
* @since JavaFX 2.2
*/
public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
int bw = bimg.getWidth();
int bh = bimg.getHeight();
switch (bimg.getType()) {
case BufferedImage.TYPE_INT_ARGB:
case BufferedImage.TYPE_INT_ARGB_PRE:
break;
default:
BufferedImage converted =
new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2d = converted.createGraphics();
g2d.drawImage(bimg, 0, 0, null);
g2d.dispose();
bimg = converted;
break;
}
// assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
if (wimg != null) {
int iw = (int) wimg.getWidth();
int ih = (int) wimg.getHeight();
if (iw < bw || ih < bh) {
wimg = null;
} else if (bw < iw || bh < ih) {
int[] empty = new int[iw];
PixelWriter pw = wimg.getPixelWriter();
PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
if (bw < iw) {
pw.setPixels(bw, 0, iw - bw, bh, pf, empty, 0, 0);
}
if (bh < ih) {
pw.setPixels(0, bh, iw, ih - bh, pf, empty, 0, 0);
}
}
}
if (wimg == null) {
wimg = new WritableImage(bw, bh);
}
PixelWriter pw = wimg.getPixelWriter();
DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer();
int[] data = db.getData();
int offset = bimg.getRaster().getDataBuffer().getOffset();
int scan = 0;
SampleModel sm = bimg.getRaster().getSampleModel();
if (sm instanceof SinglePixelPackedSampleModel) {
scan = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
}
PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
PixelFormat.getIntArgbPreInstance() :
PixelFormat.getIntArgbInstance());
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
return wimg;
}
}

View File

@@ -787,7 +787,8 @@ launcher.agreement.hint=You must agree to the EULA to use this software.
launcher.background=Background Image launcher.background=Background Image
launcher.background.choose=Choose background image launcher.background.choose=Choose background image
launcher.background.classic=Classic launcher.background.classic=Classic
launcher.background.default=Default (Or "background.png/.jpg/.gif" and the images in the "bg" directory) launcher.background.default=Default
launcher.background.default.tooltip=Or "background.png/.jpg/.gif/.webp" and the images in the "bg" directory
launcher.background.network=From URL launcher.background.network=From URL
launcher.background.translucent=Translucent launcher.background.translucent=Translucent
launcher.cache_directory=Cache Directory launcher.cache_directory=Cache Directory

View File

@@ -682,7 +682,8 @@ launcher.agreement.hint=Debe aceptar el EULA para utilizar este software.
launcher.background=Imagen de fondo launcher.background=Imagen de fondo
launcher.background.choose=Elige una imagen de fondo launcher.background.choose=Elige una imagen de fondo
launcher.background.classic=Clásico launcher.background.classic=Clásico
launcher.background.default=Por defecto (o background.png/jpg/gif, o imágenes en la carpeta bg) launcher.background.default=Por defecto
launcher.background.default.tooltip=o background.png/.jpg/.gif/.webp, o imágenes en la carpeta bg
launcher.background.network=Desde la URL launcher.background.network=Desde la URL
launcher.background.translucent=Translúcido launcher.background.translucent=Translúcido
launcher.cache_directory=Directorio de la caché launcher.cache_directory=Directorio de la caché

View File

@@ -535,7 +535,8 @@ launcher.agreement.hint=このソフトウェアを使用するには、EULAに
launcher.background=背景画像 launcher.background=背景画像
launcher.background.classic=クラシック launcher.background.classic=クラシック
launcher.background.choose=背景画像ファイルを選択してください launcher.background.choose=背景画像ファイルを選択してください
launcher.background.default=標準(ランチャーと同じディレクトリにある background.png/jpg/gif と bg フォルダから自動的に画像を取得します。) launcher.background.default=標準
launcher.background.default.tooltip=ランチャーと同じディレクトリにある background.png/.jpg/.gif/.webp と bg フォルダから自動的に画像を取得します。
launcher.background.network=ネットワーク launcher.background.network=ネットワーク
launcher.background.translucent=半透明 launcher.background.translucent=半透明
launcher.cache_directory=キャッシュ用のディレクトリ launcher.cache_directory=キャッシュ用のディレクトリ

View File

@@ -557,7 +557,8 @@ launcher.agreement.hint=Нужно принять пользовательско
launcher.background=Фоновое изображение launcher.background=Фоновое изображение
launcher.background.choose=Выберите фоновое изображение launcher.background.choose=Выберите фоновое изображение
launcher.background.classic=Классическое launcher.background.classic=Классическое
launcher.background.default=По умолчанию (или background.png/jpg/gif, или папку с изображениями) launcher.background.default=По умолчанию
launcher.background.default.tooltip=или background.png/.jpg/.gif/.webp, или папку с изображениями
launcher.background.network=По ссылке launcher.background.network=По ссылке
launcher.background.translucent=Полупрозрачный launcher.background.translucent=Полупрозрачный
launcher.cache_directory=Папка с кэшем launcher.cache_directory=Папка с кэшем

View File

@@ -602,7 +602,8 @@ launcher.agreement.hint=同意本軟體的使用者協議與免責宣告以使
launcher.background=背景圖片 launcher.background=背景圖片
launcher.background.choose=選取背景圖片 launcher.background.choose=選取背景圖片
launcher.background.classic=經典 launcher.background.classic=經典
launcher.background.default=預設 (自動尋找啟動器同目錄下的「background.png/.jpg/.gif」及「bg」目錄內的圖片) launcher.background.default=預設
launcher.background.default.tooltip=自動尋找啟動器同目錄下的「background.png/.jpg/.gif/.webp」及「bg」目錄內的圖片
launcher.background.network=網路 launcher.background.network=網路
launcher.background.translucent=半透明 launcher.background.translucent=半透明
launcher.cache_directory=檔案下載快取目錄 launcher.cache_directory=檔案下載快取目錄

View File

@@ -613,7 +613,8 @@ launcher.agreement.hint=同意本软件的用户协议与免责声明以使用
launcher.background=背景图片 launcher.background=背景图片
launcher.background.choose=选择背景图片 launcher.background.choose=选择背景图片
launcher.background.classic=经典 launcher.background.classic=经典
launcher.background.default=默认 (自动检索启动器同文件夹下的“background.png/.jpg/.gif”及“bg”文件夹内的图片) launcher.background.default=默认
launcher.background.default.tooltip=自动检索启动器同文件夹下的“background.png/.jpg/.gif/.webp”及“bg”文件夹内的图片
launcher.background.network=网络 launcher.background.network=网络
launcher.background.translucent=半透明 launcher.background.translucent=半透明
launcher.cache_directory=文件下载缓存文件夹 launcher.cache_directory=文件下载缓存文件夹