I have this program:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class TestLine {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestLine().start();
}
});
}
private static void start() {
JFrame frame = new JFrame();
frame.setContentPane(new CarthPanel());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private static class CarthPanel extends JComponent {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D) g;
int w = gg.getClipBounds().width;
int h = gg.getClipBounds().height;
System.out.println("(w,h)=(" + w + "," + h + ")");
gg.translate(w - 1, 0); // <<< when uncommenting both lines, mirroring applies
gg.scale(-1.0, 1.0); //
paintTest(gg, w, h);
}
private static void paintTest(Graphics2D g, int w, int h) {
// black background
g.setColor(Color.black);
g.fillRect(0, 0, w, h);
// colored corners
g.setColor(Color.RED);
g.drawLine(0, 0, 10, 0);
g.drawLine(0, 0, 0, 10);
g.setColor(Color.RED);
g.drawLine(0, 199, 10, 199);
g.drawLine(0, 199, 0, 189);
g.setColor(Color.CYAN);
g.drawLine(189, 0, 199, 0);
g.drawLine(199, 0, 199, 10);
g.setColor(Color.CYAN);
g.drawLine(189, 199, 199, 199);
g.drawLine(199, 199, 199, 189);
// yellow squares
g.setColor(Color.yellow);
g.drawRect(3, 3, 10, 10);
g.fillRect(186, 3, 11, 11);
g.fillRect(3, 186, 11, 11);
g.drawRect(186, 186, 10, 10);
String chars = "ABC";
g.setFont(Font.decode("Arial 72"));
Rectangle2D rect = g.getFontMetrics().getStringBounds(chars, g);
g.drawString(chars, (int) (200 - rect.getWidth()) / 2, (int) (200 - rect.getHeight()));
}
}
}
If you run this program once with two particular lines commented in and then once with the same lines commented out (see the code), then you get to see this:
[
If you didn't spot the problem, here's a zoomed pic:
One would expect the image to be perfectly mirrored. This is true for all strokes and hollow shapes (= drawXxx() methods). All filled shapes however (= fillXxx() methods) are drawn exactly one pixel to the left of where I expect them; e.g. the filled yellow rectangles, and you can also notice that the black background has shifted, as seen by the white line at the right border.
Is this a bug or is this intended? I suspect that it has something to do with the difference how "width" and "height" are being handled in drawXxx() and fillXxx() methods:
drawRect(x,y,w,h) results in a hollow rectangle with X-axis boundaries x and x+w, so the rectangle is w+1 pixels wide.
fillRect(x,y,w,h) results in a filled rectangle with X-axis boundaries x and x+w-1, so the rectangle is w pixels wide.
What am I missing?
Not an answer, rather a hypothesis - the "contains(x,y)" of a rectangle will be true for the sides which forms the (x,y) corner of the rect and false for the sides making the (maxx, maxy) corner. Demo:
Rectangle r=new Rectangle(0, 0, 10, 10);
System.out.println(r.contains(0, 5)); // true
System.out.println(r.contains(5, 0)); // true
System.out.println(r.contains(10, 5)); // false
System.out.println(r.contains(5, 10)); // false
The hypothesis is that fillRect will not consider the "max sides" as inside points to be filled.
To assess the hypothesis, I'd suggest you to try using a GeneralPath which defines the same rectangle and see it there is any difference in filling it. GeneralPath should be forbidden to make any assumption on "the right-top borders are out of the shape's interior"
It is a bug, it is not a feature :)
Surely it is not intended, the mirrored image should be perfect, there's no reason why it shouldn't.
There is a way to use BufferedImage to render on it normally, and then drawing this BufferedImage flipped, the only drawback is a little performance influence and not well looking text drawn using LCD subpixeling.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class TestLine {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestLine().start();
}
});
}
private static void start() {
JFrame frame = new JFrame();
frame.setContentPane(new CarthPanel());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private static class CarthPanel extends JComponent {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D) g;
// System.out.println("(w,h)=(" + w + "," + h + ")");
gg.translate(getWidth(), 0); // <<< when uncommenting both lines, mirroring applies
gg.scale(-1.0, 1.0); //
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
try {
paintTest(img.createGraphics());
} catch (Exception e) {
e.printStackTrace();
}
g.drawImage(img, 0, 0, null);
}
private void paintTest(Graphics2D g) {
// black background
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
// colored corners
g.setColor(Color.RED);
g.drawLine(0, 0, 10, 0);
g.drawLine(0, 0, 0, 10);
g.setColor(Color.RED);
g.drawLine(0, 199, 10, 199);
g.drawLine(0, 199, 0, 189);
g.setColor(Color.CYAN);
g.drawLine(189, 0, 199, 0);
g.drawLine(199, 0, 199, 10);
g.setColor(Color.CYAN);
g.drawLine(189, 199, 199, 199);
g.drawLine(199, 199, 199, 189);
// yellow squares
g.setColor(Color.yellow);
g.drawRect(3, 3, 10, 10);
g.fillRect(186, 3, 11, 11);
g.fillRect(3, 186, 11, 11);
g.drawRect(186, 186, 10, 10);
String chars = "ABC";
g.setFont(Font.decode("Arial 72"));
Rectangle2D rect = g.getFontMetrics().getStringBounds(chars, g);
g.drawString(chars, (int) (200 - rect.getWidth()) / 2, (int) (200 - rect.getHeight()));
}
}
}
Related
I'm getting an error saying "error: paintComponent(Graphics) in HelloGraphics cannot override paintComponent(Graphics) in JComponent
public static void paintComponent(Graphics g) {"
I'm trying to repeat a certain image 25 times in the window, but I didn't write the starter code, so I am unfamiliar with #Override and super.paintComponent(g)
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Dimension;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class HelloGraphics extends JPanel{
public static final int BOX_WIDTH = 1024;
public static final int BOX_HEIGHT = 768;
public static final Color MAMMOTH_PURPLE = new Color(63, 31, 105);
public static final Color SPRING_LEAF = new Color(91, 161, 81);
public static int x = 0;
public static int y = 0;
public HelloGraphics(){
this.setPreferredSize(new Dimension(BOX_WIDTH, BOX_HEIGHT));
}
#Override
public static void paintComponent(Graphics g) {
super.paintComponent(g);
//Your code here: feel free to remove what is below
for (int i = 0; i < 25; i++) {
x += 5;
y += 5;
g.setColor(Color.WHITE);
g.fillRect(0, 0, BOX_WIDTH, BOX_HEIGHT);
g.setColor(Color.ORANGE);
g.fillOval(x + 60, 70, 120, 140);
g.fillOval(x + 65, 170, 40, 50);
g.fillOval(x + 140, 170, 40, 50);
g.setColor(Color.black);
g.fillOval(x + 70, y + 100, 10, 15);
g.fillOval(x + 100, y + 100, 10, 15);
g.setColor(Color.MAGENTA);
g.drawOval(x + 70, y + 120, 50, 40);
}
}
public static void main(String args[]){
JFrame frame = new JFrame("Hello, Graphics!");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new HelloGraphics());
frame.pack();
frame.setVisible(true);
}
}
Remove the static keyword
#Override
public static void paintComponent(Graphics g) {
becomes
#Override
public void paintComponent(Graphics g) {
The method is not static in the base class, and even if it were, you can't override static methods anyway.
I am trying to draw a ring/partial ring using arc's.
But I need the middle of the partial ring/ring to be transparent and show whats behind it. Since I am using an arc and spinning it, is there a way to subtract away the inner portion of each arc and leave only the end at the desired thickness which will be spun to create the ring?
This is my code so far: I have the arcs working, but I am only simulating the ring by layering over each one with a couple circles, I need to actually subtract out the area of each circle from each arc.
Not sure how to do that to achieve transparency in the center. If there is a better way to do this please let me know, this is going to be a custom progress bar.
public class JCustomProgressBar extends JComponent{
private final Dimension SIZE = new Dimension( 50, 50 );
public JCustomProgressBar() {
super();
this.setVisible(true);
System.out.println("CAlled");
}
int progress = 1;
public void updateProgress (int progress){
this.progress = progress;
}
#Override
public void paintComponent (Graphics g){
super.paintComponent(g);
System.out.println("called");
Graphics2D g2D = (Graphics2D) g.create();
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2D.translate(this.getWidth()/2, this.getHeight()/2);
g2D.rotate(Math.toRadians(270));
Arc2D.Float arc = new Arc2D.Float (Arc2D.PIE);
Ellipse2D circle = new Ellipse2D.Float(0, 0, 80, 80);
arc.setFrameFromCenter (new Point(0,0), new Point (90, 90));
circle.setFrameFromCenter(new Point(0,0), new Point (80, 80));
arc.setAngleExtent(-progress*360/100);
g2D.setColor(new Color(120,192,0));
g2D.draw(arc);
g2D.fill(arc);
g2D.setColor(this.getParent().getBackground());
g2D.draw(circle);
g2D.fill(circle);
arc.setFrameFromCenter (new Point(0,0), new Point (75, 75));
arc.setAngleExtent(-90*360/100);
g2D.setColor(new Color(197,228,146));
g2D.draw(arc);
g2D.fill(arc);
circle.setFrameFromCenter(new Point(0,0), new Point (70, 70));
g2D.setColor(this.getParent().getBackground());
g2D.draw(circle);
g2D.fill(circle);
circle.setFrameFromCenter(new Point(0,0), new Point (60, 60));
g2D.setColor(new Color(245, 245, 245));
g2D.draw(circle);
g2D.fill(circle);
g2D.setColor(Color.black);
g2D.rotate(Math.toRadians(90));
g2D.setFont(new Font("Verdana", Font.PLAIN, 30));
FontMetrics fm = g2D.getFontMetrics();
Rectangle2D r2D = fm.getStringBounds(progress + "%", g);
int x = (0 - (int) r2D.getWidth())/2;
int y = (0 - (int) r2D.getHeight())/2 +fm.getAscent();
g2D.drawString(progress + "%", x, y-10);
//Rectangle2D r2d = fm.getStringBounds(progress + "", g);
// g2D.setFont(new Font("Verdana", Font.PLAIN, 22));
// g2D.drawString("%", x + 40, y-10);
g2D.setFont(new Font("Verdana", Font.PLAIN, 15));
g2D.drawString("Progress", -35, y+5);
g2D.dispose();
}
}
There are a number of ways you "might" do this, but the simplest might be to just use BasicStroke and simply draw the arcs (and not fill anything at all)
This example deliberately sets the background color so you can see that it's transparent.
import java.awt.BasicStroke;
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.Point;
import java.awt.RenderingHints;
import java.awt.geom.Arc2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JCustomProgressBar pb = new JCustomProgressBar();
pb.setProgress(25);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class JCustomProgressBar extends JPanel {
private final Dimension SIZE = new Dimension(200, 200);
public JCustomProgressBar() {
super();
setBackground(Color.RED);
// Uncomment this to make it transparent
//setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return SIZE;
}
int progress = 1;
public void setProgress(int progress) {
this.progress = progress;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g.create();
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2D.translate(this.getWidth() / 2, this.getHeight() / 2);
BasicStroke bs = new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
Arc2D.Float arc = new Arc2D.Float(Arc2D.OPEN);
arc.setAngleStart(90);
arc.setFrameFromCenter(new Point(0, 0), new Point(90, 90));
arc.setAngleExtent(-((progress / 100d) * 360));
g2D.setStroke(bs);
g2D.setColor(new Color(120, 192, 0));
g2D.draw(arc);
bs = new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
arc.setFrameFromCenter(new Point(0, 0), new Point(75, 75));
arc.setAngleExtent(-(((100 - progress) / 100d) * 360));
g2D.setStroke(bs);
g2D.setColor(new Color(197, 228, 146));
g2D.draw(arc);
g2D.setColor(Color.black);
g2D.setFont(new Font("Verdana", Font.PLAIN, 30));
FontMetrics fm = g2D.getFontMetrics();
Rectangle2D r2D = fm.getStringBounds(progress + "%", g);
int x = (0 - (int) r2D.getWidth()) / 2;
int y = (0 - (int) r2D.getHeight()) / 2 + fm.getAscent();
g2D.drawString(progress + "%", x, y - 10);
g2D.setFont(new Font("Verdana", Font.PLAIN, 15));
g2D.drawString("Progress", -35, y + 5);
g2D.dispose();
}
}
}
You can take a look at Stroking and Filling Graphics Primitives for more details.
Before you tell me how you "don't want the ends rounded" (because I like it that way), make sure you take the time to read the BasicStroke JavaDocs
When I use the coordinates of (0,0) "Rectangle(0,0,100,100)" for the rectangle I get gradient. When I use:
GradientPaint gp = new GradientPaint(0, 0, c1, 0, 100, c2);
Rectangle reckt = new Rectangle(0,100,100,200);
the gradient disappears. What am I doing wrong?
public void draw( Graphics g ) {
Graphics2D g2d = (Graphics2D) g;
c1 = new Color(0, 0, 255);
c2 = new Color(0, 255, 255);
GradientPaint gp = new GradientPaint(0, 0, c1, 0, 100, c2);
g2d.setPaint(gp);
Rectangle reckt = new Rectangle(0,0,100,100);
g2d.fill(reckt);
}
The first two parameters define the x/y point at which the gradient starts and the fourth and fifth define the height and width. So basically, you're drawing your rectangle beyond the the gradient fill
You have two options, either change the x/y position if the GradientFill or use a AffineTransform and translate the Graphics context to where you want to paint and simply always paint at 0x0 for both
A AffineTransform allows you to translate (among other things) the Graphics top/left position, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTranslate {
public static void main(String[] args) {
new TestTranslate();
}
public TestTranslate() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color c1 = new Color(0, 0, 255);
Color c2 = new Color(0, 255, 255);
GradientPaint gp = new GradientPaint(0, 0, c1, 0, 100, c2);
for (int offset = 0; offset < getWidth(); offset += 50) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(gp);
g2d.setTransform(AffineTransform.getTranslateInstance(offset, offset));
g2d.fill(new Rectangle(0, 0, 100, 100));
g2d.dispose();
}
}
}
}
How would i make this into a two argument constructor that can be used to specify where to draw the object and/or scale the object.This is being used for a Jframe graphics project. I can post the Jframe too if it will help.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import javax.swing.JComponent;
import java.awt.GradientPaint;
/*
component that draws the concert speakers
*/
public class ConcertSpeaker extends JComponent
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Recover Graphics2D
Graphics2D g2 = (Graphics2D) g;
// Speaker base
g2.setColor(Color.BLACK);
Rectangle base = new Rectangle (300, 400, 50, 100);
g2.fill(base);
// Speakers circles gray top
g2.setColor(Color.DARK_GRAY);
Ellipse2D.Double speakerTop = new Ellipse2D.Double(310, 410, 30, 30);
g2.fill(speakerTop);
//speakers circles black top
g2.setColor(Color.BLACK);
Ellipse2D.Double speakerTop1 = new Ellipse2D.Double(315, 415, 20, 20);
g2.fill(speakerTop1);
// Speakers circles gray bottom
g2.setColor(Color.DARK_GRAY);
Ellipse2D.Double speakerBottom = new Ellipse2D.Double(310, 450, 30, 30);
g2.fill(speakerBottom);
//speakers circles black bottom
g2.setColor(Color.BLACK);
Ellipse2D.Double speakerBottom1 = new Ellipse2D.Double(315, 455, 20, 20);
g2.fill(speakerBottom1);
}
}
I'm not sure having a big component is the best thing to do but here is how you achieve what I understood you're asking (just the body of the class):
private int x, y;
private float scale;
public PosSCale(int x, int y, float scale) {
this.x = x;
this.y = y;
this.scale = scale;
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Recover Graphics2D
Graphics2D g2 = (Graphics2D) g;
g2.translate(x, y);
g2.scale(scale, scale);
// Speaker base
g2.setColor(Color.BLACK);
Rectangle base = new Rectangle (0, 0, 50, 100);
g2.fill(base);
// Speakers circles gray top
g2.setColor(Color.DARK_GRAY);
Ellipse2D.Double speakerTop = new Ellipse2D.Double(10, 10, 30, 30);
g2.fill(speakerTop);
//speakers circles black top
g2.setColor(Color.BLACK);
Ellipse2D.Double speakerTop1 = new Ellipse2D.Double(15, 15, 20, 20);
g2.fill(speakerTop1);
// Speakers circles gray bottom
g2.setColor(Color.DARK_GRAY);
Ellipse2D.Double speakerBottom = new Ellipse2D.Double(10, 50, 30, 30);
g2.fill(speakerBottom);
//speakers circles black bottom
g2.setColor(Color.BLACK);
Ellipse2D.Double speakerBottom1 = new Ellipse2D.Double(15, 55, 20, 20);
g2.fill(speakerBottom1);
}
Is there a way to duplicate a generalpath, mirror it, and move it?
I was creating a cartoon character, and I realized her left side of the hair, body is the same as the right side.
For example:
I have done the left part of her hair and body and the code is very long.
So to finish the character faster, I thought maybe there is a way of duplicating the code, flip it horizontally and move it to the right position.
I have this sample code:
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
GeneralPath body, mirror;
body = new GeneralPath();
mirror = new GeneralPath();
body.moveTo(205.5,97);
body.lineTo(207,132);
body.quadTo(193,105, 197,80);
body.curveTo(188,98, 156,127, 159,167);
body.quadTo(163,174, 166,184);
body.curveTo(173,196, 193,210, 213,208);
body.curveTo(247,208, 267,196, 274,184);
g2d.setPaint(new Color(255,251,223));
g2d.fill(body);
g2d.setPaint(Color.black);
g2d.draw(body);
//mirror = duplicate(body)
//flip(mirror)
//mirror.moveTo(x,y)
//..something like that
}
You can use a transformation. See Transforming Shapes, Text, and Images tutorial for some examples.
Here is an example based on the original code in the question:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final JPanel panel = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponents(g);
final Graphics2D g2d = (Graphics2D) g.create();
try {
GeneralPath body, mirror;
body = new GeneralPath();
mirror = new GeneralPath();
body.moveTo(205.5, 97);
body.lineTo(207, 132);
body.quadTo(193, 105, 197, 80);
body.curveTo(188, 98, 156, 127, 159, 167);
body.quadTo(163, 174, 166, 184);
body.curveTo(173, 196, 193, 210, 213, 208);
body.curveTo(247, 208, 267, 196, 274, 184);
g2d.setPaint(new Color(255, 251, 223));
g2d.fill(body);
g2d.setPaint(Color.black);
g2d.draw(body);
AffineTransform tx = AffineTransform
.getScaleInstance(-1, 1);
tx.translate(-274 * 2, 0);
g2d.transform(tx);
g2d.setPaint(Color.YELLOW);
g2d.fill(body);
g2d.setPaint(Color.BLACK);
g2d.draw(body);
} finally {
g2d.dispose();
}
}
};
JOptionPane.showMessageDialog(null, panel, "Mirror",
JOptionPane.INFORMATION_MESSAGE);
}
});
}
}
The result looks like this: