Skip to content

Instantly share code, notes, and snippets.

@lachlanhurst
Created November 1, 2015 09:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lachlanhurst/5c88d209f98a30af36a9 to your computer and use it in GitHub Desktop.
Save lachlanhurst/5c88d209f98a30af36a9 to your computer and use it in GitHub Desktop.
Swift playground for identifying the edge around a 2D array of boolean values
// The MIT License (MIT)
// Copyright (c) 2015 Lachlan Hurst
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
import XCPlayground
struct Point2D {
var x:Float
var y:Float
}
enum MovementDirection:Int {
case up = 1
case down = 2
case left = 3
case right = 4
var displacement:(x:Int, y:Int) {
get {
switch self{
case .up:
return (x:0, y:1)
case .down:
return (x:0, y:-1)
case .left:
return (x:-1, y:0)
case .right:
return (x:1, y:0)
}
}
}
}
enum MarchingSquarePossibility:UInt8 {
case tl = 0b00001000
case tr = 0b00000100
case br = 0b00000010
case bl = 0b00000001
static func fromBools(tlBool:Bool, trBool:Bool, brBool:Bool, blBool:Bool) -> UInt8 {
let tlVal:UInt8 = tlBool ? tl.rawValue : 0
let trVal:UInt8 = trBool ? tr.rawValue : 0
let brVal:UInt8 = brBool ? br.rawValue : 0
let blVal:UInt8 = blBool ? bl.rawValue : 0
let res = tlVal | trVal | brVal | blVal
return res
}
static func asString(val:UInt8) -> String {
let tlS = val & tl.rawValue != 0 ? "tl " : " "
let trS = val & tr.rawValue != 0 ? "tr " : " "
let brS = val & br.rawValue != 0 ? "br " : " "
let blS = val & bl.rawValue != 0 ? "bl " : " "
return tlS + trS + brS + blS
}
}
class MarchingSquaresBool {
var data:[[Bool]]
var rows:Int!
var cols:Int!
var startIndexCol:Int? = nil
var startIndexRow:Int? = nil
init(data:[[Bool]]) {
self.data = data
rows = data.count - 1;
cols = data[0].count - 1;
}
func findStart() {
for(var rowIndex = 0; rowIndex < rows; rowIndex++){
for(var colIndex = 0; colIndex < cols; colIndex++){
if data[rowIndex][colIndex] {
startIndexCol = colIndex
startIndexRow = rowIndex
return
}
}
}
}
func computeContour() -> [Point2D] {
findStart()
guard let startIndexCol = self.startIndexCol else { return [] }
guard let startIndexRow = self.startIndexRow else { return [] }
//print("start ", startIndexCol, ", ", startIndexRow)
var res:[Point2D] = []
var posCol = startIndexCol
var posRow = startIndexRow
var first = true
var lastMovement:MovementDirection? = nil
while !(posCol == startIndexCol && posRow == startIndexRow) || first {
let tl = data[posRow][posCol-1]
let tr = data[posRow][posCol]
let br = data[posRow-1][posCol]
let bl = data[posRow-1][posCol-1]
let val = MarchingSquarePossibility.fromBools(tl, trBool: tr, brBool: br, blBool: bl)
//print("x= ", posCol, " y= ", posRow, " :",MarchingSquarePossibility.asString(val))
let movement:MovementDirection
switch val {
case MarchingSquarePossibility.tr.rawValue:
movement = .right
case MarchingSquarePossibility.tl.rawValue:
movement = .up
case MarchingSquarePossibility.tl.rawValue | MarchingSquarePossibility.bl.rawValue:
movement = .up
case MarchingSquarePossibility.tl.rawValue | MarchingSquarePossibility.bl.rawValue | MarchingSquarePossibility.tr.rawValue:
movement = .right
case MarchingSquarePossibility.tl.rawValue | MarchingSquarePossibility.tr.rawValue:
movement = .right
case MarchingSquarePossibility.bl.rawValue:
movement = .left
case MarchingSquarePossibility.bl.rawValue | MarchingSquarePossibility.br.rawValue:
movement = .left
case MarchingSquarePossibility.br.rawValue:
movement = .down
case MarchingSquarePossibility.br.rawValue | MarchingSquarePossibility.tr.rawValue:
movement = .down
case MarchingSquarePossibility.tl.rawValue | MarchingSquarePossibility.tr.rawValue | MarchingSquarePossibility.br.rawValue:
movement = .down
case MarchingSquarePossibility.br.rawValue | MarchingSquarePossibility.bl.rawValue | MarchingSquarePossibility.tl.rawValue:
movement = .up
case MarchingSquarePossibility.tr.rawValue | MarchingSquarePossibility.br.rawValue | MarchingSquarePossibility.bl.rawValue:
movement = .left
case MarchingSquarePossibility.tl.rawValue | MarchingSquarePossibility.br.rawValue:
if lastMovement == MovementDirection.right {
movement = .down
} else if lastMovement == MovementDirection.left {
movement = .up
} else {
print("bad")
return []
}
case MarchingSquarePossibility.tr.rawValue | MarchingSquarePossibility.bl.rawValue:
if lastMovement == MovementDirection.down {
movement = .left
} else if lastMovement == MovementDirection.up {
movement = .right
} else {
print("bad")
return []
}
default:
print("hi")
return []
}
// tr br bl
if movement != lastMovement {
res.append(Point2D(x:Float(posCol),y:Float(posRow)))
}
posCol += movement.displacement.x
posRow += movement.displacement.y
lastMovement = movement
first = false
}
return res
}
}
let data = [
[false,false,false,false,false, false,false, false, false],
[false,true, false,true, true, true, true, false, false],
[false,true, false,true, false, false,true, false, false],
[false,true ,true ,true, false, true, false, true, false],
[false,false,false,false,false, false,false, false, false],
[false,false,false,false,false, false,false, false, false]
]
let ms = MarchingSquaresBool(data: data)
let contour = ms.computeContour()
//print("")
//print("")
//contour.forEach {print("x = ", $0.x, " y = ", $0.y) }
let size = CGSizeMake(200, 200)
UIGraphicsBeginImageContext(size)
let ctx = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(ctx, UIColor.whiteColor().CGColor)
CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height))
CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextSetFillColorWithColor(ctx, UIColor.greenColor().CGColor)
var first = true
for pt in contour {
if first {
CGContextMoveToPoint(ctx, CGFloat(pt.x) * 20, CGFloat(pt.y) * 20)
first = false
}
else {
CGContextAddLineToPoint(ctx, CGFloat(pt.x) * 20, CGFloat(pt.y) * 20)
}
//
}
if let first = contour.first {
CGContextAddLineToPoint(ctx,CGFloat(first.x) * 20, CGFloat(first.y) * 20)
}
CGContextStrokePath(ctx)
for pt in contour {
CGContextFillEllipseInRect(ctx, CGRectMake(CGFloat(pt.x) * 20, CGFloat(pt.y) * 20, 4, 4))
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment