summaryrefslogtreecommitdiff
path: root/cleanup/main.go
blob: e0b66cee069b922723ce3a5e8e6684737509f3eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package main

// TODO: add minimum size variable (default ~30%?)
// TODO: add tests
// TODO: make into a small library
// TODO: have the integral image specific stuff done by interface functions

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	_ "image/jpeg"
	"image/png"
	"log"
	"os"

	"rescribe.xyz/go.git/binarize"
)

type windowslice struct {
	topleft     uint64
	topright    uint64
	bottomleft  uint64
	bottomright uint64
}

func getwindowslice(i [][]uint64, x int, size int) windowslice {
	maxy := len(i) - 1
	maxx := x + size
	if maxx > len(i[0])-1 {
		maxx = len(i[0]) - 1
	}

	return windowslice{i[0][x], i[0][maxx], i[maxy][x], i[maxy][maxx]}
}

// checkwindow checks the window from x to see whether more than
// thresh proportion of the pixels are white, if so it returns true.
func checkwindow(integral [][]uint64, x int, size int, thresh float64) bool {
	height := len(integral)
	window := getwindowslice(integral, x, size)
	// divide by 255 as each on pixel has the value of 255
	sum := (window.bottomright + window.topleft - window.topright - window.bottomleft) / 255
	area := size * height
	proportion := float64(area)/float64(sum) - 1
	return proportion <= thresh
}

// returns the proportion of the given window that is black pixels
func proportion(integral [][]uint64, x int, size int) float64 {
	height := len(integral)
	window := getwindowslice(integral, x, size)
	// divide by 255 as each on pixel has the value of 255
	sum := (window.bottomright + window.topleft - window.topright - window.bottomleft) / 255
	area := size * height
	return float64(area)/float64(sum) - 1
}

// wipesides fills the sections of image not within the boundaries
// of lowedge and highedge with white
func wipesides(img *image.Gray, lowedge int, highedge int) *image.Gray {
	b := img.Bounds()
	new := image.NewGray(b)

	// set left edge white
	for x := b.Min.X; x < lowedge; x++ {
		for y := b.Min.Y; y < b.Max.Y; y++ {
			new.SetGray(x, y, color.Gray{255})
		}
	}
	// copy middle
	for x := lowedge; x < highedge; x++ {
		for y := b.Min.Y; y < b.Max.Y; y++ {
			new.SetGray(x, y, img.GrayAt(x, y))
		}
	}
	// set right edge white
	for x := highedge; x < b.Max.X; x++ {
		for y := b.Min.Y; y < b.Max.Y; y++ {
			new.SetGray(x, y, color.Gray{255})
		}
	}

	return new
}

// findbestedge goes through every vertical line from x to x+w to
// find the one with the lowest proportion of black pixels.
func findbestedge(integral [][]uint64, x int, w int) int {
	var bestx int
	var best float64

	if w == 1 {
		return x
	}

	right := x + w
	for ; x < right; x++ {
		prop := proportion(integral, x, 1)
		if prop > best {
			best = prop
			bestx = x
		}
	}

	return bestx
}

// findedges finds the edges of the main content, by moving a window of wsize
// from the middle of the image to the left and right, stopping when it reaches
// a point at which there is a lower proportion of black pixels than thresh.
func findedges(integral [][]uint64, wsize int, thresh float64) (int, int) {
	maxx := len(integral[0]) - 1
	var lowedge, highedge int = 0, maxx

	for x := maxx / 2; x < maxx-wsize; x++ {
		if checkwindow(integral, x, wsize, thresh) {
			highedge = findbestedge(integral, x, wsize)
			break
		}
	}

	for x := maxx / 2; x > 0; x-- {
		if checkwindow(integral, x, wsize, thresh) {
			lowedge = findbestedge(integral, x, wsize)
			break
		}
	}

	return lowedge, highedge
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: cleanup [-t thresh] [-w winsize] inimg outimg\n")
		flag.PrintDefaults()
	}
	wsize := flag.Int("w", 5, "Window size for mask finding algorithm.")
	thresh := flag.Float64("t", 0.05, "Threshold for the proportion of black pixels below which a window is determined to be the edge.")
	flag.Parse()
	if flag.NArg() < 2 {
		flag.Usage()
		os.Exit(1)
	}

	f, err := os.Open(flag.Arg(0))
	defer f.Close()
	if err != nil {
		log.Fatalf("Could not open file %s: %v\n", flag.Arg(0), err)
	}
	img, _, err := image.Decode(f)
	if err != nil {
		log.Fatalf("Could not decode image: %v\n", err)
	}
	b := img.Bounds()
	gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))
	draw.Draw(gray, b, img, b.Min, draw.Src)

	integral := binarize.Integralimg(gray)

	lowedge, highedge := findedges(integral, *wsize, *thresh)

	clean := wipesides(gray, lowedge, highedge)

	f, err = os.Create(flag.Arg(1))
	if err != nil {
		log.Fatalf("Could not create file %s: %v\n", flag.Arg(1), err)
	}
	defer f.Close()
	err = png.Encode(f, clean)
	if err != nil {
		log.Fatalf("Could not encode image: %v\n", err)
	}
}