Printing JTable with printable class just print last page - java

I want to print a table with Java but my class that implements the Printable class print one page and when my table is big, break it into two pages and just prints the last page. I won't print one page if there is more pages print all of them.
this is my TablePrintable Class:
class TablePrintable implements Printable {
/** The table to print. */
private JTable table;
/** For quick reference to the table's header. */
private JTableHeader header;
/** For quick reference to the table's column model. */
private TableColumnModel colModel;
/** To save multiple calculations of total column width. */
private int totalColWidth;
/** The printing mode of this printable. */
private JTable.PrintMode printMode;
/** Provides the header text for the table. */
private MessageFormat headerFormat;
/** Provides the footer text for the table. */
private MessageFormat footerFormat;
/** The most recent page index asked to print. */
private int last = -1;
/** The next row to print. */
private int row = 0;
/** The next column to print. */
private int col = 0;
/** Used to store an area of the table to be printed. */
private final Rectangle clip = new Rectangle(0, 0, 0, 0);
/** Used to store an area of the table's header to be printed. */
private final Rectangle hclip = new Rectangle(0, 0, 0, 0);
/** Saves the creation of multiple rectangles. */
private final Rectangle tempRect = new Rectangle(0, 0, 0, 0);
/** Vertical space to leave between table and header/footer text. */
private static final int H_F_SPACE = 15;
/** Font size for the header text. */
private static final float HEADER_FONT_SIZE = 14.0f;
/** Font size for the footer text. */
private static final float FOOTER_FONT_SIZE = 10.0f;
/** The font to use in rendering header text. */
private Font headerFont;
/** The font to use in rendering footer text. */
private Font footerFont;
/**
* Create a new <code>TablePrintable</code> for the given
* <code>JTable</code>. Header and footer text can be specified using the
* two <code>MessageFormat</code> parameters. When called upon to provide a
* String, each format is given the current page number.
*
* #param table
* the table to print
* #param printMode
* the printing mode for this printable
* #param headerFormat
* a <code>MessageFormat</code> specifying the text to be used in
* printing a header, or null for none
* #param footerFormat
* a <code>MessageFormat</code> specifying the text to be used in
* printing a footer, or null for none
* #throws IllegalArgumentException
* if passed an invalid print mode
*/
public TablePrintable(JTable table, JTable.PrintMode printMode,
MessageFormat headerFormat,
MessageFormat footerFormat) {
this.table = table;
header = table.getTableHeader();
colModel = table.getColumnModel();
totalColWidth = colModel.getTotalColumnWidth();
if (header != null) {
// the header clip height can be set once since it's unchanging
hclip.height = header.getHeight();
}
this.printMode = printMode;
this.headerFormat = headerFormat;
this.footerFormat = footerFormat;
// derive the header and footer font from the table's font
headerFont = table.getFont().deriveFont(Font.BOLD, HEADER_FONT_SIZE);
footerFont = table.getFont().deriveFont(Font.PLAIN, FOOTER_FONT_SIZE);
}
/**
* Prints the specified page of the table into the given {#link Graphics}
* context, in the specified format.
*
* #param graphics
* the context into which the page is drawn
* #param pageFormat
* the size and orientation of the page being drawn
* #param pageIndex
* the zero based index of the page to be drawn
* #return PAGE_EXISTS if the page is rendered successfully, or NO_SUCH_PAGE
* if a non-existent page index is specified
* #throws PrinterException
* if an error causes printing to be aborted
*/
#Override
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
throws PrinterException {
// for easy access to these values
final int imgWidth = (int) pageFormat.getImageableWidth();
final int imgHeight = (int) pageFormat.getImageableHeight();
if (imgWidth <= 0) {
throw new PrinterException("Width of printable area is too small.");
}
if((table.getRowCount() * table.getRowHeight()) > imgHeight){
pageIndex++;
}
// to pass the page number when formatting the header and footer text
Object[] pageNumber = new Object[] { new Integer(pageIndex + 1) };
// fetch the formatted header text, if any
String headerText = null;
if (headerFormat != null) {
headerText = headerFormat.format(pageNumber);
}
// fetch the formatted footer text, if any
String footerText = null;
if (footerFormat != null) {
footerText = footerFormat.format(pageNumber);
}
// to store the bounds of the header and footer text
Rectangle2D hRect = null;
Rectangle2D fRect = null;
// the amount of vertical space needed for the header and footer text
int headerTextSpace = 0;
int footerTextSpace = 0;
// the amount of vertical space available for printing the table
int availableSpace = imgHeight;
System.out.println("available space: " + availableSpace);
System.out.println("the avail space // row Height => " +
(availableSpace/table.getRowHeight()));
// if there's header text, find out how much space is needed for it
// and subtract that from the available space
if (headerText != null) {
graphics.setFont(headerFont);
hRect = graphics.getFontMetrics().getStringBounds(headerText,
graphics);
headerTextSpace = (int) Math.ceil(hRect.getHeight());
availableSpace -= headerTextSpace + H_F_SPACE;
}
// if there's footer text, find out how much space is needed for it
// and subtract that from the available space
if (footerText != null) {
graphics.setFont(footerFont);
fRect = graphics.getFontMetrics().getStringBounds(footerText,
graphics);
footerTextSpace = (int) Math.ceil(fRect.getHeight());
availableSpace -= footerTextSpace;// TODO
}
if (availableSpace <= 0) {
throw new PrinterException("Height of printable area is too
small.");
}
// depending on the print mode, we may need a scale factor to
// fit the table's entire width on the page
double sf = 1.0D;
if (printMode == JTable.PrintMode.FIT_WIDTH && totalColWidth > imgWidth)
{
// if not, we would have thrown an acception previously
assert imgWidth > 0;
// it must be, according to the if-condition, since imgWidth > 0
assert totalColWidth > 1;
sf = (double) imgWidth / (double) totalColWidth;
}
// dictated by the previous two assertions
assert sf > 0;
// This is in a loop for two reasons:
// First, it allows us to catch up in case we're called starting
// with a non-zero pageIndex. Second, we know that we can be called
// for the same page multiple times. The condition of this while
// loop acts as a check, ensuring that we don't attempt to do the
// calculations again when we are called subsequent times for the
// same page.
while (last < pageIndex) {
// if we are finished all columns in all rows
if (row >= table.getRowCount() && col == 0) {
return NO_SUCH_PAGE;
}
// rather than multiplying every row and column by the scale factor
// in findNextClip, just pass a width and height that have already
// been divided by it
int scaledWidth = (int) (imgWidth / sf);
int scaledHeight = (int) ((availableSpace - hclip.height) / sf);
// calculate the area of the table to be printed for this page
findNextClip(scaledWidth, scaledHeight);
last++;
}
// translate into the co-ordinate system of the pageFormat
Graphics2D g2d = (Graphics2D) graphics;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// to save and store the transform
AffineTransform oldTrans;
// if there's footer text, print it at the bottom of the imageable area
if (footerText != null) {
oldTrans = g2d.getTransform();
g2d.translate(0, imgHeight - footerTextSpace);
printFooterText(g2d, footerText, fRect, footerFont, imgWidth);
g2d.setTransform(oldTrans);
}
// if there's header text, print it at the top of the imageable area
// and then translate downwards
if (headerText != null) {
printHeaderText(g2d, headerText, hRect, headerFont, imgWidth);
g2d.translate(0, headerTextSpace + H_F_SPACE);
}
// constrain the table output to the available space
tempRect.x = 0;
tempRect.y = 0;
tempRect.width = imgWidth;
tempRect.height = availableSpace;
g2d.clip(tempRect);
// if we have a scale factor, scale the graphics object to fit
// the entire width
if (sf != 1.0D) {
g2d.scale(sf, sf);
// otherwise, ensure that the current portion of the table is
// centered horizontally
} else {
int diff = (imgWidth - clip.width) / 2;
g2d.translate(diff, 0);
}
// store the old transform and clip for later restoration
oldTrans = g2d.getTransform();
Shape oldClip = g2d.getClip();
// if there's a table header, print the current section and
// then translate downwards
if (header != null) {
hclip.x = clip.x;
hclip.width = clip.width;
g2d.translate(-hclip.x, 0);
g2d.clip(hclip);
header.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// translate downwards
g2d.translate(0, hclip.height);
}
// print the current section of the table
g2d.translate(-clip.x, -clip.y);
g2d.clip(clip);
table.print(g2d);
// restore the original transform and clip
g2d.setTransform(oldTrans);
g2d.setClip(oldClip);
// draw a box around the table
g2d.setColor(Color.BLACK);
g2d.drawRect(0, 0, clip.width, hclip.height + clip.height);
return PAGE_EXISTS;
}
/**
* A helper method that encapsulates common code for rendering the header
* and footer text.
*
* #param g2d
* the graphics to draw into
* #param text
* the text to draw, non null
* #param rect
* the bounding rectangle for this text, as calculated at the
* given font, non null
* #param font
* the font to draw the text in, non null
* #param imgWidth
* the width of the area to draw into
*/
private void printHeaderText(Graphics2D g2d, String text, Rectangle2D rect,
Font font, int imgWidth) {
int tx;// TODO
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int) ((imgWidth - rect.getWidth()) / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int) (Math.ceil(rect.getWidth()) - imgWidth);
}
int ty = (int) Math.ceil(Math.abs(rect.getY()));
g2d.setColor(Color.BLACK);
g2d.setFont(MainUi.fLiner);
g2d.drawString(text, tx, ty);
}
private void printFooterText(Graphics2D g2d, String text, Rectangle2D rect,
Font font, int imgWidth) {
int tx;// TODO
// if the text is small enough to fit, center it
if (rect.getWidth() < imgWidth) {
tx = (int) ((imgWidth - rect.getWidth()) / 2);
// otherwise, if the table is LTR, ensure the left side of
// the text shows; the right can be clipped
} else if (table.getComponentOrientation().isLeftToRight()) {
tx = 0;
// otherwise, ensure the right side of the text shows
} else {
tx = -(int) (Math.ceil(rect.getWidth()) - imgWidth);
}
// int ty = (int) Math.ceil(Math.abs(rect.getY() + 30));
int ty = (int) Math.ceil(rect.getHeight());
g2d.setColor(Color.BLACK);
g2d.setFont(MainUi.ffLiner);
g2d.drawString(text, tx, ty-3);
}
/**
* Calculate the area of the table to be printed for the next page. This
* should only be called if there are rows and columns left to print.
*
* To avoid an infinite loop in printing, this will always put at least one
* cell on each page.
*
* #param pw
* the width of the area to print in
* #param ph
* the height of the area to print in
*/
private void findNextClip(int pw, int ph) {
final boolean ltr = table.getComponentOrientation().isLeftToRight();
// if we're ready to start a new set of rows
if (col == 0) {
if (ltr) {
// adjust clip to the left of the first column
clip.x = 0;
} else {
// adjust clip to the right of the first column
clip.x = totalColWidth;
}
// adjust clip to the top of the next set of rows
clip.y += clip.height;
// adjust clip width and height to be zero
clip.width = 0;
clip.height = 0;
// fit as many rows as possible, and at least one
int rowCount = table.getRowCount();
int rowHeight = table.getRowHeight(row);
do {
clip.height += rowHeight;
if (++row >= rowCount) {
break;
}
rowHeight = table.getRowHeight(row);
} while (clip.height + rowHeight <= ph);
}
// we can short-circuit for JTable.PrintMode.FIT_WIDTH since
// we'll always fit all columns on the page
if (printMode == JTable.PrintMode.FIT_WIDTH) {
clip.x = 0;
clip.width = totalColWidth;
return;
}
if (ltr) {
// adjust clip to the left of the next set of columns
clip.x += clip.width;
}
// adjust clip width to be zero
clip.width = 0;
// fit as many columns as possible, and at least one
int colCount = table.getColumnCount();
int colWidth = colModel.getColumn(col).getWidth();
do {
clip.width += colWidth;
if (!ltr) {
clip.x -= colWidth;
}
if (++col >= colCount) {
// reset col to 0 to indicate we're finished all columns
col = 0;
break;
}
colWidth = colModel.getColumn(col).getWidth();
} while (clip.width + colWidth <= pw);
}
}

Related

JAVA: How to add an X coordinate to text section

I want to place the text section CONTROLS to the right instead of it just being listed, I'm a newbie and I'm using a source code from a website that publishes Java game codes. The Y coordinate exists but I can't seem to find the X coordinate which will help me to position the text section. This is the code for my side panel class:
/**
* The number of rows and columns in the preview window. Set to
* 5 because we can show any piece with some sort of padding.
*/
private static final int TILE_COUNT = 5;
/**
* The center x of the next piece preview box.
*/
private static final int SQUARE_CENTER_X = 130;
/**
* The center y of the next piece preview box.
*/
private static final int SQUARE_CENTER_Y = 65;
/**
* The size of the next piece preview box.
*/
private static final int SQUARE_SIZE = (TILE_SIZE * TILE_COUNT >> 1);
/**
* The number of pixels used on a small insets (generally used for categories).
*/
private static final int SMALL_INSET = 20;
/**
* The number of pixels used on a large insets.
*/
private static final int LARGE_INSET = 30;
/**
* The y coordinate of the stats category.
*/
private static final int STATS_INSET = 100;
/**
* The y coordinate of the controls category.
*/
private static final int CONTROLS_INSET = 175;
/**
* The number of pixels to offset between each string.
*/
private static final int TEXT_STRIDE = 25;
/**
* The small font.
*/
private static final Font SMALL_FONT = new Font("Arial", Font.BOLD, 11);
/**
* The large font.
*/
private static final Font LARGE_FONT = new Font("Arial", Font.BOLD, 13);
/**
* The color to draw the text and preview box in.
*/
private static final Color DRAW_COLOR = new Color(128, 192, 128);
/**
* The Tetris instance.
*/
private Tetris tetris;
/**
* Creates a new SidePanel and sets it's display properties.
* #param tetris The Tetris instance to use.
*/
public SidePanel(Tetris tetris) {
this.tetris = tetris;
setPreferredSize(new Dimension(200, BoardPanel.PANEL_HEIGHT));
setBackground(Color.BLACK);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//Set the color for drawing.
g.setColor(Color.WHITE);
/*
* This variable stores the current y coordinate of the string.
* This way we can re-order, add, or remove new strings if necessary
* without needing to change the other strings.
*/
int offset;
/*
* Draw the "Stats" category.
*/
g.setFont(LARGE_FONT);
g.drawString("Stats", SMALL_INSET, offset = STATS_INSET);
g.setFont(SMALL_FONT);
g.drawString("Level: " + tetris.getLevel(), LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("Score: " + tetris.getScore(), LARGE_INSET, offset += TEXT_STRIDE);
/*
* Draw the "Controls" category.
*/
g.setFont(LARGE_FONT);
g.drawString("Controls", SMALL_INSET, offset = CONTROLS_INSET);
g.setFont(SMALL_FONT);
g.drawString(" Left- Move Left", LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("Right - Move Right", LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("Z - Rotate Anticlockwise", LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("X - Rotate Clockwise", LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("Down - Drop", LARGE_INSET, offset += TEXT_STRIDE);
g.drawString("P - Pause Game", LARGE_INSET, offset += TEXT_STRIDE);
/*
* Draw the next piece preview box.
*/
g.setFont(LARGE_FONT);
g.drawString("Next Piece:", SMALL_INSET, 70);
g.drawRect(SQUARE_CENTER_X - SQUARE_SIZE, SQUARE_CENTER_Y - SQUARE_SIZE, SQUARE_SIZE * 2, SQUARE_SIZE * 2);
/*
* Draw a preview of the next piece that will be spawned. The code is pretty much
* identical to the drawing code on the board, just smaller and centered, rather
* than constrained to a grid.
*/
TileType type = tetris.getNextPieceType();
if(!tetris.isGameOver() && type != null) {
/*
* Get the size properties of the current piece.
*/
int cols = type.getCols();
int rows = type.getRows();
int dimension = type.getDimension();
/*
* Calculate the top left corner (origin) of the piece.
*/
int startX = (SQUARE_CENTER_X - (cols * TILE_SIZE / 2));
int startY = (SQUARE_CENTER_Y - (rows * TILE_SIZE / 2));
/*
* Get the insets for the preview. The default
* rotation is used for the preview, so we just use 0.
*/
int top = type.getTopInset(0);
int left = type.getLeftInset(0);
/*
* Loop through the piece and draw it's tiles onto the preview.
*/
for(int row = 0; row < dimension; row++) {
for(int col = 0; col < dimension; col++) {
if(type.isTile(col, row, 0)) {
drawTile(type, startX + ((col - left) * TILE_SIZE), startY + ((row - top) * TILE_SIZE), g);
}
}
}
}
}
/**
* Draws a tile onto the preview window.
* #param type The type of tile to draw.
* #param x The x coordinate of the tile.
* #param y The y coordinate of the tile.
* #param g The graphics object.
*/
private void drawTile(TileType type, int x, int y, Graphics g) {
/*
* Fill the entire tile with the base color.
*/
g.setColor(type.getBaseColor());
g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
/*
* Fill the bottom and right edges of the tile with the dark shading color.
*/
g.setColor(type.getDarkColor());
g.fillRect(x, y + TILE_SIZE - SHADE_WIDTH, TILE_SIZE, SHADE_WIDTH);
g.fillRect(x + TILE_SIZE - SHADE_WIDTH, y, SHADE_WIDTH, TILE_SIZE);
/*
* Fill the top and left edges with the light shading. We draw a single line
* for each row or column rather than a rectangle so that we can draw a nice
* looking diagonal where the light and dark shading meet.
*/
g.setColor(type.getLightColor());
for(int i = 0; i < SHADE_WIDTH; i++) {
g.drawLine(x, y + i, x + TILE_SIZE - i - 1, y + i);
g.drawLine(x + i, y, x + i, y + TILE_SIZE - i - 1);
}
}
}

Apache POI Excel sheet: resize a picture while keeping its ratio

Hello I have created excel sheet by using POI. I've added picture(jpg-file) in the next way:
Workbook wb = new HSSFWorkbook();
CreationHelper helper = wb.getCreationHelper();
//...
InputStream is = new FileInputStream("img.jpg");
byte[] bytes = IOUtils.toByteArray(is);
int picIdx = wb.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
anchor.setCol1(5);
anchor.setRow1(5);
Picture pict = drawing.createPicture(anchor, picIdx);
pict.resize();
Now I want the picture to fit into that cell, but i don't want to change its aspect ratio. the scales i can put into resize are in respect to the cell, which obviously can have a different ratio. I tried to calculate the scales but the problem is, that I can't get or set the row height in pixel, only in pt and i can't compute the cells ratio either bc the i can't get width and height in the same unit... Any suggestions?
There is a Units class in POI, which provide convertion between pixel and point.
And here are some methods might be helpful to set cell's width and height.
1.Centimeters to Pixels
public static int cmToPx(double cm) {
return (int) Math.round(cm * 96 / 2.54D);
}
96 is my Monitor's DPI (check yours from dpilove)
and 1 inch = 2.54 centimeters
2.Centimeters to RowHeight
public static int cmToH(double cm) {
return (int) (Units.pixelToPoints(cmToPx(cm)) * 20); //POI's Units
}
Usage: sheet.setDefaultRowHeight(cmToH(1.0))
Reference: HSSFRow#getHeightInPoints
Set the height in "twips" or 1/20th of a point.
3.Centimeters to ColumnWidth
public static int cmToW(double cm) {
return (int) Math.round(((cmToPx(cm) - 5.0D) / 8 * 7 + 5) / 7 * 256);
}
Usage: sheet.setColumnWidth(cmToW(1.0))
Use (px - 5.0D) / 8 to convert from pixels to points in excel width.(When drag column's width in excel, pixels and points will show around your cursor)
Reference: HSSFSheet#setColumnWidth
Set the width (in units of 1/256th of a character width)
Excel uses the following formula (Section 3.3.1.12 of the OOXML spec):
// Excel width, not character width
width = Truncate([{Number of Visible Characters} * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width} * 256) / 256
Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). If you set a column width to be eight characters wide, e.g. setColumnWidth(columnIndex, 8*256), then the actual value of visible characters (the value shown in Excel) is derived from the following equation:
Truncate([numChars * 7 + 5] / 7 * 256) / 256 = 8;
Use XSSFClientAnchor to resize a picture to fill the cell and keep its ratio:
// set padding between picture and gridlines so gridlines would not covered by the picture
private static final double PADDING_SIZE = 10;
private static final int PADDING = Units.toEMU(PADDING_SIZE);
/**
* Draw Image inside specific cell
*
* #param wb workbook
* #param sheet sheet
* #param cellW cell width in pixels
* #param cellH cell height in pixels
* #param imgPath image path
* #param col the column (0 based) of the first cell.
* #param row the row (0 based) of the first cell.
* #param colSize the column size of cell
* #param rowSize the row size of cell
*/
public static void drawImageInCell(SXSSFWorkbook wb, SXSSFSheet sheet, int cellW, int cellH,
String imgPath, int col, int row, int colSize, int rowSize) throws IOException {
Path path = Paths.get(imgPath);
BufferedImage img = ImageIO.read(path.toFile());
int[] anchorArray = calCellAnchor(Units.pixelToPoints(cellW), Units.pixelToPoints(cellH),
img.getWidth(), img.getHeight());
XSSFClientAnchor anchor = new XSSFClientAnchor(anchorArray[0], anchorArray[1], anchorArray[2],
anchorArray[3], (short) col, row, (short) (col + colSize), row + rowSize);
int index = wb.addPicture(Files.readAllBytes(path), XSSFWorkbook.PICTURE_TYPE_JPEG);
sheet.createDrawingPatriarch().createPicture(anchor, index);
}
/**
* calculate POI cell anchor
*
* #param cellX cell width in excel points
* #param cellY cell height in excel points
* #param imgX image width
* #param imgY image height
*/
public static int[] calCellAnchor(double cellX, double cellY, int imgX, int imgY) {
// assume Y has fixed padding first
return calCoordinate(true, cellX, cellY, imgX, imgY);
}
/**
* calculate cell coordinate
*
* #param fixTop is Y has fixed padding
*/
private static int[] calCoordinate(boolean fixTop, double cellX, double cellY, int imgX, int imgY) {
double ratio = ((double) imgX) / imgY;
int x = (int) Math.round(Units.toEMU(cellY - 2 * PADDING_SIZE) * ratio);
x = (Units.toEMU(cellX) - x) / 2;
if (x < PADDING) {
return calCoordinate(false, cellY, cellX, imgY, imgX);
}
return calDirection(fixTop, x);
}
/**
* calculate X's direction
*
* #param fixTop is Y has fixed padding
* #param x X's padding
*/
private static int[] calDirection(boolean fixTop, int x) {
if (fixTop) {
return new int[] { x, PADDING, -x, -PADDING };
} else {
return new int[] { PADDING, x, -PADDING, -x };
}
}
I am using Base64 image data and I do it like this, for me it adds at given col/row and resizes it properly, also I dont want to to fit the cell it can axpand to whatever to right and bottom:
byte[] imageBase64Data = base64DataString.getBytes();
byte[] imageRawData = Base64.getDecoder().decode(imageBase64Data);
int imgWidth = 1920; // only initial if not known
int imgHeight = 1080; // only initial if not known
try {
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageRawData));
imgWidth = img.getWidth();
imgHeight = img.getHeight();
} catch (IOException e) {
e.printStackTrace();
}
int pictureIdx = workbook.addPicture(imageRawData, Workbook.PICTURE_TYPE_PNG);
CreationHelper helper = workbook.getCreationHelper();
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
anchor.setCol1(desiredCol);
anchor.setRow1(desiredRow);
Picture picture = drawing.createPicture(anchor, pictureIdx);
picture.resize(0.7 * imgWidth / XSSFShape.PIXEL_DPI, 5 * imgHeight / XSSFShape.PIXEL_DPI);

Java SWT : Badge Notifications

I have a desktop based UI application written in Java SWT running on windows.
I want to add a button on the UI screen whose behaviour should be similar to the badges on an iphone or facebook notifications as shown in the images below.
The number on the badge will be dynamic and will increase or decrease based on the number of pending notifications.
How can I implement something similar in SWT/AWT?
IOS Badge:
Facebook Notification:
I've implemented something like that recently. You can simply paint a custom image with GC, and the overlay on your desired icon.
I'm including my helper class here. It's not the cleanest code (a lot of stuff is hardcoded), but you'll get the point. The notification bubble resizes itself depending on the number of notifications (max 999).
How to use (Remember to cache and/or dispose your images!):
Image decoratedIcon = new ImageOverlayer()
.baseImage(baseImage) // You icon/badget
.overlayImage(ImageOverlayer.createNotifImage(5)) // 5 notifications
.overlayImagePosition(OverlayedImagePosition.TOP_RIGHT)
.createImage();
/**
* <pre>
* The difference between this and the ImageBuilder is
* that ImageOverlayer does not chain the images, rather
* just overlays them one onto another.
*
*
* Rules:
*
* 1.) Images are not disposed. Resource handing must be done externally.
* 2.) Only two images allowed, for now.
* 3.) The size of the composite image should normally be the size of the
* base image, BUT: if the overlaying image is larger, then larger
* parameters are grabbed, and the base image is still underneath.
* 4.) Use the builder APIs to set the base and overlaying images. The
* position of the overlaying image is optional, and CENTER by default.
* When you've set these, simply call createImage()
*
* Further improvements:
*
* - Combine this with ImageBuilder. These two composers should be welded.
*
* </pre>
*
* #author grec.georgian#gmail.com
*
*/
public class ImageOverlayer extends CompositeImageDescriptor
{
// ==================== 1. Static Fields ========================
public enum OverlayedImagePosition
{
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER;
}
// ====================== 2. Instance Fields =============================
private ImageData baseImageData;
private ImageData overlayedImageData;
private OverlayedImagePosition overlayedImagePosition = OverlayedImagePosition.CENTER;
// ==================== 3. Static Methods ====================
/**
* Creates a red circle with a white bold number inside it.
* Does not cache the final image.
*/
public static final Image createNotifImage(final int numberOfNotifications)
{
// Initial width and height - hardcoded for now
final int width = 14;
int height = 14;
// Initial font size
int fontSize = 100;
int decorationWidth = width;
String textToDraw = String.valueOf(numberOfNotifications);
final int numberLength = Integer.toString(numberOfNotifications).length();
if(numberLength > 3)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
// - smaller font size so the new text will fit(set to 999+) if we have
// a number of notifications with more than 3 digits
decorationWidth += numberLength * 2;
height -= 4;
fontSize = 80;
textToDraw = "999+"; //$NON-NLS-1$
}
else if (numberLength > 2)
{
// spetrila, 2014.12.17: - set a width that fits the text
// - smaller height since we will have a rounded rectangle and not a circle
decorationWidth += numberLength * 1.5;
height -= 4;
}
final Font font = new Font(Display.getDefault(), "Arial", width / 2, SWT.BOLD); //$NON-NLS-1$
final Image canvas = new Image(null, decorationWidth, height);
final GC gc = new GC(canvas);
gc.setAntialias(SWT.ON);
gc.setAlpha(0);
gc.fillRectangle(0, 0, decorationWidth, height);
gc.setAlpha(255);
gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
// spetrila, 2014.12.17: In case we have more than two digits in the number of notifications,
// we will change the decoration to a rounded rectangle so it can contain
// all of the digits in the notification number
if(decorationWidth == width)
gc.fillOval(0, 0, decorationWidth - 1, height - 1);
else
gc.fillRoundRectangle(0, 0, decorationWidth, height, 10, 10);
final FontData fontData = font.getFontData()[0];
fontData.setHeight((int) (fontData.getHeight() * fontSize / 100.0 + 0.5));
fontData.setStyle(SWT.BOLD);
final Font newFont = new Font(Display.getCurrent(), fontData);
// gc.setFont(AEFUIActivator.getDefault().getCustomizedFont(font, fontSize, SWT.BOLD));
gc.setFont(newFont);
gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
final Point textSize = gc.stringExtent(textToDraw);
final int xPos = (decorationWidth - textSize.x) / 2;
final int yPos = (height - textSize.y) / 2;
gc.drawText(textToDraw, xPos + 1, yPos, true);
gc.dispose();
final ImageData imgData = canvas.getImageData();
// Remove white transparent pixels
final int whitePixel = imgData.palette.getPixel(new RGB(255,255,255));
imgData.transparentPixel = whitePixel;
final Image finalImage = new Image(null, imgData);
canvas.dispose();
font.dispose();
newFont.dispose();
return finalImage;
}
// ==================== 5. Creators ====================
#Override
public Image createImage()
{
if (baseImageData == null || overlayedImageData == null)
throw new IllegalArgumentException("Please check the ImageOverlayer. One of the overlaying images is NULL."); //$NON-NLS-1$
return super.createImage();
}
// ==================== 6. Action Methods ====================
#Override
protected void drawCompositeImage(final int width, final int height)
{
/*
* These two determine where the overlayed image top left
* corner should go, relative to the base image behind it.
*/
int xPos = 0;
int yPos = 0;
switch (overlayedImagePosition)
{
case TOP_LEFT:
break;
case TOP_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
break;
case BOTTOM_LEFT:
yPos = baseImageData.height - overlayedImageData.height;
break;
case BOTTOM_RIGHT:
xPos = baseImageData.width - overlayedImageData.width;
yPos = baseImageData.height - overlayedImageData.height;
break;
case CENTER:
xPos = (baseImageData.width - overlayedImageData.width) / 2;
yPos = (baseImageData.height - overlayedImageData.height) / 2;
break;
default:
break;
}
drawImage(baseImageData, 0, 0);
drawImage(overlayedImageData, xPos, yPos);
}
// ==================== 7. Getters & Setters ====================
final public ImageOverlayer overlayImagePosition(final OverlayedImagePosition overlayImagePosition)
{
this.overlayedImagePosition = overlayImagePosition;
return this;
}
final public ImageOverlayer baseImage(final ImageData baseImageData)
{
this.baseImageData = baseImageData;
return this;
}
final public ImageOverlayer baseImage(final Image baseImage)
{
this.baseImageData = baseImage.getImageData();
return this;
}
final public ImageOverlayer baseImage(final ImageDescriptor baseImageDescriptor)
{
this.baseImageData = baseImageDescriptor.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageData overlayImageData)
{
this.overlayedImageData = overlayImageData;
return this;
}
final public ImageOverlayer overlayImage(final Image overlayImage)
{
this.overlayedImageData = overlayImage.getImageData();
return this;
}
final public ImageOverlayer overlayImage(final ImageDescriptor overlayImageDescriptor)
{
this.overlayedImageData = overlayImageDescriptor.getImageData();
return this;
}
#Override
protected Point getSize()
{
// The size of the composite image is determined by the maximum size between the two building images,
// although keep in mind that the base image always comes underneath the overlaying one.
return new Point( max(baseImageData.width, overlayedImageData.width), max(baseImageData.height, overlayedImageData.height) );
}
}
Also you can use control decorations for this. The advantage is you can easily hide/show the notification with hide() and show() methods and add tool-tip text and listeners to it.
Check this blog for how to use control decoration. Use Button widget instead of Text for your case.
Create the notification image as shown below and set it to ControlDecoration object.
Image image = new Image(display, 20, 25);
GC gc = new GC(image);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, 20, 25);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
int notif = 5;
gc.drawText(new Integer(notif).toString(), 5, 5);
gc.dispose();

java text emojis add emoji to the image

I have one method of drawing the image where the text is get included in that image. Now the text contains emojis for ex:😍 and it's not getting printed as it is in image but the rectangular box like this [].
I have used java.text. and java.awt. classes for this
private void drawMessageString(Graphics2D g, final int messageHeight) {
int x = CRUSH_PADDING + CRUSH_MESSAGE_LR_PADDING;
int y = CRUSH_PADDING + TOP_BAR_HEIGHT + CRUSH_MESSAGE_TB_PADDING;
String myString="he new guy from New Zealand who is playing tennis 😍 😍";
try {
byte ptext[] = myString.getBytes("UTF-8");
String value;
value = new String(ptext, "UTF-8");
TextRenderer.drawString(g, value, FontUtils.getInstance().getFont(FONT_PROXIMA_NOVA, 24.0f), MSG_COLOR, new Rectangle(x, y, CRUSH_MESSAGE_WIDTH, messageHeight), TextAlignment.TOP, TextFormat.FIRST_LINE_VISIBLE);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
TextRenderer is nothing but a class created which cotains code to drawString and rectangle.
what should be the solution for this??
TextRenderer.java ==>
package com.text;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
/**
* A class which provides static methods for rendering text using alignment.
*
* #author Chris Copeland
* #version 1.0
*/
public final class TextRenderer {
/**
* Initialize a new instance of the {#link TextRenderer} class.
*/
private TextRenderer() {
}
/**
* Draws a string onto a <code>Graphics</code> handle, using a
* <code>Font</code>, <code>Color</code> and target bounds to calculate the
* location and automatic wrapping of text. The <i>align</i> property
* determines where the text will be positioned.
*
* #param g A <code>Graphics</code> handle which is the target of the draw
* operation
* #param text A <code>String</code> containing the text to draw
* #param font The <code>Font</code> to use when drawing the text
* #param color The <code>Color</code> to use when drawing the text
* #param bounds A <code>Rectangle</code> representing the bounds of the
* text
* #return A <code>Rectangle</code> representing the bounds consumed by the
* text
*/
public static Rectangle drawString(Graphics g, String text, Font font, Color color, Rectangle bounds) {
return drawString(g, text, font, color, bounds, TextAlignment.TOP_LEFT, TextFormat.NONE);
}
/**
* Draws a string onto a <code>Graphics</code> handle, using a
* <code>Font</code>, <code>Color</code> and target bounds to calculate the
* location and automatic wrapping of text. The <i>align</i> property
* determines where the text will be positioned.
*
* #param g A <code>Graphics</code> handle which is the target of the draw
* operation
* #param text A <code>String</code> containing the text to draw
* #param font The <code>Font</code> to use when drawing the text
* #param color The <code>Color</code> to use when drawing the text
* #param bounds A <code>Rectangle</code> representing the bounds of the
* text
* #param align A <code>TextAlignment</code> value representing the location
* to draw the text, relative to the <i>bounds</i>
* #return A <code>Rectangle</code> representing the bounds consumed by the
* text
*/
public static Rectangle drawString(Graphics g, String text, Font font, Color color, Rectangle bounds, TextAlignment align) {
return drawString(g, text, font, color, bounds, align, TextFormat.NONE);
}
/**
* Draws a string onto a <code>Graphics</code> handle, using a
* <code>Font</code>, <code>Color</code> and target bounds to calculate the
* location and automatic wrapping of text. The <i>align</i> property
* determines where the text will be positioned.
*
* #param g A <code>Graphics</code> handle which is the target of the draw
* operation
* #param text A <code>String</code> containing the text to draw
* #param font The <code>Font</code> to use when drawing the text
* #param color The <code>Color</code> to use when drawing the text
* #param bounds A <code>Rectangle</code> representing the bounds of the
* text
* #param align A <code>TextAlignment</code> value representing the location
* to draw the text, relative to the <i>bounds</i>
* #param format Additional formatting flags to use when drawing (see
* <code>TextFormat</code> class)
* #return A <code>Rectangle</code> representing the bounds consumed by the
* text
*/
public static Rectangle drawString(Graphics g, String text, Font font, Color color, Rectangle bounds, TextAlignment align, int format) {
if (g == null) {
throw new NullPointerException("The graphics handle cannot be null.");
}
if (text == null) {
throw new NullPointerException("The text cannot be null.");
}
if (font == null) {
throw new NullPointerException("The font cannot be null.");
}
if (color == null) {
throw new NullPointerException("The text color cannot be null.");
}
if (bounds == null) {
throw new NullPointerException("The text bounds cannot be null.");
}
if (align == null) {
throw new NullPointerException("The text alignment cannot be null.");
}
if (text.length() == 0) {
return new Rectangle(bounds.x, bounds.y, 0, 0);
}
Graphics2D g2D = (Graphics2D) g;
AttributedString attributedString = new AttributedString(text);
attributedString.addAttribute(TextAttribute.FOREGROUND, color);
attributedString.addAttribute(TextAttribute.FONT, font);
AttributedCharacterIterator attributedCharIterator = attributedString.getIterator();
FontRenderContext fontContext = new FontRenderContext(null, !TextFormat.isEnabled(format, TextFormat.NO_ANTI_ALIASING), false);
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(attributedCharIterator, fontContext);
Point targetLocation = new Point(bounds.x, bounds.y);
int nextOffset = 0;
if (align.isMiddle() || align.isBottom()) {
if (align.isMiddle()) {
targetLocation.y = bounds.y + (bounds.height / 2);
}
if (align.isBottom()) {
targetLocation.y = bounds.y + bounds.height;
}
while (lineMeasurer.getPosition() < text.length()) {
nextOffset = lineMeasurer.nextOffset(bounds.width);
nextOffset = nextTextIndex(nextOffset, lineMeasurer.getPosition(), text);
TextLayout textLayout = lineMeasurer.nextLayout(bounds.width, nextOffset, false);
if (align.isMiddle()) {
targetLocation.y -= (textLayout.getAscent() + textLayout.getLeading() + textLayout.getDescent()) / 2;
}
if (align.isBottom()) {
targetLocation.y -= (textLayout.getAscent() + textLayout.getLeading() + textLayout.getDescent());
}
}
if (TextFormat.isEnabled(format, TextFormat.FIRST_LINE_VISIBLE)) {
targetLocation.y = Math.max(0, targetLocation.y);
}
lineMeasurer.setPosition(0);
}
if (align.isRight() || align.isCenter()) {
targetLocation.x = bounds.x + bounds.width;
}
Rectangle consumedBounds = new Rectangle(targetLocation.x, targetLocation.y, 0, 0);
while (lineMeasurer.getPosition() < text.length()) {
nextOffset = lineMeasurer.nextOffset(bounds.width);
nextOffset = nextTextIndex(nextOffset, lineMeasurer.getPosition(), text);
TextLayout textLayout = lineMeasurer.nextLayout(bounds.width, nextOffset, false);
Rectangle2D textBounds = textLayout.getBounds();
targetLocation.y += textLayout.getAscent();
consumedBounds.width = Math.max(consumedBounds.width, (int) textBounds.getWidth());
switch (align) {
case TOP_LEFT:
case MIDDLE_LEFT:
case BOTTOM_LEFT:
textLayout.draw(g2D, targetLocation.x, targetLocation.y);
break;
case TOP:
case MIDDLE:
case BOTTOM:
targetLocation.x = bounds.x + (bounds.width / 2) - (int) (textBounds.getWidth() / 2);
consumedBounds.x = Math.min(consumedBounds.x, targetLocation.x);
textLayout.draw(g2D, targetLocation.x, targetLocation.y);
break;
case TOP_RIGHT:
case MIDDLE_RIGHT:
case BOTTOM_RIGHT:
targetLocation.x = bounds.x + bounds.width - (int) textBounds.getWidth();
textLayout.draw(g2D, targetLocation.x, targetLocation.y);
consumedBounds.x = Math.min(consumedBounds.x, targetLocation.x);
break;
}
targetLocation.y += textLayout.getLeading() + textLayout.getDescent();
}
consumedBounds.height = targetLocation.y - consumedBounds.y;
return consumedBounds;
}
/**
* Calculates the next maximum index of the string that will be displayed.
*
* #param nextOffset The index calculated using a
* <code>LineBreakMeasurer</code> <i>nextOffset</i> method
* #param measurerPosition The position within a
* <code>LineBreakMeasurer</code>
* #param text The text being rendered
* #return The next maximum index within the string
*/
private static int nextTextIndex(int nextOffset, int measurerPosition, String text) {
for (int i = measurerPosition + 1; i < nextOffset; ++i) {
if (text.charAt(i) == '\n') {
return i;
}
}
return nextOffset;
}
}
I used the font OpenSansEmoji.ttf which is combination of 3 fonts and now its working fine for me. the image is getting generated with the text and respective emojis.
private void drawMessageString(Graphics2D g, final int messageHeight) {
int x = CRUSH_PADDING + CRUSH_MESSAGE_LR_PADDING;
int y = CRUSH_PADDING + TOP_BAR_HEIGHT + CRUSH_MESSAGE_TB_PADDING;
String myString="he new guy from New Zealand who is playing tennis 😍 😍";
try {
byte ptext[] = myString.getBytes("UTF-8");
String value;
value = new String(ptext, "UTF-8");
TextRenderer.drawString(g, value, FontUtils.getInstance().getFont(FONT_OPEN_EMOJIFONT, 24.0f), MSG_COLOR, new Rectangle(x, y, CRUSH_MESSAGE_WIDTH, messageHeight), TextAlignment.TOP, TextFormat.FIRST_LINE_VISIBLE);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

Java image analysis - counting vertical lines

I need a little help on an image analysis algorithm in Java. I basically have images like this:
So, as you might guessed, I need to count the lines.
What approach do you think would be best?
Thanks,
Smaug
A simple segmentation algorithm can help you out. Heres how the algorithm works:
scan pixels from left to right and
record the position of the first
black (whatever the color of your
line is) pixel.
carry on this process
unless you find one whole scan when
you don't find the black pixel.
Record this position as well.
We are
just interested in the Y positions
here. Now using this Y position
segment the image horizontally.
Now
we are going to do the same process
but this time we are going to scan
from top to bottom (one column at a
time) in the segment we just created.
This time we are interested in X
positions.
So in the end we get every
lines extents or you can say a
bounding box for every line.
The
total count of these bounding boxes
is the number of lines.
You can do many optimizations in the algorithm according to your needs.
package ac.essex.ooechs.imaging.commons.edge.hough;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.util.Vector;
import java.io.File;
/**
* <p/>
* Java Implementation of the Hough Transform.<br />
* Used for finding straight lines in an image.<br />
* by Olly Oechsle
* </p>
* <p/>
* Note: This class is based on original code from:<br />
* http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm
* </p>
* <p/>
* If you represent a line as:<br />
* x cos(theta) + y sin (theta) = r
* </p>
* <p/>
* ... and you know values of x and y, you can calculate all the values of r by going through
* all the possible values of theta. If you plot the values of r on a graph for every value of
* theta you get a sinusoidal curve. This is the Hough transformation.
* </p>
* <p/>
* The hough tranform works by looking at a number of such x,y coordinates, which are usually
* found by some kind of edge detection. Each of these coordinates is transformed into
* an r, theta curve. This curve is discretised so we actually only look at a certain discrete
* number of theta values. "Accumulator" cells in a hough array along this curve are incremented
* for X and Y coordinate.
* </p>
* <p/>
* The accumulator space is plotted rectangularly with theta on one axis and r on the other.
* Each point in the array represents an (r, theta) value which can be used to represent a line
* using the formula above.
* </p>
* <p/>
* Once all the points have been added should be full of curves. The algorithm then searches for
* local peaks in the array. The higher the peak the more values of x and y crossed along that curve,
* so high peaks give good indications of a line.
* </p>
*
* #author Olly Oechsle, University of Essex
*/
public class HoughTransform extends Thread {
public static void main(String[] args) throws Exception {
String filename = "/home/ooechs/Desktop/vase.png";
// load the file using Java's imageIO library
BufferedImage image = javax.imageio.ImageIO.read(new File(filename));
// create a hough transform object with the right dimensions
HoughTransform h = new HoughTransform(image.getWidth(), image.getHeight());
// add the points from the image (or call the addPoint method separately if your points are not in an image
h.addPoints(image);
// get the lines out
Vector<HoughLine> lines = h.getLines(30);
// draw the lines back onto the image
for (int j = 0; j < lines.size(); j++) {
HoughLine line = lines.elementAt(j);
line.draw(image, Color.RED.getRGB());
}
}
// The size of the neighbourhood in which to search for other local maxima
final int neighbourhoodSize = 4;
// How many discrete values of theta shall we check?
final int maxTheta = 180;
// Using maxTheta, work out the step
final double thetaStep = Math.PI / maxTheta;
// the width and height of the image
protected int width, height;
// the hough array
protected int[][] houghArray;
// the coordinates of the centre of the image
protected float centerX, centerY;
// the height of the hough array
protected int houghHeight;
// double the hough height (allows for negative numbers)
protected int doubleHeight;
// the number of points that have been added
protected int numPoints;
// cache of values of sin and cos for different theta values. Has a significant performance improvement.
private double[] sinCache;
private double[] cosCache;
/**
* Initialises the hough transform. The dimensions of the input image are needed
* in order to initialise the hough array.
*
* #param width The width of the input image
* #param height The height of the input image
*/
public HoughTransform(int width, int height) {
this.width = width;
this.height = height;
initialise();
}
/**
* Initialises the hough array. Called by the constructor so you don't need to call it
* yourself, however you can use it to reset the transform if you want to plug in another
* image (although that image must have the same width and height)
*/
public void initialise() {
// Calculate the maximum height the hough array needs to have
houghHeight = (int) (Math.sqrt(2) * Math.max(height, width)) / 2;
// Double the height of the hough array to cope with negative r values
doubleHeight = 2 * houghHeight;
// Create the hough array
houghArray = new int[maxTheta][doubleHeight];
// Find edge points and vote in array
centerX = width / 2;
centerY = height / 2;
// Count how many points there are
numPoints = 0;
// cache the values of sin and cos for faster processing
sinCache = new double[maxTheta];
cosCache = sinCache.clone();
for (int t = 0; t < maxTheta; t++) {
double realTheta = t * thetaStep;
sinCache[t] = Math.sin(realTheta);
cosCache[t] = Math.cos(realTheta);
}
}
/**
* Adds points from an image. The image is assumed to be greyscale black and white, so all pixels that are
* not black are counted as edges. The image should have the same dimensions as the one passed to the constructor.
*/
public void addPoints(BufferedImage image) {
// Now find edge points and update the hough array
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
// Find non-black pixels
if ((image.getRGB(x, y) & 0x000000ff) != 0) {
addPoint(x, y);
}
}
}
}
/**
* Adds a single point to the hough transform. You can use this method directly
* if your data isn't represented as a buffered image.
*/
public void addPoint(int x, int y) {
// Go through each value of theta
for (int t = 0; t < maxTheta; t++) {
//Work out the r values for each theta step
int r = (int) (((x - centerX) * cosCache[t]) + ((y - centerY) * sinCache[t]));
// this copes with negative values of r
r += houghHeight;
if (r < 0 || r >= doubleHeight) continue;
// Increment the hough array
houghArray[t][r]++;
}
numPoints++;
}
/**
* Once points have been added in some way this method extracts the lines and returns them as a Vector
* of HoughLine objects, which can be used to draw on the
*
* #param percentageThreshold The percentage threshold above which lines are determined from the hough array
*/
public Vector<HoughLine> getLines(int threshold) {
// Initialise the vector of lines that we'll return
Vector<HoughLine> lines = new Vector<HoughLine>(20);
// Only proceed if the hough array is not empty
if (numPoints == 0) return lines;
// Search for local peaks above threshold to draw
for (int t = 0; t < maxTheta; t++) {
loop:
for (int r = neighbourhoodSize; r < doubleHeight - neighbourhoodSize; r++) {
// Only consider points above threshold
if (houghArray[t][r] > threshold) {
int peak = houghArray[t][r];
// Check that this peak is indeed the local maxima
for (int dx = -neighbourhoodSize; dx <= neighbourhoodSize; dx++) {
for (int dy = -neighbourhoodSize; dy <= neighbourhoodSize; dy++) {
int dt = t + dx;
int dr = r + dy;
if (dt < 0) dt = dt + maxTheta;
else if (dt >= maxTheta) dt = dt - maxTheta;
if (houghArray[dt][dr] > peak) {
// found a bigger point nearby, skip
continue loop;
}
}
}
// calculate the true value of theta
double theta = t * thetaStep;
// add the line to the vector
lines.add(new HoughLine(theta, r));
}
}
}
return lines;
}
/**
* Gets the highest value in the hough array
*/
public int getHighestValue() {
int max = 0;
for (int t = 0; t < maxTheta; t++) {
for (int r = 0; r < doubleHeight; r++) {
if (houghArray[t][r] > max) {
max = houghArray[t][r];
}
}
}
return max;
}
/**
* Gets the hough array as an image, in case you want to have a look at it.
*/
public BufferedImage getHoughArrayImage() {
int max = getHighestValue();
BufferedImage image = new BufferedImage(maxTheta, doubleHeight, BufferedImage.TYPE_INT_ARGB);
for (int t = 0; t < maxTheta; t++) {
for (int r = 0; r < doubleHeight; r++) {
double value = 255 * ((double) houghArray[t][r]) / max;
int v = 255 - (int) value;
int c = new Color(v, v, v).getRGB();
image.setRGB(t, r, c);
}
}
return image;
}
}
Source: http://vase.essex.ac.uk/software/HoughTransform/HoughTransform.java.html
I've implemented a simple solution (must be improved) using Marvin Framework that finds the vertical lines start and end points and prints the total number of lines found.
Approach:
Binarize the image using a given threshold.
For each pixel, if it is black (solid), try to find a vertical line
Save the x,y, of the start and end points
The line has a minimum lenght? It is an acceptable line!
Print the start point in red and the end point in green.
The output image is shown below:
The programs output:
Vertical line fount at: (74,9,70,33)
Vertical line fount at: (113,9,109,31)
Vertical line fount at: (80,10,76,32)
Vertical line fount at: (137,11,133,33)
Vertical line fount at: (163,11,159,33)
Vertical line fount at: (184,11,180,33)
Vertical line fount at: (203,11,199,33)
Vertical line fount at: (228,11,224,33)
Vertical line fount at: (248,11,244,33)
Vertical line fount at: (52,12,50,33)
Vertical line fount at: (145,13,141,35)
Vertical line fount at: (173,13,169,35)
Vertical line fount at: (211,13,207,35)
Vertical line fount at: (94,14,90,36)
Vertical line fount at: (238,14,236,35)
Vertical line fount at: (130,16,128,37)
Vertical line fount at: (195,16,193,37)
Vertical lines total: 17
Finally, the source code:
import java.awt.Color;
import java.awt.Point;
import marvin.image.MarvinImage;
import marvin.io.MarvinImageIO;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinPluginLoader;
public class VerticalLineCounter {
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
public VerticalLineCounter(){
// Binarize
MarvinImage image = MarvinImageIO.loadImage("./res/lines.jpg");
MarvinImage binImage = image.clone();
threshold.setAttribute("threshold", 127);
threshold.process(image, binImage);
// Find lines and save an output image
MarvinImage imageOut = findVerticalLines(binImage, image);
MarvinImageIO.saveImage(imageOut, "./res/lines_out.png");
}
private MarvinImage findVerticalLines(MarvinImage binImage, MarvinImage originalImage){
MarvinImage imageOut = originalImage.clone();
boolean[][] processedPixels = new boolean[binImage.getWidth()][binImage.getHeight()];
int color;
Point endPoint;
int totalLines=0;
for(int y=0; y<binImage.getHeight(); y++){
for(int x=0; x<binImage.getWidth(); x++){
if(!processedPixels[x][y]){
color = binImage.getIntColor(x, y);
// Black?
if(color == 0xFF000000){
endPoint = getEndOfLine(x,y,binImage,processedPixels);
// Line lenght threshold
if(endPoint.x - x > 5 || endPoint.y - y > 5){
imageOut.fillRect(x-2, y-2, 5, 5, Color.red);
imageOut.fillRect(endPoint.x-2, endPoint.y-2, 5, 5, Color.green);
totalLines++;
System.out.println("Vertical line fount at: ("+x+","+y+","+endPoint.x+","+endPoint.y+")");
}
}
}
processedPixels[x][y] = true;
}
}
System.out.println("Vertical lines total: "+totalLines);
return imageOut;
}
private Point getEndOfLine(int x, int y, MarvinImage image, boolean[][] processedPixels){
int xC=x;
int cY=y;
while(true){
processedPixels[xC][cY] = true;
processedPixels[xC-1][cY] = true;
processedPixels[xC-2][cY] = true;
processedPixels[xC-3][cY] = true;
processedPixels[xC+1][cY] = true;
processedPixels[xC+2][cY] = true;
processedPixels[xC+3][cY] = true;
if(getSafeIntColor(xC,cY,image) < 0xFF000000){
// nothing
}
else if(getSafeIntColor(xC-1,cY,image) == 0xFF000000){
xC = xC-2;
}
else if(getSafeIntColor(xC-2,cY,image) == 0xFF000000){
xC = xC-3;
}
else if(getSafeIntColor(xC+1,cY,image) == 0xFF000000){
xC = xC+2;
}
else if(getSafeIntColor(xC+2,cY,image) == 0xFF000000){
xC = xC+3;
}
else{
return new Point(xC, cY);
}
cY++;
}
}
private int getSafeIntColor(int x, int y, MarvinImage image){
if(x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()){
return image.getIntColor(x, y);
}
return -1;
}
public static void main(String args[]){
new VerticalLineCounter();
System.exit(0);
}
}
It depends on how much they look like that.
Bring the image to 1-bit (black and white) in a way that preserves the lines and brings the background to pure white
Perhaps do simple cleanup like speck removal (remove any small black components).
Then,
Find a black pixel
Use flood-fill algorithms to find its extent
See if the shape meets the criteria for being a line (lineCount++ if so)
remove it
Repeat this until there are no black pixels
A lot depends on how good you do #3, some ideas
Use Hough just on this section to check that you have one line, and that it is vertical(ish)
(after #1) rotate it to the vertical and check its width/height ratio

Categories

Resources