I'm using g.drawString(str, x, y) to draw a String with a Graphics2D object g. The current font of g does not cover all the characters of str (I have e.g. Chinese chars in there). On Mac OS X, a fallback font seems to be automatically used, but not on Windows, where black square outlines appear instead of the wanted characters.
Why is the behavior different depending on the platform?
How do I specify a fallback font (or several fallback fonts) in case of missing characters?
(For instance, one of the nice fonts there.)
Update/More Info
So, the original font that doesn't support all characters is not one of the JVM's logical fonts, but is a bundled font that comes with my app and was obtained with Font.createFont(). So, adding fonts to the JRE's lib/fonts/fallback folder doesn't work here.
We could attribute string to switch font on "bad" symbols and use Graphics2D.drawString(AttributedCharacterIterator iterator, int x, int y) to render result. Advantage: this will work with any font. Drawback: without some sort of caching of intermediate objects this will work slower and dirtier.
So, i suggest using AttributedString with main font attribute on whole string:
AttributedString astr = new AttributedString(text);
astr.addAttribute(TextAttribute.FONT, mainFont, 0, textLength);
and with fallback font on specific parts:
astr.addAttribute(TextAttribute.FONT, fallbackFont, fallbackBegin, fallbackEnd);
The rendering itself:
g2d.drawString(astr.getIterator(), 20, 30);
The result (Physical "Segoe Print" as main font, logical "Serif" as fallback):
Complete supposed-to-be-SSCCE code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.TextAttribute;
import java.text.AttributedString;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class FontTest extends JFrame {
public FontTest() {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(new TestStringComponent());
pack();
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FontTest().setVisible(true);
}
});
}
}
class TestStringComponent extends JComponent {
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(getForeground());
Font mainFont = new Font("Segoe Print", Font.PLAIN, 25);
Font fallbackFont = new Font("Serif", Font.PLAIN, 25);
String s = "Test 漢鼎繁古印 Test 漢鼎繁古印 Test";
g2d.drawString(createFallbackString(s, mainFont, fallbackFont).getIterator(), 20, 30);
}
public Dimension getPreferredSize() {
return new Dimension(500, 40);
}
private AttributedString createFallbackString(String text, Font mainFont, Font fallbackFont) {
AttributedString result = new AttributedString(text);
int textLength = text.length();
result.addAttribute(TextAttribute.FONT, mainFont, 0, textLength);
boolean fallback = false;
int fallbackBegin = 0;
for (int i = 0; i < text.length(); i++) {
boolean curFallback = !mainFont.canDisplay(text.charAt(i));
if (curFallback != fallback) {
fallback = curFallback;
if (fallback) {
fallbackBegin = i;
} else {
result.addAttribute(TextAttribute.FONT, fallbackFont, fallbackBegin, i);
}
}
}
return result;
}
}
Related
I've been trying to code a program or 'game' which transfers to new scenarios depending on whatever the user chooses to do. The user will be given an option to choose 'A' or 'B'. In order to do that, i need to display the information that the user needs, but I cannot remove the past dialogue, which leads to overlapping go the text and makes it impossible to read.
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
public class Window extends JFrame{
private static final long serialVersionUID = 1L;
public static String name, choice;
public static BufferedImage wizard;
public static int width = 640;
public static int height = 400;
static String write =
"You will be entering a new world now!\nOne where your decision may either save you or cost you your life.\nYou will be given two options to choose from.\nPlease enter the letters 'A' or 'B' and if you wish to leave, '!'.\npress 'ENTER' to begin!";
public Window() { //first method that gets called from other class
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Yasaman's game");
setSize(width, height);
setResizable(true);
setLocation(0,0);
setVisible(true);
setLayout(new FlowLayout());
setAlwaysOnTop(true);
readWizard();
name = JOptionPane.showInputDialog("What is your name");
repaint();
}
private void readWizard() { //read wizard from file
try {
wizard = ImageIO.read(new File("magic.png"));
} catch (IOException e) {
System.out.println("Somebody ate my Wizard!");
}
}
public void paint(Graphics g) //this is the god method, everything that you want to put onto the screen goes in here
{
int sBx = 100;
int sBy = 100;
int sBwid = 400;
int sBhei = 200;
int sBcurve = 50;
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(0, 255, 155));
if(wizard != null) {
g2.drawImage(wizard,0,0, this);
}else {
System.out.println("Wizard was eaten alive, have a box");
}
g2.fillRoundRect(sBx, sBy, sBwid, sBhei, sBcurve, sBcurve);
g2.fillRect(100, 100, 100, 100);
g2.setColor(new Color(0,0,0));
//THIS IS WHERE THE PROBLEM BEGINS
g2.setBackground(new Color(255, 255, 255));
int y = 170;
if (write!= null)
for(String line: write.split("\n"))
g2.drawString(line, 130, y+= g.getFontMetrics().getHeight());
//g2.setBackground(new Color(255, 255, 255));
senario1();
y = 170;
if (write!= null)
for(String line: write.split("\n"))
g2.drawString(line, 130, y+= g.getFontMetrics().getHeight());
choice = JOptionPane.showInputDialog("Would you A. Stay Home , B. Go to school ?");
if (choice.equalsIgnoreCase("B")){
//g2.setBackground(new Color(255, 255, 255));
senario2();
y = 170;
if (write!= null)
for(String line: write.split("\n"))
g2.drawString(line, 130, y+= g.getFontMetrics().getHeight());
}
}
public static void senario1(){
write = "Today is the day.\nThe day you can finally use your magic to hustle through life.\nBefore you use your powers, you must learn how to use them!\nLet's get started!\n";
}
public static void senario2(){
write = "You are so lazy! It's okay though if you had went to school\n, due to many unfortunate events your life would have ended... \n\nOr maybe not...";
}
}
How would I be able to remove the past text to display the new ones. Also the fisr display and the second display merge together for some reason.
Thank you and please help me I really appriciate it!
First of all the code inside the paint function was a mess. that function is meant to be used for drawing porpuses, not for lots of if...else statements (because each time you resize the frame, the paint method is being called and can cause your app to be very laggy). Anyway, I've created a separated class so everytime you hit "enter" writes value changes:
class adapter extends KeyAdapter{
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
Draw.write = nextScenario();
repaint();
}
}
}
Then I've added a FocusListener to the Window constructor so you can hit enter while you're doing other stuff and the app doesn't do something "strange":
public Draw() { //first method that gets called from other class
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Yasaman's game");
write =
"You will be entering a new world now!\nOne where your decision may either save you or cost you your life.\nYou will be given two options to choose from.\nPlease enter the letters 'A' or 'B' and if you wish to leave, '!'.\npress 'ENTER' to begin!";
setSize(width, height);
setResizable(true);
adapter adapter = new adapter();
addFocusListener(new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
addKeyListener(adapter);
}
#Override
public void focusLost(FocusEvent e) {
removeKeyListener(adapter);
}
});
setLocation(0,0);
setVisible(true);
readWizard();
name = JOptionPane.showInputDialog("What is your name?");
repaint();
}
Then, I've created a "nextscenario" function for switching between scenarios in an easy way. private static int scenario = 0;and the method which is being called from the KeyListener class:
private static String nextScenario(){
switch(scenario) {
case 0:
scenario++; return senario1();
case 1:
scenario++; return senario2();
default:
return null;
}
}
Finally the paint function became this "short" for being more efficent:
public void paint(Graphics g) //this is the god method, everything that you want to put onto the screen goes in here
{
int sBx = 100;
int sBy = 100;
int sBwid = 400;
int sBhei = 200;
int sBcurve = 50;
int y = 170;
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(new Color(0, 255, 155));
if (wizard != null) {
g2.drawImage(wizard, 0, 0, this);
} else {
System.out.println("Wizard was eaten alive, have a box");
}
g2.fillRoundRect(sBx, sBy, sBwid, sBhei, sBcurve, sBcurve);
g2.fillRect(100, 100, 100, 100);
g2.setColor(new Color(0, 0, 0));
g2.setBackground(new Color(255, 255, 255));
for (String line : write.split("\n"))
g2.drawString(line, 130, y += g.getFontMetrics().getHeight());
}
If there's any problem let me know and I'll solve it to you. Mark as answer if it solved your problem! :D
There are many (many) questions about computing the size (width or height) of a string that should be painted into a Swing component. And there are many proposed solutions. However, I noticed that most of these solutions do not work properly for small fonts.
The following is an MCVE that shows some of the approaches:
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.function.BiFunction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TextBoundsTest
{
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);
Font baseFont = new Font("Sans Serif", Font.PLAIN, 10);
Font smallFont0 = baseFont.deriveFont(0.5f);
Font smallFont1 = baseFont.deriveFont(0.4f);
f.getContentPane().setLayout(new GridLayout(5,2));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontMetrics,
"FontMetrics"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontMetrics,
"FontMetrics"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
"Font+FontRenderContext"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithFontAndFontRenderContext,
"Font+FontRenderContext"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
"GlyphVectorLogicalBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorLogicalBounds,
"GlyphVectorLogicalBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
"GlyphVectorVisualBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithGlyphVectorVisualBounds,
"GlyphVectorVisualBounds"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont0,
TextBoundsTest::computeBoundsWithTextLayout,
"TextLayout"));
f.getContentPane().add(
new TextBoundsTestPanel(smallFont1,
TextBoundsTest::computeBoundsWithTextLayout,
"TextLayout"));
f.setSize(600,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static Rectangle2D computeBoundsWithFontMetrics(
String string, Graphics2D g)
{
FontMetrics fontMetrics = g.getFontMetrics();
Rectangle2D bounds = fontMetrics.getStringBounds(string, g);
return bounds;
}
private static Rectangle2D computeBoundsWithFontAndFontRenderContext(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext =
new FontRenderContext(g.getTransform(),true, true);
Font font = g.getFont();
Rectangle2D bounds = font.getStringBounds(string, fontRenderContext);
return bounds;
}
private static Rectangle2D computeBoundsWithGlyphVectorLogicalBounds(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
GlyphVector glyphVector = font.createGlyphVector(
fontRenderContext, string);
return glyphVector.getLogicalBounds();
}
private static Rectangle2D computeBoundsWithGlyphVectorVisualBounds(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
GlyphVector glyphVector = font.createGlyphVector(
fontRenderContext, string);
return glyphVector.getVisualBounds();
}
private static Rectangle2D computeBoundsWithTextLayout(
String string, Graphics2D g)
{
FontRenderContext fontRenderContext = g.getFontRenderContext();
Font font = g.getFont();
TextLayout textLayout = new TextLayout(string, font, fontRenderContext);
return textLayout.getBounds();
}
}
class TextBoundsTestPanel extends JPanel
{
private final Font textFont;
private final BiFunction<String, Graphics2D, Rectangle2D> boundsComputer;
private final String boundsComputerName;
TextBoundsTestPanel(Font textFont,
BiFunction<String, Graphics2D, Rectangle2D> boundsComputer,
String boundsComputerName)
{
this.textFont = textFont;
this.boundsComputer = boundsComputer;
this.boundsComputerName = boundsComputerName;
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
g.drawString("Font size: "+textFont.getSize2D(), 10, 20);
g.drawString("Bounds : "+boundsComputerName, 10, 40);
AffineTransform oldAt = g.getTransform();
AffineTransform at = AffineTransform.getScaleInstance(50, 50);
g.transform(at);
g.translate(1, 2);
g.setFont(textFont);
String string = "Test";
g.drawString(string, 0, 0);
Rectangle2D bounds = boundsComputer.apply(string, g);
Shape boundsShape = at.createTransformedShape(bounds);
g.setTransform(oldAt);
g.setColor(Color.RED);
g.translate(50, 100);
g.draw(boundsShape);
}
}
The result of this program is shown in this screenshot:
As one can see, the simple methods work nicely for a font with size 0.5, but suddenly bail out and return bounds with a height of 0.0 for the font with size 0.4.
(Side note: I wonder whether this is simply a bug - although it might be caused by some internal round-off-errors, as it happens exactly between font sizes of 0.5 and 0.49 ...)
The only solutions that work for these smaller fonts are the computation using a GlyphVector, or the TextLayout. But both of these approaches are tremendously expensive, as they require the creation of the Shape of the string and lots of auxiliary objects. Furthermore, they only return the visual bounds (that is, the bounds of the actual shape), and not the logical bounds of the text.
Is there any efficient solution for computing the logical bounds of strings in small fonts?
You can normalize the font first. Measure that then scale the dimensions of the rectangle by the true size2D of the font.
private static Rectangle2D computeBoundsUsingNormalizedFont(
String string, Graphics2D g) {
Font normalizedFont = g.getFont().deriveFont(1f);
Rectangle2D bounds = normalizedFont.getStringBounds(string, g.getFontRenderContext());
float scale = g.getFont().getSize2D();
return new Rectangle2D.Double(bounds.getX() * scale,
bounds.getY() * scale,
bounds.getWidth() * scale,
bounds.getHeight() * scale);
}
Then obviously you can cache the normalized font and hide this work around inside a calculator class, something like this:
TextBoundsCalculator textBoundsCalculator = TextBoundsCalculator.forFont(smallFontX);
Rectangle2D bounds = textBoundsCalculator.boundsFor(string, g);
Where TextBoundsCalculator:
import java.awt.*;
import java.awt.geom.Rectangle2D;
public final class TextBoundsCalculator {
private interface MeasureStrategy {
Rectangle2D boundsFor(String string, Graphics2D g);
}
private MeasureStrategy measureStrategy;
private TextBoundsCalculator(MeasureStrategy measureStrategy) {
this.measureStrategy = measureStrategy;
}
public static TextBoundsCalculator forFont(Font font) {
if (font.getSize() == 0)
return new TextBoundsCalculator(new ScaleMeasureStrategy(font));
// The bug appears to be only when font.getSize()==0.
// So there's no need to normalize, measure and scale with fonts
// where this is not the case
return new TextBoundsCalculator(new NormalMeasureStrategy(font));
}
public Rectangle2D boundsFor(String string, Graphics2D g) {
return measureStrategy.boundsFor(string, g);
}
private static class ScaleMeasureStrategy implements MeasureStrategy {
private final float scale;
private final Font normalizedFont;
public ScaleMeasureStrategy(Font font) {
scale = font.getSize2D();
normalizedFont = font.deriveFont(1f);
}
public Rectangle2D boundsFor(String string, Graphics2D g) {
Rectangle2D bounds = NormalMeasureStrategy.boundsForFont(normalizedFont, string, g);
return scaleRectangle2D(bounds, scale);
}
}
private static class NormalMeasureStrategy implements MeasureStrategy {
private final Font font;
public NormalMeasureStrategy(Font font) {
this.font = font;
}
public Rectangle2D boundsFor(String string, Graphics2D g) {
return boundsForFont(font, string, g);
}
private static Rectangle2D boundsForFont(Font font, String string, Graphics2D g) {
return font.getStringBounds(string, g.getFontRenderContext());
}
}
private static Rectangle2D scaleRectangle2D(Rectangle2D rectangle2D, float scale) {
return new Rectangle2D.Double(
rectangle2D.getX() * scale,
rectangle2D.getY() * scale,
rectangle2D.getWidth() * scale,
rectangle2D.getHeight() * scale);
}
}
I am creating a basic Tic-Tac-Toe application in Java Swing, and for this purpose, I have started investigating drawing. However, I have encountered an issue when a subclass of JPanel contains more than one instance of a subclass of JLabel, which overrides the paintComponent(Graphic) method, in a GridLayout array format embedded in itself. The issue is that only the first element in this array is painted.
An example for the customized subclass of JLabel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class DrawLabel extends JLabel {
private boolean hasPaint;
public DrawLabel() {
super();
this.hasPaint = false;
}
public void draw() {
hasPaint = true;
repaint();
}
public void clear() {
hasPaint = false;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (hasPaint) {
g2.drawOval(getX(), getY(), getWidth(), getHeight());
} else {
g2.clearRect(getX(), getY(), getWidth(), getHeight());
}
}
}
An example for the customized subclass of JPanel:
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class DrawPanel extends JPanel {
private Dimension size;
private DrawLabel[][] fields;
public DrawPanel(Dimension d) {
super();
this.size = d;
this.fields = new DrawLabel[size.height][size.width];
this.setLayout(new GridLayout(size.height, size.width));
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
this.fields[i][j] = new DrawLabel();
this.add(fields[i][j]);
}
}
}
public void draw(int row, int col) {
fields[row][col].draw();
}
public void clear() {
for (int i = 0; i < size.height; i++) {
for (int j = 0; j < size.width; j++) {
fields[i][j].clear();
}
}
}
}
An example of an issue-provoking scenario:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Driver {
public static void main(String[] args) {
DrawPanel board = new DrawPanel(new Dimension(2, 1));
JFrame canvas = new JFrame();
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas.add(board, BorderLayout.CENTER);
canvas.pack();
canvas.setLocationRelativeTo(null);
canvas.setVisible(true);
board.draw(0, 0); // This gets painted.
board.draw(0, 1); // This does not get painted!
}
}
Curiously, if the DrawPanel.draw(int, int) is changed to set the text of the element rather than draw upon it, only the second element is updated, while the first is not, which is the exact opposite of the original problem.
I have tried to look up other issues and questions related to painting of subcomponents, but I have yet to find an issue like this one, where it seems that every instance beyond the first in the GridLayout are not drawable in the same way. What could be wrong?
Thank you for your time and effort!
g2.drawOval(getX(), getY(), getWidth(), getHeight());
Painting of a component is done relative to the component, not the panel the component is painted in. The getX/Y() methods return the location of the component relative to the parent component.
So if the size of each component is (200, 200) then the oval of the first component will be painted using
g2.drawOval(0, 0, 200, 200);
The second component will be painted at:
g2.drawOval(200, 0, 200, 200);
but since the component is only 200 pixels wide, the start x location of 200 is outside the bounds of the component so there is nothing to paint.
Instead just use:
g2.drawOval(0, 0, getWidth(), getHeight());
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have a jLabel with an Icon that I want to print in a printer (canon,hp,epson anything) using a button.
How can I do that?
Any useful code?
Code snippet?
links?
All I can see is like this:
How to print content of a label in java?
But It's not what I want.
I'm using netbeans
Thanks in advance.
Basically, the answer will depend on whether the label is displayed on the screen or not. To ensure that the label (or in fact, any component) can be printed, it must first be sized properly...
This can be done using setSize and feeding it getPreferredSize at the very basic level.
The next step is passing using the components printAll method (or print method depending on your needs) which is better suited for...printing...as it disables double buffering and won't produce nasty exceptions when it's not attached to a native peer...
Example printed as preferred size...
Example printed to fill available area...
Now the example uses the printComponentToFile method, but you'll want to use the printComponent method for actually printing it a printer, the first is useful for doing things like page previews and screen dumps...
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class PrintALabel {
public static void main(String[] args) {
try {
JLabel label = new JLabel(
"This is a test",
new ImageIcon("path/to/image"),
JLabel.CENTER);
printComponentToFile(label, true);
printComponentToFile(label, false);
} catch (PrinterException exp) {
exp.printStackTrace();
}
}
public static void printComponent(JComponent comp, boolean fill) throws PrinterException {
PrinterJob pjob = PrinterJob.getPrinterJob();
PageFormat pf = pjob.defaultPage();
pf.setOrientation(PageFormat.LANDSCAPE);
PageFormat postformat = pjob.pageDialog(pf);
if (pf != postformat) {
//Set print component
pjob.setPrintable(new ComponentPrinter(comp, fill), postformat);
if (pjob.printDialog()) {
pjob.print();
}
}
}
public static void printComponentToFile(Component comp, boolean fill) throws PrinterException {
Paper paper = new Paper();
paper.setSize(8.3 * 72, 11.7 * 72);
paper.setImageableArea(18, 18, 559, 783);
PageFormat pf = new PageFormat();
pf.setPaper(paper);
pf.setOrientation(PageFormat.LANDSCAPE);
BufferedImage img = new BufferedImage(
(int) Math.round(pf.getWidth()),
(int) Math.round(pf.getHeight()),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
ComponentPrinter cp = new ComponentPrinter(comp, fill);
try {
cp.print(g2d, pf, 0);
} finally {
g2d.dispose();
}
try {
ImageIO.write(img, "png", new File("Page-" + (fill ? "Filled" : "") + ".png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static class ComponentPrinter implements Printable {
private Component comp;
private boolean fill;
public ComponentPrinter(Component comp, boolean fill) {
this.comp = comp;
this.fill = fill;
}
#Override
public int print(Graphics g, PageFormat format, int page_index) throws PrinterException {
if (page_index > 0) {
return Printable.NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) g;
g2.translate(format.getImageableX(), format.getImageableY());
double width = (int) Math.floor(format.getImageableWidth());
double height = (int) Math.floor(format.getImageableHeight());
if (!fill) {
width = Math.min(width, comp.getPreferredSize().width);
height = Math.min(height, comp.getPreferredSize().height);
}
comp.setBounds(0, 0, (int) Math.floor(width), (int) Math.floor(height));
if (comp.getParent() == null) {
comp.addNotify();
}
comp.validate();
comp.doLayout();
comp.printAll(g2);
if (comp.getParent() != null) {
comp.removeNotify();
}
return Printable.PAGE_EXISTS;
}
}
}
I am doing this for a project at school, in which much more functionality will be added later, but I am having trouble getting the basic setup done.
I tried doing this in C++ initially, but decided to switch to Java after reading some documentation on BufferedImage, but I am running into an issue with output. Essentially this is what I am designing the flow of the program to be:
1) Extract original image's (BMP image that is supplied in grayscale) grayscale values. 2) Create a currentImage variable to preserve the original image and perform the modifications to the image I intend to perform in the later stages of the project. 3) Create a reusable output method that will take the currentImage BufferedImage, and output it to a panel (and maybe a file as well.
Here is what I have so far, which currently results in nothing being outputted to the screen, and when I tried doing System logs, it is not setting the pixel values in my draw mechanism:
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
#SuppressWarnings("serial")
public class ImageDriver extends JFrame {
DrawPanel paintPanel;
static int width = 1024, height = 768;
BufferedImage originalImage, currentImage;
File theImageFile;
int[][] imageData;
public ImageDriver() {
super("Image Processing");
setSize(width, height);
setDefaultCloseOperation(EXIT_ON_CLOSE);
paintPanel = new DrawPanel(width, height);
add(paintPanel);
}
public void updateImageDisplay() {
System.out.println("In updateImageDisplay!");
paintPanel.setImage(currentImage);
}
public void readImage() {
try {
theImageFile = new File("images/img1.bmp");
originalImage = ImageIO.read(theImageFile);
currentImage = originalImage;
}
catch (IOException e) {
e.printStackTrace();
}
//get a raster to extract grayscale values
Raster image_raster = currentImage.getData();
//get pixel by pixel
int[] pixel = new int[1];
int[] buffer = new int[1];
imageData = new int[image_raster.getWidth()][image_raster.getHeight()];
for(int i = 0 ; i < image_raster.getWidth() ; i++)
for(int j = 0 ; j < image_raster.getHeight() ; j++) {
pixel = image_raster.getPixel(i, j, buffer);
imageData[i][j] = pixel[0];
//System.out.println("Pixel at: " + i + " x " + j + ": " + imageData[i][j]);
}
}
public void increaseContrast() {
}
public static void main(String[] args) {
ImageDriver driver = new ImageDriver();
driver.setVisible(true);
driver.readImage();
driver.updateImageDisplay();
driver.increaseContrast();
driver.updateImageDisplay();
}
class DrawPanel extends JPanel {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
public DrawPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
}
public void setImage(BufferedImage image) {
System.out.println("In setImage!");
this.image = image;
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("in paintPanel component!");
g = image.getGraphics();
g.drawImage(image, 0, 0, null);
}
}
}
I have a hunch that it is because I am declaring image in my DrawPanel to be an empty image at first, and setImage is not properly copying over all of its contents upon assignment. I tried fiddling around with using this:
this.image.setData(image.getData());
but to no avail.
I missing something here? Or is a complete re-write if the mechanism in order?
Thank you for reading.
The first thing that jumps out at me is this (in your paintComponent method)...
g = image.getGraphics();
g.drawImage(image, 0, 0, null);
Basically, you are painting the image back to itself, instead you should do something like like...
g.drawImage(image, 0, 0, this);
Using the Graphics context you were passed