Add JavaFX menu renderer and player window
- RcoRenderer implements Display: draws the live RCO object tree onto a Canvas, decodes PNG textures, handles focus and resource changes (thread-safe via Platform.runLater). - PageObject now implements root/children semantics and open/close/display so opening a page adds its objects to the renderer. - Object getImage() returns the ImageObject so the UI can decode PNGs. - PlayerApp wires menu + video: loads the RCO, runs the VSMX script, executes onAutoPlay, plays video via PlaybackEngine with YUV->ARGB blitting and PTS pacing, and routes arrow/Enter keys to the controller. - Runs end-to-end on Batman Begins (window, menu load, playback).
This commit is contained in:
@@ -1,14 +1,16 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
package dev.quoije.umd.rco;
|
package dev.quoije.umd.rco;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
|
||||||
/** The rendering surface backing an RCO menu. Implemented by the UI layer. */
|
/** The rendering surface backing an RCO menu. Implemented by the UI layer. */
|
||||||
public interface Display {
|
public interface Display {
|
||||||
|
|
||||||
void setFocus(IDisplay display);
|
void setFocus(IDisplay display);
|
||||||
|
|
||||||
void add(IDisplay display);
|
void add(VsmxBaseObject object);
|
||||||
|
|
||||||
void remove(IDisplay display);
|
void remove(VsmxBaseObject object);
|
||||||
|
|
||||||
void repaint();
|
void repaint();
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class ButtonObject extends BasePositionObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getImage() {
|
public Object getImage() {
|
||||||
return image.getImage() == null ? null : image.getImage().getImage();
|
return image.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ public class IconObject extends BasePositionObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getImage() {
|
public Object getImage() {
|
||||||
return unknownImage16.getImage() == null ? null : unknownImage16.getImage().getImage();
|
return unknownImage16.getImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
package dev.quoije.umd.rco.object;
|
package dev.quoije.umd.rco.object;
|
||||||
|
|
||||||
import dev.quoije.umd.rco.ObjectField;
|
import dev.quoije.umd.rco.ObjectField;
|
||||||
|
import dev.quoije.umd.rco.RcoFile;
|
||||||
import dev.quoije.umd.rco.type.EventType;
|
import dev.quoije.umd.rco.type.EventType;
|
||||||
import dev.quoije.umd.rco.type.IntType;
|
import dev.quoije.umd.rco.type.IntType;
|
||||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.Resource;
|
||||||
|
|
||||||
/** A menu page. */
|
/** A menu page. */
|
||||||
public class PageObject extends BaseObject {
|
public class PageObject extends BaseObject {
|
||||||
@@ -15,8 +20,66 @@ public class PageObject extends BaseObject {
|
|||||||
@ObjectField(order = 4) public EventType onContextMenu;
|
@ObjectField(order = 4) public EventType onContextMenu;
|
||||||
@ObjectField(order = 5) public EventType onActivate;
|
@ObjectField(order = 5) public EventType onActivate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VsmxBaseObject createVsmxObject(VsmxInterpreter interpreter, VsmxBaseObject parent, RcoFile.Entry entry) {
|
||||||
|
VsmxBaseObject object = super.createVsmxObject(interpreter, parent, entry);
|
||||||
|
VsmxObject root = new VsmxObject(interpreter, null);
|
||||||
|
object.setPropertyValue(Resource.rootName, root);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void display(VsmxBaseObject object) {
|
||||||
|
if (!object.hasPropertyValue(Resource.childrenName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VsmxBaseObject children = object.getPropertyValue(Resource.childrenName);
|
||||||
|
for (String childName : children.getPropertyNames()) {
|
||||||
|
VsmxBaseObject child = children.getPropertyValue(childName);
|
||||||
|
display.add(child);
|
||||||
|
display(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hide(VsmxBaseObject object) {
|
||||||
|
if (!object.hasPropertyValue(Resource.childrenName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VsmxBaseObject children = object.getPropertyValue(Resource.childrenName);
|
||||||
|
for (String childName : children.getPropertyNames()) {
|
||||||
|
VsmxBaseObject child = children.getPropertyValue(childName);
|
||||||
|
display.remove(child);
|
||||||
|
hide(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BasePositionObject getFirstButton(VsmxBaseObject object) {
|
||||||
|
if (!object.hasPropertyValue(Resource.childrenName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
VsmxBaseObject children = object.getPropertyValue(Resource.childrenName);
|
||||||
|
for (String childName : children.getPropertyNames()) {
|
||||||
|
VsmxBaseObject child = children.getPropertyValue(childName);
|
||||||
|
if (child instanceof VsmxNativeObject nativeObject && nativeObject.getObject() instanceof UButtonObject button) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
BasePositionObject button = getFirstButton(child);
|
||||||
|
if (button != null) {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public VsmxBaseObject open(VsmxBaseObject object) {
|
public VsmxBaseObject open(VsmxBaseObject object) {
|
||||||
trigger(onInit);
|
trigger(onInit);
|
||||||
|
|
||||||
|
if (display != null && object.hasPropertyValue(Resource.rootName)) {
|
||||||
|
display(object.getPropertyValue(Resource.rootName));
|
||||||
|
BasePositionObject button = getFirstButton(object.getPropertyValue(Resource.rootName));
|
||||||
|
if (button != null) {
|
||||||
|
button.setFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +89,9 @@ public class PageObject extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void close(VsmxBaseObject object) {
|
public void close(VsmxBaseObject object) {
|
||||||
|
if (display != null && object.hasPropertyValue(Resource.rootName)) {
|
||||||
|
hide(object.getPropertyValue(Resource.rootName));
|
||||||
|
}
|
||||||
trigger(onCancel);
|
trigger(onCancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ public class PlaneObject extends BasePositionObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getImage() {
|
public Object getImage() {
|
||||||
return image.getImage() == null ? null : image.getImage().getImage();
|
return image.getImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class UButtonObject extends BasePositionObject {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getImage() {
|
public Object getImage() {
|
||||||
return image.getImage() == null ? null : image.getImage().getImage();
|
return image.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,36 +1,194 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
package dev.quoije.umd.ui;
|
package dev.quoije.umd.ui;
|
||||||
|
|
||||||
|
import dev.quoije.umd.iso.UmdIsoFile;
|
||||||
|
import dev.quoije.umd.iso.UmdIsoReader;
|
||||||
|
import dev.quoije.umd.media.YuvConverter;
|
||||||
|
import dev.quoije.umd.player.PlaybackEngine;
|
||||||
|
import dev.quoije.umd.playlist.Playlist;
|
||||||
|
import dev.quoije.umd.rco.RcoFile;
|
||||||
|
import dev.quoije.umd.rco.vsmx.Vsmx;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.Controller;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.GlobalVariables;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.Math;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.MenuHost;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.MoviePlayer;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.Resource;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.image.PixelFormat;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.util.List;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/** JavaFX application entry point for the UMD player. */
|
/** JavaFX application entry point for the UMD player. */
|
||||||
public final class PlayerApp extends Application {
|
public final class PlayerApp extends Application {
|
||||||
|
|
||||||
|
private static final int WIDTH = 854;
|
||||||
|
private static final int HEIGHT = 480;
|
||||||
|
|
||||||
|
private Canvas menuCanvas;
|
||||||
|
private Canvas videoCanvas;
|
||||||
|
private RcoRenderer renderer;
|
||||||
|
private volatile Controller controller;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
List<String> params = getParameters().getRaw();
|
String isoPath = getParameters().getRaw().isEmpty() ? null : getParameters().getRaw().get(0);
|
||||||
String imagePath = params.isEmpty() ? null : params.get(0);
|
|
||||||
|
|
||||||
StackPane root = new StackPane(new Label("useless-umd-player\n" + (imagePath == null ? "(no iso)" : imagePath)));
|
menuCanvas = new Canvas(WIDTH, HEIGHT);
|
||||||
|
videoCanvas = new Canvas(WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
StackPane root = new StackPane(videoCanvas, menuCanvas);
|
||||||
|
Scene scene = new Scene(root, WIDTH, HEIGHT);
|
||||||
stage.setTitle("useless-umd-player");
|
stage.setTitle("useless-umd-player");
|
||||||
stage.setScene(new Scene(root, 854, 480));
|
stage.setScene(scene);
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
// Temporary smoke test: auto-close after 2 seconds.
|
scene.setOnKeyPressed(event -> handleKey(event.getCode()));
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
if (isoPath != null) {
|
||||||
Thread.sleep(2000);
|
String path = isoPath;
|
||||||
} catch (InterruptedException ignored) {
|
Thread loader = new Thread(() -> loadMenu(path), "menu-loader");
|
||||||
|
loader.setDaemon(true);
|
||||||
|
loader.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleKey(KeyCode code) {
|
||||||
|
Controller c = controller;
|
||||||
|
if (c == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (code) {
|
||||||
|
case UP -> c.onUp();
|
||||||
|
case DOWN -> c.onDown();
|
||||||
|
case LEFT -> c.onLeft();
|
||||||
|
case RIGHT -> c.onRight();
|
||||||
|
case ENTER -> c.onPush();
|
||||||
|
default -> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMenu(String isoPath) {
|
||||||
|
try (UmdIsoReader iso = new UmdIsoReader(Path.of(isoPath))) {
|
||||||
|
Playlist playlist = Playlist.parse(iso);
|
||||||
|
byte[] rcoData = readFile(iso, "UMD_VIDEO/RESOURCE/EN100000.RCO");
|
||||||
|
if (rcoData == null) {
|
||||||
|
System.err.println("EN100000.RCO not found");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Platform.exit();
|
|
||||||
}, "smoke-close").start();
|
RcoFile rco = new RcoFile(rcoData);
|
||||||
|
if (!rco.isValid()) {
|
||||||
|
System.err.println("RCO invalid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vsmx vsmx = new Vsmx(rco.vsmxData(), "menu");
|
||||||
|
VsmxInterpreter interpreter = new VsmxInterpreter();
|
||||||
|
interpreter.setVsmx(vsmx);
|
||||||
|
|
||||||
|
renderer = new RcoRenderer(menuCanvas);
|
||||||
|
|
||||||
|
MenuHost menuHost = new MenuHost() {
|
||||||
|
@Override
|
||||||
|
public void playMovie(int playListNumber, int chapterNumber, int videoNumber, int audioNumber,
|
||||||
|
int audioFlag, int subtitleNumber, int subtitleFlag) {
|
||||||
|
Playlist.Stream stream = playlist.streamForPlayListNumber(playListNumber);
|
||||||
|
if (stream != null) {
|
||||||
|
startPlayback(isoPath, stream.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopMovie() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeResource(String resourceName) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
VsmxNativeObject globals = GlobalVariables.create(interpreter, WIDTH, HEIGHT);
|
||||||
|
VsmxNativeObject controllerObject = Controller.create(interpreter, menuHost, "EN100000");
|
||||||
|
VsmxNativeObject movieplayer = MoviePlayer.create(interpreter, menuHost, controllerObject);
|
||||||
|
VsmxNativeObject resource = Resource.create(interpreter, renderer, controllerObject, rco);
|
||||||
|
VsmxNativeObject math = Math.create(interpreter);
|
||||||
|
globals.setPropertyValue(Controller.objectName, controllerObject);
|
||||||
|
globals.setPropertyValue(MoviePlayer.objectName, movieplayer);
|
||||||
|
globals.setPropertyValue(Resource.objectName, resource);
|
||||||
|
globals.setPropertyValue(Math.objectName, math);
|
||||||
|
|
||||||
|
interpreter.run(globals);
|
||||||
|
controller = (Controller) controllerObject.getObject();
|
||||||
|
controller.callCallback(interpreter, "onAutoPlay", null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPlayback(String isoPath, String streamName) {
|
||||||
|
Thread player = new Thread(() -> {
|
||||||
|
try (UmdIsoReader iso = new UmdIsoReader(Path.of(isoPath));
|
||||||
|
PlaybackEngine engine = PlaybackEngine.open(iso, streamName, 0)) {
|
||||||
|
|
||||||
|
long basePts = -1;
|
||||||
|
long baseNanos = -1;
|
||||||
|
|
||||||
|
PlaybackEngine.VideoFrame frame;
|
||||||
|
while ((frame = engine.nextVideoFrame()) != null) {
|
||||||
|
int[] argb = YuvConverter.yuvj420ToArgb(frame.width(), frame.height(),
|
||||||
|
frame.yPlane(), frame.yStride(),
|
||||||
|
frame.uPlane(), frame.uStride(),
|
||||||
|
frame.vPlane(), frame.vStride());
|
||||||
|
blitVideo(argb, frame.width(), frame.height());
|
||||||
|
|
||||||
|
if (basePts < 0) {
|
||||||
|
basePts = frame.pts();
|
||||||
|
baseNanos = System.nanoTime();
|
||||||
|
}
|
||||||
|
long targetNanos = baseNanos + (frame.pts() - basePts) * 1_000_000_000L / PlaybackEngine.MPEG_TIMESTAMP;
|
||||||
|
long sleepNanos = targetNanos - System.nanoTime();
|
||||||
|
if (sleepNanos > 0) {
|
||||||
|
long millis = sleepNanos / 1_000_000;
|
||||||
|
int nanos = (int) (sleepNanos % 1_000_000);
|
||||||
|
Thread.sleep(millis, nanos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, "video-player");
|
||||||
|
player.setDaemon(true);
|
||||||
|
player.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void blitVideo(int[] argb, int width, int height) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
videoCanvas.getGraphicsContext2D()
|
||||||
|
.getPixelWriter()
|
||||||
|
.setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), argb, 0, width);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] readFile(UmdIsoReader iso, String path) throws IOException {
|
||||||
|
if (!iso.hasFile(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (UmdIsoFile file = iso.getFile(path)) {
|
||||||
|
byte[] buffer = new byte[(int) file.length()];
|
||||||
|
file.readFully(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.ui;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.Display;
|
||||||
|
import dev.quoije.umd.rco.IDisplay;
|
||||||
|
import dev.quoije.umd.rco.object.ImageObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX renderer for an RCO menu: draws the live object tree onto a Canvas.
|
||||||
|
*/
|
||||||
|
public final class RcoRenderer implements Display {
|
||||||
|
|
||||||
|
private final Canvas canvas;
|
||||||
|
private final List<IDisplay> displays = new ArrayList<>();
|
||||||
|
private final Map<ImageObject, Image> imageCache = new HashMap<>();
|
||||||
|
private IDisplay focus;
|
||||||
|
|
||||||
|
public RcoRenderer(Canvas canvas) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFocus(IDisplay display) {
|
||||||
|
this.focus = display;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(VsmxBaseObject object) {
|
||||||
|
IDisplay display = unwrap(object);
|
||||||
|
if (display != null) {
|
||||||
|
synchronized (displays) {
|
||||||
|
if (!displays.contains(display)) {
|
||||||
|
displays.add(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(VsmxBaseObject object) {
|
||||||
|
IDisplay display = unwrap(object);
|
||||||
|
if (display != null) {
|
||||||
|
synchronized (displays) {
|
||||||
|
displays.remove(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void repaint() {
|
||||||
|
Platform.runLater(this::render);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeResource() {
|
||||||
|
synchronized (displays) {
|
||||||
|
displays.clear();
|
||||||
|
}
|
||||||
|
imageCache.clear();
|
||||||
|
focus = null;
|
||||||
|
Platform.runLater(this::render);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IDisplay unwrap(VsmxBaseObject object) {
|
||||||
|
if (object instanceof VsmxNativeObject nativeObject && nativeObject.getObject() instanceof IDisplay display) {
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void render() {
|
||||||
|
List<IDisplay> snapshot;
|
||||||
|
synchronized (displays) {
|
||||||
|
snapshot = new ArrayList<>(displays);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsContext g = canvas.getGraphicsContext2D();
|
||||||
|
double w = canvas.getWidth();
|
||||||
|
double h = canvas.getHeight();
|
||||||
|
g.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
for (IDisplay display : snapshot) {
|
||||||
|
draw(g, display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw(GraphicsContext g, IDisplay display) {
|
||||||
|
Image image = decode(display.getImage());
|
||||||
|
if (image == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double alpha = display.getAlpha();
|
||||||
|
g.setGlobalAlpha(alpha);
|
||||||
|
|
||||||
|
int drawWidth = display.getWidth();
|
||||||
|
int drawHeight = display.getHeight();
|
||||||
|
double w = drawWidth > 0 ? drawWidth : image.getWidth();
|
||||||
|
double h = drawHeight > 0 ? drawHeight : image.getHeight();
|
||||||
|
g.drawImage(image, display.getX(), display.getY(), w, h);
|
||||||
|
g.setGlobalAlpha(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image decode(Object object) {
|
||||||
|
if (object instanceof Image image) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
if (object instanceof ImageObject imageObject) {
|
||||||
|
return imageCache.computeIfAbsent(imageObject, this::decodePng);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image decodePng(ImageObject imageObject) {
|
||||||
|
byte[] data = imageObject.getData();
|
||||||
|
if (data == null || data.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
|
||||||
|
return new Image(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user