unipdf/internal/jbig2/bitmap/seed-fill.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

657 lines
18 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 (
"image"
"github.com/unidoc/unipdf/v3/internal/jbig2/basic"
"github.com/unidoc/unipdf/v3/internal/jbig2/errors"
)
// maxIterations is a constant used to prevent infitite loops.
const maxIterations = 5000
// seedFillBinary is an algorithm that fills the resultant 'd' bitmap
// with the values from 's' bitmap, expanded in the boundaries of the 'm' bitmap.
// The connectivity defines on how many directions should the 'ON' pixel check
// if it has any other 'ONE' neighbor.
// The connectivity 4 allows to check at: top, bottom, left, right, while the
// connectivity 8 checks also the corners.
// See more at: http://www.vincent-net.com/luc/papers/93ieeeip_recons.pdf.
// If the 'd' is not provided, the function creates one.
func seedFillBinary(d, s, m *Bitmap, connectivity int) (*Bitmap, error) {
const processName = "seedFillBinary"
if s == nil {
return nil, errors.Error(processName, "source bitmap is nil")
}
if m == nil {
return nil, errors.Error(processName, "'mask' bitmap is nil")
}
if connectivity != 4 && connectivity != 8 {
return nil, errors.Error(processName, "connectivity not in range {4,8}")
}
var err error
d, err = copyBitmap(d, s)
if err != nil {
return nil, errors.Wrap(err, processName, "copy source to 'd'")
}
t := s.createTemplate()
m.setPadBits(0)
for i := 0; i < maxIterations; i++ {
t, err = copyBitmap(t, d)
if err != nil {
return nil, errors.Wrapf(err, processName, "iteration: %d", i)
}
if err = seedfillBinaryLow(d, m, connectivity); err != nil {
return nil, errors.Wrapf(err, processName, "iteration: %d", i)
}
if t.Equals(d) {
// binary seed fill converged.
break
}
}
return d, nil
}
func seedfillBinaryLow(s *Bitmap, m *Bitmap, connectivity int) (err error) {
const processName = "seedfillBinaryLow"
h := min(s.Height, m.Height)
wpl := min(s.RowStride, m.RowStride)
switch connectivity {
case 4:
err = seedfillBinaryLow4(s, m, h, wpl)
case 8:
err = seedfillBinaryLow8(s, m, h, wpl)
default:
return errors.Errorf(processName, "connectivity must be 4 or 8 - is: '%d'", connectivity)
}
if err != nil {
return errors.Wrap(err, processName, "")
}
return nil
}
func seedfillBinaryLow4(s, m *Bitmap, h, wpl int) (err error) {
const processName = "seedfillBinaryLow4"
var (
i, j, lineS, lineM int
bt, mask, btAbove, btLeft, btBelow, btRight, btPrev byte
)
for i = 0; i < h; i++ {
lineS = i * s.RowStride
lineM = i * m.RowStride
for j = 0; j < wpl; j++ {
bt, err = s.GetByte(lineS + j)
if err != nil {
return errors.Wrap(err, processName, "first get")
}
mask, err = m.GetByte(lineM + j)
if err != nil {
return errors.Wrap(err, processName, "second get")
}
if i > 0 {
btAbove, err = s.GetByte(lineS - s.RowStride + j)
if err != nil {
return errors.Wrap(err, processName, "i > 0")
}
bt |= btAbove
}
if j > 0 {
btLeft, err = s.GetByte(lineS + j - 1)
if err != nil {
return errors.Wrap(err, processName, "j > 0")
}
bt |= btLeft << 7
}
bt &= mask
if bt == 0 || (^bt) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "bt == 0 || (^bt) == 0")
}
continue
}
for {
btPrev = bt
bt = (bt | (bt >> 1) | (bt << 1)) & mask
if (bt ^ btPrev) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "setting prev byte")
}
break
}
}
}
}
for i = h - 1; i >= 0; i-- {
lineS = i * s.RowStride
lineM = i * m.RowStride
for j = wpl - 1; j >= 0; j-- {
if bt, err = s.GetByte(lineS + j); err != nil {
return errors.Wrap(err, processName, "reverse first get")
}
if mask, err = m.GetByte(lineM + j); err != nil {
return errors.Wrap(err, processName, "reverse get mask byte")
}
if i < h-1 {
if btBelow, err = s.GetByte(lineS + s.RowStride + j); err != nil {
return errors.Wrap(err, processName, "reverse i < h -1")
}
bt |= btBelow
}
if j < wpl-1 {
if btRight, err = s.GetByte(lineS + j + 1); err != nil {
return errors.Wrap(err, processName, "reverse j < wpl - 1")
}
bt |= btRight >> 7
}
bt &= mask
if bt == 0 || (^bt) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "setting masked byte failed")
}
continue
}
for {
btPrev = bt
bt = (bt | (bt >> 1) | (bt << 1)) & mask
if (bt ^ btPrev) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "reverse setting prev byte")
}
break
}
}
}
}
return nil
}
func seedfillBinaryLow8(s, m *Bitmap, h, wpl int) (err error) {
const processName = "seedfillBinaryLow8"
var (
i, j, lineS, lineM int
bt, mask, btAbove, btLeft, btBelow, btRight, btPrev, temp byte
)
for i = 0; i < h; i++ {
lineS = i * s.RowStride
lineM = i * m.RowStride
for j = 0; j < wpl; j++ {
if bt, err = s.GetByte(lineS + j); err != nil {
return errors.Wrap(err, processName, "get source byte")
}
if mask, err = m.GetByte(lineM + j); err != nil {
return errors.Wrap(err, processName, "get mask byte")
}
if i > 0 {
if btAbove, err = s.GetByte(lineS - s.RowStride + j); err != nil {
return errors.Wrap(err, processName, "i > 0 byte")
}
bt |= btAbove | (btAbove << 1) | (btAbove >> 1)
if j > 0 {
if temp, err = s.GetByte(lineS - s.RowStride + j - 1); err != nil {
return errors.Wrap(err, processName, "i > 0 && j > 0 byte")
}
bt |= temp << 7
}
if j < wpl-1 {
if temp, err = s.GetByte(lineS - s.RowStride + j + 1); err != nil {
return errors.Wrap(err, processName, "j < wpl - 1 byte")
}
bt |= temp >> 7
}
}
if j > 0 {
if btLeft, err = s.GetByte(lineS + j - 1); err != nil {
return errors.Wrap(err, processName, "j > 0")
}
bt |= btLeft << 7
}
bt &= mask
if bt == 0 || ^bt == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "setting empty byte")
}
}
for {
btPrev = bt
bt = (bt | (bt >> 1) | (bt << 1)) & mask
if (bt ^ btPrev) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "setting prev byte")
}
break
}
}
}
}
for i = h - 1; i >= 0; i-- {
lineS = i * s.RowStride
lineM = i * m.RowStride
for j = wpl - 1; j >= 0; j-- {
if bt, err = s.GetByte(lineS + j); err != nil {
return errors.Wrap(err, processName, "reverse get source byte")
}
if mask, err = m.GetByte(lineM + j); err != nil {
return errors.Wrap(err, processName, "reverse get mask byte")
}
if i < h-1 {
if btBelow, err = s.GetByte(lineS + s.RowStride + j); err != nil {
return errors.Wrap(err, processName, "i < h - 1 -> get source byte")
}
bt |= btBelow | (btBelow << 1) | btBelow>>1
if j > 0 {
if temp, err = s.GetByte(lineS + s.RowStride + j - 1); err != nil {
return errors.Wrap(err, processName, "i < h-1 & j > 0 -> get source byte")
}
bt |= temp << 7
}
if j < wpl-1 {
if temp, err = s.GetByte(lineS + s.RowStride + j + 1); err != nil {
return errors.Wrap(err, processName, "i < h-1 && j <wpl-1 -> get source byte")
}
bt |= temp >> 7
}
}
if j < wpl-1 {
if btRight, err = s.GetByte(lineS + j + 1); err != nil {
return errors.Wrap(err, processName, "j < wpl -1 -> get source byte")
}
bt |= btRight >> 7
}
bt &= mask
if bt == 0 || (^bt) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "set masked byte")
}
}
for {
btPrev = bt
bt = (bt | (bt >> 1) | (bt << 1)) & mask
if (bt ^ btPrev) == 0 {
if err = s.SetByte(lineS+j, bt); err != nil {
return errors.Wrap(err, processName, "reverse set prev byte")
}
break
}
}
}
}
return nil
}
// seedFillStack4BB does the Paul Heckbert's stack based on 4 and 8 connectivity seedfill algorithm.
// The function takes 's' Bitmap and removes pixel at 'x' and 'y' coordinates as well as all the 4 or 8-connected 'ON' pixels.
// Returns the bounding box image.Rectangle of the removed 4 or 8-connected component.
func seedFillStackBB(s *Bitmap, stack *basic.Stack, x, y, connectivity int) (box *image.Rectangle, err error) {
const processName = "seedFillStackBB"
if s == nil {
return nil, errors.Error(processName, "provided nil 's' Bitmap")
}
if stack == nil {
return nil, errors.Error(processName, "provided nil 'stack'")
}
switch connectivity {
case 4:
if box, err = seedFillStack4BB(s, stack, x, y); err != nil {
return nil, errors.Wrap(err, processName, "")
}
return box, nil
case 8:
if box, err = seedFillStack8BB(s, stack, x, y); err != nil {
return nil, errors.Wrap(err, processName, "")
}
return box, nil
default:
return nil, errors.Errorf(processName, "connectivity is not 4 or 8: '%d'", connectivity)
}
}
// seedFillStack4BB does the Paul Heckbert's stack based 4 connectivity seedfill algorithm.
// The function takes 's' Bitmap and removes pixel at 'x' and 'y' coordinates as well as all the 4-connected 'ON' pixels.
// Returns the bounding box image.Rectangle of the removed 4-connected component.
func seedFillStack4BB(s *Bitmap, stack *basic.Stack, x, y int) (box *image.Rectangle, err error) {
const processName = "seedFillStackBB"
if s == nil {
return nil, errors.Error(processName, "provided nil 's' Bitmap")
}
if stack == nil {
return nil, errors.Error(processName, "provided nil 'stack'")
}
w, h := s.Width, s.Height
xMax := w - 1
yMax := h - 1
// check if the provided 'x' and 'y' are in the given image boundaries.
// Also check if the bit value at the 'x' and 'y' position is 'ON'.
if x < 0 || x > xMax || y < 0 || y > yMax || !s.GetPixel(x, y) {
return nil, nil
}
// define the initial value limits for the 'x' and 'y' in the 'rect' bounding box.
var rect *image.Rectangle
rect, err = Rect(100000, 100000, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "")
}
if err = pushFillSegmentBoundingBox(stack, x, x, y, 1, yMax, rect); err != nil {
return nil, errors.Wrap(err, processName, "initial push")
}
if err = pushFillSegmentBoundingBox(stack, x, x, y+1, -1, yMax, rect); err != nil {
return nil, errors.Wrap(err, processName, "2nd initial push")
}
rect.Min.X, rect.Max.X = x, x
rect.Min.Y, rect.Max.Y = y, y
var (
out *fillSegment
xStart int
)
// pop all the segments off the stack and fill a neighboring scan lines.
for stack.Len() > 0 {
if out, err = popFillSegment(stack); err != nil {
return nil, errors.Wrap(err, processName, "")
}
y = out.y
// Segment scan line 'out.y - out.dy' for each 'x'
// in range out.xLeft <= x <= out.xRight.
// Explore the adjacent pixels in scan line 'y'.
// There are three possible regions:
// - to the left of out.xLeft - 1
// - between out.xLeft and out.xRight
// - to the right of the out.xRight + 1
for x = out.xLeft; x >= 0 && s.GetPixel(x, y); x-- {
if err = s.SetPixel(x, y, 0); err != nil {
return nil, errors.Wrap(err, processName, "")
}
}
// check if the out.xLeft pixel was off and was not cleared.
if x >= out.xLeft {
// skip the bits to the next 'ON' bit in the provided bounds
for x++; x <= out.xRight && x <= xMax && !s.GetPixel(x, y); x++ {
}
xStart = x
// check if the x is still in the bounds.
if !(x <= out.xRight && x <= xMax) {
continue
}
} else {
xStart = x + 1
// check if there is a leak on left side
if xStart < out.xLeft-1 {
if err = pushFillSegmentBoundingBox(stack, xStart, out.xLeft-1, out.y, -out.dy, yMax, rect); err != nil {
return nil, errors.Wrap(err, processName, "leak on left side")
}
}
x = out.xLeft + 1
}
for {
for ; x <= xMax && s.GetPixel(x, y); x++ {
if err = s.SetPixel(x, y, 0); err != nil {
return nil, errors.Wrap(err, processName, "2nd set")
}
}
// push the x,y bb
if err = pushFillSegmentBoundingBox(stack, xStart, x-1, out.y, out.dy, yMax, rect); err != nil {
return nil, errors.Wrap(err, processName, "normal push")
}
// check if there is a leak on the right side
if x > out.xRight+1 {
if err = pushFillSegmentBoundingBox(stack, out.xRight+1, x-1, out.y, -out.dy, yMax, rect); err != nil {
return nil, errors.Wrap(err, processName, "leak on right side")
}
}
// skip to the next 'OFF' bit.
for x++; x <= out.xRight && x <= xMax && !s.GetPixel(x, y); x++ {
}
xStart = x
if !(x <= out.xRight && x <= xMax) {
break
}
}
}
rect.Max.X++
rect.Max.Y++
return rect, nil
}
func seedFillStack8BB(s *Bitmap, stack *basic.Stack, x, y int) (box *image.Rectangle, err error) {
const processName = "seedFillStackBB"
if s == nil {
return nil, errors.Error(processName, "provided nil 's' Bitmap")
}
if stack == nil {
return nil, errors.Error(processName, "provided nil 'stack'")
}
w, h := s.Width, s.Height
xMax := w - 1
yMax := h - 1
// check if the provided 'x' and 'y' are in the given image boundaries.
// Also check if the bit value at the 'x' and 'y' position is 'ON'.
if x < 0 || x > xMax || y < 0 || y > yMax || !s.GetPixel(x, y) {
return nil, nil
}
// define the initial value limits for the 'x' and 'y' in the 'rect' bounding box.
rect := image.Rect(100000, 100000, 0, 0)
if err = pushFillSegmentBoundingBox(stack, x, x, y, 1, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "initial push")
}
if err = pushFillSegmentBoundingBox(stack, x, x, y+1, -1, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "2nd initial push")
}
rect.Min.X, rect.Max.X = x, x
rect.Min.Y, rect.Max.Y = y, y
var (
out *fillSegment
xStart int
)
for stack.Len() > 0 {
// pop the fillSegment from the stack and use it's values in the line scanning process.
if out, err = popFillSegment(stack); err != nil {
return nil, errors.Wrap(err, processName, "")
}
y = out.y
// explore adjacent pixels in scan line 'y'. There are three possible regions:
// - to the left of out.xLeft - 1
// - between out.xLeft and out.xRight
// - to the right of the out.xRight +1
for x = out.xLeft - 1; x >= 0 && s.GetPixel(x, y); x-- {
if err = s.SetPixel(x, y, 0); err != nil {
return nil, errors.Wrap(err, processName, "1st set")
}
}
// check if the pixel at 'out.xLeft' was off and wasn't cleared.
if x >= out.xLeft-1 {
for {
// skip the bits until 'ON' pixel is found.
for x++; x <= out.xRight+1 && x <= xMax && !s.GetPixel(x, y); x++ {
}
xStart = x
// if x > out.xRight+1 || x > xMax {
if !(x <= out.xRight+1 && x <= xMax) {
break
}
for ; x <= xMax && s.GetPixel(x, y); x++ {
if err = s.SetPixel(x, y, 0); err != nil {
return nil, errors.Wrap(err, processName, "2nd set")
}
}
// push the x,y bb
if err = pushFillSegmentBoundingBox(stack, xStart, x-1, out.y, out.dy, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "normal push")
}
// check if there is a leak on the right side
if x > out.xRight {
if err = pushFillSegmentBoundingBox(stack, out.xRight+1, x-1, out.y, -out.dy, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "leak on right side")
}
}
}
continue
}
xStart = x + 1
// check if there is a leak on left side
// possible when the 'x' was < out.xLeft
if xStart < out.xLeft {
if err = pushFillSegmentBoundingBox(stack, xStart, out.xLeft-1, out.y, -out.dy, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "leak on left side")
}
}
x = out.xLeft
for {
for ; x <= xMax && s.GetPixel(x, y); x++ {
if err = s.SetPixel(x, y, 0); err != nil {
return nil, errors.Wrap(err, processName, "2nd set")
}
}
// push the x,y bb
if err = pushFillSegmentBoundingBox(stack, xStart, x-1, out.y, out.dy, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "normal push")
}
// check if there is a leak on the right side
if x > out.xRight {
if err = pushFillSegmentBoundingBox(stack, out.xRight+1, x-1, out.y, -out.dy, yMax, &rect); err != nil {
return nil, errors.Wrap(err, processName, "leak on right side")
}
}
// skip the bits until 'ON' pixel is found.
for x++; x <= out.xRight+1 && x <= xMax && !s.GetPixel(x, y); x++ {
}
xStart = x
// if x > out.xRight+1 || x > xMax {
if !(x <= out.xRight+1 && x <= xMax) {
break
}
}
}
rect.Max.X++
rect.Max.Y++
return &rect, nil
}
// fillSegment is the Heckber seedfill algorithm helper that holds the current run segment information.
type fillSegment struct {
// xLeft is the left edge of run
xLeft int
// xRight is the right edge of run
xRight int
// y is the currount run 'y'
y int
// direction of the parent segment: 1 above, -1 below.
dy int
}
// pushFillSegmentBoundingBox is a helper function for push and poping fillSegs.
func pushFillSegmentBoundingBox(stack *basic.Stack, xLeft, xRight, y, dy, yMax int, rect *image.Rectangle) (err error) {
const processName = "pushFillSegmentBoundingBox"
if stack == nil {
return errors.Error(processName, "nil stack provided")
}
if rect == nil {
return errors.Error(processName, "provided nil image.Rectangle")
}
rect.Min.X = basic.Min(rect.Min.X, xLeft)
rect.Max.X = basic.Max(rect.Max.X, xRight)
rect.Min.Y = basic.Min(rect.Min.Y, y)
rect.Max.Y = basic.Max(rect.Max.Y, y)
if !(y+dy >= 0 && y+dy <= yMax) {
return nil
}
if stack.Aux == nil {
return errors.Error(processName, "auxStack not defined")
}
var fSeg *fillSegment
fv, ok := stack.Aux.Pop()
if ok {
if fSeg, ok = fv.(*fillSegment); !ok {
return errors.Error(processName, "auxStack data is not a *fillSegment")
}
} else {
fSeg = &fillSegment{}
}
fSeg.xLeft = xLeft
fSeg.xRight = xRight
fSeg.y = y
fSeg.dy = dy
stack.Push(fSeg)
return nil
}
// popFillSegment is a helper function for the Heckber algorithm seedFills.
func popFillSegment(stack *basic.Stack) (out *fillSegment, err error) {
const processName = "popFillSegment"
if stack == nil {
return nil, errors.Error(processName, "nil stack provided")
}
if stack.Aux == nil {
return nil, errors.Error(processName, "auxStack not defined")
}
fv, ok := stack.Pop()
if !ok {
return nil, nil
}
fSeg, ok := fv.(*fillSegment)
if !ok {
return nil, errors.Error(processName, "stack doesn't contain *fillSegment")
}
out = &fillSegment{fSeg.xLeft, fSeg.xRight, fSeg.y + fSeg.dy, fSeg.dy}
stack.Aux.Push(fSeg)
return out, nil
}