I've been having some issues with a ConvolveOp that can be fixed by setting the imageType of the BufferedImage I'm working with to TYPE_INT_ARGB_PRE (see related SO answer here).
Unfortunately I don't fully understand all the implications of selecting this different imageType and I can't seem to find a good reference either, so let me try here:
Which drawing operations are affected by changing the imageType of a BufferedImage from TYPE_INT_ARGB to TYPE_INT_ARGB_PRE? Is it just BufferedImageOps? Or does it affect any of the draw commands on the image's Graphics object or the way the image is rendered if it is drawn onto a different Graphics object?
This basically depends on whether the painting algorithms take into account the information of whether the image is using premultiplied alpha or not.
As already pointed out in the comment: The results will in most cases be the same - at least for the basic drawing operations: Whether you are painting a "non-premultiplied" image into a premultiplied one, or vice versa, will not affect the result, because the differences are handled internally.
A special case are the BufferedImageOps. The JavaDoc comments explicitly say how the alpha channel is treated, and passing in the wrong kind of image can lead to the undesirable results described in the question that you linked to.
It's hard to pin down "the" reason why they decided to implement the BufferedImageOp this way. But a (somewhat vague) statement here is: When operating on (and combining) the pixels of a single source, and these pixels have different alpha values, the treatment of the alpha channel may become fiddly. It's simply not always perfectly obvious what should happen with the alpha channel.
For example, imagine a stack of thee pixels (here, in ARGB, with floating point values):
[1.00, 1.00, 0.00, 0.00] // 100% red, 100% alpha
[0.00, 0.00, 0.00, 0.00] // black, 0% alpha
[0.00, 0.00, 0.00, 0.00] // black, 0% alpha
Now, you want to do a convolution on these pixels (as in the question that you linked to). The kernel could then be
[0.33...]
[0.33...],
[0.33...]
which means that the center pixel of the result should just be the "average" of all pixels (ignoring the borders - roughly as with ConvolveOp#EDGE_ZERO_FILL).
The convolution would then treat all channels equally. For a non-premultiplied image, this would mean that the resulting pixel is a dark red with low opacity:
[0.33, 0.33, 0.00, 0.00]
For the premultiplied image, the components are assumed to be multipled with their alpha value. In this case, the resulting pixel would be fully red, with the same opacity:
[0.33, 1.00, 0.00, 0.00]
Doing the maths behind this is tedious. And in fact, too tedious for me to do it manually - so here is an example:
and the corresponding code
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PremultipliedAlphaTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new PremultipliedAlphaTestPanel());
f.setSize(550,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class PremultipliedAlphaTestPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
BufferedImage imageS = createImage(BufferedImage.TYPE_INT_ARGB);
BufferedImage imageP = createImage(BufferedImage.TYPE_INT_ARGB_PRE);
Kernel kernel = new Kernel(1, 3,
new float[]{ 1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f });
ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_ZERO_FILL, null);
BufferedImage resultS = op.filter(imageS, null);
BufferedImage resultP = op.filter(imageP, null);
g.setColor(Color.BLACK);
g.setFont(new Font("Monospaced", Font.PLAIN, 12));
g.drawString("Straight:", 10, 40);
print(g, 2, 1, imageS.getRGB(0, 0));
print(g, 2, 2, imageS.getRGB(0, 1));
print(g, 2, 3, imageS.getRGB(0, 2));
print(g, 7, 2, resultS.getRGB(0, 1));
g.drawString("Premultiplied:", 10, 240);
print(g, 2, 5, imageP.getRGB(0, 0));
print(g, 2, 6, imageP.getRGB(0, 1));
print(g, 2, 7, imageP.getRGB(0, 2));
print(g, 7, 6, resultP.getRGB(0, 1));
g.scale(50, 50);
g.drawImage(imageS, 1, 1, null);
g.drawImage(resultS, 6, 1, null);
g.drawImage(imageP, 1, 5, null);
g.drawImage(resultP, 6, 5, null);
}
private static void print(Graphics2D g, int px, int py, int argb)
{
g.drawString(stringFor(argb), px*50+5, py*50+25);
}
private static String stringFor(int argb)
{
int a = (argb >> 24) & 0xFF;
int r = (argb >> 16) & 0xFF;
int g = (argb >> 8) & 0xFF;
int b = (argb ) & 0xFF;
float fa = a / 255.0f;
float fr = r / 255.0f;
float fg = g / 255.0f;
float fb = b / 255.0f;
return String.format(Locale.ENGLISH,
"%4.2f %4.2f %4.2f %4.2f", fa, fr, fg, fb);
}
private static BufferedImage createImage(int type)
{
BufferedImage b = new BufferedImage(1,3, type);
Graphics2D g = b.createGraphics();
g.setColor(new Color(1.0f,0.0f,0.0f,1.0f));
g.fillRect(0, 0, 1, 1);
g.setColor(new Color(0.0f,0.0f,0.0f,0.0f));
g.fillRect(0, 1, 1, 1);
g.setColor(new Color(0.0f,0.0f,0.0f,0.0f));
g.fillRect(0, 2, 1, 1);
g.dispose();
return b;
}
}
Related
I have been unable to find an answer to this issue, so hopefully someone can help me out.
I am doing a class assignment where I have to make a scene using graphics. We've only had two lectures covering graphics so far, so I am hardly familiar. The issue is that she methods are not being painted in the preferred order. As you can see in the code to follow, I have two recursive methods making a sky and grass background/foreground. On top of those, I wish to have a cross (as well as many components thereafter granted I can get past this problem), but the cross does not appear as it should despite having that being the last method called within paint.
import java.awt.*;
import javax.swing.*;
public class DrawPicture extends JApplet
{
public final int SIZE = 800;
public final int DIST = 20;
public Color bg1 = new Color(0, 200, 255);
public Color bg2 = new Color(0, 175, 255);
public Color grass1 = new Color(0, 220, 60);
public Color grass2 = new Color(30, 255, 150);
public Color brown = new Color(110, 80, 20);
public void backgroundRec(int x, int y, int c, Graphics g)
{
if (c%2==0)
g.setColor(bg1);
else if (c%2==1)
g.setColor(bg2);
if (c < 40)
{
g.fillOval(x, y, SIZE, SIZE);
backgroundRec(x, y - DIST, c+1, g);
}
}
public void foregroundRec(int x, int y, int c, Graphics g)
{
if (c%2==0)
g.setColor(grass1);
else if (c%2==1)
g.setColor(grass2);
if (x < SIZE/2)
{
g.fillRect(x, y, SIZE, DIST);
foregroundRec(x, y + DIST, c+1, g);
}
}
public void cross(int x, int y, Graphics g)
{
g.setColor(brown);
g.fillRect(SIZE/2, SIZE/2, 45, 275);
g.fillRect(SIZE/2-50, SIZE/2+45, 150, 45);
}
public void paint(Graphics g)
{
//backgroundRec(0, 0, 0, g);
//foregroundRec(0, 400, 0, g);
cross(SIZE/2, SIZE/2, g);
}
}
One of the problems is, you have a StackOverflowException in foregroundRec, because nothing is seems to be stopping it from updating, probably because you're not changing the value of x.
I "think" foregroundRec(x, y + DIST, c + 1, g); should be foregroundRec(x + DIST, y + DIST, c + 1, g); or something :P - but based on your code, x has to change
You should also be calling super.paint before you do any of your own painting, this ensures that you don't end up with any weird paint artifacts
The other problem you're having is the fact that you're relying on the SIZE property, instead, you should be using getWidth or getHeight to determine what the actual size of the applet is
I'd also suggest having a look at:
Java Plugin support deprecated and Moving to a Plugin-Free Web
Painting in AWT and Swing
Performing Custom Painting
2D Graphics
Updated
Okay, after going through a few iterations, what I actually think you're trying to do is...
if (y < SIZE) {
g.fillRect(x, y, SIZE, DIST);
foregroundRec(x, y + DIST, c + 1, g);
}
The following code is the code that I am using to rotate two rectangles is below
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setColor(Color.WHITE);
//r1
Rectangle2D r1 = new Rectangle2D.Double(0, 0, 50, 4);
g2d.rotate(Math.toRadians(45));
g2d.fill(r1);
//r3
Rectangle2D r3 = new Rectangle2D.Double(0, 25, 50, 4);
g2d.rotate(Math.toRadians(-90));
g2d.fill(r3);
This create something which looks like this
Whereas I am trying to create something which looks like this
This occurs since when the rectangles are rotated, they are both rotated around the point 0,0. To fix that I tried using rotate(double theta, double x, double y). However I am having trouble using that correctly. For example when I have tried
Rectangle2D r3 = new Rectangle2D.Double(0, 25, 50, 4);
g2d.rotate(Math.toRadians(-90), 25, 25);
or
Rectangle2D r3 = new Rectangle2D.Double(0, 25, 50, 4);
g2d.rotate(Math.toRadians(-90), 0, 25);
I get similar undesired results when both the rectangles were being rotated around the point 0,0. I would appreciate any help if fixing my problem.
If you are wondering why I have done it like this, it is because I am hoping to make a effect similar to when you click on the 3 parallel lines seen here by the time I finish coding the graphic
package test;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.swing.*;
public class Cross extends JPanel {
private Rectangle2D rectangle;
Cross() {
rectangle = new Rectangle2D.Double(0, 0, 50, 4);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setColor(Color.white);
AffineTransform at = g2.getTransform();
g2.translate(5, 5);
g2.rotate(Math.toRadians(45));
g2.fill(rectangle);
g2.setTransform(at);
g2.translate(5, 5 + Math.sqrt(2) * 25);
g2.rotate(Math.toRadians(-45));
g2.fill(rectangle);
g2.setTransform(at);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Cross");
frame.add(new Cross());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(128, 128);
frame.setVisible(true);
}
}
Although I think I might have done mistake somewhere with maths (it looks somewhat odd), this should give you an idea.
So it turns out that this can be done with some relatively simple maths. As the shape I am trying to make is a perfect X.
To work out the position for the rectangle we can use Pythagorean theorem.
The image above shows two steps.
Translation [2√2, 0] from the point [0, 0]
Rotate 45deg from the point [2√2, 0]
Next we need to work out the minimum point of this rectangle. Again we can use Pythagorean theorem.
This tells us where the top point of the second rectangle will be
Difference in height: 4 - 2√2
Bottom of line when straight: [0, 27√2 + (4 - 2√2)] = [0, 4 + 25√2]
Top of line when straight: [0, 25√2]
Finally we can put in the last rectangle starting at [0, 0]
Translation [0, 25√2] from the point [0, 0]
Rotate -45deg from the point [0, 25√2]
Now that the theory is out of the way, what does this look like in code? It looks similar to the code below
//Values
final static double[] r1Points = {2.828427125, 0}; //Equivilant 2√2
final static double[] r3Points = {0, 35.35533906}; //Equivilant 25√2
final static int[] widthNHeight = {50, 4}; //Width then height
final static double angle = 45.0; //Angle to rotate lines
//Declaring the rectangles
Rectangle2D r1 = new Rectangle2D.Double(r1Points[0], r1Points[1], widthNHeight[0], widthNHeight[1]);
Rectangle2D r3 = new Rectangle2D.Double(r3Points[0], r3Points[1], widthNHeight[0], widthNHeight[1]);
//r1
g2d.rotate(Math.toRadians(angle), r1Points[0], r1Points[1]); //Rotates graphic for first rectangle
g2d.fill(r1);
//r3
g2d.rotate(Math.toRadians(-angle), r1Points[0], r1Points[1]); //Rotates the graphic back to straight
g2d.rotate(Math.toRadians(-angle), r3Points[0], r3Points[1]); //Rotates graphic for second rectangle
g2d.fill(r3);
This might sound like a bit of strange title, but bear with me, there is a reason:
I am trying to generate a white glow around a text on a gray background.
To generate the glow, I created a new BufferedImage that's bigger than the text, then I drew the text in white onto the canvas of the image and ran a Gaussian Blur over the image via a ConvolveOp, hoping for something like this:
At first I was a bit surprised when the glow turned out darker than the gray background of the text:
But after a bit of thinking, I understood the problem:
The convolution operates on each color channel (R, G, B, and A) independently to calculate the blurred image. The transparent background of the picture has color value 0x00000000, i.e. a fully transparent black! So, when the convolution filter runs over the image, it not only blends the alpha value, but also mixes the black into the RGB values of the white pixels. This is why the glow comes out dark.
To fix this, I need to initialize the image to 0x00FFFFFF, i.e. a fully transparent white instead, but if I just set that color and fill a rectangle with it, it simply does nothing as Java says "well, it's a fully transparent rectangle that you're drawing! That's not going to change the image... Let me optimize that away for you... Done... You're welcome.".
If I instead set the color to 0x01FFFFFF, i.e. an almost fully transparent white, it does draw the rectangle and the glow looks beautiful, except I end up with a very faint white box around it...
Is there a way I can initialize the image to 0x00FFFFFF everywhere?
UPDATE:
I found one way, but it's probably as non-optimal as you can get:
I draw an opaque white rectangle onto the image and then I run a RescaleOp over the image that sets all alpha values to 0. This works, but it's probably a terrible approach as far as performance goes.
Can I do better somehow?
PS: I'm also open to entirely different suggestions for creating such a glow effect
The main reason why the glow appeared darker with your initial approach is most likely that you did not use an image with a premultiplied alpha component. The JavaDoc of ConvolveOp contains some information about how the alpha component is treated during a convolution.
You could work around this with an "almost fully transparent white". But alternatively, you may simply use an image with premultiplied alpha, i.e. one with the type TYPE_INT_ARGB_PRE.
Here is a MCVE that draws a panel with some text, and some pulsing glow around the text (remove the timer and set a fixed radius to remove the pulse - I couldn't resist playing around a little here ...).
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TextGlowTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new TextGlowPanel());
f.setSize(300,200);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class TextGlowPanel extends JPanel
{
private BufferedImage image;
private int radius = 1;
TextGlowPanel()
{
Timer t = new Timer(50, new ActionListener()
{
long startMillis = -1;
#Override
public void actionPerformed(ActionEvent e)
{
if (startMillis == -1)
{
startMillis = System.currentTimeMillis();
}
long d = System.currentTimeMillis() - startMillis;
double s = d / 1000.0;
radius = (int)(1 + 15 * (Math.sin(s * 3) * 0.5 + 0.5));
repaint();
}
});
t.start();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
gr.setColor(Color.GRAY);
int w = getWidth();
int h = getHeight();
gr.fillRect(0, 0, w, h);
if (image == null || image.getWidth() != w || image.getHeight() != h)
{
// Must be prmultiplied!
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
}
Graphics2D g = image.createGraphics();
Font font = g.getFont().deriveFont(70.0f).deriveFont(Font.BOLD);
g.setFont(font);
g.setComposite(AlphaComposite.Src);
g.setColor(new Color(255,255,255,0));
g.fillRect(0,0,w,h);
g.setComposite(AlphaComposite.SrcOver);
g.setColor(new Color(255,255,255,0));
g.fillRect(0,0,w,h);
g.setColor(Color.WHITE);
g.drawString("Glow!", 50, 100);
image = getGaussianBlurFilter(radius, true).filter(image, null);
image = getGaussianBlurFilter(radius, false).filter(image, null);
g.dispose();
g = image.createGraphics();
g.setFont(font);
g.setColor(Color.BLUE);
g.drawString("Glow!", 50, 100);
g.dispose();
gr.drawImage(image, 0, 0, null);
}
// From
// http://www.java2s.com/Code/Java/Advanced-Graphics/GaussianBlurDemo.htm
public static ConvolveOp getGaussianBlurFilter(
int radius, boolean horizontal)
{
if (radius < 1)
{
throw new IllegalArgumentException("Radius must be >= 1");
}
int size = radius * 2 + 1;
float[] data = new float[size];
float sigma = radius / 3.0f;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
for (int i = -radius; i <= radius; i++)
{
float distance = i * i;
int index = i + radius;
data[index] =
(float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
total += data[index];
}
for (int i = 0; i < data.length; i++)
{
data[i] /= total;
}
Kernel kernel = null;
if (horizontal)
{
kernel = new Kernel(size, 1, data);
}
else
{
kernel = new Kernel(1, size, data);
}
return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}
}
I've found that clearRect should paint a transparent color.
g.setBackground(new Color(0x00FFFFFF, true));
g.clearRect(0, 0, img.getWidth(), img.getHeight());
You should also be able to force the BufferedImage to fill with a transparent color by setting the pixel data directly.
public static void forceFill(BufferedImage img, int rgb) {
for(int x = 0; x < img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
img.setRGB(x, y, rgb);
}
}
}
It is not clearly documented but I tested it and setRGB appears to accept an ARGB value.
I have one color, for example 0xFF0000. I want to create simple gradient using only this color as start-point information. The second point will be lighter color the first color.
For example if I have value - 0xFF0000 and I want to get 0xCC0000 from it. Then I can draw simple gradient.
so second color should be, for example 10 or 20% lighter then first
Hard code values are not acceptable. User will select the color from the color wheel and application should automatically generate the second color to draw simple gradient.
Is there any algorithm or way to to implement this?
Probably algorithm will count what higher: R or G or B and then parallel decrease other components, or something... I'm not sure how this works.
P.S.: I'm using android SDK
I think the best is converting from the RGB to the HSV space:
public int enlight(int color, float amount) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] = Math.min(1.0f, amount * hsv[2]);
return Color.HSVToColor(hsv);
}
then you can enlight a color simply by incrementing the v (value) component, and then converting back to RGB if needed with the very same Android API Color
I made a simple example to show you a possible way, which makes use of the getRGBColorComponents() method of the Color class.
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test extends JFrame {
public Test(Color c1, Color c2) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(200, 200);
setVisible(true);
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
panel.add(new JLabel(createColorIcon(c1)));
panel.add(new JLabel(createColorIcon(c2)));
add(panel);
}
public ImageIcon createColorIcon(Color c) {
int w = 44, h = 20;
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
if (c != null) {
g.setColor(c);
g.fillRect(0, 0, w, h);
} else {
g.setColor(Color.GRAY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawLine(1, 1, w - 2, h - 2);
g.drawLine(1, h - 2, w - 2, 1);
}
g.setColor(Color.GRAY);
g.drawRect(0, 0, w - 1, h - 1);
g.dispose();
return new ImageIcon(bi);
}
public static void main(String[] args) {
Color c1 = Color.decode("0xFF0000");
float[] f = c1.getRGBColorComponents(null);
System.out.println(f.length);
for (int i = 0; i < f.length; i++) {
System.out.println(f[i]);
}
f[0] = f[0] * 0.8f;
Color c2 = new Color(f[0],f[1],f[2]);
new Test(c1,c2);
}
}
You just call the getRGBColorComponents()method and you get an array with the color values for red, green and blue as float values in the range from 0.0 to 1.0, where a int value of 255 (or 0xFF) corresponds to a 1.0 float value. You just have to multply it with a factor chosen by you, as I made it at the end of the example.
Seems to be something like this:
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int secondColor = 0;
int lighting = 51; // from 0 to 255. The larger the number is the brighter second color
if (red > green && red > blue)
secondColor = Color.rgb(red, green - lighting, blue - lighting);
else if (green > red && green > blue)
secondColor = Color.rgb(red - lighting, green, blue - lighting);
else if (blue > red && blue > green)
secondColor = Color.rgb(red - lighting, green - lighting, blue);
I should test it for all colors...
Update:
it works, but you need to put some other conditions to check if green and blue equals or red and blue and so..
Please fallow #Raffaele answer
I posted a question in sun java forums sometime ago and i am finding it hard to understand the first response i received from the replier though it seems he gave me the correct approach to my problem. The link to the question is:
http://forums.sun.com/thread.jspa?threadID=5436562&tstart=0
Someone replied that i should use BufferedImage and make tiles. I don't really understand what the tiles mean in connection with the BufferedImage.
I would like someone to explain to me what the tiles are and how they are created in the BufferedImage.
I have searched the web for a while but couldn't find any link that can help me understanding the basics of the tiles and creating the tiles. Any pointer to a site is also appreciated.
I need help in understanding the tiles in connection with the BufferedImage and also how they are created.
A "tile" in a 2D game simply means an "image smaller than whole screen that you can reuse several times to create the background".
Here's a a working example where four tiles are created (adding some random noise to every pixel). Each tile is 50x50 pixels.
Then there's a "map" (that you call a "grid" in your case) representing which tiles you want to put where.
From that map, a bigger BufferedImage is created (note that it's just an example, in a real program you'll want to use a BufferedImage copy, not a pixel-by-pixel copy).
The map is 9x7, each tile is 50x50 pixels, hence the resulting image is 9*50 x 7*50 (ie 450 by 350).
Note that the following is really just a simple example, as short as possible, showing how to create a bigger BufferedImage using several tiles: the goal is not to give a tutorial on best Swing usage nor on how to squeeze every bit of performances out of BufferedImages, etc.
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
public class ToyTiled extends JFrame {
private static final int IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB;
private BufferedImage img;
public static void main( String[] args ) {
new ToyTiled();
}
public ToyTiled() {
super();
this.add(new JPanel() {
#Override
protected void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, null);
}
});
img = new BufferedImage( 450, 350, IMAGE_TYPE ); // here you should create a compatible BufferedImage
this.setSize(img.getWidth(), img.getHeight());
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
final int NB_TILES = 4;
BufferedImage[] tiles = new BufferedImage[NB_TILES];
tiles[0] = createOneTile( new Color( 255, 255, 255 ) );
tiles[1] = createOneTile( new Color( 255, 0, 255 ) );
tiles[2] = createOneTile( new Color( 0, 0, 255 ) );
tiles[3] = createOneTile( new Color( 0, 255, 255 ) );
final int[][] map = new int[][] {
{ 1, 0, 2, 3, 0, 1, 2, 2, 2 },
{ 0, 2, 3, 0, 1, 2, 2, 2, 3 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 2 },
{ 2, 1, 0, 1, 2, 3, 2, 0, 0 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 3 },
{ 1, 0, 2, 2, 1, 1, 2, 2, 3 },
{ 1, 0, 2, 3, 0, 1, 2, 2, 3 },
};
for (int i = 0; i < map[0].length; i++) {
for (int j = 0; j < map.length; j++) {
final BufferedImage tile = tiles[map[j][i]];
for (int x = 0; x < tile.getWidth(); x++) {
for (int y = 0; y < tile.getHeight(); y++) {
img.setRGB( x + i * 50, y + j * 50, tile.getRGB(x,y) );
}
}
}
}
this.setVisible( true );
}
private BufferedImage createOneTile( final Color c ) {
final Random r = new Random();
final BufferedImage res = new BufferedImage( 50, 50, IMAGE_TYPE );
for (int x = 0; x < res.getWidth(); x++) {
for (int y = 0; y < res.getHeight(); y++) {
res.setRGB( x, y, c.getRGB() - r.nextInt(150) );
}
}
return res;
}
}
If you want to rotate a portion of a BufferedImage you might find these classes/methods useful:
AffineTransform.getRotateInstance or AffineTransform.getQuadrantRotateInstance
AffineTransformOp
BufferedImage.getSubImage()
BufferedImage.setData
Example:
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;
public class TileTest extends JFrame {
public static void main(String[] args) throws IOException {
URL logo = new URL("http://sstatic.net/so/img/logo.png");
TileTest tileTest = new TileTest(ImageIO.read(logo));
tileTest.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
tileTest.setVisible(true);
}
private TileTest(BufferedImage image) throws IOException {
this.image = image;
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
JLabel label = new JLabel(new ImageIcon(image));
add(label);
BufferedImage tile = image.getSubimage(0, 0, 61, 61);
add(new JButton(new RotateAction(tile, label)));
pack();
}
private BufferedImage image;
}
class RotateAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
BufferedImage tmpImage = op.filter(image, null);
image.setData(tmpImage.getRaster());
component.repaint();
}
RotateAction(BufferedImage image, Component component) {
super("Rotate");
this.component = component;
this.image = image;
double x = 0.5 * image.getWidth();
double y = 0.5 * image.getHeight();
AffineTransform xfrm =
AffineTransform.getQuadrantRotateInstance(1, x, y);
op = new AffineTransformOp(
xfrm, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
}
private final Component component;
private final BufferedImage image;
private final BufferedImageOp op;
}