mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-05 19:30:30 +08:00
Add CCITTFaxDecode filter
This commit is contained in:
parent
15f437dc92
commit
43625d5c66
1074
pdf/core/ccittfaxdecode/codes.go
Normal file
1074
pdf/core/ccittfaxdecode/codes.go
Normal file
File diff suppressed because it is too large
Load Diff
733
pdf/core/ccittfaxdecode/decode.go
Normal file
733
pdf/core/ccittfaxdecode/decode.go
Normal file
@ -0,0 +1,733 @@
|
|||||||
|
package ccittfaxdecode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
whiteTree = &decodingTreeNode{
|
||||||
|
Val: 255,
|
||||||
|
}
|
||||||
|
blackTree = &decodingTreeNode{
|
||||||
|
Val: 255,
|
||||||
|
}
|
||||||
|
twoDimTree = &decodingTreeNode{
|
||||||
|
Val: 255,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for runLen, code := range WTerms {
|
||||||
|
addNode(whiteTree, code, 0, runLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for runLen, code := range WMakeups {
|
||||||
|
addNode(whiteTree, code, 0, runLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for runLen, code := range BTerms {
|
||||||
|
addNode(blackTree, code, 0, runLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for runLen, code := range BMakeups {
|
||||||
|
addNode(blackTree, code, 0, runLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
for runLen, code := range CommonMakeups {
|
||||||
|
addNode(whiteTree, code, 0, runLen)
|
||||||
|
addNode(blackTree, code, 0, runLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(twoDimTree, P, 0, 0)
|
||||||
|
addNode(twoDimTree, H, 0, 0)
|
||||||
|
addNode(twoDimTree, V0, 0, 0)
|
||||||
|
addNode(twoDimTree, V1R, 0, 0)
|
||||||
|
addNode(twoDimTree, V2R, 0, 0)
|
||||||
|
addNode(twoDimTree, V3R, 0, 0)
|
||||||
|
addNode(twoDimTree, V1L, 0, 0)
|
||||||
|
addNode(twoDimTree, V2L, 0, 0)
|
||||||
|
addNode(twoDimTree, V3L, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) Decode(encoded []byte) ([][]byte, error) {
|
||||||
|
if e.BlackIs1 {
|
||||||
|
white = 0
|
||||||
|
black = 1
|
||||||
|
} else {
|
||||||
|
white = 1
|
||||||
|
black = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K == 0 {
|
||||||
|
return e.decodeG31D(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K > 0 {
|
||||||
|
return e.decodeG32D(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K < 4 {
|
||||||
|
return e.decodeG4(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) decodeG31D(encoded []byte) ([][]byte, error) {
|
||||||
|
var pixels [][]byte
|
||||||
|
|
||||||
|
// do g31d decoding
|
||||||
|
var bitPos int
|
||||||
|
for (bitPos / 8) < len(encoded) {
|
||||||
|
var gotEOL bool
|
||||||
|
|
||||||
|
gotEOL, bitPos = tryFetchEOL(encoded, bitPos)
|
||||||
|
if !gotEOL {
|
||||||
|
if e.EndOfLine {
|
||||||
|
return nil, errors.New("no EOL found while the EndOfLine parameter is true")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 5 EOLs left to fill RTC
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
gotEOL, bitPos = tryFetchEOL(encoded, bitPos)
|
||||||
|
|
||||||
|
if !gotEOL {
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("invalid EOL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// got RTC
|
||||||
|
if gotEOL {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var row []byte
|
||||||
|
row, bitPos = e.decodeRow1D(encoded, bitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign && bitPos%8 != 0 {
|
||||||
|
bitPos += 8 - bitPos%8
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = append(pixels, row)
|
||||||
|
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//saveToImage(pixels, "/home/darkrengarius/Downloads/yeatAnotherTest.png")
|
||||||
|
|
||||||
|
return pixels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToImage(pixels [][]byte, path string) error {
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, len(pixels[0]), len(pixels)))
|
||||||
|
|
||||||
|
imgPix := 0
|
||||||
|
for i := range pixels {
|
||||||
|
for j := range pixels[i] {
|
||||||
|
img.Pix[imgPix] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+1] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+2] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+3] = 255
|
||||||
|
|
||||||
|
imgPix += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = png.Encode(f, img)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) decodeG32D(encoded []byte) ([][]byte, error) {
|
||||||
|
var (
|
||||||
|
pixels [][]byte
|
||||||
|
bitPos int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
byteLoop:
|
||||||
|
for (bitPos / 8) < len(encoded) {
|
||||||
|
var gotEOL bool
|
||||||
|
gotEOL, bitPos, err = tryFetchRTC2D(encoded, bitPos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotEOL {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
gotEOL, bitPos = tryFetchEOL1(encoded, bitPos)
|
||||||
|
|
||||||
|
if !gotEOL {
|
||||||
|
if e.EndOfLine {
|
||||||
|
return nil, errors.New("no EOL found while the EndOfLine parameter is true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode 1 of K rows as 1D
|
||||||
|
var row []byte
|
||||||
|
row, bitPos = e.decodeRow1D(encoded, bitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign && bitPos%8 != 0 {
|
||||||
|
bitPos += 8 - bitPos%8
|
||||||
|
}
|
||||||
|
|
||||||
|
if row != nil {
|
||||||
|
pixels = append(pixels, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode K-1 rows as 2D
|
||||||
|
for i := 1; i < e.K && (bitPos/8) < len(encoded); i++ {
|
||||||
|
gotEOL, bitPos = tryFetchEOL0(encoded, bitPos)
|
||||||
|
if !gotEOL {
|
||||||
|
gotEOL, bitPos, err = tryFetchRTC2D(encoded, bitPos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotEOL {
|
||||||
|
break byteLoop
|
||||||
|
} else {
|
||||||
|
if e.EndOfLine {
|
||||||
|
return nil, errors.New("no EOL found while the EndOfLine parameter is true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
twoDimCode Code
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
isWhite := true
|
||||||
|
var pixelsRow []byte
|
||||||
|
a0 := -1
|
||||||
|
for twoDimCode, bitPos, ok = fetchNext2DCode(encoded, bitPos); ok; twoDimCode, bitPos, ok = fetchNext2DCode(encoded, bitPos) {
|
||||||
|
switch twoDimCode {
|
||||||
|
case P:
|
||||||
|
// do pass mode decoding
|
||||||
|
pixelsRow, a0 = decodePassMode(pixels, pixelsRow, isWhite, a0)
|
||||||
|
case H:
|
||||||
|
// do horizontal mode decoding
|
||||||
|
pixelsRow, bitPos, a0, err = decodeHorizontalMode(encoded, pixelsRow, bitPos, isWhite, a0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case V0:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 0)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V1R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 1)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V2R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 2)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V3R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 3)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V1L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -1)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V2L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -2)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V3L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -3)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: additionally check for errors. EOL should be fetched when a0 == len(pixels[len(pixels)-1])
|
||||||
|
if len(pixelsRow) >= e.Columns {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.EncodedByteAlign && bitPos%8 != 0 {
|
||||||
|
bitPos += 8 - bitPos%8
|
||||||
|
}
|
||||||
|
|
||||||
|
if pixelsRow != nil {
|
||||||
|
pixels = append(pixels, pixelsRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= e.Rows {
|
||||||
|
break byteLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) decodeG4(encoded []byte) ([][]byte, error) {
|
||||||
|
whiteReferenceLine := make([]byte, e.Columns)
|
||||||
|
for i := range whiteReferenceLine {
|
||||||
|
whiteReferenceLine[i] = white
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels := make([][]byte, 1)
|
||||||
|
pixels[0] = whiteReferenceLine
|
||||||
|
|
||||||
|
var (
|
||||||
|
gotEOL bool
|
||||||
|
err error
|
||||||
|
bitPos int
|
||||||
|
)
|
||||||
|
for (bitPos / 8) < len(encoded) {
|
||||||
|
if len(pixels) == 1198 {
|
||||||
|
log.Println()
|
||||||
|
}
|
||||||
|
// try get EOFB
|
||||||
|
gotEOL, bitPos, err = tryFetchEOFB(encoded, bitPos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if gotEOL {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
twoDimCode Code
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
isWhite := true
|
||||||
|
var pixelsRow []byte
|
||||||
|
a0 := -1
|
||||||
|
for a0 < e.Columns {
|
||||||
|
twoDimCode, bitPos, ok = fetchNext2DCode(encoded, bitPos)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("wrong 2 dim code")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch twoDimCode {
|
||||||
|
case P:
|
||||||
|
// do pass mode decoding
|
||||||
|
pixelsRow, a0 = decodePassMode(pixels, pixelsRow, isWhite, a0)
|
||||||
|
case H:
|
||||||
|
// do horizontal mode decoding
|
||||||
|
pixelsRow, bitPos, a0, err = decodeHorizontalMode(encoded, pixelsRow, bitPos, isWhite, a0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case V0:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 0)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V1R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 1)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V2R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 2)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V3R:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, 3)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V1L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -1)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V2L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -2)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
case V3L:
|
||||||
|
pixelsRow, a0 = decodeVerticalMode(pixels, pixelsRow, isWhite, a0, -3)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pixelsRow) >= e.Columns {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.EncodedByteAlign && bitPos%8 != 0 {
|
||||||
|
bitPos += 8 - bitPos%8
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = append(pixels, pixelsRow)
|
||||||
|
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = pixels[1:]
|
||||||
|
|
||||||
|
return pixels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeVerticalMode(pixels [][]byte, pixelsRow []byte, isWhite bool, a0, shift int) ([]byte, int) {
|
||||||
|
b1 := seekB12D(pixelsRow, pixels[len(pixels)-1], a0, isWhite)
|
||||||
|
// true for V0
|
||||||
|
a1 := b1 + shift
|
||||||
|
|
||||||
|
if a0 == -1 {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, a1-a0-1)
|
||||||
|
} else {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, a1-a0)
|
||||||
|
}
|
||||||
|
|
||||||
|
a0 = a1
|
||||||
|
|
||||||
|
return pixelsRow, a0
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodePassMode(pixels [][]byte, pixelsRow []byte, isWhite bool, a0 int) ([]byte, int) {
|
||||||
|
b1 := seekB12D(pixelsRow, pixels[len(pixels)-1], a0, isWhite)
|
||||||
|
b2 := seekChangingElem(pixels[len(pixels)-1], b1)
|
||||||
|
|
||||||
|
if a0 == -1 {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, b2-a0-1)
|
||||||
|
} else {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, b2-a0)
|
||||||
|
}
|
||||||
|
|
||||||
|
a0 = b2
|
||||||
|
|
||||||
|
return pixelsRow, a0
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHorizontalMode(encoded, pixelsRow []byte, bitPos int, isWhite bool, a0 int) ([]byte, int, int, error) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
var err error
|
||||||
|
pixelsRow, bitPos, err = decodeNextRunLen(encoded, pixelsRow, bitPos, isWhite)
|
||||||
|
if err != nil {
|
||||||
|
return pixelsRow, startingBitPos, a0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
|
||||||
|
pixelsRow, bitPos, err = decodeNextRunLen(encoded, pixelsRow, bitPos, isWhite)
|
||||||
|
if err != nil {
|
||||||
|
return pixelsRow, startingBitPos, a0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last code was the code of a1a2 run. a0 was moved to a2
|
||||||
|
// while encoding. that's why we put a0 on the a2 and continue
|
||||||
|
a0 = len(pixelsRow)
|
||||||
|
|
||||||
|
return pixelsRow, bitPos, a0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNextRunLen(encoded, pixelsRow []byte, bitPos int, isWhite bool) ([]byte, int, error) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
var runLen int
|
||||||
|
|
||||||
|
for runLen, bitPos = fetchNextRunLen(encoded, bitPos, isWhite); runLen != -1; runLen, bitPos = fetchNextRunLen(encoded, bitPos, isWhite) {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, runLen)
|
||||||
|
|
||||||
|
// got terminal code, switch color
|
||||||
|
if runLen < 64 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runLen == -1 {
|
||||||
|
return pixelsRow, startingBitPos, errors.New("wrong code in horizontal mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelsRow, bitPos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchRTC2D(encoded []byte, bitPos int) (bool, int, error) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
var gotEOL = false
|
||||||
|
|
||||||
|
// 6 EOL1s to fill RTC
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
gotEOL, bitPos = tryFetchEOL1(encoded, bitPos)
|
||||||
|
|
||||||
|
if !gotEOL {
|
||||||
|
if i > 1 {
|
||||||
|
return false, startingBitPos, errors.New("RTC code is corrupted")
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gotEOL, bitPos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchEOFB(encoded []byte, bitPos int) (bool, int, error) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
var gotEOL bool
|
||||||
|
gotEOL, bitPos = tryFetchEOL(encoded, bitPos)
|
||||||
|
if gotEOL {
|
||||||
|
// 1 EOL to fill EOFB
|
||||||
|
gotEOL, bitPos = tryFetchEOL(encoded, bitPos)
|
||||||
|
|
||||||
|
if gotEOL {
|
||||||
|
return true, bitPos, nil
|
||||||
|
} else {
|
||||||
|
return false, startingBitPos, errors.New("EOFB code is corrupted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, startingBitPos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitFromUint16(num uint16, bitPos int) byte {
|
||||||
|
if bitPos < 8 {
|
||||||
|
num >>= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
bitPos %= 8
|
||||||
|
|
||||||
|
mask := byte(0x01 << (7 - uint(bitPos)))
|
||||||
|
|
||||||
|
return (byte(num) & mask) >> (7 - uint(bitPos))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchNextRunLen(data []byte, bitPos int, isWhite bool) (int, int) {
|
||||||
|
var (
|
||||||
|
codeNum uint16
|
||||||
|
codeBitPos int
|
||||||
|
startingBitPos int
|
||||||
|
)
|
||||||
|
|
||||||
|
startingBitPos = bitPos
|
||||||
|
codeNum, codeBitPos, bitPos = fetchNextCode(data, bitPos)
|
||||||
|
|
||||||
|
runLen, code := decodeRunLenFromUint16(codeNum, codeBitPos, isWhite)
|
||||||
|
if runLen == -1 {
|
||||||
|
return -1, startingBitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
return runLen, startingBitPos + code.BitsWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchNext2DCode(data []byte, bitPos int) (Code, int, bool) {
|
||||||
|
var (
|
||||||
|
codeNum uint16
|
||||||
|
codeBitPos int
|
||||||
|
startingBitPos int
|
||||||
|
)
|
||||||
|
|
||||||
|
startingBitPos = bitPos
|
||||||
|
codeNum, codeBitPos, bitPos = fetchNextCode(data, bitPos)
|
||||||
|
|
||||||
|
code, ok := get2DCodeFromUint16(codeNum, codeBitPos)
|
||||||
|
if !ok {
|
||||||
|
return Code{}, startingBitPos, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return code, startingBitPos + code.BitsWritten, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func get2DCodeFromUint16(encoded uint16, bitPos int) (Code, bool) {
|
||||||
|
_, codePtr := findRunLen(twoDimTree, encoded, bitPos)
|
||||||
|
|
||||||
|
if codePtr == nil {
|
||||||
|
return Code{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return *codePtr, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeRunLenFromUint16(encoded uint16, bitPos int, isWhite bool) (int, Code) {
|
||||||
|
var runLenPtr *int
|
||||||
|
var codePtr *Code
|
||||||
|
|
||||||
|
if isWhite {
|
||||||
|
runLenPtr, codePtr = findRunLen(whiteTree, encoded, bitPos)
|
||||||
|
} else {
|
||||||
|
runLenPtr, codePtr = findRunLen(blackTree, encoded, bitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runLenPtr == nil {
|
||||||
|
return -1, Code{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *runLenPtr, *codePtr
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchNextCode(data []byte, bitPos int) (uint16, int, int) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
bytePos := bitPos / 8
|
||||||
|
bitPos %= 8
|
||||||
|
|
||||||
|
if bytePos >= len(data) {
|
||||||
|
return 0, 16, startingBitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the rest bits from the current byte
|
||||||
|
mask := byte(0xFF >> uint(bitPos))
|
||||||
|
code := uint16((data[bytePos]&mask)<<uint(bitPos)) << 8
|
||||||
|
|
||||||
|
bitsWritten := 8 - bitPos
|
||||||
|
|
||||||
|
bytePos++
|
||||||
|
bitPos = 0
|
||||||
|
|
||||||
|
if bytePos >= len(data) {
|
||||||
|
return code >> (16 - uint(bitsWritten)), 16 - bitsWritten, startingBitPos + bitsWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the whole next byte
|
||||||
|
code |= uint16(data[bytePos]) << (8 - uint(bitsWritten))
|
||||||
|
|
||||||
|
bitsWritten += 8
|
||||||
|
|
||||||
|
bytePos++
|
||||||
|
bitPos = 0
|
||||||
|
|
||||||
|
if bytePos >= len(data) {
|
||||||
|
return code >> (16 - uint(bitsWritten)), 16 - bitsWritten, startingBitPos + bitsWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitsWritten == 16 {
|
||||||
|
return code, 0, startingBitPos + bitsWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the needed bits from the next byte
|
||||||
|
leftToWrite := 16 - bitsWritten
|
||||||
|
|
||||||
|
code |= uint16(data[bytePos] >> (8 - uint(leftToWrite)))
|
||||||
|
|
||||||
|
return code, 0, startingBitPos + 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) decodeRow1D(encoded []byte, bitPos int) ([]byte, int) {
|
||||||
|
var pixelsRow []byte
|
||||||
|
|
||||||
|
isWhite := true
|
||||||
|
|
||||||
|
var runLen int
|
||||||
|
runLen, bitPos = fetchNextRunLen(encoded, bitPos, isWhite)
|
||||||
|
for runLen != -1 {
|
||||||
|
pixelsRow = drawPixels(pixelsRow, isWhite, runLen)
|
||||||
|
|
||||||
|
// got terminal code, switch color
|
||||||
|
if runLen < 64 {
|
||||||
|
if len(pixelsRow) >= e.Columns {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
runLen, bitPos = fetchNextRunLen(encoded, bitPos, isWhite)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelsRow, bitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawPixels(row []byte, isWhite bool, length int) []byte {
|
||||||
|
runLenPixels := make([]byte, length)
|
||||||
|
if isWhite {
|
||||||
|
for i := 0; i < len(runLenPixels); i++ {
|
||||||
|
runLenPixels[i] = white
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < len(runLenPixels); i++ {
|
||||||
|
runLenPixels[i] = black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row = append(row, runLenPixels...)
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchEOL(encoded []byte, bitPos int) (bool, int) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
var (
|
||||||
|
code uint16
|
||||||
|
codeBitPos int
|
||||||
|
)
|
||||||
|
|
||||||
|
code, codeBitPos, bitPos = fetchNextCode(encoded, bitPos)
|
||||||
|
|
||||||
|
// when this is true, the fetched code if less than 12 bits long,
|
||||||
|
// which doesn't fit the EOL code length
|
||||||
|
if codeBitPos > 4 {
|
||||||
|
return false, startingBitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the fetched code
|
||||||
|
code >>= uint(4 - codeBitPos)
|
||||||
|
code <<= 4
|
||||||
|
|
||||||
|
if code != EOL.Code {
|
||||||
|
return false, startingBitPos
|
||||||
|
} else {
|
||||||
|
return true, bitPos - 4 + codeBitPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchExtendedEOL(encoded []byte, bitPos int, eolCode Code) (bool, int) {
|
||||||
|
startingBitPos := bitPos
|
||||||
|
|
||||||
|
var (
|
||||||
|
code uint16
|
||||||
|
codeBitPos int
|
||||||
|
)
|
||||||
|
|
||||||
|
code, codeBitPos, bitPos = fetchNextCode(encoded, bitPos)
|
||||||
|
|
||||||
|
// when this is true, the fetched code if less than 13 bits long,
|
||||||
|
// which doesn't fit the EOL0/EOL1 code length
|
||||||
|
if codeBitPos > 3 {
|
||||||
|
return false, startingBitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the fetched code
|
||||||
|
code >>= uint(3 - codeBitPos)
|
||||||
|
code <<= 3
|
||||||
|
|
||||||
|
if code != eolCode.Code {
|
||||||
|
return false, startingBitPos
|
||||||
|
} else {
|
||||||
|
return true, bitPos - 3 + codeBitPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchEOL0(encoded []byte, bitPos int) (bool, int) {
|
||||||
|
return tryFetchExtendedEOL(encoded, bitPos, EOL0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryFetchEOL1(encoded []byte, bitPos int) (bool, int) {
|
||||||
|
return tryFetchExtendedEOL(encoded, bitPos, EOL1)
|
||||||
|
}
|
292
pdf/core/ccittfaxdecode/decode_test.go
Normal file
292
pdf/core/ccittfaxdecode/decode_test.go
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package ccittfaxdecode
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestBitFromUint16(t *testing.T) {
|
||||||
|
type testData struct {
|
||||||
|
Num uint16
|
||||||
|
BitPos int
|
||||||
|
Want byte
|
||||||
|
}
|
||||||
|
var tests []testData
|
||||||
|
|
||||||
|
var num uint16 = 43690
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
var want byte = 1
|
||||||
|
if (i % 2) != 0 {
|
||||||
|
want = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
tests = append(tests, testData{
|
||||||
|
Num: num,
|
||||||
|
BitPos: i,
|
||||||
|
Want: want,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
bit := bitFromUint16(test.Num, test.BitPos)
|
||||||
|
|
||||||
|
if bit != test.Want {
|
||||||
|
t.Errorf("Wrong bit. Got %v want %v\n", bit, test.Want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchNextCode(t *testing.T) {
|
||||||
|
testData := []byte{95, 21, 197}
|
||||||
|
|
||||||
|
type testResult struct {
|
||||||
|
Code uint16
|
||||||
|
CodeBitPos int
|
||||||
|
DataBitPos int
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Data []byte
|
||||||
|
BitPos int
|
||||||
|
Want testResult
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 0,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 24341,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 1,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 48683,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 17,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 2,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 31831,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 3,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 63662,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 19,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 4,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 61788,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 5,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 58040,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 21,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 6,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 50545,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 7,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 35554,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 23,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 8,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5573,
|
||||||
|
CodeBitPos: 0,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 9,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5573,
|
||||||
|
CodeBitPos: 1,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 10,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5573,
|
||||||
|
CodeBitPos: 2,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 11,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5573,
|
||||||
|
CodeBitPos: 3,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 12,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 1477,
|
||||||
|
CodeBitPos: 4,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 13,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 1477,
|
||||||
|
CodeBitPos: 5,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 14,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 453,
|
||||||
|
CodeBitPos: 6,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 15,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 453,
|
||||||
|
CodeBitPos: 7,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 16,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 197,
|
||||||
|
CodeBitPos: 8,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 17,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 69,
|
||||||
|
CodeBitPos: 9,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 18,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5,
|
||||||
|
CodeBitPos: 10,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 19,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5,
|
||||||
|
CodeBitPos: 11,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 20,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5,
|
||||||
|
CodeBitPos: 12,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 21,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 5,
|
||||||
|
CodeBitPos: 13,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 22,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 1,
|
||||||
|
CodeBitPos: 14,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 23,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 1,
|
||||||
|
CodeBitPos: 15,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: testData,
|
||||||
|
BitPos: 24,
|
||||||
|
Want: testResult{
|
||||||
|
Code: 0,
|
||||||
|
CodeBitPos: 16,
|
||||||
|
DataBitPos: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
gotCode, gotCodeBitPos, gotDataBitPos := fetchNextCode(test.Data, test.BitPos)
|
||||||
|
|
||||||
|
if gotCode != test.Want.Code {
|
||||||
|
t.Errorf("Wrong code. Got %v, want %v\n", gotCode, test.Want.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotCodeBitPos != test.Want.CodeBitPos {
|
||||||
|
t.Errorf("Wrong code bit pos. Got %v, want %v\n", gotCodeBitPos, test.Want.CodeBitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotDataBitPos != test.Want.DataBitPos {
|
||||||
|
t.Errorf("Wrong data bit pos. Got %v, want %v\n", gotDataBitPos, test.Want.DataBitPos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
pdf/core/ccittfaxdecode/decoding_tree.go
Normal file
70
pdf/core/ccittfaxdecode/decoding_tree.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package ccittfaxdecode
|
||||||
|
|
||||||
|
type decodingTreeNode struct {
|
||||||
|
Val byte
|
||||||
|
RunLen *int
|
||||||
|
Code *Code
|
||||||
|
Left *decodingTreeNode
|
||||||
|
Right *decodingTreeNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func addNode(root *decodingTreeNode, code Code, bitPos int, runLen int) {
|
||||||
|
val := bitFromUint16(code.Code, bitPos)
|
||||||
|
bitPos++
|
||||||
|
|
||||||
|
if val == 1 {
|
||||||
|
if root.Right == nil {
|
||||||
|
root.Right = &decodingTreeNode{
|
||||||
|
Val: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitPos == code.BitsWritten {
|
||||||
|
root.Right.RunLen = &runLen
|
||||||
|
root.Right.Code = &code
|
||||||
|
} else {
|
||||||
|
addNode(root.Right, code, bitPos, runLen)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if root.Left == nil {
|
||||||
|
root.Left = &decodingTreeNode{
|
||||||
|
Val: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitPos == code.BitsWritten {
|
||||||
|
root.Left.RunLen = &runLen
|
||||||
|
root.Left.Code = &code
|
||||||
|
} else {
|
||||||
|
addNode(root.Left, code, bitPos, runLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findRunLen(root *decodingTreeNode, code uint16, bitPos int) (*int, *Code) {
|
||||||
|
if root == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitPos == 16 {
|
||||||
|
return root.RunLen, root.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
val := bitFromUint16(code, bitPos)
|
||||||
|
bitPos++
|
||||||
|
|
||||||
|
var runLenPtr *int
|
||||||
|
var codePtr *Code
|
||||||
|
if val == 1 {
|
||||||
|
runLenPtr, codePtr = findRunLen(root.Right, code, bitPos)
|
||||||
|
} else {
|
||||||
|
runLenPtr, codePtr = findRunLen(root.Left, code, bitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runLenPtr == nil {
|
||||||
|
runLenPtr = root.RunLen
|
||||||
|
codePtr = root.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
return runLenPtr, codePtr
|
||||||
|
}
|
539
pdf/core/ccittfaxdecode/encoder.go
Normal file
539
pdf/core/ccittfaxdecode/encoder.go
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
package ccittfaxdecode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
white byte = 1
|
||||||
|
black byte = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type Encoder struct {
|
||||||
|
K int
|
||||||
|
EndOfLine bool
|
||||||
|
EncodedByteAlign bool
|
||||||
|
Columns int
|
||||||
|
Rows int
|
||||||
|
EndOfBlock bool
|
||||||
|
BlackIs1 bool
|
||||||
|
DamagedRowsBeforeError int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) Encode(pixels [][]byte) []byte {
|
||||||
|
if e.BlackIs1 {
|
||||||
|
white = 0
|
||||||
|
black = 1
|
||||||
|
} else {
|
||||||
|
white = 1
|
||||||
|
black = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K == 0 {
|
||||||
|
// do G31D
|
||||||
|
return e.encodeG31D(pixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K > 0 {
|
||||||
|
// do G32D
|
||||||
|
return e.encodeG32D(pixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.K < 0 {
|
||||||
|
// do G4
|
||||||
|
return e.encodeG4(pixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// never happens
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) encodeG31D(pixels [][]byte) []byte {
|
||||||
|
var encoded []byte
|
||||||
|
|
||||||
|
prevBitPos := 0
|
||||||
|
for i := range pixels {
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && i == e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedRow, bitPos := encodeRow1D(pixels[i], prevBitPos, EOL)
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRow, prevBitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign {
|
||||||
|
bitPos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBitPos = bitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.EndOfBlock {
|
||||||
|
// put rtc
|
||||||
|
encodedRTC, _ := encodeRTC(prevBitPos)
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRTC, prevBitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) encodeG32D(pixels [][]byte) []byte {
|
||||||
|
var encoded []byte
|
||||||
|
var prevBitPos int
|
||||||
|
for i := 0; i < len(pixels); i += e.K {
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && i == e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedRow, bitPos := encodeRow1D(pixels[i], prevBitPos, EOL1)
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRow, prevBitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign {
|
||||||
|
bitPos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBitPos = bitPos
|
||||||
|
|
||||||
|
for j := i + 1; j < (i+e.K) && j < len(pixels); j++ {
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && j == e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedRow, bitPos := addCode(nil, prevBitPos, EOL0)
|
||||||
|
|
||||||
|
var a1, b1, b2 int
|
||||||
|
|
||||||
|
a0 := -1
|
||||||
|
|
||||||
|
// !EOL
|
||||||
|
for a0 < len(pixels[j]) {
|
||||||
|
a1 = seekChangingElem(pixels[j], a0)
|
||||||
|
b1 = seekB1(pixels[j], pixels[j-1], a0)
|
||||||
|
b2 = seekChangingElem(pixels[j-1], b1)
|
||||||
|
|
||||||
|
if b2 < a1 {
|
||||||
|
// do pass mode
|
||||||
|
encodedRow, bitPos = encodePassMode(encodedRow, bitPos)
|
||||||
|
|
||||||
|
a0 = b2
|
||||||
|
} else {
|
||||||
|
if math.Abs(float64(b1-a1)) > 3 {
|
||||||
|
// do horizontal mode
|
||||||
|
encodedRow, bitPos, a0 = encodeHorizontalMode(pixels[j], encodedRow, bitPos, a0, a1)
|
||||||
|
} else {
|
||||||
|
// do vertical mode
|
||||||
|
encodedRow, bitPos = encodeVerticalMode(encodedRow, bitPos, a1, b1)
|
||||||
|
|
||||||
|
a0 = a1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRow, prevBitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign {
|
||||||
|
bitPos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBitPos = bitPos % 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.EndOfBlock {
|
||||||
|
// put rtc
|
||||||
|
encodedRTC, _ := encodeRTC2D(prevBitPos)
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRTC, prevBitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encoder) encodeG4(pixels [][]byte) []byte {
|
||||||
|
pixels = appendWhiteReferenceLine(pixels)
|
||||||
|
|
||||||
|
var encoded []byte
|
||||||
|
var prevBitPos int
|
||||||
|
for i := 1; i < len(pixels); i++ {
|
||||||
|
if e.Rows > 0 && !e.EndOfBlock && i == e.Rows {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodedRow []byte
|
||||||
|
var a1, b1, b2 int
|
||||||
|
|
||||||
|
bitPos := prevBitPos
|
||||||
|
a0 := -1
|
||||||
|
|
||||||
|
// !EOL
|
||||||
|
for a0 < len(pixels[i]) {
|
||||||
|
a1 = seekChangingElem(pixels[i], a0)
|
||||||
|
b1 = seekB1(pixels[i], pixels[i-1], a0)
|
||||||
|
b2 = seekChangingElem(pixels[i-1], b1)
|
||||||
|
|
||||||
|
if b2 < a1 {
|
||||||
|
// do pass mode
|
||||||
|
encodedRow, bitPos = addCode(encodedRow, bitPos, P)
|
||||||
|
|
||||||
|
a0 = b2
|
||||||
|
} else {
|
||||||
|
if math.Abs(float64(b1-a1)) > 3 {
|
||||||
|
encodedRow, bitPos, a0 = encodeHorizontalMode(pixels[i], encodedRow, bitPos, a0, a1)
|
||||||
|
} else {
|
||||||
|
// do vertical mode
|
||||||
|
encodedRow, bitPos = encodeVerticalMode(encodedRow, bitPos, a1, b1)
|
||||||
|
|
||||||
|
a0 = a1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedRow, prevBitPos)
|
||||||
|
|
||||||
|
if e.EncodedByteAlign {
|
||||||
|
bitPos = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBitPos = bitPos % 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.EndOfBlock {
|
||||||
|
// put eofb
|
||||||
|
encodedEOFB, _ := encodeEOFB(prevBitPos)
|
||||||
|
|
||||||
|
encoded = e.appendEncodedRow(encoded, encodedEOFB, prevBitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeRTC encodes the RTC code for the G3 1D (6 EOL in a row). bitPos is equal to the one in
|
||||||
|
// encodeRow
|
||||||
|
func encodeRTC(bitPos int) ([]byte, int) {
|
||||||
|
var encoded []byte
|
||||||
|
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
encoded, bitPos = addCode(encoded, bitPos, EOL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, bitPos % 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeRTC2D encodes the RTC code for the G3 2D (6 EOL + 1 bit in a row). bitPos is equal to the one in
|
||||||
|
// encodeRow
|
||||||
|
func encodeRTC2D(bitPos int) ([]byte, int) {
|
||||||
|
var encoded []byte
|
||||||
|
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
encoded, bitPos = addCode(encoded, bitPos, EOL1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, bitPos % 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeEOFB encodes the EOFB code for the G4 (2 EOL in a row). bitPos is equal to the one in
|
||||||
|
// encodeRow
|
||||||
|
func encodeEOFB(bitPos int) ([]byte, int) {
|
||||||
|
var encoded []byte
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
encoded, bitPos = addCode(encoded, bitPos, EOL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, bitPos % 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeRow1D encodes single raw of the image in 1D mode. bitPos is the bit position
|
||||||
|
// global for the row array. bitPos is used to indicate where to start the
|
||||||
|
// encoded sequences. It is used for the EncodedByteAlign option implementation.
|
||||||
|
// Returns the encoded data and the number of the bits taken from the last byte
|
||||||
|
func encodeRow1D(row []byte, bitPos int, prefix Code) ([]byte, int) {
|
||||||
|
// always start with whites
|
||||||
|
isWhite := true
|
||||||
|
var encoded []byte
|
||||||
|
|
||||||
|
// always add EOL before the scan line
|
||||||
|
encoded, bitPos = addCode(nil, bitPos, prefix)
|
||||||
|
|
||||||
|
bytePos := 0
|
||||||
|
var runLen int
|
||||||
|
for bytePos < len(row) {
|
||||||
|
runLen, bytePos = calcRun(row, isWhite, bytePos)
|
||||||
|
|
||||||
|
encoded, bitPos = encodeRunLen(encoded, bitPos, runLen, isWhite)
|
||||||
|
|
||||||
|
// switch color
|
||||||
|
isWhite = !isWhite
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, bitPos % 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeRunLen encodes the calculated run length to the encoded starting with bitPos bit
|
||||||
|
func encodeRunLen(encoded []byte, bitPos int, runLen int, isWhite bool) ([]byte, int) {
|
||||||
|
var (
|
||||||
|
code Code
|
||||||
|
isTerminal bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for !isTerminal {
|
||||||
|
code, runLen, isTerminal = getRunCode(runLen, isWhite)
|
||||||
|
encoded, bitPos = addCode(encoded, bitPos, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, bitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRunCode gets the code for the specified run. If the code is not
|
||||||
|
// terminal, returns the remainder to be determined later. Otherwise
|
||||||
|
// returns 0 remainder. Also returns the bool flag to indicate if
|
||||||
|
// the code is terminal
|
||||||
|
func getRunCode(runLen int, isWhite bool) (Code, int, bool) {
|
||||||
|
if runLen < 64 {
|
||||||
|
if isWhite {
|
||||||
|
return WTerms[runLen], 0, true
|
||||||
|
} else {
|
||||||
|
return BTerms[runLen], 0, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
multiplier := runLen / 64
|
||||||
|
|
||||||
|
// stands for lens more than 2560 which are not
|
||||||
|
// covered by the Huffman codes
|
||||||
|
if multiplier > 40 {
|
||||||
|
return CommonMakeups[2560], runLen - 2560, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// stands for lens more than 1728. These should be common
|
||||||
|
// for both colors
|
||||||
|
if multiplier > 27 {
|
||||||
|
return CommonMakeups[multiplier*64], runLen - multiplier*64, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lens < 27 we use the specific makeups for each color
|
||||||
|
if isWhite {
|
||||||
|
return WMakeups[multiplier*64], runLen - multiplier*64, false
|
||||||
|
} else {
|
||||||
|
return BMakeups[multiplier*64], runLen - multiplier*64, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcRun calculates the nex pixel run. Returns the number of the
|
||||||
|
// pixels and the new position in the original array
|
||||||
|
func calcRun(row []byte, isWhite bool, pos int) (int, int) {
|
||||||
|
count := 0
|
||||||
|
for pos < len(row) {
|
||||||
|
if isWhite {
|
||||||
|
if row[pos] != white {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if row[pos] != black {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCode encodes the specified code to the encoded starting with pos bit. pos
|
||||||
|
// bit is a bit position global to the whole encoded array
|
||||||
|
func addCode(encoded []byte, pos int, code Code) ([]byte, int) {
|
||||||
|
i := 0
|
||||||
|
for i < code.BitsWritten {
|
||||||
|
bytePos := pos / 8
|
||||||
|
bitPos := pos % 8
|
||||||
|
|
||||||
|
if bytePos >= len(encoded) {
|
||||||
|
encoded = append(encoded, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
toWrite := 8 - bitPos
|
||||||
|
leftToWrite := code.BitsWritten - i
|
||||||
|
if toWrite > leftToWrite {
|
||||||
|
toWrite = leftToWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < 8 {
|
||||||
|
encoded[bytePos] = encoded[bytePos] | byte(code.Code>>uint(8+bitPos-i))&masks[8-toWrite-bitPos]
|
||||||
|
} else {
|
||||||
|
encoded[bytePos] = encoded[bytePos] | (byte(code.Code<<uint(i-8))&masks[8-toWrite])>>uint(bitPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += toWrite
|
||||||
|
|
||||||
|
i += toWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEncodedRow appends the newly encoded row to the array of the encoded data.
|
||||||
|
// bitPos is a bitPos in the last byte of the encoded data. bitPos points where to
|
||||||
|
// write the next piece of data
|
||||||
|
func (e *Encoder) appendEncodedRow(encoded, newRow []byte, bitPos int) []byte {
|
||||||
|
if len(encoded) > 0 && bitPos != 0 && !e.EncodedByteAlign {
|
||||||
|
encoded[len(encoded)-1] = encoded[len(encoded)-1] | newRow[0]
|
||||||
|
|
||||||
|
encoded = append(encoded, newRow[1:]...)
|
||||||
|
} else {
|
||||||
|
encoded = append(encoded, newRow...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func rightShiftArray(row []byte, shiftAmount int) []byte {
|
||||||
|
for i := len(row) - 1; i >= 0; i-- {
|
||||||
|
row[i] = row[i] >> uint(shiftAmount)
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
row[i] |= row[i-1] << uint(8-shiftAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// seekChangingElem gets the position of the changing elem in the row based on
|
||||||
|
// the position of the currElem
|
||||||
|
func seekChangingElem(row []byte, currElem int) int {
|
||||||
|
if currElem >= len(row) {
|
||||||
|
return currElem
|
||||||
|
}
|
||||||
|
|
||||||
|
var color byte
|
||||||
|
if currElem > -1 {
|
||||||
|
color = row[currElem]
|
||||||
|
} else {
|
||||||
|
color = white
|
||||||
|
}
|
||||||
|
|
||||||
|
i := currElem + 1
|
||||||
|
for i < len(row) {
|
||||||
|
if row[i] != color {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// seekB1 gets the position of b1 based on the position of a0
|
||||||
|
func seekB1(codingLine, refLine []byte, a0 int) int {
|
||||||
|
changingElem := seekChangingElem(refLine, a0)
|
||||||
|
|
||||||
|
if changingElem < len(refLine) && (a0 == -1 && refLine[changingElem] == white ||
|
||||||
|
a0 >= 0 && a0 < len(codingLine) && codingLine[a0] == refLine[changingElem] ||
|
||||||
|
a0 >= len(codingLine) && codingLine[a0-1] != refLine[changingElem]) {
|
||||||
|
changingElem = seekChangingElem(refLine, changingElem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changingElem
|
||||||
|
}
|
||||||
|
|
||||||
|
func seekB12D(codingLine, refLine []byte, a0 int, a0isWhite bool) int {
|
||||||
|
changingElem := seekChangingElem(refLine, a0)
|
||||||
|
|
||||||
|
if changingElem < len(refLine) && (a0 == -1 && refLine[changingElem] == white ||
|
||||||
|
a0 >= 0 && a0 < len(codingLine) && codingLine[a0] == refLine[changingElem] ||
|
||||||
|
a0 >= len(codingLine) && a0isWhite && refLine[changingElem] == white ||
|
||||||
|
a0 >= len(codingLine) && !a0isWhite && refLine[changingElem] == black) {
|
||||||
|
changingElem = seekChangingElem(refLine, changingElem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changingElem
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodePassMode encodes the pass mode to the encodedRow starting with bitPos bit
|
||||||
|
func encodePassMode(encodedRow []byte, bitPos int) ([]byte, int) {
|
||||||
|
return addCode(encodedRow, bitPos, P)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeHorizontalMode encodes the horizontal mode to the encodedRow starting with bitPos bit
|
||||||
|
func encodeHorizontalMode(row, encodedRow []byte, bitPos, a0, a1 int) ([]byte, int, int) {
|
||||||
|
a2 := seekChangingElem(row, a1)
|
||||||
|
|
||||||
|
isWhite := a0 >= 0 && row[a0] == white || a0 == -1
|
||||||
|
|
||||||
|
encodedRow, bitPos = addCode(encodedRow, bitPos, H)
|
||||||
|
var a0a1RunLen int
|
||||||
|
if a0 > -1 {
|
||||||
|
a0a1RunLen = a1 - a0
|
||||||
|
} else {
|
||||||
|
a0a1RunLen = a1 - a0 - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedRow, bitPos = encodeRunLen(encodedRow, bitPos, a0a1RunLen, isWhite)
|
||||||
|
|
||||||
|
isWhite = !isWhite
|
||||||
|
|
||||||
|
a1a2RunLen := a2 - a1
|
||||||
|
|
||||||
|
encodedRow, bitPos = encodeRunLen(encodedRow, bitPos, a1a2RunLen, isWhite)
|
||||||
|
|
||||||
|
a0 = a2
|
||||||
|
|
||||||
|
return encodedRow, bitPos, a0
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeVerticalMode encodes vertical mode to the encodedRow starting with bitPos bit
|
||||||
|
func encodeVerticalMode(encodedRow []byte, bitPos, a1, b1 int) ([]byte, int) {
|
||||||
|
vCode := getVCode(a1, b1)
|
||||||
|
|
||||||
|
encodedRow, bitPos = addCode(encodedRow, bitPos, vCode)
|
||||||
|
|
||||||
|
return encodedRow, bitPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVCode get the code for the vertical mode based on
|
||||||
|
// the locations of a1 and b1
|
||||||
|
func getVCode(a1, b1 int) Code {
|
||||||
|
var vCode Code
|
||||||
|
|
||||||
|
switch b1 - a1 {
|
||||||
|
case -1:
|
||||||
|
vCode = V1R
|
||||||
|
case -2:
|
||||||
|
vCode = V2R
|
||||||
|
case -3:
|
||||||
|
vCode = V3R
|
||||||
|
case 0:
|
||||||
|
vCode = V0
|
||||||
|
case 1:
|
||||||
|
vCode = V1L
|
||||||
|
case 2:
|
||||||
|
vCode = V2L
|
||||||
|
case 3:
|
||||||
|
vCode = V3L
|
||||||
|
}
|
||||||
|
|
||||||
|
return vCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendWhiteReferenceLine appends the line full of white pixels just before
|
||||||
|
// the first image line
|
||||||
|
func appendWhiteReferenceLine(pixels [][]byte) [][]byte {
|
||||||
|
whiteRefLine := make([]byte, len(pixels[0]))
|
||||||
|
for i := range whiteRefLine {
|
||||||
|
whiteRefLine[i] = white
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = append(pixels, []byte{})
|
||||||
|
for i := len(pixels) - 1; i > 0; i-- {
|
||||||
|
pixels[i] = pixels[i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels[0] = whiteRefLine
|
||||||
|
|
||||||
|
return pixels
|
||||||
|
}
|
@ -34,6 +34,7 @@ import (
|
|||||||
lzw1 "golang.org/x/image/tiff/lzw"
|
lzw1 "golang.org/x/image/tiff/lzw"
|
||||||
|
|
||||||
"github.com/unidoc/unidoc/common"
|
"github.com/unidoc/unidoc/common"
|
||||||
|
"github.com/unidoc/unidoc/pdf/core/ccittfaxdecode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -1521,39 +1522,457 @@ func (enc *RawEncoder) EncodeBytes(data []byte) ([]byte, error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CCITTFaxEncoder implements CCITTFax encoder/decoder (dummy, for now)
|
//
|
||||||
// FIXME: implement
|
// CCITTFax encoder/decoder (dummy, for now)
|
||||||
type CCITTFaxEncoder struct{}
|
//
|
||||||
|
type CCITTFaxEncoder struct {
|
||||||
|
Mode CCITTMode
|
||||||
|
K int
|
||||||
|
EndOfLine bool
|
||||||
|
EncodedByteAlign bool
|
||||||
|
Columns int
|
||||||
|
Rows int
|
||||||
|
EndOfBlock bool
|
||||||
|
BlackIs1 bool
|
||||||
|
DamagedRowsBeforeError int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CCITTMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Group3 CCITTMode = iota
|
||||||
|
Group4
|
||||||
|
GroupMixed
|
||||||
|
GroupUnknown
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mode CCITTMode) String() string {
|
||||||
|
switch mode {
|
||||||
|
case Group3:
|
||||||
|
return "Group 3"
|
||||||
|
case Group4:
|
||||||
|
return "Group 4"
|
||||||
|
case GroupMixed:
|
||||||
|
return "Mixed"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// <0 : Pure two-dimensional encoding (Group 4)
|
||||||
|
// =0 : Pure one-dimensional encoding (Group 3, 1-D)
|
||||||
|
// >0 : Mixed one- and two-dimensional encoding (Group 3, 2-D)
|
||||||
|
func IntToCCITTMode(v int) CCITTMode {
|
||||||
|
switch {
|
||||||
|
case v == 0:
|
||||||
|
return Group3
|
||||||
|
case v < 0:
|
||||||
|
return Group4
|
||||||
|
case v > 0:
|
||||||
|
return GroupMixed
|
||||||
|
default:
|
||||||
|
return GroupUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewCCITTFaxEncoder() *CCITTFaxEncoder {
|
func NewCCITTFaxEncoder() *CCITTFaxEncoder {
|
||||||
return &CCITTFaxEncoder{}
|
return &CCITTFaxEncoder{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *CCITTFaxEncoder) GetFilterName() string {
|
func (this *CCITTFaxEncoder) GetFilterName() string {
|
||||||
return StreamEncodingFilterNameCCITTFax
|
return StreamEncodingFilterNameCCITTFax
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *CCITTFaxEncoder) MakeDecodeParams() PdfObject {
|
func (this *CCITTFaxEncoder) MakeDecodeParams() PdfObject {
|
||||||
return nil
|
decodeParams := MakeDict()
|
||||||
|
decodeParams.Set("K", MakeInteger(int64(this.K)))
|
||||||
|
decodeParams.Set("Columns", MakeInteger(int64(this.Columns)))
|
||||||
|
decodeParams.Set("BlackIs1", MakeBool(this.BlackIs1))
|
||||||
|
decodeParams.Set("EncodedByteAlign", MakeBool(this.EncodedByteAlign))
|
||||||
|
decodeParams.Set("EndOfLine", MakeBool(this.EndOfLine))
|
||||||
|
decodeParams.Set("Rows", MakeInteger(int64(this.Rows)))
|
||||||
|
decodeParams.Set("EndOfBlock", MakeBool(this.EndOfBlock))
|
||||||
|
decodeParams.Set("DamagedRowsBeforeError", MakeInteger(int64(this.DamagedRowsBeforeError)))
|
||||||
|
|
||||||
|
return decodeParams
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeStreamDict makes a new instance of an encoding dictionary for a stream object.
|
// Make a new instance of an encoding dictionary for a stream object.
|
||||||
func (enc *CCITTFaxEncoder) MakeStreamDict() *PdfObjectDictionary {
|
func (this *CCITTFaxEncoder) MakeStreamDict() *PdfObjectDictionary {
|
||||||
return MakeDict()
|
dict := MakeDict()
|
||||||
|
dict.Set("Filter", MakeName(this.GetFilterName()))
|
||||||
|
|
||||||
|
decodeParams := this.MakeDecodeParams()
|
||||||
|
if decodeParams != nil {
|
||||||
|
dict.Set("DecodeParms", decodeParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *CCITTFaxEncoder) DecodeBytes(encoded []byte) ([]byte, error) {
|
// Create a new CCITTFax decoder from a stream object, getting all the encoding parameters
|
||||||
common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName())
|
// from the DecodeParms stream object dictionary entry.
|
||||||
return encoded, ErrNoCCITTFaxDecode
|
func newCCITTFaxEncoderFromStream(streamObj *PdfObjectStream, decodeParams *PdfObjectDictionary) (*CCITTFaxEncoder, error) {
|
||||||
|
encoder := NewCCITTFaxEncoder()
|
||||||
|
|
||||||
|
encDict := streamObj.PdfObjectDictionary
|
||||||
|
if encDict == nil {
|
||||||
|
// No encoding dictionary.
|
||||||
|
return encoder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If decodeParams not provided, see if we can get from the stream.
|
||||||
|
if decodeParams == nil {
|
||||||
|
obj := encDict.Get("DecodeParms")
|
||||||
|
if obj != nil {
|
||||||
|
if dp, isDict := obj.(*PdfObjectDictionary); isDict {
|
||||||
|
decodeParams = dp
|
||||||
|
} else if a, isArr := obj.(*PdfObjectArray); isArr {
|
||||||
|
if a.Len() == 1 {
|
||||||
|
if dp, isDict := a.Get(0).(*PdfObjectDictionary); isDict {
|
||||||
|
decodeParams = dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if decodeParams == nil {
|
||||||
|
common.Log.Error("DecodeParms not a dictionary %#v", obj)
|
||||||
|
return nil, fmt.Errorf("Invalid DecodeParms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := decodeParams.Get("K")
|
||||||
|
if obj != nil {
|
||||||
|
k, ok := obj.(*PdfObjectInteger)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("K is invalid")
|
||||||
|
}
|
||||||
|
fmt.Printf("K = %v\n", *k)
|
||||||
|
|
||||||
|
encoder.Mode = IntToCCITTMode(int(*k))
|
||||||
|
encoder.K = int(*k)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.Columns = 1728
|
||||||
|
obj = decodeParams.Get("Columns")
|
||||||
|
if obj != nil {
|
||||||
|
columns, ok := obj.(*PdfObjectInteger)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Columns is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.Columns = int(*columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = decodeParams.Get("BlackIs1")
|
||||||
|
if obj != nil {
|
||||||
|
switch inverse := obj.(type) {
|
||||||
|
case *PdfObjectInteger:
|
||||||
|
encoder.BlackIs1 = inverse != nil && *inverse > 0
|
||||||
|
case *PdfObjectBool:
|
||||||
|
encoder.BlackIs1 = inverse != nil && bool(*inverse)
|
||||||
|
default:
|
||||||
|
common.Log.Trace("BlackIs1 type: %T", obj)
|
||||||
|
return nil, fmt.Errorf("BlackIs1 is invalid")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj = encDict.Get("Decode")
|
||||||
|
if arr, ok := obj.(*PdfObjectArray); ok {
|
||||||
|
intArr, err := arr.ToIntegerArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Decode is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.BlackIs1 = intArr[0] == 1 && intArr[1] == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = decodeParams.Get("EncodedByteAlign")
|
||||||
|
if obj != nil {
|
||||||
|
switch align := obj.(type) {
|
||||||
|
case *PdfObjectInteger:
|
||||||
|
encoder.EncodedByteAlign = align != nil && *align > 0
|
||||||
|
case *PdfObjectBool:
|
||||||
|
encoder.EncodedByteAlign = align != nil && bool(*align)
|
||||||
|
default:
|
||||||
|
common.Log.Trace("EncodedByteAlign type: %T", obj)
|
||||||
|
return nil, fmt.Errorf("EncodedByteAlign is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = decodeParams.Get("EndOfLine")
|
||||||
|
if obj != nil {
|
||||||
|
switch eol := obj.(type) {
|
||||||
|
case *PdfObjectInteger:
|
||||||
|
encoder.EndOfLine = eol != nil && *eol > 0
|
||||||
|
case *PdfObjectBool:
|
||||||
|
encoder.EncodedByteAlign = eol != nil && bool(*eol)
|
||||||
|
default:
|
||||||
|
common.Log.Trace("EncodedByteAlign type: %T", obj)
|
||||||
|
return nil, fmt.Errorf("EncodedByteAlign is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.Rows = 0
|
||||||
|
obj = decodeParams.Get("Rows")
|
||||||
|
if obj != nil {
|
||||||
|
rows, ok := obj.(*PdfObjectInteger)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Rows is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.Columns = int(*rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.EndOfBlock = true
|
||||||
|
obj = decodeParams.Get("EndOfBlock")
|
||||||
|
if obj != nil {
|
||||||
|
switch eob := obj.(type) {
|
||||||
|
case *PdfObjectInteger:
|
||||||
|
if eob != nil && *eob <= 0 {
|
||||||
|
encoder.EndOfBlock = false
|
||||||
|
}
|
||||||
|
case *PdfObjectBool:
|
||||||
|
if eob != nil {
|
||||||
|
encoder.EndOfBlock = bool(*eob)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
common.Log.Trace("EncodedByteAlign type: %T", obj)
|
||||||
|
return nil, fmt.Errorf("EncodedByteAlign is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.DamagedRowsBeforeError = 0
|
||||||
|
obj = decodeParams.Get("DamagedRowsBeforeError")
|
||||||
|
if obj != nil {
|
||||||
|
rows, ok := obj.(*PdfObjectInteger)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("DamagedRowsBeforeError is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.DamagedRowsBeforeError = int(*rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
common.Log.Trace("decode params: %s", decodeParams.String())
|
||||||
|
return encoder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *CCITTFaxEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
|
/*func getPixels(file io.Reader) ([][]byte, error) {
|
||||||
common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName())
|
img, _, err := goimage.Decode(file)
|
||||||
return streamObj.Stream, ErrNoCCITTFaxDecode
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bounds := img.Bounds()
|
||||||
|
w, h := bounds.Max.X, bounds.Max.Y
|
||||||
|
var pixels [][]byte
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
var row []byte
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
r, g, b, _ := img.At(x, y).RGBA()
|
||||||
|
if r == 65535 && g == 65535 && b == 65535 {
|
||||||
|
// append white
|
||||||
|
row = append(row, 1)
|
||||||
|
} else {
|
||||||
|
row = append(row, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixels = append(pixels, row)
|
||||||
|
}
|
||||||
|
return pixels, nil
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (this *CCITTFaxEncoder) DecodeBytes(encoded []byte) ([]byte, error) {
|
||||||
|
encoder := &ccittfaxdecode.Encoder{
|
||||||
|
K: this.K,
|
||||||
|
Columns: this.Columns,
|
||||||
|
EndOfLine: this.EndOfLine,
|
||||||
|
EndOfBlock: this.EndOfBlock,
|
||||||
|
BlackIs1: this.BlackIs1,
|
||||||
|
DamagedRowsBeforeError: this.DamagedRowsBeforeError,
|
||||||
|
Rows: this.Rows,
|
||||||
|
EncodedByteAlign: this.EncodedByteAlign,
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels, err := encoder.Decode(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded := make([]byte, len(pixels)*len(pixels[0])*3)
|
||||||
|
|
||||||
|
decodedInd := 0
|
||||||
|
for i := range pixels {
|
||||||
|
for j := range pixels[i] {
|
||||||
|
if pixels[i][j] == 1 {
|
||||||
|
decoded[decodedInd] = 255
|
||||||
|
decoded[decodedInd+1] = 255
|
||||||
|
decoded[decodedInd+2] = 255
|
||||||
|
//decoded[decodedInd+3] = 255
|
||||||
|
} else {
|
||||||
|
decoded[decodedInd] = 0
|
||||||
|
decoded[decodedInd+1] = 0
|
||||||
|
decoded[decodedInd+2] = 0
|
||||||
|
//decoded[decodedInd+3] = 255
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedInd += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*goimage.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
|
||||||
|
t := goimage.NewRGBA(goimage.Rect(0, 0, len(pixels[0]), len(pixels)))
|
||||||
|
t.Pix = decoded
|
||||||
|
file, err := os.Create("/home/darkrengarius/Downloads/test2222222.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error opening file: %v\n", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = png.Encode(file, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*file, err := os.Open("/home/darkrengarius/Downloads/scan223.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error opening file: %v\n", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
img, _, err := goimage.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bounds := img.Bounds()
|
||||||
|
w, h := bounds.Max.X, bounds.Max.Y
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
var row []byte
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
r, g, b, _ := img.At(x, y).RGBA()
|
||||||
|
if r == 65535 && g == 65535 && b == 65535 {
|
||||||
|
// append white
|
||||||
|
row = append(row, 1)
|
||||||
|
} else {
|
||||||
|
row = append(row, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixels = append(pixels, row)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*img := image.NewRGBA(image.Rect(0, 0, len(pixels[0]), len(pixels)))
|
||||||
|
imgPix := 0
|
||||||
|
for i := range pixels {
|
||||||
|
for j := range pixels[i] {
|
||||||
|
//log.Printf("%v: %v\n", i, j)
|
||||||
|
img.Pix[imgPix] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+1] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+2] = 255 * pixels[i][j]
|
||||||
|
img.Pix[imgPix+3] = 255
|
||||||
|
imgPix += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := png.Encode(buf, img)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
imgData := buf.Bytes()
|
||||||
|
f, err := os.Create("/home/darkrengarius/Downloads/testDecodePdf.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error writing to file: %v\n", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.Write(imgData); err != nil {
|
||||||
|
log.Fatalf("Error writing to file: %v\n", err)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*arr, err := ccittfaxdecode.NewCCITTFaxDecoder(uint(this.Columns), encoded).Decode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]byte, 0)
|
||||||
|
for i := range arr {
|
||||||
|
result = append(result, arr[i]...)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return decoded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *CCITTFaxEncoder) EncodeBytes(data []byte) ([]byte, error) {
|
func (this *CCITTFaxEncoder) DecodeStream(streamObj *PdfObjectStream) ([]byte, error) {
|
||||||
common.Log.Debug("Error: Attempting to use unsupported encoding %s", enc.GetFilterName())
|
return this.DecodeBytes(streamObj.Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CCITTFaxEncoder) EncodeBytes(data []byte) ([]byte, error) {
|
||||||
|
/*file, err := os.Open("/home/darkrengarius/Downloads/scan223.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error opening file: %v\n", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bounds := img.Bounds()
|
||||||
|
w, h := bounds.Max.X, bounds.Max.Y
|
||||||
|
var pixels [][]byte
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
var row []byte
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
if y == 8 && x == 12 {
|
||||||
|
log.Println()
|
||||||
|
}
|
||||||
|
r, g, b, _ := img.At(x, y).RGBA()
|
||||||
|
if r == 65535 && g == 65535 && b == 65535 {
|
||||||
|
// append white
|
||||||
|
row = append(row, 1)
|
||||||
|
} else {
|
||||||
|
row = append(row, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixels = append(pixels, row)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var pixels [][]byte
|
||||||
|
|
||||||
|
for i := 0; i < len(data); i += 3 * this.Columns {
|
||||||
|
pixelsRow := make([]byte, this.Columns)
|
||||||
|
|
||||||
|
pixel := 0
|
||||||
|
for j := 0; j < 3*this.Columns; j += 3 {
|
||||||
|
|
||||||
|
// TODO: check BlackIs1
|
||||||
|
if data[i+j] == 255 {
|
||||||
|
if this.BlackIs1 {
|
||||||
|
pixelsRow[pixel] = 0
|
||||||
|
} else {
|
||||||
|
pixelsRow[pixel] = 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if this.BlackIs1 {
|
||||||
|
pixelsRow[pixel] = 1
|
||||||
|
} else {
|
||||||
|
pixelsRow[pixel] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixel++
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = append(pixels, pixelsRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := &ccittfaxdecode.Encoder{
|
||||||
|
K: this.K,
|
||||||
|
Columns: this.Columns,
|
||||||
|
EndOfLine: this.EndOfLine,
|
||||||
|
EndOfBlock: this.EndOfBlock,
|
||||||
|
BlackIs1: this.BlackIs1,
|
||||||
|
DamagedRowsBeforeError: this.DamagedRowsBeforeError,
|
||||||
|
Rows: this.Rows,
|
||||||
|
EncodedByteAlign: this.EncodedByteAlign,
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoder.Encode(pixels), nil
|
||||||
|
|
||||||
|
common.Log.Debug("Error: Attempting to use unsupported encoding %s", this.GetFilterName())
|
||||||
return data, ErrNoCCITTFaxDecode
|
return data, ErrNoCCITTFaxDecode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ func NewEncoderFromStream(streamObj *PdfObjectStream) (StreamEncoder, error) {
|
|||||||
} else if *method == StreamEncodingFilterNameASCII85 || *method == "A85" {
|
} else if *method == StreamEncodingFilterNameASCII85 || *method == "A85" {
|
||||||
return NewASCII85Encoder(), nil
|
return NewASCII85Encoder(), nil
|
||||||
} else if *method == StreamEncodingFilterNameCCITTFax {
|
} else if *method == StreamEncodingFilterNameCCITTFax {
|
||||||
return NewCCITTFaxEncoder(), nil
|
return newCCITTFaxEncoderFromStream(streamObj, nil)
|
||||||
} else if *method == StreamEncodingFilterNameJBIG2 {
|
} else if *method == StreamEncodingFilterNameJBIG2 {
|
||||||
return NewJBIG2Encoder(), nil
|
return NewJBIG2Encoder(), nil
|
||||||
} else if *method == StreamEncodingFilterNameJPX {
|
} else if *method == StreamEncodingFilterNameJPX {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user