Java: drawing scaled objects (buffered image and vector graphics) - java

I would like to draw scaled objects containing raster as well as vector data. Currently, I am drawing into a scaled Graphics2D object
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
//Get transformation, apply scale and shifts
at = g2d.getTransform();
at.translate(x, y);
at.scale(zoom, zoom);
//Transform Graphics2D object
g2d.setTransform(at);
//Draw buffered image into the transformed object
g2d.drawImage(img, 0, 0, this);
//Draw line into transformed Graphics2D object
Line2D.Double line = new Line2D.Double();
line.x1 = (int)p1.getX();
line.y1 = (int)p1.getY();
line.x2 = (int)p2.getX();
line.y2 = (int)p2.getY();
g2d.draw(line);
g2d.dispose();
}
Unfortunately, this approach has some disadvantages (affects the Stroke width, worse quality of zoomed images).
Instead of that I would like to draw scaled objects. For the vector data, the approach is simple:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
//Update affine transformation
at = AffineTransform.getTranslateInstance(x, y);
at = AffineTransform.getScaleInstance(zoom, zoom);
//Transform line and draw
Line2D.Double line = new Line2D.Double();
line.x1 = (int)p1.getX();
line.y1 = (int)p1.getY();
line.x2 = (int)p2.getX();
line.y2 = (int)p2.getY();
g2d.draw(at.createTransformedShape((Shape)line));
//Transform buffered image and draw ???
g2d.draw(at.createTransformedShape((Shape)img)); //Error
g2d.dispose();
}
However, how to apply the transformation to the buffered image without rescaling the Graphics2D object? This approach does not work
g2d.draw(at.createTransformedShape((Shape)img));
because raster image cannot be understood as a vector shape...
Thanks for your help.

A possible solution may represent an affine transformation of the raster:
//Update affine transformation
at = AffineTransform.getTranslateInstance(x, y);
at = AffineTransform.getScaleInstance(zoom, zoom);
at.translate(x, y);
at.scale(zoom, zoom);
//Create affine transformation
AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
//Apply transformation
BufferedImage img2 = op.filter(img, null);
//Draw image
g2d.drawImage(img2, 0, 0, this);
Unfortunately, this approach is inappropriate for the zoom operations; it is more computationally expensive. For the raster data (JPG, 5000 x 7000 pix) a simple bilinear interpolation
AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
takes about 1.5 sec... Conversely, using a scaling Graphics2D object is significantly faster (< 0.1 s), the image can be shifted and zoomed in the real-time.
In my opinion, rescaling objects together with the raster data is slower than rescaling Graphics2D object....

Related

How can I paint an image from BufferStrategy to Png file?

I've created a Java program that generates snowflakes and I'd like to save the image created as a .png file once the program finishes drawing.
I've searched on Internet, but I've found only programs using BufferedImage, while I use a BufferStrategy, so I don't know exactly where to start.
The draw method in my program uses a BufferStrategy to create the Graphics component.
For example, to draw a simple line the method is:
bs = display.getCanvas().getBufferStrategy();
if (bs == null) {
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
g.clearRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawLine(0, 0, 50, 50);
What I would like is to get an exact copy of what has been drawn on the screen by the program to be saved as a .png image.
Hope you can help me.
Why not take a screenshot and then past it onto MS paint or some other(and better) image editing software like Photoshop or fire alpaca? That should solve your problem.
The common denominator between BufferedStrategy and BufferedImage is Graphics, so you want to write a paint routine so that you can simply pass a reference of Graphics to it
public void render(Graphics g) {
g.clearRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawLine(0, 0, 50, 50);
}
Then you can pass what ever context you want.
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_RGB);
Graphics2D g2d = img.createGraphics();
render(g2d);
g2d.dispose();
Then you can use ImageIO.write to write the image to disk. See Writing/Saving an Image for more details

Is it possible to get the right string size without use paintComponent(Graphics g)?

Actually I have these methods to get the width/height size of a string, both requires the command inside a paint component. But I want to get this values inside a class constructor. This is possible?
public int getStringWidth(Graphics g){
g2d = (Graphics2D) g;
metrics = g2d.getFontMetrics(this.font);
return metrics.stringWidth(this.string);
}
public int getStringHeight(Graphics g){
g2d = (Graphics2D) g;
metrics = g2d.getFontMetrics(this.font);
int height = (int)font.createGlyphVector(metrics.getFontRenderContext(), this.string).getVisualBounds().getHeight();
return height;
}
font.createGlyphVector(metrics.getFontRenderContext(), this.string).getVisualBounds().getHeight() it's the best command that I got to precisely calculate the string height size and it needs Graphics g too.
You can try to create a BufferedImage with the right size and use createGraphics() on it to get an actual Graphics object that is drawable.
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.createGraphics();

How to copy a rotated square BufferedImage part using Java?

I want to copy a part of BufferedImage, but the copying form is not a simple square but rather a square rotated on some angle. As an output I want to get the BufferedImage with width and height equals to the copying square size and contents from the initial image, where copying square intersects the initial image.
What is the simplest way to do it?
A simple approach would be to create a new image with the desired size, transform a Graphics2D of this image according to the selected square region, and then simply paint the original image into this graphics:
private static BufferedImage extractRegion(
BufferedImage source, Point2D center, Point2D size, double angleRad)
{
int w = (int)size.getX();
int h = (int)size.getY();
BufferedImage result =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.translate(size.getX() / 2, size.getY() / 2);
g.rotate(angleRad);
g.translate(-center.getX(), -center.getY());
g.drawImage(source, 0, 0, null);
g.dispose();
return result;
}

Retrieving pixel data from Java Image

Recently I have been attempting to scale pixel arrays (int[]) in Java. I used .setRGB() to add all my pixel data into the BufferedImage. BufferedImage then offers a function called .getScaledInstance(). This should work great for my purposes, but I ran into a problem. .getScaledInstance() returns a Image, not a BufferedImage. With an Image object, I cannot use .getRGB() to add all the pixel data (in int[] form) from the scaled Image back into an array. Is there a way to get raw pixel data from an Image file? Am I missing something? I looked at other questions and did a bit of googling, and they only seemed to be wanting to get picture data in a different form of array (int[][]) or in bytes. Any help would be appreciated, thanks. Also, Sprite is a class I made that is being used. Here is my code:
public Sprite scaleSprite(Sprite s, int newWidth, int newHeight){
BufferedImage image = new BufferedImage(s.getWidth(), s.getHeight(), BufferedImage.TYPE_INT_RGB);
for(int y = 0; y < s.getHeight(); y++){
for(int x = 0; x < s.getWidth(); x++){
image.setRGB(x, y, s.getPixel(x, y));
}
}
Image newImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
Sprite newS = new Sprite(newWidth, newHeight);
int[] pixels = new int[newWidth * newHeight];
newImage.getRGB(0, 0, newWidth, newHeight, pixels, 0, newWidth); //This is where I am running into problems. newImage is an Image and I cannot retrieve the raw pixel data from it.
newS.setPixels(pixels);
return newS;
}
You can draw the resulting Image onto a BufferedImage like this:
Image newImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
BufferedImage buffImg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) buffImg.getGraphics();
g2.drawImage(newImage, 0, 0, 10, 10, null);
g2.dispose();
Or you can scale the image directly by drawing it on another BufferedImage:
BufferedImage scaled = new BufferedImage(newWidth, newWidth, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) scaled.getGraphics();
g2.drawImage(originalImage, 0, 0, newWidth, newWidth, 0, 0, originalImage.getWidth(), originalImage.getHeight(), null);
g2.dispose();
The second approach will work correctly if the two BufferedImages have the same aspect ratio.
To be clear, getScaledInstance() is a method of Image, not BufferedImage. You don't generally want to revert to working directly with the Image superclass once you're working with BufferedImage; Image is really not easy to work with.
Please see if this will help: How to scale a BufferedImage
Or from Scaling a BufferedImage, where they yield the following example:
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
public class Main {
public static void main(String[] argv) throws Exception {
BufferedImage bufferedImage = new BufferedImage(200, 200,
BufferedImage.TYPE_BYTE_INDEXED);
AffineTransform tx = new AffineTransform();
tx.scale(1, 2);
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_BILINEAR);
bufferedImage = op.filter(bufferedImage, null);
}
This will give you the ability to scale entirely at the level of BufferedImage. From there you can apply whatever sprite specific or array data algorithm you wish.

Fill rectangle with pattern in Java Swing

I know how to fill a rectangle in Swing with a solid color:
Graphics2D g2d = bi.createGraphics();
g2d.setColor(Color.RED);
g2d.fillRect(0,0,100,100);
I know how to fill it with an image:
BufferedImage bi;
Graphics2D g2d = bi.createGraphics();
g2d.setPaint (new Color(r, g, b));
g2d.fillRect (0, 0, bi.getWidth(), bi.getHeight());
But how to fill rectangle of size 950x950 with some tiled pattern of size 100x100?
(pattern image should be used 100 times)
You're on the right track with setPaint. However, instead of setting it to a color, you want to set it to a TexturePaint object.
From the Java tutorial:
The pattern for a TexturePaint class is defined by a BufferedImage class. To create a TexturePaint object, you specify the image that contains the pattern and a rectangle that is used to replicate and anchor the pattern. The following image represents this feature:
If you have a BufferedImage for the texture, create a TexturePaint like so:
TexturePaint tp = new TexturePaint(myImage, new Rectangle(0, 0, 16, 16));
where the given rectangle represents the area of the source image you want to tile.
The constructor JavaDoc is here.
Then, run
g2d.setPaint(tp);
and you're good to go.
As #wchargin said, you can use TexturePaint. Here is an example:
public class TexturePanel extends JPanel {
private TexturePaint paint;
public TexturePanel(BufferedImage bi) {
super();
this.paint = new TexturePaint(bi, new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(paint);
g2.fill(new Rectangle(0, 0, getWidth(), getHeight()));
}
}

Categories