java.awt.Graphics.fillRect() misbehaving - java

Below I have a simple method for painting a set of objects onto an java.awt.applet.Applet. I thought this was very straightforward, but it ends up only painting the objects as a single pixel at the top-left corner of the applet. The idea is that the Display class takes in a set of fairly lightweight objects that extend GameObject and contain information about their location, size, and appearance on the screen, and then draws them pixel-by-pixel onto the applet, stretching and positioning them proportionally depending on the specified display height and width. In testing this, I set the width and height of the Display to 128, and pass two objects to the Display, which are both 32-pixel squares (both return 32 for getWidth() and getHeight()), one is red and returns 24 for getX() and getY(), and the other is blue and returns 16 for getX() and getY(). I put the Display in a javax.swing.JFrame and use a java.awt.BorderLayout to ensure it fills the frame (I use add(display, java.awt.BorderLayout.CENTER); within the aforementioned javax.swing.JFrame).
As far as I can tell, this should be paining a blue 32-pixel square that's 16 pixels from the top and left edges and a red 32-pixel square that is either obscured by or obscuring part of the other one. However, all I get is a single red or blue pixel in the top-left corner of the Display. This is consistent no matter how big or small the window is.
Code
public class Display extends java.awt.applet.Applet
{
private int w,h;
private ArrayPP<GameObject> gameObjects;//ArrayPP is my way of making a dynamically expanding array. It is similar to Vector, but has many more useful methods I use elsewhere.
public Display(int width, int height)
{
w = width;
h = height;
}
public void addGameObject(GameObject go)
{
gameObjects.add(go);
}
public void refresh(java.awt.Graphics g)
{
int x, y, w, h;
final int W = getWidth(), H = getHeight();
for (GameObject go : gameObjects)//Draw all objects
for (x = go.getX(), y = go.getY(), w = go.getWidth() + x, h = go.getHeight() + y; y < h; y++)//Draw all lines of the object
for (x = go.getX(); x < w; x++)//Draw all the pixels on this line of the object
{
g.setColor(go.getColorForPixel(x, y));
g.fillRect((x / this.w) * W, (y / this.h) * H, w/W, h/H);
}
}
}
public interface GameObject
{
public int getX();
public int getY();
public int getWidth();
public int hetHeight();
public java.awt.Color getColorForPixel(int x, int y);
}
Question
Why is java.awt.Graphics.fillRect(int x, int y, int width, int height) only painting the topleft corner of the applet?
Solution
The solution lies in the line that reads
g.fillRect((x / this.w) * W, (y / this.h) * H, w/W, h/H);
wherein integer calculations cause all values to be 0. The solution is as follows:
g.fillRect((int)(((float)x/(float)this.w) *W),
(int)(((float)y/(float)this.h) *H),
(int)((float)W/(float)w),
(int)((float)H/(float)h));

The problem lies with
(x / this.w) * W
If x and this.w are both integers and x
Convert one or more of x and w to float to force a floting point division.
(int)(((float)x/(float)this.w) *W)

Related

creating a slope in Java

Im trying to create a slope in java. I can use the DrawLine function and it'll create a perfect one but I dont want to use that but rather create my own function for it. The problem is that it has gaps between the dots.
import java.applet.Applet;
import java.awt.Graphics;
public class slope extends Applet{
public void drawLine(int x1, int y1, int x2, int y2, Graphics g) {
double m = (y2 - y1) / (double)(x2-x1);
double y = y1;
for (int x =x1; x < x2; x++) {
drawPoint(x,(int)y,g);
y +=m;
}
}
public void paint(Graphics g) {
drawLine(20, 10, 300, 700, g); //has spaces between the dots
g.drawLine(20, 10, 300, 700); //this is perfect
}
private void drawPoint(int x, int y, Graphics g) {
g.drawLine(x, y, x, y);
}
}
Two loops: you loop over x++ only when deltaX > deltaY.
else you loop over y++ only.
Dual stepping x and y in the same loop, deciding which should be incremented (assuming you have x as a function of y too) could lead to slower drawing due to extra tests and adjacent pixels may look like a dot in the line. You'd need to play with color intensity to do antialiasing by hand (gold plating). Two loops is much simpler.
fyi, you are trying to generate an image, you could also just set ints in a matrix and make an offscreen raw image (BufferedImage and it's .setRGB() method), which you draw later. That would likely be faster and avoid visible painting delays.
Generally this is done by using an algorithm that doesn't step only along the x or y axis, but adjust the update increment by a variable amount, such that each dot is at most sqrt(2) away from each other.
So, if you think you have a point at the x value, but when you calculate it, you find that it is 3.5 pixels away (because the slope is very steep), you fall into a routine that calculates (typically recursively) an intermediate pixel between that x step
(x, y)
(0, 0) to (1, 5) distance 5.09
-> fill routine
(0, 0) to (0.5, 2.5) distance 2.69
-> fill routine
(0, 0) to (0.25, 1.25) distance 1.34 < 1.41
(0.25, 1.25) to (0.5, 2.5) distance 1.34 < 1.41
(0.5, 2.5) to (0.75, 3.75) distance 1.34 < 1.41
(0.75, 3.75) to (1, 5) distance 1.34 < 1.41
(1, 5) to (2, 10) etc...
The reason one uses 1.41 (sqrt(2)) as the maximum distance allowed is because a of pixels at a 45 degree angle from the bottom of the screen would still appear connected.
Now, in your plotting, you'll need to round the values to align to the exact pixels. There are a number of ways to do this. The simplest is just to round to the next valid value, and this works most of the time. It does have one unfortunate side effect, that is your line will appear to have jagged steps (where the rounding is moving the pixel more, the step will appear more jagged). This jaggedness is called "aliasing" as the true point is presenting through a non-true representation of the point (the alias).
A second approach is to alternatively darken both pixels proportionally, based on how close the point is. A point that is at (0.5) on the x axis would darken both pixels by 50% while a point that is at (0.25) would darken the 0 pixel by 75% and the 1 pixel by 25%. This is anti-aliasing, and may result in a line that is slightly more fuzzy, but appears to be straighter. This fuzziness can be somewhat combated by drawing a thicker line.
I hope this gives you some idea of the math behind many of the higher quality drawing routines, and certainly there are approaches that are even more sophisticated than the one I just presented.
Based on the Bresenham Algorithm, here is a java implementation that
assumes x2 > x1 and y2 > y1 and uses integer arithmetic
import java.applet.Applet;
import java.awt.*;
public class Slope extends Applet{
private int x1 = 20, y1 = 10;
private int x2 = 300, y2 = 700;
#Override
public void paint(Graphics g) {
drawLine(x1, y1, x2, y2, g);
//g.drawLine(x1, y1, x2, y2, g);
}
private void drawPoint(int x, int y, Graphics g) {
g.drawLine(x,y,x,y);
}
#Override
public void init(){
this.setSize(500,700);
}
private void drawLine(int x1,int y1,int x2,int y2,Graphics g){
int dx = x2 - x1;
int dy = y2 - y1;
int xi = 1;
int D = 2*dx - dy;
int x = x1;
for(int y = y1; y <y2; y++) {
drawPoint(x,y,g);
if(D > 0) {
x = x + xi;
D = D - 2 * dy;
}
D = D + 2 * dx;
}
}
}

Unexpected circle border drawn with AWT

With AWT I draw a border using java.awt.Graphics#drawOval and java.awt.Graphics2D#setStroke. For situations when the set stroke has a size bigger than the oval's diameter the resulting border is not like expected. In that situation the stroke overlaps the stroke of the other side of the circle: Circles north stroke overlaps the south stroke. AWT renders this overlapping in an XOR way as you can see in the following image.
What I'd expect instead is that the stroke overlapping is drawn in an OR way, so that in all situations when stroke width > circle diameter the center is black.
Is there a simple way I can set to change the behaviour to an OR overlapping mode, even when width or height of the circle (then its an ellipse) is not equal?
Same diameter (10px) with increasing stroke width:
Based on the solution that Marco13 mentioned in his comment I came up with this custom drawOval function. It basically switch from drawOval to fillOval once the stroke width is greater than the diameter. The position and dimensions for the fillOval function are calculated to match the drawOval output.
public static void drawOval(Graphics2D g2d, int strokeWidth, int x, int y, int width, int height) {
int minLength = Math.min(width, height);
int maxLength = Math.max(width, height);
if (minLength >= strokeWidth) {
g2d.drawOval(x, y, width, height);
} else {
int x1 = x - (strokeWidth - maxLength) / 2 - (maxLength / 2);
int y1 = y - (strokeWidth - maxLength) / 2 - (maxLength / 2);
int width1 = width + strokeWidth;
int height1 = height + strokeWidth;
g2d.fillOval(x1, y1, width1, height1);
}
}
This is how it looks like

Is it possible to use BasicStrokes join types on a GeneralPath?

I have a polygon I've made with GeneralPath, and I've made a basic stroke with line thickness of 8 and set the join type to JOIN_MITER. When I try to paint this shape like so:
g2d.setStroke(stroke);
g2d.draw(generalPath);
It paints the shape with the correct line thickness, but it treats each line like it's own, giving it whatever type of cap end I define in the stroke. CAP_ROUND looks the best like this, but I would much prefer the if lines properly joined with JOIN_MITER. Can this be done? Am I using the right class when I use GeneralPath? Any help or advice would be much appreciated.
For context, here's the class stripped down to just the pertinent info. the paintComponent method is called from the paintComponent method of the JPanel which I'm drawing on, it has an arrayList of these which it iterates through and calls each their paintComponent methods at a time:
public class RShape extends RComponent{
GeneralPath linkedLines;
Stroke stroke;
public RShape(int x, int y, int sides, Stroke stroke, int defaultLineLength) {
this.stroke = stroke;
linkedLines = new GeneralPath();
double angle = ((double)360)/sides;//find what the angles would need to be to make a shape with that many sides
double dStartX = x;
double dStartY = y;
double nextAngle;
for(int i=0;i<sides;i++) {
nextAngle = angle*i;
nextAngle = nextAngle * Math.PI / 180;//convert to radians
double dEndX = dStartX + defaultLineLength * Math.sin(nextAngle);//find where the end of the line should be for the given angle and line length
double dEndY = dStartY + defaultLineLength * Math.cos(nextAngle);
int endX = (int) Math.round(dEndX);//round to the nearest int
int endY = (int) Math.round(dEndY);
int startX = (int) Math.round(dStartX);
int startY = (int) Math.round(dStartY);
linkedLines.moveTo(startX, startY);//add the next segment of the GeneralPath
linkedLines.lineTo(endX, endY);
dStartX = dEndX;//move the starting point to the end point so it's ready for the next loop
dStartY = dEndY;
}
linkedLines.closePath();//close the last gap.
}
public void paintComponent(Graphics2D g2d) {
g2d.setStroke(stroke);
g2d.draw(linkedLines);
}
}
This is called like this:
ComponentList components = new ArrayList<RComponent>();
components.add(new RShape(50,50,5,new BasicStroke(8,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER),60));

How do you draw a rectangle and an oval using mouse dragging?

I understand how to use MouseMotionListener but I can't get the parameters right for drawing a rectangle and an oval.
This is my attempt at a rectangle, but the problem is if go to the left from the start point, the rectangle gets filled.
public void draw(Graphics g) {
g.drawRect((int)startPoint.getX(), (int)startPoint.getY(),(int)controlPoint.getX() - (int)startPoint.getX(), (int) controlPoint.getY() - (int)startPoint.getY());
}
This is my method for a circle, this seems to work fine. But i cannot alter it for it to form an oval.
public void draw(Graphics g) {
g.drawOval((int)startPoint.getX() - (int)controlPoint.distance(startPoint),((int)startPoint.getY() - (int)controlPoint.distance(startPoint)),
(int)controlPoint.distance(startPoint)*2,(int)controlPoint.distance(startPoint)*2);
}
The mousePressed must be the center(startPoint) and the drag should be the radius for an oval.
Let me for brevity change the variable names from startPoint to sp and from controlPoint to cp, then these changes to your code should do the trick:
int minX = Math.min(sp.x, sp.y);
int minY = Math.min(sp.x, sp.y);
int width = Math.abs(cp.x - sp.x);
int height = Math.abs(cp.y - sp.y);
g.drawRect(minX, minY, width, height);
g.drawOval(minX, minY, width, height);
The reason is that those methods should receive the top-left corner coordinates, as well as the width and height of the bounding box of the rect/oval being drawn.
Both Graphics#drawRect and Graphics#drawOval expect the parameters to mean x, y, width, height, not x1, y1, x2, y2...
Your start points may be greater than your end points, resulting in either or both the width and/or height been negative values (based on width = x1 - x2). The Graphics API doesn't like negative values very much. You will need to take this into consideration when calculating the starting points and size.
The crust of the problem can be solved using something like...
int minX = Math.min(currentX, startX);
int minY = Math.min(currentY, startY);
int maxX = Math.max(currentX, startX);
int maxY = Math.max(currentY, startY);
int x = minX;
int y = minY;
int width = maxX - minX;
int height = maxX - minX;
Take a look at java draws rectangle one way not both for a working example...

Rotate BufferedImage

I'm following a textbook and have become stuck at a particular point.
This is a console application.
I have the following class with a rotate image method:
public class Rotate {
public ColorImage rotateImage(ColorImage theImage) {
int height = theImage.getHeight();
int width = theImage.getWidth();
//having to create new obj instance to aid with rotation
ColorImage rotImage = new ColorImage(height, width);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color pix = theImage.getPixel(x, y);
rotImage.setPixel(height - y - 1, x, pix);
}
}
//I want this to return theImage ideally so I can keep its state
return rotImage;
}
}
The rotation works, but I have to create a new ColorImage (class below) and this means I am creating a new object instance (rotImage) and losing the state of the object I pass in (theImage). Presently, it's not a big deal as ColorImage does not house much, but if I wanted it to house the state of, say, number of rotations it has had applied or a List of something I'm losing all that.
The class below is from the textbook.
public class ColorImage extends BufferedImage {
public ColorImage(BufferedImage image) {
super(image.getWidth(), image.getHeight(), TYPE_INT_RGB);
int width = image.getWidth();
int height = image.getHeight();
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
setRGB(x, y, image.getRGB(x, y));
}
public ColorImage(int width, int height) {
super(width, height, TYPE_INT_RGB);
}
public void setPixel(int x, int y, Color col) {
int pixel = col.getRGB();
setRGB(x, y, pixel);
}
public Color getPixel(int x, int y) {
int pixel = getRGB(x, y);
return new Color(pixel);
}
}
My question is, how can I rotate the image I pass in so I can preserve its state?
Unless you limit yourself to square images or to 180° rotations, you need a new object, as the dimensions would have changed. The dimensions of a BufferedImage object, once created, are constant.
If I wanted it to house the state of, say, number of rotations it has had applied or a List of something I'm losing all that
You can create another class to hold that other information along with the ColorImage/BufferedImage, then limit the ColorImage/BufferedImage class itself to holding only the pixels. An example:
class ImageWithInfo {
Map<String, Object> properties; // meta information
File file; // on-disk file that we loaded this image from
ColorImage image; // pixels
}
Then you can replace the pixels object freely, while preserving the other state. It's often helpful to favor composition over inheritance. In brief that means, instead of extending a class, create a separate class that contains the original class as a field.
Also note that the rotation implementation from your book seems to be mainly for learning purposes. It's fine for that, but will show its performance limitations if you manipulate very big images or for continuous graphics rotation at animation speeds.

Categories