From 13a6270723684c01c4cc571cbdc1b58e9c96f1c3 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Fri, 22 Jun 2018 17:04:52 -0500 Subject: [PATCH] Expanded sweep optimization. #16. --- .../bastools/api/shapes/BitmapShape.java | 213 ++++++++++-------- .../bastools/api/shapes/VectorCommand.java | 14 +- .../bastools/api/shapes/VectorShape.java | 36 +-- 3 files changed, 146 insertions(+), 117 deletions(-) diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/BitmapShape.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/BitmapShape.java index cdaf2d5..93eb0c6 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/BitmapShape.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/BitmapShape.java @@ -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> 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> 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 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 { + 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); + } } } } diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java index f0d91a1..fa462a2 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorCommand.java @@ -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; - } } \ No newline at end of file diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorShape.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorShape.java index dcd751c..03ce97b 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorShape.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/VectorShape.java @@ -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 tokens = new LinkedList<>(Arrays.asList(line.split("\\s+")));