Java Forum / General / March 2008
Graphics2D question
RichT - 13 Mar 2008 21:05 GMT Hello,
I want to build my application from three main classes. 1. A frame class to handle file operations. 2. A Drawing panel to render images to. 3. An engine to perform the actual rendering.
All the examples I have read so far for Image stuff in Java have everything stuffed into one monolithic class, and the crux of the rendering seems to be done in paint(Graphics g) or paintComponent(Graphics g) using a g2d object cast againg the parameter g.
If I wanted to do calculations for rotations translations etc I can do this with the AffineTransform class setX methods, but the drawImage method of g2d seems to be handled in the paint/paintComponent methods.
This is fine until I want to specify x, y arguments in the drawImage Method as neither Paint or PaintComponent take these parameters in their method signature how can I do this, is there another way to draw apart from the paint/paint component methods?
Any help appreciated. Rich
Peter Duniho - 13 Mar 2008 21:24 GMT > [...] > If I wanted to do calculations for rotations translations etc I can do [quoted text clipped - 5 lines] > method signature how can I do this, is there another way to draw apart > from the paint/paint component methods? Can you rephrase the question? As stated it's a little confusing.
You write "the drawImage method of g2d seems to be handled in...". I assume you're actually talking about Graphics2D.drawImage(), but that's not _handled_ in paintComponent(). It might be _used_ there, but it wouldn't be handled there.
As far as specifying coordinates when using drawImage(), since you shouldn't call paintComponent() yourself, the parameter list for the method isn't relevant. Instead, your component overrides the paintComponent() and draws what it needs to when called. If an image you want to draw using drawImage() requires a specific location to be drawn (and it's natural that it would), then that location needs to be set prior to when paintComponent() is called.
A typical sequence of events would be for the location to be updated by some internal calculation in the component, or by some method called by a client of the component, and when the location is changed, the component would call repaint() to signal that it needs to be repainted. Then the Java run-time will eventually call the paintComponent() method to have the actual painting done, at which time the location that was previously set would then be used to actually draw the image in question.
If that information doesn't clarify for you how to manage what you're trying to do, you should try to be more specific about what problem you're having. A concise-but-complete code sample would be very helpful. Try to show what it is you're trying to do and be clear about why the code doesn't work as you want it to.
Pete
RichT - 13 Mar 2008 22:40 GMT > Can you rephrase the question? As stated it's a little confusing. Sorry that is me all over type faster than I think :)
> You write "the drawImage method of g2d seems to be handled in...". I > assume you're actually talking about Graphics2D.drawImage(), but that's > not _handled_ in paintComponent(). It might be _used_ there, but it > wouldn't be handled there. Ok to simplify my question ;) I have nothing to show code wise, still thinking about a/the design.
Yours and Knutes answers have clarified some things for me thanks :) but I still have a problem getting my head around this.
My thoughts were to have something that extends a JPanel for rendering images to, but I want the logic for doing this in another non gui class.
If for instance I have an image that I wanted to scale and centre in the JPanel, I would want to calculate if enlarged images is bigger than the JPanel and if so crop it to fit.
This would require as far as I know an AffineTransform.setSclale(x, y) and also as far as I know a bufferedImage.getSubImages(x1,y1,x2,y2) and would require some code to calculate all of this.
I would prefer to have all the above in my non gui logic class and somehow have the JPanel class repaint all of this after.
While I could call repaint() in the JPanel, how would it know about the changes to the image and what if I wanted to specify a different x, y location for the image if it is smaller than the JPanel and needed to be centred? how would I get this information to the g2d object in the PaintComponent method?
Peter Duniho - 13 Mar 2008 23:09 GMT > [...] > Yours and Knutes answers have clarified some things for me thanks :) but > I still have a problem getting my head around this. Please keep in mind that apparently you've multi-posted your question. I haven't looked at his answer in the other newsgroup, so I don't know what's already been suggested, nor do I know anything you've posted there. (This is a good example of why you shouldn't multi-post, by the way).
> My thoughts were to have something that extends a JPanel for rendering > images to, but I want the logic for doing this in another non gui class. Any particular reason for extending JPanel? Does your custom component actually need to act as a simple container for other components? If not, perhaps you should simply be extending JComponent.
> If for instance I have an image that I wanted to scale and centre in the > JPanel, I would want to calculate if enlarged images is bigger than the > JPanel and if so crop it to fit. Why would an image be "enlarged"? How does that happen? Hint: it's at whatever point the image display, including scaling, is configured that you'd set the necessary data that will be used later for drawing the image.
> This would require as far as I know an AffineTransform.setSclale(x, y) > and also as far as I know a bufferedImage.getSubImages(x1,y1,x2,y2) > and would require some code to calculate all of this. I doubt you need to use getSubImage(). Your component's drawing will be clipped to the boundary of the image, so all you really need to do is transform the Graphics2D correctly (scale and translate, if I understand your post correctly) and the image will be placed and draw appropriately.
The transformation will be based on whatever configuration was performed prior to the painting (as I mention above).
It's possible that calling getSubImage() might improve performance, by avoiding having the graphics subsystem drawing outside the clip area. But since clipping is usually a highly optimized operation, and because most of the cost of drawing is in the actual moving of bits that are drawn (the area that's clipped won't be processed), I would be surprised if it's a necessary optimization, and it may not improve performance much, if at all (depending on the exact implementation, the cost of creating the new object and then collecting it later could in fact cause reduced performance).
> I would prefer to have all the above in my non gui logic class and > somehow have the JPanel class repaint all of this after. 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. If you have in mind a class that doesn't know anything about the GUI, then perhaps you could be more explicit about that. 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? How does your custom component use this hypothetical "non gui logic class"?
> While I could call repaint() in the JPanel, how would it know about the > changes to the image 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.
> and what if I wanted to specify a different x, y location for the image > if it is smaller than the JPanel and needed to be centred? It seems to me that if you want the image centered in the custom component, the you're not "specifying a different x, y location". Instead, that location is implied by the size of the image. You could either compute the location in the paintComponent() method, or you could pre-compute and cache that information any time the image or component size changes (since either would affect the location of the image).
> how would I get this information to the g2d object in the PaintComponent > method? Again, I'm not really sure what you mean here. The Graphics2D instance doesn't have any state that specifically would know about your image. However, you can certainly set the transformation for the Graphics2D based on whatever scaling and translation is necessary. In 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.
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.
Pete
RichT - 14 Mar 2008 17:49 GMT > Please keep in mind that apparently you've multi-posted your question. Yes sorry about that, I posted to comp.lang.java.gui first and then here as I was unsure if this question belonged to the gui NG as it is more a design thing?
> I haven't looked at his answer in the other newsgroup, so I don't know > what's already been suggested, nor do I know anything you've posted > there. (This is a good example of why you shouldn't multi-post, by the > way). 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?
> Any particular reason for extending JPanel? Does your custom component > 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 seen use this.
> Why would an image be "enlarged"? How does that happen? Hint: it's at > whatever point the image display, including scaling, is configured that > 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?
> I doubt you need to use getSubImage(). Your component's drawing will be > clipped to the boundary of the image, so all you really need to do is > transform the Graphics2D correctly (scale and translate, if I understand > your post correctly) and the image will be placed and draw appropriately. Thanks, the more I learn about Java the more I realise how much it can do for you.
> 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 :)
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 then 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.
> 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 few
> 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 constructor and would implement property change listener.
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 repaint on the component, possibly via an event, not 100% sure though
> 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 the Swing Model View pattern?
> Again, I'm not really sure what you mean here. The Graphics2D instance > doesn't have any state that specifically would know about your image. [quoted text clipped - 4 lines] > 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 ?
> 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?
Thanks for you help and paitience Rich
Peter Duniho - 14 Mar 2008 19:06 GMT > [...] > 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?), while some other code draws into the "graphic" and then tells the component that the "graphic" has changed.
Is that a correct understanding of what you wrote? If so, then that seems fine to me.
>> Any particular reason for extending JPanel? Does your custom component >> 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 seen > 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 that [quoted text clipped - 3 lines] > 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 meaning in this context, "integer", doesn't seem to apply).
> [...] >> I'm not really clear on what the "non gui logic class" is. Anything [quoted text clipped - 3 lines] > > 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 [quoted text clipped - 4 lines] > 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 few 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? That 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 want 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 constructor > and would implement property change listener. What's the difference between "its graphic object" and "an image"? At the 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 repaint > 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 can 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 [quoted text clipped - 6 lines] > This sounds interesting, how would this work? this sounds similar to the > 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 well in an event-driven paradigm. But it's not mandatory...you can easily have 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 view.
>> Again, I'm not really sure what you mean here. The Graphics2D >> instance doesn't have any state that specifically would know about your [quoted text clipped - 6 lines] > > 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. You can also pass a transform to some of the Graphics2D.drawImage() overloads, 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 an 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 that 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 that the image's changed so that the component knows to call repaint(), but as I mentioned before I think it would be better to implement a full-fledged "listener" API that the component can subscribe to if you're going to have anything more elaborate than just a simple reference to an Image instance 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 image 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); } }); } }
RichT - 14 Mar 2008 21:05 GMT > 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?), while some other code draws into the "graphic" and then > tells the component that the "graphic" has changed. I am not sure, I was referring to Graphics2D or Graphics as the graphic object, which appears to be an abstract class in the API, so I am guessing rightly or wrongly that this would be the image/buffered image?
> Is that a correct understanding of what you wrote? If so, then that > seems fine to me. If my understanding of your understanding (see above) is correct then yes :)
> 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. Ok thanks for the tip :)
> 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 > meaning in this context, "integer", doesn't seem to apply). Oh dear that was a mistake it seems that half the text vanished (more likely I deleted it by mistake when editing ;))
I meant This would most likely be triggered by an event from the user, in the gui logic class the calculations would be made in an AffineTransform passed to the Grahics2D.DrawImage(img, affineTransform, null) and then an event sent to the component to redraw itself.
> Well, fine. But that's not an explanation as to what a "non gui logic > class" is. Ok I think I mean a class that handles the logic for the gui component :)
> 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. :) Excellent :)
> Are the transformation operations (scale, translate, rotate...and what's > the difference between "translate" and "origin"?) Nothing :) they mean the same thing
> That 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, correct :)
> and now from this most recent description I have the impression that you > also want to be able to do things like that and other operations on the > non-GUI class. Correct again :) including drawing test into the image (perhaps later much later when I understand this better)
> What's the difference between "its graphic object" and "an image"? I am not entirely sure I thought Graphics2D was a reference to the components graphical context?
At
> the 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. I am unsure now too :(
Thanks for all your help so far, I will answer the rest of this post later :)
Rich
Peter Duniho - 14 Mar 2008 21:33 GMT >> 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 [quoted text clipped - 5 lines] > object, which appears to be an abstract class in the API, so I am > guessing rightly or wrongly that this would be the image/buffered image? Not exactly. There is a BufferedImage class. That would be the actual image. You can call BufferedImage.createGraphics() to get a Graphics2D that will allow you to draw into that image. That's a "Graphics2D" instance, not a "graphic".
I apologize if it seems like I'm being picky, but the compiler will be at least as picky :) and I feel that since there are precise terms that refer to specific classes, those terms should be used. Otherwise, it's impossible to know that we're really talking about the same thing. It will be much more productive if you are careful to use the exact names for things, where those things have exact names.
So, anyway, the Graphics2D isn't the actual image per se, but it is a sort of portal that lets you get at the image with various drawing operations, allowing you to modify it according to your needs.
>> Is that a correct understanding of what you wrote? If so, then that >> seems fine to me. > > If my understanding of your understanding (see above) is correct then > yes :) See above. I think I _almost_ understood what you were talking about. :)
I don't know how expensive it is in Java to create a Graphics2D from a BufferedImage instance. But assuming it's not overly expensive, then it seems to me that the object you really want to hang on to would be the BufferedImage instance only, creating a Graphics2D as necessary whenever you want to update the image.
You haven't been very specific about what sorts of updates you might do, but again there seems to be the assumption that there's some higher-level, user-defined class (where "user" is you) that encapsulates the behavior of the BufferedImage and Graphics2D, exposing it in a more general way. If that's the correct assumption, then I think the "listener" mechanism I mentioned earlier is still appropriate for dealing with updates to the Swing component that displays this image.
> [...] > I meant This would most likely be triggered by an event from the user, > in the gui logic class the calculations would be made in an > AffineTransform passed to the Grahics2D.DrawImage(img, affineTransform, > null) and then an event sent to the component to redraw itself. That doesn't make much sense as I understand the previous discussion. The only image that you've mentioned so far is the one that represents your non-GUI data structure. The only time you'd draw that image would be in the paintComponent() method itself.
Are you saying that one of the operations you might have on this non-GUI data structure is to draw yet another image into it? How does the "gui logic class" come into play here? By "gui logic class", are you talking about your custom Swing component that's displaying your non-GUI data structure? Or something else?
>> Well, fine. But that's not an explanation as to what a "non gui logic >> class" is. > > Ok I think I mean a class that handles the logic for the gui component :) Why are you using the phrase "non gui logic class" to description something that "handles the logic for the gui component"? To me, "non gui" implies no connection with the GUI. Conversely, "handles the logic for the gui component" implies a direct connection with the GUI.
The two seem mutually exclusive to me, and yet you seem to be using the phrases as being directly related to each other. I'm obviously not understanding what you mean, and I hope that my explanation here makes it clear why so that you can clarify.
> [...] >> That 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, > > correct :) Then hopefully the code I posted can help with that part of the problem.
>> and now from this most recent description I have the impression that >> you also want to be able to do things like that and other operations on >> the non-GUI class. > > Correct again :) including drawing test into the image (perhaps later > much later when I understand this better) As I noted at the beginning of this message, you can draw into a BufferedImage by getting a Graphics2D from it (by calling createGraphics()). With that Graphics2D instance you can draw whatever you like onto the BufferedImage, including text (I assume that's what you meant instead of "test" :) ).
>> What's the difference between "its graphic object" and "an image"? > > I am not entirely sure I thought Graphics2D was a reference to the > components graphical context? The Graphics2D instance passed to paintComponent() is such a reference. It's used for drawing the representation of the custom Swing component. As such it has very little to do with a Graphics2D that you might obtain from an image data structure, such as a BufferedImage. I mean, it's the same class, and so of course you can do all the same drawing operations regardless. But in the former, those operations wind up shown on the screen, whereas in the latter, they wind up modifying the BufferedImage.
It kind of sounds like you really have at least two parts to this design question:
* how to display an image in a custom Swing component * how to maintain that image in a class that isn't specific to the GUI
I think that mostly I've been answering the first question, though hopefully some of the things I've written above help address the second. If you disagree with the "two parts" assertion, please explain why so that I (and anyone else reading) can understand better what the actual question or questions are.
Pete
RichT - 15 Mar 2008 18:32 GMT Hi again Peter :)
> I apologize if it seems like I'm being picky, but the compiler will be > at least as picky :) and I feel that since there are precise terms that > refer to specific classes, those terms should be used. No problem you are not being picky ;) I am just not being as clear and concise with my questions as you have been with you answers :)
Otherwise, it's
> impossible to know that we're really talking about the same thing. It > will be much more productive if you are careful to use the exact names > for things, where those things have exact names. This makes sense and reduces the possibility of any ambiguity :)
> So, anyway, the Graphics2D isn't the actual image per se, but it is a > sort of portal that lets you get at the image with various drawing > operations, allowing you to modify it according to your needs. This was my understanding too I think :)
>>> Is that a correct understanding of what you wrote? If so, then that >>> seems fine to me. [quoted text clipped - 3 lines] > > See above. I think I _almost_ understood what you were talking about. :) Yes you did :)
> I don't know how expensive it is in Java to create a Graphics2D from a > BufferedImage instance. But assuming it's not overly expensive, then it > seems to me that the object you really want to hang on to would be the > BufferedImage instance only, creating a Graphics2D as necessary whenever > you want to update the image. Ok to the 'Gui Componet class' = the swing component and 'Gui Component Engine class' = the non Swing class which will hold a reference to the image and perform changes to it.
Yes this is what I want :) So the 'Gui Component class' will create the the 'Gui Component Engine class' and pass it the buffered image that itself will display, itself being the 'Gui Component class'.
> You haven't been very specific about what sorts of updates you might do, > but again there seems to be the assumption that there's some [quoted text clipped - 3 lines] > "listener" mechanism I mentioned earlier is still appropriate for > dealing with updates to the Swing component that displays this image. Spot on :)
> That doesn't make much sense as I understand the previous discussion. > The only image that you've mentioned so far is the one that represents > your non-GUI data structure. The only time you'd draw that image would > be in the paintComponent() method itself. No I am not sure it does either :), no my 'non-GUI' data structure as I understand it is not the 'Gui Component class' but the 'Gui Component Engine class' 'BufferedImage' reference.
> Are you saying that one of the operations you might have on this non-GUI > data structure is to draw yet another image into it? How does the "gui > logic class" come into play here? By "gui logic class", are you talking > about your custom Swing component that's displaying your non-GUI data > structure? Or something else? No :) not custome Swing component, the 'Gui Component Engine class'
> Why are you using the phrase "non gui logic class" to description > something that "handles the logic for the gui component"? To me, "non > gui" implies no connection with the GUI. Conversely, "handles the logic > for the gui component" implies a direct connection with the GUI. Yes sorry again ;) this is very ambiguous, I am not referring to the 'Gui Component class' but the 'Gui Component Engine class.
> The two seem mutually exclusive to me, and yet you seem to be using the > phrases as being directly related to each other. I'm obviously not > understanding what you mean, and I hope that my explanation here makes > it clear why so that you can clarify.
> Then hopefully the code I posted can help with that part of the problem. It has :)
> As I noted at the beginning of this message, you can draw into a > BufferedImage by getting a Graphics2D from it (by calling > createGraphics()). With that Graphics2D instance you can draw whatever > you like onto the BufferedImage, including text (I assume that's what > you meant instead of "test" :) ). Yes I did mean text :) and of course spell check did not pick this up because test is a legitimate word in the English Dictionary as is text :)
> The Graphics2D instance passed to paintComponent() is such a reference. > It's used for drawing the representation of the custom Swing component. [quoted text clipped - 3 lines] > regardless. But in the former, those operations wind up shown on the > screen, whereas in the latter, they wind up modifying the BufferedImage. I understand this now, Graphics2D is more an interface than a class, so that any class which implements it is able to implement its methods in a way that makes sense to that particular class, but anything that accepts a Graphics2D can call this interfaces methods and they will just work? have I understood this bit correctly :)
> It kind of sounds like you really have at least two parts to this design > question: > > * how to display an image in a custom Swing component > * how to maintain that image in a class that isn't specific to the GUI Yes this is correct :) I know how to display the image in the component now, and I also believe I now understand how to maintain and manipulate this image in the 'Gui Component Engine class' using the 'BufferedImage' Graphics2D object applying an AffineTransform to it and then sending an event to the 'Gui Component class' informing it that the image has been modified and this class could then call its repaint() method which would then call its paintComponent(Graphics g) method :)
> I think that mostly I've been answering the first question, though > hopefully some of the things I've written above help address the > second. Indeed they have :)
If you disagree with the "two parts" assertion, please explain
> why so that I (and anyone else reading) can understand better what the > actual question or questions are. I don't ;)
Rich
Peter Duniho - 15 Mar 2008 18:48 GMT > [...] >> The Graphics2D instance passed to paintComponent() is such a [quoted text clipped - 11 lines] > a Graphics2D can call this interfaces methods and they will just work? > have I understood this bit correctly :) Yes, basically. I mean, technically the Graphics2D class is a class, but since it's an abstract class with pretty much all abstract methods, it operates very much like an interface. I think there are good reasons for it being an abstract class rather than an interface, but from the client point of view, it is basically just an interface, in the sense that it's a contract that is implemented by various graphical objects to allow a uniform way to draw to those graphical objects.
>> It kind of sounds like you really have at least two parts to this >> design question: [quoted text clipped - 9 lines] > modified and this class could then call its repaint() method which would > then call its paintComponent(Graphics g) method :) Please see my other post. In particular, where you write "using the 'BufferedImage' Graphics2D object applying an AffineTransform to it", I believe you've misunderstood what setting a transform on that particular Graphics2D object will do.
I have the impression that this spur of the thread is basically dealt with, and so for simplicity's sake I'll try to keep my replies in the other main trunk of the thread.
Pete
RichT - 15 Mar 2008 23:37 GMT > Please see my other post. In particular, where you write "using the > 'BufferedImage' Graphics2D object applying an AffineTransform to it", I [quoted text clipped - 6 lines] > > Pete Thank you for your help :)
RichT - 15 Mar 2008 17:36 GMT Hi Peter,
> I think it makes more sense to follow the "listener" idiom, where your > "non-GUI" class defines a listener interface that the Swing component > can implement and respond to. Yes this was indeed my initial thought :) the two classes which I shall now refer to as the 'Gui Component class' and the 'Gui Component Engine class'
When the Gui Component Engine makes a change to the image in some may perhaps to scale the image, this will send the Gui Component class an event which will call repaint().
I understand now that the overridden paintComponent(Graphics g) method is not called directly but will be called by the repaint() method of the component indirectly ? :)
Now if my understanding of the rest of the post is correct I would place in the overridden paintComponent method something like g2d.drawImage(bufferedImage, affineTransform, null) as per your code?
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? Yes this is true :)
> Model/View is about separating the data from the model. And it works > well in an event-driven paradigm. But it's not mandatory...you can > easily have 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. I see, I think this is what I want for my design ?
> 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 view. I think I understand now :)
> Again, that depends on what you mean by "graphic object". But sure, if > that "graphic" object keeps track of the transform needed for drawing, [quoted text clipped - 4 lines] > overloads, and it would have the same effect as changing the > Graphics2D's own transform. This is what I'm talking about here: Ok I understand now I think, from your other post to reply part I, I can get the graphic object of the Image and do calculations on this and then send an event to the listener i.e. the Gui Component Class which will then call its repaint() and anything else it wants to method?
> 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. Thank you for this code it has been very helpful, I possibly would not always want the image scaled up or down but certainly want it centred.
It doesn't demonstrate the
> additional layer that I think you've been talking about, where there's > an intermediate class that actually manages things like holding on to > the image, storing settings like scale factor, and implementing Yes I believe this is the additional layer I am looking for, but if my understanding is correct, just having a reference to the image will allow me to do all this by getting a reference to its graphic and apply a transform to this and then send an event to the component informing of a change to the image, where more than likely the component would call its own repaint() method?
> notification for listeners, but hopefully it gives you a better idea of > how the Swing component side of things would work. Indeed it does thanks :)
> The custom Swing class is first, followed by some simple sample code > that demonstrates the use of it. (The demo code just opens image files, [quoted text clipped - 5 lines] > you're going to have anything more elaborate than just a simple > reference to an Image instance as shown here). Thank you for all your patience, your answers have been clear and concise, unlike some of my questions, sorry about that, a mix of not understanding certain concepts myself and a wireless keyboard which intermittently misses key stroke (or perhaps I just type too fast ;)) :)
Rich
Lew - 15 Mar 2008 18:13 GMT > a wireless keyboard which > intermittently misses key stroke (or perhaps I just type too fast ;)) :) I have a wireless keyboard/mouse combination that feeds through an A/B USB switch, and it used to miss keystrokes abominably.
When I changed the batteries in the mouse, the keyboard behaved better, in fact, flawlessly since then. Even at speeds in the vicinity of 50 wpm, at times.
It is true that the kb and the mouse share a USB port with this model (Microsoft Wireless Optical Desktop 4000). It may also be that weak or scrambled radio transmissions from the mouse were harming the receiver's ability to decode the keyboard's transmissions.
I use rechargeable batteries, incidentally. The life of the charge in these units seems to be months, three to sixish.
 Signature Lew
Peter Duniho - 15 Mar 2008 18:30 GMT > [...] > I understand now that the overridden paintComponent(Graphics g) method > is not called directly but will be called by the repaint() method of the > component indirectly ? :) Yes. And by "indirectly", this means that the paintComponent() method does not generally get called until some time _after_ repaint() has returned to its caller. That is, it's not even that repaint() itself winds up calling paintComponent() or something that calls it. Instead, it simply signals to the Java system that the component needs to be redrawn and returns. Later on, Java then gets around to calling paintComponent() to have the component redrawn (in fact, if you look you'll notice that there are some overloads of repaint() that give you some degree of control regarding the definition of "later on").
> Now if my understanding of the rest of the post is correct I would place > in the overridden paintComponent method something like > g2d.drawImage(bufferedImage, affineTransform, null) as per your code? Well, I obviously think so. :) That's why I wrote the code that way.
> [...] >> Again, that depends on what you mean by "graphic object". But sure, if [quoted text clipped - 9 lines] > send an event to the listener i.e. the Gui Component Class which will > then call its repaint() and anything else it wants to method? I'll ask you again to be more specific about your terminology. You've already written that you're going to describe your Swing component as "Gui Component class" and "Gui Component Engine class", neither of which are "graphic". So do you mean something else by "graphic"? Perhaps you mean to write "Graphics2D"?
It's difficult to make sure that your question is answered correctly and precisely if you yourself aren't using correct and/or precise terminology.
Now, assuming that you mean "Graphics2D", I would say that the answer is "no". You don't "do calculations" on a Graphics2D instance, at least not as I am in the habit of using the phrase "do calculations". You use it to draw into some image presentation object (for example, an Image object, or the on-screen representation of a JComponent).
You can do two things with a Graphics2D: you can change its own state, and you can perform a drawing operation (which changes the state of something the Graphics2D refers to, like a BufferedImage). Only the drawing operations persist and affect later users of the underyling image object. Changing state of the Graphics2D object, such as setting a transformation, setting a stroke, a color, etc. these things only affect subsequent drawing operations using _that_ Graphics2D object.
So, for example, you can't have a BufferedImage, get a Graphics2D object from it, set the scaling for that Graphics2D, and then expect that to change how the BufferedImage is drawn when used later by being drawn into a _different_ Graphics2D object passed to a control's paintComponent() method.
Again, because your question is somewhat vague it's hard for me to know whether this is what you expect or not. But it sort of sounds like you expect to be able to set the transformation for the Graphics2D, doing nothing else, and then have that have some effect when the underlying image object is drawn later. If you do, then that expectation isn't going to work.
> [...] > It doesn't demonstrate the [quoted text clipped - 8 lines] > a change to the image, where more than likely the component would call > its own repaint() method? See above. Most of what you wrote seems fine, except for the part where you write "apply a transform to this and then send an event to the component informing of a change to the image". Applying a transform to a Graphics2D that you've obtained from an Image object doesn't change the image itself. It only affects drawing operations _to_ that Image done later through the Graphics2D object that you set a new transform for.
Again, I don't know whether this is what you meant, because you're not using precise terminology. But previously in this thread you seem to be using the word "graphic" (incorrectly) to refer to a Graphics2D object. If that's the case here, then my reply is accurate.
It seems to me that if you want to be able to apply scaling, translation, or rotation changes to the image itself (as opposed to things being drawn into the image), then those are things that should be stored as state in your "Gui Component Engine class".
You might even just maintain a single AffineTransform instance, updated any time those things are changed, and then used to draw the image later. You could do this by having your "Gui Component Engine class" have a property that returns this transform as well as the Image that the class stores. Or you could have your "Gui Component Engine class" have a method that takes as a parameter a Graphics2D instance, and then itself draws the Image into that Graphics2D using the transform that's been set.
In either case, note that the transform in this situation is used for _presenting_ the underlying image object that your "Gui Component Engine class" is storing. It doesn't affect the image directly, but rather affects how the image is displayed at some later point.
At least based on what you've written so far, it seems to me that you should be careful to keep in mind that you have two different places where "drawing" happens. There is drawing into your "Gui Component Engine class"'s image object, and then there is the drawing _of_ the "Gui Component Engine class"'s image object. Each will use its own Graphics2D object to handle drawing, and you'll need to be careful to make sure you're changing the state you care about in the appropriate place depending on what effect you want it to have.
Pete
RichT - 15 Mar 2008 23:58 GMT Hi Peter,
> Well, I obviously think so. :) That's why I wrote the code that way. Doh ;)
> I'll ask you again to be more specific about your terminology. You've > already written that you're going to describe your Swing component as > "Gui Component class" and "Gui Component Engine class", neither of which > are "graphic". So do you mean something else by "graphic"? Perhaps you > mean to write "Graphics2D"? Yes I do mean Graphic2D :)
> It's difficult to make sure that your question is answered correctly and > precisely if you yourself aren't using correct and/or precise terminology. Sorry :(
> Now, assuming that you mean "Graphics2D", I do :) I would say that the answer is
> "no". You don't "do calculations" on a Graphics2D instance, at least > not as I am in the habit of using the phrase "do calculations". You use [quoted text clipped - 14 lines] > into a _different_ Graphics2D object passed to a control's > paintComponent() method. Ah Now I really do think I understand now :)
> Again, because your question is somewhat vague it's hard for me to know > whether this is what you expect or not. But it sort of sounds like you > expect to be able to set the transformation for the Graphics2D, doing > nothing else, and then have that have some effect when the underlying > image object is drawn later. If you do, then that expectation isn't > going to work. The Grpahics2D of the Image affects drawing to the image, the Graphics2D in the 'Gui Component class' affects drawing of the component, so to display the image correctly in the component will require that the 'Gui Components class' Grpahics2D to be updated too ?
> Again, I don't know whether this is what you meant, because you're not > using precise terminology. But previously in this thread you seem to be > using the word "graphic" (incorrectly) to refer to a Graphics2D object. > If that's the case here, then my reply is accurate. Yes it is what I meant :)
> It seems to me that if you want to be able to apply scaling, > translation, or rotation changes to the image itself (as opposed to > things being drawn into the image), then those are things that should be > stored as state in your "Gui Component Engine class". using fields perhaps an AffineTransform?
> You might even just maintain a single AffineTransform instance, updated > any time those things are changed, and then used to draw the image [quoted text clipped - 4 lines] > itself draws the Image into that Graphics2D using the transform that's > been set. Which way would you suggest? I guess the 'Gui Component class' should draw its own Graphics2D?
> In either case, note that the transform in this situation is used for > _presenting_ the underlying image object that your "Gui Component Engine > class" is storing. It doesn't affect the image directly, but rather > affects how the image is displayed at some later point. I think I get this now :) I hope
> At least based on what you've written so far, it seems to me that you > should be careful to keep in mind that you have two different places [quoted text clipped - 4 lines] > make sure you're changing the state you care about in the appropriate > place depending on what effect you want it to have. Thank you for all your help, I really do believe I understand this now thanks to your patience.
I think it makes more sense for the 'Gui Component class' to handle its own drawing so providing a method in the 'Gui Component Engine class' to return the AffineTransform is possibly better?
I may not have acces to internet for a couple of days, but I am going to prototype some of these ideas and will post back the results to check my understanding
Thanks for everything Rich
Peter Duniho - 16 Mar 2008 00:58 GMT > [...] >> Again, because your question is somewhat vague it's hard for me to [quoted text clipped - 8 lines] > display the image correctly in the component will require that the 'Gui > Components class' Grpahics2D to be updated too ? Sort of. At the time that you want to adjust how the image is displayed, there is no Graphics2D to update. You only get one of those when the component actually needs to be repainted and that Graphics2D is only around long enough for you to repaint the component.
But you can (and should) store data that can be used at that point in time, applying the data (an AffineTransform, for example) to the Graphics2D passed to you when the paintComponent() method is called by Java.
> [...] >> You might even just maintain a single AffineTransform instance, [quoted text clipped - 7 lines] > > Which way would you suggest? It depends on how exactly you're using the "engine".
Part of the lack of a clearly superior choice is that it seems as though you want to include in your engine properties that affect how the image is drawn. You mentioned scaling, translation, and rotation for example. These things can all be defined relative to the image itself, independent of wherever it might actually be drawn.
At the same time, you've also said that the image should be drawn centered in the component, which implies a contributor to the translation that's relative to the component itself. This cannot be defined relative to the image; it only would make sense in the context of a component.
So, on the one hand, it'd be nice to have the "engine" draw itself into some arbitrary Graphics2D. That way if you want to reuse the "engine" in a different context, it doesn't require you duplicating a bunch of code that gets the properties from the "engine" and uses them to draw the image.
On the other hand, doing it that way means you necessarily have to pass _some_ kind of contextual information in addition to the Graphics2D instance. That's not terrible -- after all, the Graphics2D is itself contextual information -- but it does complicate things a bit.
I guess I sort of lean toward the latter suggestion I offered. As I describe here, the fact is that there's a good argument for doing it the other way. But I think in the long run, the more reusable technique is preferable, even if it does mean that your "engine" winds up a little more complicated.
> I guess the 'Gui Component class' should draw its own Graphics2D? Not only should it, it has to. That's the only way that the Swing component can repaint itself. And as I mentioned, it will only have that Graphics2D long enough to repaint itself once. When it's done, so is that Graphics2D. You'll get (effectively) a fresh one the next time it needs to be repainted.
> [...] > Thank you for all your help, I really do believe I understand this now > thanks to your patience. You're welcome. :)
> I think it makes more sense for the 'Gui Component class' to handle its > own drawing so providing a method in the 'Gui Component Engine class' to > return the AffineTransform is possibly better? As I mentioned above, I think the other way is marginally more desirable. But if you feel differently, I have to say that it's not a terrible choice either way. IMHO, having the Swing component handle all of the drawing itself definitely has the edge in simplicity, even if it would require more code later on if you want to reuse the "engine" class.
Pete
RichT - 21 Mar 2008 22:43 GMT Hi Peter :) Internet online once again
> Sort of. At the time that you want to adjust how the image is > displayed, there is no Graphics2D to update. You only get one of those [quoted text clipped - 5 lines] > Graphics2D passed to you when the paintComponent() method is called by > Java. I do believe the penny has dropped, must have been the aroma of coffee or was that Java ;)
>> Which way would you suggest? > [quoted text clipped - 4 lines] > is drawn. You mentioned scaling, translation, and rotation for > example. True :) These things can all be defined relative to the image itself,
> independent of wherever it might actually be drawn. With you so far...
> At the same time, you've also said that the image should be drawn > centered in the component, which implies a contributor to the > translation that's relative to the component itself. This cannot be > defined relative to the image; it only would make sense in the context > of a component. This is the tricky part :) if the engine will do the drawing, then to centre the image it will require the dimensions of the component it is drawing the image on to.
> So, on the one hand, it'd be nice to have the "engine" draw itself into > some arbitrary Graphics2D. That way if you want to reuse the "engine" > in a different context, it doesn't require you duplicating a bunch of > code that gets the properties from the "engine" and uses them to draw > the image. True...
> On the other hand, doing it that way means you necessarily have to pass > _some_ kind of contextual information in addition to the Graphics2D > instance. That's not terrible -- after all, the Graphics2D is itself > contextual information -- but it does complicate things a bit. When you say contextual are you referring to the dimension of the 'Gui Component class' object? If so I guess it will be easy enough to store this info either directly in the 'Gui Component Engine class' object as fields and have the 'Gui Component class' set the dimensions when it resizes, or store this in another class which the 'Gui Component class' can update as it resizes and the 'Gui Component Engine class' can read when it needs the dimension information of the 'Gui Component class'?
> I guess I sort of lean toward the latter suggestion I offered. As I > describe here, the fact is that there's a good argument for doing it the > other way. But I think in the long run, the more reusable technique is > preferable, even if it does mean that your "engine" winds up a little > more complicated. Yes I am leaning that way too, I do believe this was my original intention, which at the time I did not fully understand enough to realise this :)
> As I mentioned above, I think the other way is marginally more > desirable. I also agree, after reading your explanation it is true that the centring of the image is a minor complication, but I also understand that having the 'Gui Component Engine class' handle the drawing operations will enable better reuse.
But if you feel differently,
I do not :)
IMHO, having the Swing component handle all
> of the drawing itself definitely has the edge in simplicity, even if it > would require more code later on if you want to reuse the "engine" class. I guess it is the choice of more work now or more work later, I have always been taught 'Don't put off till tomorrow what you can do today!' :)
Thanks again for you help :) Rich
Peter Duniho - 21 Mar 2008 23:21 GMT > [...] >> On the other hand, doing it that way means you necessarily have to [quoted text clipped - 4 lines] > When you say contextual are you referring to the dimension of the 'Gui > Component class' object? Yes. And any other information that might be required (that's all I'm aware of, but you never know what else you might want to add later).
> If so I guess it will be easy enough to store this info either directly > in the 'Gui Component Engine class' object as fields and have the 'Gui > Component class' set the dimensions when it resizes, or store this in > another class which the 'Gui Component class' can update as it resizes > and the 'Gui Component Engine class' can read when it needs the > dimension information of the 'Gui Component class'? I think that actually, the easiest thing to do is just pass it when you ask the "engine" to draw itself. You have to pass the component's Graphics2D then anyway, since that's the only time you'll have it, so you might as well include the other information needed as well.
There's nothing wrong per se with the other options you describe, and there are even other alternatives beyond those (for example, implementing some sort of "listener" connection between the two classes). But since the "engine" isn't going to need the information except when drawing, why add all that overhead?
I have the impression that otherwise you feel you've got a handle on the question. Have fun! :)
Pete
RichT - 21 Mar 2008 23:39 GMT > I have the impression that otherwise you feel you've got a handle on the > question. Have fun! :) > > Pete I sure hope so :) Thanks for everything Peter, you really are a star Rich
Knute Johnson - 13 Mar 2008 21:26 GMT > Hello, > [quoted text clipped - 19 lines] > Any help appreciated. > Rich See my response in comp.lang.gui.
 Signature Knute Johnson email s/nospam/linux/
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|