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];
Related
I am clearly missing an important concept here. I have written code using mouse events to draw a boundary (a polygon) on an existing BufferedImage. Here is the relevant section:
public void paintComponent(Graphics g)
{
super.paintComponent(g); //Paint parent's background
//G3 displays the BufferedImage "Drawing" with each paint
Graphics2D G3 = (Graphics2D)g;
G3.drawImage(this.Drawing, 0, 0, null);
G3.dispose();
}
public void updateDrawing()
{
int x0, y0, x1, y1; // Vertex coordinates
Line2D.Float seg;
// grafix is painting the mouse drawing to the BufferedImage "Drawing"
if(this.pts.size() > 0)
{
for(int ip = 0; ip < pts.size(); ip++)
{
x0 = (int)this.pts.get(ip).x;
y0 = (int)this.pts.get(ip).y;
this.grafix.drawRect(x0 - this.sqw/2, y0 - this.sqh/2, + this.sqw, this.sqh);
if (ip > 0)
{
x1 = (int)this.pts.get(ip-1).x;
y1 = (int)this.pts.get(ip-1).y;
this.grafix.drawLine(x1, y1, x0, y0);
seg = new Line2D.Float(x1, y1, x0, y0);
this.segments.add(seg);
}
}
}
repaint();
}
The next two routines are called by the mouse events: Left click gets the next point and right click closes the region.
public void getNextPoint(Point2D p)
{
this.isDrawing = true;
Point2D.Float next = new Point2D.Float();
next.x = (float) p.getX();
next.y = (float) p.getY();
this.pts.add(next);
updateDrawing();
}
public void closeBoundary()
{
//Connects the last point to the first point to close the loop
Point2D.Float next = new Point2D.Float(this.pts.get(0).x, this.pts.get(0).y);
this.pts.add(next);
this.isDrawing = false;
updateDrawing();
}
It all works fine and I can save the image with my drawing on it:
image with drawing
The list of vertices (pts) and the line segments (segments) are all that describe the region/shape/polygon.
I wish to extract from the original image only that region enclosed within the boundary. That is, I plan to create a new BufferedImage by moving through all of the pixels, testing to see if they fall within the figure and keep them if they do.
So I want to create an AREA from the points and segments I've collected in drawing the shape. Everything says: create an AREA variable and "getPathIterator". But on what shape? My AREA variable will be empty. How does the path iterator access the points in my list?
I've been all over the literature and this website as well.
I'm missing something.
Thank you haraldK for your suggestion. Before I saw your post, I came to a similar conclusion:
Using the Arraylist of vertices from the paint operation, I populated a "Path2D.Float" object called "contour" by looping through the points list that was created during the "painting" operation. Using this "contour" object, I instantiated an Area called "interferogram". Just to check my work, I created another PathIterator, "PI", from the Area and decomposed the Area, "interferogram" into "segments" sending the results to the console. I show the code below:
private void mnuitmKeepInsideActionPerformed(java.awt.event.ActionEvent evt)
{
// Keeps the inner area of interest
// Vertices is the "pts" list from Class MouseDrawing (mask)
// It is already a closed path
ArrayList<Point2D.Float> vertices =
new ArrayList<>(this.mask.getVertices());
this.contour = new Path2D.Float(Path2D.WIND_NON_ZERO);
// Read the vertices into the Path2D variable "contour"
this.contour.moveTo((float)vertices.get(0).getX(),
(float)vertices.get(0).getY()); //Starting location
for(int ivertex = 1; ivertex < vertices.size(); ivertex++)
{
this.contour.lineTo((float)vertices.get(ivertex).getX(),
(float)vertices.get(ivertex).getY());
}
this.interferogram = new Area(this.contour);
PathIterator PI = this.interferogram.getPathIterator(null);
//Test print out the segment types and vertices for debug
float[] p = new float[6];
int icount = 0;
while( !PI.isDone())
{
int type = PI.currentSegment(p);
System.out.print(icount);
System.out.print(" Type " + type);
System.out.print(" X " + p[0]);
System.out.println(" Y " + p[1]);
icount++;
PI.next();
}
BufferedImage masked = Mask(this.image_out, this.interferogram);
// Write image to file for debug
String dir;
dir = System.getProperty("user.dir");
dir = dir + "\\00masked.png";
writeImage(masked, dir, "PNG");
}
Next, I applied the mask to the image testing each pixel for inclusion in the area using the code below:
public BufferedImage Mask(BufferedImage BIM, Area area)
{
/** Loop through the pixels in the image and test each one for inclusion
* within the area.
* Change the colors of those outside
**/
Point2D p = new Point2D.Double(0,0);
// rgb should be white
int rgb = (255 << 24);
for (int row = 0; row < BIM.getWidth(); row++)
{
for (int col = 0; col < BIM.getHeight(); col++)
{
p.setLocation(col, row);
if(!area.contains(p))
{
BIM.setRGB(col, row, rgb);
}
}
}
return BIM;
}
public static BufferedImage deepCopy(BufferedImage B2M)
{
ColorModel cm = B2M.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = B2M.copyData(B2M.getRaster()
.createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
This worked beautifully (I was surprised!) except for one slight detail: the lines of the area appeared around the outside of the masked image.
In order to remedy this, I copied the original (resized) image before the painting operation. Many thanks to user1050755 (Nov 2014) for the routine deepCopy that I found on this website. Applying my mask to the copied image resulted in the portion of the original image I wanted without the mask lines. The result is shown in the attached picture. I am stoked!
masked image
I wrote a program that reads an image from command line and want to read each pixel to draw a rectangle of the respective colour to "recreate" the image from rectangles.
However, although the rectangles have the correct size, every pixel seems to be black. At least, what I see in the output panel is a black picture that has the same size as the input picture.
class AppDrawPanel extends JPanel {
private BufferedImage bi;
/* ... */
public void loadAPPImage( String s ) throws IOException{
bi = ImageIO.read(new File(s));
}
#Override
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
int w = bi.getWidth();
int h = bi.getHeight();
for( int x = 0; x < w; x++){
for ( int z = 0; z < h; z++ ){
Color c = new Color(bi.getRGB(x, z));
super.setForeground(c);
g2.fillRect(x, z, 3, 3);
}
}
}
}
And the main function:
public static void main( String[] args ) throws IOException{
/* ... */
AppDrawPanel draw = new AppDrawPanel();
draw.loadAPPImage(args[0]);
frame.add(draw);
/* ... */
}
where /* ... */ represents code that has nothing to do with drawing the rectangles or reading the image.
In this related example, the width and height of each pixel is scaled by an arbitrary factor of 10. The method drawImage() then scales the image to the component's preferred size. As an exercise, override getPreferredSize() to return an appropriate dimension:
new Dimension(imgW * 10, imgH * 10);
Also consider making the arbitrary factor a class-level property.
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
I am currently in the process of experimenting with a 2D tile based side scrolling game in Java, primarily based on the code and examples from "Developing Games in Java" by David Brackeen
At the moment the map files are 100x100 tiles in size (each tile is 64x64 pixels). I have already configured the system to only display the tiles which are visible to the player. The Graphics system is managed by a ScreenManager class that returns the graphics object of the current BufferStrategy as follows:
ScreenManager.java
private GraphicsDevice device;
...
/**
* Gets the graphics context for the display. The
* ScreenManager uses double buffering, so applications must
* call update() to show any graphics drawn.
* <p>
* The application must dispose of the graphics object.
*/
public Graphics2D getGraphics(){
Window window = device.getFullScreenWindow();
if(window != null){
BufferStrategy strategy = window.getBufferStrategy();
return (Graphics2D)strategy.getDrawGraphics();
}
else{
return null;
}
}
After the graphics from this ScreenManager is passed along in the game loop to the draw method of the TreeRenderer.
TreeMapRenderer.java
/**
Draws the specified TileMap.
*/
public void draw(Graphics2D g, TileMap map,
int screenWidth, int screenHeight, float fr)
{
Sprite player = map.getPlayer();
int mapWidth = tilesToPixels(map.getWidth());
int mapHeight = tilesToPixels(map.getHeight());
// get the scrolling position of the map
// based on player's position
int offsetX = screenWidth / 2 -
Math.round(player.getX()) - TILE_SIZE;
offsetX = Math.min(offsetX, 0);
offsetX = Math.max(offsetX, screenWidth - mapWidth);
// get the y offset to draw all sprites and tiles
int offsetY = screenHeight /2 -
Math.round(player.getY()) - TILE_SIZE;
offsetY = Math.min(offsetY,0);
offsetY = Math.max(offsetY, screenHeight - mapHeight);
// draw the visible tiles
int firstTileY = pixelsToTiles(-offsetY);
int lastTileY = firstTileY + pixelsToTiles(screenHeight) +1;
int firstTileX = pixelsToTiles(-offsetX);
int lastTileX = firstTileX +
pixelsToTiles(screenWidth) + 1;
//HERE IS WHERE THE SYSTEM BOGS dOWN (checking ~280 tiles per iteration)
for (int y=firstTileY; y<lastTileY; y++) {
for (int x=firstTileX; x <= lastTileX; x++) {
if(map.getTile(x, y) != null){
Image image = map.getTile(x, y).getImage();
if (image != null) {
g.drawImage(image,
tilesToPixels(x) + offsetX,
tilesToPixels(y) + offsetY,
null);
}
}
}
}
// draw player
g.drawImage(player.getImage(),
Math.round(player.getX()) + offsetX,
Math.round(player.getY()) + offsetY,
null);
The algorithm works correctly selecting the correct FROM and TO values for the X and Y axis culling the needed tiles from 10000 to ~285.
My problem is that even with this the game will only run at around 8-10 FPS while the tiles are being rendered. If I turn off tile rendering than the system runs at 80 FPS (easy to run fast when there is nothing to do)
Do you have any ideas on how to speed up this process? I would like to see something at least around the 30 FPS mark to make this playable.
And finally although I am open to using 3rd party libraries to do this I would like to try and implement this logic myself before admitting defeat.
EDIT:
As requested here is the extra information for how the call for Image image = map.getTile(x, y).getImage(); works.
The map here come from the following TileMap class
TileMap.java
public class TileMap {
private Tile[][] tiles;
private LinkedList sprites;
private Sprite player;
private GraphicsConfiguration gc;
/**
Creates a new TileMap with the specified width and
height (in number of tiles) of the map.
*/
public TileMap(GraphicsConfiguration gc, int width, int height) {
this.gc = gc;
tiles = new Tile[width][height];
overlayer = new Tile[width][height];
sprites = new LinkedList();
}
/**
Gets the width of this TileMap (number of tiles across).
*/
public int getWidth() {
return tiles.length;
}
/**
Gets the height of this TileMap (number of tiles down).
*/
public int getHeight() {
return tiles[0].length;
}
/**
Gets the tile at the specified location. Returns null if
no tile is at the location or if the location is out of
bounds.
*/
public Tile getTile(int x, int y) {
if (x < 0 || x >= getWidth() ||
y < 0 || y >= getHeight())
{
return null;
}
else {
return tiles[x][y];
}
}
/**
* Helper method to set a tile. If blocking is not defined than it is set to false.
*
* #param x
* #param y
* #param tile
*/
public void setTile(int x, int y,Image tile){
this.setTile(x,y,tile,false);
}
/**
Sets the tile at the specified location.
*/
public void setTile(int x, int y, Image tile, boolean blocking) {
if(tiles[x][y] == null){
Tile t = new Tile(gc, tile, blocking);
tiles[x][y] = t;
}
else{
tiles[x][y].addImage(tile);
tiles[x][y].setBlocking(blocking);
}
}
...
With the Tile here being an instance of the following code. Essentially this class just holds the Image which can be updated by adding an overlay layer to it always using gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT); and a boolean to tell if it will block the player. The image that is passed in is also created in this manner.
Tile.java
public class Tile {
private Image image;
private boolean blocking = false;
private GraphicsConfiguration gc;
/**
* Creates a new Tile to be used with a TileMap
* #param image The base image for this Tile
* #param blocking Will this tile allow the user to walk over/through
*/
public Tile(GraphicsConfiguration gc, Image image, boolean blocking){
this.gc = gc;
this.image = image;
this.blocking = blocking;
}
public Tile(GraphicsConfiguration gc, Image image){
this.gc = gc;
this.image = image;
this.blocking = false;
}
/**
Creates a duplicate of this animation. The list of frames
are shared between the two Animations, but each Animation
can be animated independently.
*/
public Object clone() {
return new Tile(gc, image, blocking);
}
/**
* Used to add an overlay to the existing tile
* #param image2 The image to overlay
*/
public void addImage(Image image2){
BufferedImage base = (BufferedImage)image;
BufferedImage overlay = (BufferedImage)image2;
// create the new image, canvas size is the max. of both image sizes
int w = Math.max(base.getWidth(), overlay.getWidth());
int h = Math.max(base.getHeight(), overlay.getHeight());
//BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
BufferedImage combined = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
// paint both images, preserving the alpha channels
Graphics g = combined.getGraphics();
g.drawImage(image, 0, 0, null);
g.drawImage(overlay, 0, 0, null);
this.image = (Image)combined;
}
public boolean isBlocking(){
return this.blocking;
}
public void setBlocking(boolean blocking){
this.blocking = blocking;
}
public Image getImage(){
return this.image;
}
}
I would use a pixel rendering engine (google it ;D)
Basically what you do, it have a giant array of intergers corresponding to the image you are drawing.
Basically you have each tile have an array of intergers representing its pixels.
When you render that tile, you "copy" (it's slightly more complicated than that) the array of tile to the big array :)
Then once you are done rendering everything to the master array, you draw that on the screen.
This way, you are only dealing with integers and not whole pictures everytime you draw something. This makes it a lot faster.
I learned this using MrDeathJockey's (youtube) tutorials and combining them with DesignsbyZephyr's (also youtube). Although I do not recommend using his technique (he only uses 4 colors and 8 bit graphics, as with deathJockey's tutorials you can customize the size of the images and even have multiple sprite sheets with different resolutions (useful for fonts)
I did however use some of the offset stuff (to make the screen move instead of the player) and the InputHandler by Zephyr :)
Hope this helps!
-Camodude009
Create transparent images (not translucent) since translucent images require higher ram and are stored in the system memory instead of the standard java heap. Creating translucent images require several native calls to access the native system memory. Use BufferedImages instead of Image. You can cast BufferedImage to Image at any time.
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().