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

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
}