200 lines
5.9 KiB
Go
200 lines
5.9 KiB
Go
package qrcode
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/makiuchi-d/gozxing"
|
|
"github.com/makiuchi-d/gozxing/common"
|
|
"github.com/makiuchi-d/gozxing/common/util"
|
|
"github.com/makiuchi-d/gozxing/qrcode/decoder"
|
|
"github.com/makiuchi-d/gozxing/qrcode/detector"
|
|
)
|
|
|
|
type QRCodeReader struct {
|
|
decoder *decoder.Decoder
|
|
}
|
|
|
|
func NewQRCodeReader() gozxing.Reader {
|
|
return &QRCodeReader{
|
|
decoder.NewDecoder(),
|
|
}
|
|
}
|
|
|
|
func (this *QRCodeReader) GetDecoder() *decoder.Decoder {
|
|
return this.decoder
|
|
}
|
|
|
|
func (this *QRCodeReader) DecodeWithoutHints(image *gozxing.BinaryBitmap) (*gozxing.Result, error) {
|
|
return this.Decode(image, nil)
|
|
}
|
|
|
|
func (this *QRCodeReader) Decode(image *gozxing.BinaryBitmap, hints map[gozxing.DecodeHintType]interface{}) (*gozxing.Result, error) {
|
|
var decoderResult *common.DecoderResult
|
|
var points []gozxing.ResultPoint
|
|
|
|
blackMatrix, e := image.GetBlackMatrix()
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if _, ok := hints[gozxing.DecodeHintType_PURE_BARCODE]; ok {
|
|
bits, e := this.extractPureBits(blackMatrix)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
decoderResult, e = this.decoder.Decode(bits, hints)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
points = []gozxing.ResultPoint{}
|
|
} else {
|
|
detectorResult, e := detector.NewDetector(blackMatrix).Detect(hints)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
decoderResult, e = this.decoder.Decode(detectorResult.GetBits(), hints)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
points = detectorResult.GetPoints()
|
|
}
|
|
|
|
// If the code was mirrored: swap the bottom-left and the top-right points.
|
|
if metadata, ok := decoderResult.GetOther().(*decoder.QRCodeDecoderMetaData); ok {
|
|
metadata.ApplyMirroredCorrection(points)
|
|
}
|
|
|
|
result := gozxing.NewResult(decoderResult.GetText(), decoderResult.GetRawBytes(), points, gozxing.BarcodeFormat_QR_CODE)
|
|
byteSegments := decoderResult.GetByteSegments()
|
|
if len(byteSegments) > 0 {
|
|
result.PutMetadata(gozxing.ResultMetadataType_BYTE_SEGMENTS, byteSegments)
|
|
}
|
|
ecLevel := decoderResult.GetECLevel()
|
|
if ecLevel != "" {
|
|
result.PutMetadata(gozxing.ResultMetadataType_ERROR_CORRECTION_LEVEL, ecLevel)
|
|
}
|
|
if decoderResult.HasStructuredAppend() {
|
|
result.PutMetadata(
|
|
gozxing.ResultMetadataType_STRUCTURED_APPEND_SEQUENCE,
|
|
decoderResult.GetStructuredAppendSequenceNumber())
|
|
result.PutMetadata(
|
|
gozxing.ResultMetadataType_STRUCTURED_APPEND_PARITY,
|
|
decoderResult.GetStructuredAppendParity())
|
|
}
|
|
result.PutMetadata(
|
|
gozxing.ResultMetadataType_SYMBOLOGY_IDENTIFIER, "]Q"+strconv.Itoa(decoderResult.GetSymbologyModifier()))
|
|
return result, nil
|
|
}
|
|
|
|
func (this *QRCodeReader) Reset() {
|
|
// do nothing
|
|
}
|
|
|
|
func (this *QRCodeReader) extractPureBits(image *gozxing.BitMatrix) (*gozxing.BitMatrix, error) {
|
|
|
|
leftTopBlack := image.GetTopLeftOnBit()
|
|
rightBottomBlack := image.GetBottomRightOnBit()
|
|
if leftTopBlack == nil || rightBottomBlack == nil {
|
|
return nil, gozxing.NewNotFoundException()
|
|
}
|
|
|
|
moduleSize, e := this.moduleSize(leftTopBlack, image)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
top := leftTopBlack[1]
|
|
bottom := rightBottomBlack[1]
|
|
left := leftTopBlack[0]
|
|
right := rightBottomBlack[0]
|
|
|
|
// Sanity check!
|
|
if left >= right || top >= bottom {
|
|
return nil, gozxing.NewNotFoundException(
|
|
"(left,right)=(%v,%v), (top,bottom)=(%v,%v)", left, right, top, bottom)
|
|
}
|
|
|
|
if bottom-top != right-left {
|
|
// Special case, where bottom-right module wasn't black so we found something else in the last row
|
|
// Assume it's a square, so use height as the width
|
|
right = left + (bottom - top)
|
|
if right >= image.GetWidth() {
|
|
// Abort if that would not make sense -- off image
|
|
return nil, gozxing.NewNotFoundException("right = %v, width = %v", right, image.GetWidth())
|
|
}
|
|
}
|
|
|
|
matrixWidth := util.MathUtils_Round(float64(right-left+1) / moduleSize)
|
|
matrixHeight := util.MathUtils_Round(float64(bottom-top+1) / moduleSize)
|
|
if matrixWidth <= 0 || matrixHeight <= 0 {
|
|
return nil, gozxing.NewNotFoundException("matrixWidth/Height = %v, %v", matrixWidth, matrixHeight)
|
|
}
|
|
if matrixHeight != matrixWidth {
|
|
// Only possibly decode square regions
|
|
return nil, gozxing.NewNotFoundException("matrixWidth/Height = %v, %v", matrixWidth, matrixHeight)
|
|
}
|
|
|
|
// Push in the "border" by half the module width so that we start
|
|
// sampling in the middle of the module. Just in case the image is a
|
|
// little off, this will help recover.
|
|
nudge := int(moduleSize / 2.0)
|
|
top += nudge
|
|
left += nudge
|
|
|
|
// But careful that this does not sample off the edge
|
|
// "right" is the farthest-right valid pixel location -- right+1 is not necessarily
|
|
// This is positive by how much the inner x loop below would be too large
|
|
nudgedTooFarRight := left + int(float64(matrixWidth-1)*moduleSize) - right
|
|
if nudgedTooFarRight > 0 {
|
|
if nudgedTooFarRight > nudge {
|
|
// Neither way fits; abort
|
|
return nil, gozxing.NewNotFoundException("Neither way fits")
|
|
}
|
|
left -= nudgedTooFarRight
|
|
}
|
|
// See logic above
|
|
nudgedTooFarDown := top + int(float64(matrixHeight-1)*moduleSize) - bottom
|
|
if nudgedTooFarDown > 0 {
|
|
if nudgedTooFarDown > nudge {
|
|
// Neither way fits; abort
|
|
return nil, gozxing.NewNotFoundException("Neither way fits")
|
|
}
|
|
top -= nudgedTooFarDown
|
|
}
|
|
|
|
// Now just read off the bits
|
|
bits, _ := gozxing.NewBitMatrix(matrixWidth, matrixHeight)
|
|
for y := 0; y < matrixHeight; y++ {
|
|
iOffset := top + int(float64(y)*moduleSize)
|
|
for x := 0; x < matrixWidth; x++ {
|
|
if image.Get(left+int(float64(x)*moduleSize), iOffset) {
|
|
bits.Set(x, y)
|
|
}
|
|
}
|
|
}
|
|
return bits, nil
|
|
}
|
|
|
|
func (this *QRCodeReader) moduleSize(leftTopBlack []int, image *gozxing.BitMatrix) (float64, error) {
|
|
height := image.GetHeight()
|
|
width := image.GetWidth()
|
|
x := leftTopBlack[0]
|
|
y := leftTopBlack[1]
|
|
inBlack := true
|
|
transitions := 0
|
|
for x < width && y < height {
|
|
if inBlack != image.Get(x, y) {
|
|
transitions++
|
|
if transitions == 5 {
|
|
break
|
|
}
|
|
inBlack = !inBlack
|
|
}
|
|
x++
|
|
y++
|
|
}
|
|
if x == width || y == height {
|
|
return 0, gozxing.NewNotFoundException()
|
|
}
|
|
return float64(x-leftTopBlack[0]) / 7.0, nil
|
|
}
|