fckeuspy-go/vendor/github.com/makiuchi-d/gozxing/global_histogram_binarizer.go
2025-09-28 21:03:39 +02:00

192 lines
5.0 KiB
Go

package gozxing
const (
LUMINANCE_BITS = 5
LUMINANCE_SHIFT = 8 - LUMINANCE_BITS
LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS
)
type GlobalHistogramBinarizer struct {
source LuminanceSource
luminances []byte
buckets []int
}
func NewGlobalHistgramBinarizer(source LuminanceSource) Binarizer {
return &GlobalHistogramBinarizer{
source: source,
luminances: []byte{},
buckets: make([]int, LUMINANCE_BUCKETS),
}
}
func (this *GlobalHistogramBinarizer) GetLuminanceSource() LuminanceSource {
return this.source
}
func (this *GlobalHistogramBinarizer) GetWidth() int {
return this.source.GetWidth()
}
func (this *GlobalHistogramBinarizer) GetHeight() int {
return this.source.GetHeight()
}
func (this *GlobalHistogramBinarizer) GetBlackRow(y int, row *BitArray) (*BitArray, error) {
source := this.GetLuminanceSource()
width := source.GetWidth()
if row == nil || row.GetSize() < width {
row = NewBitArray(width)
} else {
row.Clear()
}
this.initArrays(width)
localLuminances, e := source.GetRow(y, this.luminances)
if e != nil {
return nil, e
}
localBuckets := this.buckets
for x := 0; x < width; x++ {
localBuckets[(localLuminances[x]&0xff)>>LUMINANCE_SHIFT]++
}
blackPoint, e := this.estimateBlackPoint(localBuckets)
if e != nil {
return nil, e
}
if width < 3 {
// Special case for very small images
for x := 0; x < width; x++ {
if int(localLuminances[x]&0xff) < blackPoint {
row.Set(x)
}
}
} else {
left := int(localLuminances[0] & 0xff)
center := int(localLuminances[1] & 0xff)
for x := 1; x < width-1; x++ {
right := int(localLuminances[x+1] & 0xff)
// A simple -1 4 -1 box filter with a weight of 2.
if ((center*4)-left-right)/2 < blackPoint {
row.Set(x)
}
left = center
center = right
}
}
return row, nil
}
func (this *GlobalHistogramBinarizer) GetBlackMatrix() (*BitMatrix, error) {
source := this.GetLuminanceSource()
width := source.GetWidth()
height := source.GetHeight()
matrix, e := NewBitMatrix(width, height)
if e != nil {
return nil, e
}
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
// more robust on the blackbox tests than sampling a diagonal as we used to do.
this.initArrays(width)
localBuckets := this.buckets
for y := 1; y < 5; y++ {
row := height * y / 5
localLuminances, _ := source.GetRow(row, this.luminances)
right := (width * 4) / 5
for x := width / 5; x < right; x++ {
pixel := localLuminances[x] & 0xff
localBuckets[pixel>>LUMINANCE_SHIFT]++
}
}
blackPoint, e := this.estimateBlackPoint(localBuckets)
if e != nil {
return nil, e
}
// We delay reading the entire image luminance until the black point estimation succeeds.
// Although we end up reading four rows twice, it is consistent with our motto of
// "fail quickly" which is necessary for continuous scanning.
localLuminances := source.GetMatrix()
for y := 0; y < height; y++ {
offset := y * width
for x := 0; x < width; x++ {
pixel := int(localLuminances[offset+x] & 0xff)
if pixel < blackPoint {
matrix.Set(x, y)
}
}
}
return matrix, nil
}
func (this *GlobalHistogramBinarizer) CreateBinarizer(source LuminanceSource) Binarizer {
return NewGlobalHistgramBinarizer(source)
}
func (this *GlobalHistogramBinarizer) initArrays(luminanceSize int) {
if len(this.luminances) < luminanceSize {
this.luminances = make([]byte, luminanceSize)
}
for x := 0; x < LUMINANCE_BUCKETS; x++ {
this.buckets[x] = 0
}
}
func (this *GlobalHistogramBinarizer) estimateBlackPoint(buckets []int) (int, error) {
// Find the tallest peak in the histogram.
numBuckets := len(buckets)
maxBucketCount := 0
firstPeak := 0
firstPeakSize := 0
for x := 0; x < numBuckets; x++ {
if buckets[x] > firstPeakSize {
firstPeak = x
firstPeakSize = buckets[x]
}
if buckets[x] > maxBucketCount {
maxBucketCount = buckets[x]
}
}
// Find the second-tallest peak which is somewhat far from the tallest peak.
secondPeak := 0
secondPeakScore := 0
for x := 0; x < numBuckets; x++ {
distanceToBiggest := x - firstPeak
// Encourage more distant second peaks by multiplying by square of distance.
score := buckets[x] * distanceToBiggest * distanceToBiggest
if score > secondPeakScore {
secondPeak = x
secondPeakScore = score
}
}
// Make sure firstPeak corresponds to the black peak.
if firstPeak > secondPeak {
firstPeak, secondPeak = secondPeak, firstPeak
}
// If there is too little contrast in the image to pick a meaningful black point, throw rather
// than waste time trying to decode the image, and risk false positives.
if secondPeak-firstPeak <= numBuckets/16 {
return 0, NewNotFoundException()
}
// Find a valley between them that is low and closer to the white peak.
bestValley := secondPeak - 1
bestValleyScore := -1
for x := secondPeak - 1; x > firstPeak; x-- {
fromFirst := x - firstPeak
score := fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x])
if score > bestValleyScore {
bestValley = x
bestValleyScore = score
}
}
return bestValley << LUMINANCE_SHIFT, nil
}