From 273ff6412ec3461900ba978776f24295bdac2207 Mon Sep 17 00:00:00 2001 From: hexaredecimal Date: Sat, 9 Aug 2025 11:25:51 +0200 Subject: [PATCH 1/2] editor: Fix the rampant memory usage that was caused by compiling per frame --- src/main/java/org/editor/CanvasFrame.java | 49 +++++--- .../java/org/editor/events/AccessEvents.java | 4 +- .../PiccodeBrushedMetalFilterModule.java | 112 ++++++++++++++++++ .../nativemods/PiccodeFilterModule.java | 57 +++++++++ .../editor/nativemods/PiccodeImageModule.java | 92 ++++++++++++++ 5 files changed, 294 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java create mode 100644 src/main/java/org/editor/nativemods/PiccodeFilterModule.java create mode 100644 src/main/java/org/editor/nativemods/PiccodeImageModule.java diff --git a/src/main/java/org/editor/CanvasFrame.java b/src/main/java/org/editor/CanvasFrame.java index 96f76cb..594f720 100644 --- a/src/main/java/org/editor/CanvasFrame.java +++ b/src/main/java/org/editor/CanvasFrame.java @@ -2,6 +2,7 @@ import com.vlsolutions.swing.docking.DockKey; import com.vlsolutions.swing.docking.Dockable; +import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; @@ -21,6 +22,7 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.Timer; @@ -46,6 +48,7 @@ public class CanvasFrame extends JPanel implements MouseListener, MouseMotionLis private int lastMouseX, lastMouseY; private BufferedImage gridImage; + private BufferedImage render; private Point lastDragPoint; public static Graphics2D gfx = null; private long lastTime; @@ -66,17 +69,13 @@ public class CanvasFrame extends JPanel implements MouseListener, MouseMotionLis private static CanvasFrame _the = null; private DockKey key = new DockKey("canvas"); - public static String code = null; - public static String file = null; - public static boolean start = false; - private CanvasFrame() { super(new BorderLayout()); this.setBackground(new Color(18, 18, 18)); this.addMouseListener(this); this.addMouseMotionListener(this); drawGrid(); - setPreferredSize(new Dimension(getWidth(), getHeight())); + compile(() -> null); Timer timer = new Timer(16, new ActionListener() { public void actionPerformed(ActionEvent e) { repaint(getVisibleRect()); @@ -108,30 +107,21 @@ protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; drawGrid(); - g2.drawImage(gridImage, 0, 0, null); // Smooth rendering g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(Color.BLACK); - gfx = g2; - if (start && file != null && code != null) { - AccessFrame.msgs.setText(""); - compileFrame(); - } + if (render != null) { + g2.drawImage(render, 0, 0, null); + } drawSelection(g2); if (showHighlight) { drawCrosshair(g2); } } - private PiccodeValue compileFrame() { - Context.top.resetContext(); - var result = Compiler.compile(file, code); - return result; - } - private void drawSelection(Graphics2D g2) { if (selecting && selectionStart != null && selectionEnd != null) { Point start = selectionStart; @@ -306,8 +296,33 @@ private void drawGrid() { lastGridOffsetX = offsetX; lastGridOffsetY = offsetY; + g2.dispose(); + } + + public void compile(Supplier fx) { + int width = getWidth(); + int height = getHeight(); + if (width <= 0 || height <= 0) { + return; + } + + render = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Get the Graphics2D object + gfx = render.createGraphics(); + + // Enable transparency by drawing a fully transparent background + gfx.setComposite(AlphaComposite.Clear); + gfx.fillRect(0, 0, width, height); + + gfx.setColor(Color.BLACK); + // Switch back to normal composite mode for drawing + gfx.setComposite(AlphaComposite.SrcOver); + + fx.get(); } + @Override public void mouseClicked(MouseEvent e) { } diff --git a/src/main/java/org/editor/events/AccessEvents.java b/src/main/java/org/editor/events/AccessEvents.java index 5b2f6da..41503e0 100644 --- a/src/main/java/org/editor/events/AccessEvents.java +++ b/src/main/java/org/editor/events/AccessEvents.java @@ -29,9 +29,7 @@ public static void compileAndRender(ActionEvent e) { var file = ed.file.toString(); var code = ed.textArea.getText(); - CanvasFrame.file = file; - CanvasFrame.code = code; - CanvasFrame.start = true; + CanvasFrame.the().compile(() -> Compiler.compile(file, code)); AccessFrame.writeSuccess("Compilation started: "); } diff --git a/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java b/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java new file mode 100644 index 0000000..f0e0843 --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeBrushedMetalFilterModule.java @@ -0,0 +1,112 @@ +package org.editor.nativemods; + +import com.jhlabs.image.BrushedMetalFilter; +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.HashMap; +import java.util.List; +import org.editor.CanvasFrame; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeBrushedMetalFilterModule { + + public static void addFunctions() { + NativeFunctionFactory.create("brush_metal_new", List.of(), (args, namedArgs, frame) -> { + var bmFilter = new BrushedMetalFilter(); + return new PiccodeReference(bmFilter); + }, null); + + NativeFunctionFactory.create("brush_metal_set_rad", List.of("filter", "rad"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var rad = namedArgs.get("rad"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, rad, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var radius = (int) (double) ((PiccodeNumber) rad).raw(); + + filter.setRadius(radius); + + return new PiccodeReference(_filter_object); + }, null); + + NativeFunctionFactory.create("brush_metal_set_amount", List.of("filter", "amount"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var amount = namedArgs.get("amount"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, amount, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var _amount = (int) (double) ((PiccodeNumber) amount).raw(); + + filter.setRadius(_amount); + + return new PiccodeReference(_filter_object); + }, null); + + NativeFunctionFactory.create("brush_metal_set_shine", List.of("filter", "shine"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var shine = namedArgs.get("shine"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, shine, Type.NUMBER); + + var obj = (PiccodeReference) _filter; + var _filter_object = obj.deref(); + if (!(_filter_object instanceof BrushedMetalFilter)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + var filter = (BrushedMetalFilter) _filter_object; + var _shine = (int) (double) ((PiccodeNumber) shine).raw(); + + filter.setRadius(_shine); + + return new PiccodeReference(_filter_object); + }, null); + + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodeFilterModule.java b/src/main/java/org/editor/nativemods/PiccodeFilterModule.java new file mode 100644 index 0000000..278ae3d --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeFilterModule.java @@ -0,0 +1,57 @@ +package org.editor.nativemods; + +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.util.List; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeFilterModule { + + public static void addFunctions() { + NativeFunctionFactory.create("filter_apply", List.of("filter", "image"), (args, namedArgs, frame) -> { + var _filter = namedArgs.get("filter"); + var image = namedArgs.get("image"); + + var ctx = frame == null + ? Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, _filter, Type.REFERENCE); + PiccodeValue.verifyType(caller, image, Type.REFERENCE); + + var obj = (PiccodeReference) _filter; + var img = (PiccodeReference) image; + var _filter_object = obj.deref(); + var _buffered_image = img.deref(); + + if (!(_filter_object instanceof BufferedImageOp)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Filter is not a correct object."); + } + + if (!(_buffered_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); + } + + + var filter = (BufferedImageOp) _filter_object; + var _image = (BufferedImage) _buffered_image; + + var result = filter.filter(_image, null); + + return new PiccodeReference(result); + }, null); + + + } + +} diff --git a/src/main/java/org/editor/nativemods/PiccodeImageModule.java b/src/main/java/org/editor/nativemods/PiccodeImageModule.java new file mode 100644 index 0000000..e40654f --- /dev/null +++ b/src/main/java/org/editor/nativemods/PiccodeImageModule.java @@ -0,0 +1,92 @@ +package org.editor.nativemods; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.List; +import org.editor.CanvasFrame; +import org.piccode.rt.Context; +import org.piccode.rt.PiccodeException; +import org.piccode.rt.PiccodeNumber; +import org.piccode.rt.PiccodeObject; +import org.piccode.rt.PiccodeReference; +import org.piccode.rt.PiccodeString; +import org.piccode.rt.PiccodeUnit; +import org.piccode.rt.PiccodeValue; +import org.piccode.rt.PiccodeValue.Type; +import org.piccode.rt.modules.NativeFunctionFactory; + +/** + * + * @author hexaredecimal + */ +public class PiccodeImageModule { + + public static void addFunctions() { + + NativeFunctionFactory.create("image_new", List.of("w", "h"), (args, namedArgs, frame) -> { + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + + var image = new BufferedImage(_w, _h, BufferedImage.TYPE_INT_ARGB); + return new PiccodeReference(image); + }, null); + + NativeFunctionFactory.create("image_new_typed", List.of("w", "h", "type"), (args, namedArgs, frame) -> { + var w = namedArgs.get("w"); + var h = namedArgs.get("h"); + var type = namedArgs.get("type"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, w, Type.NUMBER); + PiccodeValue.verifyType(caller, h, Type.NUMBER); + PiccodeValue.verifyType(caller, type, Type.NUMBER); + + var _w = (int) (double) ((PiccodeNumber) w).raw(); + var _h = (int) (double) ((PiccodeNumber) h).raw(); + var _type = (int) (double) ((PiccodeNumber) type).raw(); + + var image = new BufferedImage(_w, _h, _type); + return new PiccodeReference(image); + }, null); + + NativeFunctionFactory.create("image_get_context", List.of("img"), (args, namedArgs, frame) -> { + var img = namedArgs.get("img"); + + var ctx = frame == null ? + Context.top + : Context.getContextAt(frame); + var caller = ctx.getTopFrame().caller; + + PiccodeValue.verifyType(caller, img, Type.REFERENCE); + + var _buffered_image = ((PiccodeReference)img).deref(); + + if (!(_buffered_image instanceof BufferedImage)) { + throw new PiccodeException(caller.file, caller.line, caller.column, "Expected a buffer image. Found " + _buffered_image); + } + + var bufferedmage = (BufferedImage) _buffered_image; + var gfx = bufferedmage.createGraphics(); + return new PiccodeReference(gfx); + }, null); + + } + +} From 6e74751eed770e677b822c027f508adf51bfcb16 Mon Sep 17 00:00:00 2001 From: hexaredecimal Date: Sat, 9 Aug 2025 11:39:02 +0200 Subject: [PATCH 2/2] pom.xml: Add JHLabs filters --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 0c67325..d66ea4c 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,12 @@ PiccodeScript main-SNAPSHOT + + + com.github.Glimmr-Lang + JHLabs + main-SNAPSHOT +