Java Image Scaling Quality Lost in JAR File - java

I am coding a program which has an entire JPanel zoomed in with a JScrollPane as a map, but I also want to give users a scaled & minimized view of the whole map in the corner of the JScrollPane. I am using BlueJ, & (in BlueJ, at least) I have basically gotten what I want. To scale down the map, I create a single BufferedImage, bi, that is the size of the mini-map, then take its Graphics object, g, convert it to a Graphics2D object, g2, then give g2 a single set of RenderingHints, through:
g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
From there, I use the Graphics.scale(double sx, double sy) method on g2 to set the scale so that upon calling the paint(Graphics g) method of the large map with g2 as its parameter, it will draw a scaled version of the large map on bi that is the exact size of the mini-map. This happens 20 times per second (somewhat arbitrary, at that speed I think it looks best. The mini-map reflects what the user is doing in real-time & I think it looks laggy if done fewer than 20 times per second.)
On my Macbook Pro, The resulting look of this code is:
There is no noticeable lag with this code when I run the program, which has led me to keep the picture re-sampling at 20 times per second, since there appears to be plenty of CPU for this. However, upon exporting my project to a JAR file, the resulting look is this:
There still is little to no lag with the program running, but what the heck happened? So long as I run the code in BlueJ, it works - but in a JAR file, the mini-map looks horrible.
For clarification, this is the map to be scaled (which, ironically, is being scaled when I'm placing it in this post):
And here are the two BlueJ & JAR file mini-maps, side-by-side:
What could be the cause of this?
When I run "java -version", this is the output:
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
Tl;dr:
When I run my code in BlueJ, the small picture looks good. After exporting to a JAR file, it looks bad. What could be causing this?
EDIT: I am now including an SSCCE to give a better idea of what I mean.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import javax.swing.border.*;
public class ScaleTest
{
int scrollSpeed = 16, imageSize = 200;
int rows = 40, columns = 40;
JFrame f1 = new JFrame("ScaleTest: Full Size");
JScrollPane scrollPane = new JScrollPane();
JPanel f1Panel = new JPanel(new GridLayout(rows,columns,1,1));
JFrame f2 = new JFrame("ScaleTest: Scaled");
JPanel f2Panel = new JPanel()
{
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.drawImage(bi,0,0,null);
}
};
BufferedImage bi = new BufferedImage(imageSize,imageSize,BufferedImage.TYPE_INT_ARGB);
public ScaleTest()
{
f1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f1.add(scrollPane);
scrollPane.setPreferredSize(new Dimension(600,600));
scrollPane.getHorizontalScrollBar().setUnitIncrement(scrollSpeed);
scrollPane.getVerticalScrollBar().setUnitIncrement(scrollSpeed);
scrollPane.setViewportView(f1Panel);
for (int i = 0; i < rows*columns; i++)
{
JPanel p = new JPanel();
p.setBackground(Color.WHITE);
p.setBorder(BorderFactory.createLineBorder(Color.BLACK,1));
p.setPreferredSize(new Dimension(100,100));
f1Panel.add(p);
}
f1.pack();
f2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f2.setResizable(false);
f2.add(f2Panel);
f2Panel.setPreferredSize(new Dimension(imageSize,imageSize));
f2.pack();
Graphics2D g2 = (Graphics2D)bi.getGraphics();
g2.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON));
g2.scale(((double)imageSize)/f1Panel.getWidth(),((double)imageSize)/f1Panel.getHeight());
f1Panel.paint(g2);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
ScaleTest s = new ScaleTest();
s.f1.setVisible(true);
s.f2.setVisible(true);
}
}
In BlueJ the resulting scaled image is this:
Yet, in the JAR file that is produced from BlueJ, running the JAR produces this image:
What could be going on?

I would guess that your BlueJ using JDK1.6.0.
java version "1.6.0_45"
java version "1.8.0_31"

I found this answer on SE: https://stackoverflow.com/a/24746194/1695856 .
The upshot of it is that one way to improve the quality of the scaled down image is to blur it and scale it down in stages. In the following example I scaled the image down by 50% three times, and then by 80%, to get a final scale of 10% of the original.
The file referred to (map.png) was the large image you posted earlier. Setting the Antialiasing rendering hint is of no use when drawing scaled images as it only has an effect on lines, ovals, rectangles etc, but not images, from what I can tell. The Interpolation rendering hint helps a little, but relying on any rendering hint is not always going to work as they may not be implemented the same (or at all!) on different platforms.
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Rescale {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
private ConvolveOp cop;
#Override
public void run() {
JFrame frame = new JFrame("Map");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
BufferedImage im = ImageIO.read(new File("map.png"));
cop = new ConvolveOp(new Kernel(3, 3, new float[] { 0f, 1f / 5f, 0f, 1f / 5f, 1f / 5f, 1f / 5f, 0f, 1f / 5f, 0f }),
ConvolveOp.EDGE_NO_OP, null);
for (int i = 0; i < 3; i++) {
im = getScaled(im, 0.5);
}
im = getScaled(im, 0.8f);
JLabel lab = new JLabel(new ImageIcon(im));
frame.setContentPane(lab);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException e) {
e.printStackTrace();
}
}
private BufferedImage getScaled(BufferedImage im, double scale) {
BufferedImage nim;
Dimension dim = new Dimension((int) (im.getWidth() * scale), (int) (im.getHeight() * scale));
nim = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) nim.getGraphics();
g.scale(scale, scale);
g.drawImage(im, cop, 0, 0);
g.dispose();
return nim;
}
});
}
}
I hope this helps.

Related

How can I display my Java (Swing GUI) application with a higher pixel density?

My application is showing up quite pixelated / blurry, as if I didn't have a retina (High PPI) display. How can I increase the pixel density of the canvas so that everything looks sharp?
Operating System: macOS Mojave 10.14.4 Java: "12" 2019-03-19
I have tried transforming, scaling, rendering hints etc.. but I can't seem to get any higher pixel density.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SimpleCanvas
{
private JFrame frame;
private CanvasPane canvas;
private Graphics2D graphic;
private Image canvasImage;
/**
* Creates and displays a SimpleCanvas of the specified size and background
*/
public SimpleCanvas(String title, int width, int height, Color bgColour) {
frame = new JFrame();
canvas = new CanvasPane();
frame.setContentPane(canvas);
frame.setTitle(title);
canvas.setPreferredSize(new Dimension(width,height));
frame.pack();
Dimension size = canvas.getSize();
canvasImage = canvas.createImage(size.width,size.height);
graphic = (Graphics2D) canvasImage.getGraphics();
graphic.setColor(bgColour);
graphic.fillRect(0,0,size.width,size.height);
graphic.setColor(Color.black);
frame.setVisible(true);
// higher quality rendering
graphic.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphic.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphic.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphic.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
graphic.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
graphic.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
graphic.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphic.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
}
/**
* Draws a disc on this SimpleCanvas centred at x,y with radius r with colour c.
*/
public void drawDisc(int x, int y, int r, Color c) {
int d = r*2;
setForegroundColour(c);
graphic.fillOval(x - r, y - r, d, d);
}
class CanvasPane extends JPanel {
public void paint(Graphics g) {
g.drawImage(canvasImage,0,0,null);
}
}
}
I think the sad answer is: not possible.
When you check out this answer from 2013, you will find that people did not find a way to "force" higher DPI onto their swing applications.
And to my knowledge, nothing changed about that.
The really sad answer is that Java isn't a good language for creating a really great "modern" user experience, at least regarding "fat" desktop GUI clients. And as you can see on that other answer, Java9 promised to do better, but it seems: it doesn't.
If at all, the answer might be to go try JavaFX. Which is a problem by itself, as correctly bundling a JavaFX app can turn into a problem of its own.

BufferedImage.setRGB pixel wrong color

Trying to do something relatively simple, given a 512x512 png of a map, I'm attempting to plot points. My code's fairly straightforward, I've tried using both the setRGB function and the Graphics2D object that is returned by the createGraphics function. I must be overlooking something simple. EDIT: I should mention that I'm not looking to create a new BufferedImage, I'm looking to modify the existing BufferedImage, since successive library calls will continue to modify the BufferedImage that I'm working with. (In the example code below, I read the BufferedImage from a file, for a simple way to replicate the issue.
File outputImage = new File("before.png");
BufferedImage img = ImageIO.read(outputImage);
img.setRGB(255, 255, new Color(0f, 1f, 0).getRGB());
File after = new File("after.png");
ImageIO.write(img, "png", after);
If you zoom in on the resulting pixel, it's not green, but some darker grey. Since this behavior is uniform with the Graphics2D, I'm hoping solving this problem will address that as well.
The color space of the BufferedImage must be causing a problem.
In the code below I use your original image and paint it to a BufferedImage with the specified color space:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.io.*;
import javax.imageio.*;
import java.net.*;
public class SSCCE extends JPanel
{
SSCCE()
{
try
{
BufferedImage original = ImageIO.read( new File("map.png") );
int width = original.getWidth(null);
int height = original.getHeight(null);
int size = 100;
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = bi.createGraphics();
g2d.drawImage(original, 0, 0, null);
int color = new Color(0f, 1f, 0f).getRGB();
bi.setRGB(10, 10, color);
bi.setRGB(10, 11, color);
bi.setRGB(11, 10, color);
bi.setRGB(11, 11, color);
add( new JLabel( new ImageIcon(bi) ) );
}
catch(Exception e2) {}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Here's an attempt to describe a few ways you could work around the problem, based on discussion in the comments:
The problem is that the original image is using an IndexColorModel (or a color map or "palette" if you like). There is no green color that exactly matches the color you specify, so instead the color model does a lookup to get the "closest" color to the one you specified (you may not agree to this color being the closest match, but it is given the algorithm used).
If you set the color to one matching the colors of the image, you can paint in that color. Try new Color(0.8f, 0.9019608f, 0.6392157f) or RGB value 0xffcce6a3 for the light green one. Use new Color(0.6392157f, 0.8f, 1f) or 0xffa3ccff for the light blue.
If you wonder how I found those values, here's the explanation. Assuming colorModel is an IndexColorModel, you can use:
int[] rgbs = new int[colorModel.getMapSize()];
colorModel.getRGBs(rgbs);
...to get the colors in the color map. Choosing one of these colors should always work.
Now, if your "library" (which you haven't disclosed much details about) is using a fixed palette for generating these images, you are good, and can use one of the colors I mentioned, or use the approach described to get the colors, and choose an appropriate one. If not, you need to dynamically find the best color. And if you're really out of luck, there might be no suitable color available at all (ie., your map tile is all ocean, and the only color available is sea blue, it will be impossible to plot a green dot). Then there's really no other way to solve this, than to modify the library.
A completely different approach, could be similar to #camickr's solution, where you temporarily convert the image to true color (BufferedImage.TYPE_INT_RGB or TYPE_3BYTE_BGR), paint your changes onto this temporary image, then paint that image back onto the original. The reason why this might work better, is that the composing mechanism will use a dither and a better color lookup algorithm. But you'll still have the same issue related to available colors as described in the previous paragraph.
Here's a code sample, using the warm yellow color, and the output:
Color color = new Color(0.89411765f, 0.5686275f, 0.019607844f);
int argb = color.getRGB();
Graphics2D g = image.createGraphics();
try {
g.setColor(color);
g.fillRect(10, 10, 50, 50);
}
finally {
g.dispose();
}

Reflecting text in java

I am trying to make a digital clock in this way:
As shown in the image 1 i want the clock to be reflected.
What i have tried:
I tried using java (Graphics2D)g by rotating the string and the using substring but i got this problem:
My code for this:
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Calendar;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class DigitalClock extends JPanel {
JLabel label = new JLabel();
int c = 0;
Font font = null;
JFrame frame = new JFrame();
//Constructor
public DigitalClock(){
frame.setSize(700,500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
frame.setVisible(true);
DigitalThread();
}
//Paint Method
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D)g;
//The background
g2d.setColor(Color.BLACK);
g2d.fillRect(0,0,500,100);
//Show Time
g2d.setColor(Color.WHITE);
g2d.setFont(new Font(g.getFont().toString(),10,15));
g2d.drawString(timeNow(),100, 25);
//Show time Reflected
g2d.rotate(Math.PI,100,25);
g2d.drawString(timeNowRot(timeNow()),45, 20);
}
//Change time Value with this Thread
public void DigitalThread(){
new Thread(new Runnable(){
public void run(){
boolean flag=true;
while(flag==true){
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
repaint();
}
}}).start();
}
//Return time
public String timeNow(){
return zero(Calendar.getInstance().get(Calendar.HOUR_OF_DAY))+":"+
zero(Calendar.getInstance().get(Calendar.MINUTE))+":"+zero(Calendar.getInstance().get(Calendar.SECOND));
}
//Return time reflected
public String timeNowRot(String time){
return time.substring(time.length()-1,time.length())+time.substring(time.length()-2,time.length()-1)+":";
}
//Add Zero if value<10
public String zero(int num){
if(num<10) return "0"+num;
else return ""+num;
}
}
I can achieve this by using java 2d? is there a method to rotate a string again vertically so i have not this problem thanks..
Use scale(x, y) which multiplies the x and y coordinates.
g2d.scale(1.0, -1.0);
g2.drawString(....);
g2d.scale(1.0, -1.0); // Undo
This does a transformation where the y axis is reversed.
You could also use shear for a paralellogram look.
A g2d.scale(-1.0, 1.0); would draw the string backwards. Can be used in combination with the rotation.
It allt depends on the usage of any rotation and the order, how to scale. Without rotation: scale y by -1:
good
ƃooq
Actually i found the answer based n your answers thanks all:
It is and irony but the next day after the question i had a lesson at university about that...
Info about scaling:
g2d.scale(1,-1); /*scale by y axes(Flipping vertical
|
flip here (scale(-1,1) ) | here it was
_ _ _ _ _|_ _ _ _ _
|
flip here (scale(-1,-1) ) | fliped here( scale(1,-1) )
|
*/
g2d.drawString(timeNow(),10,-27);
I also added some more effect so the clock be more realistic like:
g2d.setPaint(new GradientPaint(0,-15,color,0,-50,Color.BLACK));
Your link shows a clock face AND its reflection. If that's what you want to do then this is the way.
Draw the clock face into an Image, instead of direct to the Component. Then you render the resulting image into the component twice using drawImage - once the normal way, and once transformed so it looks reflected.
This uses slightly more memory, but only temporarily, and it saves you having to do the paint twice.
If you only want to draw the reflected clock face, then set the image transformation properties in the Graphics to get the reflection.
I'll leave the details as an exercise to the reader.

Scale images as a single surface in Java 2D API

There is a method called scale(double sx, double sy) in Graphics2D in Java. But this method seems like to scale images as separate surfaces rather than a single surface. As a result, scaled images have sharp corners if original images have no extra width and height. The following screenshot demonstrates the problem:
Here is the code:
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class TestJava {
static int scale = 10;
public static class Test extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.scale(scale, scale);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage img = null;
try {
img = ImageIO.read(new File("Sprite.png"));
} catch (IOException e) {
e.printStackTrace();
}
g2.drawImage(img, null, 5, 5);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Test test = new Test();
test.setBackground(Color.WHITE);
frame.add(test);
frame.setSize(300, 350);
frame.setVisible(true);
}
}
One possible solution to the problem is original images having extra width and height (in this case "Sprite.png"). But this does not seem to be a good way to eliminate the problem. So I am seeking for a programmatic way in Java to solve this problem rather than using an image editor. What is the way to do so?
In your example it's not the image you scale, but you set a scaling transformation on the Graphics2D object which will be applied on all operations performed on that graphics context.
If you want to scale an image, you have 2 options. All I write below uses java.awt.Image, but since BufferedImage extends Image, all this applies to BufferedImage as well.
1. Image.getScaledInstance()
You can use the Image.getScaledInstance(int width, int height, int hints) method. The 3rd parameter (the hints) tells what scaling algorithm you want to use which will affect the "quality" of the scaled image. Possible values are:
SCALE_DEFAULT, SCALE_FAST, SCALE_SMOOTH, SCALE_REPLICATE, SCALE_AREA_AVERAGING
Try the SCALE_AREA_AVERAGING and the SCALE_SMOOTH for nicer scaled images.
// Scaled 3 times:
Image img2 = img.getScaledInstance(img.getWidth(null)*3, img.getHeight(null)*3,
Image.SCALE_AREA_AVERAGING);
// Tip: you should cache the scaled image and not scale it in the paint() method!
// To draw it at x=100, y=200
g2.drawImage(img2, 100, 200, null);
2. Graphics.drawImage()
You can use different Graphics.drawImage() overloads where you can specify the size of the scaled image. You can "control" the image quality with the KEY_INTERPOLATION rendering hint. It has 3 possible values:
VALUE_INTERPOLATION_NEAREST_NEIGHBOR, VALUE_INTERPOLATION_BILINEAR,
VALUE_INTERPOLATION_BICUBIC
The VALUE_INTERPOLATION_BILINEAR uses a bilinear interpolation algorithm of the 4 nearest pixels. The VALUE_INTERPOLATION_BICUBIC uses a cubic interpolation of the 9 nearby pixels.
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// To draw image scaled 3 times, x=100, y=200:
g2.drawImage(img, 100, 200, img.getWidth(null)*3, img.getHeight(null)*3, null);
Removing sharp edges
If you want to avoid sharp edges around the image, you should write a loop to go over the pixels at the edge of the image, and set some kind of transparency, e.g. alpha=0.5 (or alpha=128). You might also do this on multiple rows/columns, e.g. 0.8 alpha for the edge, 0.5 alpha for the 2nd line and 0.3 alpha for the 3rd line.
An interesting question (+1). I think that it is not trivial to find a good solution for this: The interpolation when scaling up the image always happens inside the image, and I can not imagine a way to make it blur the scaled pixels outside the image.
This leads to fairly simple solution: One could add a 1-pixel-margin around the whole image. In fact, this is the programmatic way of the solution that you proposed yourself. The resuld would look like this:
(the left one is the original, and the right one has the additional 1-pixel-border)
Here as a MCVE, based on your example
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class ScaledPaint
{
static int scale = 10;
public static class Test extends JPanel
{
BufferedImage image = createTestImage();
BufferedImage imageWithMargin = addMargin(image);
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.scale(scale, scale);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2.drawImage(image, 5, 5, null);
g2.drawImage(imageWithMargin, 30, 5, null);
}
}
private static BufferedImage createTestImage()
{
BufferedImage image =
new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
g.setColor(Color.RED);
g.drawOval(0, 0, 19, 19);
g.dispose();
return image;
}
private static BufferedImage addMargin(BufferedImage image)
{
return addMargin(image, 1, 1, 1, 1);
}
private static BufferedImage addMargin(BufferedImage image,
int left, int right, int top, int bottom)
{
BufferedImage newImage =
new BufferedImage(
image.getWidth() + left + right,
image.getHeight() + top + bottom,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, left, top, null);
g.dispose();
return newImage;
}
private 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;
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Test test = new Test();
test.setBackground(Color.WHITE);
frame.add(test);
frame.setSize(600, 350);
frame.setVisible(true);
}
}
But...
... one problem with this approach can already be seen in the screenshot: The image becomes larger. And you'll have to take this into account when painting the image. So if your original sprites all had a nice, predefined, easy-to-handle size like 16x32, they will afterwards have a size of 18x34, which is rather odd for a tile. This may not a problem, depending on how you are handling your tile sizes. But if it is a problem, one could think about possible solutions. One solution might be to ...
take the 16x32 input image
create a 16x32 output image
paint the 16x32 intput image into the region (1,1)-(15,31) of the output image
But considering the fact that in sprites of this size, every single pixel may be important, this may have undesirable effects as well...
An aside: Altough I assume that the code that you posted was only intended as a MCVE, I'd like to point out (for others who might read this question and the code) :
You shoud NOT load images in the paintComponent method
For efficient painting, any PNG that is loaded (particularly when it contains transparency) should be converted into an image with a known type. This can be done with the convertToARGB method in my code snippet.

Draw an image of growing size in Java

I have an application where I want to draw an image that grows in width over time. Specifically, the application listens to the microphone and computes a spectrogram every 6 milliseconds, and I want to draw the updated spectrogram when it comes in. I've been using java.awt.image.BufferedImage to draw the spectrograms, but only for pre-recorded files, so I have a fixed width for the image. What's the best way to do this for a streaming application, where I don't know a priori the width of the image?
One possibility is to just create a new BufferedImage with one extra pixel on the right and copy the data over, but that seems inefficient to do hundreds of times per second. Or I could start with a relatively large width and keep the right side blank until it fills up, and double the width when it does, similar to how an ArrayList amortizes its size - I would only have to copy the data a few times per second, or once every few seconds, or so. Is there a better option?
I would use a combination of what you suggest and an overriding component that only paints a subimage of the total image and returns an appropriate preferred size.
Here is a demo code which shows what I mean:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestRecording {
public static class MyPanel extends JPanel {
private BufferedImage buffer = new BufferedImage(3000, 100, BufferedImage.TYPE_INT_RGB);
private int width = 0;
private int lastY = 50;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (width > 0) {
BufferedImage sub = buffer.getSubimage(0, 0, width, buffer.getHeight());
g.drawImage(sub, 0, Math.max(0, (getHeight() - buffer.getHeight()) / 2), this);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, 100);
}
protected void drawSomething() {
// Here need to handle growing image
Graphics g = buffer.getGraphics();
g.setColor(Color.GREEN);
int y = new Random().nextInt(buffer.getHeight());
g.drawLine(width, lastY, width + 1, y);
lastY = y;
width += 1;
Rectangle r = new Rectangle();
r.x = getWidth();
// Lame hack to auto-scroll to the end
scrollRectToVisible(r);
revalidate();
repaint();
}
}
protected void initUI() {
JFrame frame = new JFrame(TestRecording.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final MyPanel p = new MyPanel();
JScrollPane scrollpane = new JScrollPane(p);
frame.add(scrollpane);
frame.setSize(400, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer t = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.drawSomething();
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestRecording().initUI();
}
});
}
}
And here is the result:
May be just scale the original image to get desired size.
Image has getScaledInstance(int width, int height, int hints) method
Do you have 1) gui where you draw it, or 2) do you need to realy output image file (or stream)?
if 1)
Draw only what is seen. Extend JComponent, I usually use JPanel and override paintComponent and paint what is visible.
To make it more efficient, create "tiles" - list of eg. BufferedImage of constant width and create them from incoming data and draw to gui only them.
2) Something similar, but you can use one image with relatively low width and draw new data to it. When full, "append" it to so far created "left" image (initially empty). This way you frequently modify small image and big not so frequently.
If end comes before filling whole right image, join only filled part with left.
You could try to gradually increase size of right image (*X, eg *1.5 or *2) to reduce number of joining images at cost of using more memory.
You could store those "images" as byte(or int) array (1D array representing 2D, but store it by columns, not by rows for more efficient modifications), this way you can store it bit more efficiently if you know that you need some unusual ammount of bits per pixels, because some colors will never be in result image.
If image gets too big, save it to disk and clear your left image and later join them together or use them separately.

Categories

Resources