From 8ac0c775b62937792db35e5632070b42500e9075 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 16:10:20 +0300 Subject: [PATCH 1/8] Git ignores --- .gitignore | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f567bac..606b05b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,14 @@ **/target/ -.idea/ - +### IntelliJ IDEA ### +.idea +out +*.iws +*.iml +*.ipr out/ cov-int/ java-ascii-render.tgz + From 0cae2af2a032fe7d8ad2c896259178a359544af7 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 16:16:33 +0300 Subject: [PATCH 2/8] Add commentary --- .../java/com/indvd00m/ascii/render/tests/TestPseudoText.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java index e56e824..0044861 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java @@ -19,7 +19,7 @@ * @author indvd00m (gotoindvdum[at]gmail[dot]com) * @since 1.1.0 */ -@Ignore +@Ignore("Results of this tests is a platform dependent") public class TestPseudoText { @Test From 8ffab902413f01b14fb7bb5e3149665bbfaf41fb Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 16:12:42 +0300 Subject: [PATCH 3/8] Implemented rendering of canvas to image --- .../indvd00m/ascii/render/api/ICanvas.java | 8 + .../ascii/render/api/IImageRender.java | 22 +++ .../com/indvd00m/ascii/render/Canvas.java | 9 +- .../indvd00m/ascii/render/ImageRender.java | 89 +++++++++++ .../ascii/render/elements/PseudoText.java | 39 +---- .../ascii/render/util/AsciiUtils.java | 52 +++++++ .../ascii/render/tests/TestCanvas.java | 138 ++++++++++++++++++ .../ascii/render/tests/TestImageRender.java | 81 ++++++++++ .../ascii/render/tests/TestPseudoText.java | 2 +- .../ascii/render/tests/TestReadme.java | 2 +- 10 files changed, 404 insertions(+), 38 deletions(-) create mode 100644 ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/IImageRender.java create mode 100644 ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java create mode 100644 ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java create mode 100644 ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java diff --git a/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/ICanvas.java b/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/ICanvas.java index bc527a4..c3c4e82 100644 --- a/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/ICanvas.java +++ b/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/ICanvas.java @@ -17,6 +17,14 @@ public interface ICanvas { */ String getText(); + /** + * One line of canvas, without trailed new line symbol. + * + * @param index index of line, from top to bottom + * @return + */ + String getLine(int index); + /** * Height of canvas. * diff --git a/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/IImageRender.java b/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/IImageRender.java new file mode 100644 index 0000000..960e3a8 --- /dev/null +++ b/ascii-render-api/src/main/java/com/indvd00m/ascii/render/api/IImageRender.java @@ -0,0 +1,22 @@ +package com.indvd00m.ascii.render.api; + +import java.awt.image.BufferedImage; + +/** + * An image render. Image render can create and render images from {@link ICanvas}. + * + * @author indvd00m (gotoindvdum[at]gmail[dot]com) + * @since 2.3.0 + */ +public interface IImageRender { + + /** + * Render canvas to image. Width of image will be calculated + * + * @param canvas canvas of text + * @param height height of image in pixels + * @return + */ + BufferedImage render(ICanvas canvas, int height); + +} diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/Canvas.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/Canvas.java index fd45624..16da420 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/Canvas.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/Canvas.java @@ -2,6 +2,7 @@ import com.indvd00m.ascii.render.api.ICanvas; import com.indvd00m.ascii.render.api.IRegion; +import com.indvd00m.ascii.render.util.AsciiUtils; import java.util.ArrayList; import java.util.Iterator; @@ -13,7 +14,8 @@ */ public class Canvas implements ICanvas { - public static final char NULL_CHAR = '\0'; + @Deprecated + public static final char NULL_CHAR = AsciiUtils.NULL_CHAR; protected final int width; protected final int height; @@ -149,6 +151,11 @@ public String getText() { return cachedText; } + @Override + public String getLine(int index) { + return lines.get(index).toString().replace(NULL_CHAR, ' '); + } + @Override public String toString() { return getText(); diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java new file mode 100644 index 0000000..a51b433 --- /dev/null +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java @@ -0,0 +1,89 @@ +package com.indvd00m.ascii.render; + +import com.indvd00m.ascii.render.api.ICanvas; +import com.indvd00m.ascii.render.api.IImageRender; + +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; + +import static com.indvd00m.ascii.render.util.AsciiUtils.getDejaVuSansMonoFont; +import static com.indvd00m.ascii.render.util.AsciiUtils.repeatChar; + +/** + * @author indvd00m (gotoindvdum[at]gmail[dot]com) + * @since 2.3.0 + */ +public class ImageRender implements IImageRender { + + protected Font font; + + public ImageRender() { + } + + /** + * @param font monospaced font + */ + public ImageRender(Font font) { + this.font = font; + } + + @Override + public BufferedImage render(final ICanvas canvas, final int height) { + int textWidth = canvas.getWidth(); + int textHeight = canvas.getHeight(); + int proportionalWidth = (int) ((float) height * textWidth / textHeight); + + Font font = getFont().deriveFont(24); + final int ascent; + final int leading; + final int linesWidth; + float lineHeight = height / textHeight; + { + String sampleString = repeatChar(' ', textWidth); + BufferedImage sampleImage = new BufferedImage(proportionalWidth, height, BufferedImage.TYPE_INT_RGB); + Graphics2D sampleGraphics = sampleImage.createGraphics(); + sampleGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + sampleGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + + FontMetrics fm = sampleGraphics.getFontMetrics(font); + Rectangle2D r2d = fm.getStringBounds(sampleString, sampleGraphics); + float leadingFactor = (float) fm.getLeading() / fm.getHeight(); + float size = (float) (font.getSize2D() * lineHeight / (r2d.getHeight() - r2d.getHeight() * leadingFactor)); + font = font.deriveFont(size); + fm = sampleGraphics.getFontMetrics(font); + ascent = fm.getAscent(); + leading = fm.getLeading(); + linesWidth = (int) fm.getStringBounds(sampleString, sampleGraphics).getWidth(); + } + + BufferedImage image = new BufferedImage(linesWidth, height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = image.createGraphics(); + graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + + Color fontColor = Color.BLACK; + Color backgroundColor = Color.WHITE; + + graphics.setFont(font); + graphics.setColor(backgroundColor); + graphics.fillRect(0, 0, linesWidth, height); + graphics.setColor(fontColor); + for (int i = 0; i < textHeight; i++) { + String line = canvas.getLine(i); + graphics.drawString(line, 0, lineHeight * i + ascent + leading); + } + + return image; + } + + public Font getFont() { + if (font == null) { + font = getDejaVuSansMonoFont(); + } + return font; + } + +} diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java index 3806dbf..cb91882 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java @@ -6,13 +6,11 @@ import com.indvd00m.ascii.render.api.IElement; import com.indvd00m.ascii.render.api.IPoint; -import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; + +import static com.indvd00m.ascii.render.util.AsciiUtils.getDejaVuSansMonoFont; /** * PseudoText element. Default font DejaVu Sans Mono. @@ -149,7 +147,7 @@ public IPoint draw(ICanvas canvas, IContext context) { graphics.setColor(fontColor); graphics.drawString(text, 0, ascent + leading); - // writeImageToPNG(image, "/tmp/pseudotext.png"); + // AsciiUtils.writeImageToPNG(image, "/tmp/pseudotext.png"); for (int imgX = 0; imgX < width; imgX++) { for (int imgY = 0; imgY < height; imgY++) { @@ -205,35 +203,6 @@ protected double getColorDistance(Color c1, Color c2) { return Math.sqrt(weightR * r * r + weightG * g * g + weightB * b * b); } - protected Font createFont() { - InputStream is = null; - try { - is = getClass().getResourceAsStream("/fonts/DejaVuSansMono/DejaVuSansMono.ttf"); - Font font = Font.createFont(Font.TRUETYPE_FONT, is); - return font; - } catch (FontFormatException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - } - - protected void writeImageToPNG(BufferedImage image, String path) { - try { - ImageIO.write(image, "png", new File(path)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public String getText() { return text; } @@ -252,7 +221,7 @@ public int getHeight() { public Font getFont() { if (font == null) { - font = createFont(); + font = getDejaVuSansMonoFont(); } return font; } diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java new file mode 100644 index 0000000..560f4a9 --- /dev/null +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java @@ -0,0 +1,52 @@ +package com.indvd00m.ascii.render.util; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class AsciiUtils { + + public static final char NULL_CHAR = '\0'; + public static final String PNG_FORMAT = "png"; + + public static String repeatChar(char c, int count) { + return repeatString(c + "", count); + } + + public static String repeatString(String s, int count) { + String repeated = new String(new char[count]).replace(NULL_CHAR + "", s); + return repeated; + } + + public static Font getDejaVuSansMonoFont() { + InputStream is = null; + try { + is = AsciiUtils.class.getResourceAsStream("/fonts/DejaVuSansMono/DejaVuSansMono.ttf"); + Font font = Font.createFont(Font.TRUETYPE_FONT, is); + return font; + } catch (FontFormatException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + public static void writeImageToPNG(BufferedImage image, String path) { + try { + ImageIO.write(image, PNG_FORMAT, new File(path)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestCanvas.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestCanvas.java index 2aea94f..fccb80a 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestCanvas.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestCanvas.java @@ -776,4 +776,142 @@ public void test28() { assertFalse(canvas2.equals(canvas1)); } + @Test + public void test29() { + ICanvas canvas = new Canvas(10, 5); + String s = ""; + s += " \n"; + s += " \n"; + s += " \n"; + s += " \n"; + s += " "; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" ", canvas.getLine(0)); + assertEquals(" ", canvas.getLine(1)); + assertEquals(" ", canvas.getLine(2)); + assertEquals(" ", canvas.getLine(3)); + assertEquals(" ", canvas.getLine(4)); + + canvas.draw(0, 1, '1'); + s = ""; + s += " \n"; + s += "1 \n"; + s += " \n"; + s += " \n"; + s += " "; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" ", canvas.getLine(0)); + assertEquals("1 ", canvas.getLine(1)); + assertEquals(" ", canvas.getLine(2)); + assertEquals(" ", canvas.getLine(3)); + assertEquals(" ", canvas.getLine(4)); + + canvas.draw(0, 1, "123"); + s = ""; + s += " \n"; + s += "123 \n"; + s += " \n"; + s += " \n"; + s += " "; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" ", canvas.getLine(0)); + assertEquals("123 ", canvas.getLine(1)); + assertEquals(" ", canvas.getLine(2)); + assertEquals(" ", canvas.getLine(3)); + assertEquals(" ", canvas.getLine(4)); + + canvas.draw(8, 0, "12345"); + s = ""; + s += " 12\n"; + s += "123 \n"; + s += " \n"; + s += " \n"; + s += " "; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 ", canvas.getLine(1)); + assertEquals(" ", canvas.getLine(2)); + assertEquals(" ", canvas.getLine(3)); + assertEquals(" ", canvas.getLine(4)); + + canvas.draw(4, 2, "line1\nline2\nline3"); + s = ""; + s += " 12\n"; + s += "123 \n"; + s += " line1 \n"; + s += " line2 \n"; + s += " line3 "; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 ", canvas.getLine(1)); + assertEquals(" line1 ", canvas.getLine(2)); + assertEquals(" line2 ", canvas.getLine(3)); + assertEquals(" line3 ", canvas.getLine(4)); + + canvas.draw(6, 2, "1line\n2line\n3line"); + s = ""; + s += " 12\n"; + s += "123 \n"; + s += " li1lin\n"; + s += " li2lin\n"; + s += " li3lin"; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 ", canvas.getLine(1)); + assertEquals(" li1lin", canvas.getLine(2)); + assertEquals(" li2lin", canvas.getLine(3)); + assertEquals(" li3lin", canvas.getLine(4)); + + canvas.draw(6, 4, "1line\n2line\n3line"); + s = ""; + s += " 12\n"; + s += "123 \n"; + s += " li1lin\n"; + s += " li2lin\n"; + s += " li1lin"; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 ", canvas.getLine(1)); + assertEquals(" li1lin", canvas.getLine(2)); + assertEquals(" li2lin", canvas.getLine(3)); + assertEquals(" li1lin", canvas.getLine(4)); + + canvas.draw(5, 1, '1', 3); + s = ""; + s += " 12\n"; + s += "123 111 \n"; + s += " li1lin\n"; + s += " li2lin\n"; + s += " li1lin"; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 111 ", canvas.getLine(1)); + assertEquals(" li1lin", canvas.getLine(2)); + assertEquals(" li2lin", canvas.getLine(3)); + assertEquals(" li1lin", canvas.getLine(4)); + + canvas.draw(5, 1, "12", 3); + s = ""; + s += " 12\n"; + s += "123 12121\n"; + s += " li1lin\n"; + s += " li2lin\n"; + s += " li1lin"; + System.out.println(canvas.getText()); + assertEquals(s, canvas.getText()); + assertEquals(" 12", canvas.getLine(0)); + assertEquals("123 12121", canvas.getLine(1)); + assertEquals(" li1lin", canvas.getLine(2)); + assertEquals(" li2lin", canvas.getLine(3)); + assertEquals(" li1lin", canvas.getLine(4)); + } + } diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java new file mode 100644 index 0000000..2f2eff8 --- /dev/null +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java @@ -0,0 +1,81 @@ +package com.indvd00m.ascii.render.tests; + +import com.indvd00m.ascii.render.ImageRender; +import com.indvd00m.ascii.render.Point; +import com.indvd00m.ascii.render.Render; +import com.indvd00m.ascii.render.api.ICanvas; +import com.indvd00m.ascii.render.api.IContextBuilder; +import com.indvd00m.ascii.render.api.IImageRender; +import com.indvd00m.ascii.render.api.IRender; +import com.indvd00m.ascii.render.elements.Label; +import com.indvd00m.ascii.render.elements.Line; +import com.indvd00m.ascii.render.elements.Table; +import org.junit.Test; + +import java.awt.image.BufferedImage; + +import static com.indvd00m.ascii.render.util.AsciiUtils.writeImageToPNG; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * We can not compare images with expected values, because rendering of a font to image is platform dependent operation. + * + * @author indvd00m (gotoindvdum[at]gmail[dot]com) + * @since 0.9.0 + */ +public class TestImageRender { + + @Test + public void test01() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(10).height(1); + builder.element(new Line(new Point(0, 0), new Point(9, 0))); + builder.element(new Label(" test ", 3, 0)); + ICanvas canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "●●● test ●"; + System.out.println(t); + assertEquals(s, t); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 245); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + @Test + public void test02() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(72).height(14); + Table table = new Table(4, 3); + table.setHighlighted(2, 3, true); + builder.element(table); + ICanvas canvas = render.render(builder.build()); + String s = canvas.getText(); + System.out.println(s); + String e = ""; + e += "┌─────────────────┬─────────────────┬─────────────────┬────────────────┐\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "├─────────────────┼─────────────────┼─────────────────┼────────────────┤\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "├─────────────────╆━━━━━━━━━━━━━━━━━╅─────────────────┼────────────────┤\n"; + e += "│ ┃ ┃ │ │\n"; + e += "│ ┃ ┃ │ │\n"; + e += "│ ┃ ┃ │ │\n"; + e += "└─────────────────┺━━━━━━━━━━━━━━━━━┹─────────────────┴────────────────┘"; + assertEquals(e, s); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 245); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + +} diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java index 0044861..db77499 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestPseudoText.java @@ -19,7 +19,7 @@ * @author indvd00m (gotoindvdum[at]gmail[dot]com) * @since 1.1.0 */ -@Ignore("Results of this tests is a platform dependent") +@Ignore("Results of this tests are platform dependent") public class TestPseudoText { @Test diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestReadme.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestReadme.java index 28f30c2..d63c926 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestReadme.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestReadme.java @@ -100,7 +100,7 @@ public void test01() { } { - // conclustion + // conclusion Label label = new Label("CONCLUSION: APPROVE", 0, 1); Region region = new Region(width - label.getText().length(), height - 2, label.getText().length(), 2); builder.layer(region); From 31f36743ac259e32e25503c7240c38b06aea0c43 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 17:25:50 +0300 Subject: [PATCH 4/8] Added tests for image render --- .../ascii/render/tests/TestImageRender.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java index 2f2eff8..6a67ddc 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java @@ -2,6 +2,7 @@ import com.indvd00m.ascii.render.ImageRender; import com.indvd00m.ascii.render.Point; +import com.indvd00m.ascii.render.Region; import com.indvd00m.ascii.render.Render; import com.indvd00m.ascii.render.api.ICanvas; import com.indvd00m.ascii.render.api.IContextBuilder; @@ -9,10 +10,21 @@ import com.indvd00m.ascii.render.api.IRender; import com.indvd00m.ascii.render.elements.Label; import com.indvd00m.ascii.render.elements.Line; +import com.indvd00m.ascii.render.elements.Rectangle; import com.indvd00m.ascii.render.elements.Table; +import com.indvd00m.ascii.render.elements.Text; +import com.indvd00m.ascii.render.elements.plot.Axis; +import com.indvd00m.ascii.render.elements.plot.AxisLabels; +import com.indvd00m.ascii.render.elements.plot.Plot; +import com.indvd00m.ascii.render.elements.plot.api.IPlotPoint; +import com.indvd00m.ascii.render.elements.plot.misc.PlotPoint; +import org.junit.Before; import org.junit.Test; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import static com.indvd00m.ascii.render.util.AsciiUtils.writeImageToPNG; import static org.junit.Assert.assertEquals; @@ -26,6 +38,11 @@ */ public class TestImageRender { + @Before + public void setUpLocale() throws Exception { + Locale.setDefault(Locale.ENGLISH); + } + @Test public void test01() { IRender render = new Render(); @@ -78,4 +95,225 @@ public void test02() { writeImageToPNG(image, "/tmp/ascii-image.png"); } + @Test + public void test03() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + + int width = 80; + int height = 20; + + builder.width(width).height(height); + { + // title + Label title = new Label("EXPERIMENTAL RESULT"); + builder.layer(new Region(width / 2 - title.getText().length() / 2, 0, width, height)); + builder.element(title); + } + + { + // info + builder.layer(new Region(percent(width, 5), percent(height, 10), width, height)); + builder.element(new Label("Theme: Teleportation of matter through extremely dense elements")); + builder.element(new Label("Date: 1998-11-19", 0, 2)); + builder.element(new Label("Time: 08:47", 0, 3)); + builder.element(new Label("Subject: Gordon Freeman", 0, 4)); + } + + { + // plot + Region region = new Region(percent(width, 5), percent(height, 40), percent(width, 50), percent(height, 60)); + builder.layer(region); + builder.element(new Rectangle(0, 0, region.getWidth(), region.getHeight())); + + List points = new ArrayList(); + for (int degree = 0; degree <= 360; degree++) { + double val = Math.cos(Math.toRadians(degree)); + IPlotPoint plotPoint = new PlotPoint(degree, val); + points.add(plotPoint); + } + Region plotRegion = new Region(1, 1, region.getWidth() - 2, region.getHeight() - 2); + builder.element(new Axis(points, plotRegion)); + builder.element(new AxisLabels(points, plotRegion, 5, region.getHeight() - 3)); + builder.element(new Plot(points, plotRegion)); + } + + { + // text + Region region = new Region(percent(width, 60), percent(height, 40), percent(width, 35), + percent(height, 60)); + builder.layer(region); + builder.element(new Text( + "Observation of Einstein-Podolsky-Rosen Entanglement on Supraquantum Structures by Induction Through Nonlinear Transuranic Crystal of Extremely Long Wavelength (ELW) Pulse from Mode-Locked Source Array shows a very promising result.")); + } + + { + // conclusion + Label label = new Label("CONCLUSION: APPROVE", 0, 1); + Region region = new Region(width - label.getText().length(), height - 2, label.getText().length(), 2); + builder.layer(region); + builder.element(new Line(new Point(0, 0), new Point(label.getText().length(), 0), '*')); + builder.element(label); + } + + ICanvas canvas = render.render(builder.build()); + String s = canvas.getText(); + System.out.println(s); + String e = ""; + e += " EXPERIMENTAL RESULT \n"; + e += " \n"; + e += " Theme: Teleportation of matter through extremely dense elements \n"; + e += " \n"; + e += " Date: 1998-11-19 \n"; + e += " Time: 08:47 \n"; + e += " Subject: Gordon Freeman \n"; + e += " \n"; + e += " ┌──────────────────────────────────────┐ Observation of Einstein-Podo \n"; + e += " │ 1.00┼**** *****│ lsky-Rosen Entanglement on S \n"; + e += " │ 0.75┼ *** *** │ upraquantum Structures by In \n"; + e += " │ 0.50┼ ** ** │ duction Through Nonlinear Tr \n"; + e += " │ 0.25┼ ** ** │ ansuranic Crystal of Extreme \n"; + e += " │ 0.00┼ ** ** │ ly Long Wavelength (ELW) Pul \n"; + e += " │-0.25┼ *** *** │ se from Mode-Locked Source A \n"; + e += " │-0.50┼ ********* │ rray shows a very promising \n"; + e += " │-0.75┼ * │ result. \n"; + e += " │-1.00┼───────┼───────┼───────┼───────┼│ \n"; + e += " │ 0 90 180 270 360│ *******************\n"; + e += " └──────────────────────────────────────┘ CONCLUSION: APPROVE"; + assertEquals(e, s); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 360); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + int percent(int value, double percent) { + return (int) (value * percent / 100d); + } + + @Test + public void test04() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(1).height(1); + builder.element(new Label("1", 0, 0)); + ICanvas canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "1"; + System.out.println(t); + assertEquals(s, t); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 30); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + @Test + public void test05() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(62).height(1); + builder.element(new Label("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, 0)); + ICanvas canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + System.out.println(t); + assertEquals(s, t); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 30); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + @Test + public void test06() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(125).height(1); + builder.element(new Label("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, 0)); + ICanvas canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + System.out.println(t); + assertEquals(s, t); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 300); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + @Test + public void test07() { + IRender render = new Render(); + IContextBuilder builder = render.newBuilder(); + builder.width(100).height(1); + builder.element(new Label("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZa", 0, 0)); + ICanvas canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZa"; + System.out.println(t); + assertEquals(s, t); + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 100); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + + @Test + public void test08() { + IRender render = new Render(); + ICanvas canvas; + { + IContextBuilder builder = render.newBuilder(); + builder.width(62).height(1); + builder.element(new Label("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, 0)); + canvas = render.render(builder.build()); + String t = canvas.toString(); + String s = ""; + s += "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + System.out.println(t); + assertEquals(s, t); + } + + IImageRender imageRender = new ImageRender(); + BufferedImage image = imageRender.render(canvas, 30); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + + { + IContextBuilder builder = render.newBuilder(); + builder.width(72).height(14); + Table table = new Table(4, 3); + table.setHighlighted(2, 3, true); + builder.element(table); + canvas = render.render(builder.build()); + String s = canvas.getText(); + System.out.println(s); + String e = ""; + e += "┌─────────────────┬─────────────────┬─────────────────┬────────────────┐\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "├─────────────────┼─────────────────┼─────────────────┼────────────────┤\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "│ │ │ │ │\n"; + e += "├─────────────────╆━━━━━━━━━━━━━━━━━╅─────────────────┼────────────────┤\n"; + e += "│ ┃ ┃ │ │\n"; + e += "│ ┃ ┃ │ │\n"; + e += "│ ┃ ┃ │ │\n"; + e += "└─────────────────┺━━━━━━━━━━━━━━━━━┹─────────────────┴────────────────┘"; + assertEquals(e, s); + } + + image = imageRender.render(canvas, 300); + assertNotNull(image); + writeImageToPNG(image, "/tmp/ascii-image.png"); + } + } From 9869a73a9bda6935be1d6c4f0b865378b0bc5623 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 17:31:09 +0300 Subject: [PATCH 5/8] Load font in constructor --- .../indvd00m/ascii/render/ImageRender.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java index a51b433..e4a42a4 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java @@ -2,6 +2,7 @@ import com.indvd00m.ascii.render.api.ICanvas; import com.indvd00m.ascii.render.api.IImageRender; +import com.indvd00m.ascii.render.util.AsciiUtils; import java.awt.*; import java.awt.geom.Rectangle2D; @@ -16,9 +17,12 @@ */ public class ImageRender implements IImageRender { - protected Font font; + public static final int FONT_REPLICA_STYLE = 24; + public static final char SAMPLE_SYMBOL = ' '; + protected final Font font; public ImageRender() { + this(AsciiUtils.getDejaVuSansMonoFont()); } /** @@ -32,28 +36,30 @@ public ImageRender(Font font) { public BufferedImage render(final ICanvas canvas, final int height) { int textWidth = canvas.getWidth(); int textHeight = canvas.getHeight(); - int proportionalWidth = (int) ((float) height * textWidth / textHeight); - Font font = getFont().deriveFont(24); + Font fontReplica = getFont().deriveFont(FONT_REPLICA_STYLE); + final int ascent; final int leading; final int linesWidth; - float lineHeight = height / textHeight; + float lineHeight = ((float) height) / textHeight; + { - String sampleString = repeatChar(' ', textWidth); - BufferedImage sampleImage = new BufferedImage(proportionalWidth, height, BufferedImage.TYPE_INT_RGB); + int sampleWidth = (int) ((float) height * textWidth / textHeight); + String sampleString = repeatChar(SAMPLE_SYMBOL, textWidth); + BufferedImage sampleImage = new BufferedImage(sampleWidth, height, BufferedImage.TYPE_INT_RGB); Graphics2D sampleGraphics = sampleImage.createGraphics(); sampleGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); sampleGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); - FontMetrics fm = sampleGraphics.getFontMetrics(font); + FontMetrics fm = sampleGraphics.getFontMetrics(fontReplica); Rectangle2D r2d = fm.getStringBounds(sampleString, sampleGraphics); float leadingFactor = (float) fm.getLeading() / fm.getHeight(); - float size = (float) (font.getSize2D() * lineHeight / (r2d.getHeight() - r2d.getHeight() * leadingFactor)); - font = font.deriveFont(size); - fm = sampleGraphics.getFontMetrics(font); + float size = (float) (fontReplica.getSize2D() * lineHeight / (r2d.getHeight() - r2d.getHeight() * leadingFactor)); + fontReplica = fontReplica.deriveFont(size); + fm = sampleGraphics.getFontMetrics(fontReplica); ascent = fm.getAscent(); leading = fm.getLeading(); linesWidth = (int) fm.getStringBounds(sampleString, sampleGraphics).getWidth(); @@ -67,7 +73,7 @@ public BufferedImage render(final ICanvas canvas, final int height) { Color fontColor = Color.BLACK; Color backgroundColor = Color.WHITE; - graphics.setFont(font); + graphics.setFont(fontReplica); graphics.setColor(backgroundColor); graphics.fillRect(0, 0, linesWidth, height); graphics.setColor(fontColor); @@ -79,10 +85,8 @@ public BufferedImage render(final ICanvas canvas, final int height) { return image; } + public Font getFont() { - if (font == null) { - font = getDejaVuSansMonoFont(); - } return font; } From 2d60308bf952b06cd8a934b1084789cd1cccf5df Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 17:41:08 +0300 Subject: [PATCH 6/8] Do not write to file system in tests --- .../ascii/render/tests/TestImageRender.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java index 6a67ddc..19839b0 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java @@ -59,7 +59,7 @@ public void test01() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 245); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -92,7 +92,7 @@ public void test02() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 245); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -184,7 +184,7 @@ public void test03() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 360); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } int percent(int value, double percent) { @@ -206,7 +206,7 @@ public void test04() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 30); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -224,7 +224,7 @@ public void test05() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 30); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -242,7 +242,7 @@ public void test06() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 300); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -260,7 +260,7 @@ public void test07() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 100); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } @Test @@ -282,7 +282,7 @@ public void test08() { IImageRender imageRender = new ImageRender(); BufferedImage image = imageRender.render(canvas, 30); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); { IContextBuilder builder = render.newBuilder(); @@ -313,7 +313,7 @@ public void test08() { image = imageRender.render(canvas, 300); assertNotNull(image); - writeImageToPNG(image, "/tmp/ascii-image.png"); +// writeImageToPNG(image, "/tmp/ascii-image.png"); } } From 1d33ed379ac24f4e79b34efc36244bf67e3354d0 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 17:54:21 +0300 Subject: [PATCH 7/8] Refactoring --- .../src/main/java/com/indvd00m/ascii/render/ImageRender.java | 3 +-- .../java/com/indvd00m/ascii/render/elements/PseudoText.java | 4 ++-- .../main/java/com/indvd00m/ascii/render/util/AsciiUtils.java | 2 +- .../java/com/indvd00m/ascii/render/tests/TestImageRender.java | 3 ++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java index e4a42a4..70d2327 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/ImageRender.java @@ -8,7 +8,6 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import static com.indvd00m.ascii.render.util.AsciiUtils.getDejaVuSansMonoFont; import static com.indvd00m.ascii.render.util.AsciiUtils.repeatChar; /** @@ -22,7 +21,7 @@ public class ImageRender implements IImageRender { protected final Font font; public ImageRender() { - this(AsciiUtils.getDejaVuSansMonoFont()); + this(AsciiUtils.readDejaVuSansMonoFont()); } /** diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java index cb91882..307e193 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/elements/PseudoText.java @@ -10,7 +10,7 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import static com.indvd00m.ascii.render.util.AsciiUtils.getDejaVuSansMonoFont; +import static com.indvd00m.ascii.render.util.AsciiUtils.readDejaVuSansMonoFont; /** * PseudoText element. Default font DejaVu Sans Mono. @@ -221,7 +221,7 @@ public int getHeight() { public Font getFont() { if (font == null) { - font = getDejaVuSansMonoFont(); + font = readDejaVuSansMonoFont(); } return font; } diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java index 560f4a9..2d59376 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java @@ -21,7 +21,7 @@ public static String repeatString(String s, int count) { return repeated; } - public static Font getDejaVuSansMonoFont() { + public static Font readDejaVuSansMonoFont() { InputStream is = null; try { is = AsciiUtils.class.getResourceAsStream("/fonts/DejaVuSansMono/DejaVuSansMono.ttf"); diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java index 19839b0..d6f4009 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java @@ -31,7 +31,8 @@ import static org.junit.Assert.assertNotNull; /** - * We can not compare images with expected values, because rendering of a font to image is platform dependent operation. + * We can not compare images with expected values pyxel by pyxel, because rendering of a font to image is platform + * dependent operation. * * @author indvd00m (gotoindvdum[at]gmail[dot]com) * @since 0.9.0 From 54105c35f512957bdb114df3c224cb883b4e81f4 Mon Sep 17 00:00:00 2001 From: indvd00m Date: Sun, 31 Dec 2023 18:07:55 +0300 Subject: [PATCH 8/8] Fixed javadoc --- .../main/java/com/indvd00m/ascii/render/util/AsciiUtils.java | 4 ++++ .../java/com/indvd00m/ascii/render/tests/TestImageRender.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java index 2d59376..fce7880 100644 --- a/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java +++ b/ascii-render/src/main/java/com/indvd00m/ascii/render/util/AsciiUtils.java @@ -7,6 +7,10 @@ import java.io.IOException; import java.io.InputStream; +/** + * @author indvd00m (gotoindvdum[at]gmail[dot]com) + * @since 2.3.0 + */ public class AsciiUtils { public static final char NULL_CHAR = '\0'; diff --git a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java index d6f4009..74be034 100644 --- a/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java +++ b/ascii-render/src/test/java/com/indvd00m/ascii/render/tests/TestImageRender.java @@ -35,7 +35,7 @@ * dependent operation. * * @author indvd00m (gotoindvdum[at]gmail[dot]com) - * @since 0.9.0 + * @since 2.3.0 */ public class TestImageRender {