Using Graphics2D to overlay text on a BufferedImage and return a BufferedImage
The method drawString()
uses x and y for the leftmost character's baseline. Numbers typically have no descenders; if the same is true of text
, a string drawn at position (0,0) will be rendered entirely outside the image. See this example.
Addendum: You may be having trouble with an incompatible color model in your image. One simple expedient is to render the image and then modify it in situ.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @see https://stackoverflow.com/questions/2658663
*/
public class TextOverlay extends JPanel {
private BufferedImage image;
public TextOverlay() {
try {
image = ImageIO.read(new URL(
"http://cdn.sstatic.net/stackexchange/img/logos/so/so-logo.png"));
} catch (IOException e) {
e.printStackTrace();
}
image = process(image);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
private BufferedImage process(BufferedImage old) {
int w = old.getWidth() / 3;
int h = old.getHeight() / 3;
BufferedImage img = new BufferedImage(
w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.drawImage(old, 0, 0, w, h, this);
g2d.setPaint(Color.red);
g2d.setFont(new Font("Serif", Font.BOLD, 20));
String s = "Hello, world!";
FontMetrics fm = g2d.getFontMetrics();
int x = img.getWidth() - fm.stringWidth(s) - 5;
int y = fm.getHeight();
g2d.drawString(s, x, y);
g2d.dispose();
return img;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
private static void create() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TextOverlay());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
create();
}
});
}
}
Graphics2D overlaying image + text problem: text is wrong
Painting graphics is a lot like painting on a real world canvas, you are simply painting over the top of what is already there. If you want to "replace" a portion of the image, you need to paint over the top of it first.
In this case...
You could...
Fill a rectangle with a default color over the area that the text will be painted to.
This is problematic as you need to either know the "maximum" possible area covered or the size of the content that was painted previously.
Alternatively...
You could...
Start with a blank slate on each cycle. That is, start with a copy of the "master" image on each cycle and simply repaint the state into.
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
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.io.IOException;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage master;
private BufferedImage background;
private Timer timer;
private DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss a");
public TestPane() throws IOException {
master = ImageIO.read(getClass().getResource("/images/background.jpg"));
background = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_INT_ARGB);
renderBackground();
}
protected void renderBackground() {
if (master == null) {
return;
}
Graphics2D g2d = background.createGraphics();
g2d.drawImage(master, 0, 0, this);
g2d.setFont(getFont());
FontMetrics fm = g2d.getFontMetrics();
String time = LocalTime.now().format(timeFormatter);
int x = (background.getWidth() - fm.stringWidth(time)) / 2;
int y = ((background.getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(time, x, y);
g2d.dispose();
repaint();
}
@Override
public Dimension getPreferredSize() {
if (master == null) {
return new Dimension(200, 200);
}
return new Dimension(master.getWidth(), master.getHeight());
}
@Override
public void addNotify() {
super.addNotify();
if (timer != null) {
timer.stop();
}
timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
renderBackground();
}
});
timer.start();
}
@Override
public void removeNotify() {
super.removeNotify();
if (timer != null) {
timer.stop();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background == null) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.drawImage(background, x, y, this);
g2d.dispose();
}
}
}
Convert a Graphics2D to an Image or BufferedImage
I do it that way, and works very well:
BufferedImage awtImage = new BufferedImage(drawPanel.getWidth(), drawPanel.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = awtImage.getGraphics();
drawPanel.printAll(g);
try
{
String caminhoImagem = System.getProperty("user.home") + "\\temps\\assinatura.jpg";
FileOutputStream fos = new FileOutputStream(caminhoImagem);
JPEGImageEncoderImpl j = new JPEGImageEncoderImpl(fos);
j.encode(awtImage);
fos.close();
} catch(e) {..... }
That's all :)
Thanks everyone :)
Image Quality Loss When Drawing One BufferedImage to Another Using Graphics2D
The problem is this line from the clearLayers()
method:
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType());
As the GIF uses a palette, the BufferedImage
type will be TYPE_BYTE_INDEXED
. However, if you pass this parameter to the BufferedImage
constructor, it will use a default IndexColorModel
(a built-in, fixed 256 color palette), not the palette from your GIF. Thus, the frames from the GIF will have to be dithered into the destination, as the colors doesn't match.
Instead, use TYPE_INT_RGB/TYPE_INT_ARGB
for type, or use the constructor that also takes an IndexColorModel
parameter and pass the IndexColorModel
from the frames of the GIF.
In code:
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), BufferedImage.TYPE_INT_ARGB);
Alternatively, the following should also work if all frames of the GIF uses the same palette (not necessarily the case):
master = new BufferedImage((int)size.getWidth(), (int)size.getHeight(), frames[0].getType(), (IndexColorModel) frames[0].getColorModel());
However, as the OP reports back the latter option doesn't work for him, the first option is probably safer. :-)
Fitting text into BufferedImage
The major problem is you attempt to translate and the center the text within the BufferedImage
, the calculations are causing a certain amount of drift as the size of the window is changed, meaning that they don't produce an accurate position.
After much stuffing around, I basically threw out your BufferedImage
and setScale
approach and simple derived a new font based on the scale
property.
Now, you could still generate a BufferedImage
, but I would use the resulting FontMetrics
to determine the actual size of the image and simply render the image at the center position of the frame...but that's me
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
class Test101 extends JPanel {
double scale = 0;
String draw = "1";
Test101() {
setPreferredSize(new Dimension(600, 600));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
paintText(g2, 0, 0);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2.setColor(Color.RED);
g2.drawLine(x, 0, x, getHeight());
g2.drawLine(0, y, getWidth(), y);
}
public void paintText(Graphics2D g, int x, int y) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
FontMetrics fm = g.getFontMetrics();
System.out.println(fm.getAscent());
scale = getHeight() / (fm.getHeight());
Font font = g.getFont().deriveFont(Font.PLAIN, AffineTransform.getScaleInstance(scale, scale));
g2d.setFont(font);
g2d.setColor(Color.WHITE);
fm = g.getFontMetrics(font);
int xPos = (getWidth() - fm.stringWidth(draw)) / 2;
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(draw, xPos, yPos);
g2d.dispose();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("test");
frame.add(new Test101());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Related Topics
Difference Between Regex [A-Z] and [A-Za-Z]
What Is @Modelattribute in Spring MVC
Java: How to Start a Standalone Application from the Current One When Both Are in the Same Package
Jformattedtextfield Is Not Properly Cleared
How to Convert a Stack Trace to a String
Struts2 Input Result: How Does It Work? How Are Conversion/Validation Errors Handled
Java Securityexception: Signer Information Does Not Match
Eclipse Exported Runnable Jar Not Showing Images
Is There a Concurrent List in Java's Jdk
Encode Base64 Cannot Find Symbol Error
Swing Grouplayout: Resizing and Limiting Component Sizes
Simple Http Server in Java Using Only Java Se API
Differences Between Oracle Jdk and Openjdk
What's the Difference Between Concurrenthashmap and Collections.Synchronizedmap(Map)
Java 8 Iterable.Foreach() VS Foreach Loop
Nosuchelementexception with Java.Util.Scanner