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>
443 lines
12 KiB
Go
443 lines
12 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"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/unidoc/unipdf/v3/common"
|
|
"github.com/unidoc/unipdf/v3/internal/jbig2/basic"
|
|
"github.com/unidoc/unipdf/v3/internal/jbig2/errors"
|
|
)
|
|
|
|
// Bitmaps is the structure that contains slice of the bitmaps and the bounding boxes.
|
|
// It allows to safely get the Bitmap and the bounding boxes.
|
|
type Bitmaps struct {
|
|
Values []*Bitmap
|
|
Boxes []*image.Rectangle
|
|
}
|
|
|
|
// AddBitmap adds the bitmap 'bm' to the 'b' Bitmaps Values.
|
|
func (b *Bitmaps) AddBitmap(bm *Bitmap) {
|
|
b.Values = append(b.Values, bm)
|
|
}
|
|
|
|
// AddBox adds the 'box' to the 'b' Bitmaps.
|
|
func (b *Bitmaps) AddBox(box *image.Rectangle) {
|
|
b.Boxes = append(b.Boxes, box)
|
|
}
|
|
|
|
// ClipToBitmap returns a Bitmaps where each Bitmap is 'AND'ed with
|
|
// with the associated region stored in box with the 's' Bitmap.
|
|
func (b *Bitmaps) ClipToBitmap(s *Bitmap) (*Bitmaps, error) {
|
|
const processName = "Bitmaps.ClipToBitmap"
|
|
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "Bitmaps not defined")
|
|
}
|
|
if s == nil {
|
|
return nil, errors.Error(processName, "source bitmap not defined")
|
|
}
|
|
|
|
n := len(b.Values)
|
|
d := &Bitmaps{Values: make([]*Bitmap, n), Boxes: make([]*image.Rectangle, n)}
|
|
|
|
var (
|
|
bm, bmC *Bitmap
|
|
box *image.Rectangle
|
|
err error
|
|
)
|
|
|
|
for i := 0; i < n; i++ {
|
|
if bm, err = b.GetBitmap(i); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
if box, err = b.GetBox(i); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
if bmC, err = s.clipRectangle(box, nil); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
if bmC, err = bmC.And(bm); err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
d.Values[i] = bmC
|
|
d.Boxes[i] = box
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// CountPixels counts the pixels for all the bitmaps and stores into *basic.NumSlice.
|
|
func (b *Bitmaps) CountPixels() *basic.NumSlice {
|
|
ns := &basic.NumSlice{}
|
|
for _, bm := range b.Values {
|
|
ns.AddInt(bm.CountPixels())
|
|
}
|
|
return ns
|
|
}
|
|
|
|
// GetBitmap gets the bitmap at the 'i' index.
|
|
// If the index is out of possible range the function returns error.
|
|
func (b *Bitmaps) GetBitmap(i int) (*Bitmap, error) {
|
|
const processName = "GetBitmap"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "provided nil Bitmaps")
|
|
}
|
|
if i > len(b.Values)-1 {
|
|
return nil, errors.Errorf(processName, "index: '%d' out of range", i)
|
|
}
|
|
return b.Values[i], nil
|
|
}
|
|
|
|
// GetBox gets the Box at the 'i' index.
|
|
// If the index is out of range the function returns error.
|
|
func (b *Bitmaps) GetBox(i int) (*image.Rectangle, error) {
|
|
const processName = "GetBox"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "provided nil 'Bitmaps'")
|
|
}
|
|
if i > len(b.Boxes)-1 {
|
|
return nil, errors.Errorf(processName, "index: '%d' out of range", i)
|
|
}
|
|
return b.Boxes[i], nil
|
|
}
|
|
|
|
// GroupByHeight groups bitmaps by height sorted from the lowest to the highest.
|
|
func (b *Bitmaps) GroupByHeight() (*BitmapsArray, error) {
|
|
const processName = "GroupByHeight"
|
|
if len(b.Values) == 0 {
|
|
return nil, errors.Error(processName, "no values provided")
|
|
}
|
|
a := &BitmapsArray{}
|
|
b.SortByHeight()
|
|
|
|
// initialize height class
|
|
hc := -1
|
|
currentIndex := -1
|
|
for i := 0; i < len(b.Values); i++ {
|
|
h := b.Values[i].Height
|
|
if h > hc {
|
|
hc = h
|
|
currentIndex++
|
|
a.Values = append(a.Values, &Bitmaps{})
|
|
}
|
|
a.Values[currentIndex].AddBitmap(b.Values[i])
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// GroupByWidth groups bitmaps by height sorted from the lowest to the highest.
|
|
func (b *Bitmaps) GroupByWidth() (*BitmapsArray, error) {
|
|
const processName = "GroupByWidth"
|
|
if len(b.Values) == 0 {
|
|
return nil, errors.Error(processName, "no values provided")
|
|
}
|
|
a := &BitmapsArray{}
|
|
b.SortByWidth()
|
|
|
|
// initialize height class
|
|
wc := -1
|
|
currentIndex := -1
|
|
for i := 0; i < len(b.Values); i++ {
|
|
w := b.Values[i].Width
|
|
if w > wc {
|
|
wc = w
|
|
currentIndex++
|
|
a.Values = append(a.Values, &Bitmaps{})
|
|
}
|
|
a.Values[currentIndex].AddBitmap(b.Values[i])
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// HeightSorter returns sorting function based on the bitmaps height.
|
|
func (b *Bitmaps) HeightSorter() func(i, j int) bool {
|
|
return func(i, j int) bool {
|
|
v := b.Values[i].Height < b.Values[j].Height
|
|
common.Log.Debug("Height: %v < %v = %v", b.Values[i].Height, b.Values[j].Height, v)
|
|
return v
|
|
}
|
|
}
|
|
|
|
// WidthSorter returns the sorting function based on the bitmaps width.
|
|
func (b *Bitmaps) WidthSorter() func(i, j int) bool {
|
|
return func(i, j int) bool {
|
|
return b.Values[i].Width < b.Values[j].Width
|
|
}
|
|
}
|
|
|
|
// SelectBySize selects provided bitmaps by provided 'width', 'height' location filter 'tp' and size comparison 'relation.
|
|
// Returns 'b' bitmap if it's empty or all the bitmaps matches the pattern.
|
|
func (b *Bitmaps) SelectBySize(width, height int, tp LocationFilter, relation SizeComparison) (d *Bitmaps, err error) {
|
|
const processName = "Bitmaps.SelectBySize"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "'b' Bitmaps not defined")
|
|
}
|
|
// check the location filter type
|
|
switch tp {
|
|
case LocSelectWidth, LocSelectHeight, LocSelectIfEither, LocSelectIfBoth:
|
|
default:
|
|
return nil, errors.Errorf(processName, "provided invalid location filter type: %d", tp)
|
|
}
|
|
|
|
// check the relation value
|
|
switch relation {
|
|
case SizeSelectIfLT, SizeSelectIfGT, SizeSelectIfLTE, SizeSelectIfGTE, SizeSelectIfEQ:
|
|
default:
|
|
return nil, errors.Errorf(processName, "invalid relation: '%d'", relation)
|
|
}
|
|
|
|
na, err := b.makeSizeIndicator(width, height, tp, relation)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
|
|
d, err = b.selectByIndicator(na)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// Size returns bitmaps size.
|
|
func (b *Bitmaps) Size() int {
|
|
return len(b.Values)
|
|
}
|
|
|
|
// SelectByIndexes selects bitmaps by provided indexes 'idx'.
|
|
func (b *Bitmaps) SelectByIndexes(idx []int) (*Bitmaps, error) {
|
|
const processName = "Bitmaps.SortIndexesByHeight"
|
|
temp, err := b.selectByIndexes(idx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, processName, "")
|
|
}
|
|
return temp, nil
|
|
}
|
|
|
|
// byHeight is the wrapper for the Height sorting of bitmaps
|
|
type byHeight Bitmaps
|
|
|
|
// Len implements sort.Interface Len function.
|
|
func (b *byHeight) Len() int {
|
|
return len(b.Values)
|
|
}
|
|
|
|
// Less implements sort.Interface Less function.
|
|
func (b *byHeight) Less(i, j int) bool {
|
|
return b.Values[i].Height < b.Values[j].Height
|
|
}
|
|
|
|
// Swap implements sort.Interface Swap function.
|
|
func (b *byHeight) Swap(i, j int) {
|
|
b.Values[i], b.Values[j] = b.Values[j], b.Values[i]
|
|
if b.Boxes != nil {
|
|
b.Boxes[i], b.Boxes[j] = b.Boxes[j], b.Boxes[i]
|
|
}
|
|
}
|
|
|
|
// SortByHeight sorts the bitmaps by height.
|
|
func (b *Bitmaps) SortByHeight() {
|
|
byH := (*byHeight)(b)
|
|
sort.Sort(byH)
|
|
}
|
|
|
|
// SortByWidth sorts bitmaps by width.
|
|
func (b *Bitmaps) SortByWidth() {
|
|
byW := (*byWidth)(b)
|
|
sort.Sort(byW)
|
|
}
|
|
|
|
// byWidth is the wrapper for the Height sorting of bitmaps
|
|
type byWidth Bitmaps
|
|
|
|
// Len implements sort.Interface Len function.
|
|
func (b *byWidth) Len() int {
|
|
return len(b.Values)
|
|
}
|
|
|
|
// Less implements sort.Interface Less function.
|
|
func (b *byWidth) Less(i, j int) bool {
|
|
return b.Values[i].Width < b.Values[j].Width
|
|
}
|
|
|
|
// Swap implements sort.Interface Swap function.
|
|
func (b *byWidth) Swap(i, j int) {
|
|
b.Values[i], b.Values[j] = b.Values[j], b.Values[i]
|
|
if b.Boxes != nil {
|
|
b.Boxes[i], b.Boxes[j] = b.Boxes[j], b.Boxes[i]
|
|
}
|
|
}
|
|
|
|
func (b *Bitmaps) selectByIndexes(idx []int) (*Bitmaps, error) {
|
|
d := &Bitmaps{}
|
|
for _, id := range idx {
|
|
temp, err := b.GetBitmap(id)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "selectByIndexes", "")
|
|
}
|
|
d.AddBitmap(temp)
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func (b *Bitmaps) String() string {
|
|
sb := strings.Builder{}
|
|
for _, bm := range b.Values {
|
|
sb.WriteString(bm.String())
|
|
sb.WriteRune('\n')
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
func (b *Bitmaps) makeSizeIndicator(width, height int, tp LocationFilter, relation SizeComparison) (na *basic.NumSlice, err error) {
|
|
const processName = "Bitmaps.makeSizeIndicator"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "bitmaps 'b' not defined")
|
|
}
|
|
switch tp {
|
|
case LocSelectWidth, LocSelectHeight, LocSelectIfEither, LocSelectIfBoth:
|
|
default:
|
|
return nil, errors.Errorf(processName, "provided invalid location filter type: %d", tp)
|
|
}
|
|
|
|
switch relation {
|
|
case SizeSelectIfLT, SizeSelectIfGT, SizeSelectIfLTE, SizeSelectIfGTE, SizeSelectIfEQ:
|
|
default:
|
|
return nil, errors.Errorf(processName, "invalid relation: '%d'", relation)
|
|
}
|
|
na = &basic.NumSlice{}
|
|
var (
|
|
intValue, w, h int
|
|
bm *Bitmap
|
|
)
|
|
for _, bm = range b.Values {
|
|
intValue = 0
|
|
w, h = bm.Width, bm.Height
|
|
switch tp {
|
|
case LocSelectWidth:
|
|
if (relation == SizeSelectIfLT && w < width) ||
|
|
(relation == SizeSelectIfGT && w > width) ||
|
|
(relation == SizeSelectIfLTE && w <= width) ||
|
|
(relation == SizeSelectIfGTE && w >= width) ||
|
|
(relation == SizeSelectIfEQ && w == width) {
|
|
intValue = 1
|
|
}
|
|
case LocSelectHeight:
|
|
if (relation == SizeSelectIfLT && h < height) ||
|
|
(relation == SizeSelectIfGT && h > height) ||
|
|
(relation == SizeSelectIfLTE && h <= height) ||
|
|
(relation == SizeSelectIfGTE && h >= height) ||
|
|
(relation == SizeSelectIfEQ && h == height) {
|
|
intValue = 1
|
|
}
|
|
case LocSelectIfEither:
|
|
if (relation == SizeSelectIfLT && (w < width || h < height)) ||
|
|
(relation == SizeSelectIfGT && (w > width || h > height)) ||
|
|
(relation == SizeSelectIfLTE && (w <= width || h <= height)) ||
|
|
(relation == SizeSelectIfGTE && (w >= width || h >= height)) ||
|
|
(relation == SizeSelectIfEQ && (w == width || h == height)) {
|
|
intValue = 1
|
|
}
|
|
case LocSelectIfBoth:
|
|
if (relation == SizeSelectIfLT && (w < width && h < height)) ||
|
|
(relation == SizeSelectIfGT && (w > width && h > height)) ||
|
|
(relation == SizeSelectIfLTE && (w <= width && h <= height)) ||
|
|
(relation == SizeSelectIfGTE && (w >= width && h >= height)) ||
|
|
(relation == SizeSelectIfEQ && (w == width && h == height)) {
|
|
intValue = 1
|
|
}
|
|
}
|
|
na.AddInt(intValue)
|
|
}
|
|
return na, nil
|
|
}
|
|
|
|
func (b *Bitmaps) selectByIndicator(na *basic.NumSlice) (d *Bitmaps, err error) {
|
|
const processName = "Bitmaps.selectByIndicator"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "'b' bitmaps not defined")
|
|
}
|
|
if na == nil {
|
|
return nil, errors.Error(processName, "'na' indicators not defined")
|
|
}
|
|
if len(b.Values) == 0 {
|
|
return b, nil
|
|
}
|
|
|
|
if len(*na) != len(b.Values) {
|
|
return nil, errors.Errorf(processName, "na length: %d, is different than bitmaps: %d", len(*na), len(b.Values))
|
|
}
|
|
var intValue, i, changeCount int
|
|
for i = 0; i < len(*na); i++ {
|
|
if intValue, err = na.GetInt(i); err != nil {
|
|
return nil, errors.Wrap(err, processName, "first check")
|
|
}
|
|
if intValue == 1 {
|
|
changeCount++
|
|
}
|
|
}
|
|
if changeCount == len(b.Values) {
|
|
return b, nil
|
|
}
|
|
d = &Bitmaps{}
|
|
hasBoxes := len(b.Values) == len(b.Boxes)
|
|
for i = 0; i < len(*na); i++ {
|
|
if intValue = int((*na)[i]); intValue == 0 {
|
|
continue
|
|
}
|
|
d.Values = append(d.Values, b.Values[i])
|
|
if hasBoxes {
|
|
d.Boxes = append(d.Boxes, b.Boxes[i])
|
|
}
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// BitmapsArray is the struct that contains slice of the 'Bitmaps',
|
|
// with the bounding boxes around each 'Bitmaps'.
|
|
type BitmapsArray struct {
|
|
Values []*Bitmaps
|
|
Boxes []*image.Rectangle
|
|
}
|
|
|
|
// AddBitmaps adds the 'bm' Bitmaps to the 'b'.Values.
|
|
func (b *BitmapsArray) AddBitmaps(bm *Bitmaps) {
|
|
b.Values = append(b.Values, bm)
|
|
}
|
|
|
|
// AddBox adds the 'box' to the 'b' Boxes.
|
|
func (b *BitmapsArray) AddBox(box *image.Rectangle) {
|
|
b.Boxes = append(b.Boxes, box)
|
|
}
|
|
|
|
// GetBitmaps gets the 'Bitmaps' at the 'i' position.
|
|
// Returns error if the index is out of range.
|
|
func (b *BitmapsArray) GetBitmaps(i int) (*Bitmaps, error) {
|
|
const processName = "BitmapsArray.GetBitmaps"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "provided nil 'BitmapsArray'")
|
|
}
|
|
if i > len(b.Values)-1 {
|
|
return nil, errors.Errorf(processName, "index: '%d' out of range", i)
|
|
}
|
|
return b.Values[i], nil
|
|
}
|
|
|
|
// GetBox gets the boundary box at 'i' position.
|
|
// Returns error if the index is out of range.
|
|
func (b *BitmapsArray) GetBox(i int) (*image.Rectangle, error) {
|
|
const processName = "BitmapsArray.GetBox"
|
|
if b == nil {
|
|
return nil, errors.Error(processName, "provided nil 'BitmapsArray'")
|
|
}
|
|
if i > len(b.Boxes)-1 {
|
|
return nil, errors.Errorf(processName, "index: '%d' out of range", i)
|
|
}
|
|
return b.Boxes[i], nil
|
|
}
|