blob: 7dc6c0a0b2d722b6ac56fa0f2f3c7b60009f67a2 [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.pdfbox.jbig2.segments;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.pdfbox.jbig2.Bitmap;
import org.apache.pdfbox.jbig2.Region;
import org.apache.pdfbox.jbig2.SegmentHeader;
import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
import org.apache.pdfbox.jbig2.image.Bitmaps;
import org.apache.pdfbox.jbig2.io.SubInputStream;
import org.apache.pdfbox.jbig2.util.CombinationOperator;
/**
* This class represents the data of segment type "Halftone region". Parsing is described in 7.4.5, page 67. Decoding
* procedure in 6.6.5 and 7.4.5.2.
*/
public class HalftoneRegion implements Region
{
private SubInputStream subInputStream;
private SegmentHeader segmentHeader;
private long dataHeaderOffset;
private long dataHeaderLength;
private long dataOffset;
private long dataLength;
/** Region segment information field, 7.4.1 */
private RegionSegmentInformation regionInfo;
/** Halftone segment information field, 7.4.5.1.1 */
private byte hDefaultPixel;
private CombinationOperator hCombinationOperator;
private boolean hSkipEnabled;
private byte hTemplate;
private boolean isMMREncoded;
/** Halftone grid position and size, 7.4.5.1.2 */
/** Width of the gray-scale image, 7.4.5.1.2.1 */
private int hGridWidth;
/** Height of the gray-scale image, 7.4.5.1.2.2 */
private int hGridHeight;
/** Horizontal offset of the grid, 7.4.5.1.2.3 */
private int hGridX;
/** Vertical offset of the grid, 7.4.5.1.2.4 */
private int hGridY;
/** Halftone grid vector, 7.4.5.1.3 */
/** Horizontal coordinate of the halftone grid vector, 7.4.5.1.3.1 */
private int hRegionX;
/** Vertical coordinate of the halftone grod vector, 7.4.5.1.3.2 */
private int hRegionY;
/** Decoded data */
private Bitmap halftoneRegionBitmap;
/**
* Previously decoded data from other regions or dictionaries, stored to use as patterns in this region.
*/
private ArrayList<Bitmap> patterns;
public HalftoneRegion()
{
}
public HalftoneRegion(final SubInputStream subInputStream)
{
this.subInputStream = subInputStream;
this.regionInfo = new RegionSegmentInformation(subInputStream);
}
public HalftoneRegion(final SubInputStream subInputStream, final SegmentHeader segmentHeader)
{
this.subInputStream = subInputStream;
this.segmentHeader = segmentHeader;
this.regionInfo = new RegionSegmentInformation(subInputStream);
}
private void parseHeader() throws IOException, InvalidHeaderValueException
{
regionInfo.parseHeader();
/* Bit 7 */
hDefaultPixel = (byte) subInputStream.readBit();
/* Bit 4-6 */
hCombinationOperator = CombinationOperator
.translateOperatorCodeToEnum((short) (subInputStream.readBits(3) & 0xf));
/* Bit 3 */
if (subInputStream.readBit() == 1)
{
hSkipEnabled = true;
}
/* Bit 1-2 */
hTemplate = (byte) (subInputStream.readBits(2) & 0xf);
/* Bit 0 */
if (subInputStream.readBit() == 1)
{
isMMREncoded = true;
}
hGridWidth = (int) (subInputStream.readBits(32) & 0xffffffff);
hGridHeight = (int) (subInputStream.readBits(32) & 0xffffffff);
hGridX = (int) subInputStream.readBits(32);
hGridY = (int) subInputStream.readBits(32);
hRegionX = (int) subInputStream.readBits(16) & 0xffff;
hRegionY = (int) subInputStream.readBits(16) & 0xffff;
/* Segment data structure */
computeSegmentDataStructure();
}
private void computeSegmentDataStructure() throws IOException
{
dataOffset = subInputStream.getStreamPosition();
dataHeaderLength = dataOffset - dataHeaderOffset;
dataLength = subInputStream.length() - dataHeaderLength;
}
/**
* The procedure is described in JBIG2 ISO standard, 6.6.5.
*
* @return The decoded {@link Bitmap} of this region.
*
* @throws IOException if an underlying IO operation fails
* @throws InvalidHeaderValueException if a segment header value is invalid
*/
public Bitmap getRegionBitmap() throws IOException, InvalidHeaderValueException
{
if (null == halftoneRegionBitmap)
{
/* 6.6.5, page 40 */
/* 1) */
halftoneRegionBitmap = new Bitmap(regionInfo.getBitmapWidth(),
regionInfo.getBitmapHeight());
if (patterns == null)
{
patterns = getPatterns();
}
if (hDefaultPixel == 1)
{
halftoneRegionBitmap.fillBitmap((byte) 0xff);
}
/* 2) */
/*
* 6.6.5.1 Computing hSkip - At the moment SKIP is not used... we are not able to test it.
*/
// Bitmap hSkip;
// if (hSkipEnabled) {
// int hPatternHeight = (int) hPats.get(0).getHeight();
// int hPatternWidth = (int) hPats.get(0).getWidth();
// TODO: Set or get pattern width and height from referred
// pattern segments. The method is called like this:
// hSkip = computeHSkip(hPatternHeight, hPatternWidth);
// }
/* 3) */
final int bitsPerValue = (int) Math.ceil(Math.log(patterns.size()) / Math.log(2));
/* 4) */
final int[][] grayScaleValues = grayScaleDecoding(bitsPerValue);
/* 5), rendering the pattern, described in 6.6.5.2 */
renderPattern(grayScaleValues);
}
/* 6) */
return halftoneRegionBitmap;
}
/**
* This method draws the pattern into the region bitmap ({@code htReg}), as described in 6.6.5.2, page 42
*/
private void renderPattern(final int[][] grayScaleValues)
{
int x = 0, y = 0;
// 1)
for (int m = 0; m < hGridHeight; m++)
{
// a)
for (int n = 0; n < hGridWidth; n++)
{
// i)
x = computeX(m, n);
y = computeY(m, n);
// ii)
final Bitmap patternBitmap = patterns.get(grayScaleValues[m][n]);
Bitmaps.blit(patternBitmap, halftoneRegionBitmap, (x + hGridX), (y + hGridY),
hCombinationOperator);
}
}
}
/**
* @throws IOException
* @throws InvalidHeaderValueException
*
*/
private ArrayList<Bitmap> getPatterns() throws InvalidHeaderValueException, IOException
{
final ArrayList<Bitmap> patterns = new ArrayList<Bitmap>();
for (SegmentHeader s : segmentHeader.getRtSegments())
{
final PatternDictionary patternDictionary = (PatternDictionary) s.getSegmentData();
patterns.addAll(patternDictionary.getDictionary());
}
return patterns;
}
/**
* Gray-scale image decoding procedure is special for halftone region decoding and is described in Annex C.5 on page
* 98.
*/
private int[][] grayScaleDecoding(final int bitsPerValue) throws IOException
{
short[] gbAtX = null;
short[] gbAtY = null;
if (!isMMREncoded)
{
gbAtX = new short[4];
gbAtY = new short[4];
// Set AT pixel values
if (hTemplate <= 1)
gbAtX[0] = 3;
else if (hTemplate >= 2)
gbAtX[0] = 2;
gbAtY[0] = -1;
gbAtX[1] = -3;
gbAtY[1] = -1;
gbAtX[2] = 2;
gbAtY[2] = -2;
gbAtX[3] = -2;
gbAtY[3] = -2;
}
Bitmap[] grayScalePlanes = new Bitmap[bitsPerValue];
// 1)
GenericRegion genericRegion = new GenericRegion(subInputStream);
genericRegion.setParameters(isMMREncoded, dataOffset, dataLength, hGridHeight, hGridWidth,
hTemplate, false, hSkipEnabled, gbAtX, gbAtY);
// 2)
int j = bitsPerValue - 1;
grayScalePlanes[j] = genericRegion.getRegionBitmap();
while (j > 0)
{
j--;
genericRegion.resetBitmap();
// 3) a)
grayScalePlanes[j] = genericRegion.getRegionBitmap();
// 3) b)
grayScalePlanes = combineGrayScalePlanes(grayScalePlanes, j);
}
// 4)
return computeGrayScaleValues(grayScalePlanes, bitsPerValue);
}
private Bitmap[] combineGrayScalePlanes(Bitmap[] grayScalePlanes, int j)
{
int byteIndex = 0;
for (int y = 0; y < grayScalePlanes[j].getHeight(); y++)
{
for (int x = 0; x < grayScalePlanes[j].getWidth(); x += 8)
{
final byte newValue = grayScalePlanes[j + 1].getByte(byteIndex);
final byte oldValue = grayScalePlanes[j].getByte(byteIndex);
grayScalePlanes[j].setByte(byteIndex++,
Bitmaps.combineBytes(oldValue, newValue, CombinationOperator.XOR));
}
}
return grayScalePlanes;
}
private int[][] computeGrayScaleValues(final Bitmap[] grayScalePlanes, final int bitsPerValue)
{
// Gray-scale decoding procedure, page 98
final int[][] grayScaleValues = new int[hGridHeight][hGridWidth];
// 4)
for (int y = 0; y < hGridHeight; y++)
{
for (int x = 0; x < hGridWidth; x += 8)
{
final int minorWidth = hGridWidth - x > 8 ? 8 : hGridWidth - x;
int byteIndex = grayScalePlanes[0].getByteIndex(x, y);
for (int minorX = 0; minorX < minorWidth; minorX++)
{
final int i = minorX + x;
grayScaleValues[y][i] = 0;
for (int j = 0; j < bitsPerValue; j++)
{
grayScaleValues[y][i] += ((grayScalePlanes[j]
.getByte(byteIndex) >> (7 - i & 7)) & 1) * (1 << j);
}
}
}
}
return grayScaleValues;
}
private int computeX(final int m, final int n)
{
return shiftAndFill((hGridX + m * hRegionY + n * hRegionX));
}
private int computeY(final int m, final int n)
{
return shiftAndFill((hGridY + m * hRegionX - n * hRegionY));
}
private int shiftAndFill(int value)
{
// shift value by 8 and let the leftmost 8 bits be 0
value >>= 8;
if (value < 0)
{
// fill the leftmost 8 bits with 1
final int bitPosition = (int) (Math.log(Integer.highestOneBit(value)) / Math.log(2));
for (int i = 1; i < 31 - bitPosition; i++)
{
// bit flip
value |= 1 << (31 - i);
}
}
return value;
}
public void init(final SegmentHeader header, final SubInputStream sis)
throws InvalidHeaderValueException, IOException
{
this.segmentHeader = header;
this.subInputStream = sis;
this.regionInfo = new RegionSegmentInformation(subInputStream);
parseHeader();
}
public CombinationOperator getCombinationOperator()
{
return hCombinationOperator;
}
public RegionSegmentInformation getRegionInfo()
{
return regionInfo;
}
protected byte getHTemplate()
{
return hTemplate;
}
protected boolean isHSkipEnabled()
{
return hSkipEnabled;
}
protected boolean isMMREncoded()
{
return isMMREncoded;
}
protected int getHGridWidth()
{
return hGridWidth;
}
protected int getHGridHeight()
{
return hGridHeight;
}
protected int getHGridX()
{
return hGridX;
}
protected int getHGridY()
{
return hGridY;
}
protected int getHRegionX()
{
return hRegionX;
}
protected int getHRegionY()
{
return hRegionY;
}
protected byte getHDefaultPixel()
{
return hDefaultPixel;
}
}