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

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
}