Java Applet Wrapper Scale Output - java

I am creating a wrapper for Notch's "Prelude of the Chambered", and I wish to make it so that you can resize the window. I can not figure out how I could make the contents scale, I have tried to override onPaint. I can not modify the Jar to get this done.
onPaint attempt:
package com.gudenau.pc.poc;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class JScaledFrame extends JFrame {
private static final long serialVersionUID = 4044340683411982494L;
public JScaledFrame(String title) {
super(title);
}
#Override
public void paint(Graphics graphics){
Dimension min = getMinimumSize();
Dimension size = getSize();
BufferedImage image = new BufferedImage(min.width, min.height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics subGraphics = image.getGraphics();
super.paint(subGraphics);
subGraphics.dispose();
graphics.drawImage(image, 0, 0, size.width, size.height, null);
}
}

Scaling the content is very difficult. What you want to is change the scaling context of the Graphics context painting the underlying component.
Typically, what you would do, is override the paint method of the offending component and apply the scaling factor you want...
public class MyExtendedClass extends ... {
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g.create();
double scaleFactor = 1d;
// Calculate the scaling factor to apply
// based on the "default" size and the
// current size...
g2d.scale(scaleFactor, scaleFactor);
super.paint(g2d);
g2d.dispose();
}
}
This could introduce all kinds of weirdness and frankly a more robust solution might be to use JXLayer/JLayer, for example...
Zooming JLayeredPane via JLayer and the LayerUI
How to add MouseListener to item on Java Swing Canvas
You may also find using an AffineTransform easier...

Do not paint on top level container such as JFrame. Use JComponent or JPanel. Override paintComponent() for painting rather than paint() and don't forget to call super.paintComponent(g).
Do painting in paintComponent nothing else. Avoid putting extra program logic or unnecessary allocations into that method. Painting operations should be fast and optimized for better performance and user experience.
Also, avoid null image observers when calling drawImage. JPanel for instance implements ImageObserver, so you can pass this if extending JPanel.
See Performing Custom Painting for more information. Also see Painting in AWT and Swing.

Since it seems as though this will be hard to do with normal Java I will implement some dark magic with the ASM libraries. I was hoping it would not need to be done this way, but it seems as though scaling a canvas that is loaded from an external jar is not possible.

Related

Slow double buffering method

I've been trying to implement double buffering in my game library, but it is dramatically slower than without it.
This is my class that implements it. Basically, it draws onto an image, and then draws the image onto the JPanel's graphics (pg).
public abstract class DrawingPanel extends GameLoop {
private BufferedImage image;
private Graphics2D ig;
private Graphics pg;
private int backgroundRGB;
public DrawingPanel(final int fps, final int ups, final JPanel panel, final Color background) {
super(fps, ups);
initialize(panel, background);
}
/**
* Creates a panel with 60 fps and 120 ups
*
* #param panel
* #param background
*/
public DrawingPanel(final JPanel panel, final Color background) {
this(60, 120, panel, background);
}
public DrawingPanel(final JPanel panel) {
this(panel, Color.WHITE);
}
public DrawingPanel(final int ups, final JPanel panel, final Color background) {
super(ups);
initialize(panel, background);
}
private void initialize(final JPanel panel, final Color background) {
image = GraphicsUtils.createImage(panel.getWidth(), panel.getHeight(), Transparency.OPAQUE);
ig = (Graphics2D) image.getGraphics();
pg = panel.getGraphics();
GraphicsUtils.prettyGraphics(ig);
backgroundRGB = background.getRGB();
panel.addMouseListener(this);
panel.addMouseMotionListener(this);
panel.addKeyListener(this);
panel.addMouseWheelListener(this);
panel.setFocusable(true);
panel.requestFocusInWindow();
}
#Override
public void draw() {
// set background
Arrays.fill(((DataBufferInt) image.getRaster().getDataBuffer()).getData(), backgroundRGB);
// draw on the buffer
draw(ig);
// draw our buffer to the screen
pg.drawImage(image, 0, 0, null);
}
public abstract void draw(Graphics2D g);
}
The results of this:
This means that 25% - 6% = 19% of the time is being spent in the draw method alone!
I thought it might be the fact that I was using Array.fill, but it turned out not to be a useless "premature optimization;" if I replaced that line with
ig.setColor(backgroundColor);
ig.fillRect(0, 0, image.getWidth(), image.getHeight());
The time it takes is even longer: 26 - 5% = 21%. Is there any way I can speed this method up?
By the way, the GraphicsUtils.createImage creates a compatible image from my GraphicsConfiguration.
This entire library is on github, if you want to look at the entire code.
This is NOT how painting works in Swing -> pg = panel.getGraphics(); this is a horribly bad idea. You are fighting between the passive rendering engine of Swing and your attempt to paint to a "snap shot" of the component.
Swing components are, when you use them properly, double buffered internally, but, you need to be working with the API.
Start by having a look at Performing Custom Painting and Painting in AWT and Swing for more details about how painting works in Swing
Basically, what's happening is, you are using the results from getGraphics to paint to the panel, the RepaintManager is coming along and decides it needs to update your component and repaints it, which produces a blank panel, repeat really fast. This is what's causing your flickering. In Swing, you do not control the paint process, you can only ask to be notified when a paint cycle occurs (overriding paintComponent for example) and make requests to the painting system that a repaint should occur, but it's up to the paint system to actually decide WHEN that paint cycle occurs.
Instead, start by overriding the panel's paintComponent method and perform ALL your custom painting within it. Use repaint to request that the component be updated.
If you "really" need a active painting process, then have a look at BufferStrategy and BufferStrategy and BufferCapabilities, which provides you the means to directly control when the output is pushed to the screen device.

JPanel inside JScrollPane scrollbars not showing up

I am currently trying to make it possible for a JPanel to be zoomed in. My idea is pretty much as follows :
I have a JPanel (custom with overriden paintComponent etc.) that I place inside my JScrollPane.
What I do to zoom in is to scale up my JPanel using the following code (overriding the paint method)
#Override
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
if (m_hasBeenScaled)
{
m_transform.scale(m_zoomValue, m_zoomValue);
g2.setTransform(m_transform);
m_transform = new AffineTransform();
}
super.paint(g);
}
This works well, however my JScrollPane doesn't display scrollbars as I scale to bigger dimensions. How do I make the JScrollPane respond to this scale up of my JPanel ?
Here's the code I use to create both my JPanel and JScrollPan (Grid is my class extending JPanel):
m_gridPanel = new Grid();
m_gridContainer = new JScrollPane(m_gridPanel);
m_gridContainer.setPreferredSize(new Dimension(605, 605));
The size of the component will be affected by the zoom factor as well, to that end, setting the preferredSize to a "static" value makes no sense, instead, you should be overriding the getPreferredSize and adjusting the size returned by applying the zoom factor to it as well.
Zooming a component is much more complex than changing the transform, you should be translating the mouse events as well, see How to add MouseListener to item on Java Swing Canvas for example.

Background image for a jPanel not working

I am new to making GUIs so I decided to try the the windows builder for eclipse, and while great I do have some doubts. I have been searching but I cannot seen to find a good way to add a background image to my "menu". For example I tried this:
public Menu() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(50, 50, 300, 250); //Dimensiones
contentPane = new JPanel() { //Imagen de Fondo
public void paintComponent(Graphics g) {
Image img = Toolkit.getDefaultToolkit().getImage(
Menu.class.getResource("/imgs/rotom.jpg"));
g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this);
}
};
And adding the following classes:
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
But to no avail the window remains with its dull grey color, so far my code is just the standard one WindowsBuilder cooks for you plus 4 buttons but I doubt they're of importance here. Shouldn't the code I added override the paintComponent() method of the jPanel and draw the image in it?
The class for the menu is in a package within my project and the image is within a imgs package is within the same project as well.
Thanks a lot in advance.
A simple method, if you're not interested in resizing the background image or applying any effects is to use a JLabel...
BufferedImage bg = ImageIO.read(Menu.class.getResource("/imgs/rotom.jpg"));
JLabel label = new JLabel(new ImageIcon(bg));
setContentPane(label);
setLayout(...);
There are limitations to this approach (beyond scaling), in that the preferred size of the label will always be that of the image and never take into account it's content. This is both good and bad.
The other approach, which you seem to be using, is to use a specialised component
public class BackgroundPane extends JPanel {
private BufferedImage img;
public BackgroundPane(BufferedImage img) {
this.img = img;
}
#Override
public Dimension getPreferredSize() {
return img == null ? super.getPreferredSize() : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
}
}
You should avoid trying to perform any task in the paintComponent method which may take time to complete as paintComponent may be called often and usually in quick succession....
Getting the image to scale when the component is resized is an entire question into of it self, for some ideas, you could take a look at...
Java: maintaining aspect ratio of JPanel background image
Java: JPanel background not scaling
Quality of Image after resize very low -- Java
Reading/Loading images
Oh, and, you should avoid extending directly from top level containers, like JFrame, they reduce the reusability for your components and lock you into a single container

Java Paint Problem

Ok I'm wondering why the code below will not display the JLabel.
MyPanel is getting added correctly to a JFrame and everything because it all displays but will not draw the JLabel. Any help is appreciated.
public class MyPanel extends JPanel {
private Root root;
...
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
root.paint(g2);
}
}
class Root {
private Node1 node1;
...
public void paint(Graphics g) {
node1.paint(g);
}
}
class Node1 {
...
public void paint(Graphics g) {
JLabel jtp = new JLabel();
jtp.setLocation((int) x, (int) y);
jtp.setSize((int) width, (int) height);
jtp.setLocation(40, 40);
jtp.setSize(40, 40);
jtp.setText("Hello world");
//jtp.setVisible(true);
jtp.paint(g);
}
}
I suggest that you don't add Components to a Container in a paint method as 1) you do not have absolute control when or even if a paint method will be called and 2) paint and paintComponent have to be as blazing fast as possible, and this is not the time or place to update a GUI. 3) Since paint is often called many times, you will be adding components many times to your container, and all out of your direct control.
Also, while you're adding a component into Root (whatever Root is since it doesn't extend JComponent, JPanel, or similar) in the paint method, the Root object is never added to anything else that I can tell, and so it makes sense that nothing "added" to a component that is not added eventually to a top-level window will be visible.
Bottom line: I think you need a gui re-design as your solution. If you tell us more about it we can help you with it. Next we'll need to talk about use of layout managers and why setting absolute position and sizes of components is usually frowned on.
If anything I say is confusing, please ask for clarification, or if anything is wrong, please help me correct it!
You should not create your JLabel inside the paint method - instead, create it once when initializing your MyPanel. Your label is kind of a renderer component for your nodes, which in principle is a good thing. You may look how the renderers for JTable, JList, JTree work.
In your case, don't set the location of your label (it does not change anything, since it's paint-method expects its graphics object to be oriented by its own upper left corner), instead translate the Graphics-context:
Graphics copy = g.create((int)x, (int)y, (int)width, (int)height);
jtp.paint(copy);
(Graphics2D has some more fancy methods for shifting, rotating, scaling the context, too.)
Other than this, I don't see any problems. Make sure your Node1.paint() method gets actually called by putting some System.out.println() in there.

setOpaque(true/false); Java

In Java2D when you use setOpaque I am a little confused on what the true and false does.
For example I know that in Swing Opaque means that when painting Swing wont paint what is behind the component. Or is this backwards? Which one is it?
Thanks
The short answer to your question is that "opaque" is defined in English as completely non-transparent. Therefore an opaque component is one which paints its entire rectangle, and every pixel is not at all translucent to any degree.
However, the Swing component opacity API is one of those mis-designed and therefore often mis-used APIs.
What's important to understand is that isOpaque is a contract between the Swing system and a particular component. If it returns true, the component guarantees to non-translucently paint every pixel of its rectangular area. This API should have been abstract to force all component writers to consider it. The isOpaque API is used by Swing's painting system to determine whether the area covered by a given component must be painted for components which overlap it and which are behind it, including the component's container and ancestors. If a component returns true to this API, the Swing system may optimize painting to not paint anything in that area until invoking the specific component's paint method.
Because of contractual implication of isOpaque, the API setOpaque should not exist, since it is actually incorrect for anything external to call setOpaque since, in turn, the external thing can't know whether the component in question will (or even can) honor it. Instead, isOpaque should have been overridden by each concrete component to return whether it actually is, in fact, opaque given its current properties.
Because the setOpaque API does exist, many components have mis-implemented it (quite understandably) to drive whether or not they will paint their "background" (for example JLabel and JPanel filling with their background color). The effect of this is to create an impression with users of the API to think that setOpaque drives whether or not that background should paint, but it doesn't.
Furthermore, if, say, you wish to paint a JLabel with a translucent background you need to set a background color with an alpha value, and do setOpaque(true), but it's not actually opaque - it's translucent; the components behind it still need to paint in order for the component to render properly.
This problem was exposed in a significant way with the Java 6's new Nimbus Look & Feel. There are numerous bug reports regarding transparent components filed against Nimbus (see stack overflow question Java Nimbus LAF with transparent text fields). The response of the Nimbus development team is this:
This is a problem [in] the orginal design of Swing and how it has been confusing for years. The issue is setOpaque(false) has had a side effect in [existing] LAFs which is that of hiding the background which is not really what it is [meant] for. It is [meant] to say that the component may have transparent parts and [Swing] should paint the parent component behind it.
So, in summary, you should not use setOpaque. If you do use it, bear in mind that the combination of some Look & Feels and some components may do "surprising" things. And, in the end, there is actually no right answer.
javadoc says : If true the component paints every pixel within its bounds. Otherwise, the component may not paint some or all of its pixels, allowing the underlying pixels to show through.
try this example program too...
http://www.java2s.com/Code/JavaAPI/javax.swing/JPanelsetOpaquebooleanisOpaque.htm
I think that the following also needs to be added:
The term opaque has different meanings in Java 2D and in Swing.
In Java 2D opacity is a rendering concept. It is a combination
of an alpha value and the Composite mode. It is a degree to
which the pixel colours being drawn should be blended with pixel
values already present. For instance, we draw a semi-transparent
rectangle over an existing oval shape. The oval is therefore
partially visible. This concept is often compared to light
going trough glass or water.
In Swing, an opaque component paints every pixel within its
rectangular bounds. A non-opaque component paints only a subset of
its pixels or none at all, allowing the pixels underneath it to
show through. The opaque property was set for efficiency reasons; Swing
does not have to paint areas behind opaque components.
Source: Java docs and Filthy Rich Clients
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import static javax.swing.SwingConstants.CENTER;
import net.miginfocom.swing.MigLayout;
class DrawingPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.green);
g2d.fillOval(20, 20, 100, 100);
g2d.setColor(Color.blue);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.1f));
g2d.fillRect(0, 0, 150, 150);
}
}
class MyLabel extends JLabel {
public MyLabel(String text) {
super(text, null, CENTER);
}
#Override
public boolean isOpaque() {
return true;
}
}
public class OpaqueEx2 extends JFrame {
public OpaqueEx2() {
initUI();
}
private void initUI() {
JLabel lbl1 = new JLabel("Java 2D opacity");
JLabel lbl2 = new JLabel("Swing opaque");
DrawingPanel dpanel = new DrawingPanel();
MyLabel mylbl = new MyLabel("isOpaque()");
mylbl.setBackground(Color.decode("#A9A9A9"));
createLayout(lbl1, lbl2, dpanel, mylbl);
setTitle("Opaque");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
JPanel pnl = new JPanel(new MigLayout("ins 10"));
pnl.add(arg[0], "w 150");
pnl.add(arg[1], "w 150, wrap");
pnl.add(arg[2], "w 150, h 150");
pnl.add(arg[3], "w 150, h 150");
add(pnl);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
OpaqueEx2 ex = new OpaqueEx2();
ex.setVisible(true);
}
});
}
}
In the code example, we have two components. The component on the left is a panel which uses AlphaComposite to paint a highly translucent rectangle over an oval. The component on the right is a label. Labels are non-opaque in most look and feels. We overwrite the label's isOpaque() method to set a gray background.

Categories