blob: 540176b562b6deca09407eb09d766b84b2a7d21c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.common.itu_t4;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.common.BitArrayOutputStream;
import org.apache.commons.imaging.common.BitInputStreamFlexible;
public class T4AndT6Compression {
private static final HuffmanTree whiteRunLengths = new HuffmanTree();
private static final HuffmanTree blackRunLengths = new HuffmanTree();
private static final HuffmanTree controlCodes = new HuffmanTree();
private static final int WHITE = 0;
private static final int BLACK = 1;
static {
try {
for (int i = 0; i < T4_T6_Tables.whiteTerminatingCodes.length; i++) {
T4_T6_Tables.Entry entry = T4_T6_Tables.whiteTerminatingCodes[i];
whiteRunLengths.insert(entry.bitString, entry.value);
}
for (int i = 0; i < T4_T6_Tables.whiteMakeUpCodes.length; i++) {
T4_T6_Tables.Entry entry = T4_T6_Tables.whiteMakeUpCodes[i];
whiteRunLengths.insert(entry.bitString, entry.value);
}
for (int i = 0; i < T4_T6_Tables.blackTerminatingCodes.length; i++) {
T4_T6_Tables.Entry entry = T4_T6_Tables.blackTerminatingCodes[i];
blackRunLengths.insert(entry.bitString, entry.value);
}
for (int i = 0; i < T4_T6_Tables.blackMakeUpCodes.length; i++) {
T4_T6_Tables.Entry entry = T4_T6_Tables.blackMakeUpCodes[i];
blackRunLengths.insert(entry.bitString, entry.value);
}
for (int i = 0; i < T4_T6_Tables.additionalMakeUpCodes.length; i++) {
T4_T6_Tables.Entry entry = T4_T6_Tables.additionalMakeUpCodes[i];
whiteRunLengths.insert(entry.bitString, entry.value);
blackRunLengths.insert(entry.bitString, entry.value);
}
controlCodes.insert(T4_T6_Tables.EOL.bitString, T4_T6_Tables.EOL);
controlCodes.insert(T4_T6_Tables.EOL13.bitString, T4_T6_Tables.EOL13);
controlCodes.insert(T4_T6_Tables.EOL14.bitString, T4_T6_Tables.EOL14);
controlCodes.insert(T4_T6_Tables.EOL15.bitString, T4_T6_Tables.EOL15);
controlCodes.insert(T4_T6_Tables.EOL16.bitString, T4_T6_Tables.EOL16);
controlCodes.insert(T4_T6_Tables.EOL17.bitString, T4_T6_Tables.EOL17);
controlCodes.insert(T4_T6_Tables.EOL18.bitString, T4_T6_Tables.EOL18);
controlCodes.insert(T4_T6_Tables.EOL19.bitString, T4_T6_Tables.EOL19);
controlCodes.insert(T4_T6_Tables.P.bitString, T4_T6_Tables.P);
controlCodes.insert(T4_T6_Tables.H.bitString, T4_T6_Tables.H);
controlCodes.insert(T4_T6_Tables.V0.bitString, T4_T6_Tables.V0);
controlCodes.insert(T4_T6_Tables.VL1.bitString, T4_T6_Tables.VL1);
controlCodes.insert(T4_T6_Tables.VL2.bitString, T4_T6_Tables.VL2);
controlCodes.insert(T4_T6_Tables.VL3.bitString, T4_T6_Tables.VL3);
controlCodes.insert(T4_T6_Tables.VR1.bitString, T4_T6_Tables.VR1);
controlCodes.insert(T4_T6_Tables.VR2.bitString, T4_T6_Tables.VR2);
controlCodes.insert(T4_T6_Tables.VR3.bitString, T4_T6_Tables.VR3);
} catch (HuffmanTreeException cannotHappen) {
}
}
private static void compress1DLine(BitInputStreamFlexible inputStream, BitArrayOutputStream outputStream,
int[] referenceLine, int width) throws ImageWriteException {
int color = WHITE;
int runLength = 0;
for (int x = 0; x < width; x++) {
try {
int nextColor = inputStream.readBits(1);
if (referenceLine != null) {
referenceLine[x] = nextColor;
}
if (color == nextColor) {
++runLength;
} else {
writeRunLength(outputStream, runLength, color);
color = nextColor;
runLength = 1;
}
} catch (IOException ioException) {
throw new ImageWriteException("Error reading image to compress", ioException);
}
}
writeRunLength(outputStream, runLength, color);
}
/**
* Compressed with the "Modified Huffman" encoding of section 10 in the TIFF6 specification.
* No EOLs, no RTC, rows are padded to end on a byte boundary.
* @param uncompressed
* @param width
* @param height
* @return the compressed data
* @throws ImageReadException
*/
public static byte[] compressModifiedHuffman(byte[] uncompressed, int width, int height) throws ImageWriteException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(uncompressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
for (int y = 0; y < height; y++) {
compress1DLine(inputStream, outputStream, null, width);
inputStream.flushCache();
outputStream.flush();
}
return outputStream.toByteArray();
}
/**
* Decompresses the "Modified Huffman" encoding of section 10 in the TIFF6 specification.
* No EOLs, no RTC, rows are padded to end on a byte boundary.
* @param compressed
* @param width
* @param height
* @return the decompressed data
* @throws ImageReadException
*/
public static byte[] decompressModifiedHuffman(byte[] compressed, int width, int height) throws ImageReadException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(compressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
for (int y = 0; y < height; y++) {
int color = WHITE;
int rowLength;
for (rowLength = 0; rowLength < width;) {
int runLength = readTotalRunLength(inputStream, color);
for (int i = 0; i < runLength; i++) {
outputStream.writeBit(color);
}
color = 1 - color;
rowLength += runLength;
}
if (rowLength == width) {
inputStream.flushCache();
outputStream.flush();
} else if (rowLength > width) {
throw new ImageReadException("Unrecoverable row length error in image row " + y);
}
}
return outputStream.toByteArray();
}
public static byte[] compressT4_1D(byte[] uncompressed, int width, int height, boolean hasFill) throws ImageWriteException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(uncompressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
if (hasFill) {
T4_T6_Tables.EOL16.writeBits(outputStream);
} else {
T4_T6_Tables.EOL.writeBits(outputStream);
}
for (int y = 0; y < height; y++) {
compress1DLine(inputStream, outputStream, null, width);
if (hasFill) {
int bitsAvailable = outputStream.getBitsAvailableInCurrentByte();
if (bitsAvailable < 4) {
outputStream.flush();
bitsAvailable = 8;
}
for (; bitsAvailable > 4; bitsAvailable--) {
outputStream.writeBit(0);
}
}
T4_T6_Tables.EOL.writeBits(outputStream);
inputStream.flushCache();
}
return outputStream.toByteArray();
}
/**
* Decompresses T.4 1D encoded data. EOL at the beginning and after each row,
* can be preceded by fill bits to fit on a byte boundary, no RTC.
* @param compressed
* @param width
* @param height
* @return the decompressed data
* @throws ImageReadException
*/
public static byte[] decompressT4_1D(byte[] compressed, int width, int height, boolean hasFill) throws ImageReadException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(compressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
for (int y = 0; y < height; y++) {
T4_T6_Tables.Entry entry;
int rowLength;
try {
entry = (T4_T6_Tables.Entry) controlCodes.decode(inputStream);
if (!isEOL(entry, hasFill)) {
throw new ImageReadException("Expected EOL not found");
}
int color = WHITE;
for (rowLength = 0; rowLength < width;) {
int runLength = readTotalRunLength(inputStream, color);
for (int i = 0; i < runLength; i++) {
outputStream.writeBit(color);
}
color = 1 - color;
rowLength += runLength;
}
} catch (HuffmanTreeException huffmanException) {
throw new ImageReadException("Decompression error", huffmanException);
}
if (rowLength == width) {
outputStream.flush();
} else if (rowLength > width) {
throw new ImageReadException("Unrecoverable row length error in image row " + y);
}
}
return outputStream.toByteArray();
}
public static byte[] compressT4_2D(byte[] uncompressed, int width, int height, boolean hasFill, int parameterK) throws ImageWriteException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(uncompressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
int[] referenceLine = new int[width];
int[] codingLine = new int[width];
int kCounter = 0;
if (hasFill) {
T4_T6_Tables.EOL16.writeBits(outputStream);
} else {
T4_T6_Tables.EOL.writeBits(outputStream);
}
for (int y = 0; y < height; y++) {
if (kCounter > 0) {
// 2D
outputStream.writeBit(0);
for (int i = 0; i < width; i++) {
try {
codingLine[i] = inputStream.readBits(1);
} catch (IOException ioException) {
throw new ImageWriteException("Error reading image to compress", ioException);
}
}
int codingA0Color = WHITE;
int referenceA0Color = WHITE;
int a1 = nextChangingElement(codingLine, codingA0Color, 0);
int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
for (int a0 = 0; a0 < width; ) {
if (b2 < a1) {
T4_T6_Tables.P.writeBits(outputStream);
a0 = b2;
} else {
int a1b1 = a1 - b1;
if (-3 <= a1b1 && a1b1 <= 3) {
T4_T6_Tables.Entry entry;
if (a1b1 == -3) {
entry = T4_T6_Tables.VL3;
} else if (a1b1 == -2) {
entry = T4_T6_Tables.VL2;
} else if (a1b1 == -1) {
entry = T4_T6_Tables.VL1;
} else if (a1b1 == 0) {
entry = T4_T6_Tables.V0;
} else if (a1b1 == 1) {
entry = T4_T6_Tables.VR1;
} else if (a1b1 == 2) {
entry = T4_T6_Tables.VR2;
} else {
entry = T4_T6_Tables.VR3;
}
entry.writeBits(outputStream);
codingA0Color = 1 - codingA0Color;
a0 = a1;
} else {
int a2 = nextChangingElement(codingLine, 1 - codingA0Color, a1 + 1);
int a0a1 = a1 - a0;
int a1a2 = a2 - a1;
T4_T6_Tables.H.writeBits(outputStream);
writeRunLength(outputStream, a0a1, codingA0Color);
writeRunLength(outputStream, a1a2, 1 - codingA0Color);
a0 = a2;
}
}
referenceA0Color = changingElementAt(referenceLine, a0);
a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1);
if (codingA0Color == referenceA0Color) {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
} else {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
}
b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
}
int[] swap = referenceLine;
referenceLine = codingLine;
codingLine = swap;
} else {
// 1D
outputStream.writeBit(1);
compress1DLine(inputStream, outputStream, referenceLine, width);
}
if (hasFill) {
int bitsAvailable = outputStream.getBitsAvailableInCurrentByte();
if (bitsAvailable < 4) {
outputStream.flush();
bitsAvailable = 8;
}
for (; bitsAvailable > 4; bitsAvailable--) {
outputStream.writeBit(0);
}
}
T4_T6_Tables.EOL.writeBits(outputStream);
kCounter++;
if (kCounter == parameterK) {
kCounter = 0;
}
inputStream.flushCache();
}
return outputStream.toByteArray();
}
/**
* Decompressed T.4 2D encoded data. EOL at the beginning and after each row,
* can be preceded by fill bits to fit on a byte boundary, and is succeeded
* by a tag bit determining whether the next line is encoded using 1D or 2D.
* No RTC.
* @param compressed
* @param width
* @param height
* @return the decompressed data
* @throws ImageReadException
*/
public static byte[] decompressT4_2D(byte[] compressed, int width, int height, boolean hasFill) throws ImageReadException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(compressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
int[] referenceLine = new int[width];
for (int y = 0; y < height; y++) {
T4_T6_Tables.Entry entry;
int rowLength = 0;
try {
entry = (T4_T6_Tables.Entry) controlCodes.decode(inputStream);
if (!isEOL(entry, hasFill)) {
throw new ImageReadException("Expected EOL not found");
}
int tagBit = inputStream.readBits(1);
if (tagBit == 0) {
// 2D
int codingA0Color = WHITE;
int referenceA0Color = WHITE;
int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
for (int a0 = 0; a0 < width; ) {
int a1, a2;
entry = (T4_T6_Tables.Entry) controlCodes.decode(inputStream);
if (entry == T4_T6_Tables.P) {
fillRange(outputStream, referenceLine, a0, b2, codingA0Color);
a0 = b2;
} else if (entry == T4_T6_Tables.H) {
int a0a1 = readTotalRunLength(inputStream, codingA0Color);
a1 = a0 + a0a1;
fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color);
a2 = a1 + a1a2;
fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color);
a0 = a2;
} else {
int a1b1;
if (entry == T4_T6_Tables.V0) {
a1b1 = 0;
} else if (entry == T4_T6_Tables.VL1) {
a1b1 = -1;
} else if (entry == T4_T6_Tables.VL2) {
a1b1 = -2;
} else if (entry == T4_T6_Tables.VL3) {
a1b1 = -3;
} else if (entry == T4_T6_Tables.VR1) {
a1b1 = 1;
} else if (entry == T4_T6_Tables.VR2) {
a1b1 = 2;
} else if (entry == T4_T6_Tables.VR3) {
a1b1 = 3;
} else {
throw new ImageReadException("Invalid/unknown T.4 control code " + entry.bitString);
}
a1 = b1 + a1b1;
fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
a0 = a1;
codingA0Color = 1 - codingA0Color;
}
referenceA0Color = changingElementAt(referenceLine, a0);
if (codingA0Color == referenceA0Color) {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
} else {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
}
b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
rowLength = a0;
}
} else {
// 1D
int color = WHITE;
for (rowLength = 0; rowLength < width;) {
int runLength = readTotalRunLength(inputStream, color);
for (int i = 0; i < runLength; i++) {
outputStream.writeBit(color);
referenceLine[rowLength + i] = color;
}
color = 1 - color;
rowLength += runLength;
}
}
} catch (IOException ioException) {
throw new ImageReadException("Decompression error", ioException);
} catch (HuffmanTreeException huffmanException) {
throw new ImageReadException("Decompression error", huffmanException);
}
if (rowLength == width) {
outputStream.flush();
} else if (rowLength > width) {
throw new ImageReadException("Unrecoverable row length error in image row " + y);
}
}
return outputStream.toByteArray();
}
public static byte[] compressT6(byte[] uncompressed, int width, int height) throws ImageWriteException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(uncompressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
int[] referenceLine = new int[width];
int[] codingLine = new int[width];
for (int y = 0; y < height; y++) {
for (int i = 0; i < width; i++) {
try {
codingLine[i] = inputStream.readBits(1);
} catch (IOException ioException) {
throw new ImageWriteException("Error reading image to compress", ioException);
}
}
int codingA0Color = WHITE;
int referenceA0Color = WHITE;
int a1 = nextChangingElement(codingLine, codingA0Color, 0);
int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
for (int a0 = 0; a0 < width; ) {
if (b2 < a1) {
T4_T6_Tables.P.writeBits(outputStream);
a0 = b2;
} else {
int a1b1 = a1 - b1;
if (-3 <= a1b1 && a1b1 <= 3) {
T4_T6_Tables.Entry entry;
if (a1b1 == -3) {
entry = T4_T6_Tables.VL3;
} else if (a1b1 == -2) {
entry = T4_T6_Tables.VL2;
} else if (a1b1 == -1) {
entry = T4_T6_Tables.VL1;
} else if (a1b1 == 0) {
entry = T4_T6_Tables.V0;
} else if (a1b1 == 1) {
entry = T4_T6_Tables.VR1;
} else if (a1b1 == 2) {
entry = T4_T6_Tables.VR2;
} else {
entry = T4_T6_Tables.VR3;
}
entry.writeBits(outputStream);
codingA0Color = 1 - codingA0Color;
a0 = a1;
} else {
int a2 = nextChangingElement(codingLine, 1 - codingA0Color, a1 + 1);
int a0a1 = a1 - a0;
int a1a2 = a2 - a1;
T4_T6_Tables.H.writeBits(outputStream);
writeRunLength(outputStream, a0a1, codingA0Color);
writeRunLength(outputStream, a1a2, 1 - codingA0Color);
a0 = a2;
}
}
referenceA0Color = changingElementAt(referenceLine, a0);
a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1);
if (codingA0Color == referenceA0Color) {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
} else {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
}
b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
}
int[] swap = referenceLine;
referenceLine = codingLine;
codingLine = swap;
inputStream.flushCache();
}
// EOFB
T4_T6_Tables.EOL.writeBits(outputStream);
T4_T6_Tables.EOL.writeBits(outputStream);
return outputStream.toByteArray();
}
/**
* Decompress T.6 encoded data. No EOLs, except for 2 consecutive ones at the end
* (the EOFB, end of fax block). No RTC. No fill bits anywhere. All data is 2D encoded.
* @param compressed
* @param width
* @param height
* @return the decompressed data
* @throws ImageReadException
*/
public static byte[] decompressT6(byte[] compressed, int width, int height) throws ImageReadException {
BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
new ByteArrayInputStream(compressed));
BitArrayOutputStream outputStream = new BitArrayOutputStream();
int[] referenceLine = new int[width];
for (int y = 0; y < height; y++) {
T4_T6_Tables.Entry entry;
int rowLength = 0;
try {
int codingA0Color = WHITE;
int referenceA0Color = WHITE;
int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
for (int a0 = 0; a0 < width; ) {
int a1, a2;
entry = (T4_T6_Tables.Entry) controlCodes.decode(inputStream);
if (entry == T4_T6_Tables.P) {
fillRange(outputStream, referenceLine, a0, b2, codingA0Color);
a0 = b2;
} else if (entry == T4_T6_Tables.H) {
int a0a1 = readTotalRunLength(inputStream, codingA0Color);
a1 = a0 + a0a1;
fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color);
a2 = a1 + a1a2;
fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color);
a0 = a2;
} else {
int a1b1;
if (entry == T4_T6_Tables.V0) {
a1b1 = 0;
} else if (entry == T4_T6_Tables.VL1) {
a1b1 = -1;
} else if (entry == T4_T6_Tables.VL2) {
a1b1 = -2;
} else if (entry == T4_T6_Tables.VL3) {
a1b1 = -3;
} else if (entry == T4_T6_Tables.VR1) {
a1b1 = 1;
} else if (entry == T4_T6_Tables.VR2) {
a1b1 = 2;
} else if (entry == T4_T6_Tables.VR3) {
a1b1 = 3;
} else {
throw new ImageReadException("Invalid/unknown T.6 control code " + entry.bitString);
}
a1 = b1 + a1b1;
fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
a0 = a1;
codingA0Color = 1 - codingA0Color;
}
referenceA0Color = changingElementAt(referenceLine, a0);
if (codingA0Color == referenceA0Color) {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
} else {
b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
}
b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
rowLength = a0;
}
} catch (HuffmanTreeException huffmanException) {
throw new ImageReadException("Decompression error", huffmanException);
}
if (rowLength == width) {
outputStream.flush();
} else if (rowLength > width) {
throw new ImageReadException("Unrecoverable row length error in image row " + y);
}
}
return outputStream.toByteArray();
}
private static boolean isEOL(T4_T6_Tables.Entry entry, boolean hasFill) {
if (entry == T4_T6_Tables.EOL) {
return true;
}
if (hasFill) {
return entry == T4_T6_Tables.EOL13 ||
entry == T4_T6_Tables.EOL14 ||
entry == T4_T6_Tables.EOL15 ||
entry == T4_T6_Tables.EOL16 ||
entry == T4_T6_Tables.EOL17 ||
entry == T4_T6_Tables.EOL18 ||
entry == T4_T6_Tables.EOL19;
} else {
return false;
}
}
private static void writeRunLength(BitArrayOutputStream bitStream, int runLength, int color) {
final T4_T6_Tables.Entry[] makeUpCodes;
final T4_T6_Tables.Entry[] terminatingCodes;
if (color == WHITE) {
makeUpCodes = T4_T6_Tables.whiteMakeUpCodes;
terminatingCodes = T4_T6_Tables.whiteTerminatingCodes;
} else {
makeUpCodes = T4_T6_Tables.blackMakeUpCodes;
terminatingCodes = T4_T6_Tables.blackTerminatingCodes;
}
while (runLength >= 1792) {
T4_T6_Tables.Entry entry = lowerBound(T4_T6_Tables.additionalMakeUpCodes, runLength);
entry.writeBits(bitStream);
runLength -= entry.value.intValue();
}
while (runLength >= 64) {
T4_T6_Tables.Entry entry = lowerBound(makeUpCodes, runLength);
entry.writeBits(bitStream);
runLength -= entry.value.intValue();
}
T4_T6_Tables.Entry terminatingEntry = terminatingCodes[runLength];
terminatingEntry.writeBits(bitStream);
}
private static T4_T6_Tables.Entry lowerBound(T4_T6_Tables.Entry[] entries, int value) {
int first = 0;
int last = entries.length - 1;
do {
int middle = (first + last) >>> 2;
if (entries[middle].value.intValue() <= value &&
((middle + 1) >= entries.length || value < entries[middle + 1].value.intValue())) {
return entries[middle];
} else if (entries[middle].value.intValue() > value) {
last = middle - 1;
} else {
first = middle + 1;
}
} while (first < last);
return entries[first];
}
private static int readTotalRunLength(BitInputStreamFlexible bitStream, int color) throws ImageReadException {
try {
int totalLength = 0;
Integer runLength;
do {
if (color == WHITE) {
runLength = (Integer)whiteRunLengths.decode(bitStream);
} else {
runLength = (Integer)blackRunLengths.decode(bitStream);
}
totalLength += runLength.intValue();
} while (runLength.intValue() > 63);
return totalLength;
} catch (HuffmanTreeException huffmanException) {
throw new ImageReadException("Decompression error", huffmanException);
}
}
private static int changingElementAt(int[] line, int position) {
if (position < 0 || position >= line.length) {
return WHITE;
}
return line[position];
}
private static int nextChangingElement(int[] line, int currentColour, int start) {
int position;
for (position = start; position < line.length && line[position] == currentColour; position++) {
// noop
}
return position < line.length ? position : line.length;
}
private static void fillRange(BitArrayOutputStream outputStream, int[] referenceRow,
int a0, int end, int color) {
for (int i = a0; i < end; i++) {
referenceRow[i] = color;
outputStream.writeBit(color);
}
}
}