389 lines
13 KiB
QBasic
389 lines
13 KiB
QBasic
$CONSOLE
|
|
_SCREENHIDE
|
|
IF COMMAND$ = "" THEN _ECHO "please specify image file to process": SYSTEM 1
|
|
_SCREENSHOW
|
|
|
|
TYPE Point2D
|
|
X AS LONG
|
|
Y AS LONG
|
|
END TYPE
|
|
|
|
TYPE statetype
|
|
change AS _BYTE
|
|
levels AS _UNSIGNED _BYTE
|
|
threshold AS _UNSIGNED _BYTE
|
|
mode AS _BYTE
|
|
handle AS LONG
|
|
orgimg AS LONG
|
|
overlay AS LONG
|
|
END TYPE
|
|
|
|
DIM SHARED state AS statetype
|
|
DIM file AS STRING
|
|
DIM SHARED done AS _BYTE
|
|
DIM SHARED mouseclicked AS _BYTE
|
|
DIM SHARED mbd AS _BYTE
|
|
|
|
' Global polygon trace registers
|
|
REDIM SHARED Contour(4999) AS Point2D
|
|
DIM SHARED TotalPoints AS LONG
|
|
|
|
IF _FILEEXISTS(COMMAND$) THEN file = COMMAND$
|
|
state.orgimg = _LOADIMAGE(file, 32)
|
|
state.handle = _COPYIMAGE(state.orgimg)
|
|
state.overlay = _NEWIMAGE(_WIDTH(state.orgimg), _HEIGHT(state.orgimg), 32)
|
|
state.levels = 10
|
|
state.threshold=127
|
|
updateimage
|
|
|
|
SCREEN _NEWIMAGE(_WIDTH(state.orgimg) + 200, _HEIGHT(state.orgimg) + 200, 32)
|
|
|
|
DO UNTIL done
|
|
CLS
|
|
WHILE _MOUSEINPUT: WEND
|
|
mouseclicked = NOT _MOUSEBUTTON(1) AND mbd
|
|
mbd = _MOUSEBUTTON(1)
|
|
|
|
IF state.change THEN
|
|
updateimage
|
|
state.change = 0
|
|
END IF
|
|
|
|
' Render quantized base image
|
|
_PUTIMAGE (0, 0), state.handle
|
|
|
|
' Render the vector transparent overlay shapes on top
|
|
_PUTIMAGE (0, 0), state.overlay
|
|
|
|
' Process interactive trace when clicking inside image viewport
|
|
IF mouseclicked THEN
|
|
DIM clickX AS LONG, clickY AS LONG
|
|
clickX = _MOUSEX
|
|
clickY = _MOUSEY
|
|
|
|
' Verify click coordinates are safely within the source image canvas bounding box
|
|
IF clickX >= 0 AND clickX < _WIDTH(state.orgimg) AND clickY >= 0 AND clickY < _HEIGHT(state.orgimg) THEN
|
|
DIM TargetColor AS _UNSIGNED LONG
|
|
|
|
' Fetch look-up color index from the generated active handle
|
|
_SOURCE state.handle
|
|
TargetColor = POINT(clickX, clickY)
|
|
_SOURCE 0 ' Restore background source monitor pointer
|
|
|
|
' SAFEGUARD 1: Do not attempt to parse pure black background/borders
|
|
IF TargetColor <> _RGB32(0, 0, 0) AND TargetColor <> _RGBA32(0, 0, 0, 0) THEN
|
|
TotalPoints = 0
|
|
TraceContour TargetColor, clickX, clickY
|
|
|
|
IF TotalPoints >= 3 AND TotalPoints < (_WIDTH(state.orgimg) * _HEIGHT(state.orgimg)) THEN
|
|
REDIM _PRESERVE Contour(TotalPoints - 1) AS Point2D
|
|
SimplifyPolygon
|
|
|
|
IF TotalPoints >= 3 THEN
|
|
' Build flat 1D coordinate matrix map array (X1, Y1, X2, Y2...)
|
|
DIM polyPoints(0 TO (TotalPoints * 2) - 1) AS LONG
|
|
DIM i AS LONG
|
|
DIM clipStr AS STRING
|
|
|
|
clipStr = "fpolygon("
|
|
FOR i = 0 TO TotalPoints - 1
|
|
polyPoints(i * 2) = Contour(i).X
|
|
polyPoints(i * 2 + 1) = Contour(i).Y
|
|
|
|
clipStr = clipStr + LTRIM$(STR$(Contour(i).X)) + "," + LTRIM$(STR$(Contour(i).Y))
|
|
IF i < TotalPoints - 1 THEN clipStr = clipStr + ","
|
|
NEXT i
|
|
clipStr = clipStr + ")"
|
|
|
|
' Send string array payload safely to system clipboard hardware
|
|
_CLIPBOARD$ = clipStr
|
|
|
|
' Render transparent vector overlay on overlay handler sheet
|
|
DIM oldDest AS LONG
|
|
oldDest = _DEST
|
|
_DEST state.overlay
|
|
|
|
' Render with a semi-transparent green color (Alpha = 100)
|
|
filledpolygon polyPoints(), _RGBA32(0, 255, 0, 100)
|
|
|
|
_DEST oldDest
|
|
END IF
|
|
END IF
|
|
END IF
|
|
END IF
|
|
mouseclicked = 0
|
|
END IF
|
|
|
|
ol = state.levels
|
|
ot = state.threshold
|
|
interface
|
|
IF ol <> state.levels THEN state.change = -1
|
|
IF ot <> state.threshold THEN state.change = -1
|
|
|
|
_LIMIT 30
|
|
_DISPLAY
|
|
LOOP
|
|
SYSTEM
|
|
'$include: '../include/ui.bm'
|
|
|
|
SUB interface
|
|
IF button(10, _HEIGHT - 34, 60, 23, "exit") THEN done = -1
|
|
COLOR highlightcolor
|
|
_PRINTSTRING (10, _height(state.handle)+10), "levels"
|
|
state.levels = slider(10, _height(state.handle)+28, 100, state.levels * 10) / 10
|
|
COLOR highlightcolor
|
|
_PRINTSTRING (118, _height(state.handle)+28), STR$(state.levels)
|
|
_PRINTSTRING (10, _height(state.handle)+40), "threshold"
|
|
state.threshold = slider(10, _height(state.handle)+58 , 100, state.threshold / 255 * 100) / 100 * 255
|
|
COLOR highlightcolor
|
|
_PRINTSTRING (118, _height(state.handle)+58), STR$(state.threshold)
|
|
END SUB
|
|
|
|
SUB updateimage
|
|
DIM odest AS LONG
|
|
DIM osource AS LONG
|
|
odest = _DEST
|
|
osource = _SOURCE
|
|
_SOURCE state.orgimg
|
|
_DEST state.handle
|
|
|
|
DIM x AS LONG, y AS LONG, l AS INTEGER
|
|
DIM numSteps AS INTEGER
|
|
|
|
IF state.levels < 2 THEN state.levels = 2
|
|
numSteps = state.levels - 1
|
|
|
|
FOR y = 0 TO _HEIGHT - 1
|
|
FOR x = 0 TO _WIDTH - 1
|
|
l = luma(POINT(x, y))
|
|
|
|
DIM adjustedL AS SINGLE
|
|
IF state.threshold = 0 OR state.threshold = 255 THEN
|
|
adjustedL = l
|
|
ELSEIF l <= state.threshold THEN
|
|
adjustedL = (l / state.threshold) * 127.5
|
|
ELSE
|
|
adjustedL = 127.5 + ((l - state.threshold) / (255 - state.threshold)) * 127.5
|
|
END IF
|
|
|
|
DIM currentStep AS INTEGER
|
|
currentStep = INT((adjustedL / 256) * state.levels)
|
|
|
|
IF currentStep < 0 THEN currentStep = 0
|
|
IF currentStep > numSteps THEN currentStep = numSteps
|
|
|
|
l = INT(currentStep * (255 / numSteps))
|
|
|
|
PSET (x, y), _RGB32(l)
|
|
NEXT x
|
|
NEXT y
|
|
|
|
_SOURCE osource
|
|
_DEST odest
|
|
END SUB
|
|
|
|
FUNCTION luma (col AS _UNSIGNED LONG)
|
|
luma = 0.21 * _RED(col) + 0.72 * _GREEN(col) + 0.07 * _BLUE(col)
|
|
END FUNCTION
|
|
|
|
' =========================================================================
|
|
' SUBROUTINE: Solid Filled Scanning Polygon Vector Renderer
|
|
' =========================================================================
|
|
SUB filledpolygon (points() AS LONG, 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
|
|
DIM numpoints AS INTEGER
|
|
numpoints = (UBOUND(points) + 1) \ 2
|
|
|
|
DIM intersections(4999) AS SINGLE ' SAFEGUARD 2: Larger intersection limit
|
|
DIM numintersections AS INTEGER
|
|
DIM y AS LONG
|
|
|
|
FOR y = 0 TO _HEIGHT - 1
|
|
numintersections = 0
|
|
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)
|
|
|
|
IF ((y1 > y AND y2 <= y) OR (y2 > y AND y1 <= y)) THEN
|
|
IF y2 - y1 <> 0 THEN
|
|
intersectx = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
|
|
' SAFEGUARD 3: Array bounds protection
|
|
IF numintersections <= UBOUND(intersections) THEN
|
|
intersections(numintersections) = intersectx
|
|
numintersections = numintersections + 1
|
|
END IF
|
|
END IF
|
|
END IF
|
|
NEXT i
|
|
|
|
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
|
|
|
|
FOR i = 0 TO numintersections - 1 STEP 2
|
|
IF i + 1 < numintersections THEN
|
|
LINE (intersections(i), y)-(intersections(i + 1), y), col
|
|
END IF
|
|
NEXT i
|
|
NEXT y
|
|
END SUB
|
|
|
|
' =========================================================================
|
|
' SUBROUTINE: Moore-Neighbor Contour Tracer
|
|
' =========================================================================
|
|
SUB TraceContour (TargColor AS _UNSIGNED LONG, MX AS LONG, MY AS LONG)
|
|
DIM StartX AS LONG, StartY AS LONG
|
|
DIM FoundStart AS _BYTE
|
|
|
|
DIM oldSource AS LONG
|
|
oldSource = _SOURCE
|
|
_SOURCE state.handle
|
|
|
|
DIM y&
|
|
FOR y& = MY TO 0 STEP -1
|
|
IF POINT(MX, y&) <> TargColor THEN
|
|
StartX = MX
|
|
StartY = y& + 1
|
|
FoundStart = -1
|
|
EXIT FOR
|
|
END IF
|
|
NEXT y&
|
|
|
|
IF NOT FoundStart THEN
|
|
StartX = MX: StartY = MY
|
|
END IF
|
|
|
|
DIM dx(0 TO 7) AS INTEGER
|
|
DIM dy(0 TO 7) AS INTEGER
|
|
dx(0) = -1: dx(1) = 0: dx(2) = 1: dx(3) = 1: dx(4) = 1: dx(5) = 0: dx(6) = -1: dx(7) = -1
|
|
dy(0) = -1: dy(1) = -1: dy(2) = -1: dy(3) = 0: dy(4) = 1: dy(5) = 1: dy(6) = 1: dy(7) = 0
|
|
|
|
DIM CurrentX AS LONG, CurrentY AS LONG
|
|
DIM BacktrackDir AS INTEGER, CheckDir AS INTEGER
|
|
DIM StartBacktrackDir AS INTEGER
|
|
|
|
CurrentX = StartX
|
|
CurrentY = StartY
|
|
TotalPoints = 0
|
|
|
|
BacktrackDir = 1
|
|
StartBacktrackDir = 1
|
|
|
|
DIM FirstStep AS _BYTE
|
|
FirstStep = -1
|
|
|
|
' Calculate absolute ceiling area parameter to handle escape routes
|
|
DIM maxAllowedPoints AS LONG
|
|
maxAllowedPoints = _WIDTH(state.handle) * _HEIGHT(state.handle)
|
|
|
|
DO
|
|
Contour(TotalPoints).X = CurrentX
|
|
Contour(TotalPoints).Y = CurrentY
|
|
TotalPoints = TotalPoints + 1
|
|
|
|
' SAFEGUARD 4: Break if loop gets stuck spinning in empty coordinates
|
|
IF TotalPoints >= maxAllowedPoints THEN
|
|
TotalPoints = 0
|
|
EXIT DO
|
|
END IF
|
|
|
|
IF TotalPoints >= UBOUND(Contour) THEN
|
|
REDIM _PRESERVE Contour(UBOUND(Contour) + 1000) AS Point2D
|
|
END IF
|
|
|
|
CheckDir = (BacktrackDir + 1) MOD 8
|
|
DIM Steps AS INTEGER
|
|
Steps = 0
|
|
|
|
DO WHILE Steps < 8
|
|
DIM NX AS LONG, NY AS LONG
|
|
NX = CurrentX + dx(CheckDir)
|
|
NY = CurrentY + dy(CheckDir)
|
|
|
|
IF NX >= 0 AND NX < _WIDTH(state.handle) AND NY >= 0 AND NY < _HEIGHT(state.handle) THEN
|
|
IF POINT(NX, NY) = TargColor THEN
|
|
BacktrackDir = (CheckDir + 4) MOD 8
|
|
CurrentX = NX
|
|
CurrentY = NY
|
|
|
|
IF FirstStep THEN
|
|
StartBacktrackDir = BacktrackDir
|
|
FirstStep = 0
|
|
END IF
|
|
EXIT DO
|
|
END IF
|
|
END IF
|
|
|
|
CheckDir = (CheckDir + 1) MOD 8
|
|
Steps = Steps + 1
|
|
LOOP
|
|
|
|
IF Steps = 8 THEN EXIT DO
|
|
|
|
LOOP UNTIL (CurrentX = StartX AND CurrentY = StartY AND BacktrackDir = StartBacktrackDir)
|
|
_SOURCE oldSource
|
|
END SUB
|
|
|
|
' =========================================================================
|
|
' SUBROUTINE: SimplifyPolygon
|
|
' =========================================================================
|
|
SUB SimplifyPolygon
|
|
DIM numPoints AS LONG
|
|
numPoints = TotalPoints
|
|
|
|
IF numPoints > 2 THEN
|
|
IF Contour(numPoints - 1).X = Contour(0).X AND Contour(numPoints - 1).Y = Contour(0).Y THEN
|
|
numPoints = numPoints - 1
|
|
END IF
|
|
END IF
|
|
|
|
IF numPoints < 3 THEN EXIT SUB
|
|
|
|
DIM temp(0 TO numPoints - 1) AS Point2D
|
|
DIM keepCount AS LONG
|
|
|
|
temp(0) = Contour(0)
|
|
keepCount = 1
|
|
|
|
DIM i AS LONG
|
|
FOR i = 1 TO numPoints - 1
|
|
DIM prevIdx AS LONG: prevIdx = i - 1
|
|
DIM nextIdx AS LONG: nextIdx = i + 1
|
|
IF nextIdx > numPoints - 1 THEN nextIdx = 0
|
|
|
|
DIM dx1 AS SINGLE: dx1 = Contour(i).X - Contour(prevIdx).X
|
|
DIM dy1 AS SINGLE: dy1 = Contour(i).Y - Contour(prevIdx).Y
|
|
DIM dx2 AS SINGLE: dx2 = Contour(nextIdx).X - Contour(i).X
|
|
DIM dy2 AS SINGLE: dy2 = Contour(nextIdx).Y - Contour(i).Y
|
|
|
|
DIM crossProduct AS SINGLE
|
|
crossProduct = (dx1 * dy2) - (dy1 * dx2)
|
|
|
|
IF ABS(crossProduct) > 0.05 THEN
|
|
temp(keepCount) = Contour(i)
|
|
keepCount = keepCount + 1
|
|
END IF
|
|
NEXT i
|
|
|
|
IF keepCount >= 3 THEN
|
|
REDIM Contour(keepCount - 1) AS Point2D
|
|
FOR i = 0 TO keepCount - 1
|
|
Contour(i) = temp(i)
|
|
NEXT i
|
|
TotalPoints = keepCount
|
|
ELSE
|
|
REDIM Contour(numPoints - 1) AS Point2D
|
|
FOR i = 0 TO numPoints - 1
|
|
Contour(i) = temp(i)
|
|
NEXT i
|
|
TotalPoints = numPoints
|
|
END IF
|
|
END SUB
|