Why is my BufferedImage different when drawn to canvas? - java

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);

Related

Generating square thumbnails in Java by cutting (without destroying aspect ratio)?

I'm looking for a way to create a square thumbnail (250px × 250px) in Java without destroying the aspect ratio, that means if the image is rectangular with one side longer than the other it should just cut off whatever doesn't fit in the square. Currently I'm doing this:
public static void createThumbnail(File file, String extension)
throws IOException {
BufferedImage img = new BufferedImage(
250, 250, BufferedImage.TYPE_INT_RGB);
img.createGraphics().drawImage(
ImageIO.read(file).getScaledInstance(
250, 250, Image.SCALE_SMOOTH), 0, 0, null);
ImageIO.write(img, extension, new File(
"./public/images/thumbs/" + file.getName()));
}
However, it is not cutting of parts of the image, instead it is squeezing it to fit inside the 250 × 250 square.
You are using getScaledInstance() which will just expand or shrink your image to fit it in the size you are giving it.
Have a look at getSubimage(). You most probably want to first get a sub image which has the same aspect ratio of your target size (a square), then apply getScaledInstance() on it. This way you just shrink with the same aspect ratio and don't get any squeezing effect.
So something like this should work. Assuming you want to keep the middle part when cropping.
Image getThumbnail(File file) {
BufferedImage original = ImageIO.read(file);
//assuming we want a square thumbnail here
int side = Math.min(original.getWidth(), original.getHeight());
int x = (original.getWidth() - side) / 2;
int y = (original.getHeight() - side) / 2;
BufferedImage cropped = original.getSubimage(x, y, side, side);
return cropped.getScaledInstance(250, 250, Image.SCALE_SMOOTH);
}
(I haven't tried it myself, let me know if there are any problems with it.)
You can then pass it to your drawImage() creating the new rendered BufferedImage, and save it to a file.
BufferedImage img = new BufferedImage(250, 250, BufferedImage.TYPE_INT_RGB);
img.createGraphics().drawImage(getThumbnail(file), 0, 0, null);
ImageIO.write(img, extension, new File("./public/images/thumbs/" + file.getName()));

Graphics2D - Text is HUGE in scaled coordinate space

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.

Performing setRGB on BufferedImage changes pixel to black instead of color

** Important update, see below! **
I am creating a program that changes the pixels of a BufferedImage to a certain color when that pixel fulfills a set of conditions in Java. However, when I write the image to disk, the pixels that should be colored are instead black.
First I define the color, using RGB codes:
Color purple = new Color(82, 0, 99);
int PURPLE = purple.getRGB();
Then I read the image I want to alter from a File into a BufferedImage called "blank":
BufferedImage blank = ImageIO.read(new File("some path"));
Now, loop through the pixels, and when a pixel at location (x, y) matches a criteria, change its color to purple:
blank.setRGB(x, y, PURPLE);
Now, write "blank" to the disk.
File output = new File("some other path");
ImageIO.write(blankIn, "png", output); // try-catch blocks intentionally left out
The resulting file should be "blank" with some purple pixels, but the pixels in question are instead black. I know for a fact that the issue is with setRGB and NOT any import or export functions, because "blank" itself is a color image, and gets written to file as such. I read around and saw a lot of posts recommending that I use Graphics2D and to avoid setRGB, but with no discussion of pixel-by-pixel color changing.
I also tried direct bit manipulation, like this:
blank.setRGB(x, y, ((82 << 16) + (0 << 8) + 99));
I'm probably doing that wrong, but if I put it in correctly it wouldn't matter, because the pixels are getting set to transparent when I do this (regardless of what the numbers say, which is very strange, to say the least).
** When I try this:
blank.setRGB(x, y, Color.RED.getRGB());
My output file is grayscale, so that means setRGB is, in fact, modifying my picture in grayscale. I think this is actually a rather simple issue, but the solution eludes me.
Based on the insights in https://stackoverflow.com/a/21981173 that you found yourself ... (a few minutes after posting the question) ... it seems that it should be sufficient to simply convert the image into ARGB directly after it was loaded:
public static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(
image.getWidth(), image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
The original image that was imported into Java was actually grayscale, so when Java read it into the BufferedImage, it simply imported it as a grayscale BufferedImage. By adding a very small but imperceptible colored dot in the corner of my image, I was able to get Java to output a correctly colored image.
Unfortunately, this is only a half solution, because I do not know how to fix this programmatically.
SOLUTION:
Convert the BufferedImage from grayscale to ARGB with this snippet:
BufferedImage blank2 = blank;
// Create temporary copy of blank
blank = new BufferedImage(blank.getWidth(), blank.getHeight(), BufferedImage.TYPE_INT_ARGB);
// Recreate blank as an ARGB BufferedImage
ColorConvertOp convert = new ColorConvertOp(null);
// Now create a ColorConvertOp object
convert.filter(blank2, blank);
// Convert blank2 to the blank color scheme and hold it in blank
You want to add this right after blank = ImageIO.read(new File("some path")).

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.

java image crop

I am aware of BufferedImage.getSubimage However, it cant deal with cropping images that are smaller than the cropping size throwing the exception:
java.awt.image.RasterFormatException: (y + height) is outside raster
I want to be able to crop either a PNG/JPG/GIF to a certain size however if the image is smaller than the cropping area centre itself on a white background. Is there a call to do this? Or do I need to create an image manually to centre the image on if so, how would I go about this?
Thanks
You cannot crop an image larger, only smaller. So, you start with the goal dimension,let's say 100x100. And your BufferedImage (bi), let's say 150x50.
Create a rectangle of your goal:
Rectangle goal = new Rectangle(100, 100);
Then intersect it with the dimensions of your image:
Rectangle clip = goal.intersection(new Rectangle(bi.getWidth(), bi.getHeight());
Now, clip corresponds to the portion of bi that will fit within your goal. In this case 100 x50.
Now get the subImage using the value of clip.
BufferedImage clippedImg = bi.subImage(clip,1, clip.y, clip.width, clip.height);
Create a new BufferedImage (bi2), the size of goal:
BufferedImage bi2 = new BufferedImage(goal.width, goal.height);
Fill it with white (or whatever bg color you choose):
Graphics2D big2 = bi2.getGraphics();
big2.setColor(Color.white);
big2.fillRect(0, 0, goal.width, goal.height);
and draw the clipped image onto it.
int x = goal.width - (clip.width / 2);
int y = goal.height - (clip.height / 2);
big2.drawImage(x, y, clippedImg, null);

Categories