Allowing image extraction of shapes. #16.

This commit is contained in:
Rob Greene 2018-06-19 11:57:58 -05:00
parent 55434b6f53
commit 6a26e37330
8 changed files with 256 additions and 15 deletions

View File

@ -67,6 +67,17 @@ public class BitmapShape implements Shape {
}
return grid.get(y).get(x);
}
@Override
public boolean isEmpty() {
boolean isEmpty = false;
for (List<Boolean> row : grid) {
for (Boolean plot : row) {
isEmpty |= plot;
}
}
return isEmpty;
}
@Override
public BitmapShape toBitmap() {

View File

@ -1,6 +1,10 @@
package io.github.applecommander.bastools.api.shapes;
public interface Shape {
/** Indicates if this shape is empty. */
public boolean isEmpty();
/** Transform to a BitmapShape. */
public BitmapShape toBitmap();
/** Transform to a VectorShape. */
public VectorShape toVector();
}

View File

@ -7,11 +7,12 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import io.github.applecommander.bastools.api.shapes.exporters.ImageShapeExporter;
import io.github.applecommander.bastools.api.shapes.exporters.TextShapeExporter;
public interface ShapeExporter {
/** Export a single shape to the OutputStream. */
public void export(Shape shape, OutputStream outputStream);
public void export(Shape shape, OutputStream outputStream) throws IOException;
/** Export a single shape to the File. */
public default void export(Shape shape, File file) throws IOException {
Objects.requireNonNull(shape);
@ -28,7 +29,7 @@ public interface ShapeExporter {
}
/** Export the entire shape table to the OutputStream. */
public void export(ShapeTable shapeTable, OutputStream outputStream);
public void export(ShapeTable shapeTable, OutputStream outputStream) throws IOException;
/** Export the entire shape table to the File. */
public default void export(ShapeTable shapeTable, File file) throws IOException {
Objects.requireNonNull(shapeTable);
@ -47,4 +48,7 @@ public interface ShapeExporter {
public static TextShapeExporter.Builder text() {
return new TextShapeExporter.Builder();
}
public static ImageShapeExporter.Builder image() {
return new ImageShapeExporter.Builder();
}
}

View File

@ -47,6 +47,11 @@ public class VectorShape implements Shape {
this.vectors.add(vectorCommand);
return this;
}
@Override
public boolean isEmpty() {
return vectors.isEmpty();
}
@Override
public BitmapShape toBitmap() {

View File

@ -0,0 +1,175 @@
package io.github.applecommander.bastools.api.shapes.exporters;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import io.github.applecommander.bastools.api.shapes.BitmapShape;
import io.github.applecommander.bastools.api.shapes.Shape;
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
import io.github.applecommander.bastools.api.shapes.ShapeTable;
public class ImageShapeExporter implements ShapeExporter {
private int maxWidth = 1024;
private int pixelSize = 4;
private int padding = 2;
private boolean border = true;
private boolean skipEmptyShapes;
private String imageFormat = "PNG";
/** Use the {@code Builder} to create a ImageShapeExporter. */
private ImageShapeExporter() { }
@Override
public void export(Shape shape, OutputStream outputStream) throws IOException {
Objects.requireNonNull(shape);
Objects.requireNonNull(outputStream);
export(Arrays.asList(shape.toBitmap()), outputStream);
// int shapeWidth = pixelSize * bshape.getWidth();
// int shapeHeight = pixelSize * bshape.getHeight();
// int imageWidth = shapeWidth + (border ? 2 : 0);
// int imageHeight = shapeHeight + (border ? 2 : 0);
// int borderEdgeWidth = border ? 1+padding : 0;
// BufferedImage image = new BufferedImage(imageWidth + borderEdgeWidth*2,
// imageHeight + borderEdgeWidth*2,
// BufferedImage.TYPE_INT_RGB);
//
// Graphics g = image.createGraphics();
// if (border) drawBorders(g, shapeWidth, shapeHeight, imageWidth, imageHeight);
// drawShapeAt(g, bshape, new Point(borderEdgeWidth,borderEdgeWidth));
// g.dispose();
// ImageIO.write(image, imageFormat, outputStream);
}
@Override
public void export(ShapeTable shapeTable, OutputStream outputStream) throws IOException {
Objects.requireNonNull(shapeTable);
Objects.requireNonNull(outputStream);
List<BitmapShape> blist = shapeTable.shapes.stream()
.filter(this::displayThisShape)
.map(Shape::toBitmap)
.collect(Collectors.toList());
export(blist, outputStream);
}
public void export(List<BitmapShape> blist, OutputStream outputStream) throws IOException {
Objects.requireNonNull(blist);
Objects.requireNonNull(outputStream);
int shapeWidth = pixelSize * blist.stream().mapToInt(BitmapShape::getWidth).max().getAsInt();
int shapeHeight = pixelSize * blist.stream().mapToInt(BitmapShape::getHeight).max().getAsInt();
int borderDividerWidth = border ? 1+padding*2 : 0;
int borderEdgeWidth = border ? 1+padding : 0;
int columns = Math.min(blist.size(), Math.max(1, this.maxWidth / shapeWidth));
int rows = (blist.size() + columns - 1) / columns;
int imageWidth = borderEdgeWidth*2 + columns*shapeWidth + (columns-1)*borderDividerWidth;
int imageHeight = borderEdgeWidth*2 + rows*shapeHeight + (rows-1)*borderDividerWidth;
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
Queue<BitmapShape> bqueue = new LinkedList<>(blist);
Graphics g = image.createGraphics();
if (border) drawBorders(g, shapeWidth, shapeHeight, imageWidth, imageHeight);
Point pt = new Point(borderEdgeWidth, borderEdgeWidth);
while (!bqueue.isEmpty()) {
BitmapShape bshape = bqueue.remove();
drawShapeAt(g, bshape, pt);
pt.x += shapeWidth + borderDividerWidth;
if (pt.x > imageWidth) {
pt.y += shapeHeight + borderDividerWidth;
pt.x = borderEdgeWidth;
}
}
g.dispose();
ImageIO.write(image, imageFormat, outputStream);
}
private boolean displayThisShape(Shape shape) {
return !(skipEmptyShapes && shape.isEmpty());
}
public void drawBorders(Graphics g, int shapeWidth, int shapeHeight, int imageWidth, int imageHeight) {
g.setColor(Color.white);
int paddingWidth = border ? padding : 0;
for (int x=0; x<imageWidth; x+=shapeWidth + paddingWidth*2 + 1) {
for (int y=0; y<imageHeight; y+=shapeHeight + paddingWidth*2 + 1) {
g.drawLine(x, 0, x, imageHeight);
g.drawLine(0, y, imageWidth, y);
}
}
}
public void drawShapeAt(Graphics g, BitmapShape shape, Point origin) {
for (int x=0; x<shape.getWidth(); x++) {
for (int y=0; y<shape.getHeight(); y++) {
g.setColor(shape.get(x, y) ? Color.white : Color.lightGray);
g.fillRect(origin.x + (x*pixelSize), origin.y + (y*pixelSize),
pixelSize, pixelSize);
}
}
}
public static class Builder {
private ImageShapeExporter shapeExporter = new ImageShapeExporter();
public Builder maxWidth(int maxWidth) {
shapeExporter.maxWidth = maxWidth;
return this;
}
public Builder pixelSize(int pixelSize) {
shapeExporter.pixelSize = pixelSize;
return this;
}
public Builder border(boolean border) {
shapeExporter.border = border;
return this;
}
public Builder jpeg() {
return imageFormat("JPEG");
}
public Builder png() {
return imageFormat("PNG");
}
public Builder bmp() {
return imageFormat("BMP");
}
public Builder wbmp() {
return imageFormat("WBMP");
}
public Builder gif() {
return imageFormat("GIF");
}
public Builder imageFormat(String imageFormat) {
shapeExporter.imageFormat = imageFormat;
return this;
}
public Builder skipEmptyShapes() {
return skipEmptyShapes(true);
}
public Builder skipEmptyShapes(boolean skipEmptyShapes) {
shapeExporter.skipEmptyShapes = skipEmptyShapes;
return this;
}
public ShapeExporter build() {
return shapeExporter;
}
}
}

View File

@ -19,6 +19,7 @@ import io.github.applecommander.bastools.api.shapes.ShapeTable;
public class TextShapeExporter implements ShapeExporter {
private int maxWidth = 80;
private BorderStrategy borderStrategy = BorderStrategy.BOX_DRAWING;
private boolean skipEmptyShapes;
/** Use the {@code Builder} to create a TextShapeExporter. */
private TextShapeExporter() { }
@ -45,6 +46,7 @@ public class TextShapeExporter implements ShapeExporter {
Objects.requireNonNull(outputStream);
List<BitmapShape> blist = shapeTable.shapes.stream()
.filter(this::displayThisShape)
.map(Shape::toBitmap)
.collect(Collectors.toList());
int width = blist.stream().mapToInt(BitmapShape::getWidth).max().getAsInt();
@ -66,6 +68,10 @@ public class TextShapeExporter implements ShapeExporter {
pw.flush();
}
private boolean displayThisShape(Shape shape) {
return !(skipEmptyShapes && shape.isEmpty());
}
private void drawTopLine(PrintWriter pw, int columns, int width) {
borderStrategy.topLeftCorner(pw);
borderStrategy.horizontalLine(pw, width);
@ -241,6 +247,14 @@ public class TextShapeExporter implements ShapeExporter {
textShapeExporter.borderStrategy = borderStrategy;
return this;
}
public Builder skipEmptyShapes() {
return skipEmptyShapes(true);
}
public Builder skipEmptyShapes(boolean skipEmptyShapes) {
textShapeExporter.skipEmptyShapes = skipEmptyShapes;
return this;
}
public ShapeExporter build() {
return textShapeExporter;

View File

@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.Test;
@ -54,7 +55,7 @@ public class ShapesTest {
}
@Test
public void testTextShapeExporterNoBorder() {
public void testTextShapeExporterNoBorder() throws IOException {
ShapeTable st = readStandardShapeTable();
final String expected = ".XXX.\n"
@ -72,7 +73,7 @@ public class ShapesTest {
}
@Test
public void testTextShapeExporterAsciiBorder() {
public void testTextShapeExporterAsciiBorder() throws IOException {
ShapeTable st = readStandardShapeTable();
final String expected = "+-----+\n"
@ -92,7 +93,7 @@ public class ShapesTest {
}
@Test
public void testTextShapeTableExporterNoBorder() {
public void testTextShapeTableExporterNoBorder() throws IOException {
ShapeTable st = readStandardShapeTable();
// Simulate 4 of these identical shapes by adding 3 more
@ -116,7 +117,7 @@ public class ShapesTest {
}
@Test
public void testTextShapeTableExporterAsciiBorder() {
public void testTextShapeTableExporterAsciiBorder() throws IOException {
ShapeTable st = readStandardShapeTable();
// Simulate 4 of these identical shapes by adding 3 more

View File

@ -34,9 +34,15 @@ public class ExtractCommand implements Callable<Void> {
@Option(names = "--border", description = "Set border style (none, simple, box)", showDefaultValue = Visibility.ALWAYS)
private String borderStyle = "simple";
@Option(names = { "-w", "--width" }, description = "Set text width", showDefaultValue = Visibility.ALWAYS)
private int textWidth = 80;
@Option(names = "--format", description = "Select output format (text, png, gif, jpeg, bmp, wbmp)", showDefaultValue = Visibility.ALWAYS)
private String outputFormat = "text";
@Option(names = "--skip-empty", description = "Skip empty shapes")
private boolean skipEmptyShapesFlag = false;
@Option(names = { "-w", "--width" }, description = "Set width (defaults: text=80, image=1024)")
private int width = -1;
@Option(names = "--shape", description = "Extract specific shape")
private int shapeNum = 0;
@ -47,15 +53,10 @@ public class ExtractCommand implements Callable<Void> {
@Override
public Void call() throws IOException {
validateArguments();
ShapeExporter exporter = validateAndParseArguments();
ShapeTable shapeTable = stdinFlag ? ShapeTable.read(System.in) : ShapeTable.read(file);
ShapeExporter exporter = ShapeExporter.text()
.borderStrategy(borderStrategy)
.maxWidth(textWidth)
.build();
if (shapeNum > 0) {
if (shapeNum <= shapeTable.shapes.size()) {
Shape shape = shapeTable.shapes.get(shapeNum-1);
@ -78,7 +79,7 @@ public class ExtractCommand implements Callable<Void> {
return null;
}
private void validateArguments() throws IOException {
private ShapeExporter validateAndParseArguments() throws IOException {
if (stdoutFlag && filename != null) {
throw new IOException("Please choose one of stdout or output file");
}
@ -98,5 +99,31 @@ public class ExtractCommand implements Callable<Void> {
default:
throw new IOException("Please select a valid border strategy");
}
ShapeExporter exporter = null;
switch (outputFormat) {
case "text":
exporter = ShapeExporter.text()
.borderStrategy(borderStrategy)
.maxWidth(width == -1 ? 80 : width)
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
case "png":
case "jpeg":
case "gif":
case "bmp":
case "wbmp":
exporter = ShapeExporter.image()
.border(borderStrategy != BorderStrategy.NONE)
.maxWidth(width == -1 ? 1024 : width)
.imageFormat(outputFormat)
.skipEmptyShapes(skipEmptyShapesFlag)
.build();
break;
default:
throw new IOException("Please select a valid output format");
}
return exporter;
}
}