Drawing with Java: Applying Borders/Outlines to Shapes - java

I can't figure out how to get "g.setStroke(new BasicStroke(5));" to be set to all my created shapes (in this case ovals).
My code:
import java.awt.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.BasicStroke;
public class Rings
{
public static void main(String[] args)
{
DrawingPanel panel = new DrawingPanel(300, 300);
Graphics2D g = panel.getGraphics();
g.setStroke(new BasicStroke(5)); // Sets Outer Line Width of Shapes
g.setColor(new Color(255, 0, 0));
g.fillOval(50, 50, 200, 200); // Large Oval
g.setColor(new Color(200, 0, 0));
g.fillOval(100, 100, 100, 100); // Medium Oval
g.setColor(new Color(150, 0, 0));
g.fillOval(125, 125, 50, 50); // Small Oval
g.setColor(new Color(100, 0, 0));
g.fillOval(137, 137, 25, 25); // Tiny Oval
}
}
My output:
Correct output:

The stroke doesn't matter so much when you call fillOval but moreso when you call drawOval. So I recommend:
Call fillOval as you're doing
After each fillOval, then change Color to Color.BLACK (or whatever outline color you desire), and call drawOval.
See what happens to your drawing if you minimize the GUI and then restore it.
It is for this reason, and to avoid NullPointerException errors, that we don't recommend that you use a Graphics object obtained via a getGraphics() call on a Swing component. Such a Graphics object is short-lived. Instead do as the tutorials and most other similar questions will tell you: within a proper paintComponent override inside a class that extends JPanel or JComponent.

Related

Repainting a dirty region interfere with graphics2D clipbounds

In the following reproducer, I am reproducing what I'm seeing in the graphical interface.
I'd like to repaint a dirty region of a canvas (a JPanel), because a full repaint introduces more latencies.
However when the repaint (dirty rectangle) function is invoked, it causes some issues with nested Graphics2D objects. In particular
This code may mis-use clip bounds, but I wasn't expecting this "artifact" upon the creation of the sub (nested) graphic context.
What would be the proper way to deal with clips, nested graphic contexts, and the dirty region (or rectangle) repaint in this situation.
My intuition tells me the code should compute its shape without using the clipping area.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Reproducer {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.LIGHT_GRAY);
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
EDIT by moving the fill above drawRect
So, there are a couple of "mistakes"
I found this...
graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
clipBounds.y + 2,
clipBounds.width - 2,
clipBounds.height - 2));
to be a little erroneous, which meant when a clip was applied, the background area was not been filled properly.
The call to super.paintComponent was filling the clip area with the background color of the component, but graphicsBox.fill wasn't filling the entire area, allow part of the background color to be seen.
I also found the result of Graphics2D#create(int, int, int, int) to be a little surprising
The new Graphics object has its origin translated to the specified point (x, y). Its clip area is determined by the intersection of the original clip area with the specified rectangle.
(emphasis added by me)
I mean, honestly, I shouldn't be surprised, but I've just never used these features before so it took me a little by surprise.
So, I corrected the graphicsBox rendering code so that the fill will fill the entire clipping area first and THEN the rectangle is drawn (within the bounding rectangle)
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
nb: The reference to colors is just a list of Colors which I randomise during the paint process, so I can "see" what's going on
Another trick might be to change the background color of the component to make it easier to "see" where things are been updated incorrectly.
When you have issues like this, it's really important to reduce the amount of clutter to see if you can narrow down the problem, I took out most of the paint code to figure out all these "little" things which were combining to give you issues.
Runnable example...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
var comp = new MyJPanel();
var timer = new Timer(2_000, e -> {
SwingUtilities.invokeLater(comp::bug);
});
timer.start();
timer.setRepeats(true);
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Reproducer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(comp);
});
}
private static class MyJPanel extends JPanel {
private List<Color> colors;
public MyJPanel() {
setSize(600, 600);
setPreferredSize(getSize());
colors = Arrays.asList(new Color[] {
Color.BLACK,
Color.BLUE,
Color.CYAN,
Color.DARK_GRAY,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.ORANGE,
Color.PINK,
Color.RED,
Color.WHITE,
Color.YELLOW
});
}
#Override
protected void paintComponent(Graphics g) {
var g2 = (Graphics2D) g;
super.paintComponent(g2);
g2.setColor(Color.cyan);
g2.fill(g2.getClip());
var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
clipBounds.y,
clipBounds.width,
clipBounds.height));
Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);
graphicsBox.dispose();
var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
graphicsOverlay.setColor(Color.YELLOW);
graphicsOverlay.fillOval(10, 10, 60, 60);
graphicsOverlay.dispose();
}
public void bug() {
getVisibleRect();
repaint(100, 100, 100, 100);
}
}
}
If you're having "performance" issues, you might consider using a BufferedImage as the primary canvas and update it, then simply paint the image.
Alternatively, it might be time to move over to using BufferStrategy, this is going to get you as close to the "metal" as Java/Swing allows

Program to create shapes and show them on console

I was given small assignment as below . Can you please throw some light on how to implement this
Write a simple structured program and simple oo program that implements display shape function. Your program should simply print out (to console ) the number if shapes and then ask each shape to display itself which will also cause a line of output to be generated to the console , one for each shape . It is perfectly OK for your main program to create a collection of shapes before on to sorting that collection and displaying the shapes. Your program should support circles , triangles and squares but should use polymorphism so that the main program doesn't know the type of shape it is dealing with but instead treats shapes uniformly
I had created a program to create shapes like below but i'm not sure on how to create the shapes as mentioned and store them in collection and iterate to display these shapes on console. I was told not to use the database for storing shapes
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JApplet;
public class DrawShapes extends JApplet {
public void paint(Graphics g) {
g.setColor(Color.RED);
// drawing string
g.drawString("Hello World!", 100, 100);
// drawing rectangle
g.draw3DRect(120, 120, 120, 120, true);
g.setColor(Color.blue);
g.fill3DRect(120, 120, 120, 120, true);
// drawing circle
g.drawOval(240, 240, 120, 120);
g.setColor(Color.CYAN);
g.fillOval(240, 240, 120, 120);
// drawing square
g.drawRect(350, 350, 250, 100);
g.setColor(Color.magenta);
g.fillRect(350, 350, 250, 100);
// drawing trinale
}
}
Just an idea how to do it. Notice, that the drawing is hidden from the shapes collection.
interface Drawable {
public void draw(Graphics g);
}
class DrawableSquare implements Drawable{
public DrawableSquare(int x, int y, int width) { ... }
public void draw(Graphics g)
{
g.fillRect(x, y, width, width);
}
}
class Screen {
Collection<Drawable> drawables;
public void paint(Graphics g) {
for (Drawable dr: drawables) {
dr.draw(g);
}
}
}

Why does the Java Ellipse2D when drawn small, turn out as a rectangle?

I think I'll just start with the code that's giving me the problem.
class AnimationPanel extends JPanel
{
OfficeLoad load;
Timer timer = new Timer();
private static final long serialVersionUID = 1L;
public AnimationPanel()
{
setBackground(new Color(240, 240, 240));
setBorder(null);
setOpaque(false);
setBounds(10, 143, 400, 21);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
load = new OfficeLoad(g);
}
...
}
...
final Color WHITE = Color.WHITE;
public OfficeLoad(Graphics g)
{
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(WHITE);
g2.fill(new Ellipse2D.Double(30, 1, 5, 5));
g.fillOval(40, 1, 5, 5);
g.fillOval(50, 1, 5, 5);
g.fillOval(60, 1, 5, 5);
g.fillOval(70, 1, 5, 5);
g.setColor(new Color(0, 102, 51));
g.fillRect(0, 0, 10, 21);
}
Both when I use g.fillOval() and when I use g2.fill(new Ellipse2D()) it turns out as a square. Just for some extra information, I'm, just for fun of course, trying to replicate the excel 2013 splash screen when it's starting up. This part is for the loading dots underneath the "Excel". I already made it with a gif, which was much easier, but my friend challenged me to use the paint, repaint, and so forth. But I can't really do that, unless they turn up as circles rather than squares... Any help would be appreciated. :)
Thank you Halex! :D For everyone that didn't look at his comment, Halex gave this following answer.
Does it work when you enable antialiasing with g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
It did. Thanks! :D
First of all I advice to not make a new OfficeLoad object everytime you paint you can either save the officeload object or make a static void in the OfficeLoad so you don't need the object at all.
also you will need antialiassing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

nothing shows up in Java's paintComponent. How do I fix this?

In Java, I need to draw a simple line with the paintComponent. Here is my attempt, but nothing was shown when I executed the program. Please show me the correct way of doing this.
import javax.swing.*;
import java.awt.*;
public class DrawLine extends JPanel {
public Illusion(Color backColor){
setBackground(backColor);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.black);
g.drawLine(100, 100, 300, 100);
}
}
Your painting a black line on a black background, so I'd say its working just fine. Try changing the color of the line
g.setColor(Color.Red);
g.drawLine(100, 100, 300, 100);
Your also not taking into account the actual size of the panel, I'd do something more along the lines of
g.drawLine(0, 0, getWidth(), getHeight());
As a test
You might like to have a read through
Creating a GUI With JFC/Swing
Graphics2D
Painting in AWT and Swing

Change JButton gradient color, but only for one button, not all

I want to change JButton gradient color,
i found this, http://java2everyone.blogspot.com/2009/01/set-jbutton-gradient-color.html, but i want to change gradient for only one button, not all button
You can override the paintComponent method of the JButton instance and paint its Graphics object with one of the following classes that implement the Paint interface:
GradientPaint.
LinearGradientPaint
MultipleGradientPaint
RadialGradientPaint
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public final class JGradientButtonDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
final JFrame frame = new JFrame("Gradient JButton Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.add(JGradientButton.newInstance());
frame.setSize(new Dimension(300, 150)); // used for demonstration
//frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static class JGradientButton extends JButton {
private JGradientButton() {
super("Gradient Button");
setContentAreaFilled(false);
setFocusPainted(false); // used for demonstration
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
Color.WHITE,
new Point(0, getHeight()),
Color.PINK.darker()));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
public static JGradientButton newInstance() {
return new JGradientButton();
}
}
}
A little improvement over mre answer:
private static final class JGradientButton extends JButton{
private JGradientButton(String text){
super(text);
setContentAreaFilled(false);
}
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(new GradientPaint(
new Point(0, 0),
getBackground(),
new Point(0, getHeight()/3),
Color.WHITE));
g2.fillRect(0, 0, getWidth(), getHeight()/3);
g2.setPaint(new GradientPaint(
new Point(0, getHeight()/3),
Color.WHITE,
new Point(0, getHeight()),
getBackground()));
g2.fillRect(0, getHeight()/3, getWidth(), getHeight());
g2.dispose();
super.paintComponent(g);
}
}
TL;DR: it's not possible directly, but can be done with a workaround like in Luca's answer, however his/her answer uses the incorrect gradient steps. The correct ones are listed below.
The way it works
In the Metal LAF there is a hardcoded exception. If the background property is a subclass of UIResource, it's ignored* and the button is instead painted with the (also hardcoded) gradient from the UI property Button.gradient. Otherwise, if background is not a UIResource, that background is painted as-is.
*unless the button is disabled, in which case there is no gradient and the color inside the UIResource is used for the background.
The gradient
Following the logic of MetalButtonUI, I found out the used gradient it uses comes from the UI property Button.gradient, which contains the ArrayList:
0 = {Float} 0.3
1 = {Float} 0.0
2 = {ColorUIResource} "[221,232,243]"
3 = {ColorUIResource} "[255,255,255]"
4 = {ColorUIResource} "[184,207,229]"
Following the logic even further, I ended up in MetalUtils.GradientPainter.drawVerticalGradient(). This implementation interprets the above data as*:
Gradient from 0% to 30%: color1 to color2
Gradient from 30% to 60%: color2 to color1
Gradient from 60% to 100%: color1 to color3
*assuming the second float is 0.0, otherwise more gradients are drawn.
Since this is a multi-stage gradient, it can't be done with a simple GradientPaint but can be done with a LinearGradientPaint. However the background property only accepts Color. It cannot even be spoofed/hacked because the actual value is eventually given to Graphics.setColor() and not Graphics2D.setPaint() (even though Metal is Swing-based and not AWT) Dead End. The only solution seems to subclass JButton altogether.

Categories

Resources