pixler/pixler.bas
2026-06-08 12:23:37 +02:00

1466 lines
47 KiB
QBasic

type statetype
tool as long
fcolor as long
bcolor as long
offsetx as long
offsety as long
zoom as long
brushsize as integer
startx as long
starty as long
isdrawing as integer
end type
type layertype
ihandle as long
blendmode as long
filter as long
kind as long
end type
redim shared layers(3) as layertype
dim shared state as statetype
redim shared commands(0) as string
dim shared showtoolbox as _byte:showtoolbox=-1
dim shared showcolorpicker as _byte:showcolorpicker=-1
dim shared showcommands as _byte:showcommands=0
dim shared mouseclicked as integer
dim shared mousedown as integer
dim shared rmouseclicked as integer
dim shared rmousedown as integer
$resize:on
screen _newimage(750,480,32)
_delay 0.1
dim temp as long:temp=_resize
redim shared pal(0) as _unsigned long
loadpalette"custodian-8",pal()
layers(0).ihandle=_newimage(320,320,32)
layers(1).ihandle=_newimage(320,320,32)
layers(2).ihandle=_newimage(320,320,32)
layers(3).ihandle=_newimage(320,320,32)
_dest layers(0).ihandle
addcommand"canvas ("+tst(_width)+","+tst(_height)+")"
for y=0 to _height-16 step 16
for x=0 to _width-16 step 16
if ((x+y)/16 and 1)=0 then
line (x,y)-(x+16,y+16),_rgb32(127),bf
else
line (x,y)-(x+16,y+16),_rgb32(192),bf
end if
next
next
_dest 0
state.tool=1
state.zoom=1
state.offsetx=70+20
state.offsety=20
state. brushsize=1
state.fcolor=closestcolor(_rgb32(0,0,0),pal())
state.bcolor=closestcolor(_rgb32(255,255,255),pal())
addcommand"fcolor ("+hex$(state.fcolor)+")"
addcommand"bcolor ("+hex$(state.bcolor)+")"
DIM SHARED fontHandles(1 TO 10) AS INTEGER
DIM SHARED activeFontIndex AS INTEGER: activeFontIndex = 1
DIM SHARED activeFontSize AS SINGLE: activeFontSize = 16
DIM SHARED textToolString AS STRING
fontHandles(1) = LoadBGIFont("./fonts/SIMP.CHR")
fontHandles(2) = LoadBGIFont("./fonts/BOLD.CHR")
fontHandles(3) = LoadBGIFont("./fonts/EURO.CHR")
fontHandles(4) = LoadBGIFont("./fonts/GOTH.CHR")
fontHandles(5) = LoadBGIFont("./fonts/LCOM.CHR")
fontHandles(6) = LoadBGIFont("./fonts/LITT.CHR")
fontHandles(7) = LoadBGIFont("./fonts/SANS.CHR")
fontHandles(8) = LoadBGIFont("./fonts/SCRI.CHR")
fontHandles(9) = LoadBGIFont("./fonts/TRIP.CHR")
fontHandles(10) = LoadBGIFont("./fonts/TSCR.CHR")
dim lastmx,lastmy
dim keyin as string
dim mouseworldy as long
dim mouseworldx as long
dim diffx as integer
dim diffy as integer
dim oldwidth as integer
dim oldheight as integer
oldwidth=_width
oldheight=_height
do
' Check for window resizing first
if checkresize(_source)=-1 then
diffx=_width-oldwidth
diffy=_height-oldheight
state.offsetx=state.offsetx+(diffx/2)
state.offsety=state.offsety+(diffy/2)
oldwidth=_width
oldheight=_height
end if
cls,backgroundcolor1
' Process all inputs and update offsets
while _mouseinput:mw=mw+_mousewheel:wend
mouseclicked=0
rmouseclicked=0
if mousedown=-1 and _mousebutton(1)=0 then mouseclicked=-1
if rmousedown=-1 and _mousebutton(2)=0 then rmouseclicked=-1
mousedown=_mousebutton(1)
rmousedown=_mousebutton(2)
' Panning
if _mousebutton(3) then
state.offsetx=state.offsetx+(_mousex-lastmx)
state.offsety=state.offsety+(_mousey-lastmy)
end if
lastmx=_mousex:lastmy=_mousey
' Zooming
if mw<>0 then
mouseworldx=(_mousex-state.offsetx)/state.zoom
mouseworldy=(_mousey-state.offsety)/state.zoom
if mw>0 then state.zoom=state.zoom+1 else state.zoom=state.zoom-1
if state.zoom<1 then state.zoom=1
if state.zoom>20 then state.zoom=20
state.offsetx=_mousex-(mouseworldx*state.zoom)
state.offsety=_mousey-(mouseworldy*state.zoom)
mw=0
end if
' Keyboarding
if not state.tool=16 and not state.isdrawing then
keyin=inkey$
select case keyin
case chr$(27)
menu
case "+"
state.brushsize=state.brushsize+1
addcommand"brushsize ("+tst(state.brushsize)+")"
case "-"
if state.brushsize>1 then
state.brushsize=state.brushsize-1
addcommand"brushsize ("+tst(state.brushsize)+")"
end if
case "h"
state.zoom=1
state.offsetx=(_width/2)-(_width(layers(0).ihandle)/2)
state.offsety=(_height/2)-(_height(layers(0).ihandle)/2)
case "f","F"
' --- ZOOM TO FIT ("f") OR OVERFLOW SHORTEST SIDE ("F") ---
' 1. Calculate and choose the fitting ratio based on the lowercase/uppercase state
if (((_width-(showtoolbox and 70)-(showcommands and 250)) \ _width(layers(0).ihandle))<((_height-(showcolorpicker and 20)) \ _height(layers(0).ihandle)) xor (keyin="F")) then
state.zoom=((_width-(showtoolbox and 70)-(showcommands and 250)) \ _width(layers(0).ihandle))
else
state.zoom=((_height-(showcolorpicker and 20)) \ _height(layers(0).ihandle))
end if
' 2. Clamp the zoom factor within safe limits (1 to 20)
if state.zoom<1 then state.zoom=1 else if state.zoom>20 then state.zoom=20
' 3. Center the canvas inside the viewport (overflowing sides will center-clip perfectly)
state.offsetx=(showtoolbox and 70)+(((_width-(showtoolbox and 70)-(showcommands and 250)) \ 2)-((_width(layers(0).ihandle)*state.zoom) \ 2))
state.offsety=(((_height-(showcolorpicker and 20)) \ 2)-((_height(layers(0).ihandle)*state.zoom) \ 2))
case "t"
showtoolbox=not showtoolbox
case "c"
showcolorpicker=not showcolorpicker
case "l"
showcommands=not showcommands
case chr$(26) ' Ctrl+Z
if ubound(commands)>3 then
' Drop the trailing empty slot and the last valid command
redim _preserve commands(ubound(commands)-2) as string
' Re-create the empty trailing slot required by addcommand
redim _preserve commands(ubound(commands)+1) as string
redraw
end if
end select
end if
canvas
if showtoolbox then toolbox
if showcolorpicker then colorpicker
if state.tool = 12 then drawTextToolPanel
if showcommands then commandlist
_limit 30
_display
loop
sub commandlist
dim i as long
dim listwidth as integer:listwidth=250
dim x as integer:x=_width-listwidth
' Draw background for the list
line (x,0)-(_width-1,_height-1),backgroundcolor1,bf
line (x,0)-(x,_height-1),backgroundcolor2
_printmode _keepbackground
dim y as integer
for i=ubound(commands)-1 to 0 step -1
y=(ubound(commands)-i)*16
if y<_height-20 then
select case link(x+5,y+5,left$(commands(i),31))
case -1 ' Left-Click triggers the inline text rename input box
commands(i)=textinput(x+5,y+5,248,23,commands(i))
case -2 ' Right-Click opens the context menu layout overlay
listcontextmenu i, _mousex, _mousey
exit sub
end select
end if
next i
if button(x,_height-25,60,23,"redraw") then redraw
end sub
sub listcontextmenu (index as long, mx as integer, my as integer)
if index < 0 or index >= ubound(commands) then exit sub
dim done as integer: done = 0
dim menuw as integer: menuw = 120
dim menuh as integer: menuh = 6 * 24 + 4 ' 6 items at 24px layout spacing
' Position correction so context panels don't clip outside screens
dim x as integer: x = mx
if x + menuw > _width then x = _width - menuw
dim y as integer: y = my
if y + menuh > _height then y = _height - menuh
do
' UI Backdrop reconstruction stack to prevent flickering frames
cls, backgroundcolor1
canvas
if showtoolbox then toolbox
if showcolorpicker then colorpicker
if state.tool = 12 then drawTextToolPanel
' Manual render block for command list data under the active frame
dim lx as integer: lx = _width - 250
line (lx, 0)-(_width - 1, _height - 1), backgroundcolor1, bf
line (lx, 0)-(lx, _height - 1), backgroundcolor2
_printmode _keepbackground
dim ly as integer, i as long
for i = ubound(commands) - 1 to 0 step -1
ly = (ubound(commands) - i) * 16
if ly < _height - 20 then
_printstring (lx + 5, ly + 5), left$(commands(i), 31)
end if
next i
if button(lx, _height - 25, 60, 23, "redraw") then redraw
' Render Context Container Box
line (x, y)-(x + menuw, y + menuh), backgroundcolor1, bf
line (x, y)-(x + menuw, y + menuh), highlightcolor, b
' Standard poll for incoming click state loops
while _mouseinput: wend
mouseclicked = 0: rmouseclicked = 0
if mousedown = -1 and _mousebutton(1) = 0 then mouseclicked = -1
if rmousedown = -1 and _mousebutton(2) = 0 then rmouseclicked = -1
mousedown = _mousebutton(1)
rmousedown = _mousebutton(2)
' Clicking completely outside the menu closes the context window
if mouseclicked or rmouseclicked then
if not (_mousex > x and _mousex < x + menuw and _mousey > y and _mousey < y + menuh) then
done = -1
end if
end if
dim bx as integer: bx = x + 2
dim by as integer: by = y + 2
dim bw as integer: bw = menuw - 4
dim bh as integer: bh = 22
' Item 1: Delete
if button(bx, by, bw, bh, "Delete") then
dim k as long
for k = index to ubound(commands) - 1
commands(k) = commands(k + 1)
next k
redim _preserve commands(ubound(commands) - 1) as string
redraw
done = -1
end if
by = by + 24
' Item 2: Move Up (moves toward end of array visually)
if button(bx, by, bw, bh, "Move Up") then
if index < ubound(commands) - 2 then
dim tempCmd as string
tempCmd = commands(index)
commands(index) = commands(index + 1)
commands(index + 1) = tempCmd
redraw
end if
done = -1
end if
by = by + 24
' Item 3: Move Down (moves toward array index 0)
if button(bx, by, bw, bh, "Move Down") then
if index > 0 then
tempCmd = commands(index)
commands(index) = commands(index - 1)
commands(index - 1) = tempCmd
redraw
end if
done = -1
end if
by = by + 24
' Item 4: Insert Above
if button(bx, by, bw, bh, "Insert Above") then
redim _preserve commands(ubound(commands) + 1) as string
for k = ubound(commands) - 1 to index + 2 step -1
commands(k) = commands(k - 1)
next k
commands(index + 1) = ""
redraw
done = -1
end if
by = by + 24
' Item 5: Insert Below
if button(bx, by, bw, bh, "Insert Below") then
redim _preserve commands(ubound(commands) + 1) as string
for k = ubound(commands) - 1 to index + 1 step -1
commands(k) = commands(k - 1)
next k
commands(index) = ""
redraw
done = -1
end if
by = by + 24
' Item 6: Copy to OS Clipboard
if button(bx, by, bw, bh, "Copy") then
_clipboard$ = commands(index)
done = -1
end if
_limit 30
_display
loop until done or _keydown(27)
' Flush trailing triggers so the next loop cycle doesn't draw accidental strokes
mouseclicked = 0
rmouseclicked = 0
end sub
sub drawTextToolPanel
dim panelWidth as integer: panelWidth = 160
dim x as integer: x = _width - panelWidth
static showFontList as integer ' Tracks the expanding drop-down list state
if showcommands then
x = _width - panelWidth - 250
else
x = _width - panelWidth
end if
' Draw the side panel background card block
line (x, 0)-(_width - 1, _height - 1), backgroundcolor1, bf
line (x, 0)-(x, _height - 1), backgroundcolor2
_printmode _keepbackground
_printstring (x + 10, 15), "TEXT TOOL OPTIONS"
line (x + 10, 30)-(x + 140, 30), backgroundcolor2
' --- Font Size Controls ---
_printstring (x + 10, 45), "Size: " + tst(int(activeFontSize))
if button(x + 85, 42, 24, 20, "-") then
if activeFontSize > 4 then activeFontSize = activeFontSize - 2
end if
if button(x + 115, 42, 24, 20, "+") then
if activeFontSize < 120 then activeFontSize = activeFontSize + 2
end if
' --- Expandable Font Selection Links ---
dim currentFontName as string
select case activeFontIndex
case 1: currentFontName = "SIMP.CHR"
case 2: currentFontName = "BOLD.CHR"
case 3: currentFontName = "EURO.CHR"
case 4: currentFontName = "GOTH.CHR"
case 5: currentFontName = "LCOM.CHR"
case 6: currentFontName = "LITT.CHR"
case 7: currentFontName = "SANS.CHR"
case 8: currentFontName = "SCRI.CHR"
case 9: currentFontName = "TRIP.CHR"
case 10: currentFontName = "TSCR.CHR"
end select
_printstring (x + 10, 80), "Font: "
if link(x + 55, 80, "[" + currentFontName + " ]") then
showFontList = not showFontList ' Toggle expansion list visibility
end if
' Render the drop-down links when expanded
if showFontList then
dim fontNames(1 to 10) as string
fontNames(1) = "Simplex": fontNames(2) = "Bold": fontNames(3) = "Euro"
fontNames(4) = "Gothic": fontNames(5) = "Complex": fontNames(6) = "Little"
fontNames(7) = "Sans": fontNames(8) = "Script": fontNames(9) = "Triplex"
fontNames(10) = "TScript"
dim ly as integer, idx as integer
for idx = 1 to 10
ly = 80 + (idx * 20)
' Highlight the currently active font selection with an asterisk
dim itemPrefix as string
if idx = activeFontIndex then itemPrefix = "* " else itemPrefix = " "
if link(x + 20, ly, itemPrefix + fontNames(idx)) then
activeFontIndex = idx
showFontList = 0 ' Auto-collapse list upon selection
end if
next idx
end if
' Guard the UI boundaries so clicks on this panel do not draw on the canvas below it
if _mousex >= x then
if _mousebutton(1) or _mousebutton(2) then mouseclicked = 0: rmouseclicked = 0
end if
end sub
sub redraw
redim numarr(0) as long
dim i as integer
dim j as integer
dim x as long
dim y as long
_dest layers(1).ihandle
_source layers(1).ihandle
cls,_rgb32(255)
_clearcolor _rgb32(255)
for i=lbound(commands) to ubound(commands)
getnums commands(i),numarr()
_dest layers(1).ihandle
_source layers(1).ihandle
select case lcase$(_trim$(left$(commands(i),instr(commands(i),"(")-1)))
case "canvas"
for j=0 to 3
if layers(j).ihandle<>0 then _freeimage layers(j).ihandle
layers(j).ihandle=_newimage(numarr(0),numarr(1),32)
next j
_dest layers(0).ihandle
for y=0 to _height-16 step 16
for x=0 to _width-16 step 16
if ((x+y)/16 and 1)=0 then
line (x,y)-(x+16,y+16),_rgb32(127),bf
else
line (x,y)-(x+16,y+16),_rgb32(192),bf
end if
next
next
case "fcolor"
state.fcolor=numarr(0)
case "bcolor"
state.bcolor=numarr(0)
case "brushsize"
state.brushsize=numarr(0)
case "pixel"
thickpixel numarr(0),numarr(1),numarr(2)
case "line"
thickline numarr(0),numarr(1),numarr(2),numarr(3),numarr(4)
case "box"
thickbox numarr(0),numarr(1),numarr(2),numarr(3),numarr(4)
case "fbox"
filledbox numarr(0),numarr(1),numarr(2),numarr(3),numarr(4)
case "circle"
thickcircle numarr(0),numarr(1),numarr(2),numarr(3)
case "fcircle"
filledcircle numarr(0),numarr(1),numarr(2),numarr(3)
case "polygon"
polygon numarr(),state.fcolor
case "fpolygon"
filledpolygon numarr(),state.fcolor
case "floodfill"
floodfill numarr(0),numarr(1),numarr(2)
case "boundaryfill"
boundaryfill numarr(0),numarr(1),numarr(2),numarr(3)
case "gradient"
ditheredgradient numarr(0),numarr(1),numarr(2),numarr(3),state.fcolor,state.bcolor
case "gradient"
ditheredgradient numarr(0),numarr(1),numarr(2),numarr(3),state.fcolor,state.bcolor
case "text"
' Extract the string parameter from the command structure manually
' e.g., text (X, Y, FontHandleIndex, FontSize, FColor, Your String Content)
dim txtStart as integer: txtStart = instr(commands(i), ",")
' Advance past the first 5 commas to isolate the text string
dim commaCount as integer: commaCount = 0
dim searchPos as integer: searchPos = 1
while commaCount < 5
searchPos = instr(searchPos, commands(i), ",")
if searchPos > 0 then
commaCount = commaCount + 1
searchPos = searchPos + 1
else
exit while
end if
wend
if commaCount = 5 then
dim textMsg as string
textMsg = mid$(commands(i), searchPos, instr(searchPos, commands(i), ")") - searchPos)
' Draw text to the drawing layer directly
DisplayBGIText fontHandles(numarr(2)), numarr(0), numarr(1), textMsg, numarr(3), numarr(4)
end if
case ""
' blank line do nothing
case else
'debug info
print"'"+lcase$(_trim$(left$(commands(i),instr(commands(i),"(")-1)))+"'"
end select
next i
_dest 0
_source 0
end sub
sub getnums (inputstr$,numarray() as long)
' 1. Extract inner content
spos=instr(inputstr$,"(")
epos=instr(inputstr$,")")
if spos=0 or epos<=spos then exit sub
content$=mid$(inputstr$,spos+1,epos-spos-1)
idx=-1 ' Start at -1 so the first increment hits 0
' 2. Parse segments
do
cpos=instr(content$,",")
if cpos>0 then
part$=ltrim$(rtrim$(left$(content$,cpos-1)))
content$=mid$(content$,cpos+1)
else
part$=ltrim$(rtrim$(content$))
content$=""
end if
if len(part$)>0 then
idx=idx+1
redim _preserve numarray(idx) as long
' Determine if part is Hex (contains A-F)
ishex=0
for i=1 to len(part$)
c$=ucase$(mid$(part$,i,1))
if c$>="A"and c$<="F"then
ishex=1
exit for
end if
next i
if ishex then
numarray(idx)=val("&H"+part$)
else
numarray(idx)=val(part$)
end if
end if
loop while len(content$)>0
end sub
sub toolbox
dim i,x,y
dim btnsize:btnsize=32
dim spacing:spacing=1
for i=0 to 19
x=(i mod 2)*(btnsize+spacing)
y=int(i/2)*(btnsize+spacing)
if imagebutton(x,y,btnsize,btnsize,icon(i)) then
state.tool=i+1
end if
next
'colorindicator
y=y+btnsize+16
line (16,y+16)-(64,y+64),state.bcolor,bf
line (16,y+16)-(64,y+64),highlightcolor,b
line (0,y)-(48,y+48),state.fcolor,bf
line (0,y)-(48,y+48),highlightcolor,b
if clickregion(0,y,48,48) then
palettemanager state.fcolor
elseif clickregion(16,y+16,48,48) then
palettemanager state.bcolor
end if
end sub
sub colorpicker
static img as long
if img=0 then img=_newimage(16,16,32)
for i=0 to ubound(pal)
_dest img
cls,pal(i)
line (0,0)-(_width-1,height-1),pal(i),bf
_dest 0
select case imagebutton(i*16,_height-17,16,16,img)
case -1
state.fcolor=pal(i)
addcommand"fcolor ("+hex$(pal(i))+")"
case -2
state.bcolor=pal(i)
addcommand"bcolor ("+hex$(pal(i))+")"
end select
next i
end sub
sub addcommand(cmd as string)
'this check is probably more clever than good is.
if commands(ubound(commands)+(ubound(commands)>0))<>cmd then
commands(ubound(commands))=cmd
redim _preserve commands(ubound(commands)+1) as string
end if
end sub
sub canvas
' 1. Define the Viewport (The "Window" on your screen)
dim viewx1 as integer
if showtoolbox then viewx1=70 else viewx1=0
dim viewy1 as integer:viewy1=0
dim viewx2 as integer
if showcommands then viewx2=_width-151 else viewx2=_width-1
dim viewy2 as integer
if showcolorpicker then viewy2=_height-20 else viewy2=_height-1
_dest 0
' 2. Render Layers (Let QB64 handle the viewport clipping natively)
dim i as integer
for i=0 to ubound(layers)
dim img as long:img=layers(i).ihandle
dim imgw as integer:imgw=_width(img)
dim imgh as integer:imgh=_height(img)
' Calculate the absolute unclipped destination positions
drawx1=state.offsetx
drawy1=state.offsety
drawx2=state.offsetx+(imgw*state.zoom)
drawy2=state.offsety+(imgh*state.zoom)
' Direct 1:1 mapping of the entire source asset
srcx1=0
srcy1=0
srcx2=imgw
srcy2=imgh
' Only draw if the asset is within the general view window range
if drawx2>viewx1 and drawx1<viewx2 and drawy2>viewy1 and drawy1<viewy2 then
_putimage (drawx1,drawy1)-(drawx2,drawy2),img,0,(srcx1,srcy1)-(srcx2,srcy2)
end if
next
_dest layers(2).ihandle:cls,0:_dest 0
' 2.5 Check if the mouse pointer is hitting the UI boundaries
dim mouseInUI as _byte
mouseInUI = 0
if showtoolbox and (_mousex>=0 and _mousex<=70) then mouseInUI = -1
if showcolorpicker and (_mousey>=_height-20) then mouseInUI = -1
if showcommands and (_mousex>=drawx2) then mouseInUI = -1
' 3. Calculate Canvas Coordinates (Center-aligned to the zoom block)
dim canx as long
dim cany as long
canx=int((_mousex-state.offsetx+(state.zoom \ 2))/state.zoom)
cany=int((_mousey-state.offsety+(state.zoom \ 2))/state.zoom)
static drawcol
if _mousebutton(1) then drawcol=state.fcolor
if _mousebutton(2) then drawcol=state.bcolor
' ONLY initiate drawing actions if the mouse is NOT in the UI
if mouseInUI = 0 then
if (mousedown or rmousedown) and state.isdrawing=0 then
state.startx=canx
state.starty=cany
state.isdrawing=-1
end if
end if
' Bypass tool execution for regular click-and-drag shapes,
' but ALWAYS allow text tool (12) to pass through so it draws the live overlay
if mouseInUI = 0 or state.tool = 12 then
select case state.tool
case 1
do.pencil canx,cany,drawcol
case 2
do.line state.startx,state.starty,canx,cany,drawcol
case 3
do.circle state.startx,state.starty,canx,cany,drawcol
case 4
do.fcircle state.startx,state.starty,canx,cany,drawcol
case 5
do.box state.startx,state.starty,canx,cany,drawcol
case 6
do.fbox state.startx,state.starty,canx,cany,drawcol
case 7
do.polygon canx,cany
case 8
do.fpolygon canx,cany
case 9
do.floodfill canx,cany,drawcol
case 10
do.eyedropper canx,cany
case 11
do.gradient state.startx,state.starty,canx,cany
case 12
do.text canx, cany, drawcol
end select
end if
end sub
sub do.pencil(x as long,y as long,col as long)
dim osource as long
dim odest as long
osource=_source
odest=_dest
_dest layers(1).ihandle
if _mousebutton(1) or _mousebutton(2) then
thickpixel x,y,col
addcommand"pixel ("+tst(x)+","+tst(y)+","+hex$(col)+")"
end if
state.isdrawing=0
_dest odest
end sub
sub do.line(sx as long,sy as long,ex as long,ey as long,col as long)
dim osource as long
dim odest as long
if state.isdrawing then
osource=_source
odest=_dest
if _keydown(100303) or _keydown(100304) then
dim dx as single:dx=ex-sx
dim dy as single:dy=ey-sy
if dx<>0 or dy<>0 then
dim linelen as single:linelen=sqr(dx*dx+dy*dy)
dim angle as single:angle=_atan2(dy,dx)
dim degrees as single:degrees=angle*(180.0/_pi)
dim snappeddegrees as single:snappeddegrees=int((degrees+22.5)/45.0)*45.0
dim snappedangle as single:snappedangle=snappeddegrees*(_pi/180.0)
ex=sx+_round(linelen*cos(snappedangle))
ey=sy+_round(linelen*sin(snappedangle))
end if
end if
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"line ("+tst(sx)+","+tst(sy)+","+tst(ex)+","+tst(ey)+","+hex$(col)+")"
state.isdrawing=0
else
_dest layers(2).ihandle
end if
thickline sx,sy,ex,ey,col
_source osource
_dest odest
end if
end sub
sub do.circle (sx as long,sy as long,ex as long,ey as long,col as long)
dim osource as long
dim odest as long
dim x as long,y as long,r as long
if state.isdrawing then
osource=_source
odest=_dest
if _keydown(100303) or _keydown(100304) then
' --- Shift Held: Locked Edge Diameter Mode ---
' Calculate the full distance from the start click to the mouse pointer
dim diameter as single
diameter=sqr((ex-sx) ^ 2+(ey-sy) ^ 2)
r=_round(diameter/2)
' The center is the exact midpoint between the click and the cursor
x=_round((sx+ex)/2)
y=_round((sy+ey)/2)
else
' --- No Shift: Default Center-Radius Mode ---
x=sx
y=sy
r=_round(sqr((ex-sx) ^ 2+(ey-sy) ^ 2))
end if
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"circle ("+tst(x)+","+tst(y)+","+tst(int(r))+","+hex$(col)+")"
state.isdrawing=0
else
_dest layers(2).ihandle
end if
thickcircle x,y,r,col
_source osource
_dest odest
end if
end sub
sub do.fcircle (sx as long,sy as long,ex as long,ey as long,col as long)
dim osource as long
dim odest as long
dim x as long,y as long,r as long
if state.isdrawing then
osource=_source
odest=_dest
if _keydown(100303) or _keydown(100304) then
' --- Shift Held: Locked Edge Diameter Mode ---
' Calculate the full distance from the start click to the mouse pointer
dim diameter as single
diameter=sqr((ex-sx) ^ 2+(ey-sy) ^ 2)
r=_round(diameter/2)
' The center is the exact midpoint between the click and the cursor
x=_round((sx+ex)/2)
y=_round((sy+ey)/2)
else
' --- No Shift: Default Center-Radius Mode ---
x=sx
y=sy
r=_round(sqr((ex-sx) ^ 2+(ey-sy) ^ 2))
end if
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"fcircle ("+tst(x)+","+tst(y)+","+tst(int(r))+","+hex$(col)+")"
state.isdrawing=0
else
_dest layers(2).ihandle
end if
filledcircle x,y,r,col
_source osource
_dest odest
end if
end sub
sub do.box (sx as long,sy as long,ex as long,ey as long,col as long)
dim osource as long
dim odest as long
if state.isdrawing then
osource=_source
odest=_dest
' --- Shift Held: Perfect Square Constraint ---
if _keydown(100303) or _keydown(100304) then
dim dx as long:dx=ex-sx
dim dy as long:dy=ey-sy
' Determine the longest side to use as the square dimensions
dim size as long
if abs(dx)>abs(dy) then size=abs(dx) else size=abs(dy)
' Shift coordinates relative to the direction of the drag
dim signx as long:if dx>=0 then signx=1 else signx=-1
dim signy as long:if dy>=0 then signy=1 else signy=-1
ex=sx+(size*signx)
ey=sy+(size*signy)
end if
' ---------------------------------------------
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"box ("+tst(sx)+","+tst(sy)+","+tst(ex)+","+tst(ey)+","+hex$(col)+")"
state.isdrawing=0
else
_dest layers(2).ihandle
end if
thickbox sx,sy,ex,ey,col
_source osource
_dest odest
end if
end sub
sub do.fbox (sx as long,sy as long,ex as long,ey as long,col as long)
dim osource as long
dim odest as long
if state.isdrawing then
osource=_source
odest=_dest
' --- Shift Held: Perfect Square Constraint ---
if _keydown(100303) or _keydown(100304) then
dim dx as long:dx=ex-sx
dim dy as long:dy=ey-sy
' Determine the longest side to use as the square dimensions
dim size as long
if abs(dx)>abs(dy) then size=abs(dx) else size=abs(dy)
' Shift coordinates relative to the direction of the drag
dim signx as long:if dx>=0 then signx=1 else signx=-1
dim signy as long:if dy>=0 then signy=1 else signy=-1
ex=sx+(size*signx)
ey=sy+(size*signy)
end if
' ---------------------------------------------
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"fbox ("+tst(sx)+","+tst(sy)+","+tst(ex)+","+tst(ey)+","+hex$(col)+")"
state.isdrawing=0
else
_dest layers(2).ihandle
end if
filledbox sx,sy,ex,ey,col
_source osource
_dest odest
end if
end sub
sub do.polygon(x as long,y as long)
' 1. Internalized State Memory
static polypoints(500) as long
static pointcount as integer
dim osource as long
dim odest as long
dim p as integer
dim i as integer
dim tmpstr as string
if state.isdrawing then
osource=_source
odest=_dest
' If left-clicked, add the coordinate to this routine's local array
if mouseclicked then
polypoints(pointcount*2)=x
polypoints(pointcount*2+1)=y
pointcount=pointcount+1
end if
' Finish shape on Right-Click
if rmouseclicked and pointcount>2 then
_dest layers(1).ihandle
redim finalp(pointcount*2-1) as long
for p=0 to (pointcount*2)-1:finalp(p)=polypoints(p):next
polygon finalp(),state.fcolor
tmpstr="polygon ("
for i=0 to ubound(finalp)-1
tmpstr=tmpstr+tst(finalp(i))+","
next i
tmpstr=tmpstr+tst(finalp(i))+")"
addcommand tmpstr
' Clean up local tool state
state.isdrawing=0
pointcount=0
else
' Live preview rendering loop
_dest layers(2).ihandle
if pointcount>0 then
for p=1 to pointcount-1
thickline polypoints((p-1)*2),polypoints((p-1)*2+1),polypoints(p*2),polypoints(p*2+1),state.fcolor
next p
thickline polypoints((pointcount-1)*2),polypoints((pointcount-1)*2+1),x,y,state.fcolor
end if
end if
_source osource
_dest odest
end if
end sub
sub do.fpolygon(x as long,y as long)
' 1. Internalized State Memory
static polypoints(500) as long
static pointcount as integer
dim osource as long
dim odest as long
dim p as integer
dim i as integer
dim tmpstr as string
if state.isdrawing then
osource=_source
odest=_dest
' If left-clicked, add the coordinate to this routine's local array
if mouseclicked then
polypoints(pointcount*2)=x
polypoints(pointcount*2+1)=y
pointcount=pointcount+1
end if
' Finish shape on Right-Click
if rmouseclicked and pointcount>2 then
_dest layers(1).ihandle
redim finalp(pointcount*2-1) as long
for p=0 to (pointcount*2)-1:finalp(p)=polypoints(p):next
filledpolygon finalp(),state.fcolor
tmpstr="fpolygon ("
for i=0 to ubound(finalp)-1
tmpstr=tmpstr+tst(finalp(i))+","
next i
tmpstr=tmpstr+tst(finalp(i))+")"
addcommand tmpstr
' Clean up local tool state
state.isdrawing=0
pointcount=0
else
' Live preview rendering loop
_dest layers(2).ihandle
if pointcount>0 then
for p=1 to pointcount-1
thickline polypoints((p-1)*2),polypoints((p-1)*2+1),polypoints(p*2),polypoints(p*2+1),state.fcolor
next p
thickline polypoints((pointcount-1)*2),polypoints((pointcount-1)*2+1),x,y,state.fcolor
end if
end if
_source osource
_dest odest
end if
end sub
sub do.floodfill(x as long,y as long,col as long)
dim osource as long
dim odest as long
osource=_source
odest=_dest
if mouseclicked or rmouseclicked then
_source layers(1).ihandle
_dest layers(1).ihandle
' Check if either Left Shift (100303) or Right Shift (100304) is held down
if _keydown(100303) or _keydown(100304) then
' Fill until it hits the background color as a border
boundaryfill x,y,col,state.bcolor
addcommand"boundaryfill ("+tst(x)+","+tst(y)+","+hex$(col)+","+hex$(state.bcolor)+")"
else
' Standard color-replace flood fill
floodfill x,y,col
addcommand"floodfill ("+tst(x)+","+tst(y)+","+hex$(col)+")"
end if
state.isdrawing=0
_source osource
_dest odest
end if
end sub
sub do.eyedropper(x as long,y as long)
dim osource as long
if _mousebutton(1) or _mousebutton(2) then
osource=_source
_source layers(1).ihandle ' Read from the main drawing layer
' Ensure the coordinates are within the canvas boundaries
if x>=0 and x<_width(layers(1).ihandle) and y>=0 and y<_height(layers(1).ihandle) then
' Left click sets foreground color, Right click sets background color
if _mousebutton(1) then
state.fcolor=point(x,y)
addcommand"fcolor ("+hex$(point(x,y))+")"
elseif _mousebutton(2) then
state.bcolor=point(x,y)
addcommand"bcolor ("+hex$(point(x,y))+")"
end if
end if
state.isdrawing=0
_source osource
end if
end sub
sub do.gradient(sx as long,sy as long,ex as long,ey as long)
dim osource as long
dim odest as long
if state.isdrawing then
osource=_source
odest=_dest
if _keydown(100303) or _keydown(100304) then
dim dx as single:dx=ex-sx
dim dy as single:dy=ey-sy
if dx<>0 or dy<>0 then
dim linelen as single:linelen=sqr(dx*dx+dy*dy)
dim angle as single:angle=_atan2(dy,dx)
dim degrees as single:degrees=angle*(180.0/_pi)
dim snappeddegrees as single:snappeddegrees=int((degrees+22.5)/45.0)*45.0
dim snappedangle as single:snappedangle=snappeddegrees*(_pi/180.0)
ex=sx+_round(linelen*cos(snappedangle))
ey=sy+_round(linelen*sin(snappedangle))
end if
end if
if mouseclicked or rmouseclicked then
_dest layers(1).ihandle
addcommand"gradient ("+tst(sx)+","+tst(sy)+","+tst(ex)+","+tst(ey)+")"
state.isdrawing=0
ditheredgradient sx,sy,ex,ey,state.fcolor,state.bcolor
else
_dest layers(2).ihandle
thickline sx,sy,ex,ey,state.fcolor
end if
_source osource
_dest odest
end if
end sub
sub do.text (x as long, y as long, col as long)
dim osource as long
dim odest as long
' 1. Mouse Click on Canvas initiates the Typing Focus Lock
if (mouseclicked) and state.isdrawing = 0 then
state.startx = x
state.starty = y
state.isdrawing = -1
textToolString = "" ' Clear typing input buffer
_keyclear ' Flush background buffer lines
end if
if state.isdrawing then
osource = _source
odest = _dest
' 2. Intercept keyboard streams directly inside the active lock state
dim k as long
do
k = _keyhit
if k = 0 then exit do
' Character keys range check
if k >= 32 and k <= 126 then
textToolString = textToolString + chr$(k)
elseif k = 8 and len(textToolString) > 0 then ' Backspace behavior
textToolString = left$(textToolString, len(textToolString) - 1)
elseif k = 13 then
_dest layers(1).ihandle
DisplayBGIText fontHandles(activeFontIndex), state.startx, state.starty, textToolString, activeFontSize, col
addcommand "text (" + tst(state.startx) + "," + tst(state.starty) + "," + tst(activeFontIndex) + "," + tst(int(activeFontSize)) + "," + hex$(col) + "," + textToolString + ")"
state.isdrawing = 0
_dest odest
_source osource
while inkey$<>"":wend
exit sub
elseif k = 27 then
state.isdrawing = 0
exit sub
end if
loop
' 3. Render dynamic typing string overlay to active live preview layer (layer 2)
_dest layers(2).ihandle
DisplayBGIText fontHandles(activeFontIndex), state.startx, state.starty, textToolString + "_", activeFontSize, col
_source osource
_dest odest
end if
end sub
function icon (index as long)
static init as integer
static icons() as long
if not init then
redim icons(19) as long ' Room for 20 icons
dim c as _unsigned long:c=highlightcolor ' Main icon color
' --- 1. Pencil Tool ---
icons(0)=_newimage(32,32,32):_dest icons(0)
line (6,26)-(22,10),c
line (22,10)-(26,6),c
line (26,6)-(26,10),c
line (26,10)-(10,26),c
line (10,26)-(6,26),c
pset (7,25),c ' Tip point
_dest 0
' --- 2. Line Tool ---
icons(1)=_newimage(32,32,32):_dest icons(1)
line (5,27)-(27,5),c
_dest 0
' --- 3. Hollow Circle ---
icons(2)=_newimage(32,32,32):_dest icons(2)
circle (16,16),11,c
_dest 0
' --- 4. Filled Circle ---
icons(3)=_newimage(32,32,32):_dest icons(3)
filledcircle 16,16,11,c
_dest 0
' --- 5. Hollow Box ---
icons(4)=_newimage(32,32,32):_dest icons(4)
line (6,6)-(26,26),c,b
_dest 0
' --- 6. Filled Box ---
icons(5)=_newimage(32,32,32):_dest icons(5)
line (6,6)-(26,26),c,bf
_dest 0
' --- 7. Hollow Polygon (Triangle Blueprint) ---
icons(6)=_newimage(32,32,32):_dest icons(6)
line (16,5)-(6,25),c
line (6,25)-(26,25),c
line (26,25)-(16,5),c
_dest 0
' --- 8. Filled Polygon ---
icons(7)=_newimage(32,32,32):_dest icons(7)
dim py as integer
for py=5 to 25
dim hw as integer:hw=(py-5)*10/20
line (16-hw,py)-(16+hw,py),c
next py
_dest 0
' --- 9. Floodfill (Paint Bucket) ---
icons(8)=_newimage(32,32,32):_dest icons(8)
line (8,14)-(20,26),c ' Main body tilt left
line (20,26)-(26,20),c
line (26,20)-(14,8),c
line (14,8)-(8,14),c
circle (14,11),6,c,_pi(1.5),_pi(1)
pset (6,28),c ' Spilling drip point
_dest 0
' --- 10. Eyedropper ---
icons(9)=_newimage(32,32,32):_dest icons(9)
line (6,26)-(10,22),c ' Pipette tip
line (10,22)-(22,10),c ' Shaft side A
line (6,26)-(18,14),c ' Bottom point accent
line (14,18)-(26,6),c ' Shaft side B
line (22,10)-(26,6),c ' Squeeze bulb cap
_dest 0
icons(10)=_newimage(32,32,32):_dest icons(10)
ditheredgradient 6, 6, 26, 26, highlightcolor, backgroundcolor1
icons(11) = _NEWIMAGE(32, 32, 32): _DEST icons(11)
line (8, 8)-(24, 8), c
line (16, 8)-(16, 24), c
line (12, 24)-(20, 24), c
_DEST 0
' Fill remaining fallback slots (10-19) with clean blank images
dim j as integer
for j=11 to 19
if icons(j)=0 then icons(j)=_newimage(32,32,32)
next
init=-1
end if
if index>=0 and index<=19 then
icon=icons(index)
else
icon=icons(9)
end if
end function
'$include: 'include/ui.bm'
'$include: 'include/imgout.bm'
'$include: 'include/palette.bm'
'$include: 'include/tools.bm'
'$include: 'include/bgifnt.bm'
''$include: 'include/effects.bm'
function adduiicon(imagehandle as long)
dim unknown as long
adduiicon=__internaluiicon(unknown,imagehandle,1)
end function
function adduiiconfromfile(filename as string)
dim unknown as long
adduiiconfromfile=__internaluiicon(unknown,_loadimage(filename),1)
end function
function uiicon(index)
dim unknown as long
uiicon=internaluiicon(index,unknown,2)
end function
function __internaluiicon&(index as long,imagehandle as long,mode as integer)
static init as integer
static icons() as long
if not init or mode=3 then
if mode<3 then
redim icons(3) as long
else
_freeimage icons(0)
_freeimage icons(1)
_freeimage icons(2)
_freeimage icons(3)
end if
icons(0)=_newimage(23,23,32) 'Up arrow'
_dest icons(0)
color textcolor
icons(1)=_newimage(23,23,32) 'Down arrow'
_dest icons(1)
color textcolor
icons(2)=_newimage(23,23,32) 'Left arrow'
_dest icons(2)
color textcolor
icons(3)=_newimage(23,23,32) 'Right arrow'
_dest icons(3)
color textcolor
_dest 0
init=-1
end if
select case mode
case 1
redim _preserve icons(ubound(icons)+1)
icons(ubound(icons))=imagehandle
__internaluiicon=ubound(icons)
case 2
__internaluiicon=icons(index)
end select
end function
function checkresize (currentscreen as _unsigned long) 'pulled straight out of the wiki'
dim tempscreen as _unsigned long
checkresize=0
if _resize then
tempscreen=_copyimage(currentscreen,32)
screen tempscreen
_freeimage currentscreen
currentscreen=_newimage(_resizewidth,_resizeheight,32)
screen currentscreen
_putimage (0,0),tempscreen,currentscreen
_display
_freeimage tempscreen
checkresize=-1
end if
end function
sub menu()
dim logo as long
dim filename as string
' A logo is needed'
'logo=_loadimage("logo.png")
line (0,0)-(_width-1,_height-1),_rgb32(0,192),bf
'_putimage ((_width(0)-_width(logo))/2,10),logo
dim i as integer
dim fh as integer
do until done
k$=inkey$
while _mouseinput:wend
mouseclicked=mbd and not _mousebutton(1)
mbd=_mousebutton(1)
if link(10,10,"open") then
filename=textinput(10,10,100,23,"")
if filename=""then exit sub
fh=freefile
redim commands(2000) as string
open filename for input as fh
do until eof(fh)
line input #fh,commands(i)
i=i+1
if i>ubound(commands) then redim _preserve commands(ubound(commands)*2) as string
loop
close fh
redim _preserve commands(i-1) as string
redraw
done=-1
end if
if link(10,34,"save") then
filename=textinput(1,34,100,23,"")
if filename=""then exit sub
fh=freefile
open filename for output as fh
for i=0 to ubound(commands)
print #fh,commands(i)
next i
close fh
done=-1
end if
if link(10,56,"export") then
filename=textinput(10,56,100,23,"")
if filename=""then exit sub
select case lcase$(right$(filename,4))
case ".png"
save32bitpng layers(1).ihandle,filename
case ".bmp"
save24bitbmp layers(1).ihandle,filename
case ".ppm"
savebinaryppm layers(1).ihandle,filename
case else
save32bitpng layers(1).ihandle,filename
end select
done=-1
end if
if link(10,78,"refenece img") then
filename=textinput(10,78,100,23,"")
if filename=""then exit sub
if not _fileexists(filename) then exit sub
if layers(3).ihandle<>0 then _freeimage layers(3).ihandle
layers(3).ihandle=_loadimage(filename)
_setalpha 20,layers(3).ihandle
done=-1
end if
if link(10,100,"exit") then system
if k$=chr$(27) then done=-1
_limit 30
_display
loop
end sub
sub palettemanager(col as _unsigned long)
line (0,0)-(_width-1,_height-1),col,bf
'TODO: build palette mamager ui
end sub
function closestcolor~& (colour as _unsigned long,carr() as _unsigned long)
dim r as integer
dim g as integer
dim b as integer
dim ar as integer
dim ag as integer
dim ab as integer
dim i as integer
dim nearest as integer
dim shortestdistance as integer
dim distance as integer
r=_red (colour)
g=_green(colour)
b=_blue (colour)
shortestdistance=443
for i=0 to ubound(carr)
ar=_red (carr(i))
ag=_green(carr(i))
ab=_blue (carr(i))
distance=sqr((r-ar) ^ 2+(g-ag) ^ 2+(b-ab) ^ 2)
if distance<=shortestdistance then shortestdistance=distance:nearest=i
next i
closestcolor=carr(nearest)
end function
' trimmed str$
function tst$(numb)
tst=_trim$(str$(numb))
end function