Floodfill für Pixmap

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

 

PhillipK

Betreff: Floodfill für Pixmap

BeitragMo, Feb 11, 2013 3:08
Antworten mit Zitat
Benutzer-Profile anzeigen
Huhu!
Als ich eben gelesen habe, das jemand eine Floodfill funktion gesucht hat, konnte ichs mir nicht nehmen lassen, eine zu basteln.
Da ich das ganze aber in Blitzmax umgesetzt habe und auch nicht auf Rekursion gesetzt habe, wollte ich das nicht umbedingt als lösung posten: TList umzusetzen ist zb ne ecke schwerer als ne simple, gute überwachte Rekursion in BB.

Anyways.
funktionsweise:
Breitensuche. Ganz Simpel. ein startpunkt XY wird in eine liste geschmissen, ein array um bereits geprüftes zu überwachen wird angelegt und los gehts: Solange punkte da sind, umkreis checken.

Das ganze hat eine Schwellwert funktion: Man gibt eine Schwelle im bereich von 0 bis 255 an, wie die einzelnen Komponenten sich voneinander unterscheiden können.
Das ganze ist noch mit recht dreckigem testprogramm ausgestattet..

Hier der code. Funktionen stehen unten an, einfach nach PIX_ suchen.

BlitzMax: [AUSKLAPPEN]
Rem
Da ich eben eine recht intressannte und doch recht simple sache gelesen habe, wollte ich doch selbst auf die schnelle was dazu basteln..
Das ganze bezieht sich auf die Floodfill-problem-leichenschändung hier: https://www.blitzforum.de/forum/viewtopic.php?p=407644#407644
Da das ganze BB ist und ich nur Bmax vorrätig hab.. hey, machen wir ein paint auf die schnelle!
end rem


SuperStrict

Graphics(1024, 768)

Global background:TPixmap = CreatePixmap(GraphicsWidth(), GraphicsHeight(), 6) 'hintergrundbild, da bmx doof ist.

background.ClearPixels($ffffffff) 'Weißer hintergrund

Local timer:TTimer = CreateTimer(60)
Local x:Int = -1
Local y:Int = -1

While Not KeyHit(KEY_ESCAPE) And Not AppTerminate()

Local md:Int = MouseDown(1)
Local mh:Int = MouseHit(1)

If Not md And Not mh And x >= 0 And y >= 0 Then
PIX_DrawLine(background, x, y, MouseX(), MouseY(), 255, 0, 0)

x = -1
y = -1
ElseIf mh Then
x = MouseX()
y = MouseY()
End If


If MouseHit(2) Then
PIX_FloodFill(background, MouseX(), MouseY(),, 255, 127, 0)
End If
Cls


DrawPixmap(background, 0, 0)

'Vorschau zeichnen
If x >= 0 And y >= 0 Then
SetColor(255, 0, 0)
DrawLine(x, y, MouseX(), MouseY())
SetColor(255, 255, 255)
End If

Flip 0
WaitTimer(timer)
Wend


Rem
floodfill funktion basierend auf breitensuche mit schwellwert.
Breitensuche ist recht simpel. Es wird ein "isDone" array in größe des maximal setzbaren bereichs erstellt. Nun wird vom startpunkt x/y eine breitensuche gestartet.
Für jeden punkt welcher "setzbar" geprüft wurde wird weitergegangen. Jeder bearbeitete punkt setzt in "isdone" die x,y koordinate auf 1 (true).
Ist ein punkt nicht positiv gecheckt worden, sprich er entsprach nicht der startfarbe mit toleranz, wird sein umkreis nicht weiter verfolgt.

Der schwellwert ist ganz simpel: Abs(FirstCheckColor - CurrentCheckColor) < Schwelle. Natürlich pro farbanteil.
end rem

Function PIX_FloodFill(pixmap:TPixmap, x:Int, y:Int, schwellwert:Byte = 15, r:Byte, g:Byte, b:Byte, a:Byte = 255)

If x < 0 Or y < 0 Or x >= pixmap.width Or y >= pixmap.height Then Return 'startpunkt verifzieren

Local sr:Int, sg:Int, sb:Int, sa:Int
GetColor_Parts(pixmap.ReadPixel(x, y), sr, sg, sb, sa) 'startfarbe anteilhaft einlesen.



Local isDone:Int[,] = New Int[pixmap.width, pixmap.height]
isDone[x, y] = True 'startpunkt als "bearbeitet" makieren.

Local color:Int = GetIntCol(r, g, b, a) 'farbe zum setzen holen

Local worklist:TList = CreateList() 'Linked list. BB'ler müssten sich ein entsprechendes system mit Types zusammenbauen. Prinzipiell brauchts nur irgendwo eine liste an x,y koordinaten die noch zur prüfung ausstehen.
worklist.AddLast(CreatePos(x, y))

'faulheit: um per for local i:int = 0 until 4 alle umliegenden punkte zu kriegen, erstelle ich arrays die die entsprechenden richtungen darstellen.
Local dirX:Int[] = [- 1, 0, 1, 0]
Local dirY:Int[] = [0, -1, 0, 1]

Local p:TPosition
Local tr:Int, tg:Int, tb:Int, ta:Int
While worklist.IsEmpty() = 0 'solange noch punkte zu bearbeiten sind...
p = TPosition(worklist.RemoveFirst())

For Local i:Int = 0 Until 4

'position verifizieren
If p.x + dirX[i] >= 0 And p.x + dirX[i] < pixmap.width And p.y + dirY[i] >= 0 And p.y + dirY[i] < pixmap.height Then

'check: bereits bearbeitet?
If Not isDone[p.x + dirX[i], p.y + dirY[i]] Then

GetColor_Parts(pixmap.ReadPixel(p.x + dirX[i], p.y + dirY[i]), tr, tg, tb, ta)

If Abs(tr - sr) < schwellwert And Abs(tg - sg) < schwellwert And Abs(tb - sb) < schwellwert And Abs(ta - sa) < schwellwert Then
pixmap.WritePixel(p.x + dirX[i], p.y + dirY[i], color)


worklist.AddLast(CreatePos(p.x + dirX[i], p.y + dirY[i]))



End If


isDone[p.x + dirX[i], p.y + dirY[i]] = True
End If

End If

Next
Wend

End Function
Function PIX_DrawLine(pixmap:TPixmap, x1:Int, y1:Int, x2:Int, y2:Int, r:Byte, g:Byte, b:Byte, a:Byte = 255, thickness:Int = 5)
'direction bestimmen:
Local dx:Float = x2 - x1
Local dy:Float = y2 - y1
Local laenge:Float = Sqr(dx ^ 2 + dy ^ 2) 'länge für normalisierung des vektors
If laenge = 0 Then 'sicherung, das die länge nicht 0 ist
If x1 >= 0 And y1 >= 0 And x1 < pixmap.width And y1 < pixmap.height Then
'Wie ein set pixel.
pixmap.WritePixel(x1, y1, GetIntCol(r, g, b, a))
End If
Return
EndIf
dx = dx / laenge
dy = dy / laenge


'Line-Plot funktion:
Local x:Float = x1
Local y:Float = y1

Local steps:Int = Ceil (laenge)

Local i:Int = 0
Local color:Int = getIntCol(r, g, b, a)
Local tmpcol:Int = color
Local tr:Int, tg:Int, tb:Int, ta:Int
While i < steps
If x >= 0 And y >= 0 And x < pixmap.width And y < pixmap.height Then 'ist-in-pixmap prüfung. Aka: ist die koordinate auf dem bildschirm?
If a < 255 Then
'tmpcolor ändern.
GetColor_Parts(pixmap.ReadPixel(x, y), tr, tg, tb, ta)

'temp r,g,b,a enthält die vorhandenen farbwerte. Nun bestimme ich 2 faktoren: 1.0-(a / 256.0) als skalierfaktor für die zielfarbe, aka momentan in pixmap und
'(a / 256.0) als skalierfaktor für r,g,b,a.
tr = ((1.0 - (a / 256.0)) * tr) + ((a / 256.0) * r)
tg = ((1.0 - (a / 256.0)) * tg) + ((a / 256.0) * g)
tb = ((1.0 - (a / 256.0)) * tb) + ((a / 256.0) * b)
ta = 255

tmpCol = GetIntCol(tr, tg, tb, ta)

'Wer openGl schonmal direkt genutzt hat: Das dürfte, mit ausnahme des alphakanals, in etwa glBlendFunc(GL_SRc_ALPHA, GL_ONE_MINUS_SRC_ALPHA) entsprechen. Heißt also, alphablending.
End If

pixmap.WritePixel(x, y, tmpcol) 'SetColor(r,g,b) + Plot(x,y) in bb als beispiel.
End If

x:+dx
y:+dy

i:+1
Wend

If thickness > 1 Then
'normale zur linie
Local normalX:Float = -dy
Local normalY:Float = dx

If Abs(normalX) > Abs(normalY) Then
'x ist größer
x = Sgn(normalX)
y = 0
Else
y = Sgn(normalY)
x = 0
End If

PIX_DrawLine(pixmap, x1 + normalX, y1 + normalY, x2 + normalX, y2 + normalY, r, g, b, a * 0.25, thickness - 2)

normalX = dy
normalY = -dx
If Abs(normalX) > Abs(normalY) Then
'x ist größer
normalx = Sgn(normalX)
normaly = 0
Else
normaly = Sgn(normalY)
normalx = 0
End If

PIX_DrawLine(Pixmap, x1 + normalX, y1 + normaly, x2 + normalx, y2 + normaly, r, g, b, a * 0.5, thickness - 2)

End If

End Function

Rem
Getcolor_Parts formt eine intcolor in 4 "bytes" um. Entspricht zb dem bb befehl Getcolor und dem getten der coloranteile.
end rem

Function GetColor_Parts(intcolor:Int, r:Int Var, g:Int Var, b:Int Var, a:Int Var)
a = (intcolor & $ff000000) Shr 24
r = (intcolor & $00ff0000) Shr 16
g = (intcolor & $0000ff00) Shr 8
b = (intcolor & $000000ff)
End Function

Rem
GetIntcol fügt 4 farbanteile (RGB und A) zu einer integer-color zusammen wie sie in blitzmax mit pixmaps verwendet wird.
end rem

Function GetIntCol:Int(r:Byte, g:Byte, b:Byte, a:Byte = 255)
Local color:Int = 0
color:+a Shl 24
color:+r Shl 16
color:+g Shl 8
color:+b

Return color
End Function
Rem
TPosition type. Mal extra abgespeckt, damits leichter zum konvertieren zu bb ist, falls gewünscht.
Hält nur X,Y koordinaten.
end rem

Type TPosition
Field x:Int
Field y:Int
End Type
' "Konstruktor" für TPosition
Function CreatePos:TPosition(x:Int, y:Int)
Local pos:TPosition = New TPosition
pos.x = x
pos.y = y
Return pos
End Function


Steuerung: Linksklick + drag = linie setzen. Ist standartmäßig mit etwas größerer breite und festem farbwert (255,0,0) eingestellt. Ränder der linien werden "gesmoothed" um Schwellwert für floofill sichtbar zu machen.
Rechtsklick = Floodfill mit farbe 255,127,0. schwellwert ist standartmäßig 15.

Anmerkung: Da Bmx recht beschämend ist, was der zugriff auf den backbuffer ist und ich nicht umbedingt mit Plot und SetColor arbeiten wollte, mache ich das ganze in einer Pixmap. Im prinzip ist das ziel egal - solange man farbwerte lesen und schreiben kann.

Anmerkung2: Ich musste leider feststellen, das eine Floodfill funktion auf die schnelle wesentlich einfacher war, wie eine "sanfte linie mit größerer breite wie 1, welche im alphawert ausgefadet wird".
Generell die breite linie macht mir zu schaffen. So wies jetzt ist, siehts schräg aus. Gutes project für morgen: Very smoothed lines selber plotten Wink Die idee existiert!

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group