Zhang-Suen thinning algorithm

class ZhangSuen(str, black="1") {
  const NEIGHBOURS = [[-1,0],[-1,1],[0,1],[1,1],[1,0],[1,-1],[0,-1],[-1,-1]]  # 8 neighbors
  const CIRCULARS = (NEIGHBOURS + [NEIGHBOURS.first])                         # P2, ... P9, P2

  has r = 0
  has image = [[]]

  method init {
    var s1 = str.lines.map{|line| line.chars.map{|c| c==black ? 1 : 0 }}
    var s2 = s1.len.of { s1[0].len.of(0) }
    var xr = range(1, s1.end-1)
    var yr = range(1, s1[0].end-1)
    do {
        r = 0
        xr.each{|x| yr.each{|y| s2[x][y] = (s1[x][y] - self.zs(s1,x,y,1)) }}  # Step 1
        xr.each{|x| yr.each{|y| s1[x][y] = (s2[x][y] - self.zs(s2,x,y,0)) }}  # Step 2
    } while !r.is_zero
    image = s1
  }

  method zs(ng,x,y,g) {
       (ng[x][y] == 0)                                   ->
    || (ng[x-1][y] + ng[x][y+1] + ng[x+g][y+g - 1] == 3) ->
    || (ng[x+g - 1][y+g] + ng[x+1][y] + ng[x][y-1] == 3) ->
    && return 0

    var bp1 = NEIGHBOURS.map {|p| ng[x+p[0]][y+p[1]] }.sum  # B(P1)
    return 0 if ((bp1 < 2) || (6 < bp1))

    var ap1 = 0
    CIRCULARS.map {|p| ng[x+p[0]][y+p[1]] }.each_cons(2, {|a,b|
        ++ap1 if (a < b)                                    # A(P1)
    })

    return 0 if (ap1 != 1)
    r = 1
  }

  method display {
    image.each{|row| say row.map{|col| col ? '#' : ' ' }.join }
  }
}

var text = <<EOS
00000000000000000000000000000000
01111111110000000111111110000000
01110001111000001111001111000000
01110000111000001110000111000000
01110001111000001110000000000000
01111111110000001110000000000000
01110111100000001110000111000000
01110011110011101111001111011100
01110001111011100111111110011100
00000000000000000000000000000000
EOS

ZhangSuen.new(text, black: "1").display

Output:


  #######         ######
  #     #        ##
  #      #       #
  #     #        #
  ##### #        #
       ##        #
        #    #   ##    ##   #
         #         ####

Last updated