Die Idee hinter dem Projekt ist ein Charakter mittels Webcam zu steuern. Die Steuerung erfolgt mit 3 auf Daumen und Zeigefinger aufgeklebten Markern. Die große Herausforderung ist, im Bild diese 3 Marker zu erkennen. Hier kommt die Circular Hough Transform (CHT) zum Einsatz, um Kreise zu erkennen.
Download mit Media: https://www.blitzforum.de/upload/file.php?id=11973
Beinhaltet den Source-Code, Escapi-DLL und -API, Schnellanleitung und Druckvorlage für die Marker.
Habe gerade keine Marker zu Hand, weswegen ich einfach in Paint 3 schwarze Punkte gezeichnet und mit der Webcam den Bildschirm abgefilmt habe:
Die Markererkennung ist in folgendem Bild zu sehen:
[Oberes Bild: Kantenerkennung (rot) und Markererkennung (grün); unteres Bild: Colornessbild (wie farbig sind die Pixel?)]
So sind die Marker auf der Hand anzubringen:
Die Markererkennung beruht auf Methoden in der Bildverarbeitung, insbesondere dem Canny-Operator zur Kantenerkennung und der Circular Hough Transform (CHT) zur Kreiserkennung. Die Schritte sind:
1. Lese das Webcambild in ein Array ein. Das Array enthält die Farbwerte im RGB-Farbraum.
2. Projeziere das Bild zum Grauwertbild.
3. Canny-Operator zur Kantenerkennung
3.1 Tiefpassfilterung, d. h. hohe Frequenzen werden gefiltert. Hier mit einem 5x5 Gauß-Kern (bei Photoshop "Gaußscher Weichzeichner")
3.2 Gradientenbild erstellen. Hier mittels SobelX und SobelY.
3.3 Als Kantenpixel gelten diejenigen Pixel, deren Gradientenbeträge in unmittelbarer Nachbarschaft maximal sind.
4. Kreiserkennung mittels HCT
5. Markererkennung anhand von Kriterien, bspw. ob innere Randpixel dunkler sind, als äußere Randpixel
BlitzMax: [AUSKLAPPEN] [EINKLAPPEN] SuperStrict
Framework BRL.Blitz Import BRL.Max2D Import BRL.D3D9Max2D Import BRL.PNGLoader Import BRL.StandardIO Import BRL.Random Import BRL.Audio Import BRL.WAVLoader Import Pub.Win32 Import BRL.FreeAudioAudio
Import "escapi.bmx"
Const EPSILON : Float = 0.00001
Global CIRCUMFERENCE : Int[] = [.. 4, 12, 20, 20, 28, 36, 36, 44, 52, 60, 60, 68, 76, 76, 84, .. 92, 100, 100, 108, 116, 116, 124, 132, 140, 140, 148, 156, 156, 164, 172, .. 172, 180, 188, 196, 196, 204, 212, 212, 220, 228, 236, 236, 244, 252, 252, .. 260, 268, 268, 276, 284, 292, 292, 300, 308, 308, 316, 324, 332, 332, 340, .. 348, 348, 356, 364, 372, 372, 380, 388, 388, 396, 404, 404, 412, 420, 428, .. 428, 436, 444, 444, 452, 460, 468, 468, 476, 484, 484, 492, 500, 500, 508, .. 516, 524, 524, 532, 540, 540, 548, 556, 564, 564, 572, 580, 580, 588, 596, .. 596, 604, 612, 620, 620, 628, 636, 636, 644, 652, 660, 660, 668, 676, 676, .. 684, 692, 700, 700, 708, 716, 716, 724, 732, 732, 740, 748, 756, 756, 764, .. 772, 772, 780, 788, 796, 796, 804, 812, 812, 820, 828, 828, 836, 844, 852, .. 852, 860, 868, 868, 876, 884, 892, 892, 900, 908, 908, 916, 924, 932, 932, .. 940, 948, 948, 956, 964, 964, 972, 980, 988, 988, 996, 1004, 1004, 1012, 1020, .. 1028, 1028, 1036, 1044, 1044, 1052, 1060, 1060, 1068, 1076, 1084, 1084, 1092, 1100, 1100, .. 1108, 1116, 1124, 1124, 1132, 1140, 1140, 1148, 1156, 1156, 1164, 1172, 1180, 1180, 1188, .. 1196, 1196, 1204, 1212, 1220, 1220, 1228, 1236, 1236, 1244, 1252, 1260, 1260, 1268, 1276, .. 1276, 1284, 1292, 1292, 1300, 1308, 1316, 1316, 1324, 1332, 1332, 1340, 1348, 1356, 1356, .. 1364, 1372, 1372, 1380, 1388, 1388, 1396, 1404, 1412, 1412, 1420, 1428, 1428, 1436, 1444]
Const CAPTURE_WIDTH : Int = 320 Const CAPTURE_HEIGHT : Int = 240
Const DEVICE_IDX : Int = 1
Const USE_IP_CAMERA : Int = True
Const GRADIENT_THRESHOLD : Float = 150.0
Const MIN_RADIUS : Int = 3 Const MAX_RADIUS : Int = 15
Const RATIO_THRESHOLD : Float = 0.4
Const LUMINANCE_DIFFERENCE_THRESHOLD : Float = 0.3
Const COLORNESS_THRESHOLD : Byte = 40
Global gaussian3x3 : Float[3, 3] gaussian3x3[0, 0] = 1.0 / 16.0 ; gaussian3x3[1, 0] = 2.0 / 16.0 ; gaussian3x3[2, 0] = 1.0 / 16.0 gaussian3x3[0, 1] = 2.0 / 16.0 ; gaussian3x3[1, 1] = 4.0 / 16.0 ; gaussian3x3[2, 1] = 2.0 / 16.0 gaussian3x3[0, 2] = 1.0 / 16.0 ; gaussian3x3[1, 2] = 2.0 / 16.0 ; gaussian3x3[2, 2] = 1.0 / 16.0
Global gaussian5x5 : Float[5, 5] gaussian5x5[0, 0] = 1.0 / 256.0 ; gaussian5x5[1, 0] = 4.0 / 256.0 ; gaussian5x5[2, 0] = 6.0 / 256.0 ; gaussian5x5[3, 0] = 4.0 / 256.0 ; gaussian5x5[4, 0] = 1.0 / 256.0 gaussian5x5[0, 1] = 4.0 / 256.0 ; gaussian5x5[1, 1] = 16.0 / 256.0 ; gaussian5x5[2, 1] = 24.0 / 256.0 ; gaussian5x5[3, 1] = 16.0 / 256.0 ; gaussian5x5[4, 1] = 4.0 / 256.0 gaussian5x5[0, 2] = 6.0 / 256.0 ; gaussian5x5[1, 2] = 24.0 / 256.0 ; gaussian5x5[2, 2] = 36.0 / 256.0 ; gaussian5x5[3, 2] = 24.0 / 256.0 ; gaussian5x5[4, 2] = 6.0 / 256.0 gaussian5x5[0, 3] = 4.0 / 256.0 ; gaussian5x5[1, 3] = 16.0 / 256.0 ; gaussian5x5[2, 3] = 24.0 / 256.0 ; gaussian5x5[3, 3] = 16.0 / 256.0 ; gaussian5x5[4, 3] = 4.0 / 256.0 gaussian5x5[0, 4] = 1.0 / 256.0 ; gaussian5x5[1, 4] = 4.0 / 256.0 ; gaussian5x5[2, 4] = 6.0 / 256.0 ; gaussian5x5[3, 4] = 4.0 / 256.0 ; gaussian5x5[4, 4] = 1.0 / 256.0
Global sobelx : Int[3, 3] sobelx[0, 0] = -1 ; sobelx[1, 0] = 0 ; sobelx[2, 0] = 1 sobelx[0, 1] = -2 ; sobelx[1, 1] = 0 ; sobelx[2, 1] = 2 sobelx[0, 2] = -1 ; sobelx[1, 2] = 0 ; sobelx[2, 2] = 1
Global sobely : Int[3, 3] sobely[0, 0] = -1 ; sobely[1, 0] = -2 ; sobely[2, 0] = -1 sobely[0, 1] = 0 ; sobely[1, 1] = 0 ; sobely[2, 1] = 0 sobely[0, 2] = 1 ; sobely[1, 2] = 2 ; sobely[2, 2] = 1
Local deviceCount : Int = CountCaptureDevices() Print "Devices:" For Local i : Int = 0 Until deviceCount Print i + ") " + CaptureDeviceName(i) Next If deviceCount <= DEVICE_IDX Then Throw "Capture device " + DEVICE_IDX + " not found"
Global device : TCaptureDevice = OpenCaptureDevice(DEVICE_IDX, CAPTURE_WIDTH, CAPTURE_HEIGHT) If device = Null Then Throw "Unable to open capture device '" + CaptureDeviceName(DEVICE_IDX) + "'"
Print "Connected with '" + CaptureDeviceName(device.device) + "'"
Global source : TImage
Global sourceRGB : Byte[CAPTURE_WIDTH, CAPTURE_HEIGHT, 3]
Global sourceGray : Byte[CAPTURE_WIDTH, CAPTURE_HEIGHT]
Global sourceConvolved : Byte[CAPTURE_WIDTH, CAPTURE_HEIGHT]
Global sourceGradient : Float[CAPTURE_WIDTH, CAPTURE_HEIGHT, 3]
Global sourceEdge : Int[CAPTURE_WIDTH, CAPTURE_HEIGHT]
Global sourceHough : Int[CAPTURE_WIDTH, CAPTURE_HEIGHT, MAX_RADIUS - MIN_RADIUS]
Global circleQuality : Float[CAPTURE_WIDTH, CAPTURE_HEIGHT, MAX_RADIUS - MIN_RADIUS]
Const SINGLE_POINT : Int = 0 Const TOP_POINT : Int = 1 Const BOTTOM_POINT : Int = 2 Global circles : Int[3, 3]
Const MODE_ANIMATE : Int = 0 Const MODE_DEBUG : Int = 1 Const MODE_PAUSE : Int = 2 Local mode : Int = MODE_DEBUG
If SetAudioDriver("FreeAudio") Then Print "Current audio driver: FreeAudio" Else Print "Unable to set audio driver FreeAudio" EndIf
LoadFish()
AppTitle = CaptureDeviceName(device.device) Graphics 800, 600, 0, 60, GRAPHICS_BACKBUFFER
SetBlend ALPHABLEND While Not KeyDown(KEY_ESCAPE) If KeyHit(KEY_P) Then mode = MODE_PAUSE ElseIf KeyHit(KEY_A) Then mode = MODE_ANIMATE ElseIf KeyHit(KEY_D) Then mode = MODE_DEBUG EndIf If KeyHit(KEY_S) And device.pix <> Null Then SavePixmapPNG(device.pix, "snapshot.png", 0) Print "Saved to snapshot.png" EndIf Select mode Case MODE_ANIMATE Cls() ReadSource() ProjectToGray() Blur() CalculateGradients() DetermineEdges() CircularHoughTransform() FilterCircles() OrderCircles() DrawFish() Flip() Case MODE_DEBUG Cls() ReadSource() ProjectToGray() Blur() CalculateGradients() DetermineEdges() DrawEdges() CircularHoughTransform() FilterCircles() OrderCircles() DrawCircles() DrawColorness() Flip() Case MODE_PAUSE Cls() Analyse() DrawEdges() Flip() End Select Wend
CloseCaptureDevice(device) device = Null End
Function ReadSource() source = CaptureFrame(device) If source = Null Then Print "Device lost" Else If USE_IP_CAMERA Then SetColor 127, 127, 127 DrawRect 0, CAPTURE_HEIGHT - CAPTURE_HEIGHT * 0.09, CAPTURE_WIDTH, CAPTURE_HEIGHT * 0.09 GrabImage source, 0, 0 EndIf EndIf
Local pixmap : TPixmap = LockImage(source, 0, True, False)
For Local x : Int = 0 Until PixmapWidth(pixmap) For Local y : Int = 0 Until PixmapHeight(pixmap) Local argb : Int = ReadPixel(pixmap, x, y) sourceRGB[x, y, 0] = (argb & $00FF0000) Shr 16 sourceRGB[x, y, 1] = (argb & $0000FF00) Shr 8 sourceRGB[x, y, 2] = argb & $000000FF Next Next
UnlockImage(source) End Function
Function ProjectToGray() For Local x : Int = 0 Until CAPTURE_WIDTH For Local y : Int = 0 Until CAPTURE_HEIGHT sourceGray[x, y] = (sourceRGB[x, y, 0] + sourceRGB[x, y, 1] + sourceRGB[x, y, 2]) / 3 Next Next End Function
Function Blur() For Local x : Int = 2 Until CAPTURE_WIDTH - 2 For Local y : Int = 2 Until CAPTURE_HEIGHT - 2 Local accu : Float = 0.0 For Local i : Int = -2 To 2 For Local j : Int = -2 To 2 accu :+ gaussian5x5[i + 2, j + 2] * sourceGray[x + i, y + j] Next Next sourceConvolved[x, y] = accu Next Next End Function
Function CalculateGradients() For Local x : Int = 2 Until CAPTURE_WIDTH - 2 For Local y : Int = 2 Until CAPTURE_HEIGHT - 2 Local sx : Int = 0, sy : Int = 0 For Local i : Int = -1 To 1 For Local j : Int = -1 To 1 sx = sx + sobelx[i + 1, j + 1] * sourceConvolved[x + i, y + j] sy = sy + sobely[i + 1, j + 1] * sourceConvolved[x + i, y + j] Next Next sourceGradient[x, y, 0] = Sqr(sx * sx + sy * sy) sourceGradient[x, y, 1] = sx sourceGradient[x, y, 2] = sy Next Next End Function
Function DetermineEdges() For Local x : Int = 3 Until CAPTURE_WIDTH - 3 For Local y : Int = 3 Until CAPTURE_HEIGHT - 3 If sourceGradient[x, y, 0] < GRADIENT_THRESHOLD Then sourceEdge[x, y] = False Continue EndIf Local sx : Float = sourceGradient[x, y, 1] Local sy : Float = sourceGradient[x, y, 2] Local ga : Float, gb : Float If Abs(sx) < EPSILON Then If sy > 0.0 Then ga = sourceGradient[x, y + 1, 0] gb = sourceGradient[x, y - 1, 0] Else ga = sourceGradient[x, y - 1, 0] gb = sourceGradient[x, y + 1, 0] EndIf ElseIf Abs(sy) < EPSILON Then If sx > 0.0 Then ga = sourceGradient[x + 1, y, 0] gb = sourceGradient[x - 1, y, 0] Else ga = sourceGradient[x - 1, y, 0] gb = sourceGradient[x + 1, y, 0] EndIf Else If sx > 0.0 Then If sy > 0.0 Then ga = (sourceGradient[x + 1, y + 1, 0] + sourceGradient[x, y + 1, 0]) / 2.0 gb = (sourceGradient[x - 1, y - 1, 0] + sourceGradient[x, y - 1, 0]) / 2.0 Else ga = (sourceGradient[x + 1, y - 1, 0] + sourceGradient[x, y - 1, 0]) / 2.0 gb = (sourceGradient[x - 1, y + 1, 0] + sourceGradient[x, y + 1, 0]) / 2.0 EndIf Else If sx < 0.0 Then If sy > 0.0 Then ga = (sourceGradient[x - 1, y + 1, 0] + sourceGradient[x, y + 1, 0]) / 2.0 gb = (sourceGradient[x + 1, y - 1, 0] + sourceGradient[x, y - 1, 0]) / 2.0 Else ga = (sourceGradient[x - 1, y - 1, 0] + sourceGradient[x, y - 1, 0]) / 2.0 gb = (sourceGradient[x + 1, y + 1, 0] + sourceGradient[x, y + 1, 0]) / 2.0 EndIf Else
EndIf
sourceEdge[x, y] = sourceGradient[x, y, 0] > Max(ga, gb) Next Next End Function
Function DrawEdges() Local oldRed : Int, oldGreen : Int, oldBlue : Int GetColor(oldRed, oldGreen, oldBlue) SetColor 255, 0, 0 For Local x : Int = 0 Until CAPTURE_WIDTH For Local y : Int = 0 Until CAPTURE_HEIGHT If sourceEdge[x, y] Then Plot x, y Next Next SetColor(oldRed, oldGreen, oldBlue) End Function
Function CircularHoughTransform() For Local x : Int = 0 Until CAPTURE_WIDTH For Local y : Int = 0 Until CAPTURE_HEIGHT For Local r : Int = MIN_RADIUS Until MAX_RADIUS sourceHough[x, y, r - MIN_RADIUS] = 0 Next Next Next For Local x : Int = MAX_RADIUS Until CAPTURE_WIDTH - MAX_RADIUS For Local y : Int = MAX_RADIUS Until CAPTURE_HEIGHT - MAX_RADIUS If sourceEdge[x, y] Then For Local r : Int = MIN_RADIUS Until MAX_RADIUS RasterHoughCircle(x, y, r) Next EndIf Next Next End Function
Function RasterHoughCircle(mx : Int, my : Int, radius : Int) Local f : Int = 1 - radius Local ddF_x : Int = 0 Local ddF_y : Int = -2 * radius Local x : Int = 0 Local y : Int = radius SetHoughPoint(mx, my + radius, radius) SetHoughPoint(mx, my - radius, radius) SetHoughPoint(mx + radius, my, radius) SetHoughPoint(mx - radius, my, radius) While x < y If f >= 0 Then y = y - 1 ddF_y = ddF_y + 2 f = f + ddF_y EndIf x = x + 1 ddF_x = ddF_x + 2 f = f + ddF_x + 1 SetHoughPoint(mx + x, my + y, radius) SetHoughPoint(mx - x, my + y, radius) SetHoughPoint(mx + x, my - y, radius) SetHoughPoint(mx - x, my - y, radius) SetHoughPoint(mx + y, my + x, radius) SetHoughPoint(mx - y, my + x, radius) SetHoughPoint(mx + y, my - x, radius) SetHoughPoint(mx - y, my - x, radius) Wend End Function
Function SetHoughPoint(x : Int, y : Int, r : Int) sourceHough[x, y, r - MIN_RADIUS] :+ 1 End Function
Function FilterCircles() For Local x : Int = MAX_RADIUS Until CAPTURE_WIDTH - MAX_RADIUS For Local y : Int = MAX_RADIUS Until CAPTURE_HEIGHT - MAX_RADIUS For Local r : Int = MIN_RADIUS Until MAX_RADIUS If Not IsCircle(x, y, r) Then sourceHough[x, y, r - MIN_RADIUS] = 0 Next Next Next
For Local x : Int = 0 Until CAPTURE_WIDTH For Local y : Int = 0 Until CAPTURE_HEIGHT For Local r : Int = MIN_RADIUS Until MAX_RADIUS If sourceHough[x, y, r - MIN_RADIUS] > 0 Then Local bestX : Int = x, bestY : Int = y, bestR : Int = r Local bestRatio : Float = Float(sourceHough[x, y, r - MIN_RADIUS]) / CIRCUMFERENCE[r] For Local r2 : Int = MIN_RADIUS Until MAX_RADIUS For Local x2 : Int = Max(0, x - MAX_RADIUS - r2) Until Min(CAPTURE_WIDTH, x + MAX_RADIUS + r2) For Local y2 : Int = Max(0, y - MAX_RADIUS - r2) Until Min(CAPTURE_HEIGHT, y + MAX_RADIUS + r2) Local dx : Int = x - x2 Local dy : Int = y - y2 Local d : Int = Floor(Sqr(dx * dx + dy + dy)) If d <= (r + r2) Then Local ratio : Float = Float(sourceHough[x2, y2, r2 - MIN_RADIUS]) / CIRCUMFERENCE[r] If ratio > bestRatio Then bestX = x2 bestY = y2 bestR = r2 EndIf EndIf Next Next Next For Local r2 : Int = MIN_RADIUS Until MAX_RADIUS For Local x2 : Int = Max(0, x - MAX_RADIUS - r2) Until Min(CAPTURE_WIDTH, x + MAX_RADIUS + r2) For Local y2 : Int = Max(0, y - MAX_RADIUS - r2) Until Min(CAPTURE_HEIGHT, y + MAX_RADIUS + r2) If bestX <> x2 Or bestY <> y2 Or bestR <> r2 Then sourceHough[x2, y2, r2 - MIN_RADIUS] = 0 Next Next Next EndIf Next Next Next End Function
Function IsCircle:Int(mx : Int, my : Int, r : Int) If sourceHough[mx, my, r - MIN_RADIUS] < (CIRCUMFERENCE[r] * RATIO_THRESHOLD) Then Return False Local innerRGB : Int[3] Local outerRGB : Int[3] For Local angle : Int = 0 Until 360 Step 90 Local dx : Int = Cos(angle) * 2 Local dy : Int = Sin(angle) * 2 Local x : Int = mx + r * Cos(angle) Local y : Int = my + r * Sin(angle) For Local i : Int = 0 To 2 innerRGB[i] :+ sourceRGB[x - dx, y - dy, i] outerRGB[i] :+ sourceRGB[x + dx, y + dy, i] Next Next For Local i : Int = 0 To 2 innerRGB[i] :/ 4 outerRGB[i] :/ 4 Next
Local innerHSV : Float[3] Local outerHSV : Float[3] RGBToHSV(innerRGB[0], innerRGB[1], innerRGB[2], innerHSV[0], innerHSV[1], innerHSV[2]) RGBToHSV(outerRGB[0], outerRGB[1], outerRGB[2], outerHSV[0], outerHSV[1], outerHSV[2]) If (innerHSV[2] > outerHSV[2]) Or (outerHSV[2] - innerHSV[2] <= LUMINANCE_DIFFERENCE_THRESHOLD) Then Return False Local innerColorness : Byte = Colorness(innerRGB[0], innerRGB[1], innerRGB[2]) If innerColorness >= COLORNESS_THRESHOLD Then Return False
Return True End Function
Function OrderCircles() Local i : Int = 0 For Local x : Int = MAX_RADIUS Until CAPTURE_WIDTH - MAX_RADIUS For Local y : Int = MAX_RADIUS Until CAPTURE_HEIGHT - MAX_RADIUS For Local r : Int = MIN_RADIUS Until MAX_RADIUS If sourceHough[x, y, r - MIN_RADIUS] > 0 Then circles[i, 0] = x circles[i, 1] = y circles[i, 2] = r i :+ 1 If i > 2 Then i = 0 EndIf Next Next Next Local singleIdx : Int Local topIdx : Int Local bottomIdx : Int Local dx01 : Int = Abs(circles[0, 0] - circles[1, 0]) Local dx02 : Int = Abs(circles[0, 0] - circles[2, 0]) Local dx12 : Int = Abs(circles[1, 0] - circles[2, 0]) If dx01 < dx02 Then If dx01 < dx12 Then singleIdx = 2 Else singleIdx = 0 EndIf Else If dx02 < dx12 Then singleIdx = 1 Else singleIdx = 0 EndIf EndIf If singleIdx <> SINGLE_POINT Then ExchangeCircles(singleIdx, SINGLE_POINT) If circles[TOP_POINT, 1] > circles[BOTTOM_POINT, 1] Then ExchangeCircles(TOP_POINT, BOTTOM_POINT) End Function
Function ExchangeCircles(fromIdx:Int, toIdx : Int) Local temp : Int For Local i : Int = 0 To 2 temp = circles[fromIdx, i] circles[fromIdx, i] = circles[toIdx, i] circles[toIdx, i] = temp Next End Function
Function DrawCircles() SetColor 0, 255, 0 For Local i : Int = 0 To 2 Local x : Int = circles[i, 0] Local y : Int = circles[i, 1] Local r : Int = circles[i, 2] DrawOval x - r, y - r, 2 * r, 2 * r DrawText i, x + 10, y + 10 Next SetColor 255, 255, 255 End Function
Function Analyse() SetColor 255, 255, 255 DrawImage source, 0, 0
Local mx : Int = MouseX(), my : Int = MouseY() If mx < 0 Or mx >= CAPTURE_WIDTH Or my < 0 Or my >= CAPTURE_HEIGHT Then Return Local h : Float, s : Float, v : Float RGBToHSV(sourceRGB[mx, my, 0], sourceRGB[mx, my, 1], sourceRGB[mx, my, 2], h, s, v) DrawText "HSV: " + Int(h) + "°, " + Int(s * 100) + "%, " + Int(v * 100) + "%", mx + 10, my + 10 DrawText "RGB: " + sourceRGB[mx, my, 0] + ", " + sourceRGB[mx, my, 1] + ", " + sourceRGB[mx, my, 2], mx + 10, my + 25 DrawText "Coloness: " + Colorness(sourceRGB[mx, my, 0], sourceRGB[mx, my, 1], sourceRGB[mx, my, 2]), mx + 10, my + 40 SetColor sourceRGB[mx, my, 0], sourceRGB[mx, my, 2], sourceRGB[mx, my, 2] DrawRect mx + 10, my, 7, 7 SetColor 255, 255, 255 End Function
Function RGBToHSV(red : Int, green : Int, blue : Int, hue : Float Var, saturation : Float Var, value : Float Var)
Local r : Float = red / 255.0 Local g : Float = green / 255.0 Local b : Float = blue / 255.0 Local maxRGB : Float = Max(Max(r, g), b) Local minRGB : Float = Min(Min(r, g), b) If Abs(maxRGB - minRGB) <= EPSILON And (Abs(r - g) <= EPSILON And Abs(g - b) <= EPSILON) Then hue = 0.0 ElseIf maxRGB = r Then hue = 60.0 * (g - b) / (maxRGB - minRGB) ElseIf maxRGB = g Then hue = 60.0 * (2.0 + (b - r) / (maxRGB - minRGB)) ElseIf maxRGB = b Then hue = 60.0 * (4.0 + (r - g) / (maxRGB - minRGB)) EndIf If hue < 0.0 Then hue = hue + 360.0 If maxRGB <= EPSILON And (r <= EPSILON And g <= EPSILON And b <= EPSILON) Then saturation = 0.0 Else saturation = (maxRGB - minRGB) / maxRGB EndIf value = maxRGB End Function
Function Colorness:Byte(r : Byte, g : Byte, b : Byte)
Local u : Float[3] u[0] = 255.0 u[1] = 255.0 u[2] = 255.0 Local v : Float[3] v[0] = g * u[2] - b * u[1] v[1] = b * u[0] - r * u[2] v[2] = r * u[1] - g * u[0] Return Sqr(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) / Sqr(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) End Function
Function DrawColorness() Local pixmap : TPixmap = CreatePixmap(CAPTURE_WIDTH, CAPTURE_HEIGHT, PF_I8) Local pointer : Byte Ptr = PixmapPixelPtr(pixmap) For Local x : Int = 0 Until CAPTURE_WIDTH For Local y : Int = 0 Until CAPTURE_HEIGHT Local colorness : Int = Colorness(sourceRGB[x, y, 0], sourceRGB[x, y, 1], sourceRGB[x, y, 2]) colorness = (Float(colorness) / 209.0) * 255 If colorness > 255 Then colorness = 255 pointer[y * CAPTURE_WIDTH + x] = colorness Next Next DrawPixmap pixmap, 0, CAPTURE_HEIGHT End Function
Type TParticle Global bubble : TImage
Field sprite : TImage Field lifeTime : Int Field scale : Float Field positionX : Float Field positionY : Float Field velocityX : Float Field velocityY : Float Method Update() positionX :+ velocityX positionY :+ velocityY lifeTime :- 1 If lifeTime < 0 Then lifeTime = 0 End Method Method Render() SetScale scale, scale SetAlpha 1.0 - 0.7 * ((10 - Min(10, lifeTime)) / 10.0) DrawImage sprite, positionX, positionY SetAlpha 1.0 SetScale 1.0, 1.0 End Method Function Init() bubble = LoadImage("animation/bubble.png") End Function End Type
Type TEmitter Global bubbleSound : TSound Global bubbleChannel : TChannel Field particles : TList = New TList Field positionX : Float Field positionY : Float Method Emit() bubbleSound.Play(bubbleChannel) For Local i : Int = 0 Until Rand(3, 5) Local particle : TParticle = New TParticle Local angle : Float = Rand(270 - 20, 270 + 20) Local vel : Float = Rand(10, 70) / 10.0 particle.sprite = TParticle.bubble particle.positionX = Self.positionX particle.positionY = Self.positionY particle.velocityX = vel * Cos(angle) particle.velocityY = vel * Sin(angle) particle.scale = 0.03 * vel + 0.2 particle.lifeTime = Rand(30, 60) particles.AddLast(particle) Next End Method Method Update() Local toRemove : TList = New TList For Local particle : TParticle = EachIn particles particle.Update() If particle.lifeTime = 0 Then toRemove.AddLast(particle) Next For Local particle : TParticle = EachIn toRemove particles.remove(particle) Next End Method Method Render() For Local particle : TParticle = EachIn particles particle.Render() Next End Method Function Init() bubbleSound = LoadSound("animation/bubble.wav") bubbleChannel = bubbleSound.Cue() End Function End Type
Global background : TImage Global body : TImage Global mouth : TImage Global emitter : TEmitter
Function LoadFish() background : TImage = LoadImage("animation/background.png") body : TImage = LoadImage("animation/body.png") SetImageHandle body, 270, 404 mouth : TImage = LoadImage("animation/mouth.png") TParticle.Init() TEmitter.Init() emitter = New TEmitter End Function
Global bubbleActive : Int = 0 Function DrawFish() Local x1 : Int = circles[SINGLE_POINT, 0] Local y1 : Int = circles[SINGLE_POINT, 1] Local x2 : Int = circles[TOP_POINT, 0] Local y2 : Int = circles[TOP_POINT, 1] Local x3 : Int = circles[BOTTOM_POINT, 0] Local y3 : Int = circles[BOTTOM_POINT, 1]
Local dx : Float = x1 - x3 Local dy : Float = y1 - y3 Local bodyAngle : Float = 270 - ATan2(dx, dy) Local bodyScale : Float = (Sqr(dx * dx + dy * dy) / 350.0)
Local vecA : Int[2], vecB : Int[2] vecA[0] = x2 - x1 vecA[1] = y2 - y1 vecB[0] = x3 - x1 vecB[1] = y3 - y1 Local dotProduct : Int = vecA[0] * vecB[0] + vecA[1] * vecB[1] Local mouthAngle : Float = ACos(dotProduct / (Sqr(vecA[0]^2 + vecA[1]^2)*Sqr(vecB[0]^2 + vecB[1]^2))) If mouthAngle < 0.0 Then mouthAngle = 0.0 ElseIf mouthAngle > 45.0 Then mouthAngle = 45.0 EndIf Local mouthX : Float = circles[SINGLE_POINT, 0] + 440 * bodyScale * Cos(bodyAngle - 10) Local mouthY : Float = circles[SINGLE_POINT, 1] + 440 * bodyScale * Sin(bodyAngle - 10) If mouthAngle < 35.0 Then bubbleActive = bubbleActive - 1 If bubbleActive < 0 Then bubbleActive = 0 ElseIf bubbleActive = 0 Then bubbleActive = 5 emitter.positionX = mouthX emitter.positionY = mouthY emitter.Emit() EndIf emitter.Update() SetAlpha 1.0 SetColor 255, 255, 255 DrawImage source, 0, 0 DrawCircles()
SetAlpha 0.7 DrawImage background, 0, 0 Local sign : Float If x1 < x2 Or x1 < x2 Then sign = +1.0 Else sign = -1.0 EndIf SetTransform bodyAngle, bodyScale, sign * bodyScale DrawImage body, circles[SINGLE_POINT, 0], circles[SINGLE_POINT, 1] Local mouthScale : Float = bodyScale * (Tan(mouthAngle) + 0.2) SetScale mouthScale, mouthScale SetRotation bodyAngle DrawImage mouth, mouthX, mouthY SetTransform 0, 1, 1 SetHandle 0, 0 SetColor 255, 255, 255 SetAlpha 1.0 emitter.Render() End Function
|