mirror of
https://github.com/AppleCommander/bastools.git
synced 2025-04-07 20:37:10 +00:00
Expanded sweep optimization. #16.
This commit is contained in:
parent
834e708356
commit
13a6270723
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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+")));
|
||||
|
Loading…
x
Reference in New Issue
Block a user