Jacek Kucharczyk 29efa30439
JBIG2 Encoder support for inserting binary images into PDF (#288)
* Added JBIG2 PDF support
* Added JBIG2 Encoder binary image requirements
* PR #288 revision r1 fixes
* PR #288 revision r2 fixes
2020-04-03 20:54:59 +00:00

1217 lines
31 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"
"image/color"
"math"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/internal/jbig2/errors"
"github.com/unidoc/unipdf/v3/internal/jbig2/reader"
"github.com/unidoc/unipdf/v3/internal/jbig2/writer"
)
// tab8 contains number of '1' bits in each possible 8 bit value stored at it's index.
var tab8 [256]uint8
func init() {
for i := 0; i < 256; i++ {
tab8[i] = uint8(i&0x1) +
(uint8(i>>1) & 0x1) +
(uint8(i>>2) & 0x1) +
(uint8(i>>3) & 0x1) +
(uint8(i>>4) & 0x1) +
(uint8(i>>5) & 0x1) +
(uint8(i>>6) & 0x1) +
(uint8(i>>7) & 0x1)
}
}
// Bitmap is the jbig2 bitmap representation.
type Bitmap struct {
// Width and Height represents bitmap dimensions.
Width, Height int
// BitmapNumber is the bitmap's id number.
BitmapNumber int
// RowStride is the number of bytes set per row.
RowStride int
// Data saves the bits data for the bitmap.
Data []byte
// Color is the bitmap's color interpretation.
Color Color
// Special instructions for I/O.
Special int
// Text string associated with the pix.
Text string
// The XResolution and YResolution are the
// image resolution parameters at width and height.
XResolution, YResolution int
}
// New creates new bitmap with the parameters as provided in the arguments.
func New(width, height int) *Bitmap {
bm := newBitmap(width, height)
bm.Data = make([]byte, height*bm.RowStride)
return bm
}
// Copy the bitmap 's' into bitmap 'd'. If 'd' is nil, it is created by the function.
func Copy(d, s *Bitmap) (*Bitmap, error) {
return copyBitmap(d, s)
}
func newBitmap(width, height int) *Bitmap {
return &Bitmap{
Width: width,
Height: height,
RowStride: (width + 7) >> 3,
}
}
// NewWithData creates new bitmap with the provided 'width', 'height' and the byte slice 'data'.
func NewWithData(width, height int, data []byte) (*Bitmap, error) {
const processName = "NewWithData"
bm := newBitmap(width, height)
bm.Data = data
if len(data) < height*bm.RowStride {
return nil, errors.Errorf(processName, "invalid data length: %d - should be: %d", len(data), height*bm.RowStride)
}
return bm, nil
}
// NewWithUnpaddedData creates new bitmap with provided 'width', 'height' and the byte slice 'data' that doesn't
// contains paddings on the last byte of each row.
// This function adds the padding to the bitmap data.
func NewWithUnpaddedData(width, height int, data []byte) (*Bitmap, error) {
const processName = "NewWithUnpaddedData"
bm := newBitmap(width, height)
bm.Data = data
if dataLen := ((width * height) + 7) >> 3; len(data) < dataLen {
return nil, errors.Errorf(processName, "invalid data length: '%d'. The data should contain at least: '%d' bytes", len(data), dataLen)
}
if err := bm.addPadBits(); err != nil {
return nil, errors.Wrap(err, processName, "")
}
return bm, nil
}
// AddBorder creates a new bitmap with the border of size 'borderSize'. If the 'borderSize' is different than zero
// the resultant bitmap dimensions would increase by width += 2* borderSize, height += 2*borderSize.
// The value 'val' represents the binary bit 'value' of the border - '0' and '1'
func (b *Bitmap) AddBorder(borderSize, val int) (*Bitmap, error) {
if borderSize == 0 {
return b.Copy(), nil
}
d, err := b.addBorderGeneral(borderSize, borderSize, borderSize, borderSize, val)
if err != nil {
return nil, errors.Wrap(err, "AddBorder", "")
}
return d, nil
}
// AddBorderGeneral creates new bitmap on the base of the bitmap 'b' with the border of size for each side
// 'left','right','top','bot'. The 'val' sets the border white (0) or black (1).
func (b *Bitmap) AddBorderGeneral(left, right, top, bot int, val int) (*Bitmap, error) {
return b.addBorderGeneral(left, right, top, bot, val)
}
// And does the raster operation 'AND' on the provided bitmaps 'b' and 's'.
func (b *Bitmap) And(s *Bitmap) (d *Bitmap, err error) {
const processName = "Bitmap.And"
if b == nil {
return nil, errors.Error(processName, "'bitmap 'b' is nil")
}
if s == nil {
return nil, errors.Error(processName, "bitmap 's' is nil")
}
if !b.SizesEqual(s) {
common.Log.Debug("%s - Bitmap 's' is not equal size with 'b'", processName)
}
if d, err = copyBitmap(d, b); err != nil {
return nil, errors.Wrap(err, processName, "can't create 'd' bitmap")
}
if err = d.RasterOperation(0, 0, d.Width, d.Height, PixSrcAndDst, s, 0, 0); err != nil {
return nil, errors.Wrap(err, processName, "")
}
return d, nil
}
// ClipRectangle clips the 'b' Bitmap to the 'box' with relatively defined coordinates.
// If the box is not contained within the pix the 'box' is clipped to the 'b' size.
func (b *Bitmap) ClipRectangle(box *image.Rectangle) (d *Bitmap, boxC *image.Rectangle, err error) {
const processName = "ClipRectangle"
if box == nil {
return nil, nil, errors.Error(processName, "box is not defined")
}
w, h := b.Width, b.Height
sRect := image.Rect(0, 0, w, h)
if !box.Overlaps(sRect) {
return nil, nil, errors.Error(processName, "box doesn't overlap b")
}
boxCT := box.Intersect(sRect)
bx, by := boxCT.Min.X, boxCT.Min.Y
bw, bh := boxCT.Dx(), boxCT.Dy()
d = New(bw, bh)
d.Text = b.Text
if err = d.RasterOperation(0, 0, bw, bh, PixSrc, b, bx, by); err != nil {
return nil, nil, errors.Wrap(err, processName, "PixSrc to clipped")
}
boxC = &boxCT
return d, boxC, nil
}
// Copy gets a copy of the 'b' bitmap.
func (b *Bitmap) Copy() *Bitmap {
data := make([]byte, len(b.Data))
copy(data, b.Data)
return &Bitmap{
Width: b.Width,
Height: b.Height,
RowStride: b.RowStride,
Data: data,
Color: b.Color,
Text: b.Text,
BitmapNumber: b.BitmapNumber,
Special: b.Special,
}
}
// CountPixels counts the pixels for the bitmap 'b'.
func (b *Bitmap) CountPixels() int {
return b.countPixels()
}
// CreateTemplate creates a copy template bitmap on the base of the
// bitmap 'b'. A template has the same parameters as bitmap 'b', but contains empty Data.
func (b *Bitmap) CreateTemplate() *Bitmap {
return b.createTemplate()
}
// Equals checks if all the pixels in the 'b' bitmap are equals to the 's' bitmap.
func (b *Bitmap) Equals(s *Bitmap) bool {
if len(b.Data) != len(s.Data) || b.Width != s.Width || b.Height != s.Height {
return false
}
for y := 0; y < b.Height; y++ {
lineIndex := y * b.RowStride
for i := 0; i < b.RowStride; i++ {
if b.Data[lineIndex+i] != s.Data[lineIndex+i] {
return false
}
}
}
return true
}
// Equivalent checks if the bitmaps 'b' and 's' are equivalent
// from the visual point of view.
func (b *Bitmap) Equivalent(s *Bitmap) bool {
return b.equivalent(s)
}
// GetBitOffset gets the bit offset at the 'x' coordinate.
func (b *Bitmap) GetBitOffset(x int) int {
return x & 0x07
}
// GetByte gets and returns the byte at given 'index'.
func (b *Bitmap) GetByte(index int) (byte, error) {
if index > len(b.Data)-1 || index < 0 {
return 0, errors.Errorf("GetByte", "index: %d out of range", index)
}
return b.Data[index], nil
}
// GetByteIndex gets the byte index from the bitmap at coordinates 'x','y'.
func (b *Bitmap) GetByteIndex(x, y int) int {
return y*b.RowStride + (x >> 3)
}
// GetChocolateData gets bitmap data as a byte slice with Chocolate bit interpretation.
// 'Chocolate' data is the bit interpretation where the 0'th bit means white and the 1'th bit means black.
// The naming convention based on the: `https://en.wikipedia.org/wiki/Binary_image#Interpretation` page.
func (b *Bitmap) GetChocolateData() []byte {
if b.Color == Vanilla {
b.inverseData()
}
return b.Data
}
// GetPixel gets the pixel value at the coordinates 'x', 'y'.
func (b *Bitmap) GetPixel(x, y int) bool {
i := b.GetByteIndex(x, y)
o := b.GetBitOffset(x)
shift := uint(7 - o)
if i > len(b.Data)-1 {
common.Log.Debug("Trying to get pixel out of the data range. x: '%d', y:'%d', bm: '%s'", x, y, b)
return false
}
if (b.Data[i]>>shift)&0x01 >= 1 {
return true
}
return false
}
// GetUnpaddedData gets the data without row stride padding.
// The unpadded data contains bitmap.Height * bitmap.Width bits with
// optional last byte padding.
func (b *Bitmap) GetUnpaddedData() ([]byte, error) {
padding := uint(b.Width & 0x07)
if padding == 0 {
return b.Data, nil
}
size := b.Width * b.Height
if size%8 != 0 {
size >>= 3
size++
} else {
size >>= 3
}
data := make([]byte, size)
w := writer.NewMSB(data)
const processName = "GetUnpaddedData"
for y := 0; y < b.Height; y++ {
// btIndex is the byte index per row.
for btIndex := 0; btIndex < b.RowStride; btIndex++ {
bt := b.Data[y*b.RowStride+btIndex]
if btIndex != b.RowStride-1 {
err := w.WriteByte(bt)
if err != nil {
return nil, errors.Wrap(err, processName, "")
}
continue
}
for i := uint(0); i < padding; i++ {
err := w.WriteBit(int(bt >> (7 - i) & 0x01))
if err != nil {
return nil, errors.Wrap(err, processName, "")
}
}
}
}
return data, nil
}
// GetVanillaData gets bitmap data as a byte slice with Vanilla bit interpretation.
// 'Vanilla' is the bit interpretation where the 0'th bit means black and 1'th bit means white.
// The naming convention based on the `https://en.wikipedia.org/wiki/Binary_image#Interpretation` page.
func (b *Bitmap) GetVanillaData() []byte {
if b.Color == Chocolate {
b.inverseData()
}
return b.Data
}
// InverseData inverses the data color interpretation.
func (b *Bitmap) InverseData() {
b.inverseData()
}
// RemoveBorder create a new bitmap based on the 'b' with removed border of size 'borderSize'.
func (b *Bitmap) RemoveBorder(borderSize int) (*Bitmap, error) {
if borderSize == 0 {
return b.Copy(), nil
}
d, err := b.removeBorderGeneral(borderSize, borderSize, borderSize, borderSize)
if err != nil {
return nil, errors.Wrap(err, "RemoveBorder", "")
}
return d, nil
}
// RemoveBorderGeneral creates a new bitmap with removed border of size 'left', 'right', 'top', 'bot'.
// The resultant bitmap dimensions would be smaller by the value of border size.
func (b *Bitmap) RemoveBorderGeneral(left, right, top, bot int) (*Bitmap, error) {
return b.removeBorderGeneral(left, right, top, bot)
}
// SetPixel sets the pixel at 'x', 'y' coordinates with the value of 'pixel'.
// Returns an error if the index is out of range.
func (b *Bitmap) SetPixel(x, y int, pixel byte) error {
i := b.GetByteIndex(x, y)
if i > len(b.Data)-1 {
return errors.Errorf("SetPixel", "index out of range: %d", i)
}
o := b.GetBitOffset(x)
shift := uint(7 - o)
src := b.Data[i]
var result byte
if pixel == 1 {
result = src | (pixel & 0x01 << shift)
} else {
result = src & ^(1 << shift)
}
b.Data[i] = result
return nil
}
// SetByte sets the byte at 'index' with value 'v'.
// Returns an error if the index is out of range.
func (b *Bitmap) SetByte(index int, v byte) error {
if index > len(b.Data)-1 || index < 0 {
return errors.Errorf("SetByte", "index out of range: %d", index)
}
b.Data[index] = v
return nil
}
// SetDefaultPixel sets all bits within bitmap to '1'.
func (b *Bitmap) SetDefaultPixel() {
for i := range b.Data {
b.Data[i] = byte(0xff)
}
}
// SetPadBits sets the pad bits for the current bitmap.
func (b *Bitmap) SetPadBits(value int) {
b.setPadBits(value)
}
// SizesEqual checks if the bitmaps are of the same size.
func (b *Bitmap) SizesEqual(s *Bitmap) bool {
if b == s {
return true
}
if b.Width != s.Width || b.Height != s.Height {
return false
}
return true
}
// String implements the Stringer interface.
func (b *Bitmap) String() string {
var s = "\n"
for y := 0; y < b.Height; y++ {
var row string
for x := 0; x < b.Width; x++ {
pix := b.GetPixel(x, y)
if pix {
row += "1"
} else {
row += "0"
}
}
s += row + "\n"
}
return s
}
// ThresholdPixelSum checks if the number of the 'ON' pixels is above the provided 'thresh' threshold.
// If the on pixel count > thresh the function returns quckly.
func (b *Bitmap) ThresholdPixelSum(thresh int, tab8 []int) (above bool, err error) {
const processName = "Bitmap.ThresholdPixelSum"
if tab8 == nil {
tab8 = makePixelSumTab8()
}
fullBytes := b.Width >> 3
endBits := b.Width & 7
endMask := byte(0xff << uint(8-endBits))
var (
i, j, lineIndex, count int
bt byte
)
for i = 0; i < b.Height; i++ {
lineIndex = b.RowStride * i
for j = 0; j < fullBytes; j++ {
bt, err = b.GetByte(lineIndex + j)
if err != nil {
return false, errors.Wrap(err, processName, "fullByte")
}
count += tab8[bt]
}
if endBits != 0 {
bt, err = b.GetByte(lineIndex + j)
if err != nil {
return false, errors.Wrap(err, processName, "partialByte")
}
bt &= endMask
count += tab8[bt]
}
if count > thresh {
return true, nil
}
}
return above, nil
}
// ToImage gets the bitmap data and store in the image.Image.
func (b *Bitmap) ToImage() image.Image {
img := image.NewGray(image.Rect(0, 0, b.Width-1, b.Height-1))
for x := 0; x < b.Width; x++ {
for y := 0; y < b.Height; y++ {
c := color.Black
if b.GetPixel(x, y) {
c = color.White
}
img.Set(x, y, c)
}
}
return img
}
// Zero check if there is no 'ONE' pixels.
func (b *Bitmap) Zero() bool {
fullBytes := b.Width / 8
endBits := b.Width & 7
var endMask byte
if endBits != 0 {
endMask = byte(0xff << uint(8-endBits))
}
var line, i, j int
for i = 0; i < b.Height; i++ {
line = b.RowStride * i
for j = 0; j < fullBytes; j, line = j+1, line+1 {
if b.Data[line] != 0 {
return false
}
}
if endBits > 0 {
if b.Data[line]&endMask != 0 {
return false
}
}
}
return true
}
func (b *Bitmap) addBorderGeneral(left, right, top, bot int, val int) (*Bitmap, error) {
const processName = "addBorderGeneral"
if left < 0 || right < 0 || top < 0 || bot < 0 {
return nil, errors.Error(processName, "negative border added")
}
ws, hs := b.Width, b.Height
wd := ws + left + right
hd := hs + top + bot
bd := New(wd, hd)
bd.Color = b.Color
op := PixClr
if val > 0 {
op = PixSet
}
err := bd.RasterOperation(0, 0, left, hd, op, nil, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "left")
}
err = bd.RasterOperation(wd-right, 0, right, hd, op, nil, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "right")
}
err = bd.RasterOperation(0, 0, wd, top, op, nil, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "top")
}
err = bd.RasterOperation(0, hd-bot, wd, bot, op, nil, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "bottom")
}
// copy the pixels into the interior
err = bd.RasterOperation(left, top, ws, hs, PixSrc, b, 0, 0)
if err != nil {
return nil, errors.Wrap(err, processName, "copy")
}
return bd, nil
}
// addPadBits creates new data byte slice that contains extra padding on the last byte for each row.
func (b *Bitmap) addPadBits() (err error) {
const processName = "bitmap.addPadBits"
endbits := b.Width % 8
if endbits == 0 {
// no partial words
return nil
}
fullBytes := b.Width / 8
// mask := rmaskByte[endbits]
r := reader.New(b.Data)
data := make([]byte, b.Height*b.RowStride)
w := writer.NewMSB(data)
temp := make([]byte, fullBytes)
var (
i int
bits uint64
)
for i = 0; i < b.Height; i++ {
// iterate over full bytes
if _, err = r.Read(temp); err != nil {
return errors.Wrap(err, processName, "full byte")
}
if _, err = w.Write(temp); err != nil {
return errors.Wrap(err, processName, "full bytes")
}
// read unused bits
if bits, err = r.ReadBits(byte(endbits)); err != nil {
return errors.Wrap(err, processName, "skipping bits")
}
if err = w.WriteByte(byte(bits) << uint(8-endbits)); err != nil {
return errors.Wrap(err, processName, "last byte")
}
}
b.Data = w.Data()
return nil
}
func (b *Bitmap) clearAll() error {
return b.RasterOperation(0, 0, b.Width, b.Height, PixClr, nil, 0, 0)
}
// clipRectangle clips the bitmap 'b' with the bounds provided by the 'box' argument.
// The optional 'clipped' image.Rectangle argument would be changed for the actual box of clipped bitmap.
// The function returns the clipped bitmap. If the 'box' doesn't intersect with the 'b' bitmap the function returns nil bitmap.
func (b *Bitmap) clipRectangle(box, boxClipped *image.Rectangle) (clipped *Bitmap, err error) {
const processName = "clipRectangle"
if box == nil {
return nil, errors.Error(processName, "provided nil 'box'")
}
w, h := b.Width, b.Height
clippedTemp, err := ClipBoxToRectangle(box, w, h)
if err != nil {
common.Log.Warning("'box' doesn't overlap bitmap 'b': %v", err)
return nil, nil
}
bx, by := clippedTemp.Min.X, clippedTemp.Min.Y
bw, bh := clippedTemp.Max.X-clippedTemp.Min.X, clippedTemp.Max.Y-clippedTemp.Min.Y
clipped = New(bw, bh)
clipped.Text = b.Text
if err = clipped.RasterOperation(0, 0, bw, bh, PixSrc, b, bx, by); err != nil {
return nil, errors.Wrap(err, processName, "")
}
if boxClipped != nil {
*boxClipped = *clippedTemp
}
return clipped, nil
}
func (b *Bitmap) countPixels() int {
var (
sum int
endmask uint8
bt byte
btIndex int
)
fullBytes := b.RowStride
padding := uint(b.Width & 0x07)
if padding != 0 {
endmask = uint8((0xff << (8 - padding)) & 0xff)
fullBytes--
}
for y := 0; y < b.Height; y++ {
for btIndex = 0; btIndex < fullBytes; btIndex++ {
bt = b.Data[y*b.RowStride+btIndex]
sum += int(tab8[bt])
}
if padding != 0 {
sum += int(tab8[b.Data[y*b.RowStride+btIndex]&endmask])
}
}
return sum
}
func (b *Bitmap) createTemplate() *Bitmap {
return &Bitmap{
Width: b.Width,
Height: b.Height,
RowStride: b.RowStride,
Color: b.Color,
Text: b.Text,
BitmapNumber: b.BitmapNumber,
Special: b.Special,
Data: make([]byte, len(b.Data)),
}
}
func (b *Bitmap) equivalent(s *Bitmap) bool {
if b == s {
return true
}
if !b.SizesEqual(s) {
return false
}
// get the XOR of the bitmaps 'b' and 's'
xor := combineBitmap(b, s, CmbOpXor)
// count the pixels for the first bitmap.
pixelCount := b.countPixels()
thresh := int(0.25 * float32(pixelCount))
// if the symbols are significantly different end fast.
if xor.thresholdPixelSum(thresh) {
return false
}
var (
parsedPixCounts [9][9]int
horizontalParsedPix [18][9]int
verticalParsedPix [9][18]int
horizontalModuleCtr int
verticalModuleCtr int
)
divider := 9
verticalPart := b.Height / divider
horizontalPart := b.Width / divider
vp, hp := verticalPart/2, horizontalPart/2
if verticalPart < horizontalPart {
vp = horizontalPart / 2
hp = verticalPart / 2
}
pointThresh := float64(vp) * float64(hp) * math.Pi
vLineThresh := int(float64(verticalPart*horizontalPart/2) * 0.9)
hLineThresh := int(float64(horizontalPart*verticalPart/2) * 0.9)
for horizontalPosition := 0; horizontalPosition < divider; horizontalPosition++ {
horizontalStart := horizontalPart*horizontalPosition + horizontalModuleCtr
var horizontalEnd int
if horizontalPosition == divider-1 {
horizontalModuleCtr = 0
horizontalEnd = b.Width
} else {
horizontalEnd = horizontalStart + horizontalPart
if ((b.Width - horizontalModuleCtr) % divider) > 0 {
horizontalModuleCtr++
horizontalEnd++
}
}
for verticalPosition := 0; verticalPosition < divider; verticalPosition++ {
verticalStart := verticalPart*verticalPosition + verticalModuleCtr
var verticalEnd int
if verticalPosition == divider-1 {
verticalModuleCtr = 0
verticalEnd = b.Height
} else {
verticalEnd = verticalStart + verticalPart
if (b.Height-verticalModuleCtr)%divider > 0 {
verticalModuleCtr++
verticalEnd++
}
}
var leftCount, rightCount, downCount, upCount int
horizontalCenter := (horizontalStart + horizontalEnd) / 2
verticalCenter := (verticalStart + verticalEnd) / 2
for h := horizontalStart; h < horizontalEnd; h++ {
for v := verticalStart; v < verticalEnd; v++ {
if xor.GetPixel(h, v) {
if h < horizontalCenter {
leftCount++
} else {
rightCount++
}
if v < verticalCenter {
upCount++
} else {
downCount++
}
}
}
}
parsedPixCounts[horizontalPosition][verticalPosition] = leftCount + rightCount
horizontalParsedPix[horizontalPosition*2][verticalPosition] = leftCount
horizontalParsedPix[horizontalPosition*2+1][verticalPosition] = rightCount
verticalParsedPix[horizontalPosition][verticalPosition*2] = upCount
verticalParsedPix[horizontalPosition][verticalPosition*2+1] = downCount
}
}
for i := 0; i < divider*2-1; i++ {
for j := 0; j < (divider - 1); j++ {
var horizontalSum int
for x := 0; x < 2; x++ {
for y := 0; y < 2; y++ {
horizontalSum += horizontalParsedPix[i+x][j+y]
}
}
if horizontalSum > hLineThresh {
return false
}
}
}
for i := 0; i < (divider - 1); i++ {
for j := 0; j < ((divider * 2) - 1); j++ {
var verticalSum int
for x := 0; x < 2; x++ {
for y := 0; y < 2; y++ {
verticalSum += verticalParsedPix[i+x][j+y]
}
}
if verticalSum > vLineThresh {
return false
}
}
}
// check for cross lines
for i := 0; i < (divider - 2); i++ {
for j := 0; j < (divider - 2); j++ {
var leftCross, rightCross int
for x := 0; x < 3; x++ {
for y := 0; y < 3; y++ {
if x == y {
leftCross += parsedPixCounts[i+x][j+y]
}
if (2 - x) == y {
rightCross += parsedPixCounts[i+x][j+y]
}
}
}
if leftCross > hLineThresh || rightCross > hLineThresh {
return false
}
}
}
for i := 0; i < (divider - 1); i++ {
for j := 0; j < (divider - 1); j++ {
var sum int
for x := 0; x < 2; x++ {
for y := 0; y < 2; y++ {
sum += parsedPixCounts[i+x][j+y]
}
}
if float64(sum) > pointThresh {
return false
}
}
}
return true
}
func (b *Bitmap) inverseData() {
if err := b.RasterOperation(0, 0, b.Width, b.Height, PixNotDst, nil, 0, 0); err != nil {
common.Log.Debug("Inverse data failed: '%v'", err)
}
// flip the color interpretation
if b.Color == Chocolate {
b.Color = Vanilla
} else {
b.Color = Chocolate
}
}
// nextOnPixel
func (b *Bitmap) nextOnPixel(xStart, yStart int) (pt image.Point, ok bool, err error) {
const processName = "nextOnPixel"
pt, ok, err = b.nextOnPixelLow(b.Width, b.Height, b.RowStride, xStart, yStart)
if err != nil {
return pt, false, errors.Wrap(err, processName, "")
}
return pt, ok, nil
}
func (b *Bitmap) nextOnPixelLow(w, h, wpl, xStart, yStart int) (pt image.Point, ok bool, err error) {
const processName = "Bitmap.nextOnPixelLow"
var (
x int
bt byte
)
line := yStart * wpl
index := line + (xStart / 8)
if bt, err = b.GetByte(index); err != nil {
return pt, false, errors.Wrap(err, processName, "xStart and yStart out of range")
}
// if the 'bt' byte is different than 0, it must contain 'ON' pixel.
// search the byte place.
if bt != 0 {
xEnd := xStart - (xStart % 8) + 7
for x = xStart; x <= xEnd && x < w; x++ {
if b.GetPixel(x, yStart) {
pt.X = x
pt.Y = yStart
return pt, true, nil
}
}
}
// continue with the rest of the line.
startByte := (xStart / 8) + 1
x = 8 * startByte
var i int
for index = line + startByte; x < w; index, x = index+1, x+8 {
if bt, err = b.GetByte(index); err != nil {
return pt, false, errors.Wrap(err, processName, "rest of the line byte")
}
if bt == 0 {
continue
}
for i = 0; i < 8 && x < w; i, x = i+1, x+1 {
if b.GetPixel(x, yStart) {
pt.X = x
pt.Y = yStart
return pt, true, nil
}
}
}
// search till the end of the bitmap data.
for y := yStart + 1; y < h; y++ {
line = y * wpl
for index, x = line, 0; x < w; index, x = index+1, x+8 {
if bt, err = b.GetByte(index); err != nil {
return pt, false, errors.Wrap(err, processName, "following lines")
}
// if the byte is different than 0x00 it must contain at least one 'ON' bit.
if bt == 0 {
continue
}
for i = 0; i < 8 && x < w; i, x = i+1, x+1 {
if b.GetPixel(x, y) {
pt.X = x
pt.Y = y
return pt, true, nil
}
}
}
}
return pt, false, nil
}
func (b *Bitmap) removeBorderGeneral(left, right, top, bot int) (*Bitmap, error) {
const processName = "removeBorderGeneral"
if left < 0 || right < 0 || top < 0 || bot < 0 {
return nil, errors.Error(processName, "negative broder remove values")
}
ws, hs := b.Width, b.Height
wd := ws - left - right
hd := hs - top - bot
if wd <= 0 {
return nil, errors.Errorf(processName, "width: %d must be > 0", wd)
}
if hd <= 0 {
return nil, errors.Errorf(processName, "height: %d must be > 0", hd)
}
bm := New(wd, hd)
bm.Color = b.Color
err := bm.RasterOperation(0, 0, wd, hd, PixSrc, b, left, top)
if err != nil {
return nil, errors.Wrap(err, processName, "")
}
return bm, nil
}
func (b *Bitmap) resizeImageData(s *Bitmap) error {
if s == nil {
return errors.Error("resizeImageData", "src is not defined")
}
if b.SizesEqual(s) {
return nil
}
b.Data = make([]byte, len(s.Data))
b.Width = s.Width
b.Height = s.Height
b.RowStride = s.RowStride
// NOTE: if resolution included, set also resolution
return nil
}
func (b *Bitmap) setAll() error {
err := rasterOperation(b, 0, 0, b.Width, b.Height, PixSet, nil, 0, 0)
if err != nil {
return errors.Wrap(err, "setAll", "")
}
return nil
}
func (b *Bitmap) setBit(index int) {
b.Data[(index >> 3)] |= 0x80 >> uint(index&7)
}
func (b *Bitmap) setPadBits(val int) {
endbits := 8 - b.Width%8
if endbits == 8 {
// no partial words
return
}
fullBytes := b.Width / 8
mask := rmaskByte[endbits]
if val == 0 {
mask ^= mask
}
var bIndex int
for i := 0; i < b.Height; i++ {
bIndex = i*b.RowStride + fullBytes
if val == 0 {
b.Data[bIndex] &= mask
} else {
b.Data[bIndex] |= mask
}
}
}
func (b *Bitmap) setTwoBytes(index int, tb uint16) error {
if index+1 > len(b.Data)-1 {
return errors.Errorf("setTwoBytes", "index: '%d' out of range", index)
}
b.Data[index] = byte((tb & 0xff00) >> 8)
b.Data[index+1] = byte(tb & 0xff)
return nil
}
func (b *Bitmap) setFourBytes(index int, fb uint32) error {
if index+3 > len(b.Data)-1 {
return errors.Errorf("setFourBytes", "index: '%d' out of range", index)
}
b.Data[index] = byte((fb & 0xff000000) >> 24)
b.Data[index+1] = byte((fb & 0xff0000) >> 16)
b.Data[index+2] = byte((fb & 0xff00) >> 8)
b.Data[index+3] = byte(fb & 0xff)
return nil
}
func (b *Bitmap) setEightBytes(index int, eb uint64) error {
fullBytesNumber := b.RowStride - (index % b.RowStride)
if b.RowStride != b.Width>>3 {
fullBytesNumber--
}
if fullBytesNumber >= 8 {
return b.setEightFullBytes(index, eb)
}
return b.setEightPartlyBytes(index, fullBytesNumber, eb)
}
func (b *Bitmap) setEightPartlyBytes(index, fullBytesNumber int, eb uint64) (err error) {
var (
temp byte
shift int
)
const processName = "setEightPartlyBytes"
for i := 1; i <= fullBytesNumber; i++ {
// eb >> 7 * 8 = 56
// 4 + 7 - 7
shift = 64 - i*8
temp = byte(eb >> uint(shift) & 0xff)
common.Log.Trace("temp: %08b, index: %d, idx: %d, fullBytesNumber: %d, shift: %d", temp, index, index+i-1, fullBytesNumber, shift)
if err = b.SetByte(index+i-1, temp); err != nil {
return errors.Wrap(err, processName, "fullByte")
}
}
padding := b.RowStride*8 - b.Width
if padding == 0 {
return nil
}
shift -= 8
temp = byte(eb>>uint(shift)&0xff) << uint(padding)
if err = b.SetByte(index+fullBytesNumber, temp); err != nil {
return errors.Wrap(err, processName, "padded")
}
return nil
}
func (b *Bitmap) setEightFullBytes(index int, eb uint64) error {
if index+7 > len(b.Data)-1 {
return errors.Error("setEightBytes", "index out of range")
}
b.Data[index] = byte((eb & 0xff00000000000000) >> 56)
b.Data[index+1] = byte((eb & 0xff000000000000) >> 48)
b.Data[index+2] = byte((eb & 0xff0000000000) >> 40)
b.Data[index+3] = byte((eb & 0xff00000000) >> 32)
b.Data[index+4] = byte((eb & 0xff000000) >> 24)
b.Data[index+5] = byte((eb & 0xff0000) >> 16)
b.Data[index+6] = byte((eb & 0xff00) >> 8)
b.Data[index+7] = byte(eb & 0xff)
return nil
}
// thresholdPixelSum this function sums the '1' pixels and returns immediately
// if the count goes above threshold.
// Returns false if the sum is greater then 'thresh' threshold.
func (b *Bitmap) thresholdPixelSum(thresh int) bool {
var (
sum int
endmask uint8
bt byte
btIndex int
)
fullBytes := b.RowStride
padding := uint(b.Width & 0x07)
if padding != 0 {
endmask = uint8((0xff << (8 - padding)) & 0xff)
fullBytes--
}
for y := 0; y < b.Height; y++ {
for btIndex = 0; btIndex < fullBytes; btIndex++ {
bt = b.Data[y*b.RowStride+btIndex]
sum += int(tab8[bt])
}
if padding != 0 {
bt = b.Data[y*b.RowStride+btIndex] & endmask
sum += int(tab8[bt])
}
if sum > thresh {
return true
}
}
return false
}
func copyBitmap(d, s *Bitmap) (*Bitmap, error) {
if s == nil {
return nil, errors.Error("copyBitmap", "source not defined")
}
if s == d {
return d, nil
}
if d == nil {
// create new bitmap if 'd' is nil.
d = s.createTemplate()
copy(d.Data, s.Data)
return d, nil
}
err := d.resizeImageData(s)
if err != nil {
return nil, errors.Wrap(err, "copyBitmap", "")
}
d.Text = s.Text
copy(d.Data, s.Data)
return d, nil
}
func xor(d, b1, b2 *Bitmap) (*Bitmap, error) {
const processName = "bitmap.xor"
if b1 == nil {
return nil, errors.Error(processName, "'b1' is nil")
}
if b2 == nil {
return nil, errors.Error(processName, "'b2' is nil")
}
if d == b2 {
return nil, errors.Error(processName, "'d' == 'b2'")
}
if !b1.SizesEqual(b2) {
common.Log.Debug("%s - Bitmap 'b1' is not equal size with 'b2'", processName)
}
var err error
if d, err = copyBitmap(d, b1); err != nil {
return nil, errors.Wrap(err, processName, "can't create 'd'")
}
if err = d.RasterOperation(0, 0, d.Width, d.Height, PixSrcXorDst, b2, 0, 0); err != nil {
return nil, errors.Wrap(err, processName, "")
}
return d, nil
}
func abs(input int) int {
if input < 0 {
return -input
}
return input
}
func max(x, y int) int {
if x > y {
return x
}
return y
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
func subtract(d, s1, s2 *Bitmap) (*Bitmap, error) {
const processName = "subtract"
if s1 == nil {
return nil, errors.Error(processName, "'s1' is nil")
}
if s2 == nil {
return nil, errors.Error(processName, "'s2' is nil")
}
var err error
switch {
case d == s1:
if err = d.RasterOperation(0, 0, s1.Width, s1.Height, PixNotSrcAndDst, s2, 0, 0); err != nil {
return nil, errors.Wrap(err, processName, "d == s1")
}
case d == s2:
if err = d.RasterOperation(0, 0, s1.Width, s1.Height, PixNotSrcAndDst, s1, 0, 0); err != nil {
return nil, errors.Wrap(err, processName, "d == s2")
}
default:
d, err = copyBitmap(d, s1)
if err != nil {
return nil, errors.Wrap(err, processName, "")
}
if err = d.RasterOperation(0, 0, s1.Width, s1.Height, PixNotSrcAndDst, s2, 0, 0); err != nil {
return nil, errors.Wrap(err, processName, "default")
}
}
return d, nil
}