mirror of
https://github.com/AppleCommander/bastools.git
synced 2025-04-09 03:37:04 +00:00
Allowing image extraction of shapes. #16.
This commit is contained in:
parent
55434b6f53
commit
6a26e37330
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user