问题
I have an .bmp image with a comic book layout. Currently my code works like this. If I right click and hold down mouse button i can draw a marquee type box around one of the frames on the comic book page. When I release the button it will zoom into that frame. But its instant. And I would like for it to have an animation effect.
Thus instead of going and setting the values of PicRect to the "END VALUE"
PicRect.Left
PicRect.right
PicRect.top
PicRect.bottom
as seen in code below, I need a way to slowly get there, Some kind of while loop that sets those values a little at a time till untill it gets to the "end value" But I am not 100% sure on how this math is working. Nor do any of my while loop tries do anything but zoom in way too far. This is the procedure.
procedure TZImage.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var coef:Double;
t:integer;
begin
if FMouse=mNone then Exit;
if x>ShowRect.Right then x:=ShowRect.Right;
if y>ShowRect.Bottom then y:=ShowRect.Bottom;
if FMouse=mZoom then begin //calculate new PicRect
t:=startx;
startx:=Min(startx,x);
x:=Max(t,x);
t:=starty;
starty:=Min(starty,y);
y:=Max(t,y);
FMouse:=mNone;
MouseCapture:=False;
//enable the following if you want to zoom-out by dragging in the opposite direction}
{ if Startx>x then begin
DblClick;
Exit;
end;}
if Abs(x-startx)<5 then Exit;
if (x - startx < y - starty) then
begin
while (x - startx < y - starty) do
begin
x := x + 100;
startx := startx - 100;
end;
end
else if (x - startx > y - starty) then
begin
while (x - startx > y - starty) do
begin
y := y + 100;
starty := starty - 100;
end;
end;
//This is were it sets the zoom info. This is were
//I have to change to slowly get the PICRECT.Left/right/top/bottom
if (PicRect.Right=PicRect.Left)
then
coef := 100000
else
coef:=ShowRect.Right/(PicRect.Right-PicRect.Left);
PicRect.Left:=Round(PicRect.Left+startx/coef);
PicRect.Right:=PicRect.Left+Round((x-startx)/coef);
if (PicRect.Bottom=PicRect.Top)
then
coef := 100000
else
coef:=ShowRect.Bottom/(PicRect.Bottom-PicRect.Top);
PicRect.Top:=Round(PicRect.Top+starty/coef);
PicRect.Bottom:=PicRect.Top+Round((y-starty)/coef);
end;
if FMouse=mDrag then begin
FMouse:=mNone;
Canvas.Pen.Mode:=pmCopy;
Screen.Cursor:=crDefault;
end;
Invalidate;
end;
I believe this can be done in the code above. but also wanted to add this incase it helps.
type
TZImage = class(TGraphicControl)
private
FBitmap : TBitmap;
PicRect : TRect;
ShowRect : TRect;
FShowBorder : boolean;
FBorderWidth : integer;
FForceRepaint : boolean;
FMouse : (mNone, mDrag, mZoom);
FProportional : boolean;
FDblClkEnable : boolean;
startx, starty,
oldx, oldy : integer;
thanks for any help in getting this to work.
回答1:
I've got a few suggestions; I'm not sure that they'll be sufficient to solve your problem, but I hope it helps you get there.
First, your while
loops are doing a fair amount of funny fiddling:
if (x - startx < y - starty) then
begin
while (x - startx < y - starty) do
begin
x := x + 100;
startx := startx - 100;
end;
end
else if (x - startx > y - starty) then
/* similar code */
Note that x - start == y - starty
case is being completely overlooked. I don't know if this matters.
Second, this could probably be re-written without a loop. I'm guessing here, it'd take a little testing to see if this is correct, but this feels like the right path:
foo := (x - startx) - (y - starty)
if (foo > 200 || foo < -200)
bar = foo / 200 # I assume integer truncation
x += bar * 100
startx += bar * 100
I'm not entirely sure why you're trying to get (x-startx) - (y-starty)
to within 200 of each other; there may be something better still.
This section of code is a little confusing:
if (PicRect.Right=PicRect.Left)
then
coef := 100000
else
coef:=ShowRect.Right/(PicRect.Right-PicRect.Left);
PicRect.Left:=Round(PicRect.Left+startx/coef);
PicRect.Right:=PicRect.Left+Round((x-startx)/coef);
if (PicRect.Bottom=PicRect.Top)
then
coef := 100000
else
coef:=ShowRect.Bottom/(PicRect.Bottom-PicRect.Top);
PicRect.Top:=Round(PicRect.Top+starty/coef);
PicRect.Bottom:=PicRect.Top+Round((y-starty)/coef);
end;
Is coef
supposed to be overwritten from the earlier? Or, should you instead calculate a coefx
and coefy
and then pick the (larger? smaller? closer to 100000?) value to serve for both the .Left
, .Right
, .Top
, and .Bottom
calculations? I have to think that this code, as it stands, is more likely to lead to awkward stretching of your content in a way that will likely annoy users and authors both.
Now, to address the real reason why you're here, animating the zoom -- you'll probably need to drastically change something. I feel like your while
loops were probably intended to do the zooming, but they come after the coef
calculations, so I assumed they were meant for something else. But, once you do figure out where exactly to place the loop to calculate different coef
values over a range from "no zoom" to "final zoom", you'll also need to add calls to repaint the display -- or, depending upon your environment, maybe need to add some callback code fired by a timer every 50 ms or something to repaint with updated coef
values.
回答2:
Do not use a while loop to update the zoom level, because of two problems:
- Different animation durations, because the speed of the zoom operation depends on the speed of the code, the current CPU usage, the CPU model, etc...
- Blocking of te GUI, because even when you use a delay (e.g. with
Sleep
), the code is running in the main thread and the program turn out being unresponsive.
Like sarnold and Elling already said: use a timing device (e.g. a TTimer
) to perform a piece of the total zoom operation on every interval. Now, there are two ways to calculate those pieces:
- Divide the total distance to bridge into a fixed number of small distances, set the timer's interval to the total duration divided by that number and handle the sum of all processed distances on every interval. The drawback with this method is twofold:
- A timer's set interval is an approximation and will not be exact because of various reasons, one of them being dependend on the Windows messaging system,
- A possible rough or unsmooth animation because of it.
- Recalculate the part of the distance to bridge at every interval. That way the animation will appear smooth always, whether the next interval takes two times more or not.
I used the second solution in this answer to your related question, from which the following relevant snippets are taken:
procedure TZImage.Animate(Sender: TObject);
var
Done: Single;
begin
Done := (GetTickCount - FAnimStartTick) / FAnimDuration;
if Done >= 1.0 then
begin
FAnimTimer.Enabled := False;
FAnimRect := FCropRect;
end
else
with FPrevCropRect do
FAnimRect := Rect(
Left + Round(Done * (FCropRect.Left - Left)),
Top + Round(Done * (FCropRect.Top - Top)),
Right + Round(Done * (FCropRect.Right - Right)),
Bottom + Round(Done * (FCropRect.Bottom - Bottom)));
Invalidate;
end;
procedure TZImage.Zoom(const ACropRect: TRect);
begin
FPrevCropRect := FCropRect;
FAnimRect := FPrevCropRect;
FCropRect := ACropRect;
FAnimStartTick := GetTickCount;
FAnimTimer.Enabled := True;
end;
Explanation:
FCropRect
is the new zooming rectangle, andFPrevCropRect
is the previous one,FAnimRect
is the rectangle in between both, depending on the progress of the animation,FAnimStartTick
is the time on which the zoom operation is started with a call toZoom
,- at every timer interval (set to 15 msec, ~67Hz refresh rate),
Animate
is called, Done
is the percentage of animation progress,Invalidate
triggers a repaint which draws the graphic intoFAnimRect
.
回答3:
I wrote an answer to your previous question (which was deleted permanently by some, probably well meaning, stackoverflow moderator).
The suggestion in my previous answer was that you look at the glflow code example:
http://code.google.com/p/glflow/
The example uses the GLScene library, and illustrates both image loading, image zoom and animation.
Even if you do not use the GLScene library, I think you can get some inspiration for the animation part by looking at the code example.
The essence of it is that you need to use a timer to do the redraws.
First divide the distance between the start zoom level and the end zoom level into discrete steps. Then use a timer to cycle through those steps and do a redraw on each step.
回答4:
I'm actually working with Glen on this project. I wrote some of the code in question so I would like to clarify what it does. I left comments out of it because I quickly threw it together. Most of the code here is used via an open license that we found. The code originally did not have the loops:
if (x - startx < y - starty) then
begin
while (x - startx < y - starty) do
begin
x := x + 100;
startx := startx - 100;
end;
end
else if (x - startx > y - starty) then
begin
while (x - startx > y - starty) do
begin
y := y + 100;
starty := starty - 100;
end;
end;
This is what I added, and it was added because the original code was not working the way we thought it would work. Basically you select the area via drag and drop. The selected area gets zoomed in on, but instead of showing the full area selected, it fits the smaller of either x-startx or y-starty to the view area. So, to try and clarify that, if you have an area selected that is 50pix high and 100pix wide, it will zoom and fit the 50pix top to bottom filling the view area, but the 100 pix get clipped. The sides fall off the viewing area. So the code that was added here is to fix the problem by making what was the smaller of the two the larger of the two. By doing this it actually will fit the view to what was the original larger of the two which is now the smaller of the two. It's an incredibly sloppy fix, but it works. Your method would also fix this issue for us as well and might do so in a nicer manner. The big issue with it all is that if the area is a very larger area slected, then 200pix might not actually be enought to fix the difference. For our purposes it might work for 85%+, but not all of it, so this code still needs work.
The other code you question is actually what was driving us nuts before. There is an utter lack of comments throughout the entire document and we're still trying to piece together exactly what it all means. The coef is actually what is driving me mad. I'm not even entirely sure what it is meant to do in the first place. I did try a seperate coefx and coefy and that actually breaks it. The zoom box is far different than it was suppose to be. As far as I can tell the current method adds no odd stretching, as to why I am unsure.
Here's a link to the code we're using though if you would like to take a look at it on a full scale. http://www.torry.net/authorsmore.php?id=986 Zimage on that page.
As for the actual question at hand, we're not clear on exactly what coef does in the first place, so making changes to it is resulting in us just breaking things and not working in a trial and error manner. If you wouldn't mind taking a look at it for us to figure out exactly what it does that would get us on to being able to change it to the correct values, and get rid of my slop code in the process. Which would then allow us to move forward into the zoom animation.
To add another question about the animation. In doing so, would this also allow us to add an animation when moving from one zoom point on an image to another zoom point. For our application it will be from one comic panel to another, either below or to the side, and in most cases a different size as well. Would loading inbetween values for left, right, top and bottom be the best way to show that type of animation? If so I think that would also work in moving from full image to first zoomed panel as well.
来源:https://stackoverflow.com/questions/10964902/math-for-slow-image-zoom