2727import javafx .beans .WeakListener ;
2828import javafx .beans .property .BooleanProperty ;
2929import javafx .beans .property .Property ;
30+ import javafx .beans .property .SimpleObjectProperty ;
3031import javafx .beans .value .*;
3132import javafx .event .Event ;
3233import javafx .event .EventDispatcher ;
5455import javafx .util .Callback ;
5556import javafx .util .Duration ;
5657import javafx .util .StringConverter ;
57- import org .glavo .png .PNGType ;
58- import org .glavo .png .PNGWriter ;
59- import org .glavo .png .javafx .PNGJavaFXUtils ;
58+ import org .jackhuang .hmcl .task .FileDownloadTask ;
59+ import org .jackhuang .hmcl .task .Schedulers ;
6060import org .jackhuang .hmcl .task .Task ;
6161import org .jackhuang .hmcl .ui .animation .AnimationUtils ;
6262import org .jackhuang .hmcl .util .*;
7878import javax .xml .parsers .DocumentBuilder ;
7979import javax .xml .parsers .DocumentBuilderFactory ;
8080import javax .xml .parsers .ParserConfigurationException ;
81+ import java .awt .image .BufferedImage ;
8182import java .io .*;
8283import java .lang .ref .WeakReference ;
8384import java .net .*;
85+ import java .nio .ByteBuffer ;
86+ import java .nio .channels .Channels ;
87+ import java .nio .channels .FileChannel ;
8488import java .nio .file .Files ;
8589import java .nio .file .Path ;
90+ import java .nio .file .StandardOpenOption ;
8691import java .util .List ;
8792import java .util .*;
8893import java .util .concurrent .ConcurrentHashMap ;
@@ -779,6 +784,21 @@ private static Image loadWebPImage(InputStream input) throws IOException {
779784 }
780785 }
781786
787+ public static Image loadWebPImage (InputStream input ,
788+ int requestedWidth , int requestedHeight ,
789+ boolean preserveRatio , boolean smooth ) throws IOException {
790+ WebPImageReaderSpi spi = new WebPImageReaderSpi ();
791+ ImageReader reader = spi .createReaderInstance (null );
792+ BufferedImage bufferedImage ;
793+ try (ImageInputStream imageInput = ImageIO .createImageInputStream (input )) {
794+ reader .setInput (imageInput , true , true );
795+ bufferedImage = reader .read (0 , reader .getDefaultReadParam ());
796+ } finally {
797+ reader .dispose ();
798+ }
799+ return SwingFXUtils .toFXImage (bufferedImage , requestedWidth , requestedHeight , preserveRatio , smooth );
800+ }
801+
782802 public static Image loadImage (Path path ) throws Exception {
783803 try (InputStream input = Files .newInputStream (path )) {
784804 if ("webp" .equalsIgnoreCase (FileUtils .getExtension (path )))
@@ -792,6 +812,42 @@ public static Image loadImage(Path path) throws Exception {
792812 }
793813 }
794814
815+ public static Image loadImage (Path path ,
816+ int requestedWidth , int requestedHeight ,
817+ boolean preserveRatio , boolean smooth ) throws Exception {
818+ try (FileChannel channel = FileChannel .open (path , StandardOpenOption .READ )) {
819+ String ext = FileUtils .getExtension (path ).toLowerCase (Locale .ROOT );
820+ if ("webp" .equalsIgnoreCase (ext ))
821+ return loadWebPImage (Channels .newInputStream (channel ),
822+ requestedWidth , requestedHeight , preserveRatio , smooth );
823+
824+ if (!IMAGE_EXTENSIONS .contains (ext )) {
825+ byte [] header = new byte [12 ];
826+ ByteBuffer buffer = ByteBuffer .wrap (header );
827+ //noinspection StatementWithEmptyBody
828+ while (buffer .hasRemaining () && channel .read (buffer ) > 0 ) {
829+ }
830+
831+ channel .position (0L );
832+ if (!buffer .hasRemaining ()) {
833+ // WebP File
834+ if (header [0 ] == 'R' && header [1 ] == 'I' && header [2 ] == 'F' && header [3 ] == 'F' &&
835+ header [8 ] == 'W' && header [9 ] == 'E' && header [10 ] == 'B' && header [11 ] == 'P' ) {
836+ return loadWebPImage (Channels .newInputStream (channel ),
837+ requestedWidth , requestedHeight , preserveRatio , smooth );
838+ }
839+ }
840+ }
841+
842+ Image image = new Image (Channels .newInputStream (channel ),
843+ requestedWidth , requestedHeight ,
844+ preserveRatio , smooth );
845+ if (image .isError ())
846+ throw image .getException ();
847+ return image ;
848+ }
849+ }
850+
795851 public static Image loadImage (URL url ) throws Exception {
796852 URLConnection connection = NetworkUtils .createConnection (url );
797853 if (connection instanceof HttpURLConnection ) {
@@ -851,15 +907,37 @@ public static Image newBuiltinImage(String url, double requestedWidth, double re
851907 }
852908 }
853909
854- /**
855- * Load image from the internet. It will cache the data of images for the further usage.
856- * The cached data will be deleted when HMCL is closed or hidden.
857- *
858- * @param url the url of image. The image resource should be a file on the internet.
859- * @return the image resource within the jar.
860- */
861- public static Image newRemoteImage (String url ) {
862- return newRemoteImage (url , 0 , 0 , false , false , false );
910+ public static Task <Image > getRemoteImageTask (String url , int requestedWidth , int requestedHeight , boolean preserveRatio , boolean smooth ) {
911+ return Task .composeAsync (() -> {
912+ Path currentPath = remoteImageCache .get (url );
913+ if (currentPath != null ) {
914+ if (Files .isReadable (currentPath ))
915+ return Task .completed (currentPath );
916+
917+ // The file is unavailable or unreadable.
918+ remoteImageCache .remove (url );
919+
920+ try {
921+ Files .deleteIfExists (currentPath );
922+ } catch (IOException e ) {
923+ LOG .warning ("An exception encountered while deleting broken cached image file." , e );
924+ }
925+ }
926+
927+ Path newPath = Files .createTempFile ("hmcl-net-resource-cache-" , ".cache" );
928+ return new FileDownloadTask (NetworkUtils .toURL (url ), newPath .toFile ())
929+ .thenSupplyAsync (() -> {
930+ Path otherPath = remoteImageCache .putIfAbsent (url , newPath );
931+ if (otherPath == null )
932+ return newPath ;
933+ else {
934+ // The image has been loaded in another task. Delete the image here in order not to pollute the tmp folder.
935+ Files .delete (newPath );
936+ return otherPath ;
937+ }
938+
939+ });
940+ }).thenApplyAsync (path -> loadImage (path , requestedWidth , requestedHeight , preserveRatio , smooth ));
863941 }
864942
865943 /**
@@ -877,52 +955,17 @@ public static Image newRemoteImage(String url) {
877955 * the specified bounding box
878956 * @return the image resource within the jar.
879957 */
880- public static Image newRemoteImage (String url , double requestedWidth , double requestedHeight , boolean preserveRatio , boolean smooth , boolean backgroundLoading ) {
881- Path currentPath = remoteImageCache .get (url );
882- if (currentPath != null ) {
883- if (Files .isReadable (currentPath )) {
884- try (InputStream inputStream = Files .newInputStream (currentPath )) {
885- return new Image (inputStream , requestedWidth , requestedHeight , preserveRatio , smooth );
886- } catch (IOException e ) {
887- LOG .warning ("An exception encountered while reading data from cached image file." , e );
888- }
889- }
890-
891- // The file is unavailable or unreadable.
892- remoteImageCache .remove (url );
893-
894- try {
895- Files .deleteIfExists (currentPath );
896- } catch (IOException e ) {
897- LOG .warning ("An exception encountered while deleting broken cached image file." , e );
898- }
899- }
900-
901- Image image = new Image (url , requestedWidth , requestedHeight , preserveRatio , smooth , backgroundLoading );
902- image .progressProperty ().addListener ((observable , oldValue , newValue ) -> {
903- if (newValue .doubleValue () >= 1.0 && !image .isError () && image .getPixelReader () != null && image .getWidth () > 0.0 && image .getHeight () > 0.0 ) {
904- Task .runAsync (() -> {
905- Path newPath = Files .createTempFile ("hmcl-net-resource-cache-" , ".cache" );
906- try ( // Make sure the file is released from JVM before we put the path into remoteImageCache.
907- OutputStream outputStream = Files .newOutputStream (newPath );
908- PNGWriter writer = new PNGWriter (outputStream , PNGType .RGBA , PNGWriter .DEFAULT_COMPRESS_LEVEL )
909- ) {
910- writer .write (PNGJavaFXUtils .asArgbImage (image ));
911- } catch (IOException e ) {
912- try {
913- Files .delete (newPath );
914- } catch (IOException e2 ) {
915- e2 .addSuppressed (e );
916- throw e2 ;
917- }
918- throw e ;
919- }
920- if (remoteImageCache .putIfAbsent (url , newPath ) != null ) {
921- Files .delete (newPath ); // The image has been loaded in another task. Delete the image here in order not to pollute the tmp folder.
958+ public static ObservableValue <Image > newRemoteImage (String url , int requestedWidth , int requestedHeight , boolean preserveRatio , boolean smooth ) {
959+ SimpleObjectProperty <Image > image = new SimpleObjectProperty <>();
960+ getRemoteImageTask (url , requestedWidth , requestedHeight , preserveRatio , smooth )
961+ .whenComplete (Schedulers .javafx (), (result , exception ) -> {
962+ if (exception == null ) {
963+ image .set (result );
964+ } else {
965+ LOG .warning ("An exception encountered while loading remote image: " + url , exception );
922966 }
923- }).start ();
924- }
925- });
967+ })
968+ .start ();
926969 return image ;
927970 }
928971
0 commit comments