Drawing Enlarged Images - java

I am currently doing a project that requires me to draw a large number (100+) images on the screen. The original resolution of each image is 20*20 and I am scaling them to 80*80 with nearest neighbor before drawing them.
I currently use AffineTransform to scale them up when the program is initialized, and redraw the enlarged images every frame in different positions.
Seeing as the target framerate is about 60 fps, I need to find a way to draw them faster, preferably so that it doesn't render them as 80*80 bitmaps. I tried Graphics2D's drawImage method with the width and height parameters, but it actually slowed my program down.
In pseudocode:
Image image; //loaded from 20*20 png file
public void init(){
image = resize(image, 80, 80);
}
public void repaint(Graphics g){
g.drawImage(image, 0, 0, null);
}
I also tried:
Image image;
public void repaint(Graphics g){
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.drawImage(image, 0, 0, 80, 80, null);
}
Is there a solution to my problem?

There was a similar question about that: Load and Resize Image
Here is my answer: Load and Resize Image
I hope I could help.

Related

Miserable text when drawing offscreen images in Java

I am stuck (beyond the limits of fun) at trying to fix text quality with offscreen image double buffering.
Screen capture worth a thousand words.
The ugly String is drawn to an offscreen image, and then copied to the paintComponent's Graphics argument.
The good looking String is written directly to the paintComponent's Graphics argument, bypassing the offscreen image.
Both Graphics instances (onscreen and offscreen) are identically setup in terms of rendering quality, antialiasing, and so on...
Thank you very much in advance for your wisdom.
The very simple code follows:
public class AcceleratedPanel extends JPanel {
private Dimension osd; //offscreen dimension
private BufferedImage osi; //offscreen image
private Graphics osg; //offscreen graphic
public AcceleratedPanel() {
super();
}
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
osg2D.drawString("Offscreen painting", 10, 20);
//Dump offscreen buffer to screen
g.drawImage(osi, 0, 0, this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
/*
To make sure same settings are used in different Graphics instances,
a unique setup procedure is used.
*/
private void setupGraphics(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private Graphics2D getOffscreenGraphics() {
//Graphics Acceleration
Dimension currentDimension = getSize();
if (osi == null || !currentDimension.equals(osd)) {
osi = (BufferedImage)createImage(currentDimension.width, currentDimension.height);
osg = osi.createGraphics();
osd = currentDimension;
}
return (Graphics2D) osg;
}
} //End of mistery
You are not drawing your two strings with the same color. The default color for the offscreen Graphics is rgb(0, 0, 0) (that is, pure black), while Swing will set the color of a Graphics object to the look-and-feel’s default color—which, for me on Windows 7, using the default theme, is rgb(51, 51, 51), or dark gray.
Try placing g.setColor(Color.BLACK); in your setupGraphics method, to ensure both strings are drawn with the same color.
Thanks for the replies.
With mentioning DPI, MadProgrammer has lead me to a working fix which I offer here more as workaround than as a 'clean' solution to be proud of. It solves the issue, anyway.
I noticed that while my screen resolution is 2880x1800 (Retina Display), MouseEvent's getPoint() method reads x=1440, y=900 at the lower right corner of the screen. Then, the JPanel size is half the screen resolution, although it covers the full screen.
This seen, the solution is as follows:
first, create an offscreen image matching the screen resolution, not the JPanel.getSize() as suggested in dozens of double buffering articles.
then, draw in the offscreen image applying a magnifying transform, bigger than needed, in particular scaling by r = screen dimension / panel dimension ratio.
finally, copy a down scaled version of the offscreen image back into the screen, applying a shrinking factor of r (or scaling factor 1/r).
The solution implementation is split into two methods:
An ammended version of the initial paintComponent posted earlier,
a helper method getDPIFactor() explained afterwards.
The ammended paintComponent method follows:
public final void paintComponent(Graphics g) {
super.paintComponent(g);
double dpiFactor = getDPIFactor();
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
//Paint stuff bigger than needed
osg2D.setTransform(AffineTransform.getScaleInstance(dpiFactor, dpiFactor));
//Custom painting
performPainting(osg2D);
//Shrink offscreen buffer to screen.
((Graphics2D)g).drawImage(
osi,
AffineTransform.getScaleInstance(1.0/dpiFactor, 1.0/dpiFactor),
this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
To complete the task, the screen resolution must be obtained.
A call to Toolkit.getDefaultToolkit().getScreenResolution() doesn't solve the problem, as it returns the size of a JPanel covering the whole screen. As seen above, this figure doesn't match the actual screen size in physical dots.
The way to get this datum is cleared by Sarge Bosch in this stackoverflow post.
I have adapted his code to implement the last part of the puzzle, getDPIFactor().
/*
* Adapted from Sarge Bosch post in StackOverflow.
* https://stackoverflow.com/questions/40399643/how-to-find-real-display-density-dpi-from-java-code
*/
private Double getDPIFactor() {
GraphicsDevice defaultScreenDevice =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
// on OS X, it would be CGraphicsDevice
if (defaultScreenDevice instanceof CGraphicsDevice) {
CGraphicsDevice device = (CGraphicsDevice) defaultScreenDevice;
// this is the missing correction factor.
// It's equal to 2 on HiDPI a.k.a. Retina displays
int scaleFactor = device.getScaleFactor();
// now we can compute the real DPI of the screen
return scaleFactor * (device.getXResolution() + device.getYResolution()) / 2
/ Toolkit.getDefaultToolkit().getScreenResolution();
} else
return 1.0;
}
This code solves the issue for Mac Retina displays, but I am affraid nowhere else, since CGraphicsDevice is an explicit mention to a proprietary implementation of GraphicsDevice.
I do not have other HDPI hardware with which to play around to have a chance to offer a wider solution.

Is there a function that converts any image into a circle of 150x150 pixels-java?

I need a function/method that can mold(crop and resize) an imported (.png format) image into a circle of exact 150x150 pixels and it should keep transparency. I have searched all over internet, also I have my own code but I think its completely useless. I need this function for a code I am using to make GUI of a social-media app database.
private ImageIcon logo = new ImageIcon(getClass().getResource("/test/test200x200.png"));
toCircle(logo);
I need the code for the following function:
public ImageIcon toCircle(ImageIcon icon)
{
//code
return icon;
}
This function should convert this picture:
To this:
Create a new transparent image
Get a Graphics object from the image.
Set a clip for the graphics object.
Paint the PNG format image.
See also this answer that uses a clipped region.
An alternative approach, that might be more straight-forward to implement for this use case, is:
Create a transparent BufferedImage the size of your icon
Create Graphics2D from image, set hints for antialias
Fill a circle the size of your background circle
Draw the image on top of your circle, using AlphaComposite.SrcIn
Something like:
public Icon toCircle(ImageIcon logo) {
BufferedImage image = new BufferedImage(150, 150); // Assuming logo 150x150
Graphics2D g = image.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.fillOval(1, 1, 148, 148); // Leaving some room for antialiasing if needed
g.setComposite(AlphaComposite.SrcIn);
g.drawImage(logo.getImage(), 0, 0, null);
g.dispose();
return new ImageIcon(image);
}

how to draw a black rectangle over Image icon?

in my application I have a cross road picture in the background and I want to draw traffic lights on the top of it (black rectangle with 3 circles)
The problem is, I cannot see the rectangle at all, as if it was under the image or something. And if I switch the order in which the items are painted, I get all black image.
Do you have any idea how this can be solved?I am new to graphics and searched similar questions, but none helped me.
Thank you.
public MainFrame() throws HeadlessException {
super("semafor");
crossroad = new ImageIcon("cross.png");
initFrame();
initComponents();
sem1 = new Semafor(true, 100, 100);
add(sem1);
repaint();
setVisible(true);
}
//here I paint the image
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(crossroad.getImage(), 0, 45, this);
}
//and in class Semafor i paint the actual traffic lights
#Override
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.darkGray);
//and then the circles
}
The first thing I'm noticing is that you are calling <unknown>.getWidth() and <unknown>.getHeight() for the rectangle size. If it's covering the entire image, this suggests that it is getting that width and height from the panel it is being drawn on.
A simple stack trace,
(new Exception).printStackTrace();
or
Thread.dumpStack();
will tell you as much. You could also query the width and height with a System.out call to verify that you're getting the values you're expecting, or, if this really gets out of control, learn to use JUnit and the assert statement. Honestly, though, it looks like you're just accidentally calling the wrong method.

Slow Java2D Drawing on Integrated Graphics

I'm working on a simple 2D game, rendering via the Java2D API. I've noticed that when I try to draw on integrated graphics card the performance crashes.
I've tested this game on both my main rig with a newer ATI Radeon and my 5 year old laptop which also has an (incredibly antiquated) Radeon. On both I get good FPS, but when I try to use my Intel i5's onboard HD 4000 graphics, it crawls at around 20 FPS.
I'm using Full Screen Exclusive mode.
At any given moment, I am rendering approximately 1000 images at once.
Annoyingly, when I try to getAvailableAcceleratedMemory() it just returns -1 for this card, and it seems to refuse to accelerate any images.
Does anyone have any ideas how to fix this issue?
Rendering code:
Graphics g = bufferStrategy.getDrawGraphics();
g.drawImage(img, x, y, img.getWidth(), img.getHeight(), null)
g.dispose();
bufferStrategy.show();
Image Loading code:
BufferedImage I = null;
I = ImageIO.read(new File(currentFolder+imgPath));
imgMap.put(imgIdentifier, I);
The images are stored in a hashmap of BufferedImages identified by strings, so when an entity needs to draw and image it just gets it out of the hashmap and draws it. In the current case, the entities are mostly floor and wall tiles, so they never change (and thus don't have to get the image from the hashmap other than the very first time).
EDIT - I've incorporated MadProgrammer's method, but it didn't change my FPS.
This is an example of converting an image to a compatiable image...not an answer in of itself
This is some of the library code that I use...
public static BufferedImage createCompatibleImage(BufferedImage image) {
BufferedImage target = createCompatibleImage(image, image.getWidth(), image.getHeight());
Graphics2D g2d = target.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return target;
}
public static BufferedImage createCompatibleImage(BufferedImage image,
int width, int height) {
return getGraphicsConfiguration().createCompatibleImage(width, height, image.getTransparency());
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
I would do something like...
I = createCompatibleImage(ImageIO.read(new File(currentFolder+imgPath)));
imgMap.put(imgIdentifier, I);

Unable to resize image and maintain transparency

The below code resizes an image. Unfortunately the image which is a vertical image has black bars on the sides. It looks as though the transparent, or blank space is being filled with black. I've tried setting the background color to white, and using alphaRGB but can't seem to shake it.
OrderProductAssetEntity orderProductAssetEntity = productAssets.get(jobUnitEntity.getId());
File asset = OrderProductAssetService.getAssetFile(orderProductAssetEntity);
if (asset.exists()) {
//resize the asset to a smaller size
BufferedImage resizedImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
Graphics2D g = resizedImage.createGraphics();
g.setBackground(Color.WHITE);
g.drawImage(ImageIO.read(asset), 0, 0, width, height, null);
g.dispose();
jobUnitImages.put(orderProductAssetEntity.getOriginalLocation(), new PDJpeg(document, resizedImage));
} else {
jobUnitImages.put(orderProductAssetEntity.getOriginalLocation(), null);
}
First, if you need transparency, BufferedImage.TYPE_INT_RGB won't work, you'll need BufferedImage.TYPE_INT_ARGB. Not sure if you already tried it, just want to be clear.
Second, this line:
g.setBackground(Color.WHITE);
...does only set the current background color for the graphics context. It does not fill the background with that color. For that, you'll need to do g.clearRect(0, 0, width, height) as well. But I usually prefer to use g.setColor(...) and g.fillRect(...) instead to avoid confusion.
Or, if you like, you can also use the drawImage method that takes a Color as the second last parameter like this:
g.drawImage(image, 0, 0, width, height, Color.WHITE, null);
EDIT:
Third, the class name PDJpeg implies that the image is later stored as JPEG, so you will probably lose transparency anyway. So the best is probably to stick with TYPE_INT_RGB, and fill the background with the right color (and do it the right way ;-).

Categories

Resources