Early copy of bitmap -> vector transformation. #16.

This commit is contained in:
Rob Greene 2018-06-21 20:55:04 -05:00
parent 208eeb8b51
commit 834e708356
5 changed files with 241 additions and 10 deletions

View File

@ -96,6 +96,9 @@ public class BitmapShape implements Shape {
}
return grid.get(y).get(x);
}
public Boolean get(Point point) {
return get(point.x, point.y);
}
@Override
public boolean isEmpty() {
@ -115,7 +118,112 @@ public class BitmapShape implements Shape {
@Override
public VectorShape toVector() {
// TODO Auto-generated method stub
return null;
VectorShape vshape = new VectorShape();
int width = getWidth();
int height = getHeight();
Point pt = new Point(origin);
// Relocate to 0,0
while (pt.y > 0) {
vshape.moveUp();
pt.y -= 1;
}
while (pt.x > 0) {
vshape.moveLeft();
pt.x -= 1;
}
VectorMotion motion = new RightVectorMotion(pt, vshape);
while (pt.y >= 0 && pt.y < height) {
while (pt.x >= 0 && pt.x < width) {
if (get(pt)) {
motion.plot();
} else {
motion.move();
}
}
motion = motion.changeDirection();
}
return vshape;
}
// public static class Whatever {
// private VectorCommand movement;
// private VectorCommand nextRow;
// private Point point;
// private BitmapShape bitmapShape;
// private VectorShape vectorShape;
//
// public Whatever(VectorCommand movement, VectorCommand nextRow, Point point, BitmapShape bitmapShape) {
// this.movement = movement;
// this.nextRow = nextRow;
// this.point = point;
// this.bitmapShape = bitmapShape;
// this.vectorShape = new VectorShape();
// }
//
// public VectorShape transform() {
// findStartPosition();
// while (hasMoreRows()) {
// scanRow();
// }
// return vectorShape;
// }
// }
public interface VectorMotion {
public void move();
public void plot();
public VectorMotion changeDirection();
}
public static class RightVectorMotion implements VectorMotion {
private Point point;
private VectorShape vshape;
public RightVectorMotion(Point point, VectorShape vshape) {
this.point = point;
this.vshape = vshape;
}
@Override
public void move() {
point.x += 1;
vshape.moveRight();
}
@Override
public void plot() {
point.x += 1;
vshape.plotRight();
}
@Override
public VectorMotion changeDirection() {
point.x -= 1;
point.y += 1;
vshape.moveDown();
vshape.moveLeft();
return new LeftVectorMotion(point, vshape);
}
}
public static class LeftVectorMotion implements VectorMotion {
private Point point;
private VectorShape vshape;
public LeftVectorMotion(Point point, VectorShape vshape) {
this.point = point;
this.vshape = vshape;
}
@Override
public void move() {
point.x -= 1;
vshape.moveLeft();
}
@Override
public void plot() {
point.x -= 1;
vshape.plotLeft();
}
@Override
public VectorMotion changeDirection() {
point.x += 1;
point.y += 1;
vshape.moveDown();
vshape.moveRight();
return new RightVectorMotion(point, vshape);
}
}
}

View File

@ -29,4 +29,14 @@ public enum VectorCommand {
this.ymove = (this.ordinal() & 0b011) - 1;
}
}
public VectorCommand opposite() {
int newDirection = this.ordinal() ^ 0b010;
return VectorCommand.values()[newDirection];
}
public char shortCommand() {
char shortCommand = "urdl".charAt(this.ordinal() & 0b011);
return plot ? Character.toUpperCase(shortCommand) : shortCommand;
}
}

View File

@ -9,6 +9,9 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class VectorShape implements Shape {
public static VectorShape from(ByteBuffer buf) {
@ -53,6 +56,76 @@ public class VectorShape implements Shape {
return this;
}
/**
* Optimize the vectors by removing useless vectors or replacing a series with a shorter series.
* At this point, everything is based off of a regex with a potential modification.
*/
public VectorShape optimize() {
String commands = toShortCommands();
Function<String,String> opts =
// Unused moves (left followed by a right with no plotting in between, for instance).
VectorRegexOptimization.of("l([ud]*)r")
.andThen(VectorRegexOptimization.of("r([ud]*)l"))
.andThen(VectorRegexOptimization.of("u([rl]*)d"))
.andThen(VectorRegexOptimization.of("d([rl]*)u"))
// These are plot/move combinations, such as LEFT>up>right that can be replaced by just UP.
.andThen(VectorRegexOptimization.of("L([ud])r", String::toUpperCase))
.andThen(VectorRegexOptimization.of("R([ud])l", String::toUpperCase))
.andThen(VectorRegexOptimization.of("U([rl])d", String::toUpperCase))
.andThen(VectorRegexOptimization.of("D([rl])u", String::toUpperCase))
// Base assumption is that any tail moves can be removed as they don't lead to a plot.
.andThen(VectorRegexOptimization.of("()[udlr]+$"));
String oldCommands = null;
do {
oldCommands = commands;
commands = opts.apply(commands);
} while (!oldCommands.equals(commands));
VectorShape newShape = new VectorShape();
newShape.appendShortCommands(commands);
return newShape;
}
/**
* A vector optimization based on regex. Transformation is optional to (for instance) change a {@code move}
* to a {@code plot} command. Note that the regex requires a matcher group; also be aware that an empty group
* "{@code ()}" is a viable solution.
*/
public static class VectorRegexOptimization implements Function<String,String> {
public static Function<String,String> of(String regex, Function<String,String> transformation) {
VectorRegexOptimization opt = new VectorRegexOptimization();
opt.pattern = Pattern.compile(regex);
opt.fn = transformation;
return opt;
}
public static Function<String,String> of(String regex) {
return of(regex, (s) -> s);
}
private Pattern pattern;
private Function<String,String> fn;
private VectorRegexOptimization() { /* Prevent construction */ }
@Override
public String apply(String shortCommands) {
Matcher matcher = pattern.matcher(shortCommands);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, fn.apply(matcher.group(1)));
}
matcher.appendTail(sb);
return sb.toString();
}
}
public String toShortCommands() {
StringBuilder sb = new StringBuilder();
vectors.stream().map(VectorCommand::shortCommand).forEach(sb::append);
return sb.toString();
}
public void appendShortCommands(String line) {
for (char cmd : line.trim().toCharArray()) {
switch (cmd) {

View File

@ -17,7 +17,7 @@ public class ShapesTest {
/**
* These shape vectors are taken from the Applesoft BASIC Programmer's Reference Manual (1987), p146.
*/
public static VectorShape drawStandardBoxShape() {
public VectorShape drawStandardBoxShape() {
return new VectorShape()
.moveDown().moveDown()
.plotLeft().plotLeft()
@ -27,6 +27,17 @@ public class ShapesTest {
.moveLeft().plotLeft();
}
public BitmapShape plotStandardBoxShape() {
BitmapShape boxShape = new BitmapShape(5, 5);
for (int i=1; i<=3; i++) {
boxShape.plot(i, 0);
boxShape.plot(i, 4);
boxShape.plot(0, i);
boxShape.plot(4, i);
}
boxShape.origin.setLocation(2, 2);
return boxShape;
}
public ShapeTable readStandardShapeTable() {
ShapeTable st = ShapeTable.read(BOX_SAMPLE);
@ -68,18 +79,35 @@ public class ShapesTest {
public void testStandardShapeTableBitmap() {
ShapeTable st = readStandardShapeTable();
BitmapShape expected = new BitmapShape(5, 5);
for (int i=1; i<=3; i++) {
expected.plot(i, 0);
expected.plot(i, 4);
expected.plot(0, i);
expected.plot(4, i);
}
BitmapShape expected = plotStandardBoxShape();
Shape s = st.shapes.get(0);
assertEquals(expected.grid, s.toBitmap().grid);
}
@Test
public void testToVectorFromBitmap() throws IOException {
BitmapShape bitmapShape = plotStandardBoxShape();
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);
}
@Test
public void testTextShapeExporterNoBorder() throws IOException {
ShapeTable st = readStandardShapeTable();

View File

@ -29,4 +29,16 @@ public class VectorCommandTest {
assertEquals(plot, command.plot);
}
}
@Test
public void testOpposite() {
test(VectorCommand.MOVE_DOWN, VectorCommand.MOVE_UP);
test(VectorCommand.MOVE_LEFT, VectorCommand.MOVE_RIGHT);
test(VectorCommand.PLOT_DOWN, VectorCommand.PLOT_UP);
test(VectorCommand.PLOT_LEFT, VectorCommand.PLOT_RIGHT);
}
public void test(VectorCommand a, VectorCommand b) {
assertEquals(a, b.opposite());
assertEquals(b, a.opposite());
}
}