diff --git a/src/com/t_oster/liblasercut/BlackWhiteRaster.java b/src/com/t_oster/liblasercut/BlackWhiteRaster.java index e4368516c35ed9e6be839b3516ca247635e77fa0..2293629392e5ad78e5445dd9a225751217ad16a6 100644 --- a/src/com/t_oster/liblasercut/BlackWhiteRaster.java +++ b/src/com/t_oster/liblasercut/BlackWhiteRaster.java @@ -24,7 +24,7 @@ import com.t_oster.liblasercut.dithering.*; * * @author Thomas Oster <thomas.oster@rwth-aachen.de> */ -public class BlackWhiteRaster extends TimeIntensiveOperation +public class BlackWhiteRaster extends TimeIntensiveOperation implements GreyscaleRaster { public static enum DitherAlgorithm @@ -129,6 +129,22 @@ public class BlackWhiteRaster extends TimeIntensiveOperation { return raster[x][y]; } + + /** + * Convenience function to pretend this B&W image is greyscale + * @param x + * @param y + * @return 0 for black, 255 for white + */ + public int getGreyScale(int x, int y) + { + return isBlack(x, y) ? 0 : 255; + } + + public void setGreyScale(int x, int y, int color) + { + this.setBlack(x, y, color < 128); + } public int getWidth() { diff --git a/src/com/t_oster/liblasercut/LaserCutter.java b/src/com/t_oster/liblasercut/LaserCutter.java index 791d23ee84a6ad8e2af28619c68112c686f8dec7..5c5b1a87bb2a849b7eb8dc754c47f7105ef6a84d 100644 --- a/src/com/t_oster/liblasercut/LaserCutter.java +++ b/src/com/t_oster/liblasercut/LaserCutter.java @@ -181,100 +181,35 @@ public abstract class LaserCutter implements Cloneable, Customizable { public abstract String getModelName(); - protected VectorPart convertRasterToVectorPart(RasterPart rp, LaserProperty blackPixelProperty, LaserProperty whitePixelProperty, double resolution, boolean unidirectional) + /** + * Converts a raster image (B&W or greyscale) into a series of vector + * instructions suitable for printing. Lets non-raster-native cutters + * emulate this functionality using gcode. + * @param rp the raster job to convert + * @param resolution resolution to output job at + * @param bidirectional cut in both directions + * @return a VectorPart job of VectorCommands + */ + protected VectorPart convertRasterizableToVectorPart(RasterizableJobPart rp, double resolution, boolean bidirectional) { - boolean dirRight = true; - Point rasterStart = rp.getRasterStart(); - VectorPart result = new VectorPart(blackPixelProperty, resolution); - for (int line = 0; line < rp.getRasterHeight(); line++) + VectorPart result = new VectorPart(rp.getLaserProperty(), resolution); + for (int y = 0; y < rp.getRasterHeight(); y++) { - Point lineStart = rasterStart.clone(); - lineStart.y += line; - //Convert BlackWhite line into line of 0 and 255 bytes - BlackWhiteRaster bwr = rp.image; - List<Byte> bytes = new LinkedList<Byte>(); - boolean lookForStart = true; - for (int x = 0; x < bwr.getWidth(); x++) - { - if (lookForStart) - { - if (bwr.isBlack(x, line)) - { - lookForStart = false; - bytes.add((byte) 255); - } - else - { - lineStart.x += 1; - } - } - else - { - bytes.add(bwr.isBlack(x, line) ? (byte) 255 : (byte) 0); - } - } - //remove trailing zeroes - while (bytes.size() > 0 && bytes.get(bytes.size() - 1) == 0) - { - bytes.remove(bytes.size() - 1); - } - if (bytes.size() > 0) + if (rp.lineIsBlank(y) == false) { - if (dirRight) + Point lineStart = rp.getStartPosition(y); + + //move to the first point of the line + result.moveto(lineStart.x + rp.firstNonWhitePixel(y)+rp.cutCompensation(), lineStart.y); + + for (int x = rp.firstNonWhitePixel(y); !rp.hasFinishedCuttingLine(x, y);) { - //move to the first nonempyt point of the line - result.moveto(lineStart.x, lineStart.y); - byte old = bytes.get(0); - for (int pix = 0; pix < bytes.size(); pix++) - { - if (bytes.get(pix) != old) - { - if (old == 0) - {//beginning of "black" segment -> move with 0 power - result.setProperty(whitePixelProperty); - result.lineto(lineStart.x + pix, lineStart.y); - } - else - {//end of "black" segment -> move with power to pixel before - result.setProperty(blackPixelProperty); - result.lineto(lineStart.x + pix - 1, lineStart.y); - } - old = bytes.get(pix); - } - } - result.setProperty(blackPixelProperty); - result.lineto(lineStart.x + bytes.size() - 1, lineStart.y); + result.setProperty(rp.getPowerSpeedFocusPropertyForPixel(x, y)); + x = rp.nextColorChange(x, y); + result.lineto(lineStart.x + x + rp.cutCompensation(), lineStart.y); } - else - { - //move to the last nonempty point of the line - result.moveto(lineStart.x + bytes.size() - 1, lineStart.y); - byte old = bytes.get(bytes.size() - 1); - for (int pix = bytes.size() - 1; pix >= 0; pix--) - { - if (bytes.get(pix) != old || pix == 0) - { - if (old == 0) - { - result.setProperty(whitePixelProperty); - result.lineto(lineStart.x + pix, lineStart.y); - } - else - { - result.setProperty(blackPixelProperty); - result.lineto(lineStart.x + pix + 1, lineStart.y); - } - old = bytes.get(pix); - } - } - //last pixel is always black (white pixels are stripped before) - result.setProperty(blackPixelProperty); - result.lineto(lineStart.x, lineStart.y); - } - } - if (!unidirectional) - { - dirRight = !dirRight; + + if (bidirectional) rp.toggleRasteringCutDirection(); } } return result; diff --git a/src/com/t_oster/liblasercut/Raster3dPart.java b/src/com/t_oster/liblasercut/Raster3dPart.java index 08b69f2fd0412d31942464012d6a8fce4b73c14d..371ce605e57e5357d1bd70eca53a4b3df5be027c 100644 --- a/src/com/t_oster/liblasercut/Raster3dPart.java +++ b/src/com/t_oster/liblasercut/Raster3dPart.java @@ -26,12 +26,10 @@ import java.util.List; * * @author Thomas Oster <thomas.oster@rwth-aachen.de> */ -public class Raster3dPart extends JobPart +public class Raster3dPart extends RasterizableJobPart { - private GreyscaleRaster image = null; private LaserProperty property = null; - protected Point start = null; private double resolution = 500; public Raster3dPart(GreyscaleRaster image, LaserProperty laserProperty, Point offset, double resolution) @@ -127,4 +125,34 @@ public class Raster3dPart extends JobPart } return result; } + + @Override + public FloatPowerSpeedFocusProperty getPowerSpeedFocusPropertyForColor(int color) + { + FloatPowerSpeedFocusProperty power = (FloatPowerSpeedFocusProperty) getLaserProperty().clone(); + // convert 0-255 into <max power>-0. i.e.... + // - 0 (black) -> 100% + // - 127 (mid) -> 50% + // - 255 (white) -> 0% + + // y = mx + c + // x = color + // y = power + // + // x = 0 -> y = <max power> + // x = 255 -> y = 0 + // + // x = 0 -> y = <max> -> y = m*0 + c -> c = <max> + float c = (float) power.getPower(); + + // x = 255 -> y = 0 -> y = m*255 + <max> -> 0 = m*255 + <max> + // -> -<max> = m*255 -> -<max>/255 = m + float m = -c / 255f; + + float x = (float) color; + float y = m*x + c; + + power.setPower((int) y); + return power; + } } diff --git a/src/com/t_oster/liblasercut/RasterPart.java b/src/com/t_oster/liblasercut/RasterPart.java index 6b591520fec9654fea87f1cb49a7c7e7ad69c20e..bb68105d7f4264d0392a467340f3ab6e400ce334 100644 --- a/src/com/t_oster/liblasercut/RasterPart.java +++ b/src/com/t_oster/liblasercut/RasterPart.java @@ -26,20 +26,21 @@ import java.util.List; * * @author Thomas Oster <thomas.oster@rwth-aachen.de> */ -public class RasterPart extends JobPart +public class RasterPart extends RasterizableJobPart { - BlackWhiteRaster image = null; - LaserProperty property = null; - Point start = null; + LaserProperty blackPixelProperty = null; + LaserProperty whitePixelProperty = null; double resolution = 500; public RasterPart(BlackWhiteRaster image, LaserProperty laserProperty, Point offset, double resolution) { this.image = image; - this.property = laserProperty; this.start = offset; this.resolution = resolution; + this.blackPixelProperty = laserProperty; + this.whitePixelProperty = blackPixelProperty.clone(); + whitePixelProperty.setProperty("power", 0.0f); } @Override @@ -86,7 +87,6 @@ public class RasterPart extends JobPart * Returns one line of the given rasterpart * every byte represents 8 pixel and the value corresponds to * 1 when black or 0 when white - * @param raster * @param line * @return */ @@ -95,29 +95,28 @@ public class RasterPart extends JobPart List<Byte> result = new LinkedList<Byte>(); for (int x = 0; x < (image.getWidth() + 7) / 8; x++) { - result.add(image.getByte(x, line)); + result.add(((BlackWhiteRaster) image).getByte(x, line)); } return result; } public boolean isBlack(int x, int y) { - return this.image.isBlack(x, y); + return ((BlackWhiteRaster) image).isBlack(x, y); } - public int getRasterWidth() - { - return this.image.getWidth(); - } - - public int getRasterHeight() + @Override + public LaserProperty getLaserProperty() { - return this.image.getHeight(); + return this.blackPixelProperty; } - - public LaserProperty getLaserProperty() + + @Override + public FloatPowerSpeedFocusProperty getPowerSpeedFocusPropertyForColor(int color) { - return this.property; + return color <= 127 + ? (FloatPowerSpeedFocusProperty) blackPixelProperty + : (FloatPowerSpeedFocusProperty) whitePixelProperty; } } diff --git a/src/com/t_oster/liblasercut/RasterizableJobPart.java b/src/com/t_oster/liblasercut/RasterizableJobPart.java new file mode 100644 index 0000000000000000000000000000000000000000..75df4482f84e23b68148e43b75a90f138afbfead --- /dev/null +++ b/src/com/t_oster/liblasercut/RasterizableJobPart.java @@ -0,0 +1,235 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut; + +import com.t_oster.liblasercut.platform.Point; + +/** + * Common functions useful when rasterizing an image. + * @author Michael Adams <zap@michaeladams.org> + */ +abstract public class RasterizableJobPart extends JobPart +{ + protected GreyscaleRaster image; + protected Point start = null; + protected boolean cutDirectionleftToRight = true; + + /** + * The initial laser settings to start a rasterization job with. + * @return LaserProperty + */ + public abstract LaserProperty getLaserProperty(); + + /** + * Gets the height of the associated raster image + * @return height in pixels + */ + public int getRasterHeight() { + return this.image.getHeight(); + } + + /** + * Gets the width of the associated raster image + * @return width in pixels + */ + public int getRasterWidth() { + return this.image.getWidth(); + } + + /** + * Determines whether an entire line in an image is blank; i.e. can it be skipped? + * @param y + * @return true if the line is blank + */ + public boolean lineIsBlank(int y) + { + for (int x=0; x<getRasterWidth(); x++) + if (image.getGreyScale(x, y) < 255) + return false; + return true; + } + + /** + * Toggle the direction cutting is done in. Left to right by default; when changed + * then "start" of the line is the right-most side, and "end" is the left-most + * side. + */ + public void toggleRasteringCutDirection() + { + cutDirectionleftToRight = !cutDirectionleftToRight; + } + + + /** + * Adds any required compensation when cutting. + * Fixes off-by-one errors when cutting in reverse direction, since + * the pixel finding methods here always refer to the bottom left corner of + * a pixel, but when cutting in reverse direction, we need to take pixel width + * into account. + * @return amount to adjust coordinates by + */ + public int cutCompensation() + { + return cutDirectionleftToRight ? 0 : 1; + } + + /** + * Given an coordinate, and knowing the direction we are cutting in, decide + * if we have finished cutting a row of the image. + * @param x x coordinate of last pixel cut + * @param y y coordinate of last pixel cut + * @return true if we have finished cutting a line + */ + public boolean hasFinishedCuttingLine(int x, int y) + { + return cutDirectionleftToRight + ? (x > rightMostNonWhitePixel(y)) + : (x < leftMostNonWhitePixel(y)); + } + + /** + * Finds the x coordinate of the first pixel that needs lasering + * @param y + * @return x coordinate to start lasering from + */ + public int firstNonWhitePixel(int y) + { + return cutDirectionleftToRight + ? leftMostNonWhitePixel(y) + : rightMostNonWhitePixel(y); + } + + /** + * Finds the x coordinate for the left most pixel, since "start" depends on + * what direction you are cutting in. + * @param y + * @return x coordinate of left most non-white pixel + */ + protected int leftMostNonWhitePixel(int y) + { + for (int x=0; x<getRasterWidth(); x++) + if (image.getGreyScale(x, y) < 255) + return x; + return getRasterWidth(); + } + + /** + * Finds the end of the line; points after this pixel are all blank + * @param y + * @return x coordinate to end lasering at + */ + public int lastNonWhitePixel(int y) + { + return cutDirectionleftToRight + ? rightMostNonWhitePixel(y) + : leftMostNonWhitePixel(y); + } + + /** + * Finds the x coordinate for the right most pixel, since "end" depends on + * what direction you are cutting in. + * @param y + * @return x coordinate of right most non-white pixel + */ + protected int rightMostNonWhitePixel(int y) + { + for (int x=getRasterWidth()-1; x >= 0; x--) + if (image.getGreyScale(x, y) < 255) + return x; + return 0; + } + + /** + * Given a pixel in a row of an image, finds the next pixel that has a different + * color. If no more color changes take place, returns the last interesting pixel. + * @param x x coordinate to start scanning from + * @param y y coordinate to start scanning from + * @return x coordinate of the next different color in this row + */ + public int nextColorChange(int x, int y) + { + return cutDirectionleftToRight + ? nextColorChangeHeadingRight(x, y) + : nextColorChangeHeadingLeft(x, y); + } + + /** + * nextColorChange logic when heading -> + * @param x x coordinate to start scanning from + * @param y y coordinate to start scanning from + * @return x coordinate of the next different color in this row + */ + protected int nextColorChangeHeadingRight(int x, int y) + { + int color = image.getGreyScale(x, y); + for (int i=x; i<getRasterWidth(); i++) + if (image.getGreyScale(i, y) != color) + return i; + // rest of line is the same color, so next colour change is past end of line + return getRasterWidth(); + } + + /** + * nextColorChange logic when heading <- + * @param x x coordinate to start scanning from + * @param y y coordinate to start scanning from + * @return x coordinate of the next different color in this row + */ + protected int nextColorChangeHeadingLeft(int x, int y) + { + int color = image.getGreyScale(x, y); + for (int i=x; i>=0; i--) + if (image.getGreyScale(i, y) != color) + return i; + // rest of line is the same color, so next colour change is past the beginning of line + return -1; + } + + /** + * Returns the start position of the first column (x=0) for a given line + * @param y y coordinate of the row in question + * @return Point representing start of this row + */ + public Point getStartPosition(int y) + { + Point start = this.start.clone(); + start.y += y; + return start; + } + + /** + * Calculate power/speed/focus required to laser a given pixel + * @param x x coordinate of pixel + * @param y y coordinate of pixel + * @return laser property appropriate for the color at this pixel + */ + public FloatPowerSpeedFocusProperty getPowerSpeedFocusPropertyForPixel(int x, int y) + { + return getPowerSpeedFocusPropertyForColor(image.getGreyScale(x, y)); + } + + /** + * Returns a power/speed/focus property appropriate for a given color. + * 255 = white = 0% laser. + * 0 = black = 100% laser. + * @param color 0-255 value representing the color. 0 = black and 255 = white. + * @return laser property appropriate for this color + */ + public abstract FloatPowerSpeedFocusProperty getPowerSpeedFocusPropertyForColor(int color); +} diff --git a/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java index 0c0f1c8f7794f344bd9aa41ade3fe849fc3aea15..9bd596afca2253474eb1eb355a40455b049cc3a1 100644 --- a/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java +++ b/src/com/t_oster/liblasercut/drivers/GenericGcodeDriver.java @@ -68,6 +68,7 @@ public class GenericGcodeDriver extends LaserCutter { protected static final String SETTING_SERIAL_TIMEOUT = "Milliseconds to wait for response"; protected static final String SETTING_BLANK_LASER_DURING_RAPIDS = "Force laser off during G0 moves"; protected static final String SETTING_FILE_EXPORT_PATH = "Path to save exported gcode"; + protected static final String SETTING_USE_BIDIRECTIONAL_RASTERING = "Use bidirectional rastering"; protected static Locale FORMAT_LOCALE = Locale.US; @@ -322,6 +323,22 @@ public class GenericGcodeDriver extends LaserCutter { this.blankLaserDuringRapids = blankLaserDuringRapids; } + /** + * When rastering, whether to always cut from left to right, or to cut in both + * directions? (i.e. use the return stroke to raster as well) + */ + protected boolean useBidirectionalRastering = false; + + public boolean getUseBidirectionalRastering() + { + return useBidirectionalRastering; + } + + public void setUseBidirectionalRastering(boolean useBidirectionalRastering) + { + this.useBidirectionalRastering = useBidirectionalRastering; + } + @Override /** * We do not support Frequency atm, so we return power,speed and focus @@ -721,13 +738,9 @@ public class GenericGcodeDriver extends LaserCutter { int max = job.getParts().size(); for (JobPart p : job.getParts()) { - if (p instanceof RasterPart) + if (p instanceof Raster3dPart || p instanceof RasterPart) { - RasterPart rp = (RasterPart) p; - LaserProperty black = rp.getLaserProperty(); - LaserProperty white = black.clone(); - white.setProperty("power", 0.0f); - p = convertRasterToVectorPart((RasterPart) p, black, white, p.getDPI(), false); + p = convertRasterizableToVectorPart((RasterizableJobPart) p, p.getDPI(), getUseBidirectionalRastering()); } if (p instanceof VectorPart) { @@ -765,13 +778,9 @@ public void saveJob(java.io.PrintStream fileOutputStream, LaserJob job) throws I writeInitializationCode(); for (JobPart p : job.getParts()) { - if (p instanceof RasterPart) + if (p instanceof Raster3dPart || p instanceof RasterPart) { - RasterPart rp = (RasterPart) p; - LaserProperty black = rp.getLaserProperty(); - LaserProperty white = black.clone(); - white.setProperty("power", 0.0f); - p = convertRasterToVectorPart((RasterPart) p, black, white, p.getDPI(), false); + p = convertRasterizableToVectorPart((RasterizableJobPart) p, p.getDPI(), getUseBidirectionalRastering()); } if (p instanceof VectorPart) { @@ -859,7 +868,8 @@ public void saveJob(java.io.PrintStream fileOutputStream, LaserJob job) throws I SETTING_RESOLUTIONS, SETTING_WAIT_FOR_OK, SETTING_SERIAL_TIMEOUT, - SETTING_FILE_EXPORT_PATH + SETTING_FILE_EXPORT_PATH, + SETTING_USE_BIDIRECTIONAL_RASTERING }; @Override @@ -913,6 +923,8 @@ public void saveJob(java.io.PrintStream fileOutputStream, LaserJob job) throws I return this.getBlankLaserDuringRapids(); } else if (SETTING_FILE_EXPORT_PATH.equals(attribute)) { return this.getExportPath(); + } else if (SETTING_USE_BIDIRECTIONAL_RASTERING.equals(attribute)) { + return this.getUseBidirectionalRastering(); } return null; @@ -964,6 +976,8 @@ public void saveJob(java.io.PrintStream fileOutputStream, LaserJob job) throws I this.setBlankLaserDuringRapids((Boolean) value); } else if (SETTING_FILE_EXPORT_PATH.equals(attribute)) { this.setExportPath((String) value); + } else if (SETTING_USE_BIDIRECTIONAL_RASTERING.equals(attribute)) { + this.setUseBidirectionalRastering((Boolean) value); } } diff --git a/test/com/t_oster/liblasercut/RasterizableJobPartTest.java b/test/com/t_oster/liblasercut/RasterizableJobPartTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2bb416564929c22e69742fe59361bc9e13cb5490 --- /dev/null +++ b/test/com/t_oster/liblasercut/RasterizableJobPartTest.java @@ -0,0 +1,566 @@ +/** + * This file is part of LibLaserCut. + * Copyright (C) 2011 - 2014 Thomas Oster <mail@thomas-oster.de> + * + * LibLaserCut is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibLaserCut is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibLaserCut. If not, see <http://www.gnu.org/licenses/>. + * + **/ +package com.t_oster.liblasercut; + +import com.t_oster.liblasercut.platform.Point; +import com.t_oster.liblasercut.utils.BufferedImageAdapter; +import java.awt.image.BufferedImage; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Test class for RasterizableJobPart + * Michael Adams <zap@michaeladams.org> + */ +public class RasterizableJobPartTest +{ + /** + * Test of getLaserProperty method, of class RasterizableJobPart. + */ + @Test + public void testGetLaserProperty() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + FloatPowerSpeedFocusProperty expResult = new FloatPowerSpeedFocusProperty(); + expResult.setPower(100.0f); + expResult.setSpeed(100.0f); + expResult.setFocus(0.0f); + LaserProperty result = instance.getLaserProperty(); + assertEquals(expResult, result); + } + + /** + * Test of getRasterHeight method, of class RasterizableJobPart. + */ + @Test + public void testGetRasterHeight() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + int expResult = 6; + int result = instance.getRasterHeight(); + assertEquals(expResult, result); + } + + /** + * Test of getRasterWidth method, of class RasterizableJobPart. + */ + @Test + public void testGetRasterWidth() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + int expResult = 9; + int result = instance.getRasterWidth(); + assertEquals(expResult, result); + } + + /** + * Test of lineIsBlank method, of class RasterizableJobPart. + */ + @Test + public void testLineIsBlank() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + + assertEquals(false, instance.lineIsBlank(0)); + assertEquals(false, instance.lineIsBlank(1)); + assertEquals(false, instance.lineIsBlank(2)); + assertEquals(false, instance.lineIsBlank(3)); + assertEquals(true, instance.lineIsBlank(4)); + } + + /** + * Test of setRasteringCutDirection method, of class RasterizableJobPart. + */ + @Test + public void testSetRasteringCutDirection() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + instance.setRasteringCutDirectionLeftToRight(); + assertEquals("left to right", ((RasterizableJobPartImpl) instance).getRasteringCutDirection()); + + instance.toggleRasteringCutDirection(); + assertEquals("right to left", ((RasterizableJobPartImpl) instance).getRasteringCutDirection()); + } + + /** + * Test of cutCompensation method, of class RasterizableJobPart. + */ + @Test + public void testCutCompensation() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + instance.setRasteringCutDirectionLeftToRight(); + assertEquals(0, instance.cutCompensation()); + + instance.toggleRasteringCutDirection(); + assertEquals(1, instance.cutCompensation()); + } + + /** + * Test of hasFinishedCuttingLine method, of class RasterizableJobPart. + */ + @Test + public void testHasFinishedCuttingLine() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + // cut -> --XX-XXX- + instance.setRasteringCutDirectionLeftToRight(); + // pixels 0-7 are part of the job + for (int i=0; i<=7; i++) assertEquals(false, instance.hasFinishedCuttingLine(i, 0)); + // once you get to pixel 8, the line is cut + assertEquals(true, instance.hasFinishedCuttingLine(8, 0)); + + // cut <- ---X--XX- + instance.toggleRasteringCutDirection(); + // coords 8-3 are part of the job + for (int i=8; i>=3; i--) assertEquals(false, instance.hasFinishedCuttingLine(i, 1)); + // once you get to pixels 2-0 (heading left), the line is cut + for (int i=2; i>=0; i--) assertEquals(true, instance.hasFinishedCuttingLine(i, 1)); + } + + /** + * Test of firstNonWhitePixel method, of class RasterizableJobPart. + */ + @Test + public void testFirstNonWhitePixel() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + // cut -> --XX-XXX- + instance.setRasteringCutDirectionLeftToRight(); + assertEquals(2, instance.firstNonWhitePixel(0)); + + // cut <- ---X--XX- + instance.toggleRasteringCutDirection(); + assertEquals(7, instance.firstNonWhitePixel(1)); + + // cut -> 303333333 + instance.toggleRasteringCutDirection(); + assertEquals(0, instance.firstNonWhitePixel(2)); + + // cut <- 007777777 + instance.toggleRasteringCutDirection(); + assertEquals(8, instance.firstNonWhitePixel(3)); + } + + /** + * Test of leftMostNonWhitePixel method, of class RasterizableJobPart. + */ + @Test + public void testLeftMostNonWhitePixel() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + + // --XX-XXX- + assertEquals(2, instance.leftMostNonWhitePixel(0)); + + // ---X--XX- + assertEquals(3, instance.leftMostNonWhitePixel(1)); + + // 303333333 + assertEquals(0, instance.leftMostNonWhitePixel(2)); + + // 007777777 + assertEquals(2, instance.leftMostNonWhitePixel(3)); + } + + /** + * Test of lastNonWhitePixel method, of class RasterizableJobPart. + */ + @Test + public void testLastNonWhitePixel() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + // cut -> --XX-XXX- + instance.setRasteringCutDirectionLeftToRight(); + assertEquals(7, instance.lastNonWhitePixel(0)); + + // cut <- ---X--XX- + instance.toggleRasteringCutDirection(); + assertEquals(3, instance.lastNonWhitePixel(1)); + + // cut -> 303333333 + instance.toggleRasteringCutDirection(); + assertEquals(8, instance.lastNonWhitePixel(2)); + + // cut <- 007777777 + instance.toggleRasteringCutDirection(); + assertEquals(2, instance.lastNonWhitePixel(3)); + } + + /** + * Test of rightMostNonWhitePixel method, of class RasterizableJobPart. + */ + @Test + public void testRightMostNonWhitePixel() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + + // --XX-XXX- + assertEquals(7, instance.rightMostNonWhitePixel(0)); + + // ---X--XX- + assertEquals(7, instance.rightMostNonWhitePixel(1)); + + // 303333333 + assertEquals(8, instance.rightMostNonWhitePixel(2)); + + // 007777777 + assertEquals(8, instance.rightMostNonWhitePixel(3)); + } + + /** + * Test of nextColorChange method, of class RasterizableJobPart. + */ + @Test + public void testNextColorChange() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + // cut -> --XX-XXX- + instance.setRasteringCutDirectionLeftToRight(); + assertEquals(2, instance.nextColorChange(0, 0)); + assertEquals(2, instance.nextColorChange(1, 0)); + assertEquals(4, instance.nextColorChange(2, 0)); + assertEquals(4, instance.nextColorChange(3, 0)); + assertEquals(5, instance.nextColorChange(4, 0)); + assertEquals(8, instance.nextColorChange(5, 0)); + + // cut <- ---X--XX- + instance.toggleRasteringCutDirection(); + assertEquals(7, instance.nextColorChange(8, 1)); + assertEquals(5, instance.nextColorChange(7, 1)); + assertEquals(5, instance.nextColorChange(6, 1)); + assertEquals(3, instance.nextColorChange(5, 1)); + assertEquals(3, instance.nextColorChange(4, 1)); + assertEquals(2, instance.nextColorChange(3, 1)); + } + + /** + * Test of nextColorChangeHeadingRight method, of class RasterizableJobPart. + */ + @Test + public void testNextColorChangeHeadingRight() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + + // --XX-XXX- + assertEquals(2, instance.nextColorChangeHeadingRight(0, 0)); + assertEquals(2, instance.nextColorChangeHeadingRight(1, 0)); + assertEquals(4, instance.nextColorChangeHeadingRight(2, 0)); + assertEquals(4, instance.nextColorChangeHeadingRight(3, 0)); + assertEquals(5, instance.nextColorChangeHeadingRight(4, 0)); + assertEquals(8, instance.nextColorChangeHeadingRight(5, 0)); + + // ---X--XX- + assertEquals(3, instance.nextColorChangeHeadingRight(0, 1)); + assertEquals(3, instance.nextColorChangeHeadingRight(1, 1)); + assertEquals(3, instance.nextColorChangeHeadingRight(2, 1)); + assertEquals(4, instance.nextColorChangeHeadingRight(3, 1)); + assertEquals(6, instance.nextColorChangeHeadingRight(4, 1)); + assertEquals(6, instance.nextColorChangeHeadingRight(5, 1)); + assertEquals(8, instance.nextColorChangeHeadingRight(6, 1)); + assertEquals(8, instance.nextColorChangeHeadingRight(7, 1)); + + // 3-3333333 + assertEquals(9, instance.nextColorChangeHeadingRight(7, 2)); + } + + /** + * Test of nextColorChangeHeadingLeft method, of class RasterizableJobPart. + */ + @Test + public void testNextColorChangeHeadingLeft() + { + RasterizableJobPart instance = new RasterizableJobPartImpl(); + + // --XX-XXX- + assertEquals(7, instance.nextColorChangeHeadingLeft(8, 0)); + assertEquals(4, instance.nextColorChangeHeadingLeft(7, 0)); + assertEquals(4, instance.nextColorChangeHeadingLeft(6, 0)); + assertEquals(4, instance.nextColorChangeHeadingLeft(5, 0)); + assertEquals(3, instance.nextColorChangeHeadingLeft(4, 0)); + assertEquals(1, instance.nextColorChangeHeadingLeft(3, 0)); + assertEquals(1, instance.nextColorChangeHeadingLeft(2, 0)); + + // ---X--XX- + assertEquals(7, instance.nextColorChangeHeadingLeft(8, 1)); + assertEquals(5, instance.nextColorChangeHeadingLeft(7, 1)); + assertEquals(5, instance.nextColorChangeHeadingLeft(6, 1)); + assertEquals(3, instance.nextColorChangeHeadingLeft(5, 1)); + assertEquals(3, instance.nextColorChangeHeadingLeft(4, 1)); + assertEquals(2, instance.nextColorChangeHeadingLeft(3, 1)); + assertEquals(-1, instance.nextColorChangeHeadingLeft(2, 1)); + assertEquals(-1, instance.nextColorChangeHeadingLeft(1, 1)); + + // 3-3333333 + assertEquals(1, instance.nextColorChangeHeadingLeft(2, 2)); + assertEquals(0, instance.nextColorChangeHeadingLeft(1, 2)); + assertEquals(-1, instance.nextColorChangeHeadingLeft(0, 2)); + } + + /** + * Test of getStartPosition method, of class RasterizableJobPart. + */ + @Test + public void testGetStartPosition() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + instance.setRasteringCutDirectionLeftToRight(); + assertEquals(new Point(0, 0), instance.getStartPosition(0)); + + instance.toggleRasteringCutDirection(); + assertEquals(new Point(0, 1), instance.getStartPosition(1)); + } + + /** + * Test of getPowerSpeedFocusPropertyForPixel method, of class RasterizableJobPart. + */ + @Test + public void testGetPowerSpeedFocusPropertyForPixel() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + Raster3dPart raster = instance.toRaster3dPart(); + + // --XX-XXX- + assertEquals(propertyForPower(0), raster.getPowerSpeedFocusPropertyForPixel(0, 0)); + assertEquals(propertyForPower(0), raster.getPowerSpeedFocusPropertyForPixel(1, 0)); + assertEquals(propertyForPower(100), raster.getPowerSpeedFocusPropertyForPixel(2, 0)); + + // 303333333 - 3 = quite dark = 75% power + assertEquals(propertyForPower(75), raster.getPowerSpeedFocusPropertyForPixel(0, 2)); + assertEquals(propertyForPower(0), raster.getPowerSpeedFocusPropertyForPixel(1, 2)); + assertEquals(propertyForPower(75), raster.getPowerSpeedFocusPropertyForPixel(2, 2)); + } + + private FloatPowerSpeedFocusProperty propertyForPower(int power) + { + FloatPowerSpeedFocusProperty prop = new FloatPowerSpeedFocusProperty(); + prop.setFocus(0.0f); + prop.setSpeed(100.0f); + prop.setPower(power); + return prop; + } + + /** + * Test of getPowerSpeedFocusPropertyForColor method, of class RasterizableJobPart. + */ + @Test + public void testGetPowerSpeedFocusPropertyForColor() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + Raster3dPart raster = instance.toRaster3dPart(); + + assertEquals(propertyForPower(100), raster.getPowerSpeedFocusPropertyForColor(0)); + assertEquals(propertyForPower(50), raster.getPowerSpeedFocusPropertyForColor(127)); + assertEquals(propertyForPower(0), raster.getPowerSpeedFocusPropertyForColor(255)); + } + + /** + * Test the tricky case of a pixel on the very right edge + */ + @Test + public void testPixelOnRightHandEdge() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + instance.setRasteringCutDirectionLeftToRight(); + int y = 2; + int x = 8; + // 3-3333333 + x = instance.nextColorChange(x, y); + assertEquals(9, x); + + boolean done = instance.hasFinishedCuttingLine(x, y); + assertEquals(true, done); + } + + /** + * Test the tricky case of a pixel on the very left edge + */ + @Test + public void testPixelOnLeftHandEdge() + { + RasterizableJobPartImpl instance = new RasterizableJobPartImpl(); + + instance.setRasteringCutDirectionRightToLeft(); + int y = 5; + int x = 1; + // FF0000000 + x = instance.nextColorChange(x, y); + assertEquals(-1, x); + + boolean done = instance.hasFinishedCuttingLine(x, y); + assertEquals(true, done); + } + + + + public class RasterizableJobPartImpl extends RasterizableJobPart + { + public RasterizableJobPartImpl() + { + // set image data + BufferedImage imageData = new BufferedImage(9, 6, BufferedImage.TYPE_BYTE_GRAY); + BufferedImageAdapter image = new BufferedImageAdapter(imageData); + + // key... + // 255 = white = don't laser = - + // 0 = black = laser lots! = X + + // fist row (B&W) => --XX-XXX- + image.setGreyScale(0, 0, 255); + image.setGreyScale(1, 0, 255); + image.setGreyScale(2, 0, 0); + image.setGreyScale(3, 0, 0); + image.setGreyScale(4, 0, 255); + image.setGreyScale(5, 0, 0); + image.setGreyScale(6, 0, 0); + image.setGreyScale(7, 0, 0); + image.setGreyScale(8, 0, 255); + // second row (B&W) => ---X--XX- + image.setGreyScale(0, 1, 255); + image.setGreyScale(1, 1, 255); + image.setGreyScale(2, 1, 255); + image.setGreyScale(3, 1, 0); + image.setGreyScale(4, 1, 255); + image.setGreyScale(5, 1, 255); + image.setGreyScale(6, 1, 0); + image.setGreyScale(7, 1, 0); + image.setGreyScale(8, 1, 255); + // third row (greyscale) => 3-3333333 + image.setGreyScale(0, 2, 63); + image.setGreyScale(1, 2, 255); + image.setGreyScale(2, 2, 63); + image.setGreyScale(3, 2, 63); + image.setGreyScale(4, 2, 63); + image.setGreyScale(5, 2, 63); + image.setGreyScale(6, 2, 63); + image.setGreyScale(7, 2, 63); + image.setGreyScale(8, 2, 63); + // fourth row (greyscale) => --7777777 + image.setGreyScale(0, 3, 255); + image.setGreyScale(1, 3, 255); + image.setGreyScale(2, 3, 127); + image.setGreyScale(3, 3, 127); + image.setGreyScale(4, 3, 127); + image.setGreyScale(5, 3, 127); + image.setGreyScale(6, 3, 127); + image.setGreyScale(7, 3, 127); + image.setGreyScale(8, 3, 127); + // fifth row (blank) => 000000000 + image.setGreyScale(0, 4, 255); + image.setGreyScale(1, 4, 255); + image.setGreyScale(2, 4, 255); + image.setGreyScale(3, 4, 255); + image.setGreyScale(4, 4, 255); + image.setGreyScale(5, 4, 255); + image.setGreyScale(6, 4, 255); + image.setGreyScale(7, 4, 255); + image.setGreyScale(8, 4, 255); + // sixth row => FF0000000 + image.setGreyScale(0, 5, 0); + image.setGreyScale(1, 5, 0); + image.setGreyScale(2, 5, 255); + image.setGreyScale(3, 5, 255); + image.setGreyScale(4, 5, 255); + image.setGreyScale(5, 5, 255); + image.setGreyScale(6, 5, 255); + image.setGreyScale(7, 5, 255); + image.setGreyScale(8, 5, 255); + // set image data + this.image = image; + + // set start point + this.start = new Point(0, 0); + } + + public Raster3dPart toRaster3dPart() + { + return new Raster3dPart((GreyscaleRaster) this.image, this.getLaserProperty(), this.start, this.getDPI()); + } + + public String getRasteringCutDirection() + { + return cutDirectionleftToRight + ? "left to right" + : "right to left"; + } + + public void setRasteringCutDirectionLeftToRight() { + cutDirectionleftToRight = true; + } + + public void setRasteringCutDirectionRightToLeft() { + cutDirectionleftToRight = false; + } + + @Override + public int getMaxY() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getMaxX() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getMinY() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getMinX() + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public LaserProperty getLaserProperty() + { + FloatPowerSpeedFocusProperty prop = new FloatPowerSpeedFocusProperty(); + prop.setPower(100.0f); + prop.setSpeed(100.0f); + prop.setFocus(0.0f); + return prop; + } + + @Override + public FloatPowerSpeedFocusProperty getPowerSpeedFocusPropertyForColor(int color) + { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public double getDPI() + { + return 72.0; + } + } + +}