Luke Jones

asciiartist - commit c16dbfb

Added source via Uploadc16dbfbf431ecd721bd498bfdc4d2a7e0b0a2cdbLukeJ<152485046+LukePJ25@users.noreply.github.com>2024-09-02T17:12:22+01:00
diff --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