Quantum leap forward with a new image conversion wizard!

This commit is contained in:
Brendan Robert 2014-09-28 01:54:49 -05:00
parent c43f381296
commit f43c642875
8 changed files with 921 additions and 383 deletions

View File

@ -7,7 +7,6 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
@ -46,7 +45,6 @@ public class MythosEditor {
public void show() { public void show() {
primaryStage = new Stage(); primaryStage = new Stage();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/MythosScriptEditor.fxml")); FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/MythosScriptEditor.fxml"));
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
properties.put(MythosScriptEditorController.ONLOAD_SCRIPT, generateLoadScript()); properties.put(MythosScriptEditorController.ONLOAD_SCRIPT, generateLoadScript());
@ -61,22 +59,16 @@ public class MythosEditor {
throw new RuntimeException(exception); throw new RuntimeException(exception);
} }
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() { primaryStage.setOnCloseRequest((final WindowEvent t) -> {
@Override
public void handle(final WindowEvent t) {
t.consume(); t.consume();
}
}); });
primaryStage.show(); primaryStage.show();
} }
public void close() { public void close() {
javafx.application.Platform.runLater(new Runnable() { javafx.application.Platform.runLater(() -> {
@Override
public void run() {
primaryStage.getScene().getRoot().setDisable(true); primaryStage.getScene().getRoot().setDisable(true);
primaryStage.close(); primaryStage.close();
}
}); });
} }

View File

@ -8,7 +8,6 @@ import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
@ -23,7 +22,6 @@ import org.badvision.outlaweditor.FileUtils;
import org.badvision.outlaweditor.ImageEditor; import org.badvision.outlaweditor.ImageEditor;
import org.badvision.outlaweditor.Platform; import org.badvision.outlaweditor.Platform;
import org.badvision.outlaweditor.ui.UIAction; import org.badvision.outlaweditor.ui.UIAction;
import org.badvision.outlaweditor.data.DataObserver;
import org.badvision.outlaweditor.data.TileMap; import org.badvision.outlaweditor.data.TileMap;
import org.badvision.outlaweditor.data.xml.Image; import org.badvision.outlaweditor.data.xml.Image;
import org.badvision.outlaweditor.data.xml.PlatformData; import org.badvision.outlaweditor.data.xml.PlatformData;
@ -65,11 +63,8 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
@Override @Override
public void buildPatternSelector(Menu tilePatternMenu) { public void buildPatternSelector(Menu tilePatternMenu) {
FillPattern.buildMenu(tilePatternMenu, new DataObserver<FillPattern>() { FillPattern.buildMenu(tilePatternMenu, (FillPattern object) -> {
@Override
public void observedObjectChanged(FillPattern object) {
changeCurrentPattern(object); changeCurrentPattern(object);
}
}); });
} }
@ -141,16 +136,14 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
break; break;
} }
} }
redraw();
} }
@Override @Override
public void togglePanZoom() { public void togglePanZoom() {
for (Node n : anchorPane.getChildren()) { anchorPane.getChildren().stream().filter((n) -> !(n == screen)).forEach((n) -> {
if (n == screen) {
continue;
}
n.setVisible(!n.isVisible()); n.setVisible(!n.isVisible());
} });
} }
@Override @Override
@ -393,9 +386,10 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
if (pasteAppContent((String) Clipboard.getSystemClipboard().getContent(DataFormat.PLAIN_TEXT))) { if (pasteAppContent((String) Clipboard.getSystemClipboard().getContent(DataFormat.PLAIN_TEXT))) {
return; return;
} }
}; }
if (Clipboard.getSystemClipboard().hasContent(DataFormat.IMAGE)) { if (Clipboard.getSystemClipboard().hasContent(DataFormat.IMAGE)) {
javafx.scene.image.Image image = Clipboard.getSystemClipboard().getImage(); javafx.scene.image.Image image = Clipboard.getSystemClipboard().getImage();
importImage(image); importImage(image);
} }
} }
@ -435,13 +429,9 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
} }
private void importImage(javafx.scene.image.Image image) { private void importImage(javafx.scene.image.Image image) {
FloydSteinbergDither.floydSteinbergDither(image, getPlatform(), 0, 0, getWidth(), getHeight(), getImageData(), getWidth(), new FloydSteinbergDither.DitherCallback() { FloydSteinbergDither ditherEngine = new FloydSteinbergDither(getPlatform());
@Override ditherEngine.setTargetCoordinates(0,0);
public void ditherCompleted(byte[] data) { UIAction.openImageConversionModal(image, ditherEngine, getWidth(), getHeight(), this::setData);
setData(data);
redraw();
}
});
} }
private int calculateHiresOffset(int y) { private int calculateHiresOffset(int y) {
@ -478,17 +468,10 @@ public class AppleImageEditor extends ImageEditor implements EventHandler<MouseE
@Override @Override
public void resize(final int newWidth, final int newHeight) { public void resize(final int newWidth, final int newHeight) {
UIAction.confirm("Do you want to scale the image? If you select no, the image will be cropped as needed.", UIAction.confirm("Do you want to scale the image? If you select no, the image will be cropped as needed.", () -> {
new Runnable() {
@Override
public void run() {
rescale(newWidth, newHeight); rescale(newWidth, newHeight);
} }, () -> {
}, new Runnable() {
@Override
public void run() {
crop(newWidth, newHeight); crop(newWidth, newHeight);
}
}); });
} }

View File

@ -5,22 +5,17 @@ import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javafx.application.Platform;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.SnapshotParameters; import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat; import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader; import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter; import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.text.Font; import javax.swing.Renderer;
import javafx.scene.text.Text; import org.badvision.outlaweditor.Platform;
import javafx.stage.Stage;
import static org.badvision.outlaweditor.apple.AppleNTSCGraphics.hgrToDhgr; import static org.badvision.outlaweditor.apple.AppleNTSCGraphics.hgrToDhgr;
/* Copyright (c) 2013 the authors listed at the following URL, and/or /* Copyright (c) 2013 the authors listed at the following URL, and/or
@ -53,106 +48,104 @@ import static org.badvision.outlaweditor.apple.AppleNTSCGraphics.hgrToDhgr;
*/ */
public class FloydSteinbergDither { public class FloydSteinbergDither {
static final int totalPasses = 1; int byteRenderWidth;
static final int nonErrorPasses = 1;
public static interface DitherCallback {
public void ditherCompleted(byte[] data);
}
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 errorWindow = 6;
final int overlap = 2; final int overlap = 2;
final int pixelShift = -2; final int pixelShift = -2;
int byteRenderWidth = platform == org.badvision.outlaweditor.Platform.AppleII_DHGR ? 7 : 14; WritableImage source;
final WritableImage source = getScaledImage(img, width * byteRenderWidth, height); byte[] screen;
AnchorPane pane = new AnchorPane(); Platform platform;
Scene s = new Scene(pane); int bufferWidth;
final ImageView previewImage = new ImageView(source); int height;
previewImage.setLayoutX(0); int divisor;
previewImage.setLayoutY(0); int[][] coefficients;
pane.getChildren().add(previewImage); boolean resetOutput = true;
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();
Thread t = new Thread(new Runnable() { public FloydSteinbergDither(org.badvision.outlaweditor.Platform platform) {
@Override this.platform = platform;
public void run() { byteRenderWidth = platform == org.badvision.outlaweditor.Platform.AppleII_DHGR ? 7 : 14;
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++) { public void setSourceImage(Image img) {
screen[i] = (byte) Math.max(255, Math.random() * 256.0); source = getScaledImage(img, bufferWidth * byteRenderWidth, height);
resetOutput = true;
}
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<Integer> 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;
} }
// 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<Integer> pixels = new ArrayList<>();
for (int pass = 0; pass < totalPasses; pass++) {
keepScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0); keepScaled.getPixelWriter().setPixels(0, 0, 560, 192, source.getPixelReader(), 0, 0);
tmpScaled.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 y = 0; y < height; y++) {
for (int x = 0; x < width; x += 2) { for (int x = 0; x < bufferWidth; x += 2) {
Thread.yield();
switch (platform) { switch (platform) {
case AppleII: case AppleII:
hiresDither(screen, y, x, scanline, pixels, tmpScaled, pass, keepScaled); hiresDither(y, x, propagateError, keepScaled);
break; break;
case AppleII_DHGR: case AppleII_DHGR:
doubleHiresDither(screen, y, x, scanline, pixels, tmpScaled, pass, keepScaled); doubleHiresDither(y, x, propagateError, keepScaled);
break; break;
} }
} }
} }
Platform.runLater(new Runnable() { return screen;
@Override
public void run() {
callback.ditherCompleted(screen);
}
});
}
status.setText("Complete!");
} }
void hiresDither(final byte[] screen, int y, int x, int[] scanline, List<Integer> pixels, WritableImage tmpScaled, int pass, final WritableImage keepScaled) { void hiresDither(int y, int x, boolean propagateError, final WritableImage keepScaled) {
int bb1 = screen[(y + startY) * bufferWidth + startX + x] & 255; int bb1 = screen[(y + startY) * bufferWidth + startX + x] & 255;
int bb2 = screen[(y + startY) * bufferWidth + startX + x + 1] & 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 next = bb2 & 127; // Preserve hi-bit so last pixel stays solid, it is a very minor detail
@ -203,9 +196,9 @@ public class FloydSteinbergDither {
col1 = Palette.parseIntColor(on1); col1 = Palette.parseIntColor(on1);
col2 = Palette.parseIntColor(on2); col2 = Palette.parseIntColor(on2);
} }
if (pass >= nonErrorPasses) { if (propagateError) {
propagateError(x * 14 + c * 2, y, tmpScaled, col1, true, false); propagateError(x * 14 + c * 2, y, tmpScaled, col1);
propagateError(x * 14 + c * 2 + 1, y, tmpScaled, col2, false, true); propagateError(x * 14 + c * 2 + 1, y, tmpScaled, col2);
} }
} }
if (totalError < leastError) { if (totalError < leastError) {
@ -252,9 +245,9 @@ public class FloydSteinbergDither {
col1 = Palette.parseIntColor(on1); col1 = Palette.parseIntColor(on1);
col2 = Palette.parseIntColor(on2); col2 = Palette.parseIntColor(on2);
} }
if (pass >= nonErrorPasses) { if (propagateError) {
propagateError(x * 14 + c * 2 + 14, y, tmpScaled, col1, true, false); propagateError(x * 14 + c * 2 + 14, y, tmpScaled, col1);
propagateError(x * 14 + c * 2 + 15, y, tmpScaled, col2, false, true); propagateError(x * 14 + c * 2 + 15, y, tmpScaled, col2);
} }
} }
if (totalError < leastError) { if (totalError < leastError) {
@ -269,7 +262,7 @@ public class FloydSteinbergDither {
screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bb2; screen[(y + startY) * bufferWidth + startX + x + 1] = (byte) bb2;
} }
void doubleHiresDither(final byte[] screen, int y, int x, int[] scanline, List<Integer> pixels, WritableImage tmpScaled, int pass, final WritableImage keepScaled) { void doubleHiresDither(int y, int x, boolean propagateError, final WritableImage keepScaled) {
if (x % 4 != 0) { if (x % 4 != 0) {
return; return;
} }
@ -288,35 +281,35 @@ public class FloydSteinbergDither {
screen[y * 80 + x + 3] & 255 screen[y * 80 + x + 3] & 255
}; };
for (int xx = 0; xx < 4; xx++) { for (int byteOffset = 0; byteOffset < 4; byteOffset++) {
// First byte, compared with a sliding window encompassing the previous byte, if any. // First byte, compared with a sliding window encompassing the previous byte, if any.
int leastError = Integer.MAX_VALUE; int leastError = Integer.MAX_VALUE;
int b1 = (bytes[xx] & 0x07f); int b1 = (bytes[byteOffset] & 0x07f);
for (int c = 0; c < 7; c++) { for (int bit = 0; bit < 7; bit++) {
int on = b1 | (1 << c); int on = b1 | (1 << bit);
int off = on ^ (1 << c); int off = on ^ (1 << bit);
// get values for "off" // get values for "off"
int i = (xx == 3) ? off : bytes[3] & 255; int i = (byteOffset == 3) ? off : bytes[3] & 255;
i <<= 7; i <<= 7;
i |= (xx == 2) ? off : bytes[2] & 255; i |= (byteOffset == 2) ? off : bytes[2] & 255;
i <<= 7; i <<= 7;
i |= (xx == 1) ? off : bytes[1] & 255; i |= (byteOffset == 1) ? off : bytes[1] & 255;
i <<= 7; i <<= 7;
i |= (xx == 0) ? off : bytes[0] & 255; i |= (byteOffset == 0) ? off : bytes[0] & 255;
scanline[1] = i; scanline[1] = i;
int errorOff = getError((x + xx) * 7 - overlap + c, y, 28 + (xx * 7) + c - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); int errorOff = getError((x + byteOffset) * 7 - overlap + bit, y, 28 + (byteOffset * 7) + bit - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline);
int off1 = pixels.get(xx * 7 + c + 28 + pixelShift); int off1 = pixels.get(byteOffset * 7 + bit + 28 + pixelShift);
// get values for "on" // get values for "on"
i = (xx == 3) ? on : bytes[3] & 255; i = (byteOffset == 3) ? on : bytes[3] & 255;
i <<= 7; i <<= 7;
i |= (xx == 2) ? on : bytes[2] & 255; i |= (byteOffset == 2) ? on : bytes[2] & 255;
i <<= 7; i <<= 7;
i |= (xx == 1) ? on : bytes[1] & 255; i |= (byteOffset == 1) ? on : bytes[1] & 255;
i <<= 7; i <<= 7;
i |= (xx == 0) ? on : bytes[0] & 255; i |= (byteOffset == 0) ? on : bytes[0] & 255;
scanline[1] = i; scanline[1] = i;
int errorOn = getError((x + xx) * 7 - overlap + c, y, 28 + (xx * 7) + c - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline); int errorOn = getError((x + byteOffset) * 7 - overlap + bit, y, 28 + (byteOffset * 7) + bit - overlap + pixelShift, errorWindow, pixels, tmpScaled.getPixelReader(), scanline);
int on1 = pixels.get(xx * 7 + c + 28 + pixelShift); int on1 = pixels.get(byteOffset * 7 + bit + 28 + pixelShift);
int[] col1; int[] col1;
if (errorOff < errorOn) { if (errorOff < errorOn) {
@ -328,14 +321,14 @@ public class FloydSteinbergDither {
b1 = on; b1 = on;
col1 = Palette.parseIntColor(on1); col1 = Palette.parseIntColor(on1);
} }
if (pass >= nonErrorPasses) { if (propagateError) {
propagateError((x + xx) * 7 + c, y, tmpScaled, col1, false, false); propagateError((x + byteOffset) * 7 + bit, y, tmpScaled, col1);
} }
} }
// if (totalError < leastError) { // 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; // leastError = totalError;
bytes[xx] = b1; bytes[byteOffset] = b1;
// } else { // } else {
// tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y); // tmpScaled.getPixelWriter().setPixels(0, y, 560, (y < 191) ? 2 : 1, keepScaled.getPixelReader(), 0, y);
} }
@ -344,30 +337,20 @@ public class FloydSteinbergDither {
screen[(y + startY) * bufferWidth + startX + x + 2] = (byte) bytes[2]; screen[(y + startY) * bufferWidth + startX + x + 2] = (byte) bytes[2];
screen[(y + startY) * bufferWidth + startX + x + 3] = (byte) bytes[3]; screen[(y + startY) * bufferWidth + startX + x + 3] = (byte) bytes[3];
} }
});
t.start();
// progress.close();
}
private static void propagateError(int x, int y, WritableImage img, int[] newColor, boolean propagateLeft, boolean propagateRight) { public static int ALPHA_SOLID = 255 << 24;
int solid = 255 << 24;
private void propagateError(int x, int y, WritableImage img, int[] newColor) {
for (int i = 0; i < 3; i++) { 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]; int error = Palette.getComponent(img.getPixelReader().getArgb(x, y), i) - newColor[i];
if (x - 1 > 0 && propagateLeft) { for (int yy = 0; yy < 3 && y + yy < img.getHeight(); yy++) {
int c = img.getPixelReader().getArgb(x - 1, y + 1); for (int xx = -2; xx < 3 && x + xx < img.getWidth(); xx++) {
img.getPixelWriter().setArgb(x - 1, y + 1, solid | Palette.addError(c, i, (error * 3) >> 4)); if (x + xx < 0 || coefficients[xx + 2][yy] == 0) {
continue;
} }
int c = img.getPixelReader().getArgb(x, y + 1); int c = img.getPixelReader().getArgb(x + xx, y + yy);
img.getPixelWriter().setArgb(x, y + 1, solid | Palette.addError(c, i, (error * 5) >> 4)); int errorAmount = ((error * coefficients[xx+2][yy]) / divisor);
if (x + 1 < img.getWidth() && propagateRight) { img.getPixelWriter().setArgb(x + xx, y + yy, ALPHA_SOLID | Palette.addError(c, i, errorAmount));
c = img.getPixelReader().getArgb(x + 1, y + 1);
img.getPixelWriter().setArgb(x + 1, y + 1, solid | Palette.addError(c, i, error >> 4));
} }
} }
} }
@ -378,7 +361,7 @@ public class FloydSteinbergDither {
PixelWriter fakeWriter = new PixelWriter() { PixelWriter fakeWriter = new PixelWriter() {
@Override @Override
public PixelFormat getPixelFormat() { 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 @Override
@ -388,34 +371,29 @@ public class FloydSteinbergDither {
@Override @Override
public void setColor(int i, int i1, Color color) { 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 @Override
public <T extends Buffer> void setPixels(int i, int i1, int i2, int i3, PixelFormat<T> pf, T t, int i4) { public <T extends Buffer> void setPixels(int i, int i1, int i2, int i3, PixelFormat<T> pf, T t, int i4) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
} }
@Override @Override
public void setPixels(int i, int i1, int i2, int i3, PixelFormat<ByteBuffer> pf, byte[] bytes, int i4, int i5) { public void setPixels(int i, int i1, int i2, int i3, PixelFormat<ByteBuffer> pf, byte[] bytes, int i4, int i5) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
} }
@Override @Override
public void setPixels(int i, int i1, int i2, int i3, PixelFormat<IntBuffer> pf, int[] ints, int i4, int i5) { public void setPixels(int i, int i1, int i2, int i3, PixelFormat<IntBuffer> pf, int[] ints, int i4, int i5) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
} }
@Override @Override
public void setPixels(int i, int i1, int i2, int i3, PixelReader reader, int i4, int i5) { 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); AppleImageRenderer.renderScanline(fakeWriter, 0, scanline, true, false, 20);
double max = 0; // double max = 0;
double min = Double.MAX_VALUE; // double min = Double.MAX_VALUE;
double total = 0; double total = 0;
List<Double> err = new ArrayList<>(); // List<Double> err = new ArrayList<>();
for (int p = 0; p < window; p++) { for (int p = 0; p < window; p++) {
if ((imageXStart + p) < 0 || (imageXStart + p) >= 560) { if ((imageXStart + p) < 0 || (imageXStart + p) >= 560) {
continue; continue;
@ -424,9 +402,9 @@ public class FloydSteinbergDither {
int[] c2 = Palette.parseIntColor(source.getArgb(imageXStart + p, y)); int[] c2 = Palette.parseIntColor(source.getArgb(imageXStart + p, y));
double dist = Palette.distance(c1, c2); double dist = Palette.distance(c1, c2);
total += dist; total += dist;
max = Math.max(dist, max); // max = Math.max(dist, max);
min = Math.min(dist, min); // min = Math.min(dist, min);
err.add(dist); // err.add(dist);
} }
// double avg = total/((double) window); // double avg = total/((double) window);
// double range = max-min; // double range = max-min;
@ -468,12 +446,4 @@ public class FloydSteinbergDither {
// } // }
// return dest; // 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;
}
} }

View File

@ -0,0 +1,10 @@
package org.badvision.outlaweditor.ui;
/**
*
* @author blurry
*/
@FunctionalInterface
public interface ImageConversionPostAction {
public void storeConvertedImage(byte[] image);
}

View File

@ -12,7 +12,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Scene; import javafx.scene.Scene;
@ -22,6 +22,7 @@ import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.WritableImage; import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBoxBuilder; import javafx.scene.layout.HBoxBuilder;
import javafx.scene.layout.VBoxBuilder; import javafx.scene.layout.VBoxBuilder;
import javafx.scene.text.Text; import javafx.scene.text.Text;
@ -31,9 +32,11 @@ import javax.xml.bind.JAXB;
import org.badvision.outlaweditor.Application; import org.badvision.outlaweditor.Application;
import org.badvision.outlaweditor.FileUtils; import org.badvision.outlaweditor.FileUtils;
import org.badvision.outlaweditor.MythosEditor; import org.badvision.outlaweditor.MythosEditor;
import org.badvision.outlaweditor.apple.FloydSteinbergDither;
import org.badvision.outlaweditor.data.TilesetUtils; import org.badvision.outlaweditor.data.TilesetUtils;
import org.badvision.outlaweditor.data.xml.GameData; import org.badvision.outlaweditor.data.xml.GameData;
import org.badvision.outlaweditor.data.xml.Script; import org.badvision.outlaweditor.data.xml.Script;
import org.badvision.outlaweditor.ui.impl.ImageConversionWizardController;
/** /**
* *
@ -120,15 +123,12 @@ public class UIAction {
currentMenu = new Menu(action.name().replace("_", "")); currentMenu = new Menu(action.name().replace("_", ""));
} else { } else {
MenuItem item = new MenuItem(action.name().replaceAll("_", " ")); MenuItem item = new MenuItem(action.name().replaceAll("_", " "));
item.setOnAction(new EventHandler<ActionEvent>() { item.setOnAction((ActionEvent t) -> {
@Override
public void handle(ActionEvent t) {
try { try {
actionPerformed(action); actionPerformed(action);
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(Application.class.getName()).log(Level.SEVERE, null, ex);
} }
}
}); });
currentMenu.getItems().add(item); currentMenu.getItems().add(item);
} }
@ -139,12 +139,7 @@ public class UIAction {
} }
public static void quit() { public static void quit() {
confirm("Quit? Are you sure?", new Runnable() { confirm("Quit? Are you sure?", Platform::exit, null);
@Override
public void run() {
Platform.exit();
}
}, null);
} }
static Image badImage; static Image badImage;
@ -181,14 +176,11 @@ public class UIAction {
List<Button> buttons = new ArrayList<>(); List<Button> buttons = new ArrayList<>();
for (final Choice c : choices) { for (final Choice c : choices) {
Button b = new Button(c.text); Button b = new Button(c.text);
b.setOnAction(new EventHandler<ActionEvent>() { b.setOnAction((ActionEvent t) -> {
@Override
public void handle(ActionEvent t) {
if (c.handler != null) { if (c.handler != null) {
c.handler.run(); c.handler.run();
} }
dialogStage.close(); dialogStage.close();
}
}); });
buttons.add(b); buttons.add(b);
} }
@ -216,4 +208,24 @@ public class UIAction {
editor.show(); editor.show();
return script; return script;
} }
public static ImageConversionWizardController openImageConversionModal(Image image, FloydSteinbergDither ditherEngine, int targetWidth, int targetHeight, ImageConversionPostAction postAction) {
FXMLLoader fxmlLoader = new FXMLLoader(UIAction.class.getResource("/fxml/ImageConversionWizard.fxml"));
try {
Stage primaryStage = new Stage();
AnchorPane node = (AnchorPane) fxmlLoader.load();
ImageConversionWizardController controller = fxmlLoader.getController();
controller.setDitherEngine(ditherEngine);
controller.setOutputDimensions(targetWidth, targetHeight);
controller.setPostAction(postAction);
controller.setSourceImage(image);
Scene s = new Scene(node);
primaryStage.setScene(s);
primaryStage.show();
controller.setStage(primaryStage);
return controller;
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
} }

View File

@ -0,0 +1,349 @@
package org.badvision.outlaweditor.ui.impl;
import java.net.URL;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.stage.Stage;
import org.badvision.outlaweditor.Application;
import org.badvision.outlaweditor.apple.FloydSteinbergDither;
import org.badvision.outlaweditor.ui.ImageConversionPostAction;
/**
* FXML Controller class
*
* @author blurry
*/
public class ImageConversionWizardController implements Initializable {
@FXML
private TextField brightnessValue;
@FXML
private Slider brightnessSlider;
@FXML
private TextField contrastValue;
@FXML
private Slider contrastSlider;
@FXML
private TextField hueValue;
@FXML
private Slider hueSlider;
@FXML
private TextField saturationValue;
@FXML
private Slider saturationSlider;
@FXML
private TextField outputWidthValue;
@FXML
private TextField outputHeightValue;
@FXML
private TextField cropTopValue;
@FXML
private TextField cropLeftValue;
@FXML
private TextField cropBottomValue;
@FXML
private TextField cropRightValue;
@FXML
private TextField coefficientValue30;
@FXML
private TextField coefficientValue40;
@FXML
private TextField coefficientValue01;
@FXML
private TextField coefficientValue11;
@FXML
private TextField coefficientValue21;
@FXML
private TextField coefficientValue31;
@FXML
private TextField coefficientValue41;
@FXML
private TextField coefficientValue02;
@FXML
private TextField coefficientValue12;
@FXML
private TextField coefficientValue22;
@FXML
private TextField coefficientValue32;
@FXML
private TextField coefficientValue42;
private final int[][] diffusionCoeffficients = new int[5][3];
@FXML
private TextField divisorValue;
@FXML
private ImageView sourceImageView;
@FXML
private ImageView convertedImageView;
/**
* Initializes the controller class.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
for (TextField field : new TextField[]{
brightnessValue, contrastValue, hueValue, saturationValue,
cropBottomValue, cropLeftValue, cropRightValue, cropTopValue,
coefficientValue01, coefficientValue02, coefficientValue11, coefficientValue12,
coefficientValue21, coefficientValue22, coefficientValue30, coefficientValue31,
coefficientValue32, coefficientValue40, coefficientValue41, coefficientValue41,
coefficientValue42, divisorValue, outputHeightValue, outputWidthValue
}) {
configureNumberValidation(field, "0");
}
brightnessValue.textProperty().bindBidirectional(brightnessSlider.valueProperty(), NumberFormat.getNumberInstance());
contrastValue.textProperty().bindBidirectional(contrastSlider.valueProperty(), NumberFormat.getNumberInstance());
hueValue.textProperty().bindBidirectional(hueSlider.valueProperty(), NumberFormat.getNumberInstance());
saturationValue.textProperty().bindBidirectional(saturationSlider.valueProperty(), NumberFormat.getNumberInstance());
configureFastFloydSteinbergPreset(null);
}
private Stage stage;
private ImageConversionPostAction postAction;
private Image sourceImage;
private WritableImage preprocessedImage;
private WritableImage outputPreviewImage;
private FloydSteinbergDither ditherEngine;
public void setStage(Stage stage) {
this.stage = stage;
}
public void setPostAction(ImageConversionPostAction postAction) {
this.postAction = postAction;
}
public void setDitherEngine(FloydSteinbergDither engine) {
this.ditherEngine = engine;
}
public void setSourceImage(Image image) {
sourceImage = image;
preprocessImage();
}
private void preprocessImage() {
preprocessedImage = new WritableImage(sourceImage.getPixelReader(), (int) sourceImage.getWidth(), (int) sourceImage.getHeight());
ditherEngine.setSourceImage(preprocessedImage);
updateSourceView(preprocessedImage);
}
public void setOutputDimensions(int targetWidth, int targetHeight) {
ditherEngine.setOutputDimensions(targetWidth, targetHeight);
outputWidthValue.setText(String.valueOf(targetWidth));
outputHeightValue.setText(String.valueOf(targetHeight));
outputPreviewImage = ditherEngine.getPreviewImage();
}
public int getOutputWidth() {
return Integer.parseInt(outputWidthValue.getText());
}
public int getOutputHeight() {
return Integer.parseInt(outputHeightValue.getText());
}
private void updateSourceView(WritableImage image) {
sourceImageView.setImage(image);
sourceImageView.setFitWidth(0);
sourceImageView.setFitHeight(0);
int width = (int) image.getWidth();
int height = (int) image.getHeight();
defaultTextFieldValues.put(cropRightValue, String.valueOf(width));
defaultTextFieldValues.put(cropBottomValue, String.valueOf(height));
cropRightValue.setText(String.valueOf(width));
cropBottomValue.setText(String.valueOf(height));
}
@FXML
private void performQuantizePass(ActionEvent event) {
ditherEngine.setCoefficients(getCoefficients());
ditherEngine.setDivisor(getDivisor());
byte[] out = ditherEngine.dither(false);
updateConvertedImageWithData(out);
}
@FXML
private void performDiffusionPass(ActionEvent event) {
ditherEngine.setCoefficients(getCoefficients());
ditherEngine.setDivisor(getDivisor());
byte[] out = ditherEngine.dither(true);
updateConvertedImageWithData(out);
}
byte[] lastOutput;
private void updateConvertedImageWithData(byte[] data) {
lastOutput = data;
convertedImageView.setImage(Application.currentPlatform.imageRenderer.renderImage(outputPreviewImage, data, getOutputWidth(), getOutputHeight()));
}
@FXML
private void performOK(ActionEvent event) {
postAction.storeConvertedImage(lastOutput);
stage.close();
}
@FXML
private void performCancel(ActionEvent event) {
stage.close();
}
private final Map<TextField, String> defaultTextFieldValues = new HashMap<>();
private void configureNumberValidation(TextField field, String defaultValue) {
defaultTextFieldValues.put(field, defaultValue);
field.textProperty().addListener((ChangeListener) (ObservableValue observable, Object oldValue, Object newValue) -> {
if (newValue == null || "".equals(newValue)) {
field.textProperty().setValue(defaultTextFieldValues.get(field));
} else {
try {
Double.parseDouble(newValue.toString());
} catch (Exception ex) {
field.textProperty().setValue(oldValue.toString());
}
}
});
}
private void setCoefficients(int... coeff) {
coefficientValue30.setText(String.valueOf(coeff[3]));
coefficientValue40.setText(String.valueOf(coeff[4]));
coefficientValue01.setText(String.valueOf(coeff[5]));
coefficientValue11.setText(String.valueOf(coeff[6]));
coefficientValue21.setText(String.valueOf(coeff[7]));
coefficientValue31.setText(String.valueOf(coeff[8]));
coefficientValue41.setText(String.valueOf(coeff[9]));
coefficientValue02.setText(String.valueOf(coeff[10]));
coefficientValue12.setText(String.valueOf(coeff[11]));
coefficientValue22.setText(String.valueOf(coeff[12]));
coefficientValue32.setText(String.valueOf(coeff[13]));
coefficientValue42.setText(String.valueOf(coeff[14]));
}
private int[][] getCoefficients() {
diffusionCoeffficients[0][0] = 0;
diffusionCoeffficients[1][0] = 0;
diffusionCoeffficients[2][0] = 0;
diffusionCoeffficients[3][0] = Integer.parseInt(coefficientValue30.getText());
diffusionCoeffficients[4][0] = Integer.parseInt(coefficientValue40.getText());
diffusionCoeffficients[0][1] = Integer.parseInt(coefficientValue01.getText());
diffusionCoeffficients[1][1] = Integer.parseInt(coefficientValue11.getText());
diffusionCoeffficients[2][1] = Integer.parseInt(coefficientValue21.getText());
diffusionCoeffficients[3][1] = Integer.parseInt(coefficientValue31.getText());
diffusionCoeffficients[4][1] = Integer.parseInt(coefficientValue41.getText());
diffusionCoeffficients[0][2] = Integer.parseInt(coefficientValue02.getText());
diffusionCoeffficients[1][2] = Integer.parseInt(coefficientValue12.getText());
diffusionCoeffficients[2][2] = Integer.parseInt(coefficientValue22.getText());
diffusionCoeffficients[3][2] = Integer.parseInt(coefficientValue32.getText());
diffusionCoeffficients[4][2] = Integer.parseInt(coefficientValue42.getText());
return diffusionCoeffficients;
}
private void setDivisor(int div) {
divisorValue.setText(String.valueOf(div));
}
private int getDivisor() {
return Integer.valueOf(divisorValue.getText());
}
// http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
@FXML
private void configureFloydSteinbergPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 7, 0,
0, 3, 5, 1, 0,
0, 0, 0, 0, 0
);
setDivisor(16);
}
@FXML
private void configureFastFloydSteinbergPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 3, 0,
0, 0, 3, 2, 0,
0, 0, 0, 0, 0
);
setDivisor(8);
}
@FXML
private void configureJarvisJudiceNinkePreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 7, 5,
3, 5, 7, 5, 3,
1, 3, 5, 3, 1
);
setDivisor(48);
}
@FXML
private void configureStuckiPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 8, 4,
2, 4, 8, 4, 2,
1, 2, 4, 2, 1
);
setDivisor(42);
}
@FXML
private void configureAtkinsonPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0
);
setDivisor(8);
}
@FXML
private void configureBurkesPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 8, 4,
2, 4, 8, 4, 2,
0, 0, 0, 0, 0
);
setDivisor(32);
}
@FXML
private void configureSierraPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 5, 3,
2, 4, 5, 4, 2,
0, 2, 3, 2, 0
);
setDivisor(32);
}
@FXML
private void configureTwoRowSierraPreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 4, 3,
1, 2, 3, 2, 1,
0, 0, 0, 0, 0
);
setDivisor(16);
}
@FXML
private void configureSierraLitePreset(ActionEvent event) {
setCoefficients(
0, 0, 0, 2, 0,
0, 1, 1, 0, 0,
0, 0, 0, 0, 0
);
setDivisor(4);
}
}

View File

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.*?>
<?import javafx.scene.shape.*?>
<?import javafx.geometry.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" maxHeight="482.0" maxWidth="600.0" prefHeight="482.0" prefWidth="600.0" styleClass="mainFxmlClass" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.badvision.outlaweditor.ui.impl.ImageConversionWizardController">
<stylesheets>
<URL value="@/styles/imageconversionwizard.css" />
</stylesheets>
<children>
<HBox layoutX="14.0" layoutY="14.0" styleClass="imageViews" AnchorPane.bottomAnchor="276.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0">
<children>
<StackPane HBox.hgrow="ALWAYS">
<children>
<ScrollPane fitToWidth="true" minHeight="192.0" pannable="true" styleClass="imageViewScroll">
<content>
<AnchorPane minHeight="188.0" styleClass="imageView">
<children>
<ImageView fx:id="sourceImageView" fitHeight="185.0" fitWidth="271.0" pickOnBounds="true" preserveRatio="true" />
</children>
</AnchorPane>
</content>
</ScrollPane>
<Label alignment="BOTTOM_CENTER" text="Source Image" textAlignment="CENTER" StackPane.alignment="BOTTOM_CENTER">
<StackPane.margin>
<Insets bottom="-20.0" />
</StackPane.margin>
</Label>
</children>
</StackPane>
<Region prefHeight="1" prefWidth="14.0" />
<StackPane HBox.hgrow="ALWAYS">
<children>
<ScrollPane fitToWidth="true" minHeight="192.0" pannable="true" styleClass="imageViewScroll">
<content>
<AnchorPane minHeight="188.0" styleClass="imageView">
<children>
<ImageView fx:id="convertedImageView" fitHeight="185.0" fitWidth="271.0" pickOnBounds="true" preserveRatio="true" />
</children>
</AnchorPane>
</content>
</ScrollPane>
<Label alignment="BOTTOM_CENTER" text="Converted" textAlignment="CENTER" StackPane.alignment="BOTTOM_CENTER">
<StackPane.margin>
<Insets bottom="-20.0" />
</StackPane.margin>
</Label>
</children>
</StackPane>
</children>
</HBox>
<Separator layoutX="14.0" layoutY="227.0" prefHeight="9.0" prefWidth="442.0" AnchorPane.bottomAnchor="246.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" />
<TabPane layoutX="14.0" layoutY="231.0" prefHeight="203.0" prefWidth="582.0" side="BOTTOM" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="48.0" AnchorPane.leftAnchor="9.0" AnchorPane.rightAnchor="9.0">
<tabs>
<Tab text="Source Adjustments">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<Label layoutX="14.0" layoutY="14.0" text="Brightness" AnchorPane.leftAnchor="9.0" />
<TextField fx:id="brightnessValue" layoutX="98.0" layoutY="9.0" prefHeight="26.0" prefWidth="60.0" />
<Slider fx:id="brightnessSlider" blockIncrement="0.1" layoutX="148.0" layoutY="15.0" majorTickUnit="0.5" max="1.0" min="-1.0" minorTickCount="10" prefHeight="16.0" prefWidth="415.0" showTickMarks="true" snapToTicks="true" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="0.0" />
<Label layoutX="14.0" layoutY="43.0" text="Contrast" AnchorPane.leftAnchor="9.0" />
<TextField fx:id="contrastValue" layoutX="98.0" layoutY="38.0" prefHeight="26.0" prefWidth="60.0" />
<Slider fx:id="contrastSlider" blockIncrement="0.1" layoutX="148.0" layoutY="44.0" majorTickUnit="0.5" max="1.0" min="-1.0" minorTickCount="10" prefHeight="16.0" prefWidth="415.0" showTickMarks="true" snapToTicks="true" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="0.0" />
<Label layoutX="14.0" layoutY="72.0" text="Hue" AnchorPane.leftAnchor="9.0" />
<TextField fx:id="hueValue" layoutX="98.0" layoutY="67.0" prefHeight="26.0" prefWidth="60.0" />
<Slider fx:id="hueSlider" blockIncrement="0.1" layoutX="148.0" layoutY="73.0" majorTickUnit="0.5" max="1.0" min="-1.0" minorTickCount="10" prefHeight="16.0" prefWidth="415.0" showTickMarks="true" snapToTicks="true" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="0.0" />
<Label layoutX="14.0" layoutY="101.0" text="Saturation" AnchorPane.leftAnchor="9.0" />
<TextField fx:id="saturationValue" layoutX="98.0" layoutY="96.0" prefHeight="26.0" prefWidth="60.0" />
<Slider fx:id="saturationSlider" blockIncrement="0.1" layoutX="148.0" layoutY="102.0" majorTickUnit="0.5" max="1.0" min="-1.0" minorTickCount="10" prefHeight="16.0" prefWidth="415.0" showTickMarks="true" snapToTicks="true" AnchorPane.leftAnchor="158.0" AnchorPane.rightAnchor="0.0" />
<Label layoutX="9.0" layoutY="130.0" text="Crop Top (px)" />
<TextField fx:id="cropTopValue" layoutX="98.0" layoutY="125.0" prefHeight="26.0" prefWidth="60.0" text="0" />
<Label layoutX="168.0" layoutY="130.0" text="Left" />
<TextField fx:id="cropLeftValue" layoutX="196.0" layoutY="125.0" prefHeight="26.0" prefWidth="60.0" text="0" />
<Label layoutX="266.0" layoutY="130.0" text="Bottom" />
<TextField fx:id="cropBottomValue" layoutX="315.0" layoutY="125.0" prefHeight="26.0" prefWidth="60.0" />
<Label layoutX="385.0" layoutY="130.0" text="Right" />
<TextField fx:id="cropRightValue" layoutX="422.0" layoutY="125.0" prefHeight="26.0" prefWidth="60.0" />
</children>
</AnchorPane>
</content>
</Tab>
<Tab text="Diffusion Coefficients">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="175.0" prefWidth="572.0">
<children>
<GridPane layoutX="14.0" layoutY="14.0" prefHeight="110.0" prefWidth="564.0" AnchorPane.leftAnchor="4.0" AnchorPane.rightAnchor="4.0" AnchorPane.topAnchor="9.0">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label alignment="CENTER" contentDisplay="CENTER" prefHeight="16.0" prefWidth="17.0" text="X" textAlignment="CENTER" GridPane.columnIndex="2" GridPane.halignment="CENTER" />
<TextField fx:id="coefficientValue30" text="0" GridPane.columnIndex="3">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue40" text="0" GridPane.columnIndex="4">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue01" text="0" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue11" text="0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue21" text="0" GridPane.columnIndex="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue31" text="0" GridPane.columnIndex="3" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue41" text="0" GridPane.columnIndex="4" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue02" text="0" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue12" text="0" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue22" text="0" GridPane.columnIndex="2" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue32" text="0" GridPane.columnIndex="3" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<TextField fx:id="coefficientValue42" text="0" GridPane.columnIndex="4" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="5.0" right="5.0" />
</GridPane.margin>
</TextField>
<Separator prefWidth="200.0" />
<Separator prefWidth="200.0" GridPane.columnIndex="1" />
</children>
</GridPane>
<SplitMenuButton layoutX="430.0" layoutY="120.0" mnemonicParsing="false" text="Select preset..." AnchorPane.bottomAnchor="13.0" AnchorPane.rightAnchor="9.0">
<items>
<MenuItem mnemonicParsing="false" onAction="#configureFloydSteinbergPreset" text="Floyd-Steinberg" />
<MenuItem mnemonicParsing="false" onAction="#configureFastFloydSteinbergPreset" text="Fast Floyd-Steinberg" />
<MenuItem mnemonicParsing="false" onAction="#configureJarvisJudiceNinkePreset" text="Jarvis-Judice-Ninke" />
<MenuItem mnemonicParsing="false" onAction="#configureStuckiPreset" text="Stucki" />
<MenuItem mnemonicParsing="false" onAction="#configureAtkinsonPreset" text="Atkinson" />
<MenuItem mnemonicParsing="false" onAction="#configureBurkesPreset" text="Burkes" />
<MenuItem mnemonicParsing="false" onAction="#configureSierraPreset" text="Sierra" />
<MenuItem mnemonicParsing="false" onAction="#configureTwoRowSierraPreset" text="Two-Row Sierra" />
<MenuItem mnemonicParsing="false" onAction="#configureSierraLitePreset" text="Sierra Lite" />
</items>
</SplitMenuButton>
<TextField fx:id="divisorValue" layoutX="375.0" layoutY="131.0" prefHeight="26.0" prefWidth="46.0" text="1" AnchorPane.rightAnchor="161.0" />
<Label layoutX="326.0" layoutY="136.0" text="Divisor" AnchorPane.rightAnchor="212.0" />
</children>
</AnchorPane>
</content>
</Tab>
<Tab text="Output Settings">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<Label alignment="CENTER_RIGHT" layoutX="14.0" layoutY="13.0" prefHeight="16.0" prefWidth="53.0" text="Width" textAlignment="RIGHT" />
<TextField fx:id="outputWidthValue" layoutX="69.0" layoutY="8.0" prefHeight="16.0" prefWidth="122.0" promptText="Width (in pixels)" />
<Label alignment="CENTER_RIGHT" layoutX="14.0" layoutY="44.0" prefHeight="16.0" prefWidth="53.0" text="Height" textAlignment="RIGHT" />
<TextField fx:id="outputHeightValue" layoutX="69.0" layoutY="39.0" prefHeight="16.0" prefWidth="122.0" promptText="Height (in pixels)" />
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
<Button onAction="#performQuantizePass" layoutX="14.0" layoutY="412.0" mnemonicParsing="false" prefHeight="26.0" prefWidth="141.0" text="Quantize Pass" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" />
<Button onAction="#performDiffusionPass" layoutX="159.0" layoutY="412.0" mnemonicParsing="false" text="Error Diffusion Pass" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="162.0" />
<Button onAction="#performOK" layoutX="482.0" layoutY="412.0" mnemonicParsing="false" text="OK" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="81.0" />
<Button onAction="#performCancel" layoutX="526.0" layoutY="412.0" mnemonicParsing="false" text="Cancel" AnchorPane.bottomAnchor="14.0" AnchorPane.rightAnchor="14.0" />
</children>
</AnchorPane>

View File

@ -0,0 +1,3 @@
.mainFxmlClass {
}