asciiartist - commit c16dbfb
Added source via Uploadc16dbfbf431ecd721bd498bfdc4d2a7e0b0a2cdbLukeJ<152485046+LukePJ25@users.noreply.github.com>2024-09-02T17:12:22+01:00diff --git a/ASCIIArtist.iml b/ASCIIArtist.iml
new file mode 100644
index 0000000..77c4b0d
--- /dev/null
+++ b/ASCIIArtist.iml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="AdditionalModuleElements">
+ <content url="file://$MODULE_DIR$" dumb="true">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ </component>
+</module>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..8f49d7b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>groupId</groupId>
+ <artifactId>ASCIIArtist</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <maven.compiler.source>17</maven.compiler.source>
+ <maven.compiler.target>17</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-controls</artifactId>
+ <version>22.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>net.imagej</groupId>
+ <artifactId>ij</artifactId>
+ <version>1.53j</version>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/src/main/java/AsciiArtistController.java b/src/main/java/AsciiArtistController.java
new file mode 100644
index 0000000..36cce31
--- /dev/null
+++ b/src/main/java/AsciiArtistController.java
@@ -0,0 +1,257 @@
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.scene.control.*;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.ColumnConstraints;
+import javafx.scene.layout.GridPane;
+import javafx.geometry.Insets;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.scene.image.ImageView;
+import javafx.scene.image.Image;
+import javafx.scene.text.Font;
+import javafx.stage.FileChooser;
+import javafx.stage.FileChooser.ExtensionFilter;
+import javafx.stage.Stage;
+
+import java.io.File;
+
+public class AsciiArtistController {
+ private GridPane root;
+ private VBox assetItems;
+ private GridPane paramItems;
+ private GridPane imageDetails;
+ private Button selectButton;
+ private Button uploadButton;
+ private Label paramLabelBrightness;
+ private Slider paramSliderBrightness;
+ private Spinner<Integer> paramSpinnerBrightness;
+ private Label paramLabelContrast;
+ private Slider paramSliderContrast;
+ private Spinner<Integer> paramSpinnerContrast;
+ private Label paramLabelScale;
+ private Spinner<Double> paramSpinnerScale;
+ private ComboBox<String> formatSel;
+ private Label paramLabelSkipLine;
+ private Spinner<Integer> paramSpinnerSkipLine;
+ private FileChooser imageOpen;
+ private Stage primStage;
+ private File currentResourcePath;
+ private Image selImage;
+ private ImageView imgPreview;
+ private Label imageDetailsLabel;
+ private Label outputDetailsLabel;
+ private GridPane outputArea;
+ private TextArea outputText;
+ private final Processor processor;
+ private String returnData;
+ private format currentType;
+
+ public AsciiArtistController(Stage stage) {
+ primStage = stage;
+ processor = new Processor();
+ }
+
+ public void syncParamInputs(Slider slider, Spinner<Integer> spinner, int minValue, int maxValue) {
+ double sliderMin = slider.getMin();
+ double sliderMax = slider.getMax();
+ double sliderRange = sliderMax - sliderMin;
+ double spinnerRange = (double) maxValue - (double) minValue;
+ double scale = spinnerRange / sliderRange;
+
+ slider.valueProperty().addListener((obs, oldValue, newValue) -> {
+ double scaledValue = (newValue.doubleValue() - sliderMin) * scale + (double) minValue;
+ spinner.getValueFactory().setValue((int) Math.round(scaledValue));
+ });
+ spinner.valueProperty().addListener((obs, oldValue, newValue) -> {
+ double scaledValue = (newValue - (double) minValue) / scale + sliderMin;
+ slider.setValue(scaledValue);
+ });
+ formatSel.valueProperty().addListener((obs, oldValue, newValue) -> {
+ refreshType();
+ ensureVal();
+ });
+ double initialValue = (slider.getValue() - sliderMin) * scale + (double) minValue;
+ spinner.getValueFactory().setValue((int) Math.round(initialValue));
+ }
+
+ public void ensureVal() {
+ uploadButton.setDisable(true);
+ paramSpinnerScale.setDisable(true);
+ if (selImage != null) {
+ if (currentType != null) {
+ uploadButton.setDisable(false);
+ paramSpinnerScale.setDisable(false);
+ }
+ }
+ }
+
+ EventHandler<ActionEvent> processImageEvent = new EventHandler<ActionEvent>() {
+ @Override
+ public void handle(ActionEvent actionEvent) {
+ processImage();
+ }
+
+ private void processImage() {
+ returnData = processor.callIJConvert(paramSpinnerContrast.getValue(), paramSpinnerBrightness.getValue(), currentType);
+ outputText.clear();
+ outputText.setText(returnData);
+ }
+ };
+
+ EventHandler<ActionEvent> selectImageEvent = new EventHandler<ActionEvent>() {
+ @Override
+ public void handle(ActionEvent event) {
+ selectImageInterface();
+ }
+
+ private void selectImageInterface() {
+ imageOpen = new FileChooser();
+ imageOpen.setTitle("Select Image File");
+ imageOpen.getExtensionFilters().addAll(
+ new ExtensionFilter("Image Files", "*.png", "*.jpg"),
+ new ExtensionFilter("All Files", "*.*"));
+ File selectedFile = imageOpen.showOpenDialog(primStage);
+ if (selectedFile != null) {
+ currentResourcePath = selectedFile;
+ selImage = new Image(String.valueOf(currentResourcePath.toURI()));
+ imgPreview.setImage(selImage);
+ imageDetailsLabel.setText(String.valueOf(currentResourcePath.getAbsoluteFile()));
+ processor.setRes(currentResourcePath);
+ processor.setOutputScale(1);
+ updateOutputDetails();
+ }
+ ensureVal();
+ }
+ };
+
+ public void refreshType() {
+ switch (formatSel.getValue()) {
+ case "Alphanumeric":
+ currentType = format.alpha;
+ break;
+ case "Block":
+ currentType = format.block;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + formatSel.getValue());
+ }
+ }
+
+ public GridPane initGetGrid() {
+ root = new GridPane();
+ assetItems = new VBox(5);
+ paramItems = new GridPane();
+ imageDetails = new GridPane();
+
+ ColumnConstraints col1 = new ColumnConstraints();
+ col1.setMinWidth(200);
+ col1.setMaxWidth(200);
+ col1.setHgrow(Priority.ALWAYS);
+ ColumnConstraints col2 = new ColumnConstraints();
+ col2.setHgrow(Priority.ALWAYS);
+ root.getColumnConstraints().addAll(col1, col2);
+
+ selectButton = new Button("Select Image");
+ uploadButton = new Button("Generate ASCII Art");
+ selectButton.setOnAction(selectImageEvent);
+ uploadButton.setOnAction(processImageEvent);
+
+ paramLabelBrightness = new Label("Brightness");
+ paramSliderBrightness = new Slider(-1, 1, 0);
+ paramSpinnerBrightness = new Spinner<>(-100, 100, 0);
+ paramLabelContrast = new Label("Contrast");
+ paramSliderContrast = new Slider(-1, 1, 0.01);
+ paramSpinnerContrast = new Spinner<>(-100, 100, 1);
+ paramLabelScale = new Label("Output Scale");
+
+ paramSpinnerScale = new Spinner<>(new SpinnerValueFactory.DoubleSpinnerValueFactory(0.0, 2.0, 1.0, 0.05));
+ paramSpinnerScale.setPrefWidth(52);
+ paramSpinnerScale.setEditable(true);
+ paramSpinnerScale.valueProperty().addListener((obs, oldValue, newValue) -> {
+ processor.setOutputScale(newValue);
+ updateOutputDetails();
+ });
+
+ paramLabelSkipLine = new Label("Skip Line Every...");
+ paramSpinnerSkipLine = new Spinner<>(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 100, 0));
+ paramSpinnerSkipLine.setPrefWidth(52);
+ paramSpinnerSkipLine.setEditable(true);
+ paramSpinnerSkipLine.valueProperty().addListener((obs, oldValue, newValue) -> {
+ processor.setSkipLineEvery(newValue);
+ });
+
+ paramSpinnerBrightness.setPrefWidth(52);
+ paramSpinnerContrast.setPrefWidth(52);
+ paramItems.setPrefWidth(200);
+
+ formatSel = new ComboBox<>();
+ formatSel.getItems().add("Alphanumeric");
+ formatSel.getItems().add("Block");
+ formatSel.setValue("Alphanumeric");
+ refreshType();
+
+ outputDetailsLabel = new Label("");
+
+ paramSpinnerBrightness.setEditable(true);
+ paramSpinnerContrast.setEditable(true);
+
+ syncParamInputs(paramSliderBrightness, paramSpinnerBrightness, -100, 100);
+ syncParamInputs(paramSliderContrast, paramSpinnerContrast, -100, 100);
+
+ assetItems.getChildren().addAll(selectButton, uploadButton);
+ paramItems.add(paramLabelBrightness, 0, 0);
+ paramItems.add(paramSpinnerBrightness, 1, 0);
+ paramItems.add(paramSliderBrightness, 0, 1, 2, 1);
+ paramItems.add(paramLabelContrast, 0, 2);
+ paramItems.add(paramSpinnerContrast, 1, 2);
+ paramItems.add(paramSliderContrast, 0, 3, 2, 1);
+ paramItems.add(formatSel, 0, 4, 2, 1);
+ paramItems.add(paramLabelScale, 0, 5);
+ paramItems.add(paramSpinnerScale, 1, 5);
+ paramItems.add(paramLabelSkipLine,0,6);
+ paramItems.add(paramSpinnerSkipLine, 1,6);
+ paramItems.add(outputDetailsLabel,0,7,2,1);
+
+ imgPreview = new ImageView();
+ imgPreview.setFitWidth(200);
+ imgPreview.setPreserveRatio(true);
+ imageDetailsLabel = new Label();
+ imageDetailsLabel.setMaxWidth(200);
+
+ imageDetails.add(imgPreview, 0, 0, 2, 1);
+ imageDetails.add(imageDetailsLabel, 0, 1, 1, 1);
+
+ outputArea = new GridPane();
+ outputText = new TextArea();
+ outputText.setFont(Font.font("Monospaced", 12));
+ outputText.setPrefWidth(1800);
+ outputText.setPrefHeight(1800);
+ outputText.clear();
+ outputArea.add(outputText, 0, 1, 1, 1);
+
+ root.setHgap(5);
+ root.setVgap(5);
+ root.setPadding(new Insets(10, 10, 10, 10));
+ root.add(assetItems, 0, 0, 1, 1);
+ root.add(paramItems, 0, 1, 1, 1);
+ root.add(imageDetails, 0, 2, 1, 1);
+ root.add(outputArea, 1, 0, 1, 3);
+
+ ensureVal();
+ return root;
+ }
+
+
+ private void updateOutputDetails() {
+ String warningText;
+ String resol = (processor.getOutputResX() + " x " + processor.getOutputResY());
+ if(processor.getNumberOfCharacters() >= 120000) {
+ warningText = "\nThis may take some time to process!";
+ } else {
+ warningText = "";
+ }
+ outputDetailsLabel.setText("Output Size: \n" + resol + "\nNumber of Characters (approx.):\n" + processor.getNumberOfCharacters() + warningText);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/Main.java b/src/main/java/Main.java
new file mode 100644
index 0000000..92a5e06
--- /dev/null
+++ b/src/main/java/Main.java
@@ -0,0 +1,27 @@
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.scene.layout.*;
+import javafx.stage.Stage;
+
+import java.util.Objects;
+
+public class Main extends Application {
+
+ @Override
+ public void start(Stage primaryStage) {
+ AsciiArtistController primaryCont = new AsciiArtistController(primaryStage);
+ GridPane root = primaryCont.initGetGrid();
+
+ Scene mainScene = new Scene(root, 1100, 800);
+ Image icon = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/resources/icon.png")));
+ primaryStage.getIcons().add(icon);
+ primaryStage.setTitle("ASCIIArtist");
+ primaryStage.setScene(mainScene);
+ primaryStage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
diff --git a/src/main/java/Processor.java b/src/main/java/Processor.java
new file mode 100644
index 0000000..1ecb27e
--- /dev/null
+++ b/src/main/java/Processor.java
@@ -0,0 +1,144 @@
+import java.io.File;
+
+import ij.ImagePlus;
+import ij.process.ImageProcessor;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+public class Processor {
+ private String[] alphaGrad;
+ private String[] blockGrad;
+ private File res;
+ private String returnText;
+ private ImagePlus imgPlus;
+ private ImageProcessor imgProcessor;
+ private double outputScale;
+ private int outputResX;
+ private int outputResY;
+ private ArrayList<ArrayList<Double>> imageMatrix;
+ private int pixelCount;
+ private int skipLineEvery = 0;
+
+ public Processor() {
+ alphaGrad = new String[]{"@", "#", "S", "%", "?", "*", "+", ";", ":", ",", ".", " "};
+ blockGrad = new String[]{"█", "▓", "▒", "░", " "};
+ }
+
+ public void setRes(File resource) {
+ res = resource;
+ imgPlus = new ImagePlus(res.getPath());
+ imgProcessor = imgPlus.getProcessor();
+ }
+
+ public void setOutputScale(double newOutputScale) {
+ outputScale = newOutputScale;
+ outputResX = (int) (imgProcessor.getWidth() * outputScale);
+ outputResY = (int) (imgProcessor.getHeight() * outputScale);
+
+ int skippedLines = 0;
+ if (skipLineEvery > 0) {
+ skippedLines = outputResY / (skipLineEvery + 1);
+ }
+
+ pixelCount = outputResX * (outputResY - skippedLines);
+ }
+
+ public int getOutputResX() {
+ return outputResX;
+ }
+
+ public int getOutputResY() {
+ return outputResY;
+ }
+
+ public double getOutputScale() {
+ return outputScale;
+ }
+
+ public int getNumberOfCharacters() {
+ return pixelCount;
+ }
+
+ public void setSkipLineEvery(int skip) {
+ skipLineEvery = skip;
+ }
+
+ public String callIJConvert(double brightness, double contrast, format form) {
+ imageMatrix = getBrightnessValueArray(brightness, contrast);
+
+ int rowCount = 0;
+ returnText = "";
+
+ for (ArrayList<Double> matrix : imageMatrix) {
+ if (skipLineEvery > 0 && (rowCount % (skipLineEvery + 1)) == skipLineEvery) {
+ rowCount++;
+ continue;
+ }
+ for (Double aDouble : matrix) {
+ String character;
+ if (aDouble == -1) {
+ character = " ";
+ } else {
+ int index;
+ switch (form) {
+ case alpha -> {
+ index = (int) Math.max(0, Math.min(alphaGrad.length - 1, (aDouble / 255.0) * (alphaGrad.length - 1)));
+ character = alphaGrad[index];
+ }
+ case block -> {
+ index = (int) Math.max(0, Math.min(blockGrad.length - 1, (aDouble / 255.0) * (blockGrad.length - 1)));
+ character = blockGrad[index];
+ }
+ default -> {
+ character = " ";
+ }
+ }
+ }
+ returnText += character;
+ }
+ returnText += "\n";
+ rowCount++;
+ }
+ return returnText;
+ }
+
+ private ArrayList<ArrayList<Double>> getBrightnessValueArray(double contrast, double brightness) {
+ ArrayList<ArrayList<Double>> Vals = new ArrayList<ArrayList<Double>>();
+ for (int i = 0; i < outputResY; i++) {
+ ArrayList<Double> xVals = new ArrayList<Double>();
+ for (int j = 0; j < outputResX; j++) {
+ int originalX = (int) (j / outputScale);
+ int originalY = (int) (i / outputScale);
+
+ int p = imgProcessor.getPixel(originalX, originalY);
+ Color color = new Color(p, true);
+ double val;
+
+ if (color.getAlpha() == 0) {
+ val = -1;
+ } else {
+ val = getDeSat(color) + brightness;
+ val = adjustContrast(val, contrast);
+ val = Math.max(0, Math.min(255, val));
+ }
+
+ xVals.add(val);
+ }
+ Vals.add(xVals);
+ }
+ return Vals;
+ }
+
+ private double adjustContrast(double brightness, double contrast) {
+ double averageBrightness = 128.0;
+ return averageBrightness + (brightness - averageBrightness) * contrast;
+ }
+
+ private double getDeSat(Color c) {
+ int r = c.getRed();
+ int g = c.getGreen();
+ int b = c.getBlue();
+ return ((0.2989 * r) + (0.5870 * g) + (0.1140 * b));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/format.java b/src/main/java/format.java
new file mode 100644
index 0000000..6ec1212
--- /dev/null
+++ b/src/main/java/format.java
@@ -0,0 +1,4 @@
+public enum format{
+ block,
+ alpha
+}
\ No newline at end of file
diff --git a/src/resources/icon.png b/src/resources/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b06e2d62c531a3a0a34f5212875b78528223cc08
GIT binary patch
literal 795
zcmV+$1LXXPP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0<cL$K~y+TZIa(h
zQ(+j#zwdj_+3D<;HFcWH8RSU%aS>S2mO+6LAxO4$A(TPuMl4BYu$%O%E5Qq|{2xN;
zCI)pQbXP$L77j*=u8dOGHuuAGwzu~^J0n?s;P5{0d7tm|KF{+$<m%lC=2R+>z_3@5
z;F5LND1%-l*ea<gS1B6l4=~FD$d#c9_VUGZDBMq#7%EkUVud2N4;WP-&(}Kl``Rq4
z2H{YhQT*Ab_*o#>%n_vX6r0(auxwbKoJO%A7K<^SEJ3kU<j%~zHLrsg1D*+Z88yel
z#DbZb8A6#+z^{!J7=;~ZHVK+SpvnXa|M>P$MCxJ{8AzH#oo;U`<EW<tEv+5AnZkiV
zmUs}}mwUngZ})gS(~(F7i9~|X^p_7<S$>1;_6By+NqkRzf<sksgg2D3e;r(0T*UD3
zFobY?e4NDNaddTcF`}v}3*I=3K;Se;RY$Y;3_|@QNPhi{Z^@4seK>;o`FVuHVT3{<
zGB-D8zTw!|m{|(xlx22#;1Udt!td4iyE`G<d{}z-ig$Ahw{FIeN~N&AzHVkRI5>!@
zsVS09r`hc6EXV~LVFN?guq#V&cY2Xb{lM$Q62$zp9P^;Ze;%b$39GBC$Ye5PVPS#w
z_V${={C+<n=M=&o-06eia$(ykqsbHCrD(|Q<*~Dy!JVNn%H=X#{5%*8!fv;lsb{lU
z7L7(hdQ{4yk0Q`cp2FJiHTb;Ua64N06f!&>ePZ5JM+g~5*tuK|ZnvAzeR~yZyAQ2x
zJ_LN-piodO7ND0)rbr-!Vy%nCUWkQFoh`?4qPqvGP2mxnVN+}{j0#4dO^S1qgF^Gi
z0ccx$2b?Z9s6=5@7@Q6bPbcHnagDlA1ZoUIZePF1z^c#<9dF++6EU9FnK|o?d&{%{
Z{sJ=z1xBI|Ag2HT002ovPDHLkV1gouY1IG#
literal 0
HcmV?d00001