diff --git a/effect/apply_effect.py b/effect/apply_effect.py index 60d46c4..4c6f395 100644 --- a/effect/apply_effect.py +++ b/effect/apply_effect.py @@ -80,13 +80,15 @@ def process_effect(instr: dict): raise ImportError(f"Failed to load dependency {dependency}: {e}") # Process image - image_path = project_path / f"stroke/{stroke_id}_input.png" - if not image_path.is_file(): - raise FileNotFoundError(f"Image file not found at {image_path}") - - with Image.open(image_path) as img: - req["stroke_input"]["image_rgba"] = np.array(img.convert("RGBA")) # Ensure the image is in RGBA format + import base64 + b64_data = instr.get("stroke_input", {}).get("image_rgba") + img_width = instr.get("stroke_input", {}).get("width") + img_height = instr.get("stroke_input", {}).get("height") + if b64_data is None or img_width is None or img_height is None: + raise ValueError("image_rgba, width, or height missing from stroke_input in instructions.") + img_bytes = base64.b64decode(b64_data) + req["stroke_input"]["image_rgba"] = np.frombuffer(img_bytes, dtype=np.uint8).reshape(img_height, img_width, 4) # Process user_input and stroke_input @@ -121,7 +123,7 @@ def process_effect(instr: dict): # Add a few flags needed to apply the effects req["effect_id"] = effect_id req["effect_script_path"] = effect_path / f"{effect_id}.py" - req["stroke_output_path"] = project_path / f"stroke/{stroke_id}_output.png" + return req @@ -141,9 +143,12 @@ def apply_effect(req: dict): mask = np.all(new_image == input_image, axis=-1) new_image[mask] = [0, 0, 0, 0] # Set differing pixels to [0, 0, 0, 0] - output_path = req["stroke_output_path"] - output_path.parent.mkdir(parents=True, exist_ok=True) # Ensure the directory exists - Image.fromarray(new_image.astype(np.uint8)).save(output_path, format="PNG") + + import base64 + result_bytes = new_image.astype(np.uint8).tobytes() + req["stroke_result_b64"] = base64.b64encode(result_bytes).decode("ascii") + req["stroke_result_width"] = int(new_image.shape[1]) + req["stroke_result_height"] = int(new_image.shape[0]) return True @@ -228,7 +233,14 @@ def flush(self): #Apply the effect success = apply_effect(data) + + + instructions["effect_success"] = success + if success: + instructions["stroke_result_b64"] = data.get("stroke_result_b64", "") + instructions["stroke_result_width"] = data.get("stroke_result_width", 0) + instructions["stroke_result_height"] = data.get("stroke_result_height", 0) dump_json(instructions, args.stroke_path) except Exception as e: diff --git a/src/StrokeManager.java b/src/StrokeManager.java index 6eabdab..f3a8451 100644 --- a/src/StrokeManager.java +++ b/src/StrokeManager.java @@ -506,23 +506,35 @@ public String createStroke(Effect effect, Map parameters) { } DebugLogger.log("=== END FINAL SAVED JSON DEBUG ===\n"); - // Save input image + + + + + // Embed image as base64 in JSON — no PNG file written to disk if (app.getCurrentImage() != null) { - String inputPath = strokeDir.getPath() + "/" + strokeId + "_input.png"; - app.getCurrentImage().save(inputPath); - - // Update JSON with image paths + PImage img = app.getCurrentImage(); + img.loadPixels(); + int W = img.width; + int H = img.height; + byte[] rgba = new byte[W * H * 4]; + for (int i = 0; i < W * H; i++) { + int px = img.pixels[i]; + rgba[i*4] = (byte)((px >> 16) & 0xFF); // R + rgba[i*4+1] = (byte)((px >> 8) & 0xFF); // G + rgba[i*4+2] = (byte)( px & 0xFF); // B + rgba[i*4+3] = (byte)((px >> 24) & 0xFF); // A + } + String b64 = java.util.Base64.getEncoder().encodeToString(rgba); instructions = app.loadJSONObject(instructionsPath); JSONObject strokeInput = instructions.getJSONObject("stroke_input"); - strokeInput.setString("input_location", inputPath); - strokeInput.setString( - "output_location", - strokeDir.getPath() + "/" + strokeId + "_output.png" - ); + strokeInput.setString("image_rgba", b64); + strokeInput.setInt("width", W); + strokeInput.setInt("height", H); instructions.setJSONObject("stroke_input", strokeInput); app.saveJSONObject(instructions, instructionsPath); } - + + // Set current stroke index currentStrokeIndex = strokes.size() - 1; @@ -1254,19 +1266,16 @@ private boolean executeApplyEffectScript(String instructionsFilePath) throws Int // Check the effect_success flag in the JSON file JSONObject instructions = app.loadJSONObject(instructionsFilePath); - String projectId = instructions.getString("project_id", ""); - String strokeId = instructions.getString("stroke_id", ""); - String outputPath = "project/" + projectId + "/stroke/" + strokeId + "_output.png"; - File outputFile = new File(outputPath); String effectSuccess = instructions.getString("effect_success", "false"); - boolean success = exitCode == 0 && "true".equals(effectSuccess) && outputFile.exists(); + boolean success = exitCode == 0 && "true".equals(effectSuccess); if (!success) { System.err.println("Python script execution failed with exit code: " + exitCode); System.err.println("Effect success flag: " + effectSuccess); - System.err.println("Output file exists: " + outputFile.exists()); - + + + // Read error log if (stderrLog.exists()) { try (BufferedReader reader = new BufferedReader(new FileReader(stderrLog))) { @@ -1347,19 +1356,33 @@ public boolean applyEffectToCanvas(String strokeId) { } // Get the effect output image (just the effect, not layered) - String outputPath = "project/" + projectId + "/stroke/" + strokeId + "_output.png"; - PImage effectImage = app.loadImage(outputPath); - - if (effectImage == null) { - System.err.println("Failed to load effect output image: " + outputPath); + // Decode effect output from base64 stored in the instructions JSON + String b64result = instructions.getString("stroke_result_b64", ""); + int rW = instructions.getInt("stroke_result_width", 0); + int rH = instructions.getInt("stroke_result_height", 0); + + if (b64result.isEmpty() || rW == 0 || rH == 0) { + System.err.println("No result data found in instructions JSON for stroke: " + strokeId); JOptionPane.showMessageDialog( - null, - "Failed to load effect output image.", - "Error", + null, + "Failed to load effect output: result data missing from JSON.\nPlease re-run the effect.", + "Error", JOptionPane.ERROR_MESSAGE ); return false; } + + byte[] resultBytes = java.util.Base64.getDecoder().decode(b64result); + PImage effectImage = app.createImage(rW, rH, PConstants.ARGB); + effectImage.loadPixels(); + for (int i = 0; i < rW * rH; i++) { + int r = resultBytes[i*4] & 0xFF; + int g = resultBytes[i*4+1] & 0xFF; + int b = resultBytes[i*4+2] & 0xFF; + int a = resultBytes[i*4+3] & 0xFF; + effectImage.pixels[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + effectImage.updatePixels(); // Get the current image (which may already have previous effects applied) PImage currentImage = app.getCurrentImage(); @@ -1450,19 +1473,11 @@ public boolean deleteStroke(String strokeId) { // Delete stroke files String strokeDir = "project/" + projectId + "/stroke/"; File instructionsFile = new File(strokeDir + strokeId + "_instructions.json"); - File inputFile = new File(strokeDir + strokeId + "_input.png"); - File outputFile = new File(strokeDir + strokeId + "_output.png"); - + boolean success = true; if (instructionsFile.exists()) { success &= instructionsFile.delete(); } - if (inputFile.exists()) { - success &= inputFile.delete(); - } - if (outputFile.exists()) { - success &= outputFile.delete(); - } // Update current stroke index if needed if (currentStrokeIndex >= strokes.size()) {