1package qr
2
3import (
4	"image"
5	"image/color"
6	"math"
7
8	"github.com/boombuler/barcode"
9	"github.com/boombuler/barcode/utils"
10)
11
12type qrcode struct {
13	dimension int
14	data      *utils.BitList
15	content   string
16}
17
18func (qr *qrcode) Content() string {
19	return qr.content
20}
21
22func (qr *qrcode) Metadata() barcode.Metadata {
23	return barcode.Metadata{barcode.TypeQR, 2}
24}
25
26func (qr *qrcode) ColorModel() color.Model {
27	return color.Gray16Model
28}
29
30func (qr *qrcode) Bounds() image.Rectangle {
31	return image.Rect(0, 0, qr.dimension, qr.dimension)
32}
33
34func (qr *qrcode) At(x, y int) color.Color {
35	if qr.Get(x, y) {
36		return color.Black
37	}
38	return color.White
39}
40
41func (qr *qrcode) Get(x, y int) bool {
42	return qr.data.GetBit(x*qr.dimension + y)
43}
44
45func (qr *qrcode) Set(x, y int, val bool) {
46	qr.data.SetBit(x*qr.dimension+y, val)
47}
48
49func (qr *qrcode) calcPenalty() uint {
50	return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
51}
52
53func (qr *qrcode) calcPenaltyRule1() uint {
54	var result uint
55	for x := 0; x < qr.dimension; x++ {
56		checkForX := false
57		var cntX uint
58		checkForY := false
59		var cntY uint
60
61		for y := 0; y < qr.dimension; y++ {
62			if qr.Get(x, y) == checkForX {
63				cntX++
64			} else {
65				checkForX = !checkForX
66				if cntX >= 5 {
67					result += cntX - 2
68				}
69				cntX = 1
70			}
71
72			if qr.Get(y, x) == checkForY {
73				cntY++
74			} else {
75				checkForY = !checkForY
76				if cntY >= 5 {
77					result += cntY - 2
78				}
79				cntY = 1
80			}
81		}
82
83		if cntX >= 5 {
84			result += cntX - 2
85		}
86		if cntY >= 5 {
87			result += cntY - 2
88		}
89	}
90
91	return result
92}
93
94func (qr *qrcode) calcPenaltyRule2() uint {
95	var result uint
96	for x := 0; x < qr.dimension-1; x++ {
97		for y := 0; y < qr.dimension-1; y++ {
98			check := qr.Get(x, y)
99			if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
100				result += 3
101			}
102		}
103	}
104	return result
105}
106
107func (qr *qrcode) calcPenaltyRule3() uint {
108	pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
109	pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
110
111	var result uint
112	for x := 0; x <= qr.dimension-len(pattern1); x++ {
113		for y := 0; y < qr.dimension; y++ {
114			pattern1XFound := true
115			pattern2XFound := true
116			pattern1YFound := true
117			pattern2YFound := true
118
119			for i := 0; i < len(pattern1); i++ {
120				iv := qr.Get(x+i, y)
121				if iv != pattern1[i] {
122					pattern1XFound = false
123				}
124				if iv != pattern2[i] {
125					pattern2XFound = false
126				}
127				iv = qr.Get(y, x+i)
128				if iv != pattern1[i] {
129					pattern1YFound = false
130				}
131				if iv != pattern2[i] {
132					pattern2YFound = false
133				}
134			}
135			if pattern1XFound || pattern2XFound {
136				result += 40
137			}
138			if pattern1YFound || pattern2YFound {
139				result += 40
140			}
141		}
142	}
143
144	return result
145}
146
147func (qr *qrcode) calcPenaltyRule4() uint {
148	totalNum := qr.data.Len()
149	trueCnt := 0
150	for i := 0; i < totalNum; i++ {
151		if qr.data.GetBit(i) {
152			trueCnt++
153		}
154	}
155	percDark := float64(trueCnt) * 100 / float64(totalNum)
156	floor := math.Abs(math.Floor(percDark/5) - 10)
157	ceil := math.Abs(math.Ceil(percDark/5) - 10)
158	return uint(math.Min(floor, ceil) * 10)
159}
160
161func newBarcode(dim int) *qrcode {
162	res := new(qrcode)
163	res.dimension = dim
164	res.data = utils.NewBitList(dim * dim)
165	return res
166}
167