I've got a Java desktop app that works, amongst other, on OS X.
Now the new MacBook Pro has a retina display and I'm concerned: how is it going to work regarding Swing?
What about when a Java app uses both Swing components and some bitmap graphics (like custom icons / ImageIcon)?
Shall all desktop Java apps be automatically resized (for example by quadrupling every pixel) or am I going to need to create two versions of my icons set (for example one with 24x24 icons and the other with 96x96 icons) and somehow determine that the app is running on a retina display?
Use IconLoader library. It supports HiDPI images http://bulenkov.com/iconloader/ It also provides a way to work with HiDPI images (drawing, etc)
On Apple's Java 6 you can provide multiple versions of the same image. Depending on the screen (retina or not), one or the other image is picked and drawn.
However, those images have to loaded in a special way:
Toolkit.getDefaultToolkit().getImage("NSImage://your_image_name_without_extension");
For example, if your (regular resolution) image is called: "scissor.png", you have to create a high resolution version "scissor#2x.png" (following the Apple naming conventions) and place both images in the Resources directory of your app bundle (yes, you need to bundle your app).
Then call:
Image img = Toolkit.getDefaultToolkit().getImage("NSImage://scissor");
You can use the resulting image in your buttons and it will be drawn with the right resolution magically.
There are two other "tricks" you can use:
Using an AffineTransform of (0.5, 0.5) on your Graphics2D object before drawing an Image. Also see this java-dev message
Creating a high dpi version of your image programmatically using this hack
The first "trick" (0.5 scaling) by now also works on Oracle's Java 7/8.
I.e. if you draw an image with 0.5 scaling directly to the component's Graphics object, it will be rendered in high resolution on Retina displays (and also with half its original size).
Update
Starting with Java 9, there is better built-in support for images with different resolutions via the MultiResolutionImage interface. For more details, please see this answer.
I can confirm that the scaling your images works with on Oracle Java 1.8. I cannot get the NSImage hack to work on java 1.7 or 1.8. I think this only works with Java 6 from Mac...
Unless someone else has a better solution, what I do is the following:
Create two sets of icons.
If you have a 48pixel width icon create one 48px #normal DPI and another at 96px with 2x DPI. Rename the 2xDPI image as #2x.png to conform with apple naming standards.
Subclass ImageIcon and call it RetinaIcon or whatever.
You can test for a Retina display as follows:
public static boolean isRetina() {
boolean isRetina = false;
GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
try {
Field field = graphicsDevice.getClass().getDeclaredField("scale");
if (field != null) {
field.setAccessible(true);
Object scale = field.get(graphicsDevice);
if(scale instanceof Integer && ((Integer) scale).intValue() == 2) {
isRetina = true;
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return isRetina;
}
Make sure to #Override the width and height of the new ImageIcon class as follows:
#Override
public int getIconWidth()
{
if(isRetina())
{
return super.getIconWidth()/2;
}
return super.getIconWidth();
}
#Override
public int getIconHeight()
{
if(isRetina())
{
return super.getIconHeight()/2;
}
return super.getIconHeight();
}
Once you have a test for the retina screen and your custom width/height methods overridden you can customise the painIcon method as follows:
#Override
public synchronized void paintIcon(Component c, Graphics g, int x, int y)
{
ImageObserver observer = getImageObserver();
if (observer == null)
{
observer = c;
}
Image image = getImage();
int width = image.getWidth(observer);
int height = image.getHeight(observer);
final Graphics2D g2d = (Graphics2D)g.create(x, y, width, height);
if(isRetina())
{
g2d.scale(0.5, 0.5);
}
else
{
}
g2d.drawImage(image, 0, 0, observer);
g2d.scale(1, 1);
g2d.dispose();
}
I do not know how this will work with multiple screens though- is there anyone else that can help out with that???
Hope this code helps out anyway!
Jason Barraclough.
Here is an example of using the scaling as mentioned above:
RetinaIcon is on the left. ImageIcon is on the right
Here is a solution, that works also when the icons are used in the apple menu. There the icon is automatically greyed. So I have implemented a class DenseIcon which paints densely:
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
if(getImageObserver() == null) {
g.drawImage(getImage0(), x, y, getIconWidth(), getIconHeight(), c);
} else {
g.drawImage(getImage0(), x, y, getIconWidth(), getIconHeight(), getImageObserver());
}
}
How to hook into the greying I have not yet figured out. So as a kludge we return a low res image so that the menu can do its modifications:
public Image getImage() {
Image image = getImage0().getScaledInstance(
getIconWidth(),
getIconHeight(),
Image.SCALE_SMOOTH);
ImageIcon icon = new ImageIcon(image, getDescription());
return icon.getImage();
}
You find the code of the full class here on gist. You need to instantiate the icon class with an URL to an image that is twice the size. Works for 2K displays.
This how icons look like on my retina macbook '12:
On the left side icons in IntelliJ IDEA 11 (swing app) and on the right side IDEA 12 which is claimed to be retinized. As you can see automatically resized icons (on the left) looks pretty ugly.
As far as I know, they, just like the guys from Chrome team, made it by providing double sized icons.
Related
I recently made the decision to switch over to a newer version of the JDK from the one I was using. (Specifically from jdk1.8.0_261 to jdk-14.0.2). This upgrade when pretty smoothly, as most features work the way I expected them and I was able to find how to adjust to anything that was not the same.
However, as shown in the title, I came across an issue with a specific application I am writing that uses Swing. My main monitor is a 4k monitor, and I have set the windows system scaling for that monitor to 200%. While every other Swing application being scaled to match that DPI setting is nice, for this specific application I want to disable that scaling and have pixels be drawn 1:1, especially when this Application is distributed.
Is there a way to disable this automatic scaling with a VM argument? Or is there a way to disable the scaling at runtime before any calls are made to the swing library? (Similar to how any changes to the look and feel must be done before any other calls to the library.)
For reference:
Here is the code I use to create my JFrame and to create the graphics object I draw everything onto, which happens in a timed loop outside of this class and the scope of this question.
public class ScreenManager {
private GraphicsDevice device;
private final JFrame frame;
private static ScreenManager instance;
public ScreenManager() {
instance = this;
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
this.device = env.getDefaultScreenDevice();
this.frame = new JFrame(game.getGame().gameName());
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.setUndecorated(true);
this.frame.setIgnoreRepaint(true);
this.frame.setResizable(false);
this.device.setFullScreenWindow(frame);
this.device.setDisplayMode(device.getDisplayMode());
this.frame.createBufferStrategy(2);
}
public Graphics2D getRenderGraphics() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
return g;
}
return null;
}
public void updateDisplay() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
if (!strategy.contentsLost()) {
strategy.show();
}
}
Toolkit.getDefaultToolkit().sync();
}
// other methods go here
}
Using AffineTransforms would, while welcome advice, not truly help solve this issue, as I need to be able to display at the native resolution of any monitor and already extensively use AffineTransforms on the Graphics object.
EDIT: I have tried System.setProperty("sun.java2d.dpiaware", "false"); as the first line in main with no success. Is the property wrong?
EDIT 2: Addition of more clarity:
My render method looks like this:
private void render(Graphics2D g) {
// Black out the screen to prevent old stuff from showing
g.setColor(Color.BLACK);
g.fillRect(0, 0, ScreenManager.getScreenWidth(), ScreenManager.getScreenHeight());
// Set the transform for zoom to draw the zoomed stuffs
AffineTransform saveState = g.getTransform();
AffineTransform cameraTransform = ScreenManager.getInstance().getCamera().getCurrentTransform();
g.transform(cameraTransform);
// RENDER UPDATEABLE
this.game.getController().renderGame(g);
// REDNER STATIC GUI
g.setTransform(saveState);
// draw mouse cords
Point mouse = this.mouseHandler.getFrameMousePoint();
if (mouse != null) {
Font f = new Font("Consolas", 0, 40);
Point2D scaledPos;
try {
scaledPos = cameraTransform.inverseTransform(mouse, null);
} catch (NoninvertibleTransformException e) {
scaledPos = mouse;
e.printStackTrace();
}
String s1 = mouse.toString();
String s2 = scaledPos.toString();
Rectangle2D r = f.getMaxCharBounds(g.getFontRenderContext());
int yOff = (int) r.getHeight();
Color c = new Color(100, 100, 100, 191);
g.setColor(c);
g.fillRect(mouse.x, mouse.y, (int) (r.getWidth() * (s1.length() > s2.length() ? s1.length() : s2.length())), yOff + yOff + yOff);
g.setColor(Color.WHITE);
g.setFont(f);
g.drawString(s1, mouse.x, mouse.y + yOff);
g.drawString(s2, mouse.x, mouse.y + yOff + yOff);
}
}
And this method is called here:
Graphics2D g = screenManager.getRenderGraphics();
render(g);
g.dispose();
screenManager.updateDisplay(); // SYNCS SCREEN WITH VSync
I have printed out the AffineTransform that is on the graphics object when it is first created, and that prints like this AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]] which does lead me to conclude that the scaling is done in code. However, the issue is, the things that are rendered with my custom transform ScreenManager.getInstance().getCamera().getCurrentTransform(); are also scaled up by 2, even though I do not contact my transform and set it to mine. (Actually never mind, I realized that I do g.transform on my transform not g.setTransform. I am still including this in the write-up though).
A workaround solution to my rendering problem is to do g.setTransform(AffineTransform.getScaleInstance(1, 1); However, this is more than I want to do and it (after trying it on my code) does not fix the issue with the awt coordinate space. Meaning, The rendering works as desired, but the mouse position does not. When the mouse is in the bottom right corner of my screen, it will give a position that is half of the native resolution of my monitor. This solution does not work for me. Processing time is valuable and the more calculations that must be done to UNDO this feature addition. Here is a link discussing how this would be implemented. I link this to point out the section where it mentions that the developer could do it, but it would take a lot of work on the developers part.
I would still like to know if there is a way to either:
Disable the feature with a command-line VM argument
Disable the feature with a snippet of code
After coming back to this question after a while of working on something else, I have come across the solution I was looking for, mostly by accident.
The solution I was looking for was a Java system property or JVM argument that would disable the DPI awareness of Swing components, meaning that on a system with 200% scaling, the user input space and component rendering would match the native resolution of the screen and not the 200% scaled resolution (eg expecting to read mouse input from and render to a 3840x2160 screen rather than what was happening, where mouse input was capped at 1920x1080).
This solution was discussed in this answer to a similar question. I will restate it here for sake of being complete.
The solution is to pass -Dsun.java2d.uiScale=1 into the VM as a VM argument. This forces the UI scale to be 1, ignoring any system scaling and rendering and gathering mouse input at native resolution. I can also confirm that calling System.setProperty("sun.java2d.uiScale", "1"); before calling any swing class will also produce the result I was looking for.
When ran, the program displays a 3D sphere rendered in a P3D environment in the PGraphics object 'g', which is shown by taking the rendered PGraphics object and displaying it through the image() method in the main graphics context, which happens to be P2D.
The purpose of the program is to show how window size doesn't always correlate with render size. If you play an old Widows98 game in full screen, the game most likely will be rendered at 480p no matter what, so taking it into full screen just decreases the pixels per inch, plus making the image appear blurry. Which is fine, since fullscreen at 480p is preferred over windowed mode ( esp. if you're on 4K X_X )
the mouse's y position in the window changes the 3d camera's field of view, and the x position changes the rendering resolution of the P3D context used to display the sphere. Additionally, the P3D context is drawn in the main (P2D) context through the image() method, and is 'forcefully'. displayed at the size of the window. So if the P3D render resolution is smaller than the window, then it will start to look blurry and more pixelated, and if the render resolution is larger, you get a strange sharpening effect.
Now, my program works fine as it is, but. Another purpose of the program is shadowed by this issue, it's how the 'crispness' of the sphere fades as the render resolution decreases. You might say that it's clearly shown, but what I'm looking for is an image where there is no "anti-alias" effect going on. I want the image to preserve the pixels as the resolution gets smaller, so you can see the actual shape of the sphere at say, 50 x 50 pixels.
The noSmooth() method doesn't seem to work, and before you tell me to just do
g.loadPixels();
and then do a double for loop to draw the raw pixels to the 2d context. No, it's sloppy. I know that there must be some reason why this blurring is going on. I'm hoping that it's the image() method and that I should be using a different method or I should add another method before it to remove image blurring.
PGraphics g;
void setup(){
size(1000,1000,P2D);
frameRate(1000);
noSmooth();
}
void draw(){
background(200);
float res = map(mouseX,0,width,0.75,128);
if (res==0) {
res=1;
}
g = createGraphics((int)(width/res),(int)(height/res),P3D);
g.noSmooth(); // is this thing working?????
float cameraZ = ((height/2.0) / tan(PI*60.0/360.0));
g.beginDraw();
g.perspective(radians(map(mouseY,0,height,0.1,160)), width/height, cameraZ/10.0, cameraZ*10.0);
g.camera(g.width/2.0, g.height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), g.width/2.0, g.height/2.0, 0, 0, 1, 0);
g.background(200);
g.translate(g.width/2 ,g.height/2);
g.sphere(100);
g.endDraw();
image(g, 0, 0, width, height); // this is where it all comes together
text("rendering resolution: "+g.width+" x "+g.height,0,14);
text("fps: "+frameRate,0,14*2);
}
Replace g.noSmooth() with ((PGraphicsOpenGL)g).textureSampling(2);
Credits go to Vallentin as I oddly enough had the same question with the P3D renderer
(Edit: This solution fixes the problem in the default renderer, but the OP is using the P2D renderer. The solution should be similar, so if somebody knows how to change the image interpolation mode in opengl, that's the answer.)
This is not really caused by anti-aliasing. It's caused by image scaling.
Also, it's much easier to help if you provide a MCVE, like this one:
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
This code exhibits the same problem, but it's much easier to understand and work with.
Anyway, tracing through Processing's code, we can see that the image() function eventually calls the imageImpl() function in the PGraphics class here.
This function then draws your image using this code:
beginShape(QUADS);
texture(img);
vertex(x1, y1, u1, v1);
vertex(x1, y2, u1, v2);
vertex(x2, y2, u2, v2);
vertex(x2, y1, u2, v1);
endShape();
The endShape() function is then implemented in the renderer, specifically the PGraphicsJava2D class, which calls the drawShape() function here:
protected void drawShape(Shape s) {
if (fillGradient) {
g2.setPaint(fillGradientObject);
g2.fill(s);
} else if (fill) {
g2.setColor(fillColorObject);
g2.fill(s);
}
if (strokeGradient) {
g2.setPaint(strokeGradientObject);
g2.draw(s);
} else if (stroke) {
g2.setColor(strokeColorObject);
g2.draw(s);
}
}
Finally, that shows us that the Graphics2D.fill() function is being called, which is what actually draws your function.
The "problem" is that Graphics2D.fill() is scaling your image using an algorithm that causes some blurriness. We can consult the Java API and Google to figure out how to fix that though.
Specifically, this tutorial shows you how to set various rendering hints to change the scaling algorithm. We can use that in Processing like this:
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import processing.awt.PGraphicsJava2D;
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
if (mousePressed) {
Graphics2D g2d = ((PGraphicsJava2D)g).g2;
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
First, we import the classes we're going to need. Then we get to the Graphics2D instance in the renderer, and finally we call its setRenderingHint() function. I wrapped it in an if(mousePressed) so you could easily see the difference. When you click the mouse, interpolation is set to nearest neighbor, and you no longer see the blurriness.
Also notice that my code uses the g variable that's inherited from the PApplet superclass, so you would have to change your g variable so it's no longer hiding it.
I've been spending the last hours trying to solve the stack trace below. With major research on here SO and also through Google, I understand the exception can mean several things:
the program can't find the requested images with the provided path;
the images are being rendered after the width and the height are generated, reason why it equals 0...
Am I missing something? I can't figure out how to solve this...
Stack
Exception in thread "main" java.lang.IllegalArgumentException: Width
(-1) and height (-1) cannot be <= 0 at
java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1016)
at java.awt.image.BufferedImage.(BufferedImage.java:331) at
tp6.Interface.toBufferedImage(Interface.java:157) at
tp6.Interface.(Interface.java:36) at
tp6.Interface.main(Interface.java:171)
tp6.Interface.toBufferedImage(Interface.java:157):
public BufferedImage toBufferedImage(Image image) {
if( image instanceof BufferedImage ) {
return( (BufferedImage)image );
} else {
image = new ImageIcon(image).getImage();
BufferedImage bufferedImage = new BufferedImage(
image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_RGB );
Graphics g = bufferedImage.createGraphics();
g.drawImage(image,0,0,null);
g.dispose();
return( bufferedImage );
}
}
tp6.Interface.(Interface.java:36)
//IMAGE JPANEL
Image map=new ImageIcon("images/main.gif").getImage();
Image digi=new ImageIcon("images/digits.gif").getImage();
BufferedImage mapmodifiable= toBufferedImage(map);
BufferedImage digits= toBufferedImage(digi);
tp6.Interface.main(Interface.java:171)
public static void main(String[] args)
{
Window windowintro = new Window( 440, 400, 1);
//INTERFACE GRAPHIC
Interface graphic=new Interface();
graphic.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent evt) {
System.exit(0);
}
});
}
The reason for the exception has already been explained, Image methods getWidth(null) and getHeight(null) both return -1 when the image dimensions are not (yet) known. This is implemented so, because the old Image API in Java is asynchronous and loads image resources off the current thread. As you write, it could also happen because the image is not found.
However, as you want to use your images as BufferedImages (presumably because you want to modify them at some stage), it's better and easier to just load them using the more recent synchronous ImageIO API. In most cases, the code will be clearer and easier to understand, and more importantly; you'll get error messages right away if the image can't be found/loaded.
So, instead of:
Image map = new ImageIcon("images/main.gif").getImage();
BufferedImage mapmodifiable = toBufferedImage(map);
You can simply do:
BufferedImage mapmodifiable = ImageIO.read(new File("images/main.gif"));
PS: It is possible to convert an Image to a BufferedImage like you do in your toBufferedImage method, and using ImageIcon.getImage(..) should ensure the image was preloaded (ImageIcon internally uses a MediaTracker for preloading). However, as I say above, the old Image API is not very good at error feedback, so most likely the problem is that your image isn't found.
I was having this problem too. I solved it by adding one line of code. In your in the first line of your toBufferedImage() method you can put
while(image.getWidth() == -1);
This line will just keep looping until there is a value in getWidth() besides -1.
I found this question concerning your problem. Maybe you are using an asynchronous way to load the images. This mean the image may not be loaded yet, when you are calling getWidth(null) or getHeight(null). Since the image may not be loaded at this time, the width and height may not be known yet. This is the reason why -1 is returned.
Maybe you will get the right result if you add some delay with Thread.sleep(1000). I did not investigate it but it is definitively not a good solution. You may sleep not long enough on some systems. On other systems you may sleep unnecessary long. And since I do not know the specification very well, it may even be a valid implementation of Java if Thread.sleep blocks the process from reading the image (Does someone know it?).
I would see two ways which could be used to solve the problem:
First solution
One solution would be to load the image with blocking IO. Just like descripted in the answers of the linked question.
Second solution
Another solution would be to use an ImageObserver:
int width = getWidth(new ImageObserver() {
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
// TODO use the width when the image is loaded and the size is known
}
});
if (width != -1) {
// TODO use the width, it is already known
}
I am a beginner who is learning to write games in JAVA.
In the game I am writing, I am trying to get it to support multiple displayModes. First let me tell you a little about how I'm setting the display setting in the first place.
In the beginning of the code, I have an list of display modes I wish to support
//List of supported display modes
private static DisplayMode modes[] = {
new DisplayMode(640, 480, 32, 0),
new DisplayMode(1024, 768, 32, 0),
};
I then get a list of supported display Modes from the Video Card, comparing the list and use the first matching display mode.
/////////////////////////////////////////////
////Variable Declaration
/////////////////////////////////////////////
private GraphicsDevice vc;
/////////////////////////////////////////////
//Give Video Card Access to Monitor Screen
/////////////////////////////////////////////
public ScreenManager(){
GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc = e.getDefaultScreenDevice();
}
/////////////////////////////////////////////
//Find Compatible display mode
/////////////////////////////////////////////
//Compare Display mode supported by the application and display modes supported by the video card
//Use the first matching display mode;
public DisplayMode findFirstCompatibleMode(DisplayMode modes[]){
DisplayMode goodModes[] = vc.getDisplayModes();
for(int x=0; x<modes.length; x++){
for(int y=0; y<goodModes.length; y++){
if (displayModesMatch(modes[x], goodModes[y])){
return modes[x];
}
}
}
return null;
}
/////////////////////////////////////////////
//Checks if two Display Modes match each other
/////////////////////////////////////////////
public boolean displayModesMatch(DisplayMode m1, DisplayMode m2){
//Test Resolution
if (m1.getWidth() != m2.getWidth() || m1.getHeight() != m2.getHeight()){
return false;
}
//Test BitDepth
if (m1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && m2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
&& m1.getBitDepth() != m2.getBitDepth()){
return false;
}
//Test Refresh Rate
if (m1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN &&
m2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN &&
m1.getRefreshRate() != m2.getRefreshRate()){
return false;
}
return true;
}
Currently, I am only supporting two resolutions, 640x480 and 1024x768.
In order to have every element of my game available in both resolutions, first I find how much the screen is resized and store this value in a variable called resizeRatio
private void getResizeRatio(){
resizeRatio = (double)1024/(double)s.getWidth();
//s.getWidth() returns the current width of the screen.
}
And with every image I import, i would divide the image height and width by this resizeRatio.
/////////////////////////////////////////////
//Scale the image to the right proportion for the resolution
/////////////////////////////////////////////
protected Image scaleImage(Image in){
Image out = in.getScaledInstance((int)(in.getWidth(null)/resizeRatio), (int)(in.getHeight(null)/resizeRatio), Image.SCALE_SMOOTH);
return out;
}
This is all fine and good, until my application grew bigger and bigger. Soon I realize I forgot to resize some of the icons, and they are all at the wrong place when resolution is 640x480.
Additionally, I realize I must scale, not just the size of all my images, but all the movement speed, and all the positions as well, since having my character move at 5px per refresh makes him move significantly faster when displayed at 640x480 than when displayed at 1024x768
So my question is, instead of individually scaling every image, every icon, and every movement, is there a way to scale everything all at once? Or rather, there must be another way of doing this so could someone please tell me?
Thank you for reading and any help would be much appreciated.
In the paintComponent(Graphics g) or paint method you can do with Graphics2D.scale:
private double scale = 0.75;
#override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g; // The newer more ellaborate child class.
g2.scale(scale, scale);
...
g2.scale(1/scale, 1/scale);
}
In one of my projects I use JUNG2 to visualize a very large multiple-parent hierarchy graph, displayed in an applet. I would need to export the whole/parts of the graph to high resolution still images, since screenshots look hideous when printed (especially if the graph has been zoomed out).
The code I use currently is as follows:
public void writeToDisk(File saveToFolder, String filename) {
//Dimension loDims = getGraphLayout().getSize();
Dimension vsDims = getSize();
int width = vsDims.width;
int height = vsDims.height;
Color bg = getBackground();
BufferedImage im = new BufferedImage(width,height,BufferedImage.TYPE_INT_BGR);
Graphics2D graphics = im.createGraphics();
graphics.setColor(bg);
graphics.fillRect(0,0, width, height);
paintComponent(graphics);
try{
ImageIO.write(im,"png",new File(saveToFolder,filename));
}catch(Exception e){
e.printStackTrace();
}
}
This creates PNG images which are not particularly high resolution. So my questions are as follows:
Is it possible to push up the PNG export resolution to 300 dpi?
Is it possible to export the graph, or any swing component for that matter, to vector based formats such as EPS, PDF or SVG without too much hassle? I have found several libraries (VectorGraphics2D,FreeHEP) for managing vector based images in Java, however I am not sure if using them would mean that I have to "re-draw" each vertex and edge in the graph. That's obviously not very desirable...
Are there any other alternatives which I might have missed?
Thanks in advance,
Thanks for the suggestions but I have managed to get FreeHEP Vector Graphics library working the way I want to. I am sharing the code below in case anyone runs into the same questions.
The above-named library has a very nice built-in export menu, which handles the export to a bunch of different formats. Code excerpt from the modified ´ModelGraphMouse´ class:
protected void handlePopup(MouseEvent e) {
final VisualizationViewer<MyNode, MyEdge> vv = (VisualizationViewer<MyNode, MyEdge>)e.getSource();
Point2D p = e.getPoint();
GraphElementAccessor<MyNode, MyEdge> pickSupport = vv.getPickSupport();
if(pickSupport != null) {
final MyNode v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY());
// if clicked on a vertex -> show info popup
// else show contexual menu
if(v != null) {
JFrame popup = new JFrame("Node: " + v.getId());
popup.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
...
} else{
JPopupMenu menu = new JPopupMenu();
JMenuItem exportGraphMenuItem = new JMenuItem("Export graph to vector image...");
exportGraphMenuItem.addActionListener(new ExportActionListener((WritingVisualizationViewer<V, E>) vv));
menu.add(exportGraphMenuItem);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
}
and the action listener:
public class ExportActionListener implements ActionListener{
private VisualizationViewer<V, E> wvv;
public ExportActionListener(VisualizationViewer<V, E> vv) {
this.wvv = vv;
}
#Override
public void actionPerformed(ActionEvent e) {
ExportDialog export = new ExportDialog();
export.showExportDialog(wvv, "Export view as ...", wvv, "export");
}
}
Basically a PNG suffices. The dimension of resolution in a BufferedImage is pixels, not dpi. So you need to double/triple your width and height to receive a better resolution.
Graphics2D could scale too for the JUNG graphs.
You might wanna use Batik for that : http://xmlgraphics.apache.org/batik/using/svg-generator.html
you can use Xchart and then export pictures using vectorgraphics2d to SVG or PDF