mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-27 13:48:51 +08:00

* 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>
657 lines
18 KiB
Go
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
|
|
}
|