Drawing fully transparent "white" in Java BufferedImage - java

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.

Related

is there a method in java for drawing a circle with double variables for its center?

here i'm trying to draw a circle using drawOval method and I want to move it on the screen with a specific velocity. but i have a problem with double variables for the velocity.for example when vx=0.25 and vy=0 the circle is just stuck on its place.
sorry for my bad English though.
here is the java code that i'm using
int x=0 , y=0;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
move();
g.drawOval(x, y, 10, 10);
repaint();
}
public void move() {
x+=0.25;
y+=0.25;
}
You should not call move from the paintComponent method! You never know when this method will be called, and thus, you cannot control the movement speed properly.
You should not call repaint from the paintComponent method! Never. This will send the painting system into an endless cycle of repaint operations!
Regarding the question:
There is a method for drawing arbitrary shapes based on double coordinates. This is also covered and explained extensively in the 2D Graphics Tutorial. The key is to use the Shape interface. For your particular example, the relevant part of the code is this:
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
}
That is, you create an Ellipse2D instance, and then just draw it.
Here is an MVCE, showing what you're probably trying to accomplish:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PaintWithDouble
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintWithDoublePanel p = new PaintWithDoublePanel();
f.getContentPane().add(p);
startMoveThread(p);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static void startMoveThread(PaintWithDoublePanel p)
{
Thread t = new Thread(() -> {
while (true)
{
p.move();
p.repaint();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
});
t.setDaemon(true);
t.start();
}
}
class PaintWithDoublePanel extends JPanel
{
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
g.drawString("At " + x + ", " + y, 10, 30);
}
public void move()
{
x += 0.05;
y += 0.05;
}
}
Edited in response to the comment (and to clarify some things that have been said in other answers) :
While it is technically correct to say that there are "only whole pixels", and there "is no pixel with coordinates (0.3, 1.8)", this does not mean that fractional coordinates will not affect the final appearance of the rendered output. Every topic becomes a science when you're studying it long enough. Particularly, a lot of research went into the question of how to improve the visual appearance of rendered output, going beyond what you can achieve with a trivial Bresenham or so. An entry point for further research could be the article about subpixel rendering.
In many cases, as usual, there are trade-offs between the appearance and the drawing performance. As for Java and its 2D drawing capabilities, these trade-offs are mostly controlled via the RenderingHints class. For example, there is the RenderingHints#VALUE_STROKE_PURE that enables subpixel rendering. The effect is shown in this screen capture:
The slider is used to change the y-offset of the rightmost point of a horizontal line by -3 to +3 pixels. In the upper left, you see a line, rendered as-it-is. In the middle, you see the line magnified by a factor of 8, to better show the effect: The pixels are filled with different opacities, depending on how much of the pixel is covered by an idealized, 1 pixel wide line.
While it's certainly the case that this is not relevant for most application cases, it might be worth noting here.
The following is an MCVE that was used for the screen capture:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class PaintWithDoubleMagnified
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
PaintWithDoubleMagnifiedPanel p = new PaintWithDoubleMagnifiedPanel();
f.getContentPane().add(p, BorderLayout.CENTER);
JSlider slider = new JSlider(0, 100, 50);
slider.addChangeListener(e -> {
int value = slider.getValue();
double relative = -0.5 + value / 100.0;
p.setY(relative * 6);
});
f.getContentPane().add(slider, BorderLayout.SOUTH);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class PaintWithDoubleMagnifiedPanel extends JPanel
{
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.drawString("At " + y, 10, 20);
paintLine(g);
BufferedImage image = paintIntoImage();
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.scale(8.0, 8.0);
g.drawImage(image, 0, 0, null);
}
public void setY(double y)
{
this.y = y;
repaint();
}
private void paintLine(Graphics2D g)
{
g.setColor(Color.BLACK);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
Line2D line = new Line2D.Double(
10, 30, 50, 30 + y);
g.draw(line);
}
private BufferedImage paintIntoImage()
{
BufferedImage image = new BufferedImage(
100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
paintLine(g);
g.dispose();
return image;
}
}
First notice that rendering system is using int as arguments for each pixel.
So if p1 is near p2 on x axis then
p1(x,y) and p2(x+1,y)
eg:(0,0) and (1,0)
You do not have something in the middle like (0.5,1) since no pixels.
That why Graphics api is using int for (x,y) coordinates.
Graphics api
If you wanted to consider also double you have to adapt the default coordinates systems to fit your needs.(cannot render all double there in individual pixels, need to group them in categories)
Eg. say want to place x_points : 0, 0.5, 1
So 0->0, 0.5(double)->1(int) , 1->2
Other pixels could map as 0.2->1, 0.7->2 , -0.9->0
One rule map consider all double range (better say in (-0.5,1])
can be -0.5<d<=0 -> 0,0<d<=0.5 -> 1, 0.5<d<=1 -> 2 where d=input_x(double)
That means you adjust the coordinates systems to fit your needs
is there a method in java for drawing a circle with double variables for its center?
NO(using standard Graphics api). Have just what api is provided, but you could render what ever input you wanted (even based on double) by adjusting coordinates system.
class MyPaint extends JPanel
{
private double x = 0, y=0;
private int width = 30, height = 30;
//adjust coordinates system
//for x in [0,1] have [0,0.1,0.2,0.3 ..]
//from no pixel between (0,1) to 9 pixels (0,0.1, ..,1)
//0->0,0.1->1,0.2->2,0.9->9,1->10
//in that way you have full control of rendering
private double scale_x = 0.1;
//same on y as x
private double scale_y = 0.1;
//pixel scaled on x,y
//drawing with
private int xs,ys;
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
xs = (int) (x/scale_x);
ys = (int) (y/scale_y);
g.drawString("Draw At: " + xs + ", " + ys + " From:" + x+","+y, 10, 30);
g.drawOval(xs, ys, (int) (width/scale_x), (int) (height/scale_y));
}
public void move()
{
//adjustments is better to be >= then scale(x or y) seen as absolute value
//if need 0.01 to be display on individual pixel on x
//then modify scale_x = 0.01 (or even 0.001)
x+=0.1;
y+=0.5;
}
}

Generate gradient using only one color

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

How to change the brightness of an Image

My Question: I want to be able to change the brightness of a resource image and have three instances of it as ImageIcons. One at 50% brightness (so darker), another at 75% brightness (a little brighter), and finally another at 100% brightness (the same as the original image). I also want to preserve transparency.
What I've tried: I've searched around and it looks like the best solution is using RescaleOp, but I just can't figure it out. I don't know what the scaleFactor and the offset is all about. Here's my code for what I've tried.
public void initialize(String imageLocation, float regularBrightness, float focusedBrightness, float pressedBrightness, String borderTitle) throws IOException {
BufferedImage bufferedImage = ImageIO.read(ButtonIcon.class.getResource(imageLocation));
setRegularIcon(getAlteredImageIcon(bufferedImage, regularBrightness));
setFocusedIcon(getAlteredImageIcon(bufferedImage, focusedBrightness));
setPressedIcon(getAlteredImageIcon(bufferedImage, pressedBrightness));
setTitle(borderTitle);
init();
}
private ImageIcon getAlteredImageIcon(BufferedImage bufferedImage, float brightness) {
RescaleOp rescaleOp = new RescaleOp(brightness, 0, null);
return new ImageIcon(rescaleOp.filter(bufferedImage, null));
}
The call would be something like this:
seeATemplateButton.initialize("/resources/templateIcon-regular.png", 100f, 75f, 50f, "See A Template");
//I think my 100f, 75f, 50f variables need to change, but whenever I change them it behaves unexpectedly (changes colors and stuff).
What happens with that code: The image appears "invisible" I know it's there because it's on a JLabel with a mouse clicked event on it and that works just fine. If I just skip the brightness changing part and say setRegularIcon(new ImageIcon(Button.class.getResource(imageLocation)); it works just fine, but obviously it's not any darker.
What I think I need: Some help understanding what offset, scaleFactor, and the filter method mean/do, and consequently what numbers to give for the brightness variable.
Any help would be greatly appreciated! Thanks!
The doc says:
The pseudo code for the rescaling operation is as follows:
for each pixel from Source object {
for each band/component of the pixel {
dstElement = (srcElement*scaleFactor) + offset
}
}
It's just a linear transformation on every pixel. The parameters for that transformation are scaleFactor and offset. If you want 100% brightness, this transform must be an identity, i.e. dstElement = srcElement. Setting scaleFactor = 1 and offset = 0 does the trick.
Now suppose you want to make the image darker, at 75% brightness like you say. That amounts to multiplying the pixel values by 0.75. You want: dstElement = 0.75 * srcElement. So setting scaleFactor = 0.75 and offset = 0 should do the trick. The problem with your values is that they go from 0 to 100, you need to use values between 0 and 1.
I would suggest just writing over the image with a semi-transparent black.
Assuming you want to write directly on the image:
Graphics g = img.getGraphics();
float percentage = .5f; // 50% bright - change this (or set dynamically) as you feel fit
int brightness = (int)(256 - 256 * percentage);
g.setColor(new Color(0,0,0,brightness));
g.fillRect(0, 0, img.getWidth(), img.getHeight());
Or if you're just using the image for display purposes, do it in the paintComponent method. Here's an SSCCE:
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ImageBrightener extends JPanel{
BufferedImage img;
float percentage = 0.5f;
public Dimension getPreferredSize(){
return new Dimension(img.getWidth(), img.getHeight());
}
public ImageBrightener(){
try {
img = ImageIO.read(new URL("http://media.giantbomb.com/uploads/0/1176/230441-thehoff_super.jpeg"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
int brightness = (int)(256 - 256 * percentage);
g.setColor(new Color(0,0,0,brightness));
g.fillRect(0, 0, getWidth(), getHeight());
}
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ImageBrightener());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
EDIT
Assuming the same code as above, you can manipulate everything besides the Alpha by messing with the rasterizer. Here's an example (paint shadedImage instead of img if using this exmaple). Please note this doesn't catch edge cases of RGB values greater than 256 and less than 0.
img = ImageIO.read(new URL("http://media.giantbomb.com/uploads/0/1176/230441-thehoff_super.jpeg"));
shadedImage = new BufferedImage(img.getWidth(), img.getWidth(), BufferedImage.TYPE_INT_ARGB);
shadedImage.getGraphics().drawImage(img, 0, 0, this);
WritableRaster wr = shadedImage.getRaster();
int[] pixel = new int[4];
for(int i = 0; i < wr.getWidth(); i++){
for(int j = 0; j < wr.getHeight(); j++){
wr.getPixel(i, j, pixel);
pixel[0] = (int) (pixel[0] * percentage);
pixel[1] = (int) (pixel[1] * percentage);
pixel[2] = (int) (pixel[2] * percentage);
wr.setPixel(i, j, pixel);
}
}
A few more examples for study:
AlphaTest rescales just the alpha transparency of an image between zero and one with no offsets. Coincidentally, it also resamples the image to three-quarter size.
RescaleOpTest does the same using a fixed scale and no offsets.
RescaleTest scales all bands of an image between zero and two with no offsets.
As noted in the API, the scale and offset are applied to each band as the slope and y-intercept, respectively, of a linear function.
dstElement = (srcElement*scaleFactor) + offset
Basic logic is take RGB value of each pixel ,add some factor to it,set it again to resulltant matrix(Buffered Image)
import java.io.*;
import java.awt.Color;
import javax.imageio.ImageIO;
import java.io.*;
import java.awt.image.BufferedImage;
class psp{
public static void main(String a[]){
try{
File input=new File("input.jpg");
File output=new File("output1.jpg");
BufferedImage picture1 = ImageIO.read(input); // original
BufferedImage picture2= new BufferedImage(picture1.getWidth(), picture1.getHeight(),BufferedImage.TYPE_INT_RGB);
int width = picture1.getWidth();
int height = picture1.getHeight();
int factor=50;//chose it according to your need(keep it less than 100)
for (int y = 0; y < height ; y++) {//loops for image matrix
for (int x = 0; x < width ; x++) {
Color c=new Color(picture1.getRGB(x,y));
//adding factor to rgb values
int r=c.getRed()+factor;
int b=c.getBlue()+factor;
int g=c.getGreen()+factor;
if (r >= 256) {
r = 255;
} else if (r < 0) {
r = 0;
}
if (g >= 256) {
g = 255;
} else if (g < 0) {
g = 0;
}
if (b >= 256) {
b = 255;
} else if (b < 0) {
b = 0;
}
picture2.setRGB(x, y,new Color(r,g,b).getRGB());
}
}
ImageIO.write(picture2,"jpg",output);
}catch(Exception e){
System.out.println(e);
}
}}

Cutting part of an image out and keeping the original image

So I have a black image that acts as darkness (In my game). I want to show a small portion around the character only. Like so
The red square is the player.
The green bit is the ground in the game (grass).
The black is the shadow/darkness.
What I want to do is cut a Ellipse/hole out of the blank, black image. I want this Ellipse to be centered around the players (The red square) x and y position.
Currently I am using this to get the effect:
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
//draw mask
g.setColor(Color.black);
if(!nearPlayer(x, y)) {
g.drawLine(x, y, x, y);
}
}
}
But, this processes extremely slow and laggs the players movement drastically.
Is this possible?
..the Player is the red square. Basically what i want to do is cut a circle out of the blank, black image around the coordinates of the player relative to said black image.
What DYM by 'black image' - exactly? To me that just looks like the BG is painted black, which would make more sense for any solid color. In that case, just create the red thing using an Area, fill it, then for the border set a stroke & the color to black, and draw it. This example shows how.
The relevant part of that short code is..
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
I must be bored. This is an animated SSCCE version of the code that drew the image above. It is typically showing >130 FPS. And that is on a clunky machine for which I told the guy my spec. was 'cheap' & reminded him twice that I don't play (heavy rendering) games.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DaisyDisplay {
DaisyDisplay() {
JPanel gui = new JPanel(new BorderLayout(2,2));
final BufferedImage daisy = new BufferedImage(
200,200,BufferedImage.TYPE_INT_RGB);
final JLabel daisyLabel = new JLabel(new ImageIcon(daisy));
gui.add(daisyLabel,BorderLayout.CENTER);
final Daisy daisyPainter = new Daisy();
daisyPainter.setSize(200);
final JLabel fps = new JLabel("FPS: ");
gui.add(fps,BorderLayout.SOUTH);
ActionListener animator = new ActionListener() {
int counter = 0;
long timeLast = 0;
long timeNow = 0;
public void actionPerformed(ActionEvent ae) {
Graphics2D g = daisy.createGraphics();
g.setColor(Color.GREEN.darker());
g.fillRect(0, 0, 200, 200);
daisyPainter.paint(g);
g.dispose();
daisyLabel.repaint();
counter++;
timeNow = System.currentTimeMillis();
if (timeLast<timeNow-1000) {
fps.setText("FPS: " + counter);
counter = 0;
timeLast = timeNow;
}
}
};
Timer timer = new Timer(1,animator);
timer.start();
JOptionPane.showMessageDialog(null, gui);
timer.stop();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DaisyDisplay();
}
});
}
}
class Daisy {
double size = 200;
Point location;
double offset = 0.0;
public void paint(Graphics2D g) {
Area daisyArea = getDaisyShape();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
offset += .02d;
AffineTransform plain = g.getTransform();
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*1/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*3/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset,
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*2/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(plain);
}
public void setLocation(Point location) {
this.location = location;
}
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
public void setSize(double size) {
this.size = size;
}
public Area getDaisyShape() {
int diameter = (int)size*6/20;
Ellipse2D.Double core = new Ellipse2D.Double(
(size-diameter)/2,(size-diameter)/2,diameter,diameter);
int pad = 10;
int petalWidth = 50;
int petalLength = 75;
Area area = new Area(core);
// left petal
area.add(new Area(new Ellipse2D.Double(
pad,(size-petalWidth)/2,petalLength,petalWidth)));
// right petal
area.add(new Area(new Ellipse2D.Double(
(size-petalLength-pad),(size-petalWidth)/2,petalLength,petalWidth)));
// top petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,pad,petalWidth,petalLength)));
// bottom petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,(size-petalLength-pad),petalWidth,petalLength)));
return area;
}
}

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