unipdf/internal/jbig2/bitmap/correlation.go
Jacek Kucharczyk c582323a8f
JBIG2 Generic Encoder (#264)
* Prepared skeleton and basic component implementations for the jbig2 encoding.

* Added Bitset. Implemented Bitmap.

* Decoder with old Arithmetic Decoder

* Partly working arithmetic

* Working arithmetic decoder.

* MMR patched.

* rebuild to apache.

* Working generic

* Working generic

* Decoded full document

* Update Jenkinsfile go version [master] (#398)

* Update Jenkinsfile go version

* Decoded AnnexH document

* Minor issues fixed.

* Update README.md

* Fixed generic region errors. Added benchmark. Added bitmap unpadder. Added Bitmap toImage method.

* Fixed endofpage error

* Added integration test.

* Decoded all test files without errors. Implemented JBIG2Global.

* Merged with v3 version

* Fixed the EOF in the globals issue

* Fixed the JBIG2 ChocolateData Decode

* JBIG2 Added license information

* Minor fix in jbig2 encoding.

* Applied the logging convention

* Cleaned unnecessary imports

* Go modules clear unused imports

* checked out the README.md

* Moved trace to Debug. Fixed the build integrate tag in the document_decode_test.go

* Initial encoder skeleton

* Applied UniPDF Developer Guide. Fixed lint issues.

* Cleared documentation, fixed style issues.

* Added jbig2 doc.go files. Applied unipdf guide style.

* Minor code style changes.

* Minor naming and style issues fixes.

* Minor naming changes. Style issues fixed.

* Review r11 fixes.

* Added JBIG2 Encoder skeleton.

* Moved Document and Page to jbig2/document package. Created decoder package responsible for decoding jbig2 stream.

* Implemented raster functions.

* Added raster uni low test funcitons.

* Added raster low test functions

* untracked files on jbig2-encoder: c869089 Added raster low test functions

* index on jbig2-encoder: c869089 Added raster low test functions

* Added morph files.

* implemented jbig2 encoder basics

* JBIG2 Encoder - Generic method

* Added jbig2 image encode ttests, black/white image tests

* cleaned and tested jbig2 package

* unfinished jbig2 classified encoder

* jbig2 minor style changes

* minor jbig2 encoder changes

* prepared JBIG2 Encoder

* Style and lint fixes

* Minor changes and lints

* Fixed shift unsinged value build errors

* Minor naming change

* Added jbig2 encode, image gondels. Fixed jbig2 decode bug.

* Provided jbig2 core.DecodeGlobals function.

* Fixed JBIG2Encoder `r6` revision issues.

* Removed public JBIG2Encoder document.

* Minor style changes

* added NewJBIG2Encoder function.

* fixed JBIG2Encoder 'r9' revision issues.

* Cleared 'r9' commented code.

* Updated ACKNOWLEDGEMENETS. Fixed JBIG2Encoder 'r10' revision issues.

Co-authored-by: Gunnsteinn Hall <gunnsteinn.hall@gmail.com>
2020-03-27 11:47:41 +00:00

531 lines
16 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package bitmap
import (
"math"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/internal/jbig2/basic"
"github.com/unidoc/unipdf/v3/internal/jbig2/errors"
)
// CorrelationScore computes the correlation score between the bitmaps: 'bm1' and 'bm2'.
// The correlation score is the ratio of the square of the number of pixels AND of the two bitmaps
// to the product of the number of ON pixels in each.
func CorrelationScore(bm1, bm2 *Bitmap, area1, area2 int, delX, delY float32, maxDiffW, maxDiffH int, tab []int) (score float64, err error) {
const processName = "correlationScore"
if bm1 == nil || bm2 == nil {
return 0, errors.Error(processName, "provided nil bitmaps")
}
if tab == nil {
return 0, errors.Error(processName, "'tab' not defined")
}
if area1 <= 0 || area2 <= 0 {
return 0, errors.Error(processName, "areas must be greater than 0")
}
wi, hi := bm1.Width, bm1.Height
wt, ht := bm2.Width, bm2.Height
delW := abs(wi - wt)
if delW > maxDiffW {
return 0, nil
}
delH := abs(hi - ht)
if delH > maxDiffH {
return 0, nil
}
var idelX, idelY int
if delX >= 0 {
idelX = int(delX + 0.5)
} else {
idelX = int(delX - 0.5)
}
if delY >= 0 {
idelY = int(delY + 0.5)
} else {
idelY = int(delY - 0.5)
}
// decide which rows need to be considered.
loRow := max(idelY, 0)
hiRow := min(ht+idelY, hi)
row1 := bm1.RowStride * loRow
row2 := bm2.RowStride * (loRow - idelY)
// decide which columns of bm1 should be considered
loCol := max(idelX, 0)
hiCol := min(wt+idelX, wi)
rowBytes2 := bm2.RowStride
var pix1LSkip, pix2LSkip int
if idelX >= 8 {
pix1LSkip = idelX >> 3
row1 += pix1LSkip
loCol -= pix1LSkip << 3
hiCol -= pix1LSkip << 3
idelX &= 7
} else if idelX <= -8 {
pix2LSkip = -((idelX + 7) >> 3)
row2 += pix2LSkip
rowBytes2 -= pix2LSkip
idelX += pix2LSkip << 3
}
if loCol >= hiCol || loRow >= hiRow {
return 0, nil
}
rowBytes1 := (hiCol + 7) >> 3
var (
bt1, bt2, andByte byte
count, x, y int
)
switch {
case idelX == 0:
for y = loRow; y < hiRow; y, row1, row2 = y+1, row1+bm1.RowStride, row2+bm2.RowStride {
for x = 0; x < rowBytes1; x++ {
andByte = bm1.Data[row1+x] & bm2.Data[row2+x]
count += tab[andByte]
}
}
case idelX > 0:
if rowBytes2 < rowBytes1 {
for y = loRow; y < hiRow; y, row1, row2 = y+1, row1+bm1.RowStride, row2+bm2.RowStride {
bt1, bt2 = bm1.Data[row1], bm2.Data[row2]>>uint(idelX)
andByte = bt1 & bt2
count += tab[andByte]
for x = 1; x < rowBytes2; x++ {
bt1, bt2 = bm1.Data[row1+x], (bm2.Data[row2+x]>>uint(idelX))|(bm2.Data[row2+x-1]<<uint(8-idelX))
andByte = bt1 & bt2
count += tab[andByte]
}
bt1 = bm1.Data[row1+x]
bt2 = bm2.Data[row2+x-1] << uint(8-idelX)
andByte = bt1 & bt2
count += tab[andByte]
}
} else {
for y = loRow; y < hiRow; y, row1, row2 = y+1, row1+bm1.RowStride, row2+bm2.RowStride {
bt1, bt2 = bm1.Data[row1], bm2.Data[row2]>>uint(idelX)
andByte = bt1 & bt2
count += tab[andByte]
for x = 1; x < rowBytes1; x++ {
bt1 = bm1.Data[row1+x]
bt2 = (bm2.Data[row2+x] >> uint(idelX)) | (bm2.Data[row2+x-1] << uint(8-idelX))
andByte = bt1 & bt2
count += tab[andByte]
}
}
}
default:
if rowBytes1 < rowBytes2 {
for y = loRow; y < hiRow; y, row1, row2 = y+1, row1+bm1.RowStride, row2+bm2.RowStride {
for x = 0; x < rowBytes1; x++ {
bt1 = bm1.Data[row1+x]
bt2 = bm2.Data[row2+x] << uint(-idelX)
bt2 |= bm2.Data[row2+x+1] >> uint(8+idelX)
andByte = bt1 & bt2
count += tab[andByte]
}
}
} else {
for y = loRow; y < hiRow; y, row1, row2 = y+1, row1+bm1.RowStride, row2+bm2.RowStride {
for x = 0; x < rowBytes1-1; x++ {
bt1 = bm1.Data[row1+x]
bt2 = bm2.Data[row2+x] << uint(-idelX)
bt2 |= bm2.Data[row2+x+1] >> uint(8+idelX)
andByte = bt1 & bt2
count += tab[andByte]
}
bt1 = bm1.Data[row1+x]
bt2 = bm2.Data[row2+x] << uint(-idelX)
andByte = bt1 & bt2
count += tab[andByte]
}
}
}
score = float64(count) * float64(count) / (float64(area1) * float64(area2))
return score, nil
}
// CorrelationScoreSimple computes the correlation score value which should be the same as the result of the 'CorrelationScore' function.
// This function uses raster operations and is about 2-3x slower. This function makes it easier to understand how is the correlation computed.
func CorrelationScoreSimple(bm1, bm2 *Bitmap, area1, area2 int, delX, delY float32, maxDiffW, maxDiffH int, tab []int) (score float64, err error) {
const processName = "CorrelationScoreSimple"
if bm1 == nil || bm2 == nil {
return score, errors.Error(processName, "nil bitmaps provided")
}
if tab == nil {
return score, errors.Error(processName, "tab undefined")
}
if area1 == 0 || area2 == 0 {
return score, errors.Error(processName, "provided areas must be > 0")
}
wi, hi := bm1.Width, bm1.Height
wt, ht := bm2.Width, bm2.Height
if abs(wi-wt) > maxDiffW {
return 0, nil
}
if abs(hi-ht) > maxDiffH {
return 0, nil
}
var idelX, idelY int
if delX >= 0 {
idelX = int(delX + 0.5)
} else {
idelX = int(delX - 0.5)
}
if delY >= 0 {
idelY = int(delY + 0.5)
} else {
idelY = int(delY - 0.5)
}
bmT := bm1.createTemplate()
if err = bmT.RasterOperation(idelX, idelY, wt, ht, PixSrc, bm2, 0, 0); err != nil {
return score, errors.Wrap(err, processName, "bm2 to Template")
}
if err = bmT.RasterOperation(0, 0, wi, hi, PixSrcAndDst, bm1, 0, 0); err != nil {
return score, errors.Wrap(err, processName, "bm1 and bmT")
}
count := bmT.countPixels()
score = float64(count) * float64(count) / (float64(area1) * float64(area2))
return score, nil
}
// CorrelationScoreThresholded checks whether the correlation score is >= scoreThreshold.
// 'area1' - number of 'ON' pixels in 'bm1'.
// 'area2' - number of 'ON' pixels in 'bm2'.
// 'delX' - x comp of centroid difference.
// 'delY' - y comp of centroid difference.
// 'maxDiffW' - max width difference between 'bm1' and 'bm2'.
// 'maxDiffH' - max height difference between 'bm1' and 'bm2'.
// 'tab' - is a sum tab for the and byte (created by MakeSumTab8 function).
// 'downcount' - is the number of 'ON' pixels below each row of bitmap 'bm1'.
// 'score_threshold' - is the correlation score that the bitmaps should have at least to return false.
func CorrelationScoreThresholded(bm1, bm2 *Bitmap, area1, area2 int, delX, delY float32, maxDiffW, maxDiffH int, tab, downcount []int, scoreThreshold float32) (bool, error) {
const processName = "CorrelationScoreThresholded"
if bm1 == nil {
return false, errors.Error(processName, "correlationScoreThresholded bm1 is nil")
}
if bm2 == nil {
return false, errors.Error(processName, "correlationScoreThresholded bm2 is nil")
}
if area1 <= 0 || area2 <= 0 {
return false, errors.Error(processName, "correlationScoreThresholded - areas must be > 0")
}
if downcount == nil {
return false, errors.Error(processName, "provided no 'downcount'")
}
if tab == nil {
return false, errors.Error(processName, "provided nil 'sumtab'")
}
wi, hi := bm1.Width, bm1.Height
wt, ht := bm2.Width, bm2.Height
if basic.Abs(wi-wt) > maxDiffW {
return false, nil
}
if basic.Abs(hi-ht) > maxDiffH {
return false, nil
}
idelX := int(delX + basic.Sign(delX)*0.5)
idelY := int(delY + basic.Sign(delY)*0.5)
// compute the correlation count.
threshold := int(math.Ceil(math.Sqrt(float64(scoreThreshold) * float64(area1) * float64(area2))))
rowBytes2 := bm2.RowStride
// only the rows underlying the shifted bm2 need to be considered
loRow := max(idelY, 0)
hiRow := min(ht+idelY, hi)
row1Index := bm1.RowStride * loRow
row2Index := bm2.RowStride * (loRow - idelY)
var untouchable int
if hiRow <= hi {
// some rows of bm1 would never contribute
untouchable = downcount[hiRow-1]
}
loCol := max(idelX, 0)
hiCol := min(wt+idelX, wi)
var bm1LSkip, bm2LSkip int
if idelX >= 8 {
bm1LSkip = idelX >> 3
row1Index += bm1LSkip
loCol -= bm1LSkip << 3
hiCol -= bm1LSkip << 3
idelX &= 7
} else if idelX <= -8 {
bm2LSkip = -((idelX + 7) >> 3)
row2Index += bm2LSkip
rowBytes2 -= bm2LSkip
idelX += bm2LSkip << 3
}
var (
count, y, x int
andByte, byte1, byte2 byte
)
if loCol >= hiCol || loRow >= hiRow {
// there is no overlap
return false, nil
}
rowBytes1 := (hiCol + 7) >> 3
switch {
case idelX == 0:
for y = loRow; y < hiRow; y, row1Index, row2Index = y+1, row1Index+bm1.RowStride, row2Index+bm2.RowStride {
for x = 0; x < rowBytes1; x++ {
andByte = bm1.Data[row1Index+x] & bm2.Data[row2Index+x]
count += tab[andByte]
}
if count >= threshold {
return true, nil
}
if v := count + downcount[y] - untouchable; v < threshold {
return false, nil
}
}
case idelX > 0 && rowBytes2 < rowBytes1:
for y = loRow; y < hiRow; y, row1Index, row2Index = y+1, row1Index+bm1.RowStride, row2Index+bm2.RowStride {
byte1 = bm1.Data[row1Index]
byte2 = bm2.Data[row2Index] >> uint(idelX)
andByte = byte1 & byte2
count += tab[andByte]
for x = 1; x < rowBytes2; x++ {
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x]>>uint(idelX) | bm2.Data[row2Index+x-1]<<uint(8-idelX)
andByte = byte1 & byte2
count += tab[andByte]
}
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x-1] << uint(8-idelX)
andByte = byte1 & byte2
count += tab[andByte]
if count >= threshold {
return true, nil
} else if count+downcount[y]-untouchable < threshold {
return false, nil
}
}
case idelX > 0 && rowBytes2 >= rowBytes1:
for y = loRow; y < hiRow; y, row1Index, row2Index = y+1, row1Index+bm1.RowStride, row2Index+bm2.RowStride {
byte1 = bm1.Data[row1Index]
byte2 = bm2.Data[row2Index] >> uint(idelX)
andByte = byte1 & byte2
count += tab[andByte]
for x = 1; x < rowBytes1; x++ {
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x] >> uint(idelX)
byte2 |= bm2.Data[row2Index+x-1] << uint(8-idelX)
andByte = byte1 & byte2
count += tab[andByte]
}
if count >= threshold {
return true, nil
} else if count+downcount[y]-untouchable < threshold {
return false, nil
}
}
case rowBytes1 < rowBytes2:
for y = loRow; y < hiRow; y, row1Index, row2Index = y+1, row1Index+bm1.RowStride, row2Index+bm2.RowStride {
for x = 0; x < rowBytes1; x++ {
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x] << uint(-idelX)
byte2 |= bm2.Data[row2Index+x+1] >> uint(8+idelX)
andByte = byte1 & byte2
count += tab[andByte]
}
if count >= threshold {
return true, nil
} else if left := count + downcount[y] - untouchable; left < threshold {
return false, nil
}
}
case rowBytes2 >= rowBytes1:
for y = loRow; y < hiRow; y, row1Index, row2Index = y+1, row1Index+bm1.RowStride, row2Index+bm2.RowStride {
for x = 0; x < rowBytes1; x++ {
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x] << uint(-idelX)
byte2 |= bm2.Data[row2Index+x+1] >> uint(8+idelX)
andByte = byte1 & byte2
count += tab[andByte]
}
byte1 = bm1.Data[row1Index+x]
byte2 = bm2.Data[row2Index+x] << uint(-idelX)
andByte = byte1 & byte2
count += tab[andByte]
if count >= threshold {
return true, nil
} else if count+downcount[y]-untouchable < threshold {
return false, nil
}
}
}
score := float32(count) * float32(count) / (float32(area1) * float32(area2))
if score >= scoreThreshold {
common.Log.Trace("count: %d < threshold %d but score %f >= scoreThreshold %f", count, threshold, score, scoreThreshold)
}
return false, nil
}
// HausTest does the Hausdorff 2-way check for the provided bitmaps.
// Parameters:
// p1 - new not dilated bitmap
// p2 - new dilated bitmap
// p3 - exemplar not dilated bitmap
// p4 - exemplar dilated bitmap
// delX, delY - component centroid difference for 'x' and 'y' coordinates.
// maxDiffW - maximum width difference of 'p1' and 'p2'
// maxDiffH - maximum height difference of 'p1' and 'p2'
// The centroid difference is used to align two images to the nearest integer for each check.
// It checks if the dilated image of one contains all the pixels of the undilated image of the other.
func HausTest(p1, p2, p3, p4 *Bitmap, delX, delY float32, maxDiffW, maxDiffH int) (bool, error) {
const processName = "HausTest"
// do a short check if the size is out of possible difference.
wi, hi := p1.Width, p1.Height
wt, ht := p3.Width, p3.Height
if basic.Abs(wi-wt) > maxDiffW {
return false, nil
}
if basic.Abs(hi-ht) > maxDiffH {
return false, nil
}
// round difference in centroid location to nearest integer.
idelX := int(delX + basic.Sign(delX)*0.5)
idelY := int(delY + basic.Sign(delY)*0.5)
var err error
// do 1-direction hausdorff
pt := p1.CreateTemplate()
if err = pt.RasterOperation(0, 0, wi, hi, PixSrc, p1, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "p1 -SRC-> t")
}
if err = pt.RasterOperation(idelX, idelY, wi, hi, PixNotSrcAndDst, p4, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "!p4 & t")
}
if pt.Zero() {
return false, nil
}
if err = pt.RasterOperation(idelX, idelY, wt, ht, PixSrc, p3, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "p3 -SRC-> t")
}
if err = pt.RasterOperation(0, 0, wt, ht, PixNotSrcAndDst, p2, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "!p2 & t")
}
return pt.Zero(), nil
}
// RankHausTest does the test of the Hausdorff ranked check.
// Parameters:
// p1 - new bitmap, not dilated
// p2 - new bitmap, dilated
// p3 -u exemplar bitmap, not dilated
// p4 - exemplar bitmap, dilated
// delX, delY - component centroid difference for 'x' and 'y' coordinates
// maxDiffW - maximum Width difference of 'p1' and 'p2'
// maxDiffH - maximum Height difference of 'p1' and 'p2'
// area1 - 'ON' - fg - pixels area of the 'p1' bitmap
// area3 - 'ON' - fg - pixels area of the 'p3' bitmap
// rank - rank value of the test
// tab8 - table of the pixel sums for a single byte.
// The 'rank' value is being converted to a number of pixels by multiplication with the number of undilated images.
// The centroid difference is used for alignment of the images.
// The rank Hausdorff checks if dilated image of one contains the rank fraction pixels of the undilated image of the other in both directions.
func RankHausTest(p1, p2, p3, p4 *Bitmap, delX, delY float32, maxDiffW, maxDiffH, area1, area3 int, rank float32, tab8 []int) (match bool, err error) {
const processName = "RankHausTest"
// at first try to eliminate possible matches based on the size difference.
wi, hi := p1.Width, p1.Height
wt, ht := p3.Width, p3.Height
if basic.Abs(wi-wt) > maxDiffW {
return false, nil
}
if basic.Abs(hi-ht) > maxDiffH {
return false, nil
}
// convert the rank value into pixel threshold for 'p1' and 'p3' 'ON' pixels areas.
thresh1 := int(float32(area1)*(1.0-rank) + 0.5)
thresh3 := int(float32(area3)*(1.0-rank) + 0.5)
// round the difference of the centroid locations.
var iDelX, iDelY int
if delX >= 0 {
iDelX = int(delX + 0.5)
} else {
iDelX = int(delX - 0.5)
}
if delY >= 0 {
iDelY = int(delY + 0.5)
} else {
iDelY = int(delY - 0.5)
}
// do the 1-direction hausdorff check, if every pixel in 'p1' is within a dilation distance of some pixel in 'p3'.
t := p1.CreateTemplate()
if err = t.RasterOperation(0, 0, wi, hi, PixSrc, p1, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "p1 -SRC-> t")
}
if err = t.RasterOperation(iDelX, iDelY, wi, hi, PixNotSrcAndDst, p4, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "t & !p4")
}
// check if this hausdorff test check is within a threshold of 'p1' bitmap.
match, err = t.ThresholdPixelSum(thresh1, tab8)
if err != nil {
return false, errors.Wrap(err, processName, "t->thresh1")
}
if match {
return false, nil
}
// now do a 1-direction hausdorff checking that every pixel of 'p3' is within dilation distance of some pixel in 'p1'.
// (p2 entirely covers p3)
if err = t.RasterOperation(iDelX, iDelY, wt, ht, PixSrc, p3, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "p3 -SRC-> t")
}
if err = t.RasterOperation(0, 0, wt, ht, PixNotSrcAndDst, p2, 0, 0); err != nil {
return false, errors.Wrap(err, processName, "t & !p2")
}
// check if the pixel sum of the 't' bitmap is above the provided threshold
match, err = t.ThresholdPixelSum(thresh3, tab8)
if err != nil {
return false, errors.Wrap(err, processName, "t->thresh3")
}
return !match, nil
}