2019-05-16 20:44:51 +00:00

773 lines
20 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package ccittfax
import (
"errors"
)
var (
// errEOFBCorrupt is returned when the corrupt EOFB (end-of-block) code is found.
errEOFBCorrupt = errors.New("EOFB code is corrupted")
// errRTCCorrupt is returned when the corrupt RTC (return-the-carriage) code is found.
errRTCCorrupt = errors.New("RTC code is corrupted")
// errWrongCodeInHorizontalMode is returned when entered the horizontal mode and unknown bit
// sequence met.
errWrongCodeInHorizontalMode = errors.New("wrong code in horizontal mode")
// errNoEOLFound is returned when the EndOfLine parameter is true in filter but no EOL (end-of-line) met.
errNoEOLFound = errors.New("no EOL found while the EndOfLine parameter is true")
// errInvalidEOL is returned when the EOL code is corrupt.
errInvalidEOL = errors.New("invalid EOL")
// errInvalid2DCode is returned when the invalid 2 dimensional code is met. 2 dimensional code
// according to the CCITT reccommendations is one of the following: H, P, V0, V1L, V2L, V3L, V1R, V2R, V3R.
errInvalid2DCode = errors.New("invalid 2D code")
)
// trees represent the finite state machine for parsing bit sequences and fetching pixel run lengths
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)
}
// Decode performs decoding operation on the encoded image using the Group3 or Group4
// CCITT facsimile (fax) algorithm.
func (e *Encoder) Decode(encoded []byte) ([][]byte, error) {
if e.BlackIs1 {
white = 0
black = 1
} else {
white = 1
black = 0
}
if e.K == 0 {
// decode Group3 1-dimensional
return e.decodeG31D(encoded)
}
if e.K > 0 {
// decode Group3 mixed dimensional (1D/2D)
return e.decodeG32D(encoded)
}
if e.K < 4 {
// decode Group4
return e.decodeG4(encoded)
}
return nil, nil
}
// decodeG31D decodes the encoded image data using the Group3 1-dimensional
// CCITT facsimile (fax) decoding algorithm.
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, errNoEOLFound
}
} 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, errInvalidEOL
}
}
// got RTC
if gotEOL {
break
}
}
var row []byte
row, bitPos = e.decodeRow1D(encoded, bitPos)
if e.EncodedByteAlign && bitPos%8 != 0 {
// align to byte border
bitPos += 8 - bitPos%8
}
pixels = append(pixels, row)
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= e.Rows {
break
}
}
return pixels, nil
}
// decodeG32D decodes the encoded image data using the Group3 mixed (1D/2D) dimensional
// CCITT facsimile (fax) decoding algorithm.
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, errNoEOLFound
}
}
// decode 1st of K rows as 1D
var row []byte
row, bitPos = e.decodeRow1D(encoded, bitPos)
if e.EncodedByteAlign && bitPos%8 != 0 {
// align to byte border
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 {
// only EOL0 or RTC should be met here. If neither of these met -
// the data is considered corrupt
gotEOL, bitPos, err = tryFetchRTC2D(encoded, bitPos)
if err != nil {
return nil, err
}
if gotEOL {
break byteLoop
} else {
if e.EndOfLine {
return nil, errNoEOLFound
}
}
}
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
}
if len(pixelsRow) >= e.Columns {
break
}
}
if e.EncodedByteAlign && bitPos%8 != 0 {
// align to byte border
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
}
// decodeG4 decodes the encoded image data using the Group4 CCITT facsimile (fax)
// decoding algorithm.
func (e *Encoder) decodeG4(encoded []byte) ([][]byte, error) {
// append white reference line
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) {
// 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, errInvalid2DCode
}
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 {
// align to byte border
bitPos += 8 - bitPos%8
}
pixels = append(pixels, pixelsRow)
if e.Rows > 0 && !e.EndOfBlock && len(pixels) >= (e.Rows+1) {
break
}
}
// remove the white reference line
pixels = pixels[1:]
return pixels, nil
}
// decodeVerticalMode decodes the part of data using the vertical mode. Returns the moved `a0` and the
// pixels row filled with the decoded pixels.
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
}
// decodePassMode decodes the part of data using the pass mode. Returns the moved `a0` and the
// pixels row filled with the decoded pixels.
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
}
// decodeHorizontalMode decodes the part of data using the horizontal mode. Returns the moved `a0`, the moved
// global bit position and the pixels row filled with the decoded pixels. The returned bit position is not
// moved is the error occurs.
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
}
// decodeNextRunLen decodes tries to decode the next part of data using the Group3 1-dimensional code.
// Returns moved bit position and the pixels row filled with the decoded pixels. The returned bit position
// is not moved if the error occurs. Returns `errWrongCodeInHorizontalMode` if none of the 1-dimensional codes found.
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, errWrongCodeInHorizontalMode
}
return pixelsRow, bitPos, nil
}
// tryFetchRTC2D tries to fetch the RTC code (0000000000011 X 6) for Group3 mixed (1D/2D) dimensional encoding from
// the encoded data. Returns the moved bit position if the code was found. The other way returns the
// the original bit position. The `errRTCCorrupt` is returned if the RTC code is corrupt. The RTC code is considered
// corrupt if there are more than one EOL1 code (0000000000011) is met.
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, errRTCCorrupt
} else {
bitPos = startingBitPos
break
}
}
}
return gotEOL, bitPos, nil
}
// tryFetchEOFB tries to fetch the EOFB code (000000000001 X 2) for Group4 encoding from
// the encoded data. Returns the moved bit position if the code was found. The other way returns the
// the original bit position. The `errEOFBCorrupt` is returned if the EOFB code is corrupt. The EOFB code is considered
// corrupt if there is a single EOL code (000000000001).
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, errEOFBCorrupt
}
}
return false, startingBitPos, nil
}
// bitFromUint16 fetches the bit value from the `num` at the `bitPos` position.
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))
}
// fetchNextRunLen fetches the next Group3 1 dimensional code from the encoded data.
// Returns the corresponding pixel run length and the moved bit position. The returned
// bit position is not moved if no valid code is met.
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
}
// fetchNext2DCode fetches the next 2-dimensional code from the encoded data.
// Returns the code and the moved bit position. The returned bit position is not moved
// if no valid code is met.
func fetchNext2DCode(data []byte, bitPos int) (code, int, bool) {
var (
codeNum uint16
codeBitPos int
startingBitPos int
)
startingBitPos = bitPos
codeNum, codeBitPos, bitPos = fetchNextCode(data, bitPos)
codeStruct, ok := get2DCodeFromUint16(codeNum, codeBitPos)
if !ok {
return code{}, startingBitPos, false
}
return codeStruct, startingBitPos + codeStruct.BitsWritten, true
}
// get2DCodeFromUint16 finds the 2-dimensional code from the encoded data presented as
// uint16.
func get2DCodeFromUint16(encoded uint16, bitPos int) (code, bool) {
_, codePtr := findRunLen(twoDimTree, encoded, bitPos)
if codePtr == nil {
return code{}, false
}
return *codePtr, true
}
// decodeRunLenFromUint16 finds the 1-dimensional code from the encoded data presented as
// uint16.
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
}
// fetchNextCode assembles the next at most 16 bits starting from the `bitPos` into
// a single uint16 value. Returns the moved bit position.
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
}
// decodeRow1D decodes the next pixels row using the Group3 1-dimensional CCITT facsimile (fax)
// decoding algorithm.
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
}
// drawPixels appends the length pixels of the specified color to the `row`.
func drawPixels(row []byte, isWhite bool, length int) []byte {
if length < 0 {
return row
}
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
}
// tryFetchEOL tries to fetch the EOL code (000000000001) from the encoded data starting
// from the `bitPos` position. Returns the moved bit position if the code was met, and the original
// position otherwise.
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
}
}
// tryFetchExtendedEOL tries to fetch the extended EOL code (0000000000011 or 0000000000010) from
// the encoded data. Returns the moved bit position if the code was met, and the original
// position otherwise.
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
}
}
// tryFetchEOL0 tries to fetch the EOL0 code (0000000000010) from the encoded data. Returns the moved bit
// position if the code was met, and the original position otherwise.
func tryFetchEOL0(encoded []byte, bitPos int) (bool, int) {
return tryFetchExtendedEOL(encoded, bitPos, eol0)
}
// tryFetchEOL1 tries to fetch the EOL1 code (0000000000011) from the encoded data. Returns the moved bit
// position if the code was met, and the original position otherwise.
func tryFetchEOL1(encoded []byte, bitPos int) (bool, int) {
return tryFetchExtendedEOL(encoded, bitPos, eol1)
}