<> <> <> DIRECTORY Basics USING [LongNumber], BitmapEdit USING [], Imager USING [black, Box, Color, Context, MakeGray, MaskBox, SetColor, TranslateT, white], ImagerPixelMap USING [Create, DeviceRectangle, Fill, GetBit, PixelMap, ShiftMap, Window], Process USING [MsecToTicks, Pause, priorityBackground, SetPriority], TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords], ViewerClasses USING [NotifyProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, SetNewVersion]; BitmapEditImpl: CEDAR MONITOR IMPORTS Imager, ImagerPixelMap, TIPUser, ViewerOps, Process EXPORTS BitmapEdit ~ BEGIN Bitmap: TYPE ~ REF BitmapRep; BitmapRep: TYPE ~ RECORD [ xPrev, yPrev: INTEGER, xSize, ySize: NAT, xMin, yMin: INTEGER, xWidth, yWidth: INTEGER, pixelMap: ImagerPixelMap.PixelMap, shared: BOOLEAN, refreshProcess: PROCESS ]; NewBitmap: PROC [height, width: NAT] RETURNS [bitmap: Bitmap] ~ { bitmap _ NEW[BitmapRep]; bitmap.xSize _ width; bitmap.ySize _ height; bitmap.pixelMap _ ImagerPixelMap.Create[0, [0, 0, height, width]]; }; GetBit: PROC [bitmap: Bitmap, x, y: INTEGER] RETURNS [value: NAT] ~ { value _ bitmap.pixelMap.GetBit[bitmap.ySize-y-1, x]; }; StoreBit: PROC [bitmap: Bitmap, x, y: INTEGER, value: NAT] ~ { bitmap.pixelMap.Fill[[bitmap.ySize-y-1, x, 1, 1], value]; }; CreateBitmapViewer: PUBLIC PROC [height, width: NAT, info: ViewerClasses.ViewerRec] RETURNS [ViewerClasses.Viewer] ~ { info.data _ NewBitmap[height, width]; RETURN [ViewerOps.CreateViewer[$BitmapEdit, info]]; }; BitCoords: TYPE ~ REF BitCoordsRep; BitCoordsRep: TYPE ~ RECORD [x, y: INTEGER]; Box: TYPE ~ REF BoxRep; BoxRep: TYPE ~ RECORD [xMin, yMin, xMax, yMax: INTEGER]; boxHeight: NAT _ 5; RefreshProcess: PROC [viewer: ViewerClasses.Viewer] ~ { bitmap: Bitmap _ NARROW[viewer.data]; whatChanged: Box _ NEW[BoxRep]; x: INTEGER _ 0; y: INTEGER _ 0; TRUSTED {Process.SetPriority[Process.priorityBackground]}; UNTIL viewer.destroyed OR NOT bitmap.shared DO TRUSTED {Process.Pause[Process.MsecToTicks[77]]}; IF y > bitmap.ySize THEN {y _ 0; x _ x + 1}; IF x > bitmap.xSize THEN x _ 0; whatChanged^ _ [x, y, x+1, y+boxHeight]; ViewerOps.PaintViewer[viewer, client, FALSE, whatChanged]; y _ y + boxHeight; ENDLOOP; }; SetBitmap: PUBLIC PROC [viewer: ViewerClasses.Viewer, pixelMap: ImagerPixelMap.PixelMap, sWidth, fWidth: INTEGER, shared: BOOLEAN _ FALSE] ~ { bitmap: Bitmap _ NARROW[viewer.data]; window: ImagerPixelMap.DeviceRectangle _ pixelMap.Window; IF bitmap.refreshProcess # NIL THEN { bitmap.shared _ FALSE; TRUSTED {[] _ JOIN bitmap.refreshProcess}; bitmap.refreshProcess _ NIL; }; bitmap.xSize _ window.fSize; bitmap.ySize _ window.sSize; bitmap.xMin _ window.fMin; bitmap.yMin _ -window.sMin-window.sSize; bitmap.xPrev _ 0; bitmap.yPrev _ 0; bitmap.pixelMap _ pixelMap.ShiftMap[-window.sMin, -window.fMin]; bitmap.yWidth _ -sWidth; bitmap.xWidth _ fWidth; bitmap.shared _ shared; IF shared THEN { bitmap.refreshProcess _ FORK RefreshProcess[viewer]; }; ViewerOps.PaintViewer[viewer, client]; }; GetBitmap: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [pixelMap: ImagerPixelMap.PixelMap, sWidth, fWidth: INTEGER] ~ { bitmap: Bitmap _ NARROW[viewer.data]; pixelMap _ bitmap.pixelMap.ShiftMap[-bitmap.ySize-bitmap.yMin, bitmap.xMin]; sWidth _ -bitmap.yWidth; fWidth _ bitmap.xWidth; }; PixelSpacing: PROC [subjectSize, viewerSize: INT] RETURNS [pixelSpacing: INT] ~ { pixelSpacing _ MAX[(viewerSize/MAX[subjectSize, 1])/2*2, 1]; }; white: Imager.Color _ Imager.white; black: Imager.Color _ Imager.black; grey: Imager.Color _ Imager.MakeGray[0.5]; unknownAtom: ATOM _ NIL; BitmapPaintProc: PROC [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL] RETURNS [BOOL _ FALSE] ~ { bitmap: Bitmap _ NARROW[self.data]; pixelSpacing: NAT _ MIN[PixelSpacing[bitmap.xSize, self.cw-1], PixelSpacing[bitmap.ySize, self.ch-1]]; pixelSize: NAT _ MAX[pixelSpacing-1, 1]; context.TranslateT[[(self.cw-(pixelSpacing*bitmap.xSize+1))/2, (self.ch-(pixelSpacing*bitmap.ySize+1))/2]]; WITH whatChanged SELECT FROM bitCoords: BitCoords => { x: INTEGER _ bitCoords.x; y: INTEGER _ bitCoords.y; IF x IN [0..bitmap.xSize) AND y IN [0..bitmap.ySize) THEN { xDisplay: REAL _ x*pixelSpacing + 1; yDisplay: REAL _ y*pixelSpacing + 1; context.SetColor[IF GetBit[bitmap, x, y] = 0 THEN white ELSE black]; context.MaskBox[[xDisplay, yDisplay, xDisplay+pixelSize, yDisplay+pixelSize]]; }; }; box: Box => { cur: NAT _ 0; context.SetColor[white]; FOR x: INTEGER IN [MAX[box.xMin, 0]..MIN[box.xMax, bitmap.xSize]) DO xDisplay: REAL _ x*pixelSpacing + 1; yStart: INTEGER _ MAX[box.yMin, 0]; yDisplay: REAL _ yStart*pixelSpacing + 1; FOR y: INTEGER IN [yStart..MIN[box.yMax, bitmap.ySize]) DO IF GetBit[bitmap, x, y] # cur THEN { cur _ 1-cur; context.SetColor[IF cur = 0 THEN white ELSE black]; }; context.MaskBox[[xDisplay, yDisplay, xDisplay+pixelSize, yDisplay+pixelSize]]; yDisplay _ yDisplay + pixelSpacing; ENDLOOP; ENDLOOP; }; atom: ATOM => { SELECT whatChanged FROM $EraseOrigin => { IF pixelSpacing > pixelSize THEN { h: REAL _ pixelSpacing*bitmap.ySize+1; context.SetColor[grey]; context.MaskBox[[-bitmap.xMin*pixelSpacing, -8-bitmap.yMin*pixelSpacing, 1-bitmap.xMin*pixelSpacing, 8-bitmap.yMin*pixelSpacing]]; context.MaskBox[[-8-bitmap.xMin*pixelSpacing, -bitmap.yMin*pixelSpacing, 8-bitmap.xMin*pixelSpacing, 1-bitmap.yMin*pixelSpacing]]; context.MaskBox[[(bitmap.xWidth-bitmap.xMin)*pixelSpacing, -4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing, 1+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; context.MaskBox[[-4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, (bitmap.yWidth-bitmap.yMin)*pixelSpacing, 4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 1+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; context.SetColor[white]; context.MaskBox[[-9999, h, 9999, 9999]]; context.MaskBox[[-9999, 0, 0, h]]; context.MaskBox[[pixelSpacing*bitmap.xSize+1, 0, 9999, h]]; context.MaskBox[[-9999, -9999, 9999, 0]]; }; }; $SetOrigin => { IF pixelSpacing > pixelSize THEN { context.SetColor[black]; context.MaskBox[[-bitmap.xMin*pixelSpacing, -8-bitmap.yMin*pixelSpacing, 1-bitmap.xMin*pixelSpacing, 8-bitmap.yMin*pixelSpacing]]; context.MaskBox[[-8-bitmap.xMin*pixelSpacing, -bitmap.yMin*pixelSpacing, 8-bitmap.xMin*pixelSpacing, 1-bitmap.yMin*pixelSpacing]]; context.MaskBox[[(bitmap.xWidth-bitmap.xMin)*pixelSpacing, -4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing, 1+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; context.MaskBox[[-4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, (bitmap.yWidth-bitmap.yMin)*pixelSpacing, 4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 1+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; }; }; ENDCASE => unknownAtom _ atom; }; ENDCASE => { IF NOT clear THEN { context.SetColor[white]; context.MaskBox[[0, 0, self.cw, self.ch]]; }; context.SetColor[grey]; IF pixelSpacing > pixelSize THEN { xDisplay: REAL _ bitmap.xSize*pixelSpacing+1; yDisplay: REAL _ bitmap.ySize*pixelSpacing+1; FOR i: NAT IN [0..bitmap.xSize] DO x: REAL _ i*pixelSpacing; context.MaskBox[[x, 0, x+1, yDisplay]]; ENDLOOP; FOR i: NAT IN [0..bitmap.ySize] DO y: REAL _ i*pixelSpacing; context.MaskBox[[0, y, xDisplay, y+1]]; ENDLOOP; context.SetColor[black]; context.MaskBox[[-bitmap.xMin*pixelSpacing, -8-bitmap.yMin*pixelSpacing, 1-bitmap.xMin*pixelSpacing, 8-bitmap.yMin*pixelSpacing]]; context.MaskBox[[-8-bitmap.xMin*pixelSpacing, -bitmap.yMin*pixelSpacing, 8-bitmap.xMin*pixelSpacing, 1-bitmap.yMin*pixelSpacing]]; context.MaskBox[[(bitmap.xWidth-bitmap.xMin)*pixelSpacing, -4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing, 1+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 4+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; context.MaskBox[[-4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, (bitmap.yWidth-bitmap.yMin)*pixelSpacing, 4+(bitmap.xWidth-bitmap.xMin)*pixelSpacing, 1+(bitmap.yWidth-bitmap.yMin)*pixelSpacing]]; } ELSE { xDisplay: REAL _ bitmap.xSize*pixelSpacing+1; yDisplay: REAL _ bitmap.ySize*pixelSpacing+1; context.MaskBox[[0, 0, 1, yDisplay]]; context.MaskBox[[xDisplay, 0, xDisplay+1, yDisplay]]; context.MaskBox[[0, 0, xDisplay, 1]]; context.MaskBox[[0, yDisplay, xDisplay, yDisplay+1]]; }; context.SetColor[black]; { y: REAL _ 1; s: REAL _ pixelSpacing; FOR iy: NAT IN [0..bitmap.ySize) DO x: REAL _ 1; FOR ix: NAT IN [0..bitmap.xSize) DO IF GetBit[bitmap, ix, iy] = 1 THEN context.MaskBox[[x, y, x+pixelSize, y+pixelSize]]; x _ x + s; ENDLOOP; y _ y + s; ENDLOOP; }; }; }; bitCoords: BitCoords _ NEW[BitCoordsRep]; StorePixel: PROC [self: ViewerClasses.Viewer, x, y: INTEGER, value: NAT] ~ { bitmap: Bitmap _ NARROW[self.data]; bitmap.xPrev _ x; bitmap.yPrev _ y; IF x IN [0..bitmap.xSize) AND y IN [0..bitmap.ySize) THEN { StoreBit[bitmap, x, y, value]; bitCoords.x _ x; bitCoords.y _ y; ViewerOps.PaintViewer[self, client, FALSE, bitCoords]; }; ViewerOps.SetNewVersion[self]; }; StorePixels: PROC [self: ViewerClasses.Viewer, x, y: INTEGER, value: NAT] ~ { bitmap: Bitmap _ NARROW[self.data]; RecLine: PROC [x0, y0, x1, y1: INTEGER] ~ { IF ABS[x0 - x1] > 1 OR ABS[y0 - y1] > 1 THEN { xm: INTEGER _ (x0+x1)/2; ym: INTEGER _ (y0+y1)/2; RecLine[x0, y0, xm, ym]; RecLine[xm, ym, x1, y1]; }; IF x0 IN [0..bitmap.xSize) AND y0 IN [0..bitmap.ySize) THEN { StoreBit[bitmap, x0, y0, value]; bitCoords.x _ x0; bitCoords.y _ y0; ViewerOps.PaintViewer[self, client, FALSE, bitCoords]; }; }; RecLine[bitmap.xPrev, bitmap.yPrev, x, y]; bitmap.xPrev _ x; bitmap.yPrev _ y; ViewerOps.SetNewVersion[self]; }; SetOrigin: PROC [self: ViewerClasses.Viewer, x, y: INTEGER] ~ { bitmap: Bitmap _ NARROW[self.data]; IF bitmap.xMin = -x AND bitmap.yMin = - y THEN RETURN; ViewerOps.PaintViewer[self, client, FALSE, $EraseOrigin]; bitmap.xMin _ - x; bitmap.yMin _ - y; ViewerOps.PaintViewer[self, client, FALSE, $SetOrigin]; ViewerOps.SetNewVersion[self]; }; SetWidth: PROC [self: ViewerClasses.Viewer, x, y: INTEGER] ~ { bitmap: Bitmap _ NARROW[self.data]; IF bitmap.xWidth-bitmap.xMin = x AND bitmap.yWidth-bitmap.yMin = y THEN RETURN; ViewerOps.PaintViewer[self, client, FALSE, $EraseOrigin]; bitmap.xWidth _ bitmap.xMin + x; bitmap.yWidth _ bitmap.yMin + y; ViewerOps.PaintViewer[self, client, FALSE, $SetOrigin]; ViewerOps.SetNewVersion[self]; }; Div: PROC[num: INT, denom: NAT] RETURNS [quotient: INT] ~ { long: Basics.LongNumber; long.li _ num; quotient _ 0; WHILE long.li < 0 DO long.highbits _ long.highbits + denom; quotient _ quotient - LAST[CARDINAL]-1; ENDLOOP; quotient _ quotient + long.lc/denom; }; BitmapNotifyProc: ViewerClasses.NotifyProc = { IF ISTYPE[input.first, TIPUser.TIPScreenCoords] THEN { bitmap: Bitmap _ NARROW[self.data]; mousePlace: TIPUser.TIPScreenCoords _ NARROW[input.first]; pixelSpacing: NAT _ MIN[PixelSpacing[bitmap.xSize, self.cw-1], PixelSpacing[bitmap.ySize, self.ch-1]]; x: INTEGER _ Div[mousePlace.mouseX*2 - (self.cw-(pixelSpacing*bitmap.xSize+1)), pixelSpacing*2]; y: INTEGER _ Div[mousePlace.mouseY*2 - (self.ch-(pixelSpacing*bitmap.ySize+1)), pixelSpacing*2]; SELECT input.rest.first FROM $DrawPixel => StorePixel[self, x, y, 1]; $ErasePixel => StorePixel[self, x, y, 0]; $DrawPixels => StorePixels[self, x, y, 1]; $ErasePixels => StorePixels[self, x, y, 0]; $SetOrigin, $SetWidth => { x _ Div[mousePlace.mouseX*2-(self.cw-(pixelSpacing*bitmap.xSize+1)-pixelSpacing), pixelSpacing*2]; y _ Div[mousePlace.mouseY*2-(self.ch-(pixelSpacing*bitmap.ySize+1)-pixelSpacing), pixelSpacing*2]; IF input.rest.first = $SetOrigin THEN SetOrigin[self, x, y] ELSE SetWidth[self, x, y]; }; ENDCASE => NULL; }; }; bitmapClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: BitmapPaintProc, notify: BitmapNotifyProc, tipTable: TIPUser.InstantiateNewTIPTable["BitmapEdit.TIP"] ]]; ViewerOps.RegisterViewerClass[$BitmapEdit, bitmapClass]; END.