From f43c642875cf11b0d26ae9b273cce685c1e2d976 Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Sun, 28 Sep 2014 01:54:49 -0500 Subject: [PATCH 01/27] Quantum leap forward with a new image conversion wizard! --- .../badvision/outlaweditor/MythosEditor.java | 18 +- .../outlaweditor/apple/AppleImageEditor.java | 47 +- .../apple/FloydSteinbergDither.java | 602 +++++++++--------- .../ui/ImageConversionPostAction.java | 10 + .../badvision/outlaweditor/ui/UIAction.java | 56 +- .../impl/ImageConversionWizardController.java | 349 ++++++++++ .../resources/fxml/imageConversionWizard.fxml | 219 +++++++ .../styles/imageconversionwizard.css | 3 + 8 files changed, 921 insertions(+), 383 deletions(-) create mode 100644 OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/ImageConversionPostAction.java create mode 100644 OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/impl/ImageConversionWizardController.java create mode 100644 OutlawEditor/src/main/resources/fxml/imageConversionWizard.fxml create mode 100644 OutlawEditor/src/main/resources/styles/imageconversionwizard.css diff --git a/OutlawEditor/src/main/java/org/badvision/outlaweditor/MythosEditor.java b/OutlawEditor/src/main/java/org/badvision/outlaweditor/MythosEditor.java index 5f32537c..73219d61 100644 --- a/OutlawEditor/src/main/java/org/badvision/outlaweditor/MythosEditor.java +++ b/OutlawEditor/src/main/java/org/badvision/outlaweditor/MythosEditor.java @@ -7,7 +7,6 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; @@ -46,7 +45,6 @@ public class MythosEditor { public void show() { primaryStage = new Stage(); - FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/MythosScriptEditor.fxml")); Map properties = new HashMap<>(); properties.put(MythosScriptEditorController.ONLOAD_SCRIPT, generateLoadScript()); @@ -61,22 +59,16 @@ public class MythosEditor { throw new RuntimeException(exception); } - primaryStage.setOnCloseRequest(new EventHandler() { - @Override - public void handle(final WindowEvent t) { - t.consume(); - } + primaryStage.setOnCloseRequest((final WindowEvent t) -> { + t.consume(); }); primaryStage.show(); } public void close() { - javafx.application.Platform.runLater(new Runnable() { - @Override - public void run() { - primaryStage.getScene().getRoot().setDisable(true); - primaryStage.close(); - } + javafx.application.Platform.runLater(() -> { + primaryStage.getScene().getRoot().setDisable(true); + primaryStage.close(); }); } diff --git a/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/AppleImageEditor.java b/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/AppleImageEditor.java index c2589646..dd0a314f 100644 --- a/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/AppleImageEditor.java +++ b/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/AppleImageEditor.java @@ -8,7 +8,6 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javafx.event.EventHandler; -import javafx.scene.Node; import javafx.scene.control.Menu; import javafx.scene.image.ImageView; import javafx.scene.image.WritableImage; @@ -23,7 +22,6 @@ import org.badvision.outlaweditor.FileUtils; import org.badvision.outlaweditor.ImageEditor; import org.badvision.outlaweditor.Platform; import org.badvision.outlaweditor.ui.UIAction; -import org.badvision.outlaweditor.data.DataObserver; import org.badvision.outlaweditor.data.TileMap; import org.badvision.outlaweditor.data.xml.Image; import org.badvision.outlaweditor.data.xml.PlatformData; @@ -65,11 +63,8 @@ public class AppleImageEditor extends ImageEditor implements EventHandler() { - @Override - public void observedObjectChanged(FillPattern object) { - changeCurrentPattern(object); - } + FillPattern.buildMenu(tilePatternMenu, (FillPattern object) -> { + changeCurrentPattern(object); }); } @@ -141,16 +136,14 @@ public class AppleImageEditor extends ImageEditor implements EventHandler !(n == screen)).forEach((n) -> { n.setVisible(!n.isVisible()); - } + }); } @Override @@ -393,9 +386,10 @@ public class AppleImageEditor extends ImageEditor implements EventHandler { + rescale(newWidth, newHeight); + }, () -> { + crop(newWidth, newHeight); + }); } /** diff --git a/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/FloydSteinbergDither.java b/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/FloydSteinbergDither.java index 3bd0a26e..eb851650 100644 --- a/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/FloydSteinbergDither.java +++ b/OutlawEditor/src/main/java/org/badvision/outlaweditor/apple/FloydSteinbergDither.java @@ -5,22 +5,17 @@ import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; -import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.SnapshotParameters; import javafx.scene.canvas.Canvas; -import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.image.PixelFormat; import javafx.scene.image.PixelReader; import javafx.scene.image.PixelWriter; import javafx.scene.image.WritableImage; -import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.Text; -import javafx.stage.Stage; +import javax.swing.Renderer; +import org.badvision.outlaweditor.Platform; import static org.badvision.outlaweditor.apple.AppleNTSCGraphics.hgrToDhgr; /* Copyright (c) 2013 the authors listed at the following URL, and/or @@ -53,321 +48,309 @@ import static org.badvision.outlaweditor.apple.AppleNTSCGraphics.hgrToDhgr; */ public class FloydSteinbergDither { - static final int totalPasses = 1; - static final int nonErrorPasses = 1; + int byteRenderWidth; + final int errorWindow = 6; + final int overlap = 2; + final int pixelShift = -2; + WritableImage source; + byte[] screen; + Platform platform; + int bufferWidth; + int height; + int divisor; + int[][] coefficients; + boolean resetOutput = true; - public static interface DitherCallback { - - public void ditherCompleted(byte[] data); + public FloydSteinbergDither(org.badvision.outlaweditor.Platform platform) { + this.platform = platform; + byteRenderWidth = platform == org.badvision.outlaweditor.Platform.AppleII_DHGR ? 7 : 14; } - public static void floydSteinbergDither( - Image img, - final org.badvision.outlaweditor.Platform platform, - final int startX, - final int startY, - final int width, - final int height, - final byte[] screen, - final int bufferWidth, - final DitherCallback callback) { - final AppleImageRenderer renderer = (AppleImageRenderer) platform.imageRenderer; - final int errorWindow = 6; - final int overlap = 2; - final int pixelShift = -2; - int byteRenderWidth = platform == org.badvision.outlaweditor.Platform.AppleII_DHGR ? 7 : 14; - final WritableImage source = getScaledImage(img, width * byteRenderWidth, height); - AnchorPane pane = new AnchorPane(); - Scene s = new Scene(pane); - final ImageView previewImage = new ImageView(source); - previewImage.setLayoutX(0); - previewImage.setLayoutY(0); - pane.getChildren().add(previewImage); - final Text status = new Text("Status"); - status.setLayoutX(100); - status.setLayoutY(40); - status.setFont(Font.font("Arial", 18)); - status.setStroke(Color.BLACK); - status.setEffect(new DropShadow(10.0, 0, 0, Color.WHITE)); - pane.getChildren().add(status); - Stage progress = new Stage(); - progress.setScene(s); - progress.show(); + public void setSourceImage(Image img) { + source = getScaledImage(img, bufferWidth * byteRenderWidth, height); + resetOutput = true; + } - Thread t = new Thread(new Runnable() { - @Override - public void run() { - final WritableImage keepScaled = new WritableImage(source.getPixelReader(), 560, 192); - WritableImage tmpScaled = new WritableImage(source.getPixelReader(), 560, 192); - for (int i = 0; i < screen.length; i++) { - screen[i] = (byte) Math.max(255, Math.random() * 256.0); + private static WritableImage getScaledImage(Image img, int width, int height) { + Canvas c = new Canvas(width, height); + c.getGraphicsContext2D().drawImage(img, 0, 0, width, height); + WritableImage newImg = new WritableImage(width, height); + SnapshotParameters sp = new SnapshotParameters(); + c.snapshot(sp, newImg); + return newImg; + } + + public WritableImage getPreviewImage() { + return new WritableImage(bufferWidth * byteRenderWidth, height*2); + } + + public void setOutputDimensions(int width, int height) { + this.bufferWidth = width; + this.height = height; + screen = platform.imageRenderer.createImageBuffer(width, height); + resetOutput = true; + } + + public void setDivisor(int divisor) { + this.divisor = divisor; + } + + public void setCoefficients(int[][] coefficients) { + this.coefficients = coefficients; + } + + int startX; + int startY; + + public void setTargetCoordinates(int x, int y) { + startX = x; + startY = y; + } + + WritableImage keepScaled; + WritableImage tmpScaled; + int[] scanline; + List pixels; + + public void restartDither() { + keepScaled = new WritableImage(source.getPixelReader(), 560, 192); + tmpScaled = new WritableImage(source.getPixelReader(), 560, 192); + for (int i = 0; i < screen.length; i++) { + screen[i]=(byte) 255; +// screen[i] = (byte) Math.max(255, Math.random() * 256.0); + } + scanline = new int[3]; + pixels = new ArrayList<>(); + } + + public byte[] dither(boolean propagateError) { + if (resetOutput) { + restartDither(); + resetOutput = false; + } + keepScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0); + tmpScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0); + for (int y = 0; y < height; y++) { + for (int x = 0; x < bufferWidth; x += 2) { + switch (platform) { + case AppleII: + hiresDither(y, x, propagateError, keepScaled); + break; + case AppleII_DHGR: + doubleHiresDither(y, x, propagateError, keepScaled); + break; } -// Platform.runLater(new Runnable() { -// @Override -// public void run() { -// previewImage.setCache(false); -// previewImage.setImage(keepScaled); -// } -// }); -// try { -// while (previewImage.getImage() != keepScaled) { -// Thread.sleep(10); -// } -// } catch (InterruptedException ex) { -// Logger.getLogger(FloydSteinbergDither.class.getName()).log(Level.SEVERE, null, ex); -// } - int[] scanline = new int[3]; - List pixels = new ArrayList<>(); - for (int pass = 0; pass < totalPasses; pass++) { - keepScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0); - tmpScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0); - final String statusText = "Pass: " + (pass + 1) + " of " + totalPasses; - Platform.runLater(new Runnable() { - @Override - public void run() { - status.setText(statusText); - } - }); - System.out.println("Image type: " + platform.name()); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x += 2) { - Thread.yield(); - switch (platform) { - case AppleII: - hiresDither(screen, y, x, scanline, pixels, tmpScaled, pass, keepScaled); - break; - case AppleII_DHGR: - doubleHiresDither(screen, y, x, scanline, pixels, tmpScaled, pass, keepScaled); - break; - } - } - } - Platform.runLater(new Runnable() { - @Override - public void run() { - callback.ditherCompleted(screen); - } - }); - } - status.setText("Complete!"); } + } + return screen; + } - void hiresDither(final byte[] screen, int y, int x, int[] scanline, List pixels, WritableImage tmpScaled, int pass, final WritableImage keepScaled) { - int bb1 = screen[(y + startY) * bufferWidth + startX + x] & 255; - int bb2 = screen[(y + startY) * bufferWidth + startX + x + 1] & 255; - int next = bb2 & 127; // Preserve hi-bit so last pixel stays solid, it is a very minor detail - int prev = 0; - if ((x + startX) > 0) { - prev = screen[(y + startY) * bufferWidth + startX + x - 1] & 255; - } - if ((x + startX) < 38) { - next = screen[(y + startY) * bufferWidth + startX + x + 2] & 255; - } - // First byte, compared with a sliding window encompassing the previous byte, if any. - int leastError = Integer.MAX_VALUE; - for (int hi = 0; hi < 2; hi++) { - int b1 = (hi << 7) | (bb1 & 0x07f); - int totalError = 0; - for (int c = 0; c < 7; c++) { + void hiresDither(int y, int x, boolean propagateError, final WritableImage keepScaled) { + int bb1 = screen[(y + startY) * bufferWidth + startX + x] & 255; + int bb2 = screen[(y + startY) * bufferWidth + startX + x + 1] & 255; + int next = bb2 & 127; // Preserve hi-bit so last pixel stays solid, it is a very minor detail + int prev = 0; + if ((x + startX) > 0) { + prev = screen[(y + startY) * bufferWidth + startX + x - 1] & 255; + } + if ((x + startX) < 38) { + next = screen[(y + startY) * bufferWidth + startX + x + 2] & 255; + } + // First byte, compared with a sliding window encompassing the previous byte, if any. + int leastError = Integer.MAX_VALUE; + for (int hi = 0; hi < 2; hi++) { + int b1 = (hi << 7) | (bb1 & 0x07f); + int totalError = 0; + for (int c = 0; c < 7; c++) { // for (int c = 6; c >= 0; c--) { - int on = b1 | (1 << c); - int off = on ^ (1 << c); - // get values for "off" - int i = hgrToDhgr[0][prev]; - scanline[0] = i & 0x0fffffff; - i = hgrToDhgr[(i & 0x10000000) != 0 ? off | 0x0100 : off][bb2]; - scanline[1] = i & 0x0fffffff; + int on = b1 | (1 << c); + int off = on ^ (1 << c); + // get values for "off" + int i = hgrToDhgr[0][prev]; + scanline[0] = i & 0x0fffffff; + i = hgrToDhgr[(i & 0x10000000) != 0 ? off | 0x0100 : off][bb2]; + scanline[1] = i & 0x0fffffff; // scanline[2] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0] & 0x0fffffff; - int errorOff = getError(x * 14 - overlap + c * 2, y, 28 + c * 2 - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int off1 = pixels.get(c * 2 + 28 + pixelShift); - int off2 = pixels.get(c * 2 + 29 + pixelShift); - // get values for "on" - i = hgrToDhgr[0][prev]; - scanline[0] = i & 0x0fffffff; - i = hgrToDhgr[(i & 0x10000000) != 0 ? on | 0x0100 : on][bb2]; - scanline[1] = i & 0x0fffffff; + int errorOff = getError(x * 14 - overlap + c * 2, y, 28 + c * 2 - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int off1 = pixels.get(c * 2 + 28 + pixelShift); + int off2 = pixels.get(c * 2 + 29 + pixelShift); + // get values for "on" + i = hgrToDhgr[0][prev]; + scanline[0] = i & 0x0fffffff; + i = hgrToDhgr[(i & 0x10000000) != 0 ? on | 0x0100 : on][bb2]; + scanline[1] = i & 0x0fffffff; // scanline[2] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0] & 0x0fffffff; - int errorOn = getError(x * 14 - overlap + c * 2, y, 28 + c * 2 - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int on1 = pixels.get(c * 2 + 28 + pixelShift); - int on2 = pixels.get(c * 2 + 29 + pixelShift); - int[] col1; - int[] col2; - if (errorOff < errorOn) { - totalError += errorOff; - b1 = off; - col1 = Palette.parseIntColor(off1); - col2 = Palette.parseIntColor(off2); - } else { - totalError += errorOn; - b1 = on; - col1 = Palette.parseIntColor(on1); - col2 = Palette.parseIntColor(on2); - } - if (pass >= nonErrorPasses) { - propagateError(x * 14 + c * 2, y, tmpScaled, col1, true, false); - propagateError(x * 14 + c * 2 + 1, y, tmpScaled, col2, false, true); - } - } - if (totalError < leastError) { - keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); - leastError = totalError; - bb1 = b1; - } else { - tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); - } + int errorOn = getError(x * 14 - overlap + c * 2, y, 28 + c * 2 - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int on1 = pixels.get(c * 2 + 28 + pixelShift); + int on2 = pixels.get(c * 2 + 29 + pixelShift); + int[] col1; + int[] col2; + if (errorOff < errorOn) { + totalError += errorOff; + b1 = off; + col1 = Palette.parseIntColor(off1); + col2 = Palette.parseIntColor(off2); + } else { + totalError += errorOn; + b1 = on; + col1 = Palette.parseIntColor(on1); + col2 = Palette.parseIntColor(on2); } - // Second byte, compared with a sliding window encompassing the next byte, if any. - leastError = Integer.MAX_VALUE; - for (int hi = 0; hi < 2; hi++) { - int b2 = (hi << 7) | (bb2 & 0x07f); - int totalError = 0; - for (int c = 0; c < 7; c++) { -// for (int c = 6; c >= 0; c--) { - int on = b2 | (1 << c); - int off = on ^ (1 << c); - // get values for "off" - int i = hgrToDhgr[bb1][off]; - scanline[0] = i & 0xfffffff; - scanline[1] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0]; - int errorOff = getError(x * 14 + 14 - overlap + c * 2, y, 14 - overlap + c * 2 + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int off1 = pixels.get(c * 2 + 14 + pixelShift); - int off2 = pixels.get(c * 2 + 15 + pixelShift); - // get values for "on" - i = hgrToDhgr[bb1][on]; - scanline[0] = i & 0xfffffff; - scanline[1] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0]; - int errorOn = getError(x * 14 + 14 - overlap + c * 2, y, 14 - overlap + c * 2 + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int on1 = pixels.get(c * 2 + 14 + pixelShift); - int on2 = pixels.get(c * 2 + 15 + pixelShift); - int[] col1; - int[] col2; - if (errorOff < errorOn) { - totalError += errorOff; - b2 = off; - col1 = Palette.parseIntColor(off1); - col2 = Palette.parseIntColor(off2); - } else { - totalError += errorOn; - b2 = on; - col1 = Palette.parseIntColor(on1); - col2 = Palette.parseIntColor(on2); - } - if (pass >= nonErrorPasses) { - propagateError(x * 14 + c * 2 + 14, y, tmpScaled, col1, true, false); - propagateError(x * 14 + c * 2 + 15, y, tmpScaled, col2, false, true); - } - } - if (totalError < leastError) { - keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); - leastError = totalError; - bb2 = b2; - } else { - tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); - } + if (propagateError) { + propagateError(x * 14 + c * 2, y, tmpScaled, col1); + propagateError(x * 14 + c * 2 + 1, y, tmpScaled, col2); } - screen[(y + startY) * bufferWidth + startX + x] = (byte) bb1; - screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bb2; } - - void doubleHiresDither(final byte[] screen, int y, int x, int[] scanline, List pixels, WritableImage tmpScaled, int pass, final WritableImage keepScaled) { - if (x % 4 != 0) { - return; + if (totalError < leastError) { + keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); + leastError = totalError; + bb1 = b1; + } else { + tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); + } + } + // Second byte, compared with a sliding window encompassing the next byte, if any. + leastError = Integer.MAX_VALUE; + for (int hi = 0; hi < 2; hi++) { + int b2 = (hi << 7) | (bb2 & 0x07f); + int totalError = 0; + for (int c = 0; c < 7; c++) { +// for (int c = 6; c >= 0; c--) { + int on = b2 | (1 << c); + int off = on ^ (1 << c); + // get values for "off" + int i = hgrToDhgr[bb1][off]; + scanline[0] = i & 0xfffffff; + scanline[1] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0]; + int errorOff = getError(x * 14 + 14 - overlap + c * 2, y, 14 - overlap + c * 2 + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int off1 = pixels.get(c * 2 + 14 + pixelShift); + int off2 = pixels.get(c * 2 + 15 + pixelShift); + // get values for "on" + i = hgrToDhgr[bb1][on]; + scanline[0] = i & 0xfffffff; + scanline[1] = hgrToDhgr[(i & 0x10000000) != 0 ? next | 0x0100 : next][0]; + int errorOn = getError(x * 14 + 14 - overlap + c * 2, y, 14 - overlap + c * 2 + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int on1 = pixels.get(c * 2 + 14 + pixelShift); + int on2 = pixels.get(c * 2 + 15 + pixelShift); + int[] col1; + int[] col2; + if (errorOff < errorOn) { + totalError += errorOff; + b2 = off; + col1 = Palette.parseIntColor(off1); + col2 = Palette.parseIntColor(off2); + } else { + totalError += errorOn; + b2 = on; + col1 = Palette.parseIntColor(on1); + col2 = Palette.parseIntColor(on2); } - scanline[0] = 0; - if (x >= 4) { - scanline[0] = screen[y * 80 + x - 1] << 21; + if (propagateError) { + propagateError(x * 14 + c * 2 + 14, y, tmpScaled, col1); + propagateError(x * 14 + c * 2 + 15, y, tmpScaled, col2); } - scanline[1] = 0; - if (x < 76) { - scanline[2] = screen[y * 80 + x + 4]; - } - int bytes[] = new int[]{ - screen[y * 80 + x] & 255, - screen[y * 80 + x + 1] & 255, - screen[y * 80 + x + 2] & 255, - screen[y * 80 + x + 3] & 255 - }; + } + if (totalError < leastError) { + keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); + leastError = totalError; + bb2 = b2; + } else { + tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); + } + } + screen[(y + startY) * bufferWidth + startX + x] = (byte) bb1; + screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bb2; + } - for (int xx = 0; xx < 4; xx++) { - // First byte, compared with a sliding window encompassing the previous byte, if any. - int leastError = Integer.MAX_VALUE; - int b1 = (bytes[xx] & 0x07f); - for (int c = 0; c < 7; c++) { - int on = b1 | (1 << c); - int off = on ^ (1 << c); - // get values for "off" - int i = (xx == 3) ? off : bytes[3] & 255; - i <<= 7; - i |= (xx == 2) ? off : bytes[2] & 255; - i <<= 7; - i |= (xx == 1) ? off : bytes[1] & 255; - i <<= 7; - i |= (xx == 0) ? off : bytes[0] & 255; - scanline[1] = i; - int errorOff = getError((x + xx) * 7 - overlap + c, y, 28 + (xx * 7) + c - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int off1 = pixels.get(xx * 7 + c + 28 + pixelShift); - // get values for "on" - i = (xx == 3) ? on : bytes[3] & 255; - i <<= 7; - i |= (xx == 2) ? on : bytes[2] & 255; - i <<= 7; - i |= (xx == 1) ? on : bytes[1] & 255; - i <<= 7; - i |= (xx == 0) ? on : bytes[0] & 255; - scanline[1] = i; - int errorOn = getError((x + xx) * 7 - overlap + c, y, 28 + (xx * 7) + c - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); - int on1 = pixels.get(xx * 7 + c + 28 + pixelShift); + void doubleHiresDither(int y, int x, boolean propagateError, final WritableImage keepScaled) { + if (x % 4 != 0) { + return; + } + scanline[0] = 0; + if (x >= 4) { + scanline[0] = screen[y * 80 + x - 1] << 21; + } + scanline[1] = 0; + if (x < 76) { + scanline[2] = screen[y * 80 + x + 4]; + } + int bytes[] = new int[]{ + screen[y * 80 + x] & 255, + screen[y * 80 + x + 1] & 255, + screen[y * 80 + x + 2] & 255, + screen[y * 80 + x + 3] & 255 + }; - int[] col1; - if (errorOff < errorOn) { + for (int byteOffset = 0; byteOffset < 4; byteOffset++) { + // First byte, compared with a sliding window encompassing the previous byte, if any. + int leastError = Integer.MAX_VALUE; + int b1 = (bytes[byteOffset] & 0x07f); + for (int bit = 0; bit < 7; bit++) { + int on = b1 | (1 << bit); + int off = on ^ (1 << bit); + // get values for "off" + int i = (byteOffset == 3) ? off : bytes[3] & 255; + i <<= 7; + i |= (byteOffset == 2) ? off : bytes[2] & 255; + i <<= 7; + i |= (byteOffset == 1) ? off : bytes[1] & 255; + i <<= 7; + i |= (byteOffset == 0) ? off : bytes[0] & 255; + scanline[1] = i; + int errorOff = getError((x + byteOffset) * 7 - overlap + bit, y, 28 + (byteOffset * 7) + bit - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int off1 = pixels.get(byteOffset * 7 + bit + 28 + pixelShift); + // get values for "on" + i = (byteOffset == 3) ? on : bytes[3] & 255; + i <<= 7; + i |= (byteOffset == 2) ? on : bytes[2] & 255; + i <<= 7; + i |= (byteOffset == 1) ? on : bytes[1] & 255; + i <<= 7; + i |= (byteOffset == 0) ? on : bytes[0] & 255; + scanline[1] = i; + int errorOn = getError((x + byteOffset) * 7 - overlap + bit, y, 28 + (byteOffset * 7) + bit - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); + int on1 = pixels.get(byteOffset * 7 + bit + 28 + pixelShift); + + int[] col1; + if (errorOff < errorOn) { // totalError += errorOff; - b1 = off; - col1 = Palette.parseIntColor(off1); - } else { + b1 = off; + col1 = Palette.parseIntColor(off1); + } else { // totalError += errorOn; - b1 = on; - col1 = Palette.parseIntColor(on1); - } - if (pass >= nonErrorPasses) { - propagateError((x + xx) * 7 + c, y, tmpScaled, col1, false, false); - } - } + b1 = on; + col1 = Palette.parseIntColor(on1); + } + if (propagateError) { + propagateError((x + byteOffset) * 7 + bit, y, tmpScaled, col1); + } + } // if (totalError < leastError) { - keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); + keepScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, tmpScaled.getPixelReader(), 0, y); // leastError = totalError; - bytes[xx] = b1; + bytes[byteOffset] = b1; // } else { // tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); - } - screen[(y + startY) * bufferWidth + startX + x] = (byte) bytes[0]; - screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bytes[1]; - screen[(y + startY) * bufferWidth + startX + x + 2] = (byte) bytes[2]; - screen[(y + startY) * bufferWidth + startX + x + 3] = (byte) bytes[3]; - } - }); - t.start(); -// progress.close(); + } + screen[(y + startY) * bufferWidth + startX + x] = (byte) bytes[0]; + screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bytes[1]; + screen[(y + startY) * bufferWidth + startX + x + 2] = (byte) bytes[2]; + screen[(y + startY) * bufferWidth + startX + x + 3] = (byte) bytes[3]; } - private static void propagateError(int x, int y, WritableImage img, int[] newColor, boolean propagateLeft, boolean propagateRight) { - int solid = 255 << 24; + public static int ALPHA_SOLID = 255 << 24; + + private void propagateError(int x, int y, WritableImage img, int[] newColor) { for (int i = 0; i < 3; i++) { - if (x + 1 < img.getWidth() && propagateRight) { - int error = Palette.getComponent(img.getPixelReader().getArgb(x + 1, y), i) - newColor[i]; - int c = img.getPixelReader().getArgb(x + 1, y); - img.getPixelWriter().setArgb(x + 1, y, solid | Palette.addError(c, i, (error * 7) >> 4)); - } - if (y + 1 < img.getHeight()) { - int error = Palette.getComponent(img.getPixelReader().getArgb(x, y), i) - newColor[i]; - if (x - 1 > 0 && propagateLeft) { - int c = img.getPixelReader().getArgb(x - 1, y + 1); - img.getPixelWriter().setArgb(x - 1, y + 1, solid | Palette.addError(c, i, (error * 3) >> 4)); - } - int c = img.getPixelReader().getArgb(x, y + 1); - img.getPixelWriter().setArgb(x, y + 1, solid | Palette.addError(c, i, (error * 5) >> 4)); - if (x + 1 < img.getWidth() && propagateRight) { - c = img.getPixelReader().getArgb(x + 1, y + 1); - img.getPixelWriter().setArgb(x + 1, y + 1, solid | Palette.addError(c, i, error >> 4)); + int error = Palette.getComponent(img.getPixelReader().getArgb(x, y), i) - newColor[i]; + for (int yy = 0; yy < 3 && y + yy < img.getHeight(); yy++) { + for (int xx = -2; xx < 3 && x + xx < img.getWidth(); xx++) { + if (x + xx < 0 || coefficients[xx + 2][yy] == 0) { + continue; + } + int c = img.getPixelReader().getArgb(x + xx, y + yy); + int errorAmount = ((error * coefficients[xx+2][yy]) / divisor); + img.getPixelWriter().setArgb(x + xx, y + yy, ALPHA_SOLID | Palette.addError(c, i, errorAmount)); } } } @@ -378,7 +361,7 @@ public class FloydSteinbergDither { PixelWriter fakeWriter = new PixelWriter() { @Override public PixelFormat getPixelFormat() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override @@ -388,34 +371,29 @@ public class FloydSteinbergDither { @Override public void setColor(int i, int i1, Color color) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void setPixels(int i, int i1, int i2, int i3, PixelFormat pf, T t, int i4) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void setPixels(int i, int i1, int i2, int i3, PixelFormat pf, byte[] bytes, int i4, int i5) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void setPixels(int i, int i1, int i2, int i3, PixelFormat pf, int[] ints, int i4, int i5) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void setPixels(int i, int i1, int i2, int i3, PixelReader reader, int i4, int i5) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }; AppleImageRenderer.renderScanline(fakeWriter, 0, scanline, true, false, 20); - double max = 0; - double min = Double.MAX_VALUE; +// double max = 0; +// double min = Double.MAX_VALUE; double total = 0; - List err = new ArrayList<>(); +// List err = new ArrayList<>(); for (int p = 0; p < window; p++) { if ((imageXStart + p) < 0 || (imageXStart + p) >= 560) { continue; @@ -424,9 +402,9 @@ public class FloydSteinbergDither { int[] c2 = Palette.parseIntColor(source.getArgb(imageXStart + p, y)); double dist = Palette.distance(c1, c2); total += dist; - max = Math.max(dist, max); - min = Math.min(dist, min); - err.add(dist); +// max = Math.max(dist, max); +// min = Math.min(dist, min); +// err.add(dist); } // double avg = total/((double) window); // double range = max-min; @@ -468,12 +446,4 @@ public class FloydSteinbergDither { // } // return dest; // } - private static WritableImage getScaledImage(Image img, int width, int height) { - Canvas c = new Canvas(width, height); - c.getGraphicsContext2D().drawImage(img, 0, 0, width, height); - WritableImage newImg = new WritableImage(width, height); - SnapshotParameters sp = new SnapshotParameters(); - c.snapshot(sp, newImg); - return newImg; - } } diff --git a/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/ImageConversionPostAction.java b/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/ImageConversionPostAction.java new file mode 100644 index 00000000..d370ab79 --- /dev/null +++ b/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/ImageConversionPostAction.java @@ -0,0 +1,10 @@ +package org.badvision.outlaweditor.ui; + +/** + * + * @author blurry + */ +@FunctionalInterface +public interface ImageConversionPostAction { + public void storeConvertedImage(byte[] image); +} diff --git a/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/UIAction.java b/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/UIAction.java index 4351c2e7..4016de02 100644 --- a/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/UIAction.java +++ b/OutlawEditor/src/main/java/org/badvision/outlaweditor/ui/UIAction.java @@ -12,7 +12,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Platform; import javafx.event.ActionEvent; -import javafx.event.EventHandler; +import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Scene; @@ -22,6 +22,7 @@ import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.image.Image; import javafx.scene.image.WritableImage; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBoxBuilder; import javafx.scene.layout.VBoxBuilder; import javafx.scene.text.Text; @@ -31,9 +32,11 @@ import javax.xml.bind.JAXB; import org.badvision.outlaweditor.Application; import org.badvision.outlaweditor.FileUtils; import org.badvision.outlaweditor.MythosEditor; +import org.badvision.outlaweditor.apple.FloydSteinbergDither; import org.badvision.outlaweditor.data.TilesetUtils; import org.badvision.outlaweditor.data.xml.GameData; import org.badvision.outlaweditor.data.xml.Script; +import org.badvision.outlaweditor.ui.impl.ImageConversionWizardController; /** * @@ -120,14 +123,11 @@ public class UIAction { currentMenu = new Menu(action.name().replace("_", "")); } else { MenuItem item = new MenuItem(action.name().replaceAll("_", " ")); - item.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent t) { - try { - actionPerformed(action); - } catch (IOException ex) { - Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex); - } + item.setOnAction((ActionEvent t) -> { + try { + actionPerformed(action); + } catch (IOException ex) { + Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex); } }); currentMenu.getItems().add(item); @@ -139,12 +139,7 @@ public class UIAction { } public static void quit() { - confirm("Quit? Are you sure?", new Runnable() { - @Override - public void run() { - Platform.exit(); - } - }, null); + confirm("Quit? Are you sure?", Platform::exit, null); } static Image badImage; @@ -181,14 +176,11 @@ public class UIAction { List