Skip to content

Commit

Permalink
Merge pull request #12 from taga3s/fix/threshold
Browse files Browse the repository at this point in the history
fix: use otsu's method for default threshold
  • Loading branch information
taga3s committed Aug 18, 2024
2 parents 5cf11ae + 5ed4478 commit 1066bc7
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 30 deletions.
62 changes: 56 additions & 6 deletions internal/ascii_art/ascii_art.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package asciiart
import (
"image"
"image/color"
"math"
"math/rand"
"strings"
)
Expand All @@ -11,12 +12,7 @@ const (
validChars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)

func selectRandomly(chars string) string {
r := rand.Intn(len(chars))
return string(chars[r])
}

// Generate generates an ASCII art from an image.
// `Generate` generates an ASCII art from an image.
func Generate(dest image.Image, threshold int) string {
srcBounds := dest.Bounds()

Expand Down Expand Up @@ -44,3 +40,57 @@ func Generate(dest image.Image, threshold int) string {

return asciiArt.String()
}

func selectRandomly(chars string) string {
r := rand.Intn(len(chars))
return string(chars[r])
}

// `CalcOTSUThreshold` calculates the threshold value using the OTSU's method.
func CalcOTSUThreshold(dest image.Image, ySize, xSize int) int {
histogram := make([]int, 256) // 0 - 255 histogram

for y := 0; y < ySize; y++ {
for x := 0; x < xSize; x++ {
c := color.GrayModel.Convert(dest.At(x, y))
gray, _ := c.(color.Gray)
histogram[gray.Y]++
}
}

t := 0
max := 0.0

for i := 0; i < 256; i++ {
w1, w2 := 0, 0 // pixel number
sum1, sum2 := 0, 0 // total gray value
m1, m2 := 0.0, 0.0 // average gray value

for j := 0; j < i; j++ {
w1 += histogram[j]
sum1 += histogram[j] * j
}

for j := i; j < 256; j++ {
w2 += histogram[j]
sum2 += j * histogram[j]
}

if 0 < w1 {
m1 = float64(sum1) / float64(w1)
}

if 0 < w2 {
m2 = float64(sum2) / float64(w2)
}

tmp := float64(w1) * float64(w2) * math.Pow(m1-m2, 2)

if max < tmp {
max = tmp
t = i
}
}

return t
}
6 changes: 3 additions & 3 deletions internal/img/img.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"golang.org/x/image/draw"
)

// Load loads an image from a file.
// `Load` loads an image from a file.
func Load(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
Expand All @@ -25,7 +25,7 @@ func Load(path string) (image.Image, error) {
return img, nil
}

// Resize resizes an image to a given width and height.
// `Resize` resizes an image to a given width and height.
func Resize(img image.Image, magnification float64) *image.RGBA {
rect := img.Bounds()

Expand All @@ -48,7 +48,7 @@ func Resize(img image.Image, magnification float64) *image.RGBA {
return dest
}

// UnSync unsynchronizes an image file.
// `UnSync` unsynchronizes an image file.
func UnSync(dest image.Image) error {
tmp, err := os.Create("tmp.png")
if err != nil {
Expand Down
61 changes: 40 additions & 21 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,35 @@ import (
"github.com/urfave/cli/v2"
)

const (
DefaultMagnification = 1.0
)

type Inputs struct {
path string
threshold int
magnification float64
}

func main() {
inputs := Inputs{}

app := &cli.App{
Flags: []cli.Flag{
&cli.IntFlag{
Name: "threshold",
Aliases: []string{"t"},
Usage: "the threshold for ASCII Art Generation",
Value: 128,
Destination: &inputs.threshold,
Name: "threshold",
Aliases: []string{"t"},
Usage: "the threshold for ASCII Art Generation",
},
&cli.Float64Flag{
Name: "magnification",
Aliases: []string{"m"},
Usage: "the magnification factor for ASCII Art Generation",
Value: 1.0,
Destination: &inputs.magnification,
Name: "magnification",
Aliases: []string{"m"},
Usage: "the magnification factor for ASCII Art Generation",
},
},
Action: func(c *cli.Context) error {
if c.NArg() != 1 {
return fmt.Errorf("invalid number of arguments")
err := runApp(c)
if err != nil {
return err
}
inputs.path = c.Args().Get(0)

run(&inputs)

return nil
},
}
Expand All @@ -53,20 +48,44 @@ func main() {
}
}

func run(inputs *Inputs) {
func runApp(c *cli.Context) error {
inputs := Inputs{}
if c.NArg() != 1 {
return fmt.Errorf("invalid number of arguments")
}
inputs.path = c.Args().Get(0)

// Load the image
srcImg, err := img.Load(inputs.path)
if err != nil {
log.Fatalf("Error: %v", err)
return err
}

if c.IsSet("magnification") {
inputs.magnification = c.Float64("magnification")
} else {
inputs.magnification = DefaultMagnification
}

// Resize the image
resizedImg := img.Resize(srcImg, inputs.magnification)

if c.IsSet("threshold") {
inputs.threshold = c.Int("threshold")
} else {
inputs.threshold = asciiArt.CalcOTSUThreshold(resizedImg, resizedImg.Bounds().Dy(), resizedImg.Bounds().Dx())
}

// Generate the ASCII Art
output := asciiArt.Generate(resizedImg, inputs.threshold)

// Print the ASCII Art
fmt.Print(output)

err = img.UnSync(resizedImg)
if err != nil {
log.Fatalf("Error: %v", err)
return err
}

return nil
}

0 comments on commit 1066bc7

Please sign in to comment.