package main
import (
"fmt"
"image"
"image/color"
"image/png"
"os"
)
const (
Size = 21
DataCodewords = 19
ECCCodewords = 7
MaxBytesByteMode = 17
ModeIndicatorByte = 0x4
)
var gfExp [512]int
var gfLog [256]int
func initGFTables() {
x := 1
for i := 0; i < 255; i++ {
gfExp[i] = x
gfLog[x] = i
x <<= 1
if x&0x100 != 0 {
x ^= 0x11d
}
}
for i := 255; i < 512; i++ {
gfExp[i] = gfExp[i-255]
}
gfLog[0] = -1
}
func gfMul(a, b int) int {
if a == 0 || b == 0 {
return 0
}
return gfExp[(gfLog[a]+gfLog[b])%255]
}
func rsGenerator(n int) []int {
g := []int{1}
for i := 0; i < n; i++ {
newG := make([]int, len(g)+1)
for j := range newG {
newG[j] = 0
}
for j := 0; j < len(g); j++ {
newG[j] ^= g[j]
newG[j+1] ^= gfMul(g[j], gfExp[i])
}
g = newG
}
return g
}
func rsComputeECC(data []int, eccLen int, g []int) []int {
poly := make([]int, len(data)+eccLen)
copy(poly, data)
for i := 0; i < len(data); i++ {
coef := poly[i]
if coef != 0 {
for j := 0; j < len(g); j++ {
poly[i+j] ^= gfMul(coef, g[j])
}
}
}
return poly[len(data):]
}
type bitBuffer struct {
bits []bool
}
func (bb *bitBuffer) appendBits(val int, length int) {
for i := length - 1; i >= 0; i-- {
bb.bits = append(bb.bits, ((val>>i)&1) == 1)
}
}
func encodeBytesToCodewords(data []byte) ([]int, error) {
if len(data) > MaxBytesByteMode {
return nil, fmt.Errorf("input too long for version 1-L: max %d bytes", MaxBytesByteMode)
}
var bb bitBuffer
bb.appendBits(ModeIndicatorByte, 4)
bb.appendBits(len(data), 8)
for _, b := range data {
bb.appendBits(int(b), 8)
}
remainingBits := DataCodewords*8 - len(bb.bits)
if remainingBits < 0 {
return nil, fmt.Errorf("data bits exceed capacity")
}
if remainingBits >= 4 {
bb.appendBits(0, 4)
remainingBits -= 4
} else {
bb.appendBits(0, remainingBits)
remainingBits = 0
}
if (len(bb.bits) % 8) != 0 {
toPad := 8 - (len(bb.bits) % 8)
bb.appendBits(0, toPad)
remainingBits -= toPad
}
padBytes := []int{0xEC, 0x11}
idx := 0
for (len(bb.bits) / 8) < DataCodewords {
bb.appendBits(padBytes[idx%2], 8)
idx++
}
codewords := make([]int, DataCodewords)
for i := 0; i < DataCodewords; i++ {
val := 0
for b := 0; b < 8; b++ {
if bb.bits[i*8+b] {
val |= 1 << (7 - b)
}
}
codewords[i] = val
}
return codewords, nil
}
func newMatrix() [][]int {
m := make([][]int, Size)
for i := range m {
m[i] = make([]int, Size)
for j := range m[i] {
m[i][j] = -1
}
}
return m
}
func placeFinder(m [][]int, r, c int) {
p := []string{
"1111111",
"1000001",
"1011101",
"1011101",
"1011101",
"1000001",
"1111111",
}
for i := 0; i < 7; i++ {
for j := 0; j < 7; j++ {
if p[i][j] == '1' {
m[r+i][c+j] = 1
} else {
m[r+i][c+j] = 0
}
}
}
for i := -1; i <= 7; i++ {
for j := -1; j <= 7; j++ {
rr := r + i
cc := c + j
if rr < 0 || rr >= Size || cc < 0 || cc >= Size {
continue
}
if (i == -1 || i == 7 || j == -1 || j == 7) && m[rr][cc] == -1 {
m[rr][cc] = 0
}
}
}
}
func placeTiming(m [][]int) {
for i := 8; i < Size-8; i++ {
if m[6][i] == -1 {
if i%2 == 0 {
m[6][i] = 1
} else {
m[6][i] = 0
}
}
if m[i][6] == -1 {
if i%2 == 0 {
m[i][6] = 1
} else {
m[i][6] = 0
}
}
}
}
func placeData(m [][]int, dataBits []bool) {
dirUp := true
row := Size - 1
col := Size - 1
bitIdx := 0
for col > 0 {
if col == 6 {
col--
}
for {
for c := col; c >= col-1; c-- {
if m[row][c] == -1 {
var bit bool
if bitIdx < len(dataBits) {
bit = dataBits[bitIdx]
} else {
bit = false
}
if bit {
m[row][c] = 1
} else {
m[row][c] = 0
}
bitIdx++
}
}
if dirUp {
row--
} else {
row++
}
if row < 0 || row >= Size {
break
}
}
dirUp = !dirUp
if row < 0 {
row = 0
} else {
row = Size - 1
}
col -= 2
}
}
func applyMask(m [][]int) {
for r := 0; r < Size; r++ {
for c := 0; c < Size; c++ {
if isFunctionModule(r, c) {
continue
}
if (r+c)%2 == 0 {
// flip
if m[r][c] == 0 {
m[r][c] = 1
} else if m[r][c] == 1 {
m[r][c] = 0
}
}
}
}
}
func isFunctionModule(r, c int) bool {
if (r >= 0 && r <= 8 && c >= 0 && c <= 8) ||
(r >= 0 && r <= 8 && c >= Size-9 && c <= Size-1) ||
(r >= Size-9 && r <= Size-1 && c >= 0 && c <= 8) {
return true
}
if r == 6 || c == 6 {
return true
}
formatPos := [][2]int{
{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8},
{7, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8}, {0, 8},
{Size - 1, 8}, {Size - 2, 8}, {Size - 3, 8}, {Size - 4, 8}, {Size - 5, 8},
{8, Size - 1}, {8, Size - 2}, {8, Size - 3}, {8, Size - 4}, {8, Size - 5},
}
for _, p := range formatPos {
if p[0] == r && p[1] == c {
return true
}
}
return false
}
var formatBits15 = []int{
1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0,
}
func placeFormatBits(m [][]int) {
_ = [][2]int{{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8}}
_ = [][2]int{{7, 8}, {6, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8}, {0, 8}}
_ = [][2]int{{Size - 1, 8}, {Size - 2, 8}, {Size - 3, 8}, {Size - 4, 8}, {Size - 5, 8}, {Size - 6, 8}, {Size - 7, 8}, {Size - 8, 8}}
_ = [][2]int{{8, Size - 1}, {8, Size - 2}, {8, Size - 3}, {8, Size - 4}, {8, Size - 5}, {8, Size - 6}, {8, Size - 7}, {8, Size - 8}}
pos := [][2]int{
{8, 0}, {8, 1}, {8, 2}, {8, 3}, {8, 4}, {8, 5}, {8, 7}, {8, 8},
{7, 8}, {6, 8}, {5, 8}, {4, 8}, {3, 8}, {2, 8}, {1, 8},
}
for i := 0; i < 15; i++ {
r := pos[i][0]
c := pos[i][1]
m[r][c] = formatBits15[i]
}
m[Size-1][8] = formatBits15[0]
m[Size-2][8] = formatBits15[1]
m[Size-3][8] = formatBits15[2]
m[Size-4][8] = formatBits15[3]
m[Size-5][8] = formatBits15[4]
m[Size-6][8] = formatBits15[5]
m[Size-7][8] = formatBits15[6]
m[8][Size-1] = formatBits15[7]
m[8][Size-2] = formatBits15[8]
m[8][Size-3] = formatBits15[9]
m[8][Size-4] = formatBits15[10]
m[8][Size-5] = formatBits15[11]
m[8][Size-6] = formatBits15[12]
m[8][Size-7] = formatBits15[13]
m[8][Size-8] = formatBits15[14]
}
func bitsFromCodewords(codewords []int, ecc []int) []bool {
total := append(codewords, ecc...)
bits := make([]bool, 0, len(total)*8)
for _, cw := range total {
for i := 7; i >= 0; i-- {
bits = append(bits, ((cw>>i)&1) == 1)
}
}
return bits
}
func renderPNG(m [][]int, scale int, filename string) error {
img := image.NewRGBA(image.Rect(0, 0, Size*scale, Size*scale))
white := color.RGBA{255, 255, 255, 255}
black := color.RGBA{0, 0, 0, 255}
for y := 0; y < Size*scale; y++ {
for x := 0; x < Size*scale; x++ {
img.Set(x, y, white)
}
}
for r := 0; r < Size; r++ {
for c := 0; c < Size; c++ {
val := m[r][c]
if val == 1 {
// draw black
for y := r * scale; y < (r+1)*scale; y++ {
for x := c * scale; x < (c+1)*scale; x++ {
img.Set(x, y, black)
}
}
}
}
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
return png.Encode(f, img)
}
func main() {
initGFTables()
payload := []byte("Hello from Ashok")
dataCW, err := encodeBytesToCodewords(payload)
if err != nil {
fmt.Println("encode error:", err)
return
}
gen := rsGenerator(ECCCodewords)
ecc := rsComputeECC(dataCW, ECCCodewords, gen)
mat := newMatrix()
placeFinder(mat, 0, 0)
placeFinder(mat, 0, Size-7)
placeFinder(mat, Size-7, 0)
placeTiming(mat)
mat[8][Size-8] = 1
dataBits := bitsFromCodewords(dataCW, ecc)
placeData(mat, dataBits)
applyMask(mat)
placeFormatBits(mat)
out := "qrcode.png"
if err := renderPNG(mat, 8, out); err != nil {
fmt.Println("render error:", err)
return
}
fmt.Println("Written", out)
}