blob: d73f2f8fb90dba2af263bae26d3f2658ac8032b1 [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.image;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import javax.imageio.ImageReadParam;
import org.apache.pdfbox.jbig2.Bitmap;
import org.apache.pdfbox.jbig2.JBIG2ReadParam;
import org.apache.pdfbox.jbig2.util.CombinationOperator;
public class Bitmaps {
public static WritableRaster asRaster(final Bitmap bitmap) {
return asRaster(bitmap, FilterType.Gaussian);
}
public static WritableRaster asRaster(final Bitmap bitmap, final FilterType filterType) {
if (bitmap == null)
throw new IllegalArgumentException("bitmap must not be null");
final JBIG2ReadParam param = new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(0, 0, bitmap.getWidth(),
bitmap.getHeight()), new Dimension(bitmap.getWidth(), bitmap.getHeight()));
return asRaster(bitmap, param, filterType);
}
public static WritableRaster asRaster(Bitmap bitmap, final ImageReadParam param, final FilterType filterType) {
if (bitmap == null)
throw new IllegalArgumentException("bitmap must not be null");
if (param == null)
throw new IllegalArgumentException("param must not be null");
final Dimension sourceRenderSize = param.getSourceRenderSize();
double scaleX;
double scaleY;
if (sourceRenderSize != null) {
scaleX = sourceRenderSize.getWidth() / bitmap.getWidth();
scaleY = sourceRenderSize.getHeight() / bitmap.getHeight();
} else {
scaleX = scaleY = 1;
}
Rectangle sourceRegion = param.getSourceRegion();
if (sourceRegion != null && !bitmap.getBounds().equals(sourceRegion)) {
// make sure we don't request an area outside of the source bitmap
sourceRegion = bitmap.getBounds().intersection(sourceRegion);
// get region of interest
bitmap = Bitmaps.extract(sourceRegion, bitmap);
}
/*
* Subsampling is the advance of columns/rows for each pixel in the according direction. The
* resulting image's quality will be bad because we loose information if we step over
* columns/rows. For example, a thin line (1 pixel high) may disappear completely. To avoid this
* we use resize filters if scaling will be performed anyway. The resize filters use scale
* factors, one for horizontal and vertical direction. We care about the given subsampling steps
* by adjusting the scale factors. If scaling is not performed, subsampling is performed in its
* original manner.
*/
final boolean requiresScaling = scaleX != 1 || scaleY != 1;
final boolean requiresXSubsampling = param.getSourceXSubsampling() != 1;
final boolean requiresYSubsampling = param.getSourceYSubsampling() != 1;
if (requiresXSubsampling && requiresYSubsampling) {
// Apply vertical and horizontal subsampling
if (requiresScaling) {
scaleX /= (double) param.getSourceXSubsampling();
scaleY /= (double) param.getSourceYSubsampling();
} else {
bitmap = subsample(bitmap, param);
}
} else {
if (requiresXSubsampling) {
// Apply horizontal subsampling only
if (requiresScaling) {
scaleX /= (double) param.getSourceXSubsampling();
} else {
bitmap = Bitmaps.subsampleX(bitmap, param.getSourceXSubsampling(), param.getSubsamplingXOffset());
}
}
if (requiresYSubsampling) {
// Apply vertical subsampling only
if (requiresScaling) {
scaleY /= (double) param.getSourceYSubsampling();
} else {
bitmap = Bitmaps.subsampleY(bitmap, param.getSourceYSubsampling(), param.getSubsamplingYOffset());
}
}
}
return buildRaster(bitmap, filterType, scaleX, scaleY);
}
private static WritableRaster buildRaster(final Bitmap bitmap, final FilterType filterType, final double scaleX,
final double scaleY) {
final Rectangle dstBounds = new Rectangle(0, 0, //
(int) Math.round(bitmap.getWidth() * scaleX), //
(int) Math.round(bitmap.getHeight() * scaleY));
final WritableRaster dst = WritableRaster.createInterleavedRaster(DataBuffer.TYPE_BYTE, dstBounds.width,
dstBounds.height, 1, new Point());
if (scaleX != 1 || scaleY != 1) {
// scaling required
final Resizer resizer = new Resizer(scaleX, scaleY);
final Filter filter = Filter.byType(filterType);
resizer.resize(bitmap, bitmap.getBounds() /* sourceRegion */, dst, dstBounds, filter, filter);
} else {
// scaling not required, paste bitmap into raster pixel per pixel
int byteIndex = 0;
for (int y = 0; y < bitmap.getHeight(); y++) {
for (int x = 0; x < bitmap.getWidth(); byteIndex++) {
final int pixels = (~bitmap.getByte(byteIndex)) & 0xFF;
final int relevantPixels = bitmap.getWidth() - x > 8 ? 8 : bitmap.getWidth() - x;
final int endIdx = 7 - relevantPixels;
for (int bytePosition = 7; bytePosition > endIdx; bytePosition--, x++) {
dst.setSample(x, y, 0, (pixels >> bytePosition) & 0x1);
}
}
}
}
return dst;
}
public static BufferedImage asBufferedImage(Bitmap bitmap) {
return asBufferedImage(bitmap, FilterType.Gaussian);
}
public static BufferedImage asBufferedImage(Bitmap bitmap, FilterType filterType) {
if (bitmap == null)
throw new IllegalArgumentException("bitmap must not be null");
final JBIG2ReadParam param = new JBIG2ReadParam(1, 1, 0, 0, new Rectangle(0, 0, bitmap.getWidth(),
bitmap.getHeight()), new Dimension(bitmap.getWidth(), bitmap.getHeight()));
return asBufferedImage(bitmap, param, filterType);
}
public static BufferedImage asBufferedImage(Bitmap bitmap, ImageReadParam param, FilterType filterType) {
if (bitmap == null)
throw new IllegalArgumentException("bitmap must not be null");
if (param == null)
throw new IllegalArgumentException("param must not be null");
final WritableRaster raster = asRaster(bitmap, param, filterType);
final Dimension sourceRenderSize = param.getSourceRenderSize();
final double scaleX;
final double scaleY;
if (sourceRenderSize != null) {
scaleX = sourceRenderSize.getWidth() / bitmap.getWidth();
scaleY = sourceRenderSize.getHeight() / bitmap.getHeight();
} else {
scaleX = scaleY = 1d;
}
ColorModel cm = null;
final boolean isScaled = scaleX != 1 || scaleY != 1;
if (isScaled) {
final int size = 256;
final int divisor = size - 1;
final byte[] gray = new byte[size];
for (int i = size - 1, s = 0; i >= 0; i--, s++) {
gray[i] = (byte) (255 - s * 255 / divisor);
}
cm = new IndexColorModel(8, size, gray, gray, gray);
} else {
cm = new IndexColorModel(8, 2, //
new byte[]{
0x00, (byte) 0xff
}, new byte[]{
0x00, (byte) 0xff
}, new byte[]{
0x00, (byte) 0xff
});
}
return new BufferedImage(cm, raster, false, null);
}
/**
* Returns the specified rectangle area of the bitmap.
*
* @param roi - A {@link Rectangle} that specifies the requested image section.
* @return A {@code Bitmap} that represents the requested image section.
*/
public static Bitmap extract(final Rectangle roi, final Bitmap src) {
final Bitmap dst = new Bitmap(roi.width, roi.height);
final int upShift = roi.x & 0x07;
final int downShift = 8 - upShift;
int dstLineStartIdx = 0;
final int padding = (8 - dst.getWidth() & 0x07);
int srcLineStartIdx = src.getByteIndex(roi.x, roi.y);
int srcLineEndIdx = src.getByteIndex(roi.x + roi.width - 1, roi.y);
final boolean usePadding = dst.getRowStride() == srcLineEndIdx + 1 - srcLineStartIdx;
for (int y = roi.y; y < roi.getMaxY(); y++) {
int srcIdx = srcLineStartIdx;
int dstIdx = dstLineStartIdx;
if (srcLineStartIdx == srcLineEndIdx) {
final byte pixels = (byte) (src.getByte(srcIdx) << upShift);
dst.setByte(dstIdx, unpad(padding, pixels));
} else if (upShift == 0) {
for (int x = srcLineStartIdx; x <= srcLineEndIdx; x++) {
byte value = src.getByte(srcIdx++);
if (x == srcLineEndIdx && usePadding) {
value = unpad(padding, value);
}
dst.setByte(dstIdx++, value);
}
} else {
copyLine(src, dst, upShift, downShift, padding, srcLineStartIdx, srcLineEndIdx, usePadding, srcIdx, dstIdx);
}
srcLineStartIdx += src.getRowStride();
srcLineEndIdx += src.getRowStride();
dstLineStartIdx += dst.getRowStride();
}
return dst;
}
private static void copyLine(Bitmap src, Bitmap dst, int sourceUpShift, int sourceDownShift, int padding,
int firstSourceByteOfLine, int lastSourceByteOfLine, boolean usePadding, int sourceOffset, int targetOffset) {
for (int x = firstSourceByteOfLine; x < lastSourceByteOfLine; x++) {
if (sourceOffset + 1 < src.getByteArray().length) {
final boolean isLastByte = x + 1 == lastSourceByteOfLine;
byte value = (byte) (src.getByte(sourceOffset++) << sourceUpShift | (src.getByte(sourceOffset) & 0xff) >>> sourceDownShift);
if (isLastByte && !usePadding) {
value = unpad(padding, value);
}
dst.setByte(targetOffset++, value);
if (isLastByte && usePadding) {
value = unpad(padding, (byte) ((src.getByte(sourceOffset) & 0xff) << sourceUpShift));
dst.setByte(targetOffset, value);
}
} else {
final byte value = (byte) (src.getByte(sourceOffset++) << sourceUpShift & 0xff);
dst.setByte(targetOffset++, value);
}
}
}
/**
* Removes unnecessary bits from a byte.
*
* @param padding - The amount of unnecessary bits.
* @param value - The byte that should be cleaned up.
* @return A cleaned byte.
*/
private static byte unpad(int padding, byte value) {
return (byte) (value >> padding << padding);
}
public static Bitmap subsample(Bitmap src, ImageReadParam param) {
if (src == null)
throw new IllegalArgumentException("src must not be null");
if (param == null)
throw new IllegalArgumentException("param must not be null");
final int xSubsampling = param.getSourceXSubsampling();
final int ySubsampling = param.getSourceYSubsampling();
final int xSubsamplingOffset = param.getSubsamplingXOffset();
final int ySubsamplingOffset = param.getSubsamplingYOffset();
final int dstWidth = (src.getWidth() - xSubsamplingOffset) / xSubsampling;
final int dstHeight = (src.getHeight() - ySubsamplingOffset) / ySubsampling;
final Bitmap dst = new Bitmap(dstWidth, dstHeight);
for (int yDst = 0, ySrc = ySubsamplingOffset; yDst < dst.getHeight(); yDst++, ySrc += ySubsampling) {
for (int xDst = 0, xSrc = xSubsamplingOffset; xDst < dst.getWidth(); xDst++, xSrc += xSubsampling) {
final byte pixel = src.getPixel(xSrc, ySrc);
if (pixel != 0)
dst.setPixel(xDst, yDst, pixel);
}
}
return dst;
}
public static Bitmap subsampleX(Bitmap src, final int xSubsampling, final int xSubsamplingOffset) {
if (src == null)
throw new IllegalArgumentException("src must not be null");
final int dstHeight = (src.getWidth() - xSubsamplingOffset) / xSubsampling;
final Bitmap dst = new Bitmap(src.getWidth(), dstHeight);
for (int yDst = 0; yDst < dst.getHeight(); yDst++) {
for (int xDst = 0, xSrc = xSubsamplingOffset; xDst < dst.getWidth(); xDst++, xSrc += xSubsampling) {
final byte pixel = src.getPixel(xSrc, yDst);
if (pixel != 0)
dst.setPixel(xDst, yDst, pixel);
}
}
return dst;
}
public static Bitmap subsampleY(Bitmap src, final int ySubsampling, final int ySubsamplingOffset) {
if (src == null)
throw new IllegalArgumentException("src must not be null");
final int dstWidth = (src.getWidth() - ySubsamplingOffset) / ySubsampling;
final Bitmap dst = new Bitmap(dstWidth, src.getHeight());
for (int yDst = 0, ySrc = ySubsamplingOffset; yDst < dst.getHeight(); yDst++, ySrc += ySubsampling) {
for (int xDst = 0; xDst < dst.getWidth(); xDst++) {
final byte pixel = src.getPixel(xDst, ySrc);
if (pixel != 0)
dst.setPixel(xDst, yDst, pixel);
}
}
return dst;
}
/**
* The method combines two given bytes with an logical operator.
* <p>
* The JBIG2 Standard specifies 5 possible combinations of bytes.<br>
* <p>
* <b>Hint:</b> Please take a look at ISO/IEC 14492:2001 (E) for detailed definition and
* description of the operators.
*
* @param value1 - The value that should be combined with value2.
* @param value2 - The value that should be combined with value1.
* @param op - The specified combination operator.
*
* @return The combination result.
*/
public static byte combineBytes(byte value1, byte value2, CombinationOperator op) {
switch (op){
case OR :
return (byte) (value2 | value1);
case AND :
return (byte) (value2 & value1);
case XOR :
return (byte) (value2 ^ value1);
case XNOR :
return (byte) ~(value1 ^ value2);
case REPLACE :
default :
// Old value is replaced by new value.
return value2;
}
}
/**
* This method combines a given bitmap with the current instance.
* <p>
* Parts of the bitmap to blit that are outside of the target bitmap will be ignored.
*
* @param src - The bitmap that should be combined with the one of the current instance.
* @param x - The x coordinate where the upper left corner of the bitmap to blit should be
* positioned.
* @param y - The y coordinate where the upper left corner of the bitmap to blit should be
* positioned.
* @param combinationOperator - The combination operator for combining two pixels.
*/
public static void blit(Bitmap src, Bitmap dst, int x, int y, CombinationOperator combinationOperator) {
int startLine = 0;
int srcStartIdx = 0;
int srcEndIdx = (src.getRowStride() - 1);
// Ignore those parts of the source bitmap which would be placed outside the target bitmap.
if (x < 0) {
srcStartIdx = -x;
x = 0;
} else if (x + src.getWidth() > dst.getWidth()) {
srcEndIdx -= (src.getWidth() + x - dst.getWidth());
}
if (y < 0) {
startLine = -y;
y = 0;
srcStartIdx += src.getRowStride();
srcEndIdx += src.getRowStride();
} else if (y + src.getHeight() > dst.getHeight()) {
startLine = src.getHeight() + y - dst.getHeight();
}
final int shiftVal1 = x & 0x07;
final int shiftVal2 = 8 - shiftVal1;
final int padding = src.getWidth() & 0x07;
final int toShift = shiftVal2 - padding;
final boolean useShift = (shiftVal2 & 0x07) != 0;
final boolean specialCase = src.getWidth() <= ((srcEndIdx - srcStartIdx) << 3) + shiftVal2;
final int dstStartIdx = dst.getByteIndex(x, y);
final int lastLine = Math.min(src.getHeight(), startLine + dst.getHeight());
if (!useShift) {
blitUnshifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, combinationOperator);
} else if (specialCase) {
blitSpecialShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, toShift, shiftVal1,
shiftVal2, combinationOperator);
} else {
blitShifted(src, dst, startLine, lastLine, dstStartIdx, srcStartIdx, srcEndIdx, toShift, shiftVal1, shiftVal2,
combinationOperator, padding);
}
}
private static void blitUnshifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
int srcStartIdx, int srcEndIdx, CombinationOperator op) {
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
int dstIdx = dstStartIdx;
// Go through the bytes in a line of the Symbol
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
byte oldByte = dst.getByte(dstIdx);
byte newByte = src.getByte(srcIdx);
dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
}
}
}
private static void blitSpecialShifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1, int shiftVal2, CombinationOperator op) {
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
short register = 0;
int dstIdx = dstStartIdx;
// Go through the bytes in a line of the Symbol
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
byte oldByte = dst.getByte(dstIdx);
register = (short) ((register | src.getByte(srcIdx) & 0xff) << shiftVal2);
byte newByte = (byte) (register >> 8);
if (srcIdx == srcEndIdx) {
newByte = unpad(toShift, newByte);
}
dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
register <<= shiftVal1;
}
}
}
private static void blitShifted(Bitmap src, Bitmap dst, int startLine, int lastLine, int dstStartIdx,
int srcStartIdx, int srcEndIdx, int toShift, int shiftVal1, int shiftVal2, CombinationOperator op, int padding) {
for (int dstLine = startLine; dstLine < lastLine; dstLine++, dstStartIdx += dst.getRowStride(), srcStartIdx += src.getRowStride(), srcEndIdx += src.getRowStride()) {
short register = 0;
int dstIdx = dstStartIdx;
// Go through the bytes in a line of the symbol
for (int srcIdx = srcStartIdx; srcIdx <= srcEndIdx; srcIdx++) {
byte oldByte = dst.getByte(dstIdx);
register = (short) ((register | src.getByte(srcIdx) & 0xff) << shiftVal2);
byte newByte = (byte) (register >> 8);
dst.setByte(dstIdx++, Bitmaps.combineBytes(oldByte, newByte, op));
register <<= shiftVal1;
if (srcIdx == srcEndIdx) {
newByte = (byte) (register >> (8 - shiftVal2));
if (padding != 0) {
newByte = unpad(8 + toShift, newByte);
}
oldByte = dst.getByte(dstIdx);
dst.setByte(dstIdx, Bitmaps.combineBytes(oldByte, newByte, op));
}
}
}
}
}