Skip to content

Commit

Permalink
Improve bin2art, change buffer size and less otsu time.
Browse files Browse the repository at this point in the history
  • Loading branch information
msqtt committed May 7, 2023
1 parent 8d8da1c commit e1428fa
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 62 deletions.
33 changes: 17 additions & 16 deletions bobibo.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,18 @@ func BoBiBo(ima io.Reader, isGif, isInverse bool, opts ...Option) (<-chan Art, e
}
}

inStream := make(chan img.Img)

mix := u.Multiply(img.ArtotBin(params.Inverse),
u.Multiply(img.BinotImg(params.Threshold),
u.Multiply(img.TurnGray,
img.Resize(params.Scale),
)))
img.ResizeAndGray(params.Scale),
))

outStream := mix(inStream)
delays, err := putStream(inStream, params)
wrap := wrapOut(delays)
inStream, delays, err := analyzeImage(params)
if err != nil {
return nil, err
}

outStream := mix(inStream)
wrap := wrapOut(delays)
return wrap(outStream), nil
}

Expand All @@ -82,36 +80,39 @@ var wrapOut = func(delays []int) func(<-chan []string) <-chan Art {
for o := range out {
if flag {
wrapOut <- Art{Content: o, Delay: delays[cnt]}
cnt++
} else {
wrapOut <- Art{Content: o, Delay: 0}
}
cnt++
}
})
}

func putStream(in chan<- img.Img, params *Params) ([]int, error) {
func analyzeImage(params *Params) (<-chan img.Img, []int, error) {
var delays []int
var inChan <-chan img.Img
if params.Gif {
p, dls, err := img.LoadAGif(params.Image)
if err != nil {
return nil, err
return nil, nil, err
}
delays = dls
go inStream(in, p...)
inChan = newInStream(p...)
} else {
i, err := img.LoadAImage(params.Image)
if err != nil {
return nil, err
return nil, nil, err
}
go inStream(in, i)
inChan = newInStream(i)
}
return delays, nil
return inChan, delays, nil
}

func inStream[T img.Img](in chan<- img.Img, ims ...T) {
func newInStream[T img.Img](ims ...T) <-chan img.Img {
in := make(chan img.Img, len(ims))
defer close(in)
for _, v := range ims {
in <- v
}
return in
}
Binary file added bobibo.test
Binary file not shown.
89 changes: 44 additions & 45 deletions img/img.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package img

import (
"image"
"image/draw"
"image/gif"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"math"
"strings"

u "github.com/orzation/bobibo/util"
xdraw "golang.org/x/image/draw"
Expand Down Expand Up @@ -46,85 +46,82 @@ func LoadAGif(f io.Reader) ([]Pale, []int, error) {

// resizing the image with scale value, it won't change the ratio.
// return a stream chan function.
var Resize = func(scale float64) func(<-chan Img) <-chan Img {
return u.GenChanFunc(func(in <-chan Img, out chan<- Img) {
var ResizeAndGray = func(scale float64) func(<-chan Img) <-chan *image.Gray {
return u.GenChanFunc(func(in <-chan Img, out chan<- *image.Gray) {
for i := range in {
dx := int(math.Floor(scale * float64(i.Bounds().Dx())))
dy := int(math.Floor(scale * float64(i.Bounds().Dy())))
dst := image.NewRGBA(image.Rect(0, 0, dx, dy))
dst := image.NewGray(image.Rect(0, 0, dx, dy))
xdraw.NearestNeighbor.Scale(dst, dst.Rect, i, i.Bounds(), xdraw.Over, nil)
out <- dst
}
})
}

// turning gray.
var TurnGray = u.GenChanFunc(func(in <-chan Img, out chan<- Img) {
for i := range in {
dx, dy := i.Bounds().Dx(), i.Bounds().Dy()
dst := image.NewGray(image.Rect(0, 0, dx, dy))
draw.Draw(dst, dst.Bounds(), i, i.Bounds().Min, draw.Src)
out <- dst
}
})

// turning image to 2d binary matrix.
// use threshold to adjust the binarization.
var BinotImg = func(threshold int) func(<-chan Img) <-chan [][]bool {
return u.GenChanFunc(func(in <-chan Img, out chan<- [][]bool) {
var BinotImg = func(threshold int) func(<-chan *image.Gray) <-chan [][]bool {
return u.GenChanFunc(func(in <-chan *image.Gray, out chan<- [][]bool) {
for im := range in {
out <- img2bin(im, threshold)
out <- img2bin(im, &threshold)
}
})
}

func img2bin(im Img, th int) [][]bool {
if th < 0 || th > 255 {
th = int(otsu(im))
func img2bin(im *image.Gray, th *int) [][]bool {
if *th < 0 || *th > 255 {
*th = int(otsu(im))
}
dx, dy := im.Bounds().Dx(), im.Bounds().Dy()
reB := make([][]bool, dy)
for i := range reB {
reB[i] = make([]bool, dx)
for j := range reB[i] {
r, _, _, _ := im.At(j, i).RGBA()
reB[i][j] = uint8(r>>8) >= uint8(th)
reB[i][j] = uint8(r>>8) >= uint8(*th)
}
}
return reB
}

// return the best threshold to binarize.
func otsu(im Img) uint8 {
var threshold uint8 = 0
func otsu(im *image.Gray) uint8 {
var threshold int = 0
const grayScale = 256
var u float32
var w0, u0 float32

dx, dy := im.Bounds().Dx(), im.Bounds().Dy()
grayPro := make([]float32, grayScale)
pixelSum := dx * dy
hist := make([]float32, grayScale)
sumPixel := dx * dy

for i := 0; i < dy; i++ {
for j := 0; j < dx; j++ {
r, _, _, _ := im.At(j, i).RGBA()
grayPro[uint8(r>>8)]++
hist[uint8(r>>8)]++
}
}
for i := 0; i < grayScale; i++ {
grayPro[i] *= 1.0 / float32(pixelSum)
u += float32(i) * grayPro[i]

for i := range [grayScale]struct{}{} {
hist[i] *= 1.0 / float32(sumPixel)
u += float32(i) * hist[i]
}
var w1, u1, gmax float32
for i := 0; i < grayScale; i++ {
w1 += grayPro[i]
u1 += float32(i) * grayPro[i]

tmp := u1 - u*w1
sigma := tmp * tmp / (w1 * (1 - w1))
if sigma >= gmax {
threshold = uint8(i)
gmax = sigma
var sigma float32
for t := range [grayScale]struct{}{} {
w0 += hist[t]
u0 += float32(t) * hist[t]
if w0 == 0 || 1-w0 == 0 {
continue
}

tmp := u0 - u*w0
tmp = tmp * tmp / (w0 * (1 - w0))
if tmp >= sigma {
sigma = tmp
threshold = t
}
}
return threshold
return uint8(threshold)
}

// turning 2d binary matrix to string array.
Expand All @@ -139,16 +136,18 @@ var ArtotBin = func(w bool) func(<-chan [][]bool) <-chan []string {

func bin2art(bin [][]bool, isWhite bool) []string {
dy, dx := len(bin)/4, len(bin[0])/2
reStr := make([]string, dy)
bufStr := make([]strings.Builder, dy)
resStr := make([]string, dy)
for i := 0; i < dy; i++ {
for j := 0; j < dx; j++ {
reStr[i] += cell(i, j, bin, isWhite)
bufStr[i].WriteRune(cell(i, j, bin, isWhite))
}
resStr[i] = bufStr[i].String()
}
return reStr
return resStr
}

func cell(y, x int, bin [][]bool, isWhite bool) string {
func cell(y, x int, bin [][]bool, isWhite bool) rune {
var reByte uint8 = 0
for i := 0; i < 4; i++ {
for j := 0; j < 2; j++ {
Expand All @@ -163,5 +162,5 @@ func cell(y, x int, bin [][]bool, isWhite bool) string {
if isWhite {
reByte = ^reByte
}
return string(brailleMap[reByte])
return brailleMap[reByte]
}
2 changes: 1 addition & 1 deletion util/fp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package util
// to make a stream chan function
func GenChanFunc[T any, E any](logic func(in <-chan T, out chan<- E)) func(<-chan T) <-chan E {
return func(inChan <-chan T) <-chan E {
outChan := make(chan E)
outChan := make(chan E, len(inChan))
go func() {
defer close(outChan)
logic(inChan, outChan)
Expand Down

0 comments on commit e1428fa

Please sign in to comment.