blob: 6c9cc75577d8e9b3c7b6a6f2a09a04196ddc7f77 [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;
/**
* This class represents a JBIG2 page.
*/
class JBIG2Page
{
/**
* 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);
}
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;
}
}
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
{
if (null == pageBitmap)
{
composePageBitmap();
}
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)
{
pageBitmap.fillBitmap((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 + ")";
}
}