mirror of
https://github.com/AppleCommander/bastools.git
synced 2025-01-21 13:32:36 +00:00
Adding a bitmap vectorization based on distance between two points;
fixed a bug in the tracking of origin when generating a bitmap. #16.
This commit is contained in:
parent
13a6270723
commit
efbf0973bb
@ -34,7 +34,7 @@ public class BitmapShape implements Shape {
|
||||
}
|
||||
|
||||
public void insertColumn() {
|
||||
origin.y++;
|
||||
origin.x++;
|
||||
for (List<Boolean> row : grid) {
|
||||
row.add(0, Boolean.FALSE);
|
||||
}
|
||||
@ -45,7 +45,7 @@ public class BitmapShape implements Shape {
|
||||
}
|
||||
}
|
||||
public void insertRow() {
|
||||
origin.x++;
|
||||
origin.y++;
|
||||
grid.add(0, newRow(getWidth()));
|
||||
}
|
||||
public void addRow() {
|
||||
@ -61,7 +61,7 @@ public class BitmapShape implements Shape {
|
||||
origin.y = grid.size();
|
||||
};
|
||||
for (char pixel : line.toCharArray()) {
|
||||
switch (pixel) {
|
||||
switch (Character.toLowerCase(pixel)) {
|
||||
case '+':
|
||||
setOrigin.run();
|
||||
// fall through to '.'
|
||||
@ -138,7 +138,8 @@ public class BitmapShape implements Shape {
|
||||
new SweepVectorization(this, VectorCommand.MOVE_DOWN, VectorCommand.MOVE_RIGHT),
|
||||
new SweepVectorization(this, VectorCommand.MOVE_DOWN, VectorCommand.MOVE_LEFT),
|
||||
new SweepVectorization(this, VectorCommand.MOVE_UP, VectorCommand.MOVE_RIGHT),
|
||||
new SweepVectorization(this, VectorCommand.MOVE_UP, VectorCommand.MOVE_LEFT)
|
||||
new SweepVectorization(this, VectorCommand.MOVE_UP, VectorCommand.MOVE_LEFT),
|
||||
new EuclidianDistanceVectorization(this)
|
||||
);
|
||||
|
||||
int byteLength = Integer.MAX_VALUE;
|
||||
@ -249,4 +250,95 @@ public class BitmapShape implements Shape {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a bitmap shape by using the Euclidean distance between plotted points to determine
|
||||
* which vectors take precedence. Most of the math is captured in the Point class, which makes
|
||||
* this much more straight-forward.
|
||||
*/
|
||||
public static class EuclidianDistanceVectorization implements Supplier<VectorShape> {
|
||||
private List<Point> points;
|
||||
private BitmapShape bitmapShape;
|
||||
private VectorShape vshape;
|
||||
|
||||
public EuclidianDistanceVectorization(BitmapShape bitmapShape) {
|
||||
this.bitmapShape = bitmapShape;
|
||||
this.points = new ArrayList<>();
|
||||
this.vshape = new VectorShape();
|
||||
// Collect vector targets
|
||||
for (int y=0; y<bitmapShape.getHeight(); y++) {
|
||||
for (int x=0; x<bitmapShape.getWidth(); x++) {
|
||||
if (bitmapShape.get(x,y)) {
|
||||
points.add(new Point(x,y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorShape get() {
|
||||
Point point = new Point(bitmapShape.origin);
|
||||
boolean plotFirst = false;
|
||||
while (!points.isEmpty()) {
|
||||
Point target = null;
|
||||
double distance = Double.MAX_VALUE;
|
||||
for (Point candidate : points) {
|
||||
double candidateDistance = point.distance(candidate);
|
||||
if (distance >= candidateDistance) {
|
||||
distance = candidateDistance;
|
||||
target = candidate;
|
||||
}
|
||||
}
|
||||
moveTo(plotFirst, point, target);
|
||||
points.remove(target);
|
||||
point = target;
|
||||
plotFirst = true;
|
||||
}
|
||||
// Need to plot that last pixel
|
||||
vshape.plotUp();
|
||||
return vshape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move from origin to target. General strategy is to work from distance between points and bias the one that "loses"
|
||||
* each time while resetting the one that "won". Hopefully this draws more of a jagged line than something entirely
|
||||
* straight. (The goal being to prevent a bunch of plot up's in the worst case that doesn't encode nicely.)
|
||||
* @param plotFirst Indicates if the first vector needs to plot; otherwise all other vectors will be moves
|
||||
* @param origin Is the point of origin (which can be a plotted pixel)
|
||||
* @param target Is the target point
|
||||
*/
|
||||
public void moveTo(boolean plotFirst, Point origin, Point target) {
|
||||
if (origin.equals(target)) return;
|
||||
VectorCommand xvector = origin.x < target.x ? VectorCommand.MOVE_RIGHT : VectorCommand.MOVE_LEFT;
|
||||
VectorCommand yvector = origin.y < target.y ? VectorCommand.MOVE_DOWN : VectorCommand.MOVE_UP;
|
||||
if (plotFirst) {
|
||||
xvector = xvector.plot();
|
||||
yvector = yvector.plot();
|
||||
}
|
||||
Point point = new Point(origin);
|
||||
int xdist = Math.abs(point.x - target.x);
|
||||
int ydist = Math.abs(point.y - target.y);
|
||||
while (!point.equals(target)) {
|
||||
if (xdist > ydist) {
|
||||
xdist = Math.abs(point.x - target.x);
|
||||
ydist += 1;
|
||||
if (point.x != target.x) {
|
||||
point.translate(xvector.xmove, xvector.ymove);
|
||||
vshape.append(xvector);
|
||||
xvector = xvector.move();
|
||||
yvector = yvector.move();
|
||||
}
|
||||
} else {
|
||||
xdist += 1;
|
||||
ydist = Math.abs(point.y - target.y);
|
||||
if (point.y != target.y) {
|
||||
point.translate(yvector.xmove, yvector.ymove);
|
||||
vshape.append(yvector);
|
||||
xvector = xvector.move();
|
||||
yvector = yvector.move();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,4 +43,10 @@ public enum VectorCommand {
|
||||
int newDirection = this.ordinal() ^ 0b010;
|
||||
return VectorCommand.values()[newDirection];
|
||||
}
|
||||
public VectorCommand plot() {
|
||||
return VectorCommand.values()[this.ordinal() | 0b100];
|
||||
}
|
||||
public VectorCommand move() {
|
||||
return VectorCommand.values()[this.ordinal() & 0b011];
|
||||
}
|
||||
}
|
@ -42,16 +42,16 @@ public class VectorShape implements Shape {
|
||||
|
||||
public final List<VectorCommand> vectors = new ArrayList<>();
|
||||
|
||||
public VectorShape moveUp() { return add(VectorCommand.MOVE_UP); }
|
||||
public VectorShape moveRight() { return add(VectorCommand.MOVE_RIGHT); }
|
||||
public VectorShape moveDown() { return add(VectorCommand.MOVE_DOWN); }
|
||||
public VectorShape moveLeft() { return add(VectorCommand.MOVE_LEFT); }
|
||||
public VectorShape plotUp() { return add(VectorCommand.PLOT_UP); }
|
||||
public VectorShape plotRight() { return add(VectorCommand.PLOT_RIGHT); }
|
||||
public VectorShape plotDown() { return add(VectorCommand.PLOT_DOWN); }
|
||||
public VectorShape plotLeft() { return add(VectorCommand.PLOT_LEFT); }
|
||||
public VectorShape moveUp() { return append(VectorCommand.MOVE_UP); }
|
||||
public VectorShape moveRight() { return append(VectorCommand.MOVE_RIGHT); }
|
||||
public VectorShape moveDown() { return append(VectorCommand.MOVE_DOWN); }
|
||||
public VectorShape moveLeft() { return append(VectorCommand.MOVE_LEFT); }
|
||||
public VectorShape plotUp() { return append(VectorCommand.PLOT_UP); }
|
||||
public VectorShape plotRight() { return append(VectorCommand.PLOT_RIGHT); }
|
||||
public VectorShape plotDown() { return append(VectorCommand.PLOT_DOWN); }
|
||||
public VectorShape plotLeft() { return append(VectorCommand.PLOT_LEFT); }
|
||||
|
||||
private VectorShape add(VectorCommand vectorCommand) {
|
||||
public VectorShape append(VectorCommand vectorCommand) {
|
||||
this.vectors.add(vectorCommand);
|
||||
return this;
|
||||
}
|
||||
|
@ -42,12 +42,7 @@ public class ShapeGeneratorTest {
|
||||
+ "|.XXX.|\n"
|
||||
+ "+-----+\n";
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().asciiTextBorder().build();
|
||||
exp.export(st.shapes.get(0), outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
|
||||
assertEquals(expected, actual);
|
||||
assertShapeMatches(expected, st.shapes.get(0));
|
||||
}
|
||||
|
||||
public void assertShapeBoxVectors(ShapeTable st) {
|
||||
@ -66,4 +61,41 @@ public class ShapeGeneratorTest {
|
||||
assertNotNull(s);
|
||||
assertEquals(expected.vectors, s.toVector().vectors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMouseShape() throws IOException {
|
||||
final String mouse = "+--------------+\n"
|
||||
+ "|..........*X..|\n"
|
||||
+ "|....XXXX.XX...|\n"
|
||||
+ "|...XXXXXXXX...|\n"
|
||||
+ "|.XXXXXXXXXXX..|\n"
|
||||
+ "|XX.XXXXXXX.XX.|\n"
|
||||
+ "|X...XXXXXXXXXX|\n"
|
||||
+ "|XX............|\n"
|
||||
+ "|.XXX.XX.......|\n"
|
||||
+ "|...XXX........|\n"
|
||||
+ "+--------------+\n";
|
||||
|
||||
ShapeTable st = ShapeGenerator.generate(getClass().getResourceAsStream("/mouse-bitmap.st"));
|
||||
assertNotNull(st);
|
||||
assertEquals(1, st.shapes.size());
|
||||
|
||||
// Verify we read the shape correctly...
|
||||
Shape shape = st.shapes.get(0);
|
||||
assertNotNull(shape);
|
||||
assertShapeMatches(mouse, shape);
|
||||
|
||||
// Run vector transform to be certain we're ok
|
||||
Shape vectorShape = shape.toVector();
|
||||
assertNotNull(vectorShape);
|
||||
assertShapeMatches(mouse, vectorShape);
|
||||
}
|
||||
|
||||
public void assertShapeMatches(final String expected, Shape shape) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().asciiTextBorder().build();
|
||||
exp.export(shape, outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -91,19 +91,6 @@ public class ShapesTest {
|
||||
|
||||
VectorShape vectorShape = bitmapShape.toVector();
|
||||
BitmapShape newBitmapShape = vectorShape.toBitmap();
|
||||
|
||||
ShapeExporter exp = ShapeExporter.text().asciiTextBorder().build();
|
||||
System.out.println("Original/expected:");
|
||||
exp.export(bitmapShape, System.out);
|
||||
System.out.println("Transformed/actual:");
|
||||
exp.export(newBitmapShape, System.out);
|
||||
System.out.println("Transformed vectors:");
|
||||
System.out.println(vectorShape.vectors);
|
||||
System.out.println(vectorShape.vectors.size());
|
||||
System.out.println("Optimized vectors:");
|
||||
System.out.println(vectorShape.optimize().vectors);
|
||||
System.out.println(vectorShape.optimize().vectors.size());
|
||||
exp.export(vectorShape.optimize().toBitmap(), System.out);
|
||||
|
||||
assertEquals(bitmapShape.grid, newBitmapShape.grid);
|
||||
}
|
||||
|
13
api/src/test/resources/mouse-bitmap.st
Normal file
13
api/src/test/resources/mouse-bitmap.st
Normal file
@ -0,0 +1,13 @@
|
||||
; From Mouse Maze (ca. 1983)
|
||||
; See https://github.com/a2geek/mouse-maze-2001/tree/master/doc/original
|
||||
|
||||
.bitmap
|
||||
..........*X..
|
||||
....XXXX.XX...
|
||||
...XXXXXXXX...
|
||||
.XXXXXXXXXXX..
|
||||
XX.XXXXXXX.XX.
|
||||
X...XXXXXXXXXX
|
||||
XX............
|
||||
.XXX.XX.......
|
||||
...XXX........
|
Loading…
x
Reference in New Issue
Block a user