Re: Draw a rectangle keeping aspect ratio of viewport
Because the only reason it's "obvious" is that it's been pointed out to
you. :)
:) I guess so, I know what you mean about the brain seeing what it
expects to see :) though sometimes I just think my brains blocks everything.
> It's a common problem, and it's one of the best reasons for posting
code
when you've run into a brick wall. Another set of eyes is very often
helpful (it's also why authors don't edit their own manuscripts :) ).
Praise the internet and helpful people like yourself and Knute to name
but a few, who have saved me from tearing my hair out and retaining a
vague sense of sanity :)
Don't worry about it...we all have the exact same problem.
I feel better now :)
For what it's worth, I find it helpful to have another person look at my
code when I run into something like this. But often there's not another
person around to ask
Often my case :)
, or the code is more complicated than what the
person has time to review. This is where a debugger comes in.
I get strange behaviour from Eclipse when I want to inspect a variable
often it will unhelpfully inform me that no explicit return value exists
I guess because I am using getters/setters, but you are correct the
debugger is helpful, it found another problem which I could not work out
initially. When I was calculatng the aspect ratio, I was passing in
image.getWidth / image.getHeight() to a method expecting a double
argument and although the calculation came to about 0.687333 the method
received 0.0 because of an implicit cast to int as the image width and
heigh are measured as int.
I expected a warning or something about this but no it happily compiled
and ran giving me an incorrect result, had it not ben for the debugger I
would have been stumped, I am pretty certain that C++ would have thrown
a warning on this one.
AffineTransform is what I'm describing, yes. It's certainly helpful to
know how matrices work with respect to transforming between coordinate
spaces, but to some extent if you simply look at the docs for
AffineTransform and see what methods it offers, that can help you
understand what sorts of things a transform can do for you.
Been on the case this afternoon, and I now have a better understanding
of matrices, though certainly not a thorough one :)
Scale, translate, rotate, seem straight forward, still working on the
inverse bit
That said, if the only "world coordinate" object you're displaying in
"screen coordinates" is the image itself, using an AffineTransform might
be overkill.
Well not really the case
The image is scaled when drawn to the canvas, and the ideal but not
always expected behaviour is that other objects drawn to the canvas are
also scaled including fonts and rectangles to the same scale then when I
am happy with the layout of shapes and text and want o draw this into
the image I want it scaled and translated back to the 1:1 image,
rotation may b a thing of the future, but a step at a time :)
I did some playing around with a test project, writing a simple
JComponent that displays a given image at a given scale. I combined
this with a JScrollPane so that I could learn more about how the
JScrollPane works. One important thing I found is that, as I'd hoped,
the JScrollPane allows for scrolling without the contained component
having to know _anything_ about it.
True until I want to centre either the entire image within the viewport
or a selection of the image.
The canvas is sized to fit the scaled image and I can not see any other
way of centring the image within the scrollpane viewport without the
canvas knowing about scrollpane and vice-versa.
In particular, all mouse input is translated according to the scrolling,
so that when dealing with mouse input you can ignore that your component
is in a JScrollPane.
Yes thankfully :) but there does not seem to be a built in method or
flag in scrollpane that will auto scrol the pane if the mouse is dragged
near te edge of a scrollbar, I had hoped that autoscroll would achieve
this but alas :)
I'm including that test code at the end of this message, in case it's
useful to you.
Any new information and knowledge is useful to me :)
[...]
There is still a single problem with the code I posted, if you select
the x3 option it does not centre the rectangle within he scrollbar
viewport, the rectangle I am drawing is supposed to be representative
to an area of the image I select with the selection rectangle.
I'm not really clear on the above problem description.
What should happen is if the image is larger than the window, move
scrollbars so that scrollbars are centred.
I guess what I really want is to find a point in the image relative to
the centre point of the scrollpane viewport and the scale of the image
so that if I choose to draw at the centre point of the viewport I can
find this point on the image regardless of the scale the image is being
displayed at.
I ran the code,
and I saw a rectangle that isn't centered in any scenario, not just when
zoomed to 3x. As I mentioned before, a good problem description will be
very clear about how to use the program to cause the problem to happen,
what happens when you do that, and what you expect to happen instead.
Without that information, even with a working code sample it can be hard
to know exactly what's being asked about.
Sorry :( this is some code I cut from code Knute posted to solve an
earlier problem for me
This code is supposed and does on my machine centre the scrollbars
EventQueue.invokeLater(new Runnable() {
public void run() {
vp = sp.getViewport();
p = new Point(
(t.imageW - vp.getWidth())/2,
(t.imageH - vp.getHeight())/2);
vp.setViewPosition(p);
}
});
I do have some comments regarding the code you posted:
* I have a suspicion that the "image width/height larger/smaller
than viewport" stuff is superfluous. It's hard to know for sure without
a clear description of what the code is supposed to do, but it seems to
me (especially based on my own tests with JScrollPane) that the custom
component should mostly be indifferent to whatever's going on with the
JScrollPane.
This is what is supposed to happen and why.
The image is always drawn at 0,0 on the canvas.
When the image is smaller than the scrollpane area, I want whatever I am
drawing to be drawn to the centre of image initially, so if image is
smaller, i.e. has been scaled to say 50% I want to draw to the centre of
the image at the scaled ration, which was why I used the image
getwidth/height stuff.
However if the image is larger place/draw the shape centre of the
viewport, not necessarily the image.
* You have a lot of code in each menu action handler that I think
belongs elsewhere. In particular, there's a lot of management of the
custom component that IMHO belongs in the component itself, based on
simple property changes to the component. Hopefully the code I'm
including illustrates what I mean.
Sorry :( this code is a bit messy but I contrived this from existing
code that worked and kind of factored into a single entity. there are
actually 5 classes involved, the frame, canvas, the drawing engine, the
actions and the zoom class and they are growing larger all the time, I
keep breaking parts of the classes into smaller manageable classes like
zoom and the actions s I go along and I suspect a coordinate tracking
class is going to surface sooner or later.
* In your 3x action handler, you queue some kind of centering logic
for later execution. This seems odd...why not just execute that after
revalidating the container (happens implicitly when you revalidate your
custom component).
I tried this but I want the image to be centred when the window size
changes not only through zoom operations.
I initially attached a resize handler to the canvas from the frame but
the resize event just seemed to get ignored, and break points confirmed
this.
* No doubt people will give me grief for using Hungarian, but IMHO
your practice of using single-letter variable names is worse.
:) Oh yes but I promise I do not use this style normally only for this
contrived example.
I'd
recommend strongly against that, especially for non-local variables.
When a variable is named simply "p" (for example), it makes it very hard
to do a simple text search to try to figure out exactly where it's used.
:) but at least you wont see a got in sight :)
Anyway, that's all I have for now. :)
Enough!! this is more than enough I really appreciate all your feedback
and help truly I do :)
Pete
Here's the code I mentioned. Some notes:
* The code is in three sections: the custom component, the main
application, and a utility class. I realize this is less convenient,
since you have to make three different .java files to use it, but I'm
hoping that the more clear division of functionality compensates for the
inconvenience.
this is actually better and what I should have done initially, except I
thought doing it my way would make things simpler but not the case :)
* I wrote the custom component two different ways: explicitly
* I kind of "bailed" on the component size stuff. There are probably
layout-friendlier ways to manage the size of the component, but because
my intent was just to put this component in a JScrollPane, I just set
all the various "sizes" for the control to the desired size. This is
not necessarily considered a "good Java habit". :)
No problem :)
* Note that the client of my custom component doesn't know anything
about the consequences of changing its properties. It just changes
them, and then the custom component itself manages the consequences as
appropriate (changing the size and repainting, in this case). This
keeps the code simpler and less repetitive.
Co dependency is evil and you are indeed correct :)
* Finally, very minor note: your code sample had an explicit file
path, while mine prompts the user. Inasmuch as a code sample should be
_complete_ and inasmuch as you can't really post an image file with your
sample, IMHO it makes more sense for the sample to not rely on a
specific data file for input (the image file in this case). If there's
some specific characteristic of the input file (image dimensions, bit
depth, etc. for example), describe those but then allow for a way for
the reader to easily provide their own input.
I ummed and arrred about displaying a file dialog on startup to choose a
file and opted instead for the hard code approach sorry about that, I
was just trying to keep code size down.
I know that's a lot of notes, and the code may seem to be a bit long.
But in reality, I think it's reasonably simple...something like half the
custom component exists to manage things other than the specific
coordinate-mapping issues, so hopefully it does a good job of
demonstrating how uncomplicated that part of the problem really needs to
be. :)
Thanks :)
Anyway, here are the classes:
Thank you
Rich