mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-02 22:17:06 +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>
762 lines
20 KiB
Go
762 lines
20 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 (
|
|
"github.com/unidoc/unipdf/v3/common"
|
|
|
|
"github.com/unidoc/unipdf/v3/internal/jbig2/errors"
|
|
)
|
|
|
|
// BoundaryCondition is the global enum variable used to define morph operation boundary conditions.
|
|
// More information about the definition could be found at: http://www.leptonica.org/binary-morphology.html#boundary-conditions
|
|
type BoundaryCondition int
|
|
|
|
const (
|
|
// AsymmetricMorphBC defines the asymmetric boundary condition for morph functions.
|
|
AsymmetricMorphBC BoundaryCondition = iota
|
|
// SymmetricMorphBC defines the symmetric boundary condition for morph funcftions.
|
|
SymmetricMorphBC
|
|
)
|
|
|
|
// MorphBC defines current morph boundary condition used by the morph functions.
|
|
// By default it is set to 'AsymetricMorphBC'.
|
|
var MorphBC BoundaryCondition
|
|
|
|
// MorphOperation is an enum that wraps the morph operations.
|
|
type MorphOperation int
|
|
|
|
// Enum morph operations.
|
|
const (
|
|
MopDilation MorphOperation = iota
|
|
MopErosion
|
|
MopOpening
|
|
MopClosing
|
|
MopRankBinaryReduction
|
|
MopReplicativeBinaryExpansion
|
|
MopAddBorder
|
|
)
|
|
|
|
// MorphProcess is the combination of the morph operator with it's values.
|
|
type MorphProcess struct {
|
|
Operation MorphOperation
|
|
Arguments []int
|
|
}
|
|
|
|
var intlogBase2 = [5]int{1, 2, 3, 0, 4}
|
|
|
|
func (p MorphProcess) verify(i int, netRed, border *int) error {
|
|
const processName = "MorphProcess.verify"
|
|
switch p.Operation {
|
|
case MopDilation, MopErosion, MopOpening, MopClosing:
|
|
if len(p.Arguments) != 2 {
|
|
return errors.Error(processName, "Operation: 'd', 'e', 'o', 'c' requires at least 2 arguments")
|
|
}
|
|
w, h := p.getWidthHeight()
|
|
if w <= 0 || h <= 0 {
|
|
return errors.Error(processName, "Operation: 'd', 'e', 'o', 'c' requires both width and height to be >= 0")
|
|
}
|
|
case MopRankBinaryReduction:
|
|
nred := len(p.Arguments)
|
|
*netRed += nred
|
|
if nred < 1 || nred > 4 {
|
|
return errors.Error(processName, "Operation: 'r' requires at least 1 and at most 4 arguments")
|
|
}
|
|
for i := 0; i < nred; i++ {
|
|
if p.Arguments[i] < 1 || p.Arguments[i] > 4 {
|
|
return errors.Error(processName, "RankBinaryReduction level must be in range (0, 4>")
|
|
}
|
|
}
|
|
case MopReplicativeBinaryExpansion:
|
|
if len(p.Arguments) == 0 {
|
|
return errors.Error(processName, "ReplicativeBinaryExpansion requires one argument")
|
|
}
|
|
fact := p.Arguments[0]
|
|
if fact != 2 && fact != 4 && fact != 8 {
|
|
return errors.Error(processName, "ReplicativeBinaryExpansion must be of factor in range {2,4,8}")
|
|
}
|
|
*netRed -= intlogBase2[fact/4]
|
|
case MopAddBorder:
|
|
if len(p.Arguments) == 0 {
|
|
return errors.Error(processName, "AddBorder requires one argument")
|
|
}
|
|
fact := p.Arguments[0]
|
|
if i > 0 {
|
|
return errors.Error(processName, "AddBorder must be a first morph process")
|
|
}
|
|
if fact < 1 {
|
|
return errors.Error(processName, "AddBorder value lower than 0")
|
|
}
|
|
*border = fact
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p MorphProcess) getWidthHeight() (width, height int) {
|
|
return p.Arguments[0], p.Arguments[1]
|
|
}
|
|
|
|
// Centroid gets the centroid of the provided 'bm' bitmap.
|
|
// The parameters 'centTab' and 'sumTab' are optional.
|
|
// 'centTab' is a table for finding centroids.
|
|
// 'sumTab' is a table for finding pixel sums.
|
|
// The centorid point is relative to the UL corner.
|
|
func Centroid(bm *Bitmap, centTab, sumTab []int) (Point, error) {
|
|
return bm.centroid(centTab, sumTab)
|
|
}
|
|
|
|
// Centroids gets the centroids relative to the UL corner of each pix for the provided bitmaps.
|
|
func Centroids(bms []*Bitmap) (*Points, error) {
|
|
pta := make([]Point, len(bms))
|
|
centTab := makePixelCentroidTab8()
|
|
sumTab := makePixelSumTab8()
|
|
|
|
var err error
|
|
for i, bm := range bms {
|
|
pta[i], err = bm.centroid(centTab, sumTab)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
pts := Points(pta)
|
|
return &pts, nil
|
|
}
|
|
|
|
// DilateBrick dilate with all sel being hit.
|
|
func DilateBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
return dilateBrick(d, s, hSize, vSize)
|
|
}
|
|
|
|
// Dilate the source bitmap 's' using hits in the selection 'sel'.
|
|
// The 'd' destination bitmap is optional.
|
|
// The following cases are possible:
|
|
// 'd' == 's' the function writes the result back to 'src'.
|
|
// 'd' == nil the function creates new bitmap and writes the result to it.
|
|
// 'd' != 's' puts the results into existing 'd'.
|
|
func Dilate(d *Bitmap, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
return dilate(d, s, sel)
|
|
}
|
|
|
|
// MakePixelSumTab8 creates table of integers that gives
|
|
// the number of 1 bits in the 8 bit index.
|
|
func MakePixelSumTab8() []int {
|
|
return makePixelSumTab8()
|
|
}
|
|
|
|
// MakePixelCentroidTab8 creates table of integers gives
|
|
// the centroid weight of the 1 bits in the 8 bit index.
|
|
func MakePixelCentroidTab8() []int {
|
|
return makePixelCentroidTab8()
|
|
}
|
|
|
|
// MorphSequence does the morph processes over the 'src' Bitmap with the provided sequence.
|
|
func MorphSequence(src *Bitmap, sequence ...MorphProcess) (*Bitmap, error) {
|
|
return morphSequence(src, sequence...)
|
|
}
|
|
|
|
func (b *Bitmap) centroid(centTab, sumTab []int) (Point, error) {
|
|
pt := Point{}
|
|
b.setPadBits(0)
|
|
if len(centTab) == 0 {
|
|
centTab = makePixelCentroidTab8()
|
|
}
|
|
if len(sumTab) == 0 {
|
|
sumTab = makePixelSumTab8()
|
|
}
|
|
var xsum, ysum, pixsum, rowsum, i, j int
|
|
var bt byte
|
|
for i = 0; i < b.Height; i++ {
|
|
line := b.RowStride * i
|
|
rowsum = 0
|
|
for j = 0; j < b.RowStride; j++ {
|
|
bt = b.Data[line+j]
|
|
if bt != 0 {
|
|
rowsum += sumTab[bt]
|
|
xsum += centTab[bt] + j*8*sumTab[bt]
|
|
}
|
|
}
|
|
pixsum += rowsum
|
|
ysum += rowsum * i
|
|
}
|
|
if pixsum != 0 {
|
|
pt.X = float32(xsum) / float32(pixsum)
|
|
pt.Y = float32(ysum) / float32(pixsum)
|
|
}
|
|
return pt, nil
|
|
}
|
|
|
|
var (
|
|
tabExpand2x = makeExpandTab2x()
|
|
tabExpand4x = makeExpandTab4x()
|
|
tabExpand8x = makeExpandTab8x()
|
|
)
|
|
|
|
// closeBitmap does two morph functions in a row - a dilate and erode.
|
|
func closeBitmap(d, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
const processName = "closeBitmap"
|
|
var err error
|
|
if d, err = processMorphArgs2(d, s, sel); err != nil {
|
|
return nil, err
|
|
}
|
|
// dilate first
|
|
t, err := dilate(nil, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
// then erode
|
|
if _, err = erode(d, t, sel); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func closeBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
const processName = "closeBrick"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source not defined")
|
|
}
|
|
if hSize < 1 || vSize < 1 {
|
|
return nil, errors.Error(processName, "hSize and vSize not >= 1")
|
|
}
|
|
if hSize == 1 && vSize == 1 {
|
|
return s.Copy(), nil
|
|
}
|
|
if hSize == 1 || vSize == 1 {
|
|
sel := SelCreateBrick(vSize, hSize, vSize/2, hSize/2, SelHit)
|
|
var err error
|
|
d, err = closeBitmap(d, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 || vSize == 1")
|
|
}
|
|
return d, nil
|
|
}
|
|
selH := SelCreateBrick(1, hSize, 0, hSize/2, SelHit)
|
|
selV := SelCreateBrick(vSize, 1, vSize/2, 0, SelHit)
|
|
t, err := dilate(nil, s, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st dilate")
|
|
}
|
|
if d, err = dilate(d, t, selV); err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd dilate")
|
|
}
|
|
// erode
|
|
if _, err = erode(t, d, selH); err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st erode")
|
|
}
|
|
if _, err = erode(d, t, selV); err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd erode")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func closeSafeBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
const processName = "closeSafeBrick"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source is nil")
|
|
}
|
|
if hSize < 1 || vSize < 1 {
|
|
return nil, errors.Error(processName, "hsize and vsize not >= 1")
|
|
}
|
|
if hSize == 1 && vSize == 1 {
|
|
return copyBitmap(d, s)
|
|
}
|
|
if MorphBC == SymmetricMorphBC {
|
|
// Symmetric handles the pixels correctly
|
|
bm, err := closeBrick(d, s, hSize, vSize)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "SymmetricMorphBC")
|
|
}
|
|
return bm, nil
|
|
}
|
|
|
|
maxTrans := max(hSize/2, vSize/2)
|
|
bordSize := 8 * ((maxTrans + 7) / 8)
|
|
|
|
bsb, err := s.AddBorder(bordSize, 0)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, processName, "BorderSize: %d", bordSize)
|
|
}
|
|
|
|
var bdb, t *Bitmap
|
|
if hSize == 1 || vSize == 1 {
|
|
sel := SelCreateBrick(vSize, hSize, vSize/2, hSize/2, SelHit)
|
|
bdb, err = closeBitmap(nil, bsb, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 || vSize == 1")
|
|
}
|
|
} else {
|
|
selH := SelCreateBrick(1, hSize, 0, hSize/2, SelHit)
|
|
t, err := dilate(nil, bsb, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "regular - first dilate")
|
|
}
|
|
|
|
selV := SelCreateBrick(vSize, 1, vSize/2, 0, SelHit)
|
|
bdb, err = dilate(nil, t, selV)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "regular - second dilate")
|
|
}
|
|
|
|
if _, err = erode(t, bdb, selH); err != nil {
|
|
return nil, errors.Wrap(err, processName, "regular - first erode")
|
|
}
|
|
if _, err = erode(bdb, t, selV); err != nil {
|
|
return nil, errors.Wrap(err, processName, "regular - second erode")
|
|
}
|
|
}
|
|
|
|
if t, err = bdb.RemoveBorder(bordSize); err != nil {
|
|
return nil, errors.Wrap(err, processName, "regular")
|
|
}
|
|
if d == nil {
|
|
return t, nil
|
|
}
|
|
|
|
if _, err = copyBitmap(d, t); err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func dilate(d *Bitmap, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
var (
|
|
t *Bitmap
|
|
err error
|
|
)
|
|
d, err = processMorphArgs1(d, s, sel, &t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = d.clearAll(); err != nil {
|
|
return nil, err
|
|
}
|
|
var selData SelectionValue
|
|
for i := 0; i < sel.Height; i++ {
|
|
for j := 0; j < sel.Width; j++ {
|
|
selData = sel.Data[i][j]
|
|
if selData == SelHit {
|
|
if err = d.RasterOperation(j-sel.Cx, i-sel.Cy, s.Width, s.Height, PixSrcOrDst, t, 0, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// hSize is the width of the brick sel
|
|
// vSize is the height of the brick sel
|
|
func dilateBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
const processName = "dilateBrick"
|
|
if s == nil {
|
|
common.Log.Debug("dilateBrick source not defined")
|
|
return nil, errors.Error(processName, "source bitmap not defined")
|
|
}
|
|
if hSize < 1 || vSize < 1 {
|
|
return nil, errors.Error(processName, "hSzie and vSize are no greater equal to 1")
|
|
}
|
|
if hSize == 1 && vSize == 1 {
|
|
bm, err := copyBitmap(d, s)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 && vSize == 1")
|
|
}
|
|
return bm, nil
|
|
}
|
|
if hSize == 1 || vSize == 1 {
|
|
sel := SelCreateBrick(vSize, hSize, vSize/2, hSize/2, SelHit)
|
|
d, err := dilate(d, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hsize == 1 || vSize == 1")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
selH := SelCreateBrick(1, hSize, 0, hSize/2, SelHit)
|
|
selV := SelCreateBrick(vSize, 1, vSize/2, 0, SelHit)
|
|
bmT, err := dilate(nil, s, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st dilate")
|
|
}
|
|
d, err = dilate(d, bmT, selV)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd dilate")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func erode(d, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
const processName = "erode"
|
|
var (
|
|
err error
|
|
t *Bitmap
|
|
)
|
|
d, err = processMorphArgs1(d, s, sel, &t)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
if err = d.setAll(); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
var selData SelectionValue
|
|
for i := 0; i < sel.Height; i++ {
|
|
for j := 0; j < sel.Width; j++ {
|
|
selData = sel.Data[i][j]
|
|
if selData == SelHit {
|
|
err = rasterOperation(d, sel.Cx-j, sel.Cy-i, s.Width, s.Height, PixSrcAndDst, t, 0, 0)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if MorphBC == SymmetricMorphBC {
|
|
return d, nil
|
|
}
|
|
// If the MorphBoundaryCondition is set to the AsymmetricMorphBC
|
|
// then the erode function should set the pixels surrounding the image to 'OFF'.
|
|
xp, yp, xn, yn := sel.findMaxTranslations()
|
|
if xp > 0 {
|
|
if err = d.RasterOperation(0, 0, xp, s.Height, PixClr, nil, 0, 0); err != nil {
|
|
return nil, errors.Wrap(err, processName, "xp > 0")
|
|
}
|
|
}
|
|
if xn > 0 {
|
|
if err = d.RasterOperation(s.Width-xn, 0, xn, s.Height, PixClr, nil, 0, 0); err != nil {
|
|
return nil, errors.Wrap(err, processName, "xn > 0")
|
|
}
|
|
}
|
|
if yp > 0 {
|
|
if err = d.RasterOperation(0, 0, s.Width, yp, PixClr, nil, 0, 0); err != nil {
|
|
return nil, errors.Wrap(err, processName, "yp > 0")
|
|
}
|
|
}
|
|
if yn > 0 {
|
|
if err = d.RasterOperation(0, s.Height-yn, s.Width, yn, PixClr, nil, 0, 0); err != nil {
|
|
return nil, errors.Wrap(err, processName, "yn > 0")
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func erodeBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
const processName = "erodeBrick"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source not defined")
|
|
}
|
|
if hSize < 1 || vSize < 1 {
|
|
return nil, errors.Error(processName, "hsize and vsize are not greater than or equal to 1")
|
|
}
|
|
|
|
if hSize == 1 && vSize == 1 {
|
|
bm, err := copyBitmap(d, s)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 && vSize == 1")
|
|
}
|
|
return bm, nil
|
|
}
|
|
if hSize == 1 || vSize == 1 {
|
|
sel := SelCreateBrick(vSize, hSize, vSize/2, hSize/2, SelHit)
|
|
d, err := erode(d, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 || vSize == 1")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
selH := SelCreateBrick(1, hSize, 0, hSize/2, SelHit)
|
|
selV := SelCreateBrick(vSize, 1, vSize/2, 0, SelHit)
|
|
bmT, err := erode(nil, s, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st erode")
|
|
}
|
|
d, err = erode(d, bmT, selV)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd erode")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func expandReplicate(s *Bitmap, factor int) (*Bitmap, error) {
|
|
const processName = "expandReplicate"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source not defined")
|
|
}
|
|
if factor <= 0 {
|
|
return nil, errors.Error(processName, "invalid factor - <= 0")
|
|
}
|
|
if factor == 1 {
|
|
bm, err := copyBitmap(nil, s)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "factor = 1")
|
|
}
|
|
return bm, nil
|
|
}
|
|
bm, err := expandBinaryReplicate(s, factor, factor)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
return bm, nil
|
|
}
|
|
|
|
func makePixelCentroidTab8() []int {
|
|
tab := make([]int, 256)
|
|
tab[0] = 0
|
|
tab[1] = 7
|
|
var i int
|
|
for i = 2; i < 4; i++ {
|
|
tab[i] = tab[i-2] + 6
|
|
}
|
|
for i = 4; i < 8; i++ {
|
|
tab[i] = tab[i-4] + 5
|
|
}
|
|
for i = 8; i < 16; i++ {
|
|
tab[i] = tab[i-8] + 4
|
|
}
|
|
for i = 16; i < 32; i++ {
|
|
tab[i] = tab[i-16] + 3
|
|
}
|
|
for i = 32; i < 64; i++ {
|
|
tab[i] = tab[i-32] + 2
|
|
}
|
|
for i = 64; i < 128; i++ {
|
|
tab[i] = tab[i-64] + 1
|
|
}
|
|
for i = 128; i < 256; i++ {
|
|
tab[i] = tab[i-128]
|
|
}
|
|
return tab
|
|
}
|
|
|
|
func makePixelSumTab8() []int {
|
|
tab := make([]int, 256)
|
|
for i := 0; i <= 0xff; i++ {
|
|
i := byte(i)
|
|
tab[i] = int(i&0x1) + (int(i>>1) & 0x1) + (int(i>>2) & 0x1) + (int(i>>3) & 0x1) +
|
|
(int(i>>4) & 0x1) + (int(i>>5) & 0x1) + (int(i>>6) & 0x1) + (int(i>>7) & 0x1)
|
|
}
|
|
return tab
|
|
}
|
|
|
|
func morphSequence(s *Bitmap, sequence ...MorphProcess) (d *Bitmap, err error) {
|
|
const processName = "morphSequence"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "morphSequence source bitmap not defined")
|
|
}
|
|
if len(sequence) == 0 {
|
|
return nil, errors.Error(processName, "morphSequence, sequence not defined")
|
|
}
|
|
|
|
if err = verifyMorphProcesses(sequence...); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
var w, h, border int
|
|
d = s.Copy()
|
|
for _, process := range sequence {
|
|
switch process.Operation {
|
|
case MopDilation:
|
|
// 'd' character
|
|
w, h = process.getWidthHeight()
|
|
d, err = DilateBrick(nil, d, w, h)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopErosion:
|
|
// 'e' character
|
|
w, h = process.getWidthHeight()
|
|
d, err = erodeBrick(nil, d, w, h)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopOpening:
|
|
// 'o' character
|
|
w, h = process.getWidthHeight()
|
|
d, err = openBrick(nil, d, w, h)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopClosing:
|
|
// 'c' character
|
|
w, h = process.getWidthHeight()
|
|
d, err = closeSafeBrick(nil, d, w, h)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopRankBinaryReduction:
|
|
// 'r' character
|
|
d, err = reduceRankBinaryCascade(d, process.Arguments...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopReplicativeBinaryExpansion:
|
|
// 'x' character
|
|
d, err = expandReplicate(d, process.Arguments[0])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
case MopAddBorder:
|
|
// 'b' character
|
|
border = process.Arguments[0]
|
|
d, err = d.AddBorder(border, 0)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
default:
|
|
return nil, errors.Error(processName, "invalid morphOperation provided to the sequence")
|
|
}
|
|
}
|
|
|
|
if border > 0 {
|
|
d, err = d.RemoveBorder(border)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "border > 0")
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// open is the function based on the leptonica 'pixOpen' - morph.c:422
|
|
// this function does a sequence of erode and dilate morph operations.
|
|
func open(d, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
const processName = "open"
|
|
var err error
|
|
|
|
d, err = processMorphArgs2(d, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
t, err := erode(nil, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
_, err = dilate(d, t, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func openBrick(d, s *Bitmap, hSize, vSize int) (*Bitmap, error) {
|
|
const processName = "openBrick"
|
|
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source bitmap not defined")
|
|
}
|
|
if hSize < 1 && vSize < 1 {
|
|
return nil, errors.Error(processName, "hSize < 1 && vSize < 1")
|
|
}
|
|
|
|
if hSize == 1 && vSize == 1 {
|
|
return s.Copy(), nil
|
|
}
|
|
|
|
if hSize == 1 || vSize == 1 {
|
|
var err error
|
|
sel := SelCreateBrick(vSize, hSize, vSize/2, hSize/2, SelHit)
|
|
d, err = open(d, s, sel)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "hSize == 1 || vSize == 1")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
selH := SelCreateBrick(1, hSize, 0, hSize/2, SelHit)
|
|
selV := SelCreateBrick(vSize, 1, vSize/2, 0, SelHit)
|
|
t, err := erode(nil, s, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st erode")
|
|
}
|
|
d, err = erode(d, t, selV)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd erode")
|
|
}
|
|
|
|
_, err = dilate(t, d, selH)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "1st dilate")
|
|
}
|
|
_, err = dilate(d, t, selV)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "2nd dilate")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// processMorphArgs1 used for generic erosion, dilation and HMT.
|
|
func processMorphArgs1(d *Bitmap, s *Bitmap, sel *Selection, t **Bitmap) (*Bitmap, error) {
|
|
const processName = "processMorphArgs1"
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "MorphArgs1 's' not defined")
|
|
}
|
|
if sel == nil {
|
|
return nil, errors.Error(processName, "MorhpArgs1 'sel' not defined")
|
|
}
|
|
|
|
sx, sy := sel.Height, sel.Width
|
|
if sx == 0 || sy == 0 {
|
|
return nil, errors.Error(processName, "selection of size 0")
|
|
}
|
|
|
|
if d == nil {
|
|
d = s.createTemplate()
|
|
*t = s
|
|
return d, nil
|
|
}
|
|
d.Width = s.Width
|
|
d.Height = s.Height
|
|
d.RowStride = s.RowStride
|
|
d.Color = s.Color
|
|
d.Data = make([]byte, s.RowStride*s.Height)
|
|
if d == s {
|
|
*t = s.Copy()
|
|
} else {
|
|
*t = s
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func processMorphArgs2(d, s *Bitmap, sel *Selection) (*Bitmap, error) {
|
|
const processName = "processMorphArgs2"
|
|
var sx, sy int
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source bitmap is nil")
|
|
}
|
|
if sel == nil {
|
|
return nil, errors.Error(processName, "sel not defined")
|
|
}
|
|
sx = sel.Width
|
|
sy = sel.Height
|
|
if sx == 0 || sy == 0 {
|
|
return nil, errors.Error(processName, "sel of size 0")
|
|
}
|
|
if d == nil {
|
|
return s.createTemplate(), nil
|
|
}
|
|
if err := d.resizeImageData(s); err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func verifyMorphProcesses(processes ...MorphProcess) (err error) {
|
|
const processName = "verifyMorphProcesses"
|
|
var netRed, border int
|
|
for i, process := range processes {
|
|
if err = process.verify(i, &netRed, &border); err != nil {
|
|
return errors.Wrap(err, processName, "")
|
|
}
|
|
}
|
|
if border != 0 && netRed != 0 {
|
|
return errors.Error(processName, "Morph sequence - border added but net reduction not 0")
|
|
}
|
|
return nil
|
|
}
|