I have been working on a project that is displaying a grid 16 x 16 of images, based on user interaction this grid follows the user on a dynamically larger base (an example would be a base that is 50 x 50) than the 16 x 16 display.
However, I am using JLabel components to display these images, and every time the user interacts I have to move each of the 256 images and erase the ones that are no longer in the 16 x 16 display grid. This results in a lag that is close to a second per key press and is close to nonfunctional.
What I am looking to try to do is to chain these images together in the total width of the ground and simply move the focus to the portion that is within the 16 x 16 grid, making the process no longer have to use nested for loops for the display.
Is it possible that I could dynamically store and create these chained images for display using a label? If are there other ways to display .png files in Java that could be stored and used in a similar manner?
An example of my current methodology of having to draw every image upon every user interaction:
User user = game.user;
int floorWidth = game.floorWidth;
int floorHeight = game.floorHeight;
int pX = user.getTile().getX();
int pY = user.getTile().getY();
int minX = Math.max(pX - GameConstants.USER_DRAW_DISTANCE, 0);
int maxX = Math.min(floorWidth, pX + GameConstants.USER_DRAW_DISTANCE);
int minY = Math.max(pY - GameConstants.USER_DRAW_DISTANCE, 0);
int maxY = Math.min(floorHeight, pY + GameConstants.USER_DRAW_DISTANCE);
for (int i = minY; i < maxY; i++)
{
for (int x = minX; x < maxX; x++)
{
Tile tile = floor.getTile(x, i);
if (tile.getHasSeen())
{
JLabel cLabel = tile.imageLabel;
cLabel.setLocation(340 + x * 32, 140 + i * 32);
cLabel.setSize(64, 64);
cLabel.setVisible(true);
panel.add(cLabel, 1);
}
}
}
In principle your idea should work. So you're probably doing something else wrong.
I've made an example, where it displays a 16x16 square of JLabels out of 256x256 JLabels. When you move the mouse over the panel, it changes the layout to show a new set of 16x16 JLabels. The change is pretty snappy, definitely not a 1 second delay.
import javax.swing.*;
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.event.*;
import java.util.*;
public class GridViewer{
int x0, y0;
int N = 256;
int display = 16;
int length = 32;
List<JLabel> showing = new ArrayList<>();
List<JLabel> available = new ArrayList<>();
JPanel panel = new JPanel(){
Dimension sz = new Dimension(length*display, length*display);
#Override
public Dimension getPreferredSize(){
return sz;
}
};
public void showGui(){
JFrame frame = new JFrame();
panel.setLayout(null);
panel.addMouseMotionListener( new MouseAdapter(){
Random r = new Random();
#Override
public void mouseMoved(MouseEvent evt){
int x = evt.getX();
int y = evt.getY();
//map to position on the image to the position on the grid.
x0 = x/2;
x0 = Math.min(x0, N-display);
y0 = y/2;
y0 = Math.min(y0, N-display);
updateLayout();
}
});
for(int i = 0; i<N*N; i++){
available.add(createItem(i));
}
updateLayout();
frame.setContentPane(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
/**
* Creates a solid color jlabel, could be used to load an image
* as an icon.
**/
JLabel createItem(int i){
JLabel l = new JLabel("");
int r = (i/256);
int g = (0)&255;
int b = (i%256);
int c = (r << 16 ) + ( g << 8 ) + b;
l.setBackground(new Color(c));
l.setOpaque(true);
l.setSize(length, length);
return l;
}
public void updateLayout(){
for(JLabel l: showing){
panel.remove(l);
}
for(int i = 0; i<display; i++){
for(int j = 0; j<display; j++){
JLabel l = available.get((i + x0) + (j+y0)*N);
panel.add(l);
l.setLocation( i*length, j*length);
showing.add(l);
}
}
}
public static void main(String[] args){
EventQueue.invokeLater( () -> new GridViewer().showGui() );
}
}
Some variations.
Use a GridLayout
Using a layout manager has a lot of advantages. Especially when it comes to using different displays, fonts and platforms? When adding and removing elements, it could make partially showing elements tough.
Use a large JPanel with a ScrollPane
We could create a single JPanel and add all 256x256 components to it, then use a scroll pane to set the view. This would have an advantage of completely separating the layout and the view. Somebody wants a larger window, you don't have to change the layout, the view gets bigger and you just see more of the layout. For 256x256 components, it should perform well but if you have too many components you might want to reconsider it.
Use a JPanel and override paintComponent
This would involve loading your 'png' files as awt Images (probably BufferedImages) and drawing them with the graphics object. You would need to handle all of the layout and rendering. It gives you quite a bit of power over how you want to render your components.
Related
I want to draw a 13x13 tiles board using JFrame.
Here's the code:
public static void drawBoard() {
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.setResizable(false);
JButton button = null;
ImageIcon icon = null;
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setName(alphabet[x] + "" + alphabet[y]);
button.setBounds(TILE_SIZE * y, TILE_SIZE * x, TILE_SIZE, TILE_SIZE);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}//end drawBoard
Now, when I run the code, it shows me a grid of buttons, but the one at the bottom right corner is the same size of the frame.
but the one at the bottom right corner is the same size of the frame.
Swing was designed to be used with layout managers. The layout manager will determine the size/location of a component based on the rules of the layout manager.
In the case of a JFrame, the default layout manager is the BorderLayout. When you add a component to the BorderLayout without using a "constraint" the component is added to the CENTER. However only one component can be added to the CENTER. So only the last component added is give a size/location by the layout manager. In this case the rule is to make the button the size of the space available in the frame.
If you want to have a grid, then you should be using a GridLayout. Then the layout manager will make each button the same size.
Read the section from the Swing tutorial on Layout Managers for more information. There are working examples for both the BorderLayout and the GridLayout.
You should really look into a layout that handles the component size instead of setting it on the component itself (See: MIG Layout), but this should get you were you want to be.
public static void drawBoard(){
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.getContentPane().setLayout(new GridLayout(TILES, TILES));
jFrame.setResizable(false);
JButton button = null;
Dimension dim = new Dimension(TILE_SIZE, TILE_SIZE);
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setSize(dim);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}
So the crux of my problem is plotting multiple components into one JFrame in Java. I'm trying to use the same component twice to plot two different lines, but only one appears. I'm working across three separate classes in separate files, which might be making it more difficult for me. I have tried possible solutions to no avail here, here, here, here, and elsewhere. I suspect I am doing multiple things wrong, as I'm still trying to fully understand JFrame, JPanel, and LayoutManagers. Can anyone show where I went wrong?
My tester class is as follows:
import javax.swing.JFrame;
public class TransportSlabTester
{
public static void main(String[] args)
{
System.out.println("Estimation at 100 sections: ");
TransportSlab slab1 = new TransportSlab(10000,1,5,100);
System.out.println();
JFrame frame = new JFrame("Attenuated Profile");
frame.setSize(600,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TransportSlabGraph component = new TransportSlabGraph();
//analytical is a method from a 3rd class that returns double[]
component.attProfileArray(slab1.analytical(),slab1.getThickness());
frame.add(component);
component = new TransportSlabGraph();
//euler is a method from a 3rd class that returns double[]
component.attProfileArray(slab1.euler(),slab1.getThickness());
frame.add(component);
frame.setVisible(true);
}
}
Now, the class that extends JPanel:
import java.awt.*;
import java.awt.geom.Line2D;
import java.math.*;
import javax.swing.JPanel;
public class TransportSlabGraph extends JPanel
{
double[] N, xAxes, yAxes;
final int edge = 100; //Distance from edge of frame
String[] xlabel = new String[11];
String[] ylabel = new String[11];
/**
*
* #param inputN Data array of type {#code double[]}
* #param thickness Thickness set by the original constructor
*/
public void attProfileArray(double[] inputN, double thickness)
{
N = new double[inputN.length];
//Create labels for the tick marks of the x and y axis from rounded #'s
BigDecimal bd1, bd2;
for (int i = 0; i <= 10; i++)
{
bd1 = new BigDecimal((thickness/10)*i);
MathContext mc = new MathContext(2); //Round to one decimal place
bd2 = bd1.round(mc);
xlabel[i] = String.valueOf(bd2.doubleValue());
ylabel[i] = String.valueOf((inputN[0]*i)/(inputN.length-1));
}
//Set up data array and the axes
for (int i = 0; i < N.length; i++)
{
N[i]=inputN[i];
xAxes = new double[N.length];
yAxes = new double[N.length];
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//Get frame dimensions to scale drawn components
int w = getWidth();
int h = getHeight();
double xInc = (double)(w-2*edge)/(N.length-1);
double scale = (double)(h-2*edge)/N[0];
g2.draw(new Line2D.Double(edge, h-edge, w-edge, h-edge)); //draw x axis
g2.draw(new Line2D.Double(edge, edge, edge, h-edge)); // draw y axis
//Create evenly spaced tick marks for both axes and label them
for (int i = 0; i <= 10; i++)
{
g2.draw(new Line2D.Double(edge+((w-edge-edge)/10.0)*i, h-edge-10, edge+((w-edge-edge)/10.0)*i, h-edge+10)); //x ticks
g2.draw(new Line2D.Double(edge-10, h-edge-((h-edge-edge)/10.0)*i, edge+10, h-edge-((h-edge-edge)/10.0)*i)); //y ticks
g2.drawString(xlabel[i],(int)(edge+((w-edge-edge)/10.0)*i),h-edge+20);
g2.drawString(ylabel[i],edge-30,(int)(h-edge-((h-edge-edge)/10.0)*i));
}
//Scale data and convert to pixel coordinates
for (int i = 0; i < N.length; i++)
{
xAxes[i] = edge+i*xInc;
yAxes[i] = h-edge-scale*N[i];
}
//Only set the data line's color
g2.setPaint(Color.BLUE);
//Draw the data as a series of line segments
for (int i = 1; i < N.length; i++)
{
g2.draw(new Line2D.Double(xAxes[i-1],yAxes[i-1],xAxes[i],yAxes[i]));
}
}
}
Problem #1
An instance of a Component may only reside within a single Container (once).
You will need to create a new instance of each Component you want to add. I would recommend a factory pattern...
Problem #2
JFrame, but default, uses a BorderLayout, which will only allow a single component to reside at each of it's 5 available layout positions.
You will also have problems because your TransportSlabGraph class doesn't override it's getPreferredSize method, which means that, by default, instance of the component will be provided with a default size of 0x0 by many of the layout managers.
Consider changing the layout manager to something like GridLayout to start with.
Take a look at Laying Out Components Within a Container for more details
I was wondering if I could find some help on this problem. I was asked to use an image ("corn.jpg"), and flip it entirely upside down. I know I need to write a program which will switch pixels from the top left corner with the bottom left, and so on, but I wasn't able to get my program to work properly before time ran out. Could anyone provide a few tips or suggestions to solve this problem? I'd like to be able to write my code out myself, so suggestions only please. Please note that my knowledge of APImage and Pixel is very limited. I am programming in Java.
Here is what I managed to get done.
import images.APImage;
import images.Pixel;
public class Test2
{
public static void main(String [] args)
{
APImage image = new APImage("corn.jpg");
int width = image.getImageWidth();
int height = image.getImageHeight();
int middle = height / 2;
//need to switch pixels in bottom half with the pixels in the top half
//top half of image
for(int y = 0; y < middle; y++)
{
for (int x = 0; x < width; x++)
{
//bottom half of image
for (int h = height; h > middle; h++)
{
for(int w = 0; w < width; w++)
{
Pixel bottomHalf = image.getPixel(h, w);
Pixel topHalf = image.getPixel(x, y);
//set bottom half pixels to corresponding top ones?
bottomHalf.setRed(topHalf.getRed());
bottomHalf.setGreen(topHalf.getGreen());
bottomHalf.setBlue(topHalf.getBlue());
//set top half pixels to corresponding bottom ones?
topHalf.setRed(bottomHalf.getRed());
topHalf.setGreen(bottomHalf.getGreen());
topHalf.setBlue(bottomHalf.getBlue());
}
}
}
}
image.draw();
}
}
Thank you for your help!
See Transforming Shapes, Text, and Images.
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class FlipVertical {
public static BufferedImage getFlippedImage(BufferedImage bi) {
BufferedImage flipped = new BufferedImage(
bi.getWidth(),
bi.getHeight(),
bi.getType());
AffineTransform tran = AffineTransform.getTranslateInstance(0, bi.getHeight());
AffineTransform flip = AffineTransform.getScaleInstance(1d, -1d);
tran.concatenate(flip);
Graphics2D g = flipped.createGraphics();
g.setTransform(tran);
g.drawImage(bi, 0, 0, null);
g.dispose();
return flipped;
}
FlipVertical(BufferedImage bi) {
JPanel gui = new JPanel(new GridLayout(1,2,2,2));
gui.add(new JLabel(new ImageIcon(bi)));
gui.add(new JLabel(new ImageIcon(getFlippedImage(bi))));
JOptionPane.showMessageDialog(null, gui);
}
public static void main(String[] args) throws AWTException {
final Robot robot = new Robot();
Runnable r = new Runnable() {
#Override
public void run() {
final BufferedImage bi = robot.createScreenCapture(
new Rectangle(0, 660, 200, 100));
new FlipVertical(bi);
}
};
SwingUtilities.invokeLater(r);
}
}
Whenever you're swapping variables, if your language doesn't allow for simultaneous assignment (and Java doesn't), you need to use a temporary variable.
Consider this:
a = 1;
b = 2;
a = b; // a is now 2, just like b
b = a; // b now uselessly becomes 2 again
Rather than that, do this:
t = a; // t is now 1
a = b; // a is now 2
b = t; // b is now 1
EDIT: And also what #vandale says in comments :P
If you are able to use the Graphics class, the following may be of use:
http://www.javaworld.com/javatips/jw-javatip32.html
And the Graphics class documentation:
http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html
Instead of using
Pixel bottomHalf = image.getPixel(h, w);
Pixel topHalf = image.getPixel(x, y);
//set bottom half pixels to corresponding top ones?
bottomHalf.setRed(topHalf.getRed());
bottomHalf.setGreen(topHalf.getGreen());
bottomHalf.setBlue(topHalf.getBlue());
//set top half pixels to corresponding bottom ones?
topHalf.setRed(bottomHalf.getRed());
topHalf.setGreen(bottomHalf.getGreen());
topHalf.setBlue(bottomHalf.getBlue());
You should have stored the bottomHalf's RGB into a temporary Pixel and used that to set topHalf after replacing bottomHalf's values (if you follow). You could have also really used something like this.... assuming your pixel operates on integer rgb values (which would have improved your main method).
private static final Pixel updateRGB(Pixel in, int red, int green, int blue) {
in.setRed(red); in.setGreen(green); in.setBlue(blue);
}
You want to flip the image upside down, not swap the top and bottom half.
The loop could look like this.
int topRow = 0;
int bottomRow = height-1;
while(topRow < bottomRow) {
for(int x = 0; x < width; x++) {
Pixel t = image.getPixel(x, topRow);
image.setPixel(x, topRow, image.getPixel(x, bottomRow));
image.setPixel(x, bottomRow, t);
}
topRow++;
bottomRow--;
}
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().
When my program starts, the main window places itself where it was when it was last closed. I want to modify this behavior some so if the window is off-screen (or partially off-screen) it moves itself to fully on screen.
I've got this working perfectly. Here's the code:
int x = gameConfig.windowX;
int y = gameConfig.windowY;
int width = gameConfig.windowWidth;
int height = gameConfig.windowHeight;
if( x < 0 ) x = 0;
if( y < 0 ) y = 0;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
if( x + width > screenSize.width ) x = screenSize.width - width;
if( y + height > screenSize.height ) y = screenSize.height - height;
if( width > screenSize.width ) width = screenSize.width;
if( height > screenSize.height ) height = screenSize.height;
this.setLocation(x, y);
this.setSize(width, height );
if( gameConfig.screenMaximized ) {
this.setExtendedState(getExtendedState() | MAXIMIZED_BOTH );
}
This works as expected, but with one big exception; it doesn't account for taskbars. On windows, if the window is past the bottom of the screen, this code will correct it, but it still leaves a piece of the window blocked by the taskbar.
I'm not sure how to do this. Is there someway to ask java about any taskbars in the system, and what their width/height is?
Thanks that worked perfectly.
Do you know how to get it so Java will reflect the total screen size of both of my monitors when I call getScreenSize() ? Right now it is returning 1600x1200, when it's really 3200x1200, spanned across two monitors.
The Java API suggests that GraphicsConfiguration.getBounds() would do the trick, but that still returns the rectangle {0, 0, 1600, 1200}.
Use getScreenInsets (Java 4+):
static public Insets getScreenInsets(Window wnd) {
Insets si;
try {
if(wnd==null) { si=Toolkit.getDefaultToolkit().getScreenInsets(new Frame().getGraphicsConfiguration()); }
else { si=wnd.getToolkit() .getScreenInsets(wnd.getGraphicsConfiguration()); }
} catch(NoSuchMethodError thr) { si=new Insets(0,0,0,0); }
return si;
}
(This method allows for multiple screens, and older JVM's that don't support the API).
And, always remember the task bar may be on any edge of the screen, not just the bottom.
The below code worked for me, even tested by moving the toolbar to the right. But did not try with multiple screens.
public class Scene extends JFrame {
private static final long serialVersionUID = 42L;
public Scene() {
Canvas canvas = new Canvas();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setContentPane(canvas);
this.pack();
this.putFrameInRightCorner();
this.setVisible(true);
}
protected void putFrameInRightCorner(){
Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = this.getSize();
int taskBarWidth = screenSize.width - winSize.width;
int taskBarHeight = screenSize.height - winSize.height;
this.setLocation((screenSize.width - frameSize.width - taskBarWidth),
(screenSize.height - frameSize.height - taskBarHeight));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Scene();
}
});
}
}
By following Pavan Kumar Kundagol Sundara´s post, answered Jan 9 at 14:03, I got this good looking and working perfectly code:
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
Rectangle janelSOdisponivel = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
int janelaSOaltura = janelSOdisponivel.height;
int janelaSOlargura = janelSOdisponivel.width;
And then using the two variables (janelaSOaltura, janelaSOlargura) however I needed.
On the issue of multiple screens, I've not actually done that, but I believe each screen has it's own graphics config. As I understand it, you can enumerate the configurations to find the one you want - then you use the same API's as we've already discussed.
Looks like the doco for GraphicConfiguration has the detail you need:
GraphicsEnvironment ge=GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gs=ge.getScreenDevices();
for(int j = 0; j<gs.length; j++) {
GraphicsDevice gd=gs[j];
GraphicsConfiguration[] gc=gd.getConfigurations();
for (int i=0; i<gc.length; i++) {
...
}
}