StringArtGenerator build 2025-04-23

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
UEZ
Posts: 1078
Joined: May 05, 2017 19:59
Location: Germany

StringArtGenerator build 2025-04-23

Post by UEZ »

Original code https://github.com/halfmonty/StringArtGenerator.

FB code:

Code: Select all

'Original code from https://github.com/halfmonty/StringArtGenerator
'Converted to FB in cooperation with ChatGPT by UEZ :-)
'Build 2025-04-23

#include "file.bi"
#include "crt/math.bi"
#include "fbgfx.bi"
Using FB


'parameters
Const bSEPIA = False 'only available when bCOLOR = False
Const bCOLOR = True 'if false than greyscale
Const bINVERS = False
Const bAA = False 'anti-aliased lines
Const bBORDER = False 'draw border around completed result
Const bAUTOSEGMENTS = True
Dim Shared As UShort SEGMENTS = 30 'line segments from pin to pin (only color mode)
Const BORDERCLR = &hA0101010
Const BORDERSIZE = 4
Const PINS = 360 'the higher the value the higher the memory consumption!
Const ALPHA_CHANNEL = &h14
Const LINE_WEIGHT = 9  'the lower the value the more lines will be drawn
Const TUNINGFACTOR = 36 'less means less details
Const CONTRASTFACTOR = 1.666666 'for color adjustment
Const MINLENGTH = 30
Const BGCOLOR = &hFFFFFFFF

Const bSAVERESULT = False
Var FILENAME = Str(ExePath & "\StringArt.bmp")

Union _Color '...'
	As ULong argb
	Type
		As UByte b, g, r, a
	End Type
End Union

#define _Alpha(iCol)					((iCol And &hFF000000) Shr 24)
#define _Red(iCol)						((iCol And &h00FF0000) Shr 16)
#define _Green(iCol)					((iCol And &h0000FF00) Shr 8)
#define _Blue(iCol)						((iCol And &h000000FF))

Function ColBlend(col1 As ULong, col2 As ULong, blend As Single) As ULong '...'
	Dim As Single bl = 1 - blend
	Return RGBA(_Red(col1) * blend + _Red(col2) * bl, _Green(col1) * blend + _Green(col2) * bl, _Blue(col1) * blend + _Blue(col2) * bl, _Alpha(col1) * blend + _Alpha(col2) * bl)
End Function

#define fpart(x)						(Frac(x))
#define rfpart(x)						(1 - Frac(x))
'https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
Sub DrawLineAAWu(x0 As Long, y0 As Long, x1 As Long, y1 As Long, _col As ULong, pImage As Any Ptr = 0) '...'
	Dim As Boolean steep = Abs(y1 - y0) > Abs(x1 - x0)
	If steep Then
		Swap x0, y0
		Swap x1, y1
	End If
	If x0 > x1 Then
		Swap x0, x1
		Swap y0, y1
	End If
	Dim As Long dx_, dy, xend, yend, xgap, xpxl1, ypxl1, xpxl2, ypxl2
	Dim As Single  gradient, intery, f
	Dim As ULong _rgb = _col And &h00FFFFFF
	Dim As UByte a = _Alpha(_col)
	
	dx_ = x1 - x0
	dy = y1 - y0
	gradient = dy / dx_
	If dx_ = 0 Then gradient = 1
	
	'handle first endpoint
	xend = round(x0)
	yend = y0 + gradient * (xend - x0)
	xgap = rfpart(x0)
	xpxl1 = xend
	ypxl1 = floor(yend)
	If steep Then
		f = rfpart(yend) * xgap
		PSet pImage, (ypxl1, xpxl1), (a * f) Shl 24 Or _rgb
		f = fpart(yend) * xgap
		PSet pImage, (ypxl1 + 1, xpxl1), (a * f) Shl 24 Or _rgb
	Else
		f = rfpart(yend) * xgap
		PSet pImage, (xpxl1, ypxl1), (a * f) Shl 24 Or _rgb
		f = fpart(yend) * xgap
		PSet pImage, (xpxl1, ypxl1 + 1), (a * f) Shl 24 Or _rgb
	End If
	intery = yend + gradient
	
	'handle second endpoint
	xend = round(x1)
	yend = y1 + gradient * (xend - x1)
	xgap = rfpart(x1)
	xpxl2 = xend
	ypxl2 = floor(yend)
	If steep Then
		f = rfpart(yend) * xgap
		PSet pImage, (ypxl2, xpxl2), (a * f) Shl 24 Or _rgb
		f = fpart(yend) * xgap
		PSet pImage, (ypxl2 + 1, xpxl2), (a * f) Shl 24 Or _rgb
	Else
		f = rfpart(yend) * xgap
		PSet pImage,(xpxl2, ypxl2), (a * f) Shl 24 Or _rgb
		f = fpart(yend) * xgap
		PSet pImage, (xpxl2, ypxl2 + 1), (a * f) Shl 24 Or _rgb
	End If
	
	'main line
	If steep Then
		For x As Short = xpxl1 + 1 To xpxl2 - 1
			f = rfpart(intery)
			PSet pImage, (floor(intery), x), (a * f) Shl 24 Or _rgb
			f = fpart(intery)
			PSet pImage, (floor(intery) + 1, x), (a * f) Shl 24 Or _rgb
			intery += gradient
		Next
	Else
		For x As Short = xpxl1 + 1 To xpxl2 - 1
			f = rfpart(intery)
			PSet pImage, (x, floor(intery)), (a * f) Shl 24 Or _rgb
			f = fpart(intery)
			PSet pImage, (x, floor(intery) + 1), (a * f) Shl 24 Or _rgb
			intery += gradient
		Next
	End If
End Sub

Sub CenterFBWin(iW As Long, iH As Long, iTBw As Long = 0, iTBh As Long = 0) 'centers the FB-GUI on the primary screen '...'
	Dim As Long iScreenWidth, iScreenHeight
	ScreenControl GET_DESKTOP_SIZE, iScreenWidth, iScreenHeight
	ScreenControl SET_WINDOW_POS, (iScreenWidth - iW) \ 2 - iTBw, (iScreenHeight - iH) \ 2 - iTBh
End Sub

Dim Shared As Integer IMG_SIZE, IMG_SIZE_SQ, MIN_DISTANCE, MAX_LINES, iDrawn
Dim Shared As ULong colorImage()

Const PI = Acos(-1)

Type Coord
	x As Integer
	y As Integer 
End Type

Dim Shared pin_coords(PINS - 1) As Coord
Dim Shared As UByte sourceImage()
Dim Shared As Integer errorImage()
Dim Shared As Integer line_cache_x()
Dim Shared As Integer line_cache_y()
Dim Shared As Integer line_cache_len(PINS * PINS - 1)

Type tBitmap_Header Field = 1 '54 bytes '...'
	As UShort bfType	'for windows bitmap it must be 19778 (&h4D42) aka "BM" in little-endian format
	As Long bfSize
	As ULong bfReserved
	As ULong bfOffBits
	As ULong biSize
	As Long biWidth
	As Long biHeight
	As UShort biPlanes
	As UShort biBitCount
	As ULong biCompression
	As ULong biSizeImage
	As Long biXPelsPerMeter
	As Long biYPelsPerMeter
	As ULong biClrUsed
	As ULong biClrImportant
End Type

Function GetBitmapHeaderInfo(filename As String) As tBitmap_Header
	Dim As tBitmap_Header BmpInfo
	If FileExists(filename) = 0 Then Return BmpInfo
	Dim As Integer f
	f = FreeFile
	Open filename For Binary As #f
	Get #f, , BmpInfo
	Close #f
	Return BmpInfo
End Function

Sub GetImgDim(sFilename As String, ByRef imgWidth As Long = 0, ByRef imgHeight As Long = 0)
	Dim As tBitmap_Header BmpInfo
	BmpInfo = GetBitmapHeaderInfo(sFilename)
	imgWidth = BmpInfo.biWidth
	imgHeight = BmpInfo.biHeight
End Sub

Function EstimateMaxLines() As Integer '...'
	Dim As Integer sumErr = 0
	Dim As _Color c
	Dim As Double luminance
	For i As Integer = 0 To IMG_SIZE_SQ - 1
		If bCOLOR Then
			c.argb = colorImage(i)
			luminance = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b
			sumErr += 255 - luminance
		Else
			sumErr += 255 - sourceImage(i)
		End If
	Next
	
	' Standardize to a suitable factor, e.g. 1000 lines with medium error
	Dim As Double avgErr = sumErr / IMG_SIZE_SQ
	Return CInt(avgErr * TUNINGFACTOR)
End Function

Sub EllipseAA(x As Single, y As Single, radiusX As Single, radiusY As Single, _col As ULong, thickness As Single = 1, mode As UByte = 1, pImage As Any Ptr = 0) '...'
	Dim As Single dist, db, n, nn, ellR, ellE, ellCA, px, py, px2
	If radiusX > radiusY Then
		ellE = Sqr(radiusX * radiusX - radiusY * radiusY) / radiusX
	Else
		ellE = Sqr(radiusY * radiusY - radiusX * radiusX) / radiusY
	EndIf
	ellE *= ellE
	If mode = 1 Then
		For n = 0.5 To radiusX
			For nn = 0.5 To radiusY
				dist = Sqr(n * n + nn * nn)
				If radiusX > radiusY Then
					If n Then
						ellCA = n / dist
						ellR = Sqr(radiusY * radiusY / (1 - ellE * ellCA * ellCA))
					Else
						ellR = radiusY
					End If
				Else
					If nn Then
						ellCA = nn / dist
						ellR = Sqr(radiusX * radiusX / (1 - ellE * ellCA * ellCA))
					Else
						ellR = radiusX
					End If
				End If
				If dist <= ellR And dist > ellR - 1 Then 'outer border aa
					db = Abs(ellR - 1 - dist)
					px = x + n : py = y + nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x + n : py = y - nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
				 ElseIf dist <= ellR - thickness And dist > ellR - thickness - 1 Then 'inner border aa
					db = Abs(ellR - thickness - dist)
					px = x + n : py = y + nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x + n : py = y - nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
				ElseIf dist <= ellR - 1 And dist > ellR - thickness Then 'fill
					py = y + nn
					px = x + n
					px2 = x - n
					PSet pImage, (px, py), _col		'right bottom section
					PSet pImage, (px2, py), _col	'left bottom section
					py = y - nn
					PSet pImage, (px, py), _col		'right top section
					PSet pImage, (px2, py), _col	'left top section
				End If
			Next
		Next
	Else
		For n = 0.5 To radiusX
			For nn = 0.5 To radiusY
				dist = Sqr(n * n + nn * nn)
				If radiusX > radiusY Then
					If n Then
						ellCA = n / dist
						ellR = Sqr(radiusY * radiusY / (1 - ellE * ellCA * ellCA))
					Else
						ellR = radiusY
					End If
				Else
					If nn Then
						ellCA = nn / dist
						ellR = Sqr(radiusX * radiusX / (1 - ellE * ellCA * ellCA))
					Else
						ellR = radiusX
					End If
				End If
				If dist <= ellR And dist > ellR - 1 Then
					db = 1 - (ellR - dist)
					px = x + n : py = y + nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x + n : py = y - nn
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
					px = x - n
					PSet pImage, (px, py), ColBlend(Point (px, py, pImage), _col, db)
				ElseIf dist <= ellR - 1 Then
					py = y + nn
					px = x + n
					px2 = x - n
					PSet pImage, (px, py), _col
					PSet pImage, (px2, py), _col
					py = y - nn
					PSet pImage, (px, py), _col
					PSet pImage, (px2, py), _col
				EndIf
			Next
		Next
	EndIf
End Sub

Function LoadImage(filename As String) As Byte '...'
	Dim As Long imgWidth, imgHeight, sx, sy, ex, ey, x, y
	GetImgDim(filename, imgWidth, imgHeight)
	If imgWidth = 0 Or imgHeight = 0 Then '...'
		Print !"ERROR - could not load image file: " & filename
		Return 0
	End If
	
	If imgWidth = imgHeight Then '...'
		sx = 0
		sy = 0
		IMG_SIZE = imgWidth
	ElseIf imgWidth > imgHeight Then
		sx = (imgWidth - imgHeight) \ 2
		sy = 0
		IMG_SIZE = imgHeight
	Else '...'
		sx = 0
		sy = (imgHeight - imgWidth) \ 2
		IMG_SIZE = imgWidth
	End If
	
	IMG_SIZE_SQ = IMG_SIZE * IMG_SIZE
	
	Dim As Double avgPinSpacing = (2 * PI * (IMG_SIZE / 2)) / PINS
	MIN_DISTANCE = Int((MINLENGTH / avgPinSpacing) + 0.5)
	'MIN_DISTANCE = Int(IMG_SIZE / PINS * 2)

	ReDim As UByte sourceImage(IMG_SIZE_SQ - 1)
	ReDim As Integer errorImage(IMG_SIZE_SQ - 1)
	ReDim As ULong colorImage(IMG_SIZE_SQ - 1)
	ReDim As Integer line_cache_x(PINS * PINS - 1, 0 To IMG_SIZE * 2)
	ReDim As Integer line_cache_y(PINS * PINS - 1, 0 To IMG_SIZE * 2)
	
	ScreenRes IMG_SIZE, IMG_SIZE, 32, , GFX_ALPHA_PRIMITIVES
	CenterFBWin(IMG_SIZE, IMG_SIZE)
	Color 0, IIf(bINVERS, &hFF000000 Or ((&hFFFFFF And BGCOLOR) Xor &hFFFFFF), BGCOLOR)
	Cls
	
	Dim As Any Ptr img = ImageCreate(imgWidth, imgHeight)
	BLoad(filename, img)
	
	Dim As _Color c
	Dim As UByte r
	Dim As ULong o
	For y = 0 To IMG_SIZE - 1
		o = y + sy
		For x = 0 To IMG_SIZE - 1
			c.argb = Point(x + sx, o, img)
			If c.r <> c.g OrElse c.g <> c.b Then 'convert pixel to grey if not
				r = CUByte((c.r * 213 + c.g * 715 + c.b * 72) / 1000)
			Else
				r = c.r
			End If
			sourceImage(y * IMG_SIZE + x) = IIf(bINVERS, 255 - r, r)
			colorImage(y * IMG_SIZE + x) = c.argb
		Next
	Next
	ImageDestroy(img)
	
	MAX_LINES = EstimateMaxLines()
	Return 1
End Function

Sub calculatePinCoords() '...'
	Dim As Double cx = IMG_SIZE / 2, angle
	Dim radius As Double = cx - 1
	For i As Integer = 0 To PINS - 1
		angle = 2 * PI * i / PINS
		pin_coords(i).x = Int(cx + radius * Cos(angle))
		pin_coords(i).y = Int(cx + radius * Sin(angle))
	Next
End Sub

Function linspaceInt(startv As Integer , endv As Integer , count As Integer , result() As Integer ) As Integer '...'
	If count < 2 Then Return 0
	Dim As Double stepv = (endv - startv) / (count - 1)
	For i As Integer = 0 To count - 1
		result(i) = Int(startv + stepv * i)
	Next
	Return count
End Function

Sub precalculateLines() '...'
	Dim As Integer i, j, k, x0, y0, x1, y1, dx, dy, steps, used, idx1, idx2, imax = IMG_SIZE * 2
	Dim As Double dist
	For i = 0 To PINS - 1
		For j = i + MIN_DISTANCE To PINS - 1
			x0 = pin_coords(i).x
			y0 = pin_coords(i).y
			x1 = pin_coords(j).x
			y1 = pin_coords(j).y
			dx = x1 - x0
			dy = y1 - y0
			dist = Sqr(dx * dx + dy * dy)
			steps = Int(dist)
			If steps > imax Then steps = imax
			Dim tempX(0 To imax) As Integer 
			Dim tempY(0 To imax) As Integer 
			used = linspaceInt(x0, x1, steps, tempX())
			linspaceInt(y0, y1, steps, tempY())
			
			idx1 = j * PINS + i
			idx2 = i * PINS + j
			For k = 0 To used - 1
				line_cache_x(idx1, k) = tempX(k)
				line_cache_y(idx1, k) = tempY(k)
				line_cache_x(idx2, k) = tempX(k)
				line_cache_y(idx2, k) = tempY(k)
			Next
			line_cache_len(idx1) = used
			line_cache_len(idx2) = used
		Next
	Next
End Sub

Function contains(arr() As Integer, _val As Integer) As Byte '...'
	For i As Integer = LBound(arr) To UBound(arr)
		If arr(i) = _val Then Return -1
	Next
	Return 0
End Function

Function GetLineShadeByImage(idx As Integer) As ULong '...'
	Dim As Integer sum = 0, x, y
	For i As Integer = 0 To line_cache_len(idx) - 1
		x = line_cache_x(idx, i)
		y = line_cache_y(idx, i)
		sum += sourceImage(y * IMG_SIZE + x)
	Next
	Dim As Integer avg = sum \ line_cache_len(idx)
	Return RGBA(avg, avg, avg, 0)
End Function

Function GetSepiaShadeByImage(idx As Integer) As ULong '...'
	Dim As Integer rTotal = 0, gTotal = 0, bTotal = 0
	Dim As Integer x, y, gray, r, g, b
	Dim As Integer count = 0
	
	For i As Integer = 0 To line_cache_len(idx) - 1
		x = line_cache_x(idx, i)
		y = line_cache_y(idx, i)
		
		gray = sourceImage(y * IMG_SIZE + x)
		
		r = gray * 0.393 + gray * 0.769 + gray * 0.189
		g = gray * 0.349 + gray * 0.686 + gray * 0.168
		b = gray * 0.272 + gray * 0.534 + gray * 0.131
		
		r = IIf(r > 255, 255, r)
		g = IIf(g > 255, 255, g)
		b = IIf(b > 255, 255, b)
		
		rTotal += r
		gTotal += g
		bTotal += b
		count += 1
	Next
	
	If count = 0 Then Return RGBA(112, 66, 20, 0) ' Fallback to standard Sepia
	
	Return RGBA(rTotal \ count, gTotal \ count, bTotal \ count, 0)
End Function

Function GetSepiaShade(idx As Integer) As ULong
	Dim As Integer sum = 0, x, y
	For i As Integer = 0 To line_cache_len(idx) - 1
		x = line_cache_x(idx, i)
		y = line_cache_y(idx, i)
		sum += sourceImage(y * IMG_SIZE + x)
	Next
	Dim As Double gray = sum / line_cache_len(idx)
	
	Dim As Short r = gray + 40          'gray         'gray + 40
	Dim As Short g = gray * 0.6 + 30    'gray * 0.75  'gray * 0.6 + 30
	Dim As Short b = gray * 0.3         'gray * 0.4   'gray * 0.3
	
	r = IIf(r > 255, 255, r)
	g = IIf(g > 255, 255, g)
	b = IIf(b > 255, 255, b)
	
	Return RGBA(r, g, b, 0)
End Function

Function Clamp(value As Double, minVal As UByte = 0, maxVal As UByte = 255) As Double '...'
    If value < minVal Then Return minVal
    If value > maxVal Then Return maxVal
    Return value
End Function

Function AdjustContrast(r As Long, g As Long, b As Long) As ULong '...'
    Dim As UByte nr = Clamp(128 + (r - 128) * CONTRASTFACTOR)
    Dim As UByte ng = Clamp(128 + (g - 128) * CONTRASTFACTOR)
    Dim As UByte nb = Clamp(128 + (b - 128) * CONTRASTFACTOR)
    Return RGBA(nr, ng, nb, 0)
End Function

Function GetAverageColor(idx As Integer) As ULong '...'
	Dim As Long rSum = 0, gSum = 0, bSum = 0, count = 0
	Dim As Integer x, y
	Dim As _Color c
	
	For i As Integer = 0 To line_cache_len(idx) - 1
		x = line_cache_x(idx, i)
		y = line_cache_y(idx, i)
		c.argb = colorImage(y * IMG_SIZE + x)
		rSum += c.r
		gSum += c.g
		bSum += c.b
		count += 1
	Next
	
	If count = 0 Then Return &h00000000

	Return AdjustContrast(rSum \ count, gSum \ count, bSum \ count)
End Function

Function GetAverageColorArea(cx As Integer , cy As Integer , size As UByte = 2) As ULong 'size 1 = 3x3, 2 = 5x5 '...'
    Dim As Integer rSum = 0, gSum = 0, bSum = 0, count = 0
    Dim As Integer x, y
    Dim As _Color c

    For y = cy - size To cy + size
        For x = cx - size To cx + size
            If x >= 0 And y >= 0 And x < IMG_SIZE And y < IMG_SIZE Then
                c.argb = colorImage(y * IMG_SIZE + x)
                rSum += c.r
                gSum += c.g
                bSum += c.b
                count += 1
            End If
        Next
    Next

    If count = 0 Then Return &h00000000

    Return RGBA(rSum \ count, gSum \ count, bSum \ count, ALPHA_CHANNEL)
End Function

Sub DrawColoredLineSegments(x1 As Long, y1 As Long, x2 As Long, y2 As Long) '...'
    Dim As Double dx = (x2 - x1) / SEGMENTS
    Dim As Double dy = (y2 - y1) / SEGMENTS
    Dim As Integer i
    Dim As Double sx, sy, ex, ey
    Dim As _Color c
    Dim As Integer cx, cy, colorVal

    For i = 0 To SEGMENTS - 1
        sx = x1 + i * dx
        sy = y1 + i * dy
        ex = x1 + (i + 1) * dx
        ey = y1 + (i + 1) * dy

        ' Koordinaten für Farbprobe
        cx = Int((sx + ex) / 2)
        cy = Int((sy + ey) / 2)

        If cx >= 0 And cy >= 0 And cx < IMG_SIZE And cy < IMG_SIZE Then
            'c.argb = colorImage(cy * IMG_SIZE + cx)
            'colorVal = RGBA(c.r, c.g, c.b, ALPHA_CHANNEL)
            colorVal = GetAverageColorArea(cx, cy, 1)
            If bAA Then
            	DrawLineAAWu(sx, sy, ex, ey, colorVal)
            Else
            	Line (sx, sy) - (ex, ey), colorVal
            End If
            
        End If
    Next
End Sub

Sub calculateLines(iLineColor As ULong = 0)
	Dim As Integer i, j, k, current_pin, last_pins(19), line_sequence(0 To MAX_LINES - 1), _step, best_err, best_pin, best_idx, offset, test_pin, idx, sum, x, y
	For i = 0 To IMG_SIZE_SQ - 1
		errorImage(i) = 255 - sourceImage(i)
	Next
	
	current_pin = 0
	For i = 0 To 19
		last_pins(i) = -1
	Next
	
	If bAUTOSEGMENTS Then SEGMENTS = IMG_SIZE \ 22
	
	iDrawn = 0
	For _step = 0 To MAX_LINES - 1
		best_err = -1
		best_pin = -1
		best_idx = -1
		
		For offset = MIN_DISTANCE To PINS - MIN_DISTANCE
			test_pin = (current_pin + offset) Mod PINS
			If contains(last_pins(), test_pin) Then Continue For
			'Dim As Integer dist = Abs(test_pin - current_pin)
			'If dist > PINS / 2 Then dist = PINS - dist ' shortest arc on circle
			'If dist < MIN_DISTANCE Then Continue For

			idx = test_pin * PINS + current_pin
			sum = 0
			For j = 0 To line_cache_len(idx) - 1
				x = line_cache_x(idx, j)
				y = line_cache_y(idx, j)
				sum += errorImage(y * IMG_SIZE + x)
			Next
			
			If sum > best_err Then
				best_err = sum
				best_pin = test_pin
				best_idx = idx
			End If
		Next
		
		If best_err < 1000 Then 'No more significant error. Abort step
			Exit For
		End If
		
		If best_pin = -1 Then Exit For
		line_sequence(_step) = best_pin
		
		If SEGMENTS > 1 And bCOLOR Then 
			DrawColoredLineSegments(pin_coords(current_pin).x, pin_coords(current_pin).y, pin_coords(best_pin).x, pin_coords(best_pin).y)
		Else
			iLineColor = IIf(bINVERS, iLineColor Xor &h00FFFFFF, iLineColor)
			If bAA Then 
				DrawLineAAWu(pin_coords(current_pin).x, pin_coords(current_pin).y, pin_coords(best_pin).x, pin_coords(best_pin).y, ALPHA_CHANNEL Shl 24 Or IIf(bCOLOR, GetAverageColor(best_idx), IIf(bSEPIA, GetSepiaShade(_step), iLineColor)))
			Else
				Line (pin_coords(current_pin).x, pin_coords(current_pin).y) - (pin_coords(best_pin).x, pin_coords(best_pin).y), ALPHA_CHANNEL Shl 24 Or IIf(bCOLOR, GetAverageColor(best_idx), IIf(bSEPIA, GetSepiaShade(_step), iLineColor))
			End If
		EndIf
		
		' error update
		For j = 0 To line_cache_len(best_idx) - 1
			x = line_cache_x(best_idx, j)
			y = line_cache_y(best_idx, j)
			errorImage(y * IMG_SIZE + x) -= LINE_WEIGHT
		Next
		
		For k = 0 To 18
			last_pins(k) = last_pins(k + 1)
		Next
		last_pins(19) = best_pin
		current_pin = best_pin
		iDrawn += 1
	Next
End Sub


'If LoadImage(ExePath & "\salvador-dali 800x800.bmp") = 0 Then End
If LoadImage(ExePath & "\3939159_954x682.bmp") = 0 Then End
calculatePinCoords()
precalculateLines()
calculateLines()

If bBORDER Then EllipseAA(IMG_SIZE \ 2, IMG_SIZE \ 2, IMG_SIZE \ 2 + 1, IMG_SIZE \ 2 + 1, BORDERCLR, BORDERSIZE)

If bSAVERESULT Then
	Dim As Any Ptr img = ImageCreate(IMG_SIZE, IMG_SIZE)
	Get (0, 0) - (IMG_SIZE - 1, IMG_SIZE - 1), img
	BSave(FILENAME, img)
	ImageDestroy(img)
EndIf

Sleep
Currently only BMP images are supported.

Result example greyscale:
Image

Color:
Image

Color with segments:
Image

GDI+ version can be found on my 1Drv!

Download: StringArtGenerator
Last edited by UEZ on Apr 23, 2025 17:19, edited 7 times in total.
srvaldez
Posts: 3592
Joined: Sep 25, 2005 21:54

Re: StringArtGenerator build 2025-04-15

Post by srvaldez »

hi UEZ
this looks very good 👍😁
dafhi
Posts: 1738
Joined: Jun 04, 2005 9:51

Re: StringArtGenerator build 2025-04-15

Post by dafhi »

i read the algorithm overview in a link. could apply to simple shapes. full color would be a snap
UEZ
Posts: 1078
Joined: May 05, 2017 19:59
Location: Germany

Re: StringArtGenerator build 2025-04-15

Post by UEZ »

Thank you both for your feedback.
dafhi wrote: Apr 16, 2025 21:58 i read the algorithm overview in a link. could apply to simple shapes. full color would be a snap
You have to use one average color per line and the result is not as colorful as you might imagine:

Image

I will add this feature...
dafhi
Posts: 1738
Joined: Jun 04, 2005 9:51

Re: StringArtGenerator build 2025-04-15

Post by dafhi »

ah nice. yeah that picture does look .. i think if the r g b were handled separately ..
UEZ
Posts: 1078
Joined: May 05, 2017 19:59
Location: Germany

Re: StringArtGenerator build 2025-04-15

Post by UEZ »

Code update!

Added sepia and color display.
UEZ
Posts: 1078
Joined: May 05, 2017 19:59
Location: Germany

Re: StringArtGenerator build 2025-04-19

Post by UEZ »

Added line segment color for mor detailed color look.

Look to first post.
dafhi
Posts: 1738
Joined: Jun 04, 2005 9:51

Re: StringArtGenerator build 2025-04-19

Post by dafhi »

excellent!
Post Reply