3 package com.pixelmed.codec.jpeg;
5 import java.awt.Rectangle;
8 import java.io.ByteArrayOutputStream;
9 import java.io.IOException;
10 import java.io.OutputStream;
12 import java.util.HashMap;
14 import java.util.Vector;
23 public class EntropyCodedSegment {
25 private static final String identString =
"@(#) $Header: /userland/cvs/codec/com/pixelmed/codec/jpeg/EntropyCodedSegment.java,v 1.25 2020/03/28 21:05:39 dclunie Exp $";
27 private boolean copying;
28 private boolean decompressing;
30 private OutputArrayOrStream[] decompressedOutputPerComponent;
32 private boolean isHuffman;
33 private boolean isDCT;
34 private boolean isLossless;
36 private ByteArrayOutputStream copiedBytes;
38 private final MarkerSegmentSOS sos;
39 private final MarkerSegmentSOF sof;
40 private final Map<String,HuffmanTable> htByClassAndIdentifer;
41 private final Map<String,QuantizationTable> qtByIdentifer;
43 private final int nComponents;
44 private final int[] DCEntropyCodingTableSelector;
45 private final int[] ACEntropyCodingTableSelector;
46 private final int[] HorizontalSamplingFactor;
47 private final int[] VerticalSamplingFactor;
49 private final int maxHorizontalSamplingFactor;
50 private final int maxVerticalSamplingFactor;
52 private final int nMCUHorizontally;
54 private final Vector<Shape> redactionShapes;
57 private final int predictorForFirstSample;
58 private final int[] predictorForComponent;
59 private final int predictorSelectionValue;
62 private int[] rowNumberAtBeginningOfRestartInterval;
63 private final int[] rowLength;
64 private final int[] currentRowNumber;
65 private final int[] positionWithinRow;
66 private final int[][] previousReconstructedRow;
67 private final int[][] currentReconstructedRow;
71 private byte[] bytesToDecompress;
72 private int availableBytes;
73 private int byteIndex;
75 private int currentByte;
76 private int currentBits;
79 private static final int[] extractBitFromByteMask = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 };
81 private final void getEnoughBits(
int wantBits)
throws Exception {
82 while (haveBits < wantBits) {
84 if (byteIndex < availableBytes) {
85 currentByte=bytesToDecompress[byteIndex++];
90 throw new Exception(
"No more bits (having decompressed "+byteIndex+
" dec bytes)");
93 int newBit = (currentByte & extractBitFromByteMask[bitIndex++]) == 0 ? 0 : 1;
94 currentBits = (currentBits << 1) + newBit;
100 private int writeByte;
101 private int writeBitIndex;
103 private final void initializeWriteBits() {
104 copiedBytes =
new ByteArrayOutputStream();
109 private final void flushWriteBits() {
110 if (writeBitIndex > 0) {
112 while (writeBitIndex < 8) {
113 writeByte = writeByte | extractBitFromByteMask[writeBitIndex];
116 copiedBytes.write(writeByte);
117 if ((writeByte&0xff) == 0xff) {
118 copiedBytes.write(0);
126 private final void writeBits(
int bits,
int nBits) {
129 for (
int i=nBits-1; i>=0; --i) {
130 final int whichBitMask = 1 << i;
131 final int bitIsSet = bits & whichBitMask;
134 writeByte = writeByte | extractBitFromByteMask[writeBitIndex];
137 if (writeBitIndex > 7) {
139 copiedBytes.write(writeByte);
140 if ((writeByte&0xff) == 0xff) {
141 copiedBytes.write(0);
152 private HuffmanTable usingTable =
null;
163 private final int decode() throws Exception {
164 final int[] MINCODE = usingTable.getMINCODE();
165 final int[] MAXCODE = usingTable.getMAXCODE();
166 final int[] VALPTR = usingTable.getVALPTR();
167 final int[] HUFFVAL = usingTable.getHUFFVAL();
171 int CODE = currentBits;
172 while (I<MAXCODE.length && CODE > MAXCODE[I]) {
183 if (I<MAXCODE.length) {
186 J = J + CODE - MINCODE[I];
198 if (copying) { writeBits(currentBits,haveBits); }
204 private final void encode(
int VALUE) {
206 final int[] EFUFCO = usingTable.getEFUFCO();
207 final int[] EFUFSI = usingTable.getEFUFSI();
208 int CODE = EFUFCO[VALUE];
209 int size = EFUFSI[VALUE];
211 writeBits(CODE,size);
214 private final int getValueOfRequestedLength(
int wantBits)
throws Exception {
215 getEnoughBits(wantBits);
216 final int value = currentBits;
218 if (copying) { writeBits(currentBits,haveBits); }
225 private int[] dcSignBitMask = { 0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x100,0x200,0x400,0x800,0x1000,0x2000,0x4000 };
226 private int[] maxAmplitude = { 0,0x02-1,0x04-1,0x08-1,0x10-1,0x20-1,0x40-1,0x80-1,0x100-1,0x200-1,0x400-1,0x800-1,0x1000-1,0x2000-1,0x4000-1,0x8000-1 };
228 private final int convertSignAndAmplitudeBitsToValue(
int value,
int length)
throws Exception {
232 if ((value & dcSignBitMask[length]) == 0) {
234 value = value - maxAmplitude[length];
240 private final int getNumberOfSignBits(
int value) {
252 private final int getBits(
int value,
int ssss) {
259 bits = value & maxAmplitude[ssss];
265 private final void writeEntropyCodedAllZeroACCoefficients() {
267 writeBits(usingTable.getEOBCode(),usingTable.getEOBCodeLength());
286 public EntropyCodedSegment(
MarkerSegmentSOS sos,
MarkerSegmentSOF sof,Map<String,HuffmanTable> htByClassAndIdentifer,Map<String,QuantizationTable> qtByIdentifer,
int nMCUHorizontally,Vector<Shape> redactionShapes,
boolean copying,
boolean dumping,
boolean decompressing,
Parse.
DecompressedOutput decompressedOutput)
throws Exception {
289 this.htByClassAndIdentifer = htByClassAndIdentifer;
290 this.qtByIdentifer = qtByIdentifer;
291 this.nMCUHorizontally = nMCUHorizontally;
292 this.redactionShapes = redactionShapes;
293 this.copying = copying;
295 this.decompressing = decompressing;
296 this.decompressedOutputPerComponent = decompressedOutput ==
null ? null : decompressedOutput.getDecompressedOutputPerComponent();
305 nComponents = sos.getNComponentsPerScan();
306 DCEntropyCodingTableSelector = sos.getDCEntropyCodingTableSelector();
307 ACEntropyCodingTableSelector = sos.getACEntropyCodingTableSelector();
308 HorizontalSamplingFactor = sof.getHorizontalSamplingFactor();
309 VerticalSamplingFactor = sof.getVerticalSamplingFactor();
311 maxHorizontalSamplingFactor = max(HorizontalSamplingFactor);
313 maxVerticalSamplingFactor = max(VerticalSamplingFactor);
316 if (isLossless && decompressing) {
319 predictorForFirstSample = 1 << (sof.getSamplePrecision() - sos.getSuccessiveApproximationBitPositionLowOrPointTransform() - 1);
321 predictorForComponent =
new int[nComponents];
322 predictorSelectionValue = sos.getStartOfSpectralOrPredictorSelection();
325 rowLength =
new int[nComponents];
326 currentRowNumber =
new int[nComponents];
327 positionWithinRow =
new int[nComponents];
328 rowNumberAtBeginningOfRestartInterval =
new int[nComponents];
329 previousReconstructedRow =
new int[nComponents][];
330 currentReconstructedRow =
new int[nComponents][];
331 for (
int c=0; c<nComponents; ++c) {
333 rowLength[c] = (sof.getNSamplesPerLine()-1)/sof.getHorizontalSamplingFactor()[c]+1;
335 currentRowNumber[c] = 0;
336 positionWithinRow[c] = 0;
337 rowNumberAtBeginningOfRestartInterval[c] = 0;
338 previousReconstructedRow[c] =
new int[rowLength[c]];
339 currentReconstructedRow[c] =
new int[rowLength[c]];
343 predictorForFirstSample = 0;
344 predictorForComponent =
null;
345 predictorSelectionValue = 0;
347 currentRowNumber =
null;
348 positionWithinRow =
null;
349 rowNumberAtBeginningOfRestartInterval =
null;
350 previousReconstructedRow =
null;
351 currentReconstructedRow =
null;
354 if (dumping) dumpHuffmanTables();
358 private final int getOneLosslessValue(
int c,
int dcEntropyCodingTableSelector,
int colMCU,
int rowMCU)
throws Exception {
362 if (currentRowNumber[c] == rowNumberAtBeginningOfRestartInterval[c]) {
363 if (positionWithinRow[c] == 0) {
365 prediction = predictorForFirstSample;
369 prediction = currentReconstructedRow[c][positionWithinRow[c]-1];
372 else if (positionWithinRow[c] == 0) {
374 prediction = previousReconstructedRow[c][0];
377 switch(predictorSelectionValue) {
378 case 1: prediction = currentReconstructedRow[c][positionWithinRow[c]-1];
380 case 2: prediction = previousReconstructedRow[c][positionWithinRow[c]];
382 case 3: prediction = previousReconstructedRow[c][positionWithinRow[c]-1];
384 case 4: prediction = currentReconstructedRow[c][positionWithinRow[c]-1] + previousReconstructedRow[c][positionWithinRow[c]] - previousReconstructedRow[c][positionWithinRow[c]-1];
386 case 5: prediction = currentReconstructedRow[c][positionWithinRow[c]-1] + ((previousReconstructedRow[c][positionWithinRow[c]] - previousReconstructedRow[c][positionWithinRow[c]-1])>>1);
388 case 6: prediction = previousReconstructedRow[c][positionWithinRow[c]] + ((currentReconstructedRow[c][positionWithinRow[c]-1] - previousReconstructedRow[c][positionWithinRow[c]-1])>>1);
390 case 7: prediction = (currentReconstructedRow[c][positionWithinRow[c]-1] + previousReconstructedRow[c][positionWithinRow[c]])>>1;
393 throw new Exception(
"Unrecognized predictor selection value "+predictorSelectionValue);
399 usingTable = htByClassAndIdentifer.get(
"0+"+Integer.toString(dcEntropyCodingTableSelector));
401 final int ssss = decode();
407 else if (ssss == 16) {
411 final int dcBits = getValueOfRequestedLength(ssss);
412 dcValue = convertSignAndAmplitudeBitsToValue(dcBits,ssss);
416 int reconstructedValue = 0;
419 reconstructedValue = (dcValue + prediction) & 0x0000ffff;
423 currentReconstructedRow[c][positionWithinRow[c]] = reconstructedValue;
425 ++positionWithinRow[c];
426 if (positionWithinRow[c] >= rowLength[c]) {
428 positionWithinRow[c] = 0;
429 ++currentRowNumber[c];
430 int[] holdRow = previousReconstructedRow[c];
431 previousReconstructedRow[c] = currentReconstructedRow[c];
432 currentReconstructedRow[c] = holdRow;
436 return reconstructedValue;
441 private void getOneDCTDataUnit(
int dcEntropyCodingTableSelector,
int acEntropyCodingTableSelector,
boolean redact,
boolean firstRedaction,
boolean wasRedacting,
int c,
int[] originalDCValue)
throws Exception {
442 usingTable = htByClassAndIdentifer.get(
"0+"+Integer.toString(dcEntropyCodingTableSelector));
444 final boolean wasCopying = copying;
447 final int ssss = decode();
454 else if (ssss == 16) {
458 dcBits = getValueOfRequestedLength(ssss);
459 dcDIFF = convertSignAndAmplitudeBitsToValue(dcBits,ssss);
466 if (firstRedaction) {
468 newDCDIFF = - originalDCValue[c];
469 originalDCValue[c] += dcDIFF;
474 originalDCValue[c] += dcDIFF;
480 originalDCValue[c] += dcDIFF;
481 newDCDIFF = originalDCValue[c];
485 originalDCValue[c] += dcDIFF;
493 final int newSSSS = getNumberOfSignBits(newDCDIFF);
494 final int newDCBits = getBits(newDCDIFF,newSSSS);
499 if (newSSSS != ssss || newDCBits != dcBits) {
504 if (newSSSS > 0 && newSSSS < 16) {
505 writeBits(newDCBits,newSSSS);
510 copying = wasCopying;
513 usingTable = htByClassAndIdentifer.get(
"1+"+Integer.toString(acEntropyCodingTableSelector));
515 final boolean wasCopying = copying;
516 if (redact && copying) {
518 writeEntropyCodedAllZeroACCoefficients();
524 final int rrrrssss = decode();
529 else if (rrrrssss == 0xF0) {
535 final int rrrr = rrrrssss >>> 4;
536 final int ssss = rrrrssss & 0x0f;
538 final int acBits = getValueOfRequestedLength(ssss);
539 final int acValue = convertSignAndAmplitudeBitsToValue(acBits,ssss);
546 copying = wasCopying;
550 private final boolean redactionDecision(
int colMCU,
int rowMCU,
int thisHorizontalSamplingFactor,
int thisVerticalSamplingFactor,
int maxHorizontalSamplingFactor,
int maxVerticalSamplingFactor,
int h,
int v,Vector<Shape> redactionShapes) {
551 boolean redactJustBlockNotEntireMCU =
true;
554 final int vMCUSize = 8 * maxVerticalSamplingFactor;
555 final int hMCUSize = 8 * maxHorizontalSamplingFactor;
558 final int hMCUOffset = colMCU * hMCUSize;
559 final int vMCUOffset = rowMCU * vMCUSize;
562 Rectangle blockShape =
null;
563 if (redactJustBlockNotEntireMCU) {
564 final int hBlockSize = 8 * maxHorizontalSamplingFactor/thisHorizontalSamplingFactor;
565 final int vBlockSize = 8 * maxVerticalSamplingFactor/thisVerticalSamplingFactor;
568 final int xBlock = hMCUOffset + h * hBlockSize;
569 final int yBlock = vMCUOffset + v * vBlockSize;
571 blockShape =
new Rectangle(xBlock,yBlock,hBlockSize,vBlockSize);
574 blockShape =
new Rectangle(hMCUOffset,vMCUOffset,hMCUSize,vMCUSize);
578 boolean redact =
false;
579 if (redactionShapes !=
null) {
580 for (Shape redactionShape : redactionShapes) {
581 if (redactionShape.intersects(blockShape)) {
590 private final void writeDecompressedPixel(
int c,
int decompressedPixel)
throws IOException {
591 if (sof.getSamplePrecision() <= 8) {
592 decompressedOutputPerComponent[c].writeByte(decompressedPixel);
596 decompressedOutputPerComponent[c].writeShort(decompressedPixel);
600 private final void getOneMinimumCodedUnit(
int nComponents,
int[] DCEntropyCodingTableSelector,
int[] ACEntropyCodingTableSelector,
int[] HorizontalSamplingFactor,
int[] VerticalSamplingFactor,
int maxHorizontalSamplingFactor,
int maxVerticalSamplingFactor,
int colMCU,
int rowMCU,
int[] originalDCValue,Boolean[] firstRedaction,Boolean[] wasRedacting,Vector<Shape> redactionShapes)
throws Exception, IOException {
601 for (
int c=0; c<nComponents; ++c) {
603 for (
int v=0; v<VerticalSamplingFactor[c]; ++v) {
604 for (
int h=0; h<HorizontalSamplingFactor[c]; ++h) {
606 boolean redact = redactionDecision(colMCU,rowMCU,HorizontalSamplingFactor[c],VerticalSamplingFactor[c],maxHorizontalSamplingFactor,maxVerticalSamplingFactor,h,v,redactionShapes);
607 firstRedaction[c] =
false;
609 if (!wasRedacting[c]) {
610 firstRedaction[c] =
true;
615 getOneDCTDataUnit(DCEntropyCodingTableSelector[c],ACEntropyCodingTableSelector[c],redact,firstRedaction[c],wasRedacting[c],c,originalDCValue);
617 else if (isLossless) {
618 int decompressedPixel = getOneLosslessValue(c,DCEntropyCodingTableSelector[c],colMCU,rowMCU);
620 writeDecompressedPixel(c,decompressedPixel);
624 throw new Exception(
"Only DCT or Lossless processes supported (not "+Markers.getAbbreviation(sof.getMarker())+
" "+Markers.getDescription(sof.getMarker())+
")");
626 wasRedacting[c] = redact;
632 private static final int max(
int[] a) {
633 int m = Integer.MIN_VALUE;
650 public final byte[]
finish(
byte[] bytesToDecompress,
int mcuCount,
int mcuOffset)
throws Exception, IOException {
652 this.bytesToDecompress = bytesToDecompress;
653 availableBytes = this.bytesToDecompress.length;
659 initializeWriteBits();
662 if (rowNumberAtBeginningOfRestartInterval !=
null) {
663 for (
int c=0; c<nComponents; ++c) {
665 rowNumberAtBeginningOfRestartInterval[c] = currentRowNumber[c];
669 int[] originalDCValue =
new int[nComponents];
670 Boolean[] firstRedaction =
new Boolean[nComponents];
671 Boolean[] wasRedacting =
new Boolean[nComponents];
672 for (
int c=0; c<nComponents; ++c) {
673 originalDCValue[c] = 0;
674 firstRedaction[c] =
false;
675 wasRedacting[c] =
false;
679 for (
int mcu=0; mcu<mcuCount; ++mcu) {
680 int rowMCU = mcuOffset / nMCUHorizontally;
681 int colMCU = mcuOffset % nMCUHorizontally;
683 getOneMinimumCodedUnit(nComponents,DCEntropyCodingTableSelector,ACEntropyCodingTableSelector,HorizontalSamplingFactor,VerticalSamplingFactor,maxHorizontalSamplingFactor,maxVerticalSamplingFactor,colMCU,rowMCU,originalDCValue,firstRedaction,wasRedacting,redactionShapes);
704 return copying ? copiedBytes.toByteArray() :
null;
707 private final void dumpHuffmanTables() {
708 System.err.print(
"\n");
709 for (
HuffmanTable ht : htByClassAndIdentifer.values()) {
710 System.err.print(ht.toString());
714 private final void dumpQuantizationTables() {
715 System.err.print(
"\n");
716 for (QuantizationTable qt : qtByIdentifer.values()) {
717 System.err.print(qt.toString());