2020-07-22 04:53:06 -07:00
|
|
|
package qr
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2020-07-30 08:09:13 -07:00
|
|
|
"image"
|
2020-09-29 08:16:20 -07:00
|
|
|
"image/color"
|
|
|
|
"image/draw"
|
|
|
|
"image/gif"
|
2020-07-22 05:56:31 -07:00
|
|
|
"log"
|
2020-09-29 08:16:20 -07:00
|
|
|
"os"
|
2020-07-22 04:53:06 -07:00
|
|
|
|
2020-09-02 09:02:44 -07:00
|
|
|
"gocv.io/x/gocv"
|
|
|
|
|
2020-07-30 04:23:09 -07:00
|
|
|
encoder "github.com/skip2/go-qrcode"
|
|
|
|
|
2020-07-22 04:53:06 -07:00
|
|
|
"github.com/makiuchi-d/gozxing"
|
|
|
|
"github.com/makiuchi-d/gozxing/qrcode"
|
|
|
|
)
|
|
|
|
|
2020-09-29 08:16:20 -07:00
|
|
|
var palette = color.Palette{
|
|
|
|
image.Transparent,
|
|
|
|
image.Black,
|
|
|
|
image.White,
|
|
|
|
color.RGBA{G: 255, A: 255},
|
|
|
|
color.RGBA{G: 100, A: 255},
|
|
|
|
}
|
2020-07-22 04:53:06 -07:00
|
|
|
|
2020-07-30 04:23:09 -07:00
|
|
|
type Processor interface {
|
2020-07-30 04:27:47 -07:00
|
|
|
ReadQR() ([]byte, error)
|
2020-07-30 04:23:09 -07:00
|
|
|
WriteQR(path string, data []byte) error
|
2020-09-29 08:16:20 -07:00
|
|
|
SetDelay(delay int)
|
|
|
|
SetChunkSize(chunkSize int)
|
2020-07-30 04:23:09 -07:00
|
|
|
}
|
|
|
|
|
2020-09-29 08:16:20 -07:00
|
|
|
type CameraProcessor struct {
|
|
|
|
gifFramesDelay int
|
|
|
|
chunkSize int
|
|
|
|
}
|
2020-07-30 04:23:09 -07:00
|
|
|
|
|
|
|
func NewCameraProcessor() *CameraProcessor {
|
|
|
|
return &CameraProcessor{}
|
|
|
|
}
|
|
|
|
|
2020-09-29 08:16:20 -07:00
|
|
|
func (p *CameraProcessor) SetDelay(delay int) {
|
|
|
|
p.gifFramesDelay = delay
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *CameraProcessor) SetChunkSize(chunkSize int) {
|
|
|
|
p.chunkSize = chunkSize
|
|
|
|
}
|
|
|
|
|
2020-07-30 04:27:47 -07:00
|
|
|
func (p *CameraProcessor) ReadQR() ([]byte, error) {
|
2020-07-22 04:53:06 -07:00
|
|
|
webcam, err := gocv.OpenVideoCapture(0)
|
|
|
|
if err != nil {
|
2020-07-29 06:20:39 -07:00
|
|
|
return nil, fmt.Errorf("failed to OpenVideoCapture: %w", err)
|
2020-07-22 04:53:06 -07:00
|
|
|
}
|
2020-09-30 02:45:24 -07:00
|
|
|
window := gocv.NewWindow("Please, show a gif with QR codes")
|
2020-07-22 04:53:06 -07:00
|
|
|
|
2020-07-22 05:56:31 -07:00
|
|
|
defer func() {
|
|
|
|
if err := webcam.Close(); err != nil {
|
|
|
|
log.Fatalf("failed to close camera: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
defer func() {
|
|
|
|
if err := window.Close(); err != nil {
|
|
|
|
log.Fatalf("failed to close camera window: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
2020-07-22 04:53:06 -07:00
|
|
|
|
|
|
|
img := gocv.NewMat()
|
2020-09-10 07:31:53 -07:00
|
|
|
defer img.Close()
|
|
|
|
|
2020-09-29 08:16:20 -07:00
|
|
|
chunks := make([]*chunk, 0)
|
|
|
|
decodedChunksCount := uint(0)
|
2020-09-28 01:01:40 -07:00
|
|
|
// detects and scans QR-cods from camera until we scan successfully
|
2020-07-22 04:53:06 -07:00
|
|
|
for {
|
2020-09-11 06:05:28 -07:00
|
|
|
webcam.Read(&img)
|
|
|
|
window.IMShow(img)
|
|
|
|
window.WaitKey(1)
|
2020-07-22 04:53:06 -07:00
|
|
|
|
2020-09-11 06:05:28 -07:00
|
|
|
imgObject, err := img.ToImage()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get image object: %w", err)
|
|
|
|
}
|
|
|
|
data, err := ReadDataFromQR(imgObject)
|
|
|
|
if err != nil {
|
2020-09-17 06:35:47 -07:00
|
|
|
continue
|
2020-09-11 06:05:28 -07:00
|
|
|
}
|
2020-09-29 08:16:20 -07:00
|
|
|
decodedChunk, err := decodeChunk(data)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if cap(chunks) == 0 {
|
|
|
|
chunks = make([]*chunk, decodedChunk.Total)
|
|
|
|
}
|
|
|
|
if chunks[decodedChunk.Index] != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
chunks[decodedChunk.Index] = decodedChunk
|
|
|
|
decodedChunksCount++
|
2020-09-30 02:45:24 -07:00
|
|
|
window.SetWindowTitle(fmt.Sprintf("Read %d/%d chunks", decodedChunksCount, decodedChunk.Total))
|
2020-09-29 08:16:20 -07:00
|
|
|
if decodedChunksCount == decodedChunk.Total {
|
|
|
|
break
|
|
|
|
}
|
2020-07-22 04:53:06 -07:00
|
|
|
}
|
2020-09-29 08:16:20 -07:00
|
|
|
data := make([]byte, 0)
|
|
|
|
for _, c := range chunks {
|
|
|
|
data = append(data, c.Data...)
|
|
|
|
}
|
|
|
|
return data, nil
|
2020-07-22 04:53:06 -07:00
|
|
|
}
|
2020-07-30 04:23:09 -07:00
|
|
|
|
|
|
|
func (p *CameraProcessor) WriteQR(path string, data []byte) error {
|
2020-09-29 08:16:20 -07:00
|
|
|
chunks, err := DataToChunks(data, p.chunkSize)
|
2020-07-30 04:23:09 -07:00
|
|
|
if err != nil {
|
2020-09-29 08:16:20 -07:00
|
|
|
return fmt.Errorf("failed to divide data on chunks: %w", err)
|
2020-07-30 04:23:09 -07:00
|
|
|
}
|
2020-09-29 08:16:20 -07:00
|
|
|
outGif := &gif.GIF{}
|
|
|
|
for _, c := range chunks {
|
|
|
|
code, err := encoder.New(string(c), encoder.Medium)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create a QR code: %w", err)
|
|
|
|
}
|
|
|
|
frame := code.Image(512)
|
|
|
|
bounds := frame.Bounds()
|
|
|
|
palettedImage := image.NewPaletted(bounds, palette)
|
|
|
|
draw.Draw(palettedImage, palettedImage.Rect, frame, bounds.Min, draw.Src)
|
2020-07-30 04:23:09 -07:00
|
|
|
|
2020-09-29 08:16:20 -07:00
|
|
|
outGif.Image = append(outGif.Image, palettedImage)
|
|
|
|
outGif.Delay = append(outGif.Delay, p.gifFramesDelay)
|
|
|
|
}
|
|
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to open file: %w", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if err := gif.EncodeAll(f, outGif); err != nil {
|
|
|
|
return fmt.Errorf("failed to encode qr gif: %w", err)
|
|
|
|
}
|
2020-07-30 04:23:09 -07:00
|
|
|
return nil
|
|
|
|
}
|
2020-07-30 08:09:13 -07:00
|
|
|
|
2020-07-31 07:55:47 -07:00
|
|
|
func ReadDataFromQR(img image.Image) ([]byte, error) {
|
2020-07-30 08:09:13 -07:00
|
|
|
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get NewBinaryBitmapFromImage: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
qrReader := qrcode.NewQRCodeReader()
|
2020-07-31 07:55:47 -07:00
|
|
|
result, err := qrReader.Decode(bmp, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to decode the QR-code contents: %w", err)
|
|
|
|
}
|
2020-09-01 08:06:37 -07:00
|
|
|
return []byte(result.String()), nil
|
2020-07-31 07:55:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func EncodeQR(data []byte) ([]byte, error) {
|
|
|
|
return encoder.Encode(string(data), encoder.Medium, 512)
|
2020-07-30 08:09:13 -07:00
|
|
|
}
|