aberno@questor.wimsey.bc.ca (Anthony Berno) (01/07/91)
I was wondering about something, partly from curiosity and partly because I am thinking of using it myself... How is the "rubberbanding" done in programs like Superpaint when you are drawing a line on the screen? There isn't an obvious solution in Inside Macintosh, and any solutions I have come up with are hideously difficult. There must be a relatively simple way of doing it. Does anyone out there know? Thanks. Anthony Berno.
mlab2@kuhub.cc.ukans.edu (01/07/91)
In article <La6cV1w164w@questor.wimsey.bc.ca>, aberno@questor.wimsey.bc.ca (Anthony Berno) writes: > I was wondering about something, partly from curiosity and partly because > I am thinking of using it myself... > > How is the "rubberbanding" done in programs like Superpaint when you are > drawing a line on the screen? There isn't an obvious solution in Inside > Macintosh, and any solutions I have come up with are hideously difficult. > There must be a relatively simple way of doing it. Does anyone out there > know? > > Thanks. > Anthony Berno. When in line-drawing-mode, a mouseDown event in the drawing window should call a procedure similar to the below: procedure RubberBand; var originalPt, lastPt, newPt : Point; begin GetMouse(originalPoint); PenMode(patXOr); GetMouse(lastPt); MoveTo(originalPt.h, originalPt.v); LineTo(lastPt.h, lastPt.v); repeat MoveTo(originalPt.h, originalPt.v); LineTo(lastPt.h, lastPt); GetMouse(newPt); MoveTo(originalPt.h,originalPt.v); LineTo(newPt.h, newPt.v); lastPt:=newPt; until (not Button); PenNormal; end; john calhoun
urlichs@smurf.sub.org (Matthias Urlichs) (01/08/91)
In comp.sys.mac.programmer, article <27725.2787e3d0@kuhub.cc.ukans.edu>,
mlab2@kuhub.cc.ukans.edu writes:
<
< When in line-drawing-mode, a mouseDown event in the drawing window should call
< a procedure similar to the below:
<
Modify at will for drawing other shapes, of course...
I'd suggest some enhancements:
- drawing the rubberbanded line in gray
(QD pattern for monochrome Macs, real gray for Color QD)
- drawing only when the mouse position has changed
- autoscrolling the new endpoint into the window
- invalidating the old line rather than simply erasing it;
there might be other shapes behind it.
This will make the routine slightly more complicated, of course, but all the
add-ons should already be present in your program anyway. ;-)
< procedure RubberBand;
< var
< originalPt, lastPt, newPt : Point;
< begin
< GetMouse(originalPoint);
< PenMode(patXOr);
PenPat(gray);
(* you might also want to make the pen bigger, maybe one point bigger
than the line you're drawing, so that it doesn't tend to vanish
for near-45 degree angles *)
< GetMouse(lastPt);
< MoveTo(originalPt.h, originalPt.v);
< LineTo(lastPt.h, lastPt.v);
< repeat
GetMouse(newPt);
if newPt <> lastPt then begin
< MoveTo(originalPt.h, originalPt.v);
< LineTo(lastPt.h, lastPt);
< (* GetMouse(newPt); *)
ScrollIntoView(newPt);
UpdateWindow;
< MoveTo(originalPt.h,originalPt.v);
< LineTo(newPt.h, newPt.v);
< lastPt:=newPt;
end;
< until (not Button);
< PenNormal;
PenPat(black);
(* reset pen size? *)
MoveTo(originalPt.h, originalPt.v);
LineTo(lastPt.h, lastPt.v);
(* now erase and invalidate the location of the old line as there might
have been bits behind it, probably by calling Pt2Rect and InvalRect *)
< end;
--
Matthias Urlichs -- urlichs@smurf.sub.org -- urlichs@smurf.ira.uka.de /(o\
Humboldtstrasse 7 - 7500 Karlsruhe 1 - FRG -- +49+721+621127(0700-2330) \o)/
hawley@adobe.COM (Steve Hawley) (01/08/91)
In article <27725.2787e3d0@kuhub.cc.ukans.edu> mlab2@kuhub.cc.ukans.edu writes: >In article <La6cV1w164w@questor.wimsey.bc.ca>, aberno@questor.wimsey.bc.ca (Anthony Berno) writes: |> How is the "rubberbanding" done in programs like Superpaint when you are |> drawing a line on the screen? |> Anthony Berno. | |When in line-drawing-mode, a mouseDown event in the drawing window should call |a procedure similar to the below: |procedure RubberBand; | var | originalPt, lastPt, newPt : Point; |begin | GetMouse(originalPoint); | PenMode(patXOr); | GetMouse(lastPt); | MoveTo(originalPt.h, originalPt.v); | LineTo(lastPt.h, lastPt.v); | repeat | MoveTo(originalPt.h, originalPt.v); | LineTo(lastPt.h, lastPt); | GetMouse(newPt); | MoveTo(originalPt.h,originalPt.v); | LineTo(newPt.h, newPt.v); | lastPt:=newPt; | until (not Button); | PenNormal; |end; Problem: 1) If the mouse doesn't move, the line will be flickering very badly. 2) The MoveTo/LineTo pairs are separated by a GetMouse() which will increase the flicker. 3) Erasing the old line first increases the "apparent" flicker 4) The loop should be terminated by the StillDown() function, not Button() Solution: repeat GetMouse(newPt); MoveTo(originalPt.h, originalPt.v); { Do the new point first } LineTo(newPt.h, newPt.v); { This reduces apparent flicker } MoveTo(originalPt.h, originalPt.v); LineTo(lastPt.h, lastPt.v); lastPt := newPt; until (not StillDown(...I forget the args...)); Problem: 1) In such a tight loop, the overhead of the trap dispatcher is significant and contributes to the flicker in a bad way. Solution: Get the real function address from the GrafPorts QD procs in variables and execute them directly Problem: 1) When the new point differs from the old point by only a small amount, the line will still flicker because of the large amount of common area between the two lines, ie, drawing the new line will XOR over a large part of the old line leaving a big blank spot until the old line is re-drawn. Solution: Copy window contents into offscreen bitmap #1 (a paint program would have this as the actual image data) SetPort to your own GrafPort that you have already setup with offscreen bitmap #2 as its portBits Hide the cursor repeat Get the new mouse coordinates CopyBits the smallest rectangle that encloses the old line, the new line and the cursor from bitmap #1 to bitmap #2 Draw the new line in your port CopyBits the cursor mask onto bitmap #2 CopyBits the cursor data onto bitmap #2 CopyBits the smallest rectangle that encloses the old line, the new line and the cursor from bitmap #2 to the window until (not StillDown(...)); Show the cursor Can you say "Yuck"? I knew you could... The final method will give a minimal amount of flicker. The problem is that moving all that data around gets to be expensive in terms of response, escpecially on bit lines. For bonus points, you'd want the bitmaps to be word or long word aligned to speed up the transfer, and you'd want the last CopyBits (to the screen) to be in sync with the VBL rate. The method that uses the pre-fetched addresses for the functions is probably suitable for most needs, although probably not in a professional product. Good luck... Steve Hawley hawley@adobe.com Disclaimer: Adobe pays me to hack printers, not Macs. I hack Macs in my spare time -- "I'm sick and tired of being told that ordinary decent people are fed up with being sick and tired. I know I'm certainly not, and I'm sick and tired of begin told that I am." -Monty Python
jmunkki@hila.hut.fi (Juri Munkki) (01/08/91)
In article <27725.2787e3d0@kuhub.cc.ukans.edu> mlab2@kuhub.cc.ukans.edu writes: >In article <La6cV1w164w@questor.wimsey.bc.ca>, aberno@questor.wimsey.bc.ca (Anthony Berno) writes: >> How is the "rubberbanding" done in programs like Superpaint when you are >> drawing a line on the screen? There isn't an obvious solution in Inside >> Macintosh, and any solutions I have come up with are hideously difficult. >> There must be a relatively simple way of doing it. Does anyone out there >> know? >> >procedure RubberBand; > var > originalPt, lastPt, newPt : Point; >begin > GetMouse(originalPoint); > PenMode(patXOr); > GetMouse(lastPt); > MoveTo(originalPt.h, originalPt.v); > LineTo(lastPt.h, lastPt.v); > repeat > MoveTo(originalPt.h, originalPt.v); > LineTo(lastPt.h, lastPt); > GetMouse(newPt); > MoveTo(originalPt.h,originalPt.v); > LineTo(newPt.h, newPt.v); > lastPt:=newPt; > until (not Button); > PenNormal; >end; Don't do this. I can point out several faults in the above code. First of all, it creates a quickly flickering line that can be invisible to the user, if the timing happens to be right. Using exclusive or mode should be ok in non-paint programs, but inverting a line when the user is trying to paint a line is not good. Using an offscreen bitmap to restore the background will look much nicer. Superpaint certainly doesn't use XOR mode. Most importantly, even loops like this one should use GetNextEvent. GetNextEvent will tell you the mouse position as well as the mouse button state. The nice thing about it is that it can keep track of button events better than your loop. Your loop fails, if the user rapidly releases and presses the mouse button. If you have more than just two lines drawn in the loop, it's quite probable that this will happen. This also means that you never find out where the mouse button really came up. Moving the mouse quickly while releasing the button will get you this result. I also discovered that the GetMouse/Button approach doesn't really work with QuicKeys (or at least the version QuicKeys that I have). Here's what I'm using in Dizzy (a digital circuit simulator that compiles for Think C and X/Motif on unix machines. It will be soon released as free software WITH SOURCE CODE.): /* >> Gets the mouse position while in a tracking loop. >> Returns true when the mouse button comes up. */ int GetMouseTrackEvent(pt) Point *pt; { int r=0; r=GetNextEvent(mUpMask+mDownMask,&MyEvent); *pt=MyEvent.where; GlobalToLocal(pt); if(r) r=(MyEvent.what==mouseUp); return !r; } You also have to check if the mouse moved, if you want to avoid flicker. Of course this is easy to do with just an extra variable: do /* Loop until button is released. */ { downflag = GetMouseTrackEvent(&MousePoint); if(MousePoint.h!=OldSpot.h || MousePoint.v !=OldSpot.v) { /* Ok, mouse moved, do the rubberbanding... blaa blaa... OldSpot = MousePoint; } } while(downflag); I use XOR mode in dizzy, but that's because it has rubberbanding operations that do not need to be wysiwyg. The interesting thing about loops like the one above is that they will work with X toolkit just as well, if you rewrite GetMouseTrackEvent. As a bonus, this kind of loop allows for multifinder background operations. If you have terminal program in the background, it will still be able to run while you rubberband. If you want absolute multifinder compatibility, you just rewrite GetMouseTrackEvent to use WaitNextEvent. (Sorry, I don't have a version like that... The only multifinder documents I have are in some issue of Byte. I wonder why Apple doesn't make stuff like this available in bookstores or at least technotes.) ____________________________________________________________________________ / Juri Munkki / Helsinki University of Technology / Wind / Project / / jmunkki@hut.fi / Computing Center Macintosh Support / Surf / STORM / ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
laf@mbunix.mitre.org (Lee Fyock) (01/08/91)
I've found that an easier way to avoid flicker (easier than using background bitmaps, etc.) is to just put in a check to see if the mouse has moved before erasing and redrawing the line. This is extremely flicker-free, with very little code. It isn't, of course, very multifinder friendly. What happens to the GetNextEvent version of this code if the user clicks on the multifinder icon in the menu bar and the program gets switched out? It won't know it because app4events aren't being read, right? Thanks, Lee Fyock laf@mbunix.mitre.org
d88-jwa@dront.nada.kth.se (Jon W{tte) (01/09/91)
In article <9700@adobe.UUCP> hawley@adobe.UUCP (Steve Hawley) writes: >In article <27725.2787e3d0@kuhub.cc.ukans.edu> mlab2@kuhub.cc.ukans.edu writes: >>In article <La6cV1w164w@questor.wimsey.bc.ca>, aberno@questor.wimsey.bc.ca (Anthony Berno) writes: >|> How is the "rubberbanding" done in programs like Superpaint when you are >|> drawing a line on the screen? >|When in line-drawing-mode, a mouseDown event in the drawing window >|should call a procedure similar to the below: >| GetMouse(originalPoint); This is not a good way to do it, You should assign originalPoint from the where field of the event ! >Problem: >1) If the mouse doesn't move, the line will be flickering very badly. Yup ! >3) Erasing the old line first increases the "apparent" flicker Yup... >4) The loop should be terminated by the StillDown() function, not Button() Yup ! >Solution: > > repeat > until (not StillDown(...I forget the args...)); No args... >Problem: >1) In such a tight loop, the overhead of the trap dispatcher is significant > and contributes to the flicker in a bad way. >Solution: >Get the real function address from the GrafPorts QD procs in variables and >execute them directly NO ! This is NOT a recommended way anymore ! >Problem: >1) When the new point differs from the old point by only a small amount, the > line will still flicker because of the large amount of common area between >Solution: > Copy window contents into offscreen bitmap #1 (a paint program would have > this as the actual image data) [ lots of bitmap stuff deleted ] >Can you say "Yuck"? I knew you could... Fortunately, the solution is MUCH simpler ! >The method that uses the pre-fetched addresses for the functions is probably >suitable for most needs, although probably not in a professional product. It may break some exotic systems, too (like, the 8.24GC maybe ? Or some other stuff ? Don't know about A/UX, for example) >Good luck... I recommend the following function: Point RubberBand ( Point start ) { Point end , tmp ; PenPat ( gray ) ; PenMode ( patXor ) ; PenSize ( 1 , 1 ) ; GetMouse ( & end ) ; if ( ! StillDown ( ) ) return start ; MoveTo ( start . h , start . v ) ; LineTo ( end . h , end . v ) ; while ( StillDown ( ) ) { tmp = end ; GetMouse ( & end ) ; if ( end . v != tmp . v || end . h != tmp . h ) { LineTo ( start . h , start . v ) ; LineTo ( end . h , end . v ) ; } } return end ; } This routine looks very nice. One might argue that you should draw the new line first and erase the old later, but that's a matter of taste. Try it and see what you like best. Change the innermost LineTos to: MoveTo ( end . h , end . v ) ; LineTo ( start . h , start . v ) ; LineTo ( tmp . h , tmp . v ) ; The base of this approach is not to draw anything unless the pointer has moved, and then only redraw what's necessary once. No messing with offscreen bitmaps, and no messing with JSRs to long ints (shudder) or call of function pointers. Any problem can be solved easily and elegantly using some pure thought ;-) Happy hacking, H+ Jon W{tte, Stockholm, Sweden, h+@nada.kth.se
jmunkki@hila.hut.fi (Juri Munkki) (01/09/91)
In article <127611@linus.mitre.org> laf@mbunix.mitre.org (Lee Fyock) writes: >I've found that an easier way to avoid flicker (easier than using >background bitmaps, etc.) is to just put in a check to see if the >mouse has moved before erasing and redrawing the line. This is >extremely flicker-free, with very little code. I suggested using offscreen bitmaps so that the exclusive or mode could be avoided. It also eliminates flicker, that is just a side effect and IMHO, no drawing should be made until it is necessary. If you look at the code that I included, you will see that it checks if the mouse has moved and only then draws. >It isn't, of course, >very multifinder friendly. What happens to the GetNextEvent version >of this code if the user clicks on the multifinder icon in the >menu bar and the program gets switched out? It won't know it >because app4events aren't being read, right? The GetNextEvent in my code is only called when mouse button is down. It has the mouseDown mask because it is possible that the mouseUp is lost in some conditions (like when you drop into Think C's source debugger). If a mouseup is lost, a new mouseup will not be reported before a mousedown is delivered. All other events are masked out. This doesn't mean that the events are simply lost. They should be quite safe in the event queue. The application layer can not be changed by the user, because you need to release the mouse button before you can CLICK the menu bar. When the button is released, our rubberbanding loop exits and the normal main even loop is probably used for further events. The only case where a layer change might occur is if some external program (like the debugger) causes the change. As far as I know, Apple User Interface Guidelines explicitly forbid behavior like this. Using StillDown to track the loop is extremely unfriendly to other multifinder applications. A terminal program that relies on being called regularly might fail a download just because the user is holding down the mouse button. (Or maybe the mouse button is pressed down by an object that is "temporarily stored on the mouse.") Let's put a stop to programs that fail to allow background tasking less than 10 times per second. The only reason why an application will not allow background tasks should be when the user is selecting from a menu (a menu manager menu). I hope this answers your doubts about my programming techniques. ____________________________________________________________________________ / Juri Munkki / Helsinki University of Technology / Wind / Project / / jmunkki@hut.fi / Computing Center Macintosh Support / Surf / STORM / ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sfleming@cs.hw.ac.uk (Stewart T. Fleming) (01/10/91)
In article <hf"2h2."b2@smurf.sub.org>, urlichs@smurf.sub.org (Matthias Urlichs) writes: |> In comp.sys.mac.programmer, article <27725.2787e3d0@kuhub.cc.ukans.edu>, |> mlab2@kuhub.cc.ukans.edu writes: |> < |> < When in line-drawing-mode, a mouseDown event in the drawing window should |> Modify at will for drawing other shapes, of course... |> If you simply extend this procedure to do other shapes, then you will likely get horrific flicker problems. True/False ? Drawing and erasing the whole shape is fairly obvious to the user. Drawing lines with this method is fine, but for other shapes : Procedure TrackShape(Var currPt, prevPt, startPt: Point); Var currRect, prevRect: Rect; prevRgn, dRgn: RgnHandle; Begin {Draw if mouse moves or is outside the window frame (autoscroll)} If NeedToScroll(currPt) Or Not EqualPt(currPt, prevPt) Then Begin GetWindowFrame(currRect); {Pin point in frame} PinInRect(currRect, currPt); Pt2Rect(startPt, currPt, currRect); {Make shapes} Pt2Rect(startPt, prevPt, prevRect); dRgn := OutlineRgn(currRect); {Outline shapes} prevRgn := OutlineRgn(prevRect); XorRgn(dRgn, prevRgn, gUtilRgn); {Find bits changed} DisposeRgn(prevRgn); DisposeRgn(dRgn); PenMode(patXor); {Draw bits changed} PaintRgn(gUtilRgn); End; End; Function OutlineRgn (theRect: Rect): RgnHandle; Var outRgn, inRgn: RgnHandle; Begin outRgn := NewRgn; inRgn := NewRgn; RectRgn(outRgn, theRect); { Construct region consisting of a } CopyRgn(outRgn, inRgn); { one pixel thick outline of } InsetRgn(inRgn, 1, 1); { the previous selection rect } DiffRgn(outRgn, inRgn, outRgn); OutlineRgn := outRgn; DisposeRgn(inRgn); End; I wrote this as a mouse task for Think Pascal with OOP. It works, but I don't think it's too pretty. Feel free to take it apart :-) Here, I just use rectangles, but any shape could be done in the same way by changing OutlineRgn. NeedToScroll checks if the point is outside the frame; if so, it calls ScrollRect. |> I'd suggest some enhancements: > - invalidating the old line rather than simply erasing it; |> there might be other shapes behind it. Do you need to do this after every move ? Or not until the user releases the mouse ? |> Matthias Urlichs -- urlichs@smurf.sub.org -- urlichs@smurf.ira.uka.de Stewart -- sfleming@cs.hw.ac.uk ...ukc!cs.hw.ac.uk!sfleming "Before starting any programming project, try explaining it to your cat."