Getting incorrect subimage from java - java

In my game I am trying to make an animated walking sequence, so I'm trying to make sure that the image will changedepending on which animation frame it is on. When the space bar is pressed the image changes, but instead of getting the next image from my spritesheet I get the next image and the previous image. I have screenshots of the problem below, as well as the code.
Before space is pressed
After Space Is Pressed
Code
private static BufferedImage image;
private static BufferedImage[] cropped = new BufferedImage[15];
private static byte frame = 0;
public Player(){
try {
image = ImageIO.read(new File("DownWalking.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void drawPlayer(int x,int y,Graphics2D g2){
if (frame == 0){
cropped[0] = image.getSubimage(2, 1, 23, 43);
}
if (frame == 1){
cropped[1] = image.getSubimage(34, 1, 54, 45);
}
g2.drawImage(cropped[frame],x * Tile.tileSize, y * Tile.tileSize, cropped[frame].getWidth(), cropped[frame].getHeight(), null);

The method signature for getSubImage is:
getSubimage(int x, int y, int w, int h)
You hadgetSubimage(x1,y1,x2,y2) causing your image to get 2 tiles instead of 1.
Source

Related

How can I change the foreground color of a text based on the background it is on?

I have a JCheckbox,this is in a JPanel, that is supposed to show an image in the JPanel, when you select it. The problem is this: as you can see in the screenshot, the text of the JCheckbox is difficult to read because of the image.
I was thinking if there was some way to contrast the text to the image, so that the color of the text is the opposite of the image.
I know that there're other ways to fix it, like setting the JCheckbox outside the image, but I'd have to change design of my program and structure code.
For example, here's how I want it to look:
.
This is all the code that the JCheckBox currently has, it is something simple:
final JCheckBox INFO_IMG = new JCheckBox("Ver img");
INFO_IMG.setFont(new Font("Dialog", 0, 12));
INFO_IMG.setBounds(-2, 2, 78, 13);
INFO_IMG.setOpaque(false);
INFO_IMG.setFocusable(false);
INFO_IMG.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(final ItemEvent ie) {
if (1 == ie.getStateChange()) {
INFO_IMG.setText("Ver info");
IMG.setVisible(true);
/* INFO_IMG.setForegound(ROBOT.getPixelColor(
(int)INFO_IMG.getLocationOnScreen.getX() + 12
,(int) INFO_IMG.getLocationOnScreen().getY() + 10));
This is another way that I had thought of,
although it does not work well,would also have to get
the opposite color from the one return.
*/
} else {
INFO_IMG.setText("Ver img");
IMG.setVisible(false);
}
}
});
add(INFO_IMG);
Well, I have found this way to do it, but unfortunately it only works for black and white, can you think of other ways?
Public Color contrast(Image image) {
BufferedImage buffered = toBufferedImage(image);
int black = 0, white = 0;
for (int x = 0; x < buffered.getWidth(); x++) {
for (int y = 0; y < buffered.getHeight(); y++) {
if(buffered.getRGB(x, y) == Color.BLACK.getRGB()) {
black++;
}else {
white++;
}
}
}
System.out.println("Blanco: " + white + ", Negro: " + black);
return (white > black) ? Color.BLACK : Color.WHITE;
}
private BufferedImage toBufferedImage(Image image) {
int width = image.getWidth(null);
int height = image.getHeight(null);
int argb = BufferedImage.TYPE_BYTE_BINARY;
BufferedImage buffered = new BufferedImage(width, height, argb);
Graphics2D g2 = buffered.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
// JOptionPane.showMessageDialog(null, new ImageIcon(buffered));
return buffered;
}

Java - Sprite in JPanel error

Every time i run the panel is filled with a white line in the corner of it and the rest is black.
I would like to add KeyListener to the program so when i push the "W" key it will animate the sprite as it seems to be going forward.
IF the sprite is not centered all the time than how would i code a collision detect to repaint the map when the sprite image block reaches the edge of the panel.
I know im asking more than one question yet im tring to figure out one problem.
How would i spawn a simple NPC at a specific location on the tiled map that has not yet been painted. Have this NPC to be intractable.
What I would like to do is have a sprite in the center of the screen. Have a tiled map in the background with collision detect depended on color squares. IF tile layer is colored red not allow to pass through IF tile layer is colored red allow to pass through If tile color yellow apply action for it is a treasure chest.
public class gamePanel extends JPanel {
private BufferedImage image;
public gamePanel() throws IOException{
this.setBackground(Color.red);
}
public static void loadMap(Graphics g, int frame){
try {
BufferedImage bigImg = ImageIO.read(new File("C:\\Users\\czar\\Documents\\NetBeansProjects\\Inter\\src\\inter\\menu.jpg"));
} catch (IOException ex) {
Logger.getLogger(gamePanel.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void loadSprite(Graphics g, int move_int) throws IOException{
BufferedImage bigImg = ImageIO.read(new File("C:\\Users\\czar\\Documents\\NetBeansProjects\\Inter\\src\\inter\\sprite.jpg"));
// The above line throws an checked IOException which must be caught.
final int width = 25;
final int height = 40;
final int cols = 2;
BufferedImage[] left_move = new BufferedImage[3];
BufferedImage[] right_move = new BufferedImage[3];
BufferedImage[] up_move = new BufferedImage[3];
BufferedImage[] down_move = new BufferedImage[3];
for(int i = 0; i < 4; i++){
if(move_int == 0){
left_move[i] = bigImg.getSubimage(
0 * width, 0 * height, width, height );
}
if(move_int == 1){
right_move[i] = bigImg.getSubimage(
i * width, 1 * height, width, height );
}
if(move_int == 2){
up_move[i] = bigImg.getSubimage(
i * width, 2 * height, width, height );
}
if(move_int == 3){
down_move[i] = bigImg.getSubimage(
i * width, 3 * height, width, height );
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
try {
loadMap(g, 0);
loadSprite(g, 0);
} catch (IOException ex) {
// handle exception...
}
}
}
I'm not really sure what you're trying to do, but here is some quick help on solving your error.
This code...
BufferedImage[] down_move = new BufferedImage[3];
creates an array which can hold 3 images, namely:
down_move[0]
down_move[1]
down_move[2]
That is because arrays are zero-based. If you do:
System.out.println(down_move.length);
you will see it output 3. This is because your array only fits 3 images.
I assume you want 4 images on your array. Therefore you will need to modify your code to:
BufferedImage[] down_move = new BufferedImage[4];
This will result in 4 images, namely:
down_move[0]
down_move[1]
down_move[2]
down_move[3]
In your code you are trying to access down_move[3] in an array that only goes up to down_move[2].
Read the error: ArrayIndexOutOfBoundsException: 3
Your image arrays have 3 indices (0, 1, and 2), and somewhere you're trying to access the 4th index (3).
Change your image array sizes to 4 instead of 3 (as shown below) and it should no longer give you this exception.
BufferedImage[] up_move = new BufferedImage[4];
BufferedImage[] down_move = new BufferedImage[4];
BufferedImage[] left_move = new BufferedImage[4];
BufferedImage[] right_move = new BufferedImage[4];

Why do I get java.lang.StackOverflowError when using Flood Fill algorithm?

My program is supposed to fill in a non-regular shape with a color (black and white for the beginning) that I specify in the boundaryFill4 method. Here is the link to myImage.png: https://dl.dropbox.com/u/41007907/myImage.png
I use a very simple flood fill algorithm, but it does not work somehow... Here is the FULL code:
import java.awt.Color;
import java.awt.Container;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class MyPolygon extends JFrame {
private JLabel my;
public MyPolygon() throws InterruptedException {
createMy();
}
private void createMy() throws InterruptedException {
Container contentPane = getContentPane();
contentPane.setBackground(Color.WHITE);
contentPane.setLayout(null);
contentPane.setSize(1000, 700);
my = new JLabel();
my.setIcon(new ImageIcon("myImage.png"));
my.setBounds(50, 50, 300, 300);
contentPane.add(my);
setSize(1000, 700);
setVisible(true);
setLocationRelativeTo(null);
int fill = 100;
boundaryFill4(100, 100, fill, 50);
}
// Flood Fill method
public void boundaryFill4(int x, int y, int fill, int boundary) {
int current;
current = getPixel(x, y);
if ((current >= boundary) && (current != fill)) {
setPixel(x, y, fill);
boundaryFill4(x + 1, y, fill, boundary);
boundaryFill4(x - 1, y, fill, boundary);
boundaryFill4(x, y + 1, fill, boundary);
boundaryFill4(x, y - 1, fill, boundary);
}
}
// Getting the color integer at specified point(x, y)
private int getPixel(int x, int y) {
Image img = ((ImageIcon) my.getIcon()).getImage();
BufferedImage buffered = new BufferedImage(img.getWidth(null),
img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
buffered.getGraphics().drawImage(img, 0, 0, null);
Color c = new Color(buffered.getRGB(x, y));
int current = buffered.getRGB(x, y);
return current;
}
// Setting the color integer to a specified point(x, y)
private void setPixel(int x, int y, int fill) {
Image img = ((ImageIcon) my.getIcon()).getImage();
BufferedImage buffered = new BufferedImage(img.getWidth(null),
img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
buffered.getGraphics().drawImage(img, 0, 0, null);
int red = fill;
int green = fill;
int blue = fill;
Color c = new Color(buffered.getRGB(x, y));
c = new Color(red, green, blue);
buffered.setRGB(x, y, c.getRGB());
}
// Main method
public static void main(String args[]) throws InterruptedException {
MyPolygon my = new MyPolygon();
my.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Why do I get StackOverflow error? How can I correct for it so that my code works?
You could try to transform your recursive approach (boundaryFill4 calling itself) to a non-recursive one. This way the JVM stack would not overflow.
Another option would be to increase the size of the stack -- see What is the maximum depth of the java call stack?
StackOverflowException means, that your recursion is too deep for your memory or does not end.
Try on a smaller Image. When this does not solves the problem there is something wrong with your recursion-end-condition. (Does setPixel() and getPixel really change the Image? Write a JUnitTest)
Also you really should simplify your setPixel and getPixel methods. They are too complex.
For every Pixel you set or get you create a new BufferedImage-Instance and then dispose it after setting ONE pixel.
You can store and reuse the BufferedImage.
You should debug your boundaryFill4 method: it is where an infinite loop occurs. Use simple cases to track how the method reacts.
Furthermore, you should avoid to write / read the image at each iteration of the recursion. Instantiate a proper and efficient data structure representing the image at the beginning, then modify this data structure and when the algorithm ends, write the results as a image.

Java read and load images

i just finish making a Minesweeper game, everything functions perfectly except one thing, the speed of loading the images into he game. I noticed if i have a large number of cells in the game images loads really slow after the mouse click on the cell and it gets faster if i have a smaller number of cells. is there any other way that would make loading images much faster than the one i used? here is the method i used in order to load the images into the game :
private void draw(Graphics g) {
BufferedImage gRec =null, flag=null, mine=null, aCount0=null,
aCount1=null,aCount2 =null,aCount3 =null,aCount4 =null,aCount5 =null,
aCount6 =null,aCount7 =null,aCount8 = null;
try {
gRec = ImageIO.read(new File("/Users/msa_666/Desktop/blank.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
mine = ImageIO.read(new File("/Users/msa_666/Desktop/bombdeath.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
aCount0 = ImageIO.read(new File("/Users/msa_666/Desktop/open0.gif"));
aCount1 = ImageIO.read(new File("/Users/msa_666/Desktop/open1.gif"));
aCount2 = ImageIO.read(new File("/Users/msa_666/Desktop/open2.gif"));
aCount3 = ImageIO.read(new File("/Users/msa_666/Desktop/open3.gif"));
aCount4 = ImageIO.read(new File("/Users/msa_666/Desktop/open4.gif"));
aCount5 = ImageIO.read(new File("/Users/msa_666/Desktop/open5.gif"));
aCount6 = ImageIO.read(new File("/Users/msa_666/Desktop/open6.gif"));
aCount7 = ImageIO.read(new File("/Users/msa_666/Desktop/open7.gif"));
aCount8 = ImageIO.read(new File("/Users/msa_666/Desktop/open8.gif"));
}
catch (IOException e) {
e.printStackTrace();
}
if (getCovered() == true && getMarked () == false) { // gray rectangle
g.drawImage(gRec,getX(),getY(),w,h,null);
}
else if (getCovered()==true && getMarked() == true) { //flag
g.drawImage(flag,getX(),getY(),w,h,null);
}
else if (getCovered()== false && getMined()== true){ //mine
g.drawImage(mine,getX(),getY(),w,h,null);
}
else if ( getCovered() == false && getMined() == false) { // adjacency count image
switch (getAdjCount()){
case 0:
g.drawImage(aCount0,getX(),getY(),w,h,null);
break;
case 1:
g.drawImage(aCount1,getX(),getY(),w,h,null);
break;
case 2:
g.drawImage(aCount2,getX(),getY(),w,h,null);
break;
case 3:
g.drawImage(aCount3,getX(),getY(),w,h,null);
break;
case 4:
g.drawImage(aCount4,getX(),getY(),w,h,null);
break;
case 5:
g.drawImage(aCount5,getX(),getY(),w,h,null);
break;
case 6:
g.drawImage(aCount6,getX(),getY(),w,h,null);
break;
case 7:
g.drawImage(aCount7,getX(),getY(),w,h,null);
break;
case 8:
g.drawImage(aCount8,getX(),getY(),w,h,null);
break;
}
}
}
here is the mouse listener to repaint each cell after clicking on it :
public void mouseClicked(MouseEvent e) {
int sRow, sCol;
sRow= e.getX() / cellHeight;
sCol = e.getY()/ cellWidth;
System.out.println(e.getX() +"," +sCol);
System.out.println(e.getY()+","+sRow);
if (e.getButton() == MouseEvent.BUTTON1) {
if( cells[sRow][sCol].getMarked() == false)
uncoverCell(cells[sRow][sCol]);
// cells[sRow][sCol].setCovered(false);
System.out.println(cells[sRow][sCol].getMined());
repaint();
}
else if (e.getButton() == MouseEvent.BUTTON2) {
}
else if (e.getButton() == MouseEvent.BUTTON3) {
if (cells[sRow][sCol].getMarked() == false){
cells[sRow][sCol].setMarked(true);
repaint();
}
else {
cells[sRow][sCol].setMarked(false);
repaint();
}
}
if (allMinesMarked() && allNonMinesUncovered()){
System.out.println("You Win");
}
}
public void paintComponent(Graphics g) {
for ( int i=0 ; i <rowCount; i++ ) {
for (int j=0; j<columnCount; j++) {
cells[i][j].draw(g);
}
}
}
You need to tell us:
Just where is draw(...) called?
How do you obtain the Graphics object, g, that is passed into the draw method's parameter?
I'm guessing here since we don't have all of the relevant code, but it looks as if you're re-reading in your images each time you want to display one. If so, don't do this. Read the images in only once at the start of the program, and then use the Images or perhaps better, ImageIcons, obtained when you need them.
Edit
Thanks for posting more code, and this in fact confirms my suspicion: you're re-reading in the image files with every repaint of your GUI. This is highly inefficient and will slow your program down to a crawl. Again, you should read the images into your program once and then use them multiple times.
Myself I'd create ImageIcons from the images, and then display them in a JLabel. When there is need to swap images, simply call setIcon(...) on the JLabel. This way there's no need to even mess with paintComponent(...).
Edit 2
For example (compile and run this):
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SwapIcons {
private static final int CELL_SIDE_COUNT = 3;
private ImageCell[] imageCells = new ImageCell[CELL_SIDE_COUNT * CELL_SIDE_COUNT];
private JPanel mainPanel = new JPanel();
public SwapIcons(final GetImages getImages) {
mainPanel.setLayout(new GridLayout(CELL_SIDE_COUNT, CELL_SIDE_COUNT));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
for (int i = 0; i < imageCells.length; i++) {
imageCells[i] = new ImageCell(getImages);
mainPanel.add(imageCells[i].getImgLabel());
}
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui(GetImages getImages) {
SwapIcons swapIcons = new SwapIcons(getImages);
JFrame frame = new JFrame("Click on Icons to Change");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(swapIcons.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
try {
final GetImages getImages = new GetImages();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui(getImages);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ImageCell {
private JLabel imgLabel = new JLabel();
private GetImages getImages;
private int iconIndex = 0;
public ImageCell(final GetImages getImages) {
this.getImages = getImages;
imgLabel.setIcon(getImages.getIcon(0));
imgLabel.addMouseListener(new MyMouseListener());
}
public JLabel getImgLabel() {
return imgLabel;
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
iconIndex++;
iconIndex %= getImages.getIconListSize();
imgLabel.setIcon(getImages.getIcon(iconIndex));
}
}
}
// Simply gets a SpriteSheet and subdivides it into a List of ImageIcons
class GetImages {
private static final String SPRITE_PATH = "http://th02.deviantart.net/"
+ "fs70/PRE/i/2011/169/0/8/blue_player_sprite_sheet_by_resetado-d3j7zba.png";
public static final int SPRITE_ROWS = 6;
public static final int SPRITE_COLS = 6;
public static final int SPRITE_CELLS = 35;
private List<ImageIcon> iconList = new ArrayList<ImageIcon>();
public GetImages() throws IOException {
URL imgUrl = new URL(SPRITE_PATH);
BufferedImage mainImage = ImageIO.read(imgUrl);
for (int i = 0; i < SPRITE_CELLS; i++) {
int row = i / SPRITE_COLS;
int col = i % SPRITE_COLS;
int x = (int) (((double) mainImage.getWidth() * col) / SPRITE_COLS);
int y = (int) ((double) (mainImage.getHeight() * row) / SPRITE_ROWS);
int w = (int) ((double) mainImage.getWidth() / SPRITE_COLS);
int h = (int) ((double) mainImage.getHeight() / SPRITE_ROWS);
BufferedImage img = mainImage.getSubimage(x, y, w, h);
ImageIcon icon = new ImageIcon(img);
iconList.add(icon);
}
}
// get the Icon from the List at index position
public ImageIcon getIcon(int index) {
if (index < 0 || index >= iconList.size()) {
throw new ArrayIndexOutOfBoundsException(index);
}
return iconList.get(index);
}
public int getIconListSize() {
return iconList.size();
}
}
Hovercraft Full Of Eels answer is good and will work.
And is fine for a standalone app, but for an applet or web start app can further optimize by having one large image and then copying parts of it to the graphics object that is visible, think grids and use function in java.awt.Graphics object (from javadoc):
public abstract boolean drawImage(Image img,
int dx1,
int dy1,
int dx2,
int dy2,
int sx1,
int sy1,
int sx2,
int sy2,
ImageObserver observer)
Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. Transparent pixels do not affect whatever pixels are already there.
This method returns immediately in all cases, even if the image area to be drawn has not yet been scaled, dithered, and converted for the current output device. If the current output representation is not yet complete then drawImage returns false. As more of the image becomes available, the process that loads the image notifies the specified image observer.
This method always uses the unscaled version of the image to render the scaled rectangle and performs the required scaling on the fly. It does not use a cached, scaled version of the image for this operation. Scaling of the image from source to destination is performed such that the first coordinate of the source rectangle is mapped to the first coordinate of the destination rectangle, and the second source coordinate is mapped to the second destination coordinate. The subimage is scaled and flipped as needed to preserve those mappings.
Parameters:
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.
Returns:
false if the image pixels are still changing; true otherwise.
This is better as it takes a few seconds to make a new connection and download image over internet, so if you have one main image that has all the sub images in a big table then total time to download, load and render will be less. extra logic to copy from an area is trivial maybe .1KB of jar file space :)

Java bitmap font: blitting 1-bit image with different colors

I'd like to implement a simple bitmap font drawing in Java AWT-based application. Application draws on a Graphics object, where I'd like to implement a simple algorithm:
1) Load a file (probably using ImageIO.read(new File(fileName))), which is 1-bit PNG that looks something like that:
I.e. it's 16*16 (or 16*many, if I'd like to support Unicode) matrix of 8*8 characters. Black corresponds to background color, white corresponds to foreground.
2) Draw strings character-by-character, blitting relevant parts of this bitmap to target Graphics. So far I've only succeeded with something like that:
int posX = ch % 16;
int posY = ch / 16;
int fontX = posX * CHAR_WIDTH;
int fontY = posY * CHAR_HEIGHT;
g.drawImage(
font,
dx, dy, dx + CHAR_WIDTH, dy + CHAR_HEIGHT,
fontX, fontY, fontX + CHAR_WIDTH, fontY + CHAR_HEIGHT,
null
);
It works, but, alas, it blits the text as is, i.e. I can't substitute black and white with desired foreground and background colors, and I can't even make background transparent.
So, the question is: is there a simple (and fast!) way in Java to blit part of one 1-bit bitmap to another, colorizing it in process of blitting (i.e. replacing all 0 pixels with one given color and all 1 pixels with another)?
I've researched into a couple of solutions, all of them look suboptimal to me:
Using a custom colorizing BufferedImageOp, as outlined in this solution - it should work, but it seems that it would be very inefficient to recolorize a bitmap before every blit operation.
Using multiple 32-bit RGBA PNG, with alpha channel set to 0 for black pixels and to maximum for foreground. Every desired foreground color should get its own pre-rendered bitmap. This way I can make background transparent and draw it as a rectangle separately before blitting and then select one bitmap with my font, pre-colorized with desired color and draw a portion of it over that rectangle. Seems like a huge overkill to me - and what makes this option even worse - it limits number of foreground colors to a relatively small amount (i.e. I can realistically load up and hold like hundreds or thousands of bitmaps, not millions)
Bundling and loading a custom font, as outlined in this solution could work, but as far as I see in Font#createFont documentation, AWT's Font seems to work only with vector-based fonts, not with bitmap-based.
May be there's already any libraries that implement such functionality? Or it's time for me to switch to some sort of more advanced graphics library, something like lwjgl?
Benchmarking results
I've tested a couple of algorithms in a simple test: I have 2 strings, 71 characters each, and draw them continuously one after another, right on the same place:
for (int i = 0; i < N; i++) {
cv.putString(5, 5, STR, Color.RED, Color.BLUE);
cv.putString(5, 5, STR2, Color.RED, Color.BLUE);
}
Then I measure time taken and calculate speed: string per second and characters per second. So far, various implementation I've tested yield the following results:
bitmap font, 16*16 characters bitmap: 10991 strings / sec, 780391 chars / sec
bitmap font, pre-split images: 11048 strings / sec, 784443 chars / sec
g.drawString(): 8952 strings / sec, 635631 chars / sec
colored bitmap font, colorized using LookupOp and ByteLookupTable: 404 strings / sec, 28741 chars / sec
You might turn each bitmap into a Shape (or many of them) and draw the Shape. See Smoothing a jagged path for the process of gaining the Shape.
E.G.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.util.Random;
/* Gain the outline of an image for further processing. */
class ImageShape {
private BufferedImage image;
private BufferedImage ImageShape;
private Area areaOutline = null;
private JLabel labelOutline;
private JLabel output;
private BufferedImage anim;
private Random random = new Random();
private int count = 0;
private long time = System.currentTimeMillis();
private String rate = "";
public ImageShape(BufferedImage image) {
this.image = image;
}
public void drawOutline() {
if (areaOutline!=null) {
Graphics2D g = ImageShape.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0,0,ImageShape.getWidth(),ImageShape.getHeight());
g.setColor(Color.RED);
g.setClip(areaOutline);
g.fillRect(0,0,ImageShape.getWidth(),ImageShape.getHeight());
g.setColor(Color.BLACK);
g.setClip(null);
g.draw(areaOutline);
g.dispose();
}
}
public Area getOutline(Color target, BufferedImage bi) {
// construct the GeneralPath
GeneralPath gp = new GeneralPath();
boolean cont = false;
int targetRGB = target.getRGB();
for (int xx=0; xx<bi.getWidth(); xx++) {
for (int yy=0; yy<bi.getHeight(); yy++) {
if (bi.getRGB(xx,yy)==targetRGB) {
if (cont) {
gp.lineTo(xx,yy);
gp.lineTo(xx,yy+1);
gp.lineTo(xx+1,yy+1);
gp.lineTo(xx+1,yy);
gp.lineTo(xx,yy);
} else {
gp.moveTo(xx,yy);
}
cont = true;
} else {
cont = false;
}
}
cont = false;
}
gp.closePath();
// construct the Area from the GP & return it
return new Area(gp);
}
public JPanel getGui() {
JPanel images = new JPanel(new GridLayout(1,2,2,2));
JPanel gui = new JPanel(new BorderLayout(3,3));
JPanel originalImage = new JPanel(new BorderLayout(2,2));
final JLabel originalLabel = new JLabel(new ImageIcon(image));
originalImage.add(originalLabel);
images.add(originalImage);
ImageShape = new BufferedImage(
image.getWidth(),
image.getHeight(),
BufferedImage.TYPE_INT_RGB
);
labelOutline = new JLabel(new ImageIcon(ImageShape));
images.add(labelOutline);
anim = new BufferedImage(
image.getWidth()*2,
image.getHeight()*2,
BufferedImage.TYPE_INT_RGB);
output = new JLabel(new ImageIcon(anim));
gui.add(output, BorderLayout.CENTER);
updateImages();
gui.add(images, BorderLayout.NORTH);
animate();
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
animate();
}
};
Timer timer = new Timer(1,al);
timer.start();
return gui;
}
private void updateImages() {
areaOutline = getOutline(Color.BLACK, image);
drawOutline();
}
private void animate() {
Graphics2D gr = anim.createGraphics();
gr.setColor(Color.BLUE);
gr.fillRect(0,0,anim.getWidth(),anim.getHeight());
count++;
if (count%100==0) {
long now = System.currentTimeMillis();
long duration = now-time;
double fraction = (double)duration/1000;
rate = "" + (double)100/fraction;
time = now;
}
gr.setColor(Color.WHITE);
gr.translate(0,0);
gr.drawString(rate, 20, 20);
int x = random.nextInt(image.getWidth());
int y = random.nextInt(image.getHeight());
gr.translate(x,y);
int r = 128+random.nextInt(127);
int g = 128+random.nextInt(127);
int b = 128+random.nextInt(127);
gr.setColor(new Color(r,g,b));
gr.draw(areaOutline);
gr.dispose();
output.repaint();
}
public static void main(String[] args) throws Exception {
int size = 150;
final BufferedImage outline = javax.imageio.ImageIO.read(new java.io.File("img.gif"));
ImageShape io = new ImageShape(outline);
JFrame f = new JFrame("Image Outline");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(io.getGui());
f.pack();
f.setResizable(false);
f.setLocationByPlatform(true);
f.setVisible(true);
}
}
I have to figure there is a factor of ten error in the FPS count on the top left of the blue image though. 50 FPS I could believe, but 500 FPS seems ..wrong.
Okay, looks like I've found the best solution. The key to success was accessing raw pixel arrays in underlying AWT structures. Initialization goes something like that:
public class ConsoleCanvas extends Canvas {
protected BufferedImage buffer;
protected int w;
protected int h;
protected int[] data;
public ConsoleCanvas(int w, int h) {
super();
this.w = w;
this.h = h;
}
public void initialize() {
data = new int[h * w];
// Fill data array with pure solid black
Arrays.fill(data, 0xff000000);
// Java's endless black magic to get it working
DataBufferInt db = new DataBufferInt(data, h * w);
ColorModel cm = ColorModel.getRGBdefault();
SampleModel sm = cm.createCompatibleSampleModel(w, h);
WritableRaster wr = Raster.createWritableRaster(sm, db, null);
buffer = new BufferedImage(cm, wr, false, null);
}
#Override
public void paint(Graphics g) {
update(g);
}
#Override
public void update(Graphics g) {
g.drawImage(buffer, 0, 0, null);
}
}
After this one, you've got both a buffer that you can blit on canvas updates and underlying array of ARGB 4-byte ints - data.
Single character can be drawn like that:
private void putChar(int dx, int dy, char ch, int fore, int back) {
int charIdx = 0;
int canvasIdx = dy * canvas.w + dx;
for (int i = 0; i < CHAR_HEIGHT; i++) {
for (int j = 0; j < CHAR_WIDTH; j++) {
canvas.data[canvasIdx] = font[ch][charIdx] ? fore : back;
charIdx++;
canvasIdx++;
}
canvasIdx += canvas.w - CHAR_WIDTH;
}
}
This one uses a simple boolean[][] array, where first index chooses character and second index iterates over raw 1-bit character pixel data (true => foreground, false => background).
I'll try to publish a complete solution as a part of my Java terminal emulation class set soon.
This solution benchmarks for impressive 26007 strings / sec or 1846553 chars / sec - that's 2.3x times faster than previous best non-colorized drawImage().

Categories