Expanded sweep optimization. #16.

This commit is contained in:
Rob Greene 2018-06-22 17:04:52 -05:00
parent 834e708356
commit 13a6270723
3 changed files with 146 additions and 117 deletions

View File

@ -2,8 +2,16 @@ package io.github.applecommander.bastools.api.shapes;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
/**
* Represents a bitmap copy of the shape.
* This may be useful for displaying the shape or for defining shapes as a bitmap is
* easier to understand than vectors.
*/
public class BitmapShape implements Shape {
public final List<List<Boolean>> grid = new ArrayList<>();
public final Point origin = new Point();
@ -116,114 +124,129 @@ public class BitmapShape implements Shape {
return this;
}
/**
* Convert this bitmap shape to a vector shape. The shape chosen encodes to the least number of bytes
* in the resulting file.
*/
@Override
public VectorShape toVector() {
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();
}
List<Supplier<VectorShape>> scans = Arrays.asList(
new SweepVectorization(this, VectorCommand.MOVE_RIGHT, VectorCommand.MOVE_UP),
new SweepVectorization(this, VectorCommand.MOVE_RIGHT, VectorCommand.MOVE_DOWN),
new SweepVectorization(this, VectorCommand.MOVE_LEFT, VectorCommand.MOVE_UP),
new SweepVectorization(this, VectorCommand.MOVE_LEFT, VectorCommand.MOVE_DOWN),
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)
);
int byteLength = Integer.MAX_VALUE;
VectorShape vshape = null;
for (Supplier<VectorShape> scan : scans) {
VectorShape candidate = scan.get();
int length = candidate.toBytes().length;
if (vshape == null || byteLength >= length) {
vshape = candidate;
byteLength = length;
}
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 {
/**
* Encode a bitmap shape by going to a corner and sweeping back-and-forth across the image.
* The resulting shape is not optimal, so the {@link VectorShape#optimize()} should be used.
* Note that this class is setup to be dynamic in the chosen corner.
*/
public static class SweepVectorization implements Supplier<VectorShape> {
private VectorCommand[] toOrigin;
private VectorCommand movement;
private VectorCommand next;
private Point point;
private VectorShape vshape;
public RightVectorMotion(Point point, VectorShape vshape) {
this.point = point;
this.vshape = vshape;
private BitmapShape bitmapShape;
private VectorShape vectorShape;
private int width;
private int height;
/**
* Create an instance of the sweep method.
*
* @param bitmapShape is the shape to be converted
* @param initialMovement is the initial sweep movement
* @param next is the direction to advance for each line
*/
public SweepVectorization(BitmapShape bitmapShape, VectorCommand initialMovement, VectorCommand next) {
Objects.requireNonNull(bitmapShape);
Objects.requireNonNull(initialMovement);
Objects.requireNonNull(next);
if (initialMovement.horizontal == next.horizontal || initialMovement.vertical == next.vertical) {
throw new IllegalArgumentException("One vector must be horizontal and the other vector must be vertical");
}
this.toOrigin = new VectorCommand[] { next.opposite(), initialMovement.opposite() };
this.movement = initialMovement;
this.next = next;
this.bitmapShape = bitmapShape;
this.width = bitmapShape.getWidth();
this.height = bitmapShape.getHeight();
this.point = new Point(bitmapShape.origin);
this.vectorShape = new VectorShape();
}
@Override
public void move() {
point.x += 1;
vshape.moveRight();
public VectorShape get() {
findStartPosition();
while (!onOrAtEdge(next)) {
scanRow();
plotOrMove(next);
movement = movement.opposite();
point.translate(next.xmove, next.ymove);
}
return vectorShape;
}
@Override
public void plot() {
point.x += 1;
vshape.plotRight();
public void findStartPosition() {
for (VectorCommand vector : toOrigin) {
while (!onOrAtEdge(vector)) {
vectorShape.vectors.add(vector);
point.translate(vector.xmove, vector.ymove);
}
}
}
@Override
public VectorMotion changeDirection() {
point.x -= 1;
point.y += 1;
vshape.moveDown();
vshape.moveLeft();
return new LeftVectorMotion(point, vshape);
public void scanRow() {
while (!onOrAtEdge(movement)) {
plotOrMove(movement);
point.translate(movement.xmove, movement.ymove);
}
}
}
public static class LeftVectorMotion implements VectorMotion {
private Point point;
private VectorShape vshape;
public LeftVectorMotion(Point point, VectorShape vshape) {
this.point = point;
this.vshape = vshape;
public void plotOrMove(VectorCommand vector) {
if (bitmapShape.get(point)) {
vectorShape.appendShortCommand(Character.toUpperCase(vector.shortCommand));
} else {
vectorShape.appendShortCommand(Character.toLowerCase(vector.shortCommand));
}
}
@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);
public boolean onOrAtEdge(VectorCommand vector) {
// No clever way to do this?
switch (vector) {
case MOVE_DOWN:
case PLOT_DOWN:
return point.y >= height;
case MOVE_UP:
case PLOT_UP:
return point.y < 0;
case MOVE_LEFT:
case PLOT_LEFT:
return point.x < 0;
case MOVE_RIGHT:
case PLOT_RIGHT:
return point.x >= width;
default:
throw new RuntimeException("Unexpected vector: " + vector);
}
}
}
}

View File

@ -14,6 +14,10 @@ public enum VectorCommand {
public final boolean plot;
public final int xmove;
public final int ymove;
public final char shortCommand;
public final boolean vertical;
public final boolean horizontal;
private VectorCommand() {
this.plot = (this.ordinal() & 0b100) != 0;
@ -28,15 +32,15 @@ public enum VectorCommand {
this.xmove = 0;
this.ymove = (this.ordinal() & 0b011) - 1;
}
this.vertical = xmove == 0;
this.horizontal = ymove == 0;
char shortCommand = "urdl".charAt(this.ordinal() & 0b011);
this.shortCommand = plot ? Character.toUpperCase(shortCommand) : shortCommand;
}
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

@ -122,30 +122,32 @@ public class VectorShape implements Shape {
public String toShortCommands() {
StringBuilder sb = new StringBuilder();
vectors.stream().map(VectorCommand::shortCommand).forEach(sb::append);
vectors.stream().map(v -> v.shortCommand).forEach(sb::append);
return sb.toString();
}
public void appendShortCommands(String line) {
for (char cmd : line.trim().toCharArray()) {
switch (cmd) {
case 'u': moveUp(); break;
case 'd': moveDown(); break;
case 'l': moveLeft(); break;
case 'r': moveRight(); break;
case 'U': plotUp(); break;
case 'D': plotDown(); break;
case 'L': plotLeft(); break;
case 'R': plotRight(); break;
default:
if (Character.isWhitespace(cmd)) {
// whitespace is allowed
continue;
}
throw new RuntimeException("Unknown command: " + cmd);
}
appendShortCommand(cmd);
}
}
public void appendShortCommand(char cmd) {
switch (cmd) {
case 'u': moveUp(); break;
case 'd': moveDown(); break;
case 'l': moveLeft(); break;
case 'r': moveRight(); break;
case 'U': plotUp(); break;
case 'D': plotDown(); break;
case 'L': plotLeft(); break;
case 'R': plotRight(); break;
default:
// whitespace is allowed
if (!Character.isWhitespace(cmd)) {
throw new RuntimeException("Unknown command: " + cmd);
}
}
}
public void appendLongCommands(String line) {
Queue<String> tokens = new LinkedList<>(Arrays.asList(line.split("\\s+")));