diff --git a/include/tools.bm b/include/tools.bm new file mode 100644 index 0000000..e6f03fc --- /dev/null +++ b/include/tools.bm @@ -0,0 +1,145 @@ + + +sub filledPolygon (Points() as single, col as long) + dim i as integer, j as integer + dim x1 as single, y1 as single, x2 as single, y2 as single + dim intersectX as single + ' get the number of points from the upper bound of the array + ' divide by 2 since we have x,y pairs + dim numPoints as integer + numPoints = (ubound(Points) + 1) \ 2 + ' loop through each scanline (rows of pixels) + dim intersections(100) as single + dim numIntersections as integer + for y = 0 to _height ' adjust for screen height + numIntersections = 0 + ' check for intersections between the polygon edges and this scanline + for i = 0 to numPoints - 1 + x1 = Points(i * 2) + y1 = Points(i * 2 + 1) + x2 = Points(((i + 1) mod numPoints) * 2) + y2 = Points(((i + 1) mod numPoints) * 2 + 1) + ' check if the scanline intersects with the edge of the polygon + if ((y1 > y and y2 <= y) or (y2 > y and y1 <= y)) then + ' calculate intersection point with the scanline + intersectX = x1 + (y - y1) * (x2 - x1) / (y2 - y1) + intersections(numIntersections) = intersectX + numIntersections = numIntersections + 1 + end if + next i + ' sort the intersections (sort by x-coordinates) + for i = 0 to numIntersections - 1 + for j = i + 1 to numIntersections - 1 + if intersections(i) > intersections(j) then + swap intersections(i), intersections(j) + end if + next j + next i + ' fill the area between pairs of intersections + for i = 0 to numIntersections - 1 step 2 + line(intersections(i),y)-(intersections(i+1),y),col + next i + next y +end sub + +sub thickline(x1,y1,x2,y2, col as long) + if state.brushsize=1 then + line(x1,y1)-(x2,y2),col + else + dim tempimg as long + dim od as long + tempimg=_newimage(10,10,32) + od =_dest + _dest tempimg + pset(0,0),col + _dest od + a = _Atan2(y2 - y1, x2 - x1) + a = a + _Pi / 2 + x0 = 0.5 * state.brushsize * Cos(a) + y0 = 0.5 * state.brushsize * Sin(a) + _maptriangle _seamless(0,0)-(0,0)-(0,0),tempimg to(x1-x0, y1-y0)-(x1+x0,y1+y0)-(x2+x0,y2+y0),,_smooth + _maptriangle _seamless(0,0)-(0,0)-(0,0),tempimg to(x1-x0, y1-y0)-(x2+x0,y2+y0)-(x2-x0,y2-y0),,_smooth + _freeimage tempimg + end if +end sub + +Sub polygon (pa() As Long,col as long) + For i = 2 To UBound(pa) Step 2 + thickLine pa(i - 2), pa(i - 1),pa(i), pa(i + 1),col + Next i + thickLine pa(i - 1), pa(i - 2),pa(0), pa(1),col +End Sub +sub thickcircle(x, y, r, col as long) + if state.brushsize <= 1 then + circle (x, y), r, col + else + dim rp as single, rm as single, rp2 as single, rm2 as single + dim rpi2 as single, rmi2 as single, sp as single, sm as single + dim i as single + + rp = r + state.brushsize / 2 + rm = r - state.brushsize / 2 + + ' If the brush is thicker than the circle, it's just a filled circle + if rm < 0 then + filledcircle x, y, rp, col + exit sub + end if + + rp2 = rp ^ 2 + rm2 = rm ^ 2 + + ' Outer edges (Top/Bottom caps) + for i = -rp to -rm step .2 + rpi2 = rp2 - i ^ 2 + if rpi2 < 0 then rpi2 = 0 ' Safety Gate + sp = sqr(rpi2) + line (x + i, y - sp)-(x + i, y + sp), col, bf + next + + ' Side rings (where the hole in the middle exists) + for i = -rm to rm step .2 + rpi2 = rp2 - i ^ 2 + rmi2 = rm2 - i ^ 2 + if rpi2 < 0 then rpi2 = 0 ' Safety Gate + if rmi2 < 0 then rmi2 = 0 ' Safety Gate + sp = sqr(rpi2) + sm = sqr(rmi2) + ' Draw the top and bottom segments only + line (x + i, y + sm)-(x + i, y + sp), col, bf + line (x + i, y - sm)-(x + i, y - sp), col, bf + next + + ' Outer edges (Right cap) + for i = rm to rp step .2 + rpi2 = rp2 - i ^ 2 + if rpi2 < 0 then rpi2 = 0 ' Safety Gate + sp = sqr(rpi2) + line (x + i, y - sp)-(x + i, y + sp), col, bf + next + end if +end sub + +sub filledcircle(x,y,r,col as long) + dim __radius as integer, radiuserror as integer + dim tx as integer, ty as integer + __radius=abs(r)-1 + radiuserror=-__radius + tx=__radius + ty=0 + line (x-tx,y)-(x+tx,y),col + while tx>ty + radiuserror=radiuserror+ty*2+1 + if radiuserror >= 0 then + if tx<>ty+1 then + line (x-ty,y-tx)-(x+ty,y-tx),col + line (x-ty,y+tx)-(x+ty,y+tx),col + end if + tx=tx-1 + radiuserror=radiuserror-tx*2 + end if + ty=ty+1 + line (x-tx,y-ty)-(x+tx,y-ty),col + line (x-tx,y+ty)-(x+tx,y+ty),col + wend +end sub diff --git a/pixler.bas b/pixler.bas index c865fc0..70fe6df 100644 --- a/pixler.bas +++ b/pixler.bas @@ -5,6 +5,10 @@ type statetype offsetX as single offsetY as single zoom as single + brushsize as integer + startX as integer + startY as integer + isDrawing as integer end type type layertype @@ -14,11 +18,10 @@ type layertype kind as long end type -redim shared layers(1) as layertype +redim shared layers(2) as layertype dim shared state as statetype - dim shared mouseclicked as integer dim shared mousedown as integer dim shared rmouseclicked as integer @@ -33,6 +36,7 @@ dim as integer ch1,ch2,ch3,bt loadpalette "endesga16",pal() layers(0).ihandle=_newimage(640,350,32) layers(1).ihandle=_newimage(640,350,32) +layers(2).ihandle=_newimage(640,350,32) _dest layers(0).ihandle line (0,0)-(_width-1,_height-1),_rgb32(255),bf _dest 0 @@ -42,11 +46,13 @@ state.bcolor = 2 state.zoom = 1.0 state.offsetX = 70 + 20 ' To the right of the toolbox state.offsetY = 20 +state. brushsize = 1 dim lastMX, lastMY dim diffX as integer dim diffY as integer dim oldWidth as integer dim oldHeight as integer +dim keyin as string oldWidth=_width oldHeight=_height @@ -62,9 +68,9 @@ do cls line (0,0)-(_width-1,_height-1),backgroundcolor1,bf - while _mouseinput:mw=mw+_mousewheel:wend - ' --- Global Mouse Handling (Left & Right) --- + 'Mouse Handling + while _mouseinput:mw=mw+_mousewheel:wend mouseclicked = 0 rmouseclicked = 0 if mousedown = -1 and _mousebutton(1) = 0 then mouseclicked = -1 @@ -85,10 +91,19 @@ do if state.zoom < 0.1 then state.zoom = 0.1 mw=0 end if - 'state.zoom=mw/100 - canvas ' Uses mousedown and rmousedown for drawing/erasing + + ' Keyboarding + keyin=inkey$ + select case keyin + case "+" + state.brushsize=state.brushsize+1 + case "-" + if state.brushsize>1 then state.brushsize=state.brushsize-1 + end select + _dest 0 + canvas 'draw canvas first so it doesn't over toolbox - colorpicker ' Uses rmouseclicked for bcolor + colorpicker _limit 30 _display @@ -140,7 +155,7 @@ sub canvas ' Workspace background line (boxX1, 0)-(_width - 1, boxHeight), _rgb32(32), bf - ' Render Layers with Zoom/Offset[cite: 4] + ' Render Layers (0 = background,1 = drawlayer, 2=reference 3 = Preview) dim i as integer for i = 0 to ubound(layers) dim w as integer: w = _width(layers(i).ihandle) * state.zoom @@ -148,31 +163,73 @@ sub canvas _putimage (state.offsetX, state.offsetY)-(state.offsetX + w, state.offsetY + h), layers(i).ihandle next - ' --- Drawing Logic (Multi-Button) --- - if (mousedown or rmousedown) and _mousex > boxX1 then - ' Translate to Canvas Space - dim canX as integer: canX = (_mousex - state.offsetX) / state.zoom - dim canY as integer: canY = (_mousey - state.offsetY) / state.zoom - - ' Boundary Check - if canX >= 0 and canX < _width(layers(0).ihandle) then - if canY >= 0 and canY < _height(layers(0).ihandle) then - _dest layers(0).ihandle - - ' Select color based on button pressed - dim drawCol as _unsigned long - if mousedown then drawCol = state.fcolor else drawCol = state.bcolor - - ' Apply Tool - if state.tool = 1 then - pset (canX, canY), drawCol - elseif state.tool = 2 then - ' Example: Square brush - line (canX - 1, canY - 1)-(canX + 1, canY + 1), drawCol, bf - end if - - _dest 0 +' Translate Mouse to Canvas Space + dim canX as integer: canX = (_mousex - state.offsetX) / state.zoom + dim canY as integer: canY = (_mousey - state.offsetY) / state.zoom + + ' Color Selection: Left Click = Foreground, Right Click = Background + dim drawCol as _unsigned long + if mousedown or mouseclicked then drawCol = state.fcolor else drawCol = state.bcolor + + ' Interaction Boundary Check + if _mousex > boxX1 then + ' On Initial Click: Set the anchor point for shapes + if (mousedown or rmousedown) and state.isDrawing = 0 then + state.startX = canX + state.startY = canY + state.isDrawing = -1 + end if + + if state.isDrawing then + ' preview layer handles the "rubber-banding" of shapes + _dest layers(2).ihandle + cls , 0 + + select case state.tool + case 1 ' Pencil (Direct to Canvas) + _dest layers(1).ihandle + thickline state.startX, state.startY, canX, canY, drawCol + state.startX = canX: state.startY = canY + + case 2 ' Straight Line + thickline state.startX, state.startY, canX, canY, drawCol + + case 3 ' Thick Circle (Outline) + dim r as single: r = sqr((canX - state.startX)^2 + (canY - state.startY)^2) + r=abs(r)+1 + thickcircle state.startX, state.startY, r, drawCol + + case 4 ' Filled Circle + dim r_fill as single: r_fill = sqr((canX - state.startX)^2 + (canY - state.startY)^2) + filledcircle state.startX, state.startY, r_fill, drawCol + + case 5 ' Rectangle Outline (using thickline) + thickline state.startX, state.startY, canX, state.startY, drawCol + thickline canX, state.startY, canX, canY, drawCol + thickline canX, canY, state.startX, canY, drawCol + thickline state.startX, canY, state.startX, state.startY, drawCol + + case 6 ' Filled Rectangle + line (state.startX, state.startY)-(canX, canY), drawCol, bf + + case 7 + + case 8 ' Polygon Outline (using thickline) + ' Simple preview of a line; complex polygons usually require + ' a point-collection state machine. + thickline state.startX, state.startY, canX, canY, drawCol + + end select + + ' Release Logic: Commit preview to permanent layer + if _mousebutton(1) = 0 and _mousebutton(2) = 0 then + _dest layers(1).ihandle + _putimage , layers(2).ihandle + _dest layers(2).ihandle + cls , 0 + state.isDrawing = 0 end if + _dest 0 end if end if end sub @@ -208,6 +265,7 @@ end function '$include: 'include/ui.bm' '$include: 'include/imgout.bm' '$include: 'include/palette.bm' +'$include: 'include/tools.bm' function adduiicon(imagehandle as long) dim unknown as long