blob: 566cdf743c28f530102bcd1d5e610df955d8b1ea [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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.pdfbox.jbig2.err.IntegerMaxValueException;
import org.apache.pdfbox.jbig2.err.InvalidHeaderValueException;
import org.apache.pdfbox.jbig2.err.JBIG2Exception;
import org.apache.pdfbox.jbig2.image.Bitmaps;
import org.apache.pdfbox.jbig2.segments.EndOfStripe;
import org.apache.pdfbox.jbig2.segments.PageInformation;
import org.apache.pdfbox.jbig2.segments.RegionSegmentInformation;
import org.apache.pdfbox.jbig2.util.CombinationOperator;
import org.apache.pdfbox.jbig2.util.log.Logger;
import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
/**
* This class represents a JBIG2 page.
*/
class JBIG2Page {
private static final Logger log = LoggerFactory.getLogger(JBIG2Page.class);
/**
* This list contains all segments of this page, sorted by segment number in ascending order.
*/
private final Map<Integer, SegmentHeader> segments = new TreeMap<Integer, SegmentHeader>();
/** NOTE: page number != segmentList index */
private final int pageNumber;
/** The page bitmap that represents the page buffer */
private Bitmap pageBitmap;
private int finalHeight;
private int finalWidth;
private int resolutionX;
private int resolutionY;
private final JBIG2Document document;
protected JBIG2Page(JBIG2Document document, int pageNumber) {
this.document = document;
this.pageNumber = pageNumber;
}
/**
* This method searches for a segment specified by its number.
*
* @param number - Segment number of the segment to search.
*
* @return The retrieved {@link SegmentHeader} or {@code null} if not available.
*/
public SegmentHeader getSegment(int number) {
SegmentHeader s = segments.get(number);
if (null != s) {
return s;
}
if (null != document) {
return document.getGlobalSegment(number);
}
log.info("Segment not found, returning null.");
return null;
}
/**
* Returns the associated page information segment.
*
* @return The associated {@link PageInformation} segment or {@code null} if not available.
*/
protected SegmentHeader getPageInformationSegment() {
for (SegmentHeader s : segments.values()) {
if (s.getSegmentType() == 48) {
return s;
}
}
log.info("Page information segment not found.");
return null;
}
/**
* This method returns the decoded bitmap if present. Otherwise the page bitmap will be composed
* before returning the result.
*
* @return pageBitmap - The result of decoding a page
* @throws JBIG2Exception
* @throws IOException
*/
protected Bitmap getBitmap() throws JBIG2Exception, IOException {
long timestamp;
if (JBIG2ImageReader.PERFORMANCE_TEST) {
timestamp = System.currentTimeMillis();
}
if (null == pageBitmap) {
composePageBitmap();
}
if (JBIG2ImageReader.PERFORMANCE_TEST) {
log.info("PAGE DECODING: " + (System.currentTimeMillis() - timestamp) + " ms");
}
return pageBitmap;
}
/**
* This method composes the segments' bitmaps to a page and stores the page as a {@link Bitmap}
*
* @throws IOException
* @throws JBIG2Exception
*/
private void composePageBitmap() throws IOException, JBIG2Exception {
if (pageNumber > 0) {
// Page 79, 1) Decoding the page information segment
PageInformation pageInformation = (PageInformation) getPageInformationSegment().getSegmentData();
createPage(pageInformation);
clearSegmentData();
}
}
private void createPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
InvalidHeaderValueException {
if (!pageInformation.isStriped() || pageInformation.getHeight() != -1) {
// Page 79, 4)
createNormalPage(pageInformation);
} else {
createStripedPage(pageInformation);
}
}
private void createNormalPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
InvalidHeaderValueException {
pageBitmap = new Bitmap(pageInformation.getWidth(), pageInformation.getHeight());
// Page 79, 3)
// If default pixel value is not 0, byte will be filled with 0xff
if (pageInformation.getDefaultPixelValue() != 0) {
Arrays.fill(pageBitmap.getByteArray(), (byte) 0xff);
}
for (SegmentHeader s : segments.values()) {
// Page 79, 5)
switch (s.getSegmentType()){
case 6 : // Immediate text region
case 7 : // Immediate lossless text region
case 22 : // Immediate halftone region
case 23 : // Immediate lossless halftone region
case 38 : // Immediate generic region
case 39 : // Immediate lossless generic region
case 42 : // Immediate generic refinement region
case 43 : // Immediate lossless generic refinement region
final Region r = (Region) s.getSegmentData();
final Bitmap regionBitmap = r.getRegionBitmap();
if (fitsPage(pageInformation, regionBitmap)) {
pageBitmap = regionBitmap;
} else {
final RegionSegmentInformation regionInfo = r.getRegionInfo();
final CombinationOperator op = getCombinationOperator(pageInformation,
regionInfo.getCombinationOperator());
Bitmaps.blit(regionBitmap, pageBitmap, regionInfo.getXLocation(), regionInfo.getYLocation(), op);
}
break;
}
}
}
/**
* Check if we have only one region that forms the complete page. If the dimension equals the
* page's dimension set the region's bitmap as the page's bitmap. Otherwise we have to blit the
* smaller region's bitmap into the page's bitmap (see Issue 6).
*
* @param pageInformation
* @param regionBitmap
* @return
*/
private boolean fitsPage(PageInformation pageInformation, final Bitmap regionBitmap) {
return countRegions() == 1 && pageInformation.getDefaultPixelValue() == 0
&& pageInformation.getWidth() == regionBitmap.getWidth()
&& pageInformation.getHeight() == regionBitmap.getHeight();
}
private void createStripedPage(PageInformation pageInformation) throws IOException, IntegerMaxValueException,
InvalidHeaderValueException {
final ArrayList<SegmentData> pageStripes = collectPageStripes();
pageBitmap = new Bitmap(pageInformation.getWidth(), finalHeight);
int startLine = 0;
for (SegmentData sd : pageStripes) {
if (sd instanceof EndOfStripe) {
startLine = ((EndOfStripe) sd).getLineNumber() + 1;
} else {
final Region r = (Region) sd;
final RegionSegmentInformation regionInfo = r.getRegionInfo();
final CombinationOperator op = getCombinationOperator(pageInformation, regionInfo.getCombinationOperator());
Bitmaps.blit(r.getRegionBitmap(), pageBitmap, regionInfo.getXLocation(), startLine, op);
}
}
}
private ArrayList<SegmentData> collectPageStripes() {
final ArrayList<SegmentData> pageStripes = new ArrayList<SegmentData>();
for (SegmentHeader s : segments.values()) {
// Page 79, 5)
switch (s.getSegmentType()){
case 6 : // Immediate text region
case 7 : // Immediate lossless text region
case 22 : // Immediate halftone region
case 23 : // Immediate lossless halftone region
case 38 : // Immediate generic region
case 39 : // Immediate lossless generic region
case 42 : // Immediate generic refinement region
case 43 : // Immediate lossless generic refinement region
Region r = (Region) s.getSegmentData();
pageStripes.add(r);
break;
case 50 : // End of stripe
EndOfStripe eos = (EndOfStripe) s.getSegmentData();
pageStripes.add(eos);
finalHeight = eos.getLineNumber() + 1;
break;
}
}
return pageStripes;
}
/**
* This method counts the regions segments. If there is only one region, the bitmap of this
* segment is equal to the page bitmap and blitting is not necessary.
*
* @return Amount of regions.
*/
private int countRegions() {
int regionCount = 0;
for (SegmentHeader s : segments.values()) {
switch (s.getSegmentType()){
case 6 : // Immediate text region
case 7 : // Immediate lossless text region
case 22 : // Immediate halftone region
case 23 : // Immediate lossless halftone region
case 38 : // Immediate generic region
case 39 : // Immediate lossless generic region
case 42 : // Immediate generic refinement region
case 43 : // Immediate lossless generic refinement region
regionCount++;
}
}
return regionCount;
}
/**
* This method checks and sets, which combination operator shall be used.
*
* @param pi - <code>PageInformation</code> object
* @param newOperator - The combination operator, specified by actual segment
* @return the new combination operator
*/
private CombinationOperator getCombinationOperator(PageInformation pi, CombinationOperator newOperator) {
if (pi.isCombinationOperatorOverrideAllowed()) {
return newOperator;
} else {
return pi.getCombinationOperator();
}
}
/**
* Adds a {@link SegmentHeader} into the page's segments map.
*
* @param segment - The segment to be added.
*/
protected void add(SegmentHeader segment) {
segments.put(segment.getSegmentNr(), segment);
}
/**
* Resets the memory-critical segments to force on-demand-decoding and to avoid holding the
* segments' bitmap too long.
*/
private void clearSegmentData() {
Set<Integer> keySet = segments.keySet();
for (Integer key : keySet) {
segments.get(key).cleanSegmentData();
}
}
/**
* Reset memory-critical parts of page.
*/
protected void clearPageData() {
pageBitmap = null;
}
/**
* Returns the final height of the page.
*
* @return The final height of the page.
* @throws IOException
* @throws JBIG2Exception
*/
protected int getHeight() throws IOException, JBIG2Exception {
if (finalHeight == 0) {
PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
if (pi.getHeight() == 0xffffffff) {
getBitmap();
} else {
finalHeight = pi.getHeight();
}
}
return finalHeight;
}
protected int getWidth() {
if (finalWidth == 0) {
PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
finalWidth = pi.getWidth();
}
return finalWidth;
}
protected int getResolutionX() {
if (resolutionX == 0) {
PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
resolutionX = pi.getResolutionX();
}
return resolutionX;
}
protected int getResolutionY() {
if (resolutionY == 0) {
PageInformation pi = (PageInformation) getPageInformationSegment().getSegmentData();
resolutionY = pi.getResolutionY();
}
return resolutionY;
}
@Override
public String toString() {
return getClass().getSimpleName() + " (Page number: " + pageNumber + ")";
}
}