; ; File: BitMaptoRegion.a ; ; Contains: FUNCTION BitMapToRegionGlue(region: RgnHandle;bMap: BitMap): OSErr; ; ; Given a region and bitmap, BitMapRgn makes the region a bounding ; region for the 'map. If it can't get memory it will return a ; Memory Manager-type error and an empty region gibbley. Note that ; the region might also be empty with no error (if the bounds is an ; empty rectangle or there are no 1 bits in the bitmap). Lastly, ; if the region would have to exceed 32K it returns a result of ; -500 (rgnTooBigErr). ; ; The bMap parameter may be a pointer to a bitmap, a pointer to a ; pixmap, or a pointer to a portBits field in a color grafport. ; In the latter two cases, if the pixmap is not 1-bit deep, an error ; result of -148 (pixmapTooDeepErr) is returned. ; ; (the nibble state machine idea is from the Finder MaskToRgn routine) ; ; Copyright: © 1988-1992 by Apple Computer, Inc. All rights reserved. ; ; Change History (most recent first): ; ; 10/28/92 SWC Changed the INCLUDEs to a LOAD of StandardEqu.d. ; 10/26/92 CSS Change a short branch to a word branch. ; <2> 7/24/91 MH Added conditional wrapper(s) to prevent duplication of public ; interface declarations: rgnTooBigErr, pixmapTooDeepErr ; LOAD 'StandardEqu.d' IF (&TYPE('Debugging') = 'UNDEFINED') THEN Debugging EQU 0 ;debugging flag (1 = TRUE) ENDIF ;________________________________________________________________________________ ; ; FUNCTION BitMapToRegionGlue(region: RgnHandle;bMap: BitMap): OSErr; ; ;________________________________________________________________________________ ; ;Theory ; We scan each line of the bitmap and pump inversion points (ip's) into the region ; to put the areas with ones in the bitmap into the region and the areas ; with zeroes outside the region. ; ; In order to keep track of where we are in "inversion land" we use two ; techniques: ; The first is a scanline buffer which records the changes ; (zeroes to ones and vice versa) as we go. Wherever a change occurs (a ; 1 next to a 0 in the buffer) we need to put out an inversion point. ; The second is a togglin' flag which tells us whether we are "inverted" or not. ; Since we use a state machine in the innermost (nibble) loop to churn out ; ip's, the input to the state machine must be complemented if the flag is set. ; The loop stuff looks like this: ; outer line loop (grows handle in anticipation of worst case for next line) ; longword loop for current line (puts out inter-long ip's as needed) ; loop for 4 nibbles in current long (calls state maching for each nibble) ; ;________________________________________________________________________________ BitMapRgnTrap EQU $A8D7 UnimplementedTrap EQU $A89F isCPort EQU 14 ;bit 14 in rowbytes means portBits in CGrafPort MACRO SafeDisposeHandle MOVE.L A0,D0 BEQ.S @0 _DisposHandle @0 ENDM BitMapToRegionGlue PROC EXPORT ;let's check for the existence of the trap MOVE.W #BitMapRgnTrap,D0 _GetTrapAddress newTool MOVE.L A0,A1 ;stash address in A1 MOVE.W #UnimplementedTrap,D0 _GetTrapAddress newTool ;unimplemented trap is in A0 CMP.L A0,A1 ;unimplemented? BEQ.S RunRAM ;skip if so JMP (A1) ;go to _BitMapRgn ;the trap is unimplemented, run the RAM version RunRAM BMFrame RECORD {A6Link},DECR result DS.W 1 paramTop EQU * regionH DS.L 1 bMapPtr DS.L 1 paramSize EQU paramTop-* return DS.L 1 A6Link DS.L 1 rowLongs DS.L 1 ;number of longwords per line rightMask DS.L 1 ;mask for rightmost long of each line slHandle DS.L 1 ;handle to scanline buffer numLines DS.W 1 ;number of lines in bitmap rowNumBytes DS.W 1 ;rowbytes from the bitmap startSize DS.W 1 ;size of region at start of line lastLineH DS.L 1 ;last line (zeroes) handle handSize DS.L 1 ;size of handle (avoid calls to GetHandleSize) max2Add DS.L 1 ;worst case for bytes we could add for next line localSize EQU * ENDR WITH BMFrame LINK A6,#localSize MOVEM.L A2-A5/D3-D7,-(SP) ;save work registers CLR.L slHandle(A6) ;no scanline handle, yet CLR.W result(A6) ;function result presumed zero at start MOVE.L regionH(A6),A0 MOVE.L (A0),A2 MOVEQ #0,D0 MOVE.W (A2),D0 ;get size of region MOVE.L D0,handSize(A6) ;save it long ;get boundary rectangle so we can tell how to process the bitmap MOVE.L bMapPtr(A6),A1 ;get bitmap pointer MOVE.W rowBytes(A1), D0 ;rowbytes BPL.S @1 ;it's a bitmap so go ahead BTST #isCPort, D0 ;is this a ptr to portBits? BEQ.S @2 ;nope; it's a ptr to a pixmap MOVE.L baseAddr(A1), A0 ;get the PixMapHandle MOVE.L (A0), A1 ;and get the real ptr to pixmap @2 CMP.W #1, pmPixelSize(A1) ;is it 1 bit per pixel deep? BEQ.S @1 ;if yes, we're fine MOVE.W #pixmapTooDeepErr, D0 ;return an error otherwise BRA BMRBadEmpty ;clean up and bail out @1 MOVE.W rowBytes(A1), rowNumBytes(A6) ;get the rowbytes from the bit/pixmap ANDI.W #$7FFF, rowNumBytes(A6) ;mask off pixmap flag MOVE.L bounds+topLeft(A1),D2 ;get topLeft MOVE.W bounds+right(A1),D0 ;get right ;figure the number of longs per row (according to width, not rowbytes) ;so we can get a scanline buffer SUB.W D2,D0 ;right - left BLE BMREmptyOut ;if empty rect. then empty region EXT.L D0 MOVE.L D0,D4 ADD.L D4,D4 ;double width for 2 bytes/ip ADDQ.L #4+2,D4 ;add 4 bytes for y value and $7FFF word ;add 2 more for the $7FFF if the last line ADD.L D4,D4 ;double, just 'cause I feel like it! MOVE.L D4,max2Add(A6) ;save max. bytes for a given line MOVEQ #32,D7 ;(side effect: clear high word of D7) DIVU D7,D0 ;number of longs = width/32 ;get a mask for the rightmost long into rightMask MOVE.L D0,D3 ;save remainder(hi word) SWAP D3 ;get remainder from width/32 MOVEQ #-1,D1 ;default rightmost long mask TST.W D3 ;zero remainder? BEQ.S @0 ;yes, $FFFF is a good mask ADDQ.W #1,D0 ;we need one more long SUB.W D3,D7 ;32 - remainder = zero bits to shift in ASL.L D7,D1 ;get proper mask @0 MOVE.L D1,rightMask(A6) EXT.L D0 MOVE.L D0,rowLongs(A6) ;save # of longs ASL.L #2,D0 ;longs => bytes ;get the scanline buffer (D0 = number of bytes per line) _NewHandle clear ;get a scanline buffer (of zeroes) BNE BMRBadEmpty ;if we failed then return a NIL handle MOVE.L A0,slHandle(A6) ;save buffer handle ;figure the number of lines MOVE.L D2,D3 SWAP D3 ;get top MOVE.W bounds+bottom(A1),D0 ;get bottom SUB.W D3,D0 ;bottom - top BLE BMREmptyOut ;if empty rect. then empty region MOVE.W D0,numLines(A6) ;number of lines MOVE.L baseAddr(A1),A4 ;point to start of map MOVE.W #rgnData,D7 ;initial region size ;OK, now we start the loops. ; A1 will point to the bitmap long, ; A2 to the region. ; A3 points to the current scanline buffer long. ; A4 will point to the row in the map. ; A5 points to the current word (= size + A2) ; D1 holds the current long (modified). ; D2 holds the leftmost coordinate of bitmap.bounds. ; D3 has the y coordinate, and ; D4 the x coordinate (high word stays clear!). ; D5 has number of longs remaining for current line. ; D6 holds the (on or off) value of the "beam" (for the line). ; D7 holds the size outside the longword loop (used as scratch while nibbling). ; (we assume at the very end that D7's high word has remained clear) BMRHScramLine MOVE.L regionH(A6),A2 MOVE.L (A2),A2 ;point to start of region BMRLineLoop LEA (A2,D7.W),A5 ;point to new region start + size MOVE.L handSize(A6),D1 ;get handle size SUB.W D7,D1 ;handle size - region size CMP.L max2Add(A6),D1 ;is there enough for worst case on next line? BGE.S @1 ;skippy if so MOVE.L handSize(A6),D0 ;get handle size ADD.L max2Add(A6),D0 ;add more than enough for worst case on next line MOVE.L D0,handSize(A6) ;save new size MOVE.L regionH(A6),A0 ;region handle _SetHandleSize BNE BMRBadEmpty ;if we failed then return a NIL handle BRA.S BMRHScramLine ;rederef. handle and recompute current pointer @1 MOVE.W D2,D4 ;get current x coordinate from left MOVEQ #0,D6 ;beam initially off MOVE.L A4,A1 ;start of current line into map pointer MOVE.L rowLongs(A6),D5 ;longs remaining for current line MOVE.L slHandle(A6),A3 ;A3 points to the current "differences" long MOVE.L (A3),A3 ; Note: within this loop we assume that nothing will be done to move the heap MOVE.W D3,D0 ;get y position BSR OutputRgnWord ;output y pos to region MOVE.W D7,startSize(A6) ;save size at line start (a la Derwood) BRA NextBMRLong ;enter the long loop BMRLongLoop MOVE.L (A1)+,D0 ;fetch the next long for this line BMRLastLEntry MOVE.L (A3),D1 ;get differences long EOR.L D0,D1 ;compute the differences BNE BMRDiff ;if not the same, skip ahead BMRSame ;since we want to skip this long (it matches the previous line) we need to ;put out an ip if the beam is on TST.B D6 ;beam on? BEQ.S @1 ;skip if not MOVE.W D4,(A5)+ ;pump it out MOVEQ #0,D6 ;beam off @1 ADD.W #32,D4 ;slip to next long's x coordinate @2 ADDQ.W #4,A3 ;to next changes buffer long BRA NextBMRLong ;---------------------------------------------------------------------------------------- ; Start of State Machine ; Handle state 0001 BMRState1 ADDQ.W #3,D4 ;bump x by 3 State1Common MOVE.W D4,(A5)+ ;generate one ;Tog1StateDone ADDQ.W #1,D4 ;bump x by one more TogStateDone NOT.B D6 ;toggle state RTS ; Handle state 0010 BMRState2 ADDQ.W #2,D4 ;bump x by 2 MOVE.W D4,(A5)+ ;generate one Gen1BumpBy1 BSR.S Gen1InvPoint ;and another one BumpBy1 ADDQ.W #1,D4 ;bump once more RTS ;state doesn't change ; Handle state 0011 BMRState3 ADDQ.W #2,D4 ;bump x by 2 MOVE.W D4,(A5)+ ;generate one ADDQ.W #2,D4 ;bump BRA.S TogStateDone ;toggle the state ; Handle state 0100 BMRState4 BSR.S Gen1InvPoint BSR.S Gen1InvPoint BumpBy2 ADDQ.W #2,D4 RTS ; Handle state 0101 BMRState5 BSR.S BMRState4 ;start out as state 4 SUBQ #1,D4 BRA.S State1Common ;use common code ; Handle state 0110 BMRState6 BSR.S Gen1InvPoint ADDQ.W #1,D4 BRA.S Gen1BumpBy1 ; Handle state 0111 BMRState7 BSR.S Gen1InvPoint ADDQ.W #3,D4 BRA.S TogStateDone ; Gen1InvPoint bumps x by one and then generates a horizontal inversion point Gen1InvPoint ADDQ.W #1,D4 ;bump by 1, first MOVE.W D4,(A5)+ ;add x value (ip) to region RTS ; Handle State 1000 BMRState8 MOVE.W D4,(A5)+ BSR.S Gen1InvPoint ADDQ.W #3,D4 RTS ; Handle State 1001 BMRState9 MOVE.W D4,(A5)+ BSR.S Gen1InvPoint ADDQ.W #2,D4 BRA.S State1Common ; Handle State 1010 (most complicated case) BMRState10 MOVE.W D4,(A5)+ BSR.S Gen1InvPoint BSR.S Gen1InvPoint BRA.S Gen1BumpBy1 ; Handle State 1011 BMRState11 MOVE.W D4,(A5)+ BSR.S Gen1InvPoint BSR.S Gen1InvPoint ADDQ.W #2,D4 BRA.S TogStateDone ; Handle State 1100 BMRState12 MOVE.W D4,(A5)+ ADDQ.W #2,D4 MOVE.W D4,(A5)+ BRA.S BumpBy2 ; Handle State 1101 BMRState13 BSR.S BMRState12 SUBQ #1,D4 BRA.S State1Common ; Handle State 1110 BMRState14 MOVE.W D4,(A5)+ ADDQ.W #3,D4 MOVE.W D4,(A5)+ BRA.S BumpBy1 ; State table BMRHandler BRA.S BMRState0 BRA.S BMRState1 BRA.S BMRState2 BRA.S BMRState3 BRA.S BMRState4 BRA.S BMRState5 BRA.S BMRState6 BRA.S BMRState7 BRA.S BMRState8 BRA.S BMRState9 BRA.S BMRState10 BRA.S BMRState11 BRA.S BMRState12 BRA.S BMRState13 BRA.S BMRState14 ; Handle State 15 or 1111 BMRState15 MOVE.W D4,(A5)+ ;generate one now NOT.B D6 ;toggle the state ; Handle State 0 or 0000 BMRState0 ADDQ.W #4,D4 RTS ; End of the State Guys ;---------------------------------------------------------------------------------------- BMRDiff MOVE.L D0,(A3)+ ;fix up scanline buffer for next time ; this long is different from the last one, so output a bunch ; of inversion points by pumping it through the state machine, a nibble ; at a time. MOVEQ #3,D7 ;4 bytes to process (D7 high word clear) MOVEQ #0,D0 ;prevent need to mask for first nibble ; here is the loop where we feed it through a nibble at a time. ; it's worth it to special case a whole byte of 0 BMRByteLoop ROL.L #8,D1 ;get next (topmost) byte TST.B D1 ;is it zero? BNE.S BMRNibble ;if not, 4 bits at a time TST.B D6 BNE.S BMRNibble ;if beam on, must pass through ;the top 8 are zero, so we can save some time ADDQ.W #8,D4 ;bump x BRA.S BMRNextByte ;take care of the rightmost long for a line BMRLastLong MOVE.L (A1),D0 ;fetch the long from the bitmap AND.L rightMask(A6),D0 ;mask off right bits that aren't in map BRA BMRLastLEntry ;go process this long ; handle the first nibble BMRNibble MOVE.B D1,D0 ;get byte EOR.B D6,D0 ;invert nibble when beam is on LSR.B #4,D0 ;get 1st nibble ADD.W D0,D0 ;double for word index JSR BMRHandler(D0.W) ;invoke the handler ; handle the second nibble MOVE.B D1,D0 ;get byte again EOR.B D6,D0 ;invert nibble when beam is on AND.W #%1111,D0 ;mask to it ADD.W D0,D0 ;double for word index JSR BMRHandler(D0.W) ;invoke the handler BMRNextByte DBRA D7,BMRByteLoop ;loop for all 8 nibbles ; bump to the next long NextBMRLong SUBQ.W #1,D5 ;decrement longword index BGT BMRLongLoop ;not at end, loop for whole line BEQ.S BMRLastLong ;process last long for this line ; we've reached the end of the (this) line BMREOL MOVE.W A5,D7 ;current region pointer SUB.W A2,D7 ;figga region size CMP.W startSize(A6),D7 ;did we add inv. pts to this line? BEQ.S BMRNoLine ;br = no, so back up BLT BMR32KErr ;if the size decreased, we overflowed ; if the state is on, generate one last inversion point TST.B D6 BEQ.S @1 MOVE.W D4,(A5)+ ;generate a last one ADDQ.W #2,D7 ;keep sizable advantage @1 ; end the scan line with the traditional $7FFF BSR.S OutputLastRgnWord BMREOL2 ADDQ.W #1,D3 ;bump y position MOVE.W D2,D4 ;start x at left again ADD.W rowNumBytes(A6),A4 ;bump to next row in map SUBQ.W #1,numLines(A6) BGT BMRLineLoop ;if we're not done then do next line BLT.S BMRFinis ;br if really done ; as the last line process an imaginary line of zeroes to end the region… MOVE.L rowLongs(A6),D0 ASL.L #2,D0 ;longs => bytes _NewHandle clear ;get a full line of zero bits BNE BMRBadEmpty ;if we failed then return a NIL handle CSS MOVE.L A0,lastLineH(A6) ;save handle MOVE.L (A0),A4 ;start of current line BRA BMRHScramLine ;do this last one (and rederef handle) BMRNoLine SUBQ.L #2,A5 ;back up pointer SUBQ.W #2,D7 ;back down size BRA.S BMREOL2 ;go for next line ; Append the "end of line" token to the region OutputLastRgnWord MOVE.W #$7FFF,D0 ; OutputRgnWord takes the word in D0, appends it to the region, ; and leaves the condition codes set for ADDQ.W D7 (which contains the length) OutputRgnWord MOVE.W D0,(A5)+ ;put a word to the region ADDQ.W #2,D7 ;ink the size RTS ; all done so clean up, output the final $7FFF BMRFinis MOVE.L lastLineH(A6),A0 SafeDisposeHandle ;get rid of that last line of zeroes CMP.W #10,D7 ;is region empty of inversion points? BEQ.S BMREmptyOut ;skip if so (it's an empty region) BSR.S OutputLastRgnWord ;put End-O-Region ($7FFF) word BMI.S BMR32KErr ;if we went negative, we overflowed! ; find the smallest rectangle that encompasses all the inversion points ; A0 will point to the current region word, A1 to the start of the line ; D1 will have the smallest x, D2 the largest x, D4 will contain $7FFF ; D3 gets the smallest y value (which we know at the start) LEA rgnData(A2),A0 ;point A0 past the rgnBBox MOVE.W #$7FFF,D4 MOVE.W D4,D1 ;smallest x so far = $7FFF MOVE.W #$8000,D2 ;largest x so far = -32768 MOVE.W (A0),D3 ;smallest y there is BRA.S BMRPackStart ;enter loop BMRPackY MOVE.L A0,A1 ;remember where the y value is (sort of) CMP.W (A0)+,D1 ;less than smallest x so far? BLE.S @1 ;skip if not MOVE.W -2(A0),D1 ;new smallest x @1 CMP.W (A0)+,D4 ;end of line? BNE.S @1 ;if not then keep looking CMP.W -4(A0),D2 ;last x greater than largest x so far? BGE.S BMRPackStart ;skip if not MOVE.W -4(A0),D2 ;new largest x BMRPackStart MOVE.W (A0)+,D0 ;get next word (y value or $7FFF) CMP.W D4,D0 ;if $7FFF then we're done BNE.S BMRPackY ;otherwise loop SWAP D3 ;top into top word MOVE.W D1,D3 ;left into bottom word MOVE.W -2(A1),D4 ;bottom (from last y at start of line) SWAP D4 ;move bottom to high word MOVE.W D2,D4 ;get right CMP.W #28,D7 ;size = 28? (do we have a rect. region?) BEQ.S BMRRect ;skip if so BRA.S BMROut ;return complex region ;the region would exceed 32K, so we have to error out, man BMR32KErr MOVE.W #rgnTooBigErr,D0 ;if >32K needed return error ;we come here after failing a SetHandleSize (or NewHandle) BMRBadEmpty MOVE.W D0,result(A6) ;OSErr function result ; emptify the region on errors (or when it should be empty with no error) BMREmptyOut MOVE.L regionH(A6),A0 ;handle to region MOVE.L (A0),A2 ;point to it CLR.L D3 ;(0, 0) to topLeft CLR.L D4 ;(0, 0) to botRight BMRRect MOVEQ #10,D7 ;the size of the region = 10 ;output the region with size (longword, high word clear) in D7 ;D3 comes in with topLeft, D4 with botRight BMROut MOVE.W D7,(A2)+ ;the size of the region MOVE.L D3,(A2)+ ;topLeft to rgnBBox MOVE.L D4,(A2) ;botRight to rgnBBox MOVE.L D7,D0 ;size MOVE.L regionH(A6),A0 ;handle to region _SetHandleSize BMRDspSL MOVE.L slHandle(A6),A0 SafeDisposeHandle ;get rid of the scanline buffer (even if NIL) BMRDone MOVEM.L (SP)+,A2-A5/D3-D7 ;restore work registers UNLK A6 MOVE.L (SP)+,A0 ;pop return address ADD #paramSize,SP ;pop params JMP (A0) ENDWITH END