Graphics2D - Text is HUGE in scaled coordinate space - java

The first "foo" is normal, but the second one is so huge I can only see the base of the "f". The font-size is the default.
BufferedImage image = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
Shape ellipse = new Ellipse2D.Double(0, 0, 1.0, 1.0);
g2.setPaint(Color.RED);
g2.drawString("foo", 100, 100);
g2.scale(500.0f / length, 500.0f / length);
g2.drawString("foo", 1, 1);
Changing the font size will not help because it only allows int sizes and the size that would make sense for the scale is something like 0.02.
The reason I need to draw the text while in scaled space is because I am drawing a grid of nodes and I want to scale the coordinates to the number of nodes in each dimension. That way I do not have to do complicated calculations.
I need the text to label the edges.
Update: I am able to get the text by doing the following sequence each time I want to draw the text: saving the transform, translating to the desired location in scaled space, unscaling, drawing the text at (0, 0), and restoring the transform.

You can use Font method
public Font deriveFont(float size)
to obtain desired font size font. Guess the 0.02 shoud be fine.

Related

Why is my BufferedImage different when drawn to canvas?

Original
https://drive.google.com/file/d/1B3xxfWkGsMs2_MQ_bUQ8_ALYI0DL-LIo/view?usp=sharing
When saved to file
https://drive.google.com/file/d/1z5euXupeHmiFebch4A39fVqGukoUiK0p/view?usp=sharing
When printed to canvas
https://drive.google.com/file/d/1VouD-ygf0pPXFFx9Knr4pv44FHMtoqcV/view?usp=sharing
BufferedImage temp = bImg.getSubimage(100, 100, (int)imgWidth - 100, (int)imgHeight - 100);
try{
ImageIO.write(temp, "png", new File("test.png"));
}catch(Exception e){
e.printStackTrace();
}
gc.drawImage(SwingFXUtils.toFXImage(temp, null), 100, 100);
For some reason if I print an image to the canvas, it is different than if I save the same image to a file. When I save it to a file it correctly calculates the subImage but when I print it to the canvas it disregards the x and y coords I give it and takes a subImage using (0,0) as (x,y) with the given width and height.
From the documentation of the getSubimage method:
Returns a subimage defined by a specified rectangular region. The returned BufferedImage shares the same data array as the original image.
The sub-image is just a “window” into the original image; they are using the same pixel data.
The SwingFXUtils.toFXImage documentation states:
Snapshots the specified BufferedImage and stores a copy of its pixels into a JavaFX Image object, creating a new object if needed.
While it would certainly make sense to only copy the pixels in the source image’s dimensions, the above words don’t make it completely clear that it won’t copy the entire pixel data buffer, thus ignoring the boundaries of a sub-image. I would consider this a bug, but I can see where there might be an argument that it’s not.
In the meantime, you can work around this by extracting a sub-image yourself:
BufferedImage cropped = new BufferedImage(
(int) imgWidth - 100,
(int) imgHeight - 100,
bImg.getType());
Graphics g = cropped.getGraphics();
g.drawImage(bImg, -100, -100, null);
g.dispose();
gc.drawImage(SwingFXUtils.toFXImage(cropped, null), 100, 100);

Why doesn't BufferedImage of type TYPE_INT_ARGB support the AlphaComposite.CLEAR rule? [duplicate]

I have an off-screen BufferedImage, constructed with the type BufferedImage.TYPE_INT_ARGB. It can contain anything, and I'm looking for a way to (fairly efficiently) completely overwrite the image with transparent pixels, resulting in an 'invisible' image.
Using something like this:
(bufimg.getGraphics()).setColor(new Color(10, 10, 100, 0));
(bufimg.getGraphics()).fillRect (0, 0, x, y);
Has no effect. One possible method might be just to write over every pixel in the BufferedImage, but I'm not sure this is the best solution. How would you do it?
[edit]
The Graphics documentation advises against using clearRect for off-screen images, but I have tried it with the same results as above.
[edit2]
After experimenting with MeBigFatGuy's code (thanks!), it does clear an image. But it also stops further painting to that image (or appears to). This code for example:
BufferedImage img = new BufferedImage (600, 600, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.createGraphics ()
g.drawLine (100, 100, 500, 500);
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
g.setComposite(composite);
g.setColor(new Color(0, 0, 0, 0));
g.fillRect(0, 0, 600, 600);
graphicsAI.setColor(new Color (10, 10, 10, 255));
graphicsAI.drawLine (100, 100, 500, 500);
Results in nothing seen on the image (I'm drawing the image to a JPanel). Is this something to do with the addition of alpha values?
After you clear the background with the CLEAR composite, you need to set it back to SRC_OVER to draw normally again. ex:
//clear
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
g2.fillRect(0,0,256,256);
//reset composite
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
//draw
g2.setPaint(Color.RED);
g2.fillOval(50,50,100,100);
You could get the underlying int[] array of your BufferedImage (make sure to use a compatible format: that is, one that is backed by an int[]).
Then fill the int[] with ints whose alpha value are 0 (0 will do ; )
A System.arraycopy will be very fast.
You have to know that directly writing in the int[] is a lot faster than using setRGB.
Now BufferedImage are a bit of a black art in Java: depending on what you're doing and on which platform/JVM you're doing it, you may lose hardware acceleration (which may never have been there in the first place anyway). In addition to that, you may very well not care at all about hardware acceleration anyway because you may not be working on, say, a game requiring 60+ FPS to be playable etc.
This is a very complicated topic and there's more than one way to skin the BufferedImage cat. As far as I'm concerned I work directly in the int[] when I've got to mess at the pixel level because I think it makes much more sense than trying to use higher-level drawing primitives and I do really don't care about the potential lost of hardware acceleration.
If you cast the Graphics object to a Graphics2D object, you can set a Composite object thru
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setComposite(composite);
g2d.setColor(new Color(0, 0, 0, 0));
g2d.fillRect(0, 0, 10, 10);
For the sake of completeness, here is a working, testing, and fast function that is cross-platform compliant.
static public BufferedImage createTransparentBufferedImage(int width, int height) {
// BufferedImage is actually already transparent on my system, but that isn't
// guaranteed across platforms.
BufferedImage bufferedImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
// To be sure, we use clearRect, which will (unlike fillRect) totally replace
// the current pixels with the desired color, even if it's fully transparent.
graphics.setBackground(new Color(0, true));
graphics.clearRect(0, 0, width, height);
graphics.dispose();
return bufferedImage;
}
Despite you saying it doesn't work, I used clearRect quite fine.
Clears the specified rectangle by filling it with the background color
of the current drawing surface. This operation does not use the
current paint mode.
Beginning with Java 1.1, the background color of offscreen images may
be system dependent. Applications should use setColor followed by
fillRect to ensure that an offscreen image is cleared to a specific
color.
Fills the specified rectangle. The left and right edges of the
rectangle are at x and x + width - 1. The top and bottom edges are at
y and y + height - 1. The resulting rectangle covers an area width
pixels wide by height pixels tall. The rectangle is filled using the
graphics context's current color.
It is not clearly stated here that one will set the rectangle to the background color, while the other will paint with the foreground color on top of the current colors, but it's what it seems to do.
This is pure speculation, but I think the note about offscreen images relates to Graphics objects obtained from offscreen AWT components, as they are native. I can hardly imagine how the background color of a BufferedImage could be system dependent. As the API doc is for Graphics, this could be a generalization not applying to the BufferedImage case.
My testing code:
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage img = new BufferedImage(200, 300, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
//fill right half with opaque white
g.setColor(Color.WHITE);
g.fillRect(100, 0, 100, 300);
//leave top third as it is
//fill middle third with transparent color
g.setColor(new Color(0, true));
g.fillRect(0, 100, 200, 100);
//clear bottom third with transparent color
g.setBackground(new Color(0, true));
g.clearRect(0, 200, 200, 100);
g.dispose();
jf.add(new JLabel(new ImageIcon(img)));
jf.pack();
jf.setVisible(true);
the result is two white squares, top right. Where no white was painted, or clearRect was used to overwrite the white, the result is a light gray, the frame's default background color.
Performance-wise, it's regular drawing. arraycopy might well be faster, I don't know, but at least this is likely hardware accelerated just as any other drawing operation.
A plus point versus the array solution is a) no additional memory and b) independence from the color model; this should work no matter how the image was set up.
A minus point versus the Composite solution is that it only allows clearing rectangles; setting the composite allows you to clear any kind of shape.
Setting the background of the graphics Object seems to do the job:
g.setBackground(new Color(0, 0, 0, 0));
(at least when drawing images for scaling purposes)

Java Graphics2D getPixelBounds or getStringWidth not useful with drawString

I am trying to place two words at the center of the image. But the drawString method does not seem to correctly pickup the "x" coodinate. For example I am trying to place the words "setupa" and "asetup" (image width 30). My image width is 106, thus the x-cord value is 38 in both the cases. But in reality asetup is places at 1-2 pixel shift.
It produces just minor difference, but that shows up in my images.
Sample code is follows.
Graphics2D textGraphics;
textGraphics = image.createGraphics();
textGraphics.setColor(fontColor);
textGraphics.setFont(font);
FontRenderContext frc = new FontRenderContext(null, true, true);
TextLayout layout = new TextLayout(label, font, frc);
Rectangle r = layout.getPixelBounds(frc, 0, 0);
textGraphics.drawString(label, ((imageWidth / 2) - (r.width/2)), (imageWidth / 2));
Is they any way around this? or a better way to place the text at the center.
Thanks

BufferedImage INT / 4BYTE / USHORT

I know the difference in memory usage between byte, unsigned short, and integer, but when it comes to a BufferedImage, is there a 'speed' difference between them?
I have been using the Image type in my code to store images, but I require an alpha layer. Using BufferedImage provides me with ARGB, but my code is /considerably/ slower after making the change from the Image type (and it was only changed for a few objects), so I'm looking for all the performance improvement I can get.
I'm not sure how stupid of a question this may be, so I thank you in response for any replies.
Tanaki,
I have found that, when in need of using an alpha channel in a BufferedImage, the best is to premultiply the alpha channel. For example:
// Create an ARGB image
BufferedImage bi = new BufferedImage(512, 512, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
// Fill the background (for illustration)
g.setColor(Color.black);
g.fill(new Rectangle(0, 0, 512, 512));
AlphaComposite alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
// Keep the original composite
Composite original = g.getComposite();
g.setComposite(alpha);
// Paint with transparency
Rectangle r = new Rectangle(100, 200, 50, 50);
g.setColor(Color.magenta);
g.fillRect(r);
g.setComposite(original);
// ... paint further shapes or images as necessary
// ...
g.dispose();
// Convert to a premultiplied alpha image for fast painting to a Canvas
BufferedImage biPre = new BufferedImage(512, 512, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D gPre = biPre.createGraphics();
gPre.drawImage(bi, 0, 0, null);
gPre.dispose();
// clean up:
bi.flush();
// Now use biPre for painting to a Canvas, or a Component.
// ...
// Remember to flush it when done!
biPre.flush();
The reason for painting first to a TYPE_INT_ARGB is to ensure that all alpha gets painted as you expected (not pre-multiplied every time!). Then, when done, paint the whole image onto a TYPE_INT_ARGB_PRE, which is then able to bring the data to the screen with good speed.

Filling pattern changes with object's position. Java

I created a bufferedimage which i applied to a Rectangle to use as filling pattern to shape S. If i change S's position, the filling pattern changes with it instead of remaining "fixed". What could it be?
Image: (the pattern is a 3 stripes, all with the same aspect ratio) :alt text http://img88.imageshack.us/img88/8524/imageby.png
if (bannerPatternCreated == false) {
banner = new BufferedImage(size * 3, size * 3, BufferedImage.TYPE_INT_RGB);
Graphics2D gc = banner.createGraphics();
System.out.println("Creating banner...");
gc.setColor(Color.black);
gc.fillRect(0, 0, size, size * 3);
gc.setColor(Color.BLUE);
gc.fillRect(size, 0, size, size * 3);
gc.setColor(Color.WHITE);
gc.fillRect(size * 2, 0, size, size * 3);
gc.dispose();
bannerPatternCreated = true;
}
Rectangle patternPencil = new Rectangle(size, size);
g2.setPaint(new TexturePaint(banner, patternPencil));
Rectangle recto = new Rectangle(presentX-size, presentY-size, size, size);
g2.fill(recto);
It looks like the texture position is fixed, and so when you move recto around you're just getting a different view of the underlying infinitely-repeating texture.
If you change the patternPencil rect to be the same size/position as recto, I think it should get sorted:
Rectangle patternPencil = new Rectangle(presentX-size, presentY-size, size, size);
I think the TexturePaint docs indicate why the problem:
...the texture is anchored to the
upper left corner of a Rectangle2D
that is specified in user space.
Texture is computed for locations in
the device space by conceptually
replicating the specified Rectangle2D
infinitely in all directions
It's as if you're rectangle is drawn from 0,0 and replicated over and over, and the visible parts are small "windows" that are opened with the call to g2.fill.
If you're drawing to a canvas-type component, could you just use one of the Graphics.drawImage methods at the appropriate x,y?

Categories