001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.common.itu_t4; 018 019import java.io.ByteArrayInputStream; 020import java.io.IOException; 021 022import org.apache.commons.imaging.ImageReadException; 023import org.apache.commons.imaging.ImageWriteException; 024import org.apache.commons.imaging.common.itu_t4.T4_T6_Tables.Entry; 025 026public final class T4AndT6Compression { 027 private static final HuffmanTree<Integer> WHITE_RUN_LENGTHS = new HuffmanTree<>(); 028 private static final HuffmanTree<Integer> BLACK_RUN_LENGTHS = new HuffmanTree<>(); 029 private static final HuffmanTree<Entry> CONTROL_CODES = new HuffmanTree<>(); 030 031 public static final int WHITE = 0; 032 public static final int BLACK = 1; 033 034 static { 035 try { 036 for (final Entry entry : T4_T6_Tables.WHITE_TERMINATING_CODES) { 037 WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value); 038 } 039 for (final Entry entry : T4_T6_Tables.WHITE_MAKE_UP_CODES) { 040 WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value); 041 } 042 for (final Entry entry : T4_T6_Tables.BLACK_TERMINATING_CODES) { 043 BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value); 044 } 045 for (final Entry entry : T4_T6_Tables.BLACK_MAKE_UP_CODES) { 046 BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value); 047 } 048 for (final Entry entry : T4_T6_Tables.ADDITIONAL_MAKE_UP_CODES) { 049 WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value); 050 BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value); 051 } 052 CONTROL_CODES.insert(T4_T6_Tables.EOL.bitString, T4_T6_Tables.EOL); 053 CONTROL_CODES.insert(T4_T6_Tables.EOL13.bitString, T4_T6_Tables.EOL13); 054 CONTROL_CODES.insert(T4_T6_Tables.EOL14.bitString, T4_T6_Tables.EOL14); 055 CONTROL_CODES.insert(T4_T6_Tables.EOL15.bitString, T4_T6_Tables.EOL15); 056 CONTROL_CODES.insert(T4_T6_Tables.EOL16.bitString, T4_T6_Tables.EOL16); 057 CONTROL_CODES.insert(T4_T6_Tables.EOL17.bitString, T4_T6_Tables.EOL17); 058 CONTROL_CODES.insert(T4_T6_Tables.EOL18.bitString, T4_T6_Tables.EOL18); 059 CONTROL_CODES.insert(T4_T6_Tables.EOL19.bitString, T4_T6_Tables.EOL19); 060 CONTROL_CODES.insert(T4_T6_Tables.P.bitString, T4_T6_Tables.P); 061 CONTROL_CODES.insert(T4_T6_Tables.H.bitString, T4_T6_Tables.H); 062 CONTROL_CODES.insert(T4_T6_Tables.V0.bitString, T4_T6_Tables.V0); 063 CONTROL_CODES.insert(T4_T6_Tables.VL1.bitString, T4_T6_Tables.VL1); 064 CONTROL_CODES.insert(T4_T6_Tables.VL2.bitString, T4_T6_Tables.VL2); 065 CONTROL_CODES.insert(T4_T6_Tables.VL3.bitString, T4_T6_Tables.VL3); 066 CONTROL_CODES.insert(T4_T6_Tables.VR1.bitString, T4_T6_Tables.VR1); 067 CONTROL_CODES.insert(T4_T6_Tables.VR2.bitString, T4_T6_Tables.VR2); 068 CONTROL_CODES.insert(T4_T6_Tables.VR3.bitString, T4_T6_Tables.VR3); 069 } catch (final HuffmanTreeException cannotHappen) { 070 throw new Error(cannotHappen); 071 } 072 } 073 074 private T4AndT6Compression() { 075 } 076 077 private static void compress1DLine(final BitInputStreamFlexible inputStream, 078 final BitArrayOutputStream outputStream, final int[] referenceLine, final int width) 079 throws ImageWriteException { 080 int color = WHITE; 081 int runLength = 0; 082 083 for (int x = 0; x < width; x++) { 084 try { 085 final int nextColor = inputStream.readBits(1); 086 if (referenceLine != null) { 087 referenceLine[x] = nextColor; 088 } 089 if (color == nextColor) { 090 ++runLength; 091 } else { 092 writeRunLength(outputStream, runLength, color); 093 color = nextColor; 094 runLength = 1; 095 } 096 } catch (final IOException ioException) { 097 throw new ImageWriteException("Error reading image to compress", ioException); 098 } 099 } 100 101 writeRunLength(outputStream, runLength, color); 102 } 103 104 /** 105 * Compressed with the "Modified Huffman" encoding of section 10 in the 106 * TIFF6 specification. No EOLs, no RTC, rows are padded to end on a byte 107 * boundary. 108 * 109 * @param uncompressed 110 * @param width 111 * @param height 112 * @return the compressed data 113 * @throws ImageWriteException 114 */ 115 public static byte[] compressModifiedHuffman(final byte[] uncompressed, final int width, final int height) 116 throws ImageWriteException { 117 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed)); 118 try (final BitArrayOutputStream outputStream = new BitArrayOutputStream()) { 119 for (int y = 0; y < height; y++) { 120 compress1DLine(inputStream, outputStream, null, width); 121 inputStream.flushCache(); 122 outputStream.flush(); 123 } 124 return outputStream.toByteArray(); 125 } 126 } 127 128 /** 129 * Decompresses the "Modified Huffman" encoding of section 10 in the TIFF6 130 * specification. No EOLs, no RTC, rows are padded to end on a byte 131 * boundary. 132 * 133 * @param compressed 134 * @param width 135 * @param height 136 * @return the decompressed data 137 * @throws ImageReadException 138 */ 139 public static byte[] decompressModifiedHuffman(final byte[] compressed, 140 final int width, final int height) throws ImageReadException { 141 try (ByteArrayInputStream baos = new ByteArrayInputStream(compressed); 142 BitInputStreamFlexible inputStream = new BitInputStreamFlexible(baos); 143 BitArrayOutputStream outputStream = new BitArrayOutputStream()) { 144 for (int y = 0; y < height; y++) { 145 int color = WHITE; 146 int rowLength; 147 for (rowLength = 0; rowLength < width;) { 148 final int runLength = readTotalRunLength(inputStream, color); 149 for (int i = 0; i < runLength; i++) { 150 outputStream.writeBit(color); 151 } 152 color = 1 - color; 153 rowLength += runLength; 154 } 155 156 if (rowLength == width) { 157 inputStream.flushCache(); 158 outputStream.flush(); 159 } else if (rowLength > width) { 160 throw new ImageReadException("Unrecoverable row length error in image row " + y); 161 } 162 } 163 final byte[] ret = outputStream.toByteArray(); 164 return ret; 165 } catch (final IOException ioException) { 166 throw new ImageReadException("Error reading image to decompress", ioException); 167 } 168 } 169 170 public static byte[] compressT4_1D(final byte[] uncompressed, final int width, 171 final int height, final boolean hasFill) throws ImageWriteException { 172 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed)); 173 try (final BitArrayOutputStream outputStream = new BitArrayOutputStream()) { 174 if (hasFill) { 175 T4_T6_Tables.EOL16.writeBits(outputStream); 176 } else { 177 T4_T6_Tables.EOL.writeBits(outputStream); 178 } 179 180 for (int y = 0; y < height; y++) { 181 compress1DLine(inputStream, outputStream, null, width); 182 if (hasFill) { 183 int bitsAvailable = outputStream.getBitsAvailableInCurrentByte(); 184 if (bitsAvailable < 4) { 185 outputStream.flush(); 186 bitsAvailable = 8; 187 } 188 for (; bitsAvailable > 4; bitsAvailable--) { 189 outputStream.writeBit(0); 190 } 191 } 192 T4_T6_Tables.EOL.writeBits(outputStream); 193 inputStream.flushCache(); 194 } 195 196 return outputStream.toByteArray(); 197 } 198 } 199 200 /** 201 * Decompresses T.4 1D encoded data. EOL at the beginning and after each 202 * row, can be preceded by fill bits to fit on a byte boundary, no RTC. 203 * 204 * @param compressed 205 * @param width 206 * @param height 207 * @return the decompressed data 208 * @throws ImageReadException 209 */ 210 public static byte[] decompressT4_1D(final byte[] compressed, final int width, 211 final int height, final boolean hasFill) throws ImageReadException { 212 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed)); 213 try (BitArrayOutputStream outputStream = new BitArrayOutputStream()) { 214 for (int y = 0; y < height; y++) { 215 int rowLength; 216 try { 217 final T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream); 218 if (!isEOL(entry, hasFill)) { 219 throw new ImageReadException("Expected EOL not found"); 220 } 221 int color = WHITE; 222 for (rowLength = 0; rowLength < width;) { 223 final int runLength = readTotalRunLength(inputStream, color); 224 for (int i = 0; i < runLength; i++) { 225 outputStream.writeBit(color); 226 } 227 color = 1 - color; 228 rowLength += runLength; 229 } 230 } catch (final HuffmanTreeException huffmanException) { 231 throw new ImageReadException("Decompression error", huffmanException); 232 } 233 234 if (rowLength == width) { 235 outputStream.flush(); 236 } else if (rowLength > width) { 237 throw new ImageReadException("Unrecoverable row length error in image row " + y); 238 } 239 } 240 final byte[] ret = outputStream.toByteArray(); 241 return ret; 242 } 243 } 244 245 private static int compressT(final int a0, final int a1, final int b1, final BitArrayOutputStream outputStream,final int codingA0Color, final int[] codingLine ){ 246 final int a1b1 = a1 - b1; 247 if (-3 <= a1b1 && a1b1 <= 3) { 248 T4_T6_Tables.Entry entry; 249 if (a1b1 == -3) { 250 entry = T4_T6_Tables.VL3; 251 } else if (a1b1 == -2) { 252 entry = T4_T6_Tables.VL2; 253 } else if (a1b1 == -1) { 254 entry = T4_T6_Tables.VL1; 255 } else if (a1b1 == 0) { 256 entry = T4_T6_Tables.V0; 257 } else if (a1b1 == 1) { 258 entry = T4_T6_Tables.VR1; 259 } else if (a1b1 == 2) { 260 entry = T4_T6_Tables.VR2; 261 } else { 262 entry = T4_T6_Tables.VR3; 263 } 264 entry.writeBits(outputStream); 265 return a1; 266 267 } else { 268 final int a2 = nextChangingElement(codingLine, 1 - codingA0Color, a1 + 1); 269 final int a0a1 = a1 - a0; 270 final int a1a2 = a2 - a1; 271 T4_T6_Tables.H.writeBits(outputStream); 272 writeRunLength(outputStream, a0a1, codingA0Color); 273 writeRunLength(outputStream, a1a2, 1 - codingA0Color); 274 return a2; 275 } 276 } 277 public static byte[] compressT4_2D(final byte[] uncompressed, final int width, 278 final int height, final boolean hasFill, final int parameterK) 279 throws ImageWriteException { 280 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed)); 281 final BitArrayOutputStream outputStream = new BitArrayOutputStream(); 282 int[] referenceLine = new int[width]; 283 int[] codingLine = new int[width]; 284 int kCounter = 0; 285 if (hasFill) { 286 T4_T6_Tables.EOL16.writeBits(outputStream); 287 } else { 288 T4_T6_Tables.EOL.writeBits(outputStream); 289 } 290 291 for (int y = 0; y < height; y++) { 292 if (kCounter > 0) { 293 // 2D 294 outputStream.writeBit(0); 295 for (int i = 0; i < width; i++) { 296 try { 297 codingLine[i] = inputStream.readBits(1); 298 } catch (final IOException ioException) { 299 throw new ImageWriteException("Error reading image to compress", ioException); 300 } 301 } 302 int codingA0Color = WHITE; 303 int referenceA0Color = WHITE; 304 int a1 = nextChangingElement(codingLine, codingA0Color, 0); 305 int b1 = nextChangingElement(referenceLine, referenceA0Color, 0); 306 int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 307 for (int a0 = 0; a0 < width;) { 308 if (b2 < a1) { 309 T4_T6_Tables.P.writeBits(outputStream); 310 a0 = b2; 311 } else { 312 a0 = compressT(a0, a1, b1, outputStream, codingA0Color, codingLine); 313 if (a0 == a1) { 314 codingA0Color = 1 - codingA0Color; 315 } 316 } 317 referenceA0Color = changingElementAt(referenceLine, a0); 318 a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1); 319 if (codingA0Color == referenceA0Color) { 320 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 321 } else { 322 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 323 b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 324 } 325 b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1); 326 } 327 final int[] swap = referenceLine; 328 referenceLine = codingLine; 329 codingLine = swap; 330 } else { 331 // 1D 332 outputStream.writeBit(1); 333 compress1DLine(inputStream, outputStream, referenceLine, width); 334 } 335 if (hasFill) { 336 int bitsAvailable = outputStream.getBitsAvailableInCurrentByte(); 337 if (bitsAvailable < 4) { 338 outputStream.flush(); 339 bitsAvailable = 8; 340 } 341 for (; bitsAvailable > 4; bitsAvailable--) { 342 outputStream.writeBit(0); 343 } 344 } 345 T4_T6_Tables.EOL.writeBits(outputStream); 346 kCounter++; 347 if (kCounter == parameterK) { 348 kCounter = 0; 349 } 350 inputStream.flushCache(); 351 } 352 353 return outputStream.toByteArray(); 354 } 355 356 /** 357 * Decompressed T.4 2D encoded data. EOL at the beginning and after each 358 * row, can be preceded by fill bits to fit on a byte boundary, and is 359 * succeeded by a tag bit determining whether the next line is encoded using 360 * 1D or 2D. No RTC. 361 * 362 * @param compressed 363 * @param width 364 * @param height 365 * @return the decompressed data 366 * @throws ImageReadException 367 */ 368 public static byte[] decompressT4_2D(final byte[] compressed, final int width, 369 final int height, final boolean hasFill) throws ImageReadException { 370 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed)); 371 final BitArrayOutputStream outputStream = new BitArrayOutputStream(); 372 final int[] referenceLine = new int[width]; 373 for (int y = 0; y < height; y++) { 374 int rowLength = 0; 375 try { 376 T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream); 377 if (!isEOL(entry, hasFill)) { 378 throw new ImageReadException("Expected EOL not found"); 379 } 380 final int tagBit = inputStream.readBits(1); 381 if (tagBit == 0) { 382 // 2D 383 int codingA0Color = WHITE; 384 int referenceA0Color = WHITE; 385 int b1 = nextChangingElement(referenceLine, referenceA0Color, 0); 386 int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 387 for (int a0 = 0; a0 < width;) { 388 int a1; 389 int a2; 390 entry = CONTROL_CODES.decode(inputStream); 391 if (entry == T4_T6_Tables.P) { 392 fillRange(outputStream, referenceLine, a0, b2, codingA0Color); 393 a0 = b2; 394 } else if (entry == T4_T6_Tables.H) { 395 final int a0a1 = readTotalRunLength(inputStream, codingA0Color); 396 a1 = a0 + a0a1; 397 fillRange(outputStream, referenceLine, a0, a1, codingA0Color); 398 final int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color); 399 a2 = a1 + a1a2; 400 fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color); 401 a0 = a2; 402 } else { 403 int a1b1; 404 if (entry == T4_T6_Tables.V0) { 405 a1b1 = 0; 406 } else if (entry == T4_T6_Tables.VL1) { 407 a1b1 = -1; 408 } else if (entry == T4_T6_Tables.VL2) { 409 a1b1 = -2; 410 } else if (entry == T4_T6_Tables.VL3) { 411 a1b1 = -3; 412 } else if (entry == T4_T6_Tables.VR1) { 413 a1b1 = 1; 414 } else if (entry == T4_T6_Tables.VR2) { 415 a1b1 = 2; 416 } else if (entry == T4_T6_Tables.VR3) { 417 a1b1 = 3; 418 } else { 419 throw new ImageReadException("Invalid/unknown T.4 control code " + entry.bitString); 420 } 421 a1 = b1 + a1b1; 422 fillRange(outputStream, referenceLine, a0, a1, codingA0Color); 423 a0 = a1; 424 codingA0Color = 1 - codingA0Color; 425 } 426 referenceA0Color = changingElementAt(referenceLine, a0); 427 if (codingA0Color == referenceA0Color) { 428 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 429 } else { 430 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 431 b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 432 } 433 b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1); 434 rowLength = a0; 435 } 436 } else { 437 // 1D 438 int color = WHITE; 439 for (rowLength = 0; rowLength < width;) { 440 final int runLength = readTotalRunLength(inputStream, color); 441 for (int i = 0; i < runLength; i++) { 442 outputStream.writeBit(color); 443 referenceLine[rowLength + i] = color; 444 } 445 color = 1 - color; 446 rowLength += runLength; 447 } 448 } 449 } catch (final IOException ioException) { 450 throw new ImageReadException("Decompression error", ioException); 451 } catch (final HuffmanTreeException huffmanException) { 452 throw new ImageReadException("Decompression error", huffmanException); 453 } 454 455 if (rowLength == width) { 456 outputStream.flush(); 457 } else if (rowLength > width) { 458 throw new ImageReadException("Unrecoverable row length error in image row " + y); 459 } 460 } 461 462 return outputStream.toByteArray(); 463 } 464 465 public static byte[] compressT6(final byte[] uncompressed, final int width, final int height) 466 throws ImageWriteException { 467 try (BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed))) { 468 final BitArrayOutputStream outputStream = new BitArrayOutputStream(); 469 int[] referenceLine = new int[width]; 470 int[] codingLine = new int[width]; 471 for (int y = 0; y < height; y++) { 472 for (int i = 0; i < width; i++) { 473 try { 474 codingLine[i] = inputStream.readBits(1); 475 } catch (final IOException ioException) { 476 throw new ImageWriteException("Error reading image to compress", ioException); 477 } 478 } 479 int codingA0Color = WHITE; 480 int referenceA0Color = WHITE; 481 int a1 = nextChangingElement(codingLine, codingA0Color, 0); 482 int b1 = nextChangingElement(referenceLine, referenceA0Color, 0); 483 int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 484 for (int a0 = 0; a0 < width;) { 485 if (b2 < a1) { 486 T4_T6_Tables.P.writeBits(outputStream); 487 a0 = b2; 488 } else { 489 a0 = compressT(a0, a1, b1, outputStream, codingA0Color, codingLine); 490 if (a0 == a1) { 491 codingA0Color = 1 - codingA0Color; 492 } 493 } 494 referenceA0Color = changingElementAt(referenceLine, a0); 495 a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1); 496 if (codingA0Color == referenceA0Color) { 497 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 498 } else { 499 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 500 b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 501 } 502 b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1); 503 } 504 final int[] swap = referenceLine; 505 referenceLine = codingLine; 506 codingLine = swap; 507 inputStream.flushCache(); 508 } 509 // EOFB 510 T4_T6_Tables.EOL.writeBits(outputStream); 511 T4_T6_Tables.EOL.writeBits(outputStream); 512 final byte[] ret = outputStream.toByteArray(); 513 return ret; 514 } catch (final IOException ioException) { 515 throw new ImageWriteException("I/O error", ioException); 516 } 517 } 518 519 /** 520 * Decompress T.6 encoded data. No EOLs, except for 2 consecutive ones at 521 * the end (the EOFB, end of fax block). No RTC. No fill bits anywhere. All 522 * data is 2D encoded. 523 * 524 * @param compressed 525 * @param width 526 * @param height 527 * @return the decompressed data 528 * @throws ImageReadException 529 */ 530 public static byte[] decompressT6(final byte[] compressed, final int width, final int height) 531 throws ImageReadException { 532 final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed)); 533 final BitArrayOutputStream outputStream = new BitArrayOutputStream(); 534 final int[] referenceLine = new int[width]; 535 for (int y = 0; y < height; y++) { 536 int rowLength = 0; 537 try { 538 int codingA0Color = WHITE; 539 int referenceA0Color = WHITE; 540 int b1 = nextChangingElement(referenceLine, referenceA0Color, 0); 541 int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 542 for (int a0 = 0; a0 < width;) { 543 int a1; 544 int a2; 545 final T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream); 546 if (entry == T4_T6_Tables.P) { 547 fillRange(outputStream, referenceLine, a0, b2, codingA0Color); 548 a0 = b2; 549 } else if (entry == T4_T6_Tables.H) { 550 final int a0a1 = readTotalRunLength(inputStream, codingA0Color); 551 a1 = a0 + a0a1; 552 fillRange(outputStream, referenceLine, a0, a1, codingA0Color); 553 final int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color); 554 a2 = a1 + a1a2; 555 fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color); 556 a0 = a2; 557 } else { 558 int a1b1; 559 if (entry == T4_T6_Tables.V0) { 560 a1b1 = 0; 561 } else if (entry == T4_T6_Tables.VL1) { 562 a1b1 = -1; 563 } else if (entry == T4_T6_Tables.VL2) { 564 a1b1 = -2; 565 } else if (entry == T4_T6_Tables.VL3) { 566 a1b1 = -3; 567 } else if (entry == T4_T6_Tables.VR1) { 568 a1b1 = 1; 569 } else if (entry == T4_T6_Tables.VR2) { 570 a1b1 = 2; 571 } else if (entry == T4_T6_Tables.VR3) { 572 a1b1 = 3; 573 } else { 574 throw new ImageReadException("Invalid/unknown T.6 control code " + entry.bitString); 575 } 576 a1 = b1 + a1b1; 577 fillRange(outputStream, referenceLine, a0, a1, codingA0Color); 578 a0 = a1; 579 codingA0Color = 1 - codingA0Color; 580 } 581 referenceA0Color = changingElementAt(referenceLine, a0); 582 if (codingA0Color == referenceA0Color) { 583 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 584 } else { 585 b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1); 586 b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1); 587 } 588 b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1); 589 rowLength = a0; 590 } 591 } catch (final HuffmanTreeException huffmanException) { 592 throw new ImageReadException("Decompression error", huffmanException); 593 } 594 595 if (rowLength == width) { 596 outputStream.flush(); 597 } else if (rowLength > width) { 598 throw new ImageReadException("Unrecoverable row length error in image row " + y); 599 } 600 } 601 602 return outputStream.toByteArray(); 603 } 604 605 private static boolean isEOL(final T4_T6_Tables.Entry entry, final boolean hasFill) { 606 if (entry == T4_T6_Tables.EOL) { 607 return true; 608 } 609 if (hasFill) { 610 return entry == T4_T6_Tables.EOL13 || entry == T4_T6_Tables.EOL14 611 || entry == T4_T6_Tables.EOL15 612 || entry == T4_T6_Tables.EOL16 613 || entry == T4_T6_Tables.EOL17 614 || entry == T4_T6_Tables.EOL18 615 || entry == T4_T6_Tables.EOL19; 616 } 617 return false; 618 } 619 620 private static void writeRunLength(final BitArrayOutputStream bitStream, 621 int runLength, final int color) { 622 final T4_T6_Tables.Entry[] makeUpCodes; 623 final T4_T6_Tables.Entry[] terminatingCodes; 624 if (color == WHITE) { 625 makeUpCodes = T4_T6_Tables.WHITE_MAKE_UP_CODES; 626 terminatingCodes = T4_T6_Tables.WHITE_TERMINATING_CODES; 627 } else { 628 makeUpCodes = T4_T6_Tables.BLACK_MAKE_UP_CODES; 629 terminatingCodes = T4_T6_Tables.BLACK_TERMINATING_CODES; 630 } 631 while (runLength >= 1792) { 632 final T4_T6_Tables.Entry entry = lowerBound( 633 T4_T6_Tables.ADDITIONAL_MAKE_UP_CODES, runLength); 634 entry.writeBits(bitStream); 635 runLength -= entry.value; 636 } 637 while (runLength >= 64) { 638 final T4_T6_Tables.Entry entry = lowerBound(makeUpCodes, runLength); 639 entry.writeBits(bitStream); 640 runLength -= entry.value; 641 } 642 final T4_T6_Tables.Entry terminatingEntry = terminatingCodes[runLength]; 643 terminatingEntry.writeBits(bitStream); 644 } 645 646 private static T4_T6_Tables.Entry lowerBound(final T4_T6_Tables.Entry[] entries, final int value) { 647 int first = 0; 648 int last = entries.length - 1; 649 do { 650 final int middle = (first + last) >>> 1; 651 if (entries[middle].value <= value 652 && ((middle + 1) >= entries.length || value < entries[middle + 1].value)) { 653 return entries[middle]; 654 } else if (entries[middle].value > value) { 655 last = middle - 1; 656 } else { 657 first = middle + 1; 658 } 659 } while (first < last); 660 661 return entries[first]; 662 } 663 664 private static int readTotalRunLength(final BitInputStreamFlexible bitStream, 665 final int color) throws ImageReadException { 666 try { 667 int totalLength = 0; 668 Integer runLength; 669 do { 670 if (color == WHITE) { 671 runLength = WHITE_RUN_LENGTHS.decode(bitStream); 672 } else { 673 runLength = BLACK_RUN_LENGTHS.decode(bitStream); 674 } 675 totalLength += runLength; 676 } while (runLength > 63); 677 return totalLength; 678 } catch (final HuffmanTreeException huffmanException) { 679 throw new ImageReadException("Decompression error", huffmanException); 680 } 681 } 682 683 private static int changingElementAt(final int[] line, final int position) { 684 if (position < 0 || position >= line.length) { 685 return WHITE; 686 } 687 return line[position]; 688 } 689 690 private static int nextChangingElement(final int[] line, final int currentColour, final int start) { 691 int position; 692 for (position = start; position < line.length 693 && line[position] == currentColour; position++) { 694 // noop 695 } 696 697 return position < line.length ? position : line.length; 698 } 699 700 private static void fillRange(final BitArrayOutputStream outputStream, 701 final int[] referenceRow, final int a0, final int end, final int color) { 702 for (int i = a0; i < end; i++) { 703 referenceRow[i] = color; 704 outputStream.writeBit(color); 705 } 706 } 707}