Re: Graphics2D question
On Fri, 14 Mar 2008 09:49:12 -0700, RichT <someone@somewhere.org> wrote:=
[...]
Yes sorry again I really didn't give it a thought at the time, what =
Knute suggested has been extremely helpful and gives me part of my =
answer, and that is that as long as I have a reference to the graphic =
of =
the component I can draw to this and send a repaint call to the =
component after?
The way I read the above: you've got a GUI component that only redraws =
itself using a referenced "graphic" (there's no such thing as a "graphic=
" =
in Java...do you mean an instance of Image, such as BufferedImage?), whi=
le =
some other code draws into the "graphic" and then tells the component th=
at =
the "graphic" has changed.
Is that a correct understanding of what you wrote? If so, then that see=
ms =
fine to me.
Any particular reason for extending JPanel? Does your custom compone=
nt =
actually need to act as a simple container for other components? If =
=
not, perhaps you should simply be extending JComponent.
No particular reason, I chose JPanel as most of the examples I have se=
en =
use this.
Well, unless you have a specific reason for using JPanel, then don't. =
Extend JComponent instead, as it's the actual base class for Swing =
components.
Why would an image be "enlarged"? How does that happen? Hint: it's =
at =
whatever point the image display, including scaling, is configured th=
at =
you'd set the necessary data that will be used later for drawing the =
=
image.
Int the graphic object and then call a repaint on the component to =
refresh display?
You're answering a question with a question (and one I don't really =
understand either...what does the word "int" mean here? The usual meani=
ng =
in this context, "integer", doesn't seem to apply).
[...]
I'm not really clear on what the "non gui logic class" is. Anything =
=
that needs to know the size of your custom component and involves =
itself in the drawing of the component is, to my perception, a GUI =
class.
Ah my idea of a gui class is an extended JPanel stuffed with code :)
Well, fine. But that's not an explanation as to what a "non gui logic =
class" is.
If you have in mind a class that doesn't know anything about the GUI, =
=
then
perhaps you could be more explicit about that.
This is exactly what I had in mind, if my understanding is correct the=
n =
passin a reference of tthe components graphic object will allow me to =
=
draw to the graphic and then fire an event for the component to redraw=
=
itself.
See above. If I understand this statement correctly, then it seems like=
a =
fine approach. If I don't, then it might or might not be. :)
What interface does that class implement?
I don't mean what's the name of the interface; I mean,
what methods, properties, and/or fields are exposed by that interface=
?
I would imagine methods something like
scale, translate, draw, update, origin, rotate, drawText, to name a fe=
w
Are the transformation operations (scale, translate, rotate...and what's=
=
the difference between "translate" and "origin"?) independent of the =
similar operations that would be set for the custom Swing component? Th=
at =
is, from your previous description, I have the impression that you want =
to =
be able to scale and center the image in the custom component, and now =
from this most recent description I have the impression that you also w=
ant =
to be able to do things like that and other operations on the non-GUI =
class.
How does your custom component use this hypothetical "non gui logic =
class"?
I am guessing that the component class would create an instance of the=
=
logic class and pass it's graphic object and an image in the construct=
or =
and would implement property change listener.
What's the difference between "its graphic object" and "an image"? At t=
he =
outset of this message, I made the assumption that your ambiguous term =
"graphic" referred to some sort of Image instance. But if you can have =
=
both a "graphic" and an "image", then I'm not so sure.
Any buttons or menu item events would call the relevant method in the =
=
logic class.
The logic class would calculate positions, scales and translations etc=
=
and then using the graphic reference draw to this and then call repain=
t =
on the component, possibly via an event, not 100% sure though
I think it makes more sense to follow the "listener" idiom, where your =
"non-GUI" class defines a listener interface that the Swing component ca=
n =
implement and respond to. Then when changes are made, the listener is =
notified. In this case, the Swing component would call repaint() itself=
, =
but this allows for a more general-purpose "non-GUI" class. After all, =
if =
it's "non-GUI" then why should it know about a JComponent that needs to =
=
have repaint() called?
Other than that, your description sounds fine. Of course, that assumes =
I =
understand the rest of the model correctly, which may or may not be the =
=
case.
Well, typically the custom component would either itself be directly =
=
informed of changes to the image, or it would refer to some data =
structure that itself knows about the changes. By the time repaint()=
=
is called, everything should already be set up to draw the custom =
component correctly. The paintComponent() method itself would then =
simply use the current state of the data structures involved to draw =
=
the correct thing.
This sounds interesting, how would this work? this sounds similar to t=
he =
Swing Model View pattern?
Well, it's more like being "similar" to the event-driven GUI updating =
paradigm that practically all mainstream GUI's use.
Model/View is about separating the data from the model. And it works we=
ll =
in an event-driven paradigm. But it's not mandatory...you can easily ha=
ve =
views (i.e. Swing components) that incorporate their own model, and that=
's =
not in line with the Model/View pattern. Yet, they would still use this=
=
"change some data, call repaint() to ask to be repainted" paradigm.
In other words, what I'm describing really isn't optional, the way that =
=
Model/View is. A correctly-written GUI application will always use this=
=
event-driven paradigm, whether or not it separates the model from the vi=
ew.
Again, I'm not really sure what you mean here. The Graphics2D =
instance doesn't have any state that specifically would know about yo=
ur =
image. However, you can certainly set the transformation for the =
Graphics2D based on whatever scaling and translation is necessary. I=
n =
fact, creating this transformation could be part of the "pre-compute =
=
and cache" operation I mentioned earlier. Then all that the =
paintComponent() method would have to do is call Graphics2D.transform=
() =
with the AffineTransform that you'd computed earlier.
By using the reference to the components graphic object ?
Again, that depends on what you mean by "graphic object". But sure, if =
=
that "graphic" object keeps track of the transform needed for drawing, =
that would be a natural place to get the AffineTransform instance.
Note that setting the Graphics2D transform is not the only approach. Yo=
u =
can also pass a transform to some of the Graphics2D.drawImage() overload=
s, =
and it would have the same effect as changing the Graphics2D's own =
transform. This is what I'm talking about here:
Alternatively, you could just compute the necessary parameters that =
=
would be used later to call one of the many drawImage() overloads =
defined in Graphics and Graphics2D and rather than setting the =
transform, just call the appropriate overload directly.
Do you mean Graphics2D.DrawImage, DrawLine etc?
Those are not the names of any methods in the Graphics2D class. But if =
=
you mean Graphics2D.drawImage(), yes. For that method, you could just =
pass a precomputed transform. For drawLine(), there's no such =
overload...you've have to explicitly transform integer coordinates =
yourself and then use the Graphics.drawLine() method, or create a Shape =
=
representing the line and either transform the Shape before drawing it =
(some Shape implementations include a transform() method) or set the =
Graphics2D transform itself.
It all depends on exactly what you want to do.
In the hopes that it might help you out a little, I've attached below an=
=
example of a simple custom Swing component that has image and scale =
properties that can be set, and which always draws the image centered in=
=
the component at the scale that's been set. It doesn't demonstrate the =
=
additional layer that I think you've been talking about, where there's a=
n =
intermediate class that actually manages things like holding on to the =
image, storing settings like scale factor, and implementing notification=
=
for listeners, but hopefully it gives you a better idea of how the Swing=
=
component side of things would work.
The custom Swing class is first, followed by some simple sample code tha=
t =
demonstrates the use of it. (The demo code just opens image files, but =
=
the custom component can also just be assigned some arbitrary image; it =
=
would be simple enough to add a public method to notify the component th=
at =
the image's changed so that the component knows to call repaint(), but a=
s =
I mentioned before I think it would be better to implement a full-fledge=
d =
"listener" API that the component can subscribe to if you're going to ha=
ve =
anything more elaborate than just a simple reference to an Image instanc=
e =
as shown here).
Pete
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public class JImageBox extends JComponent
{
private Image _image;
private double _scale = 1.0;
AffineTransform _transform;
public JImageBox()
{
this.addComponentListener(new ComponentAdapter()
{
public void componentResized(ComponentEvent arg0)
{
_UpdateImage();
}
});
}
public void setImage(BufferedImage image)
{
_image = image;
_UpdateImage();
}
public void setImage(File file)
{
try
{
_image = ImageIO.read(file);
}
catch (IOException e)
{
e.printStackTrace();
_image = null;
}
_UpdateImage();
}
public void setScale(double scale)
{
_scale = scale;
_UpdateImage();
}
protected void paintComponent(Graphics gfx)
{
if (_image != null)
{
Graphics2D gfx2 = (Graphics2D)gfx;
gfx2.drawImage(_image, _transform, null);
}
}
private void _UpdateImage()
{
_transform = null;
if (_image != null)
{
Dimension size = getSize();
double cxImage, cyImage;
cxImage = _image.getWidth(null) * _scale;
cyImage = _image.getHeight(null) * _scale;
_transform = AffineTransform.getTranslateInstance(
(size.getWidth() - cxImage) / 2,
(size.getHeight() - cyImage) / 2);
_transform.scale(_scale, _scale);
}
repaint();
}
}
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class TestImageComponentFrame extends JFrame
{
private JImageBox _imagebox = new JImageBox();
public TestImageComponentFrame(String arg0)
{
super(arg0);
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
Box box = Box.createHorizontalBox();
JButton button = new JButton("Open File...");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
_ChooseImage();
}
});
box.add(button);
final JTextField text = new JTextField("1.0", 8);
text.setMaximumSize(text.getPreferredSize());
button = new JButton("Set Scale");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
try
{
_imagebox.setScale(Double.parseDouble(text.getText(=
)));
}
catch (NumberFormatException e)
{
e.printStackTrace();
}
}
});
box.add(button);
box.add(text);
add(box);
add(_imagebox);
}
private void _ChooseImage()
{
FileDialog filedlg = new FileDialog(this, "Please choose an i=
mage =
file to load:", FileDialog.LOAD);
filedlg.setVisible(true);
if (filedlg.getFile() != null)
{
_imagebox.setImage(new File(filedlg.getDirectory() =
+ filedlg.getFile()));
}
}
}
import java.awt.*;
public class TestImageComponent
{
/**
* @param args
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
TestImageComponentFrame frame = new =
TestImageComponentFrame("TestImageComponentFrame");
frame.setSize(640, 480);
frame.setVisible(true);
}
});
}
}