/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed 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.icepdf.core.pobjects;
import org.icepdf.core.pobjects.graphics.*;
import org.icepdf.core.pobjects.graphics.RasterOps.*;
import org.icepdf.core.util.Defs;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility methods for applying various colour models and masks to
* image data.
*
* @since 5.0
*/
@SuppressWarnings("serial")
public class ImageUtility {
static final Logger logger =
Logger.getLogger(ImageUtility.class.toString());
static final int[] GRAY_1_BIT_INDEX_TO_RGB_REVERSED = new int[]{
0xFFFFFFFF,
0xFF000000
};
static final int[] GRAY_1_BIT_INDEX_TO_RGB = new int[]{
0xFF000000,
0xFFFFFFFF
};
static final int[] GRAY_2_BIT_INDEX_TO_RGB = new int[]{
0xFF000000,
0xFF555555,
0xFFAAAAAA,
0xFFFFFFFF
};
// 0. 1 2 3 4 5. 6 7 8 9 A. B C D E F. 0/3, 1/3, 2/3, 3/3
static final int[] GRAY_4_BIT_INDEX_TO_RGB = new int[]{
0xFF000000,
0xFF111111,
0xFF222222,
0xFF333333,
0xFF444444,
0xFF555555,
0xFF666666,
0xFF777777,
0xFF888888,
0xFF999999,
0xFFAAAAAA,
0xFFBBBBBB,
0xFFCCCCCC,
0xFFDDDDDD,
0xFFEEEEEE,
0xFFFFFFFF
};
static final int JPEG_ENC_UNKNOWN_PROBABLY_YCbCr = 0;
static final int JPEG_ENC_RGB = 1;
static final int JPEG_ENC_CMYK = 2;
static final int JPEG_ENC_YCbCr = 3;
static final int JPEG_ENC_YCCK = 4;
static final int JPEG_ENC_GRAY = 5;
private static boolean scaleQuality;
private static GraphicsConfiguration configuration;
static {
try {
configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
} catch (Throwable e) {
// just eat it as we likely have a headless exception and need to fall back to
// creating our own buffers.
}
// decide if large images will be scaled
scaleQuality = Defs.booleanProperty("org.icepdf.core.imageMaskScale.quality", true);
}
public ImageUtility() {
}
/**
* Creates a new buffered image using a GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
* instance. If not available (headless) we full back to raw buffer creation.
*
* @param width width of new image.
* @param height height of new image.
* @return returns an INT_RGB images.
*/
public static BufferedImage createCompatibleImage(int width, int height) {
if (configuration != null) {
return configuration.createCompatibleImage(width, height);
} else {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}
}
/**
* Creates a new bufferd image using a GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
* instance. If not available (headless) we full back to raw buffer creation.
*
* @param width width of new image.
* @param height height of new image.
*/
public static BufferedImage createTranslucentCompatibleImage(int width, int height) {
if (configuration != null) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Creating translucent image buffer " + width + "x" + height);
}
return configuration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
} else {
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
}
private BufferedImage alterBufferedImageAlpha(BufferedImage bi, int[] maskMinRGB, int[] maskMaxRGB) {
// check for alpha, if not we need to create a copy
if (!hasAlpha(bi)) {
bi = createBufferedImage(bi);
}
int width = bi.getWidth();
int height = bi.getHeight();
int maskMinRed = 0xFF;
int maskMinGreen = 0xFF;
int maskMinBlue = 0xFF;
int maskMaxRed = 0x00;
int maskMaxGreen = 0x00;
int maskMaxBlue = 0x00;
if (maskMinRGB != null && maskMaxRGB != null) {
maskMinRed = maskMinRGB[0];
maskMinGreen = maskMinRGB[1];
maskMinBlue = maskMinRGB[2];
maskMaxRed = maskMaxRGB[0];
maskMaxGreen = maskMaxRGB[1];
maskMaxBlue = maskMaxRGB[2];
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int alpha = 0xFF;
int argb = bi.getRGB(x, y);
int red = ((argb >> 16) & 0xFF);
int green = ((argb >> 8) & 0xFF);
int blue = (argb & 0xFF);
if (blue >= maskMinBlue && blue <= maskMaxBlue &&
green >= maskMinGreen && green <= maskMaxGreen &&
red >= maskMinRed && red <= maskMaxRed) {
alpha = 0x00;
}
if (alpha != 0xFF) {
argb = bi.getRGB(x, y);
argb &= 0x00FFFFFF;
argb |= ((alpha << 24) & 0xFF000000);
bi.setRGB(x, y, argb);
}
}
}
return bi;
}
public void writeImage(final BufferedImage bufferedImage, final String fileName, String baseOutputPath) {
try {
ImageIO.write(bufferedImage, "PNG",
new File(baseOutputPath + fileName + ".png"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void displayImage(final BufferedImage bufferedImage, final String title) {
if (bufferedImage == null) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final JFrame f = new JFrame("Image - " + title);
f.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
final int width = (int) (bufferedImage.getWidth() * 1.2);
final int height = (int) (bufferedImage.getHeight() * 1.2);
JComponent image = new JComponent() {
@Override
public void paint(Graphics g_) {
super.paint(g_);
g_.setColor(Color.green);
g_.fillRect(0, 0, 10000, 10000);
g_.drawImage(bufferedImage, 0, 0, f);
g_.setColor(Color.red);
g_.drawRect(0, 0, bufferedImage.getWidth() - 2, bufferedImage.getHeight() - 2);
}
};
image.setPreferredSize(new Dimension(bufferedImage.getWidth(), bufferedImage.getHeight()));
image.setSize(new Dimension(width, height));
JPanel test = new JPanel();
test.setPreferredSize(new Dimension(width, height));
JScrollPane tmp = new JScrollPane(image);
tmp.revalidate();
f.setSize(new Dimension(width, height));
f.getContentPane().add(tmp);
f.validate();
f.setVisible(true);
}
});
}
/**
* Utility to build an RGBA buffered image using the specified raster and
* a Transparency.OPAQUE transparency model.
*
* @param wr writable raster of image.
* @return constructed image.
*/
protected BufferedImage makeRGBABufferedImage(WritableRaster wr) {
return makeRGBABufferedImage(wr, Transparency.OPAQUE);
}
/**
* Utility to build an RGBA buffered image using the specified raster and
* transparency type.
*
* @param wr writable raster of image.
* @param transparency any valid Transparency interface type. Bitmask,
* opaque and translucent.
* @return constructed image.
*/
private BufferedImage makeRGBABufferedImage(WritableRaster wr, final int transparency) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] bits = new int[4];
for (int i = 0; i < bits.length; i++) {
bits[i] = 8;
}
ColorModel cm = new ComponentColorModel(
cs, bits, true, false,
transparency,
wr.getTransferType());
return new BufferedImage(cm, wr, false, null);
}
private BufferedImage makeBufferedImage(Raster raster) {
// create a generic colour model and reuse the wraster, intent
// is that this should save quite bit of memory
DirectColorModel colorModel = new DirectColorModel(24,
0x00ff0000, // Red
0x0000ff00, // Green
0x000000ff, // Blue
0x0 // Alpha
);
raster = colorModel.createCompatibleWritableRaster(raster.getWidth(),
raster.getHeight());
return new BufferedImage(colorModel, (WritableRaster) raster, false, null);
}
BufferedImage makeRGBBufferedImage(WritableRaster wr) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] bits = new int[3];
for (int i = 0; i < bits.length; i++)
bits[i] = 8;
ColorModel cm = new ComponentColorModel(
cs, bits, false, false,
ColorModel.OPAQUE,
wr.getTransferType());
return new BufferedImage(cm, wr, false, null);
}
BufferedImage makeGrayBufferedImage(WritableRaster wr) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
int[] bits = new int[1];
for (int i = 0; i < bits.length; i++)
bits[i] = 8;
ColorModel cm = new ComponentColorModel(
cs, bits, false, false,
ColorModel.OPAQUE,
wr.getTransferType());
return new BufferedImage(cm, wr, false, null);
}
// This method returns a buffered image with the contents of an image from
// java almanac
protected BufferedImage makeRGBABufferedImageFromImage(Image image) {
if (image instanceof BufferedImage) {
return (BufferedImage) image;
}
// This code ensures that all the pixels in the image are loaded
image = new ImageIcon(image).getImage();
// Determine if the image has transparent pixels; for this method's
// implementation, see Determining If an Image Has Transparent Pixels
boolean hasAlpha = hasAlpha(image);
// Create a buffered image with a format that's compatible with the screen
BufferedImage bImage = null;
try {
// graphics environment calls can through headless exceptions so
// proceed with caution.
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
// Determine the type of transparency of the new buffered image
int transparency = Transparency.OPAQUE;
if (hasAlpha) {
transparency = Transparency.BITMASK;
}
// Create the buffered image
int width = image.getWidth(null);
int height = image.getHeight(null);
if (width == -1 || height == -1) {
return null;
}
bImage = gc.createCompatibleImage(
image.getWidth(null), image.getHeight(null), transparency);
} catch (HeadlessException e) {
// The system does not have a screen
}
if (bImage == null) {
// Create a buffered image using the default color model
int width = image.getWidth(null);
int height = image.getHeight(null);
if (width == -1 || height == -1) {
return null;
}
if (hasAlpha) {
bImage = createTranslucentCompatibleImage(width, height);
} else {
bImage = createCompatibleImage(width, height);
}
}
// Copy image to buffered image
Graphics g = bImage.createGraphics();
// Paint the image onto the buffered image
g.drawImage(image, 0, 0, null);
g.dispose();
image.flush();
return bImage;
}
// returns true if the specified image has transparent pixels, from
// java almanac
public boolean hasAlpha(Image image) {
// If buffered image, the color model is readily available
if (image instanceof BufferedImage) {
BufferedImage bufferedImage = (BufferedImage) image;
return bufferedImage.getColorModel().hasAlpha();
}
// Use a pixel grabber to retrieve the image's color model;
// grabbing a single pixel is usually sufficient
PixelGrabber pixelGrabber = new PixelGrabber(image, 0, 0, 1, 1, false);
try {
pixelGrabber.grabPixels();
} catch (InterruptedException e) {
// fail quietly
}
// Get the image's color model
ColorModel cm = pixelGrabber.getColorModel();
return cm == null || cm.hasAlpha();
}
/**
* Apply the Decode Array domain for each colour component. Assumes output
* range is 0-1f for each value in out.
*
* @param pixels colour to process by decode
* @param decode decode array for colour space
* @param out return value
* always (2<sup>bitsPerComponent</sup> - 1).
*/
protected void getNormalizedComponents(
byte[] pixels,
float[] decode,
float[] out) {
// interpolate each colour component for the given decode domain.
for (int i = 0; i < pixels.length; i++) {
out[i] = decode[i * 2] + (pixels[i] & 0xff) * decode[(i * 2) + 1];
}
}
protected WritableRaster alterRasterRGBA(WritableRaster wr, BufferedImage smaskImage, BufferedImage maskImage, int[] maskMinRGB, int[] maskMaxRGB) {
Raster smaskRaster = null;
int smaskWidth = 0;
int smaskHeight = 0;
if (smaskImage != null) {
smaskRaster = smaskImage.getRaster();
smaskWidth = smaskRaster.getWidth();
smaskHeight = smaskRaster.getHeight();
}
Raster maskRaster = null;
int maskWidth = 0;
int maskHeight = 0;
if (maskImage != null) {
maskRaster = maskImage.getRaster();
maskWidth = maskRaster.getWidth();
maskHeight = maskRaster.getHeight();
}
int maskMinRed = 0xFF;
int maskMinGreen = 0xFF;
int maskMinBlue = 0xFF;
int maskMaxRed = 0x00;
int maskMaxGreen = 0x00;
int maskMaxBlue = 0x00;
if (maskMinRGB != null && maskMaxRGB != null) {
maskMinRed = maskMinRGB[0];
maskMinGreen = maskMinRGB[1];
maskMinBlue = maskMinRGB[2];
maskMaxRed = maskMaxRGB[0];
maskMaxGreen = maskMaxRGB[1];
maskMaxBlue = maskMaxRGB[2];
}
if (smaskRaster == null && maskRaster == null && (maskMinRGB == null || maskMaxRGB == null))
return null;
int[] rgbaValues = new int[4];
int width = wr.getWidth();
int height = wr.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
wr.getPixel(x, y, rgbaValues);
int red = rgbaValues[0];
int green = rgbaValues[1];
int blue = rgbaValues[2];
int alpha = 0xFF;
if (y < smaskHeight && x < smaskWidth && smaskRaster != null) {
// Alpha equals greyscale value of smask
alpha = (smaskImage.getRGB(x, y)) & 0xFF;//(smaskRaster.getSample(x, y, 0) & 0xFF);
} else if (y < maskHeight && x < maskWidth && maskRaster != null) {
// When making an ImageMask, the alpha channnel is setup so that
// it both works correctly for the ImageMask being painted,
// and also for when it's used here, to determine the alpha
// of an image that it's masking
alpha = (maskImage.getRGB(x, y) >>> 24) & 0xFF; // Extract Alpha from ARGB
} else if (blue >= maskMinBlue && blue <= maskMaxBlue &&
green >= maskMinGreen && green <= maskMaxGreen &&
red >= maskMinRed && red <= maskMaxRed) {
alpha = 0x00;
}
if (alpha != 0xFF) {
rgbaValues[3] = alpha;
wr.setPixel(x, y, rgbaValues);
}
}
}
return wr;
}
/**
* (see 8.9.6.3, "Explicit Masking")
* Explicit Masking algorithm, as of PDF 1.3. The entry in an image dictionary
* may be an image mask, as described under "Stencil Masking", which serves as
* an explicit mask for the primary or base image. The base image and the
* image mask need not have the same resolution (width, height), but since
* all images are defined on the unit square in user space, their boundaries on the
* page will coincide; that is, they will overlay each other.
* <p>
* The image mask indicates indicates which places on the page are to be painted
* and which are to be masked out (left unchanged). Unmasked areas are painted
* with the corresponding portions of the base image; masked areas are not.
*
* @param baseImage base image in which the mask weill be applied to
* @param maskImage image mask to be applied to base image.
*/
BufferedImage applyExplicitMask(BufferedImage baseImage, BufferedImage maskImage) {
// check to see if we need to scale the mask to match the size of the
// base image.
int baseWidth;
int baseHeight;
// check to make sure the mask and the image are the same size.
BufferedImage[] images = scaleImagesToSameSize(baseImage, maskImage);
baseImage = images[0];
maskImage = images[1];
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
baseWidth = baseImage.getWidth();
baseHeight = baseImage.getHeight();
boolean hasAlpha = hasAlpha(baseImage);
BufferedImage argbImage;
if (hasAlpha) {
argbImage = baseImage;
} else {
// aways create a new buffer as we need leave the pevioius image un change for some type of masks.
argbImage = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
int[] srcBand = new int[baseWidth];
int[] maskBnd = new int[baseWidth];
// iterate over each band to apply the mask
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
maskImage.getRGB(0, i, baseWidth, 1, maskBnd, 0, baseWidth);
// apply the soft mask blending
for (int j = 0; j < baseWidth; j++) {
if (maskBnd[j] == 0 || maskBnd[j] == 0xffffff) {
// set the pixel as transparent
maskBnd[j] = 0xff;
} else {
maskBnd[j] = srcBand[j];
}
}
argbImage.setRGB(0, i, baseWidth, 1, maskBnd, 0, baseWidth);
}
baseImage.flush();
baseImage = argbImage;
return baseImage;
}
/**
* Blending mode colour transparency test.
*
* @param baseImage
* @param blendingMode
* @param blendColor
* @return
*/
public BufferedImage applyBlendingMode(BufferedImage baseImage, Name blendingMode, Color blendColor) {
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
int baseWidth = baseImage.getWidth();
int baseHeight = baseImage.getHeight();
boolean hasAlpha = hasAlpha(baseImage);
BufferedImage argbImage;
if (hasAlpha) {
argbImage = baseImage;
} else {
// aways create a new buffer as we need leave the pevioius image un change for some type of masks.
argbImage = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
int[] srcBand = new int[baseWidth];
int[] blendBand = new int[baseWidth];
int blendColorValue = blendColor.getRGB();
// iterate over each band to apply the mask
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
// apply the soft mask blending
for (int j = 0; j < baseWidth; j++) {
if (srcBand[j] == blendColorValue || srcBand[j] == 0xffffff || srcBand[j] == 0xffff) {
// set the pixel as transparent
blendBand[j] = 0xff;
} else {
blendBand[j] = srcBand[j];
}
}
argbImage.setRGB(0, i, baseWidth, 1, blendBand, 0, baseWidth);
}
baseImage.flush();
baseImage = argbImage;
return baseImage;
}
/**
* (see 11.6.5.3, "Soft-Mask Images")
* A subsidiary image XObject defining a soft-mask image that shall be used
* as a source of mask shape or mask opacity values in the transparent imaging
* model. The alpha source parameter in the graphics state determines whether
* the mask values shall be interpreted as shape or opacity.
* <p>
* If present, this entry shall override the current soft mask in the graphics
* state, as well as the image’s Mask entry, if any. However, the other
* transparency-related graphics state parameters—blend mode and alpha
* constant—shall remain in effect. If SMask is absent, the image shall
* have no associated soft mask (although the current soft mask in the
* graphics state may still apply).
*
* @param baseImage base image in which the mask weill be applied to
*/
public BufferedImage applyExplicitSMask(BufferedImage baseImage, BufferedImage sMaskImage) {
// check to make sure the mask and the image are the same size.
BufferedImage[] images = scaleImagesToSameSize(baseImage, sMaskImage);
baseImage = images[0];
sMaskImage = images[1];
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
int baseWidth = baseImage.getWidth();
int baseHeight = baseImage.getHeight();
boolean hasAlpha = hasAlpha(baseImage);
BufferedImage argbImage;
if (hasAlpha) {
argbImage = baseImage;
} else {
// aways create a new buffer as we need leave the pevioius image un change for some type of masks.
argbImage = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
int[] srcBand = new int[baseWidth];
int[] sMaskBand = new int[baseWidth];
int red, alpha, sa;
// iterate over each band to apply the mask
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
sMaskImage.getRGB(0, i, baseWidth, 1, sMaskBand, 0, baseWidth);
// apply the soft mask blending
for (int j = 0; j < baseWidth; j++) {
// take any one of the primaries and apply src image alpha.
red = (sMaskBand[j] >> 16) & 0x000000FF;
alpha = (srcBand[j] >> 24) & 0x000000FF;
sa = ((int) (red * (alpha / 255.0f))) << 24;
// apply the smask value as the alpha value
srcBand[j] = sa | (srcBand[j] & ~0xff000000);
}
argbImage.setRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
}
baseImage.flush();
baseImage = argbImage;
return baseImage;
}
public BufferedImage applyExplicitLuminosity(BufferedImage baseImage, BufferedImage sMaskImage) {
// check to make sure the mask and the image are the same size.
BufferedImage[] images = scaleImagesToSameSize(baseImage, sMaskImage);
baseImage = images[0];
sMaskImage = images[1];
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
int baseWidth = baseImage.getWidth();
int baseHeight = baseImage.getHeight();
boolean hasAlpha = hasAlpha(baseImage);
BufferedImage argbImage;
if (hasAlpha) {
argbImage = baseImage;
} else {
// aways create a new buffer as we need leave the pevioius image un change for some type of masks.
argbImage = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
int[] srcBand = new int[baseWidth];
int[] sMaskBand = new int[baseWidth];
// iterate over each band to apply the mask
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
sMaskImage.getRGB(0, i, baseWidth, 1, sMaskBand, 0, baseWidth);
for (int j = 0; j < baseWidth; j++) {
int red = (srcBand[j] >> 16) & 0x000000FF;
int green = (srcBand[j] >> 8) & 0x000000FF;
int blue = (srcBand[j]) & 0x000000FF;
// colour is used as a degree of masking.
int alpha = (sMaskBand[j] >> 16) & 0x000000FF;
int trans = (sMaskBand[j] >> 24) & 0x000000FF;
int redOut = Math.min(255, red - alpha);
int greenOut = Math.min(255, green - alpha);
int blueOut = Math.min(255, blue - alpha);
// trans = trans - alpha;,
// todo still broken, needs some more examples/
srcBand[j] = trans << 24
| Math.max(0, redOut) << 16
| Math.max(0, greenOut) << 8
| Math.max(0, blueOut);
// if ((red >= 0 || green >= 0 || blue >= 0) && alpha != 0) {
// srcBand[j] = (Math.min(mAlpha, alpha) )<< 24
//// srcBand[j] = mAlpha << 24
// | (redOut & 0xff) << 16
// | (greenOut & 0xff) << 8
// | (blueOut & 0xff);
// }
}
argbImage.setRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
}
baseImage.flush();
baseImage = argbImage;
return baseImage;
}
public BufferedImage applyExplicitOutline(BufferedImage baseImage, BufferedImage sMaskImage) {
// check to make sure the mask and the image are the same size.
BufferedImage[] images = scaleImagesToSameSize(baseImage, sMaskImage);
baseImage = images[0];
sMaskImage = images[1];
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
int baseWidth = baseImage.getWidth();
int baseHeight = baseImage.getHeight();
boolean hasAlpha = hasAlpha(baseImage);
BufferedImage argbImage;
if (hasAlpha) {
argbImage = baseImage;
} else {
// aways create a new buffer as we need leave the pevioius image un change for some type of masks.
argbImage = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
int[] srcBand = new int[baseWidth];
int[] sMaskBand = new int[baseWidth];
// iterate over each band to apply the outline, where the outline is any pixel with alpha.
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
sMaskImage.getRGB(0, i, baseWidth, 1, sMaskBand, 0, baseWidth);
for (int j = 0; j < baseWidth; j++) {
// take any one of the primaries and apply src image alpha.
int alpha = (sMaskBand[j] >> 24) & 0x000000FF;
int sa = alpha << 24;
// apply the smask value as the alpha value
if (sMaskBand[j] == 0)
srcBand[j] = sa
| (srcBand[j] & ~0xff000000);
}
argbImage.setRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
}
baseImage.flush();
baseImage = argbImage;
return baseImage;
}
/**
* Treats the base image as as mask data applying the specified fill colour
* to the flagged bytes and a transparency value otherwise. This method
* creates a new BufferedImage with a transparency model so it will cause
* a memory spike.
*
* @param baseImage masking image.
* @param fill fill value to apply to mask.
* @return masked image encoded with the fill colour and transparency.
*/
BufferedImage applyExplicitMask(BufferedImage baseImage, Color fill) {
// create an
int baseWidth = baseImage.getWidth();
int baseHeight = baseImage.getHeight();
BufferedImage imageMask;
if (hasAlpha(baseImage)) {
imageMask = baseImage;
} else {
imageMask = createTranslucentCompatibleImage(baseWidth, baseHeight);
}
// apply the mask by simply painting white to the base image where
// the mask specified no colour.
int[] srcBand = new int[baseWidth];
int[] maskBnd = new int[baseWidth];
int fillRgb = fill.getRGB();
// iterate over each band to apply the mask
for (int i = 0; i < baseHeight; i++) {
baseImage.getRGB(0, i, baseWidth, 1, srcBand, 0, baseWidth);
imageMask.getRGB(0, i, baseWidth, 1, maskBnd, 0, baseWidth);
// apply the soft mask blending
for (int j = 0; j < baseWidth; j++) {
if (!(srcBand[j] == -1 || srcBand[j] == 0xffffff)) {
maskBnd[j] = fillRgb;
}
}
imageMask.setRGB(0, i, baseWidth, 1, maskBnd, 0, baseWidth);
}
// clean up the old image.
baseImage.flush();
// return the mask.
return imageMask;
}
/**
* Temporarily pulled out the index colur model application for images
* from the raw image decode. This method is only called from JPEG2000
* code for now but will be consolidate as we move to to 5.0
*/
BufferedImage applyIndexColourModel(WritableRaster wr, PColorSpace colourSpace, int bitsPerComponent) {
BufferedImage img = null;
try {
colourSpace.init();
// build out the colour table.
Color[] colors = ((Indexed) colourSpace).accessColorTable();
int colorsLength = (colors == null) ? 0 : colors.length;
int[] cmap = new int[256];
for (int i = 0; i < colorsLength; i++) {
cmap[i] = colors[i].getRGB();
}
for (int i = colorsLength; i < cmap.length; i++) {
cmap[i] = 0xFF000000;
}
// build a new buffer with indexed colour model.
DataBuffer db = wr.getDataBuffer();
// SampleModel sm = new PixelInterleavedSampleModel(db.getDataType(), width, height, 1, width, new int[]{0});
// WritableRaster wr = Raster.createWritableRaster(sm, db, new Point(0, 0));
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, true, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.fine("Indexed colour model initialization interrupted.");
}
return img;
}
BufferedImage proJBig2Decode(ImageInputStream imageInputStream, HashMap decodeParams, Stream globalsStream)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, IOException,
ClassNotFoundException, InstantiationException {
BufferedImage tmpImage;
try {
// ICEpdf-pro has a commercial license of the levigo library but the OS library can use it to if the project
// can comply with levigo's open source licence.
Class<?> levigoJBIG2ImageReaderClass = Class.forName("com.levigo.jbig2.JBIG2ImageReader");
Class<?> jbig2ImageReaderSpiClass = Class.forName("com.levigo.jbig2.JBIG2ImageReaderSpi");
Class<?> jbig2GlobalsClass = Class.forName("com.levigo.jbig2.JBIG2Globals");
Object jbig2ImageReaderSpi = jbig2ImageReaderSpiClass.newInstance();
Constructor levigoJbig2DecoderClassConstructor =
levigoJBIG2ImageReaderClass.getDeclaredConstructor(javax.imageio.spi.ImageReaderSpi.class);
Object levigoJbig2Reader = levigoJbig2DecoderClassConstructor.newInstance(jbig2ImageReaderSpi);
// set the input
Class partypes[] = new Class[1];
partypes[0] = Object.class;
Object arglist[] = new Object[1];
arglist[0] = imageInputStream;
Method setInput =
levigoJBIG2ImageReaderClass.getMethod("setInput", partypes);
setInput.invoke(levigoJbig2Reader, arglist);
// apply decode params if any.
if (decodeParams != null) {
if (globalsStream != null) {
byte[] globals = globalsStream.getDecodedStreamBytes(0);
if (globals != null && globals.length > 0) {
partypes = new Class[1];
partypes[0] = ImageInputStream.class;
arglist = new Object[1];
arglist[0] = ImageIO.createImageInputStream(new ByteArrayInputStream(globals));
Method processGlobals =
levigoJBIG2ImageReaderClass.getMethod("processGlobals", partypes);
Object globalSegments = processGlobals.invoke(levigoJbig2Reader, arglist);
if (globalSegments != null) {
// invoked encoder.setGlobalData(globals);
partypes = new Class[1];
partypes[0] = jbig2GlobalsClass;
arglist = new Object[1];
arglist[0] = globalSegments;
// pass the segment data back into the decoder.
Method setGlobalData =
levigoJBIG2ImageReaderClass.getMethod("setGlobals", partypes);
setGlobalData.invoke(levigoJbig2Reader, arglist);
}
}
}
}
partypes = new Class[1];
partypes[0] = int.class;
arglist = new Object[1];
arglist[0] = 0;
Method read =
levigoJBIG2ImageReaderClass.getMethod("read", partypes);
tmpImage = (BufferedImage) read.invoke(levigoJbig2Reader, arglist);
// call dispose on the reader
Method dispose =
levigoJBIG2ImageReaderClass.getMethod("dispose", (Class<?>[]) null);
dispose.invoke(levigoJbig2Reader);
} finally {
// dispose the stream
if (imageInputStream != null) {
imageInputStream.close();
}
}
return tmpImage;
}
BufferedImage jbig2Decode(byte[] data, HashMap decodeParams, Stream globalsStream) {
BufferedImage tmpImage = null;
try {
Class<?> jbig2DecoderClass = Class.forName("org.jpedal.jbig2.JBIG2Decoder");
// create instance of decoder
Constructor jbig2DecoderClassConstructor =
jbig2DecoderClass.getDeclaredConstructor();
Object jbig2Decoder = jbig2DecoderClassConstructor.newInstance();
// get the decode params form the stream
if (decodeParams != null) {
if (globalsStream != null) {
byte[] globals = globalsStream.getDecodedStreamBytes(0);
if (globals != null && globals.length > 0) {
// invoked ecoder.setGlobalData(globals);
Class partypes[] = new Class[1];
partypes[0] = byte[].class;
Object arglist[] = new Object[1];
arglist[0] = globals;
Method setGlobalData =
jbig2DecoderClass.getMethod("setGlobalData", partypes);
setGlobalData.invoke(jbig2Decoder, arglist);
}
}
}
// decode the data stream, decoder.decodeJBIG2(data);
Class<?> argTypes[] = new Class[]{byte[].class};
Object arglist[] = new Object[]{data};
Method decodeJBIG2 = jbig2DecoderClass.getMethod("decodeJBIG2", argTypes);
decodeJBIG2.invoke(jbig2Decoder, arglist);
// From decoding, memory usage increases more than (width*height/8),
// due to intermediate JBIG2Bitmap objects, used to build the final
// one, still hanging around. Cleanup intermediate data-structures.
// decoder.cleanupPostDecode();
Method cleanupPostDecode = jbig2DecoderClass.getMethod("cleanupPostDecode");
cleanupPostDecode.invoke(jbig2Decoder);
// final try an fetch the image. tmpImage = decoder.getPageAsBufferedImage(0);
argTypes = new Class[]{Integer.TYPE};
arglist = new Object[]{0};
Method getPageAsBufferedImage = jbig2DecoderClass.getMethod("getPageAsBufferedImage", argTypes);
tmpImage = (BufferedImage) getPageAsBufferedImage.invoke(jbig2Decoder, arglist);
} catch (Exception e) {
logger.log(Level.WARNING, "Problem loading JBIG2 image: ", e);
}
return tmpImage;
}
int getJPEGEncoding(byte[] data, int dataLength) {
int jpegEncoding = JPEG_ENC_UNKNOWN_PROBABLY_YCbCr;
boolean foundAPP14 = false;
byte compsTypeFromAPP14 = 0;
boolean foundSOF = false;
int numCompsFromSOF = 0;
boolean foundSOS = false;
int numCompsFromSOS = 0;
int index = 0;
while (true) {
if (index >= dataLength)
break;
if (data[index] != ((byte) 0xFF))
break;
if (foundAPP14 && foundSOF)
break;
byte segmentType = data[index + 1];
index += 2;
if (segmentType == ((byte) 0xD8)) {
//System.out.println("Found SOI (0xD8)");
continue;
}
//System.out.println("Segment: " + Integer.toHexString( ((int)segmentType)&0xFF ));
int length = (((data[index] << 8)) & 0xFF00) + (((int) data[index + 1]) & 0xFF);
//System.out.println(" Length: " + length + " Index: " + index);
// APP14 (Might be Adobe file)
if (segmentType == ((byte) 0xEE)) {
//System.out.println("Found APP14 (0xEE)");
if (length >= 14) {
foundAPP14 = true;
compsTypeFromAPP14 = data[index + 13];
//System.out.println("APP14 format: " + compsTypeFromAPP14);
}
} else if (segmentType == ((byte) 0xC0)) {
foundSOF = true;
//System.out.println("Found SOF (0xC0) Start Of Frame");
//int bitsPerSample = ( ((int)data[index+2]) & 0xFF );
//int imageHeight = ( ((int)(data[index+3] << 8)) & 0xFF00 ) + ( ((int)data[index+4]) & 0xFF );
//int imageWidth = ( ((int)(data[index+5] << 8)) & 0xFF00 ) + ( ((int)data[index+6]) & 0xFF );
numCompsFromSOF = (((int) data[index + 7]) & 0xFF);
//System.out.println(" bitsPerSample: " + bitsPerSample + ", imageWidth: " + imageWidth + ", imageHeight: " + imageHeight + ", numComps: " + numCompsFromSOF);
//int[] compIds = new int[numCompsFromSOF];
//for(int i = 0; i < numCompsFromSOF; i++) {
// compIds[i] = ( ((int)data[index+8+(i*3)]) & 0xff );
// System.out.println(" compId: " + compIds[i]);
//}
} else if (segmentType == ((byte) 0xDA)) {
foundSOS = true;
//System.out.println("Found SOS (0xDA) Start Of Scan");
numCompsFromSOS = (((int) data[index + 2]) & 0xFF);
//int[] compIds = new int[numCompsFromSOS];
//for(int i = 0; i < numCompsFromSOS; i++) {
// compIds[i] = ( ((int)data[index+3+(i*2)]) & 0xff );
// System.out.println(" compId: " + compIds[i]);
//}
}
//System.out.println(" Data: " + org.icepdf.core.util.Utils.convertByteArrayToHexString( data, index+2, Math.min(length-2,dataLength-index-2), true, 20, '\n' ));
index += length;
}
if (foundAPP14 && foundSOF) {
if (compsTypeFromAPP14 == 0) { // 0 seems to indicate no conversion
if (numCompsFromSOF == 1)
jpegEncoding = JPEG_ENC_GRAY;
if (numCompsFromSOF == 3) // Most assume RGB. DesignJava_times_roman_substitution.PDF supports this.
jpegEncoding = JPEG_ENC_RGB;
else if (numCompsFromSOF == 4) // CMYK
jpegEncoding = JPEG_ENC_CMYK;
} else if (compsTypeFromAPP14 == 1) { // YCbCr
jpegEncoding = JPEG_ENC_YCbCr;
} else if (compsTypeFromAPP14 == 2) { // YCCK
jpegEncoding = JPEG_ENC_YCCK;
}
} else if (foundSOS) {
if (numCompsFromSOS == 1)
jpegEncoding = JPEG_ENC_GRAY; // Y
else if (numCompsFromSOS == 3)
jpegEncoding = JPEG_ENC_YCbCr;
else if (numCompsFromSOS == 4)
jpegEncoding = JPEG_ENC_CMYK;
}
return jpegEncoding;
}
BufferedImage applyGrayDecode(BufferedImage rgbImage, int bitsPerComponent, float[] decode) {
WritableRaster wr = rgbImage.getRaster();
int[] cmap = null;
if (bitsPerComponent == 1) {
boolean defaultDecode = 0.0f == decode[0];
cmap = defaultDecode ? GRAY_1_BIT_INDEX_TO_RGB : GRAY_1_BIT_INDEX_TO_RGB_REVERSED;
} else if (bitsPerComponent == 2) {
cmap = GRAY_2_BIT_INDEX_TO_RGB;
} else if (bitsPerComponent == 4) {
cmap = GRAY_4_BIT_INDEX_TO_RGB;
}
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, false, -1, wr.getDataBuffer().getDataType());
rgbImage = new BufferedImage(cm, wr, false, null);
return rgbImage;
}
BufferedImage convertSpaceToRgb(Raster colourRaster, PColorSpace colorSpace, float[] decode) {
BufferedImage rgbImage = makeBufferedImage(colourRaster);
WritableRaster rgbRaster = rgbImage.getRaster();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(colourRaster, (WritableRaster) colourRaster);
// apply colour space
PColorSpaceRasterOp pColorSpaceRasterOp = new PColorSpaceRasterOp(colorSpace, null);
pColorSpaceRasterOp.filter(colourRaster, rgbRaster);
return rgbImage;
}
BufferedImage convertGrayToRgb(Raster grayRaster, float[] decode) {
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(grayRaster, (WritableRaster) grayRaster);
// convert from gray.
GrayRasterOp grayRasterOp = new GrayRasterOp(decode, null);
grayRasterOp.filter(grayRaster, (WritableRaster) grayRaster);
return makeGrayBufferedImage((WritableRaster) grayRaster);
}
/**
* Utility method to convert an CMYK based raster to RGB. The can be
* configured to use to different approaches. The first and more accurate
* method uses a ICC color profile specified by the DeviceCMYK.java class.
* This method can be turned off using the system property
* org.icepdf.core.cmyk.disableICCProfile=true at which point an less
* precise method is used to calculate the resultan RGB color.
*
* @param cmykRaster CMYK base raster to convert to RGB.
* @return Buffered image representation of raster.
*/
BufferedImage convertCmykToRgb(Raster cmykRaster, float[] decode) {
BufferedImage rgbImage = makeBufferedImage(cmykRaster);
if (!DeviceCMYK.isDisableICCCmykColorSpace()) {
WritableRaster rgbRaster = rgbImage.getRaster();
// ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(cmykRaster, (WritableRaster) cmykRaster);
// convert it to rgb
IccCmykRasterOp cmykToRgb = new IccCmykRasterOp(null);
cmykToRgb.filter(cmykRaster, rgbRaster);
return rgbImage;
} else {
WritableRaster rgbRaster = rgbImage.getRaster();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(cmykRaster, (WritableRaster) cmykRaster);
// apply the Old non ICC color conversion code
// convert it to rgb
CMYKRasterOp cmykRasterOp = new CMYKRasterOp(null);
cmykRasterOp.filter(cmykRaster, rgbRaster);
return rgbImage;
}
}
BufferedImage convertYCbCrToRGB(Raster yCbCrRaster, float[] decode) {
BufferedImage rgbImage = makeBufferedImage(yCbCrRaster);
WritableRaster rgbRaster = rgbImage.getRaster();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(yCbCrRaster, (WritableRaster) yCbCrRaster);
// convert to rgb
RasterOp rasterOp;
rasterOp = new YCbCrRasterOp(null);
rasterOp.filter(yCbCrRaster, rgbRaster);
return rgbImage;
}
BufferedImage convertYCCKToRgb(Raster ycckRaster, float[] decode) {
BufferedImage rgbImage = makeBufferedImage(ycckRaster);
if (!DeviceCMYK.isDisableICCCmykColorSpace()) {
WritableRaster rgbRaster = rgbImage.getRaster();
//ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(ycckRaster, (WritableRaster) ycckRaster);
// apply the YCCK to CMYK
YCCKRasterOp ycckRasterOp = new YCCKRasterOp(null);
ycckRasterOp.filter(ycckRaster, (WritableRaster) ycckRaster);
// convert it to rgb
IccCmykRasterOp cmykToRgb = new IccCmykRasterOp(null);
cmykToRgb.filter(ycckRaster, rgbRaster);
return rgbImage;
} else {
WritableRaster rgbRaster = rgbImage.getRaster();
// apply the decode filter
DecodeRasterOp decodeRasterOp = new DecodeRasterOp(decode, null);
decodeRasterOp.filter(ycckRaster, (WritableRaster) ycckRaster);
// apply the YCCK to CMYK
YCCKRasterOp ycckRasterOp = new YCCKRasterOp(null);
ycckRasterOp.filter(ycckRaster, (WritableRaster) ycckRaster);
// apply the Old non ICC color conversion code
// convert it to rgb
CMYKRasterOp cmykRasterOp = new CMYKRasterOp(null);
cmykRasterOp.filter(ycckRaster, rgbRaster);
return rgbImage;
}
}
BufferedImage makeImageWithRasterFromBytes(
PColorSpace colourSpace,
GraphicsState graphicsState,
int width, int height,
int colorSpaceCompCount,
int bitsPerComponent,
boolean imageMask,
float[] decode,
BufferedImage smaskImage,
BufferedImage maskImage,
int[] maskMinRGB, int[] maskMaxRGB,
int maskMinIndex, int maskMaxIndex, byte[] data, int dataLength) {
BufferedImage img = null;
// check if the ICCBased colour has an alternative that
// we might support for decoding with a colorModel.
if (colourSpace instanceof ICCBased) {
ICCBased iccBased = (ICCBased) colourSpace;
if (iccBased.getAlternate() != null) {
// set the alternate as the current
colourSpace = iccBased.getAlternate();
}
}
if (colourSpace instanceof DeviceGray) {
if (imageMask && bitsPerComponent == 1) {
//int data_length = data.length;
DataBuffer db = new DataBufferByte(data, dataLength);
WritableRaster wr = Raster.createPackedRaster(db, width, height,
bitsPerComponent, new Point(0, 0));
// From PDF 1.6 spec, concerning ImageMask and Decode array:
// it in different places different ways.
// [0 1] (the default for an image mask), a sample value of 0 marks
// the page with the current color, and a 1 leaves the previous
// contents unchanged.
// [1 0] Is the reverse
// In case alpha transparency doesn't work, it'll paint white opaquely
boolean defaultDecode = decode[0] == 0.0f;
//int a = Color.white.getRGB();
int a = 0x00FFFFFF; // Clear if alpha supported, else white
Color fill = graphicsState.getFillColor();
int[] cmap = new int[]{
(defaultDecode ? fill.getRGB() : a),
(defaultDecode ? a : fill.getRGB())
};
int transparentIndex = (defaultDecode ? 1 : 0);
IndexColorModel icm = new IndexColorModel(
bitsPerComponent, // the number of bits each pixel occupies
cmap.length, // the size of the color component arrays
cmap, // the array of color components
0, // the starting offset of the first color component
colorSpaceCompCount == 4, // indicates whether alpha values are contained in the cmap array
transparentIndex, // the index of the fully transparent pixel
db.getDataType()); // the data type of the array used to represent pixel values. The data type must be either DataBuffer.TYPE_BYTE or DataBuffer.TYPE_USHORT
img = new BufferedImage(icm, wr, false, null);
} else if (bitsPerComponent == 1 || bitsPerComponent == 2 || bitsPerComponent == 4) {
//int data_length = data.length;
DataBuffer db = new DataBufferByte(data, dataLength);
WritableRaster wr = Raster.createPackedRaster(db, width, height, bitsPerComponent, new Point(0, 0));
int[] cmap;
if (bitsPerComponent == 1) {
boolean defaultDecode = 0.0f == decode[0];
cmap = defaultDecode ? GRAY_1_BIT_INDEX_TO_RGB : GRAY_1_BIT_INDEX_TO_RGB_REVERSED;
} else if (bitsPerComponent == 2) {
cmap = GRAY_2_BIT_INDEX_TO_RGB;
} else {
cmap = GRAY_4_BIT_INDEX_TO_RGB;
}
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, false, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
} else if (bitsPerComponent == 8) {
img = createCompatibleImage(width, height);
// convert image data to rgb, seems to to give better colour tones. ?
int[] dataToRGB = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
copyDecodedStreamBytesIntoGray(data, dataToRGB, decode);
}
} else if (colourSpace instanceof DeviceRGB) {
if (bitsPerComponent == 8) {
boolean usingAlpha = smaskImage != null || maskImage != null || ((maskMinRGB != null) && (maskMaxRGB != null));
int type = usingAlpha ? BufferedImage.TYPE_INT_ARGB :
BufferedImage.TYPE_INT_RGB;
img = new BufferedImage(width, height, type);
// convert image data to rgb, a little out of order maybe?
int[] dataToRGB = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
copyDecodedStreamBytesIntoRGB(data, dataToRGB);
// apply alpha data.
if (usingAlpha) {
img = alterBufferedImageAlpha(img, maskMinRGB, maskMaxRGB);
}
}
} else if (colourSpace instanceof DeviceCMYK) {
// this is slow and doesn't do decode properly, push off parseImage()
// as its quick and we can do the generic decode and masking.
if (false && bitsPerComponent == 8) {
DataBuffer db = new DataBufferByte(data, dataLength);
int[] bandOffsets = new int[colorSpaceCompCount];
for (int i = 0; i < colorSpaceCompCount; i++) {
bandOffsets[i] = i;
}
SampleModel sm = new PixelInterleavedSampleModel(db.getDataType(), width, height, colorSpaceCompCount, colorSpaceCompCount * width, bandOffsets);
WritableRaster wr = Raster.createWritableRaster(sm, db, new Point(0, 0));
//WritableRaster wr = Raster.createInterleavedRaster( db, width, height, colorSpaceCompCount*width, colorSpaceCompCount, bandOffsets, new Point(0,0) );
ColorSpace cs = DeviceCMYK.getIccCmykColorSpace();
int[] bits = new int[colorSpaceCompCount];
for (int i = 0; i < colorSpaceCompCount; i++) {
bits[i] = bitsPerComponent;
}
ColorModel cm = new ComponentColorModel(cs, bits, false, false, ColorModel.OPAQUE, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
}
} else if (colourSpace instanceof Indexed) {
if (bitsPerComponent == 1 || bitsPerComponent == 2 || bitsPerComponent == 4) {
try {
colourSpace.init();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Color[] colors = ((Indexed) colourSpace).accessColorTable();
int[] cmap = new int[(colors == null) ? 0 : colors.length];
for (int i = 0; i < cmap.length; i++) {
cmap[i] = colors[i].getRGB();
}
int cmapMaxLength = 1 << bitsPerComponent;
if (cmap.length > cmapMaxLength) {
int[] cmapTruncated = new int[cmapMaxLength];
System.arraycopy(cmap, 0, cmapTruncated, 0, cmapMaxLength);
cmap = cmapTruncated;
}
boolean usingIndexedAlpha = maskMinIndex >= 0 && maskMaxIndex >= 0;
boolean usingAlpha = smaskImage != null || maskImage != null ||
((maskMinRGB != null) && (maskMaxRGB != null));
if (usingAlpha) {
DataBuffer db = new DataBufferByte(data, dataLength);
WritableRaster wr = Raster.createPackedRaster(db, width, height, bitsPerComponent, new Point(0, 0));
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, true, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
img = alterBufferedImageAlpha(img, maskMinRGB, maskMaxRGB);
} else {
DataBuffer db = new DataBufferByte(data, dataLength);
WritableRaster wr = Raster.createPackedRaster(db, width, height, bitsPerComponent, new Point(0, 0));
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, false, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
}
} else if (bitsPerComponent == 8) {
try {
colourSpace.init();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Color[] colors = ((Indexed) colourSpace).accessColorTable();
int colorsLength = (colors == null) ? 0 : colors.length;
int[] cmap = new int[256];
for (int i = 0; i < colorsLength; i++) {
cmap[i] = colors[i].getRGB();
}
for (int i = colorsLength; i < cmap.length; i++) {
cmap[i] = 0xFF000000;
}
boolean usingIndexedAlpha = maskMinIndex >= 0 && maskMaxIndex >= 0;
boolean usingAlpha = smaskImage != null || maskImage != null || ((maskMinRGB != null) && (maskMaxRGB != null));
if (usingIndexedAlpha) {
for (int i = maskMinIndex; i <= maskMaxIndex; i++) {
cmap[i] = 0x00000000;
}
DataBuffer db = new DataBufferByte(data, dataLength);
SampleModel sm = new PixelInterleavedSampleModel(db.getDataType(), width, height, 1, width, new int[]{0});
WritableRaster wr = Raster.createWritableRaster(sm, db, new Point(0, 0));
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, true, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
} else if (usingAlpha) {
int[] rgbaData = new int[width * height];
// use rgbaData length as an inline image may have a couple extra bytes at the end.
for (int index = 0, max = rgbaData.length; index < max; index++) {
int cmapIndex = (data[index] & 0xFF);
rgbaData[index] = cmap[cmapIndex];
}
DataBuffer db = new DataBufferInt(rgbaData, rgbaData.length);
int[] masks = new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
//SampleModel sm = new SinglePixelPackedSampleModel(
// db.getDataType(), width, height, masks );
WritableRaster wr = Raster.createPackedRaster(db, width, height, width, masks, new Point(0, 0));
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorModel cm = new DirectColorModel(cs, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, false, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
} else {
DataBuffer db = new DataBufferByte(data, dataLength);
SampleModel sm = new PixelInterleavedSampleModel(db.getDataType(), width, height, 1, width, new int[]{0});
WritableRaster wr = Raster.createWritableRaster(sm, db, new Point(0, 0));
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, false, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
}
}
} else if (colourSpace instanceof Separation || colourSpace instanceof CalGray) {
if (colourSpace instanceof CalGray || ((Separation) colourSpace).isNamedColor()) {
DataBuffer db = new DataBufferByte(data, dataLength);
WritableRaster wr = Raster.createPackedRaster(db, width, height, bitsPerComponent, new Point(0, 0));
int[] cmap = null;
if (bitsPerComponent == 1) {
cmap = GRAY_1_BIT_INDEX_TO_RGB;
} else if (bitsPerComponent == 2) {
cmap = GRAY_2_BIT_INDEX_TO_RGB;
} else if (bitsPerComponent == 4) {
cmap = GRAY_4_BIT_INDEX_TO_RGB;
} else if (bitsPerComponent == 8) {
return null;
}
ColorModel cm = new IndexColorModel(bitsPerComponent, cmap.length, cmap, 0, false, -1, db.getDataType());
img = new BufferedImage(cm, wr, false, null);
}
}
// todo add further raw decode types to help speed up image decode
return img;
}
private void copyDecodedStreamBytesIntoRGB(byte[] data, int[] pixels) {
byte[] rgb = new byte[3];
try {
InputStream input = new ByteArrayInputStream(data);
for (int pixelIndex = 0; pixelIndex < pixels.length; pixelIndex++) {
int argb = 0xFF000000;
final int toRead = 3;
int haveRead = 0;
while (haveRead < toRead) {
int currRead = input.read(rgb, haveRead, toRead - haveRead);
if (currRead < 0)
break;
haveRead += currRead;
}
if (haveRead >= 1)
argb |= ((((int) rgb[0]) << 16) & 0x00FF0000);
if (haveRead >= 2)
argb |= ((((int) rgb[1]) << 8) & 0x0000FF00);
if (haveRead >= 3)
argb |= (((int) rgb[2]) & 0x000000FF);
pixels[pixelIndex] = argb;
}
input.close();
} catch (IOException e) {
logger.log(Level.FINE, "Problem copying decoding stream bytes: ", e);
}
}
private void copyDecodedStreamBytesIntoGray(byte[] data, int[] pixels, float[] decode) {
byte[] rgb = new byte[1];
boolean defaultDecode = 0.0f == decode[0];
int Y;
try {
InputStream input = new ByteArrayInputStream(data);
for (int pixelIndex = 0; pixelIndex < pixels.length; pixelIndex++) {
int argb = 0xFF000000;
final int toRead = 1;
int haveRead = 0;
while (haveRead < toRead) {
int currRead = input.read(rgb, haveRead, toRead - haveRead);
if (currRead < 0)
break;
haveRead += currRead;
}
Y = (int) rgb[0] & 0xff;
Y = defaultDecode ? Y : 255 - Y;
argb |= (Y << 16) & 0x00FF0000;
argb |= (Y << 8) & 0x0000FF00;
argb |= (Y & 0x000000FF);
pixels[pixelIndex] = argb;
}
input.close();
} catch (IOException e) {
logger.log(Level.FINE, "Problem copying decoding stream bytes: ", e);
}
}
// default version of createBufferedImage
public BufferedImage createBufferedImage(Image imageIn) {
return createBufferedImage(imageIn, BufferedImage.TYPE_INT_ARGB);
}
private BufferedImage createBufferedImage(Image imageIn, int imageType) {
BufferedImage bufferedImageOut = new BufferedImage(imageIn
.getWidth(null), imageIn.getHeight(null), imageType);
Graphics g = bufferedImageOut.getGraphics();
g.drawImage(imageIn, 0, 0, null);
imageIn.flush();
return bufferedImageOut;
}
/**
* Utility method to scale the two provided images. There are two modes based
* on the system property "". The d
*
* @param baseImage base image that mask will be applied to
* @param maskImage mask image that will be applied to base image.
* @return array of altered baseImage and maskImage, should be same size on
* return.
*/
private BufferedImage[] scaleImagesToSameSize(BufferedImage baseImage,
BufferedImage maskImage) {
int width = baseImage.getWidth();
int height = baseImage.getHeight();
WritableRaster maskRaster = maskImage.getRaster();
int maskWidth = maskRaster.getWidth();
int maskHeight = maskRaster.getHeight();
if (scaleQuality) {
// scale the image to match the image mask.
if (width < maskWidth || height < maskHeight) {
// calculate scale factors.
baseImage = scale(maskWidth, maskHeight, width, height, baseImage);
} else if (width > maskWidth || height > maskHeight) {
// calculate scale factors.
maskImage = scale(width, height, maskWidth, maskHeight, maskImage);
}
return new BufferedImage[]{baseImage, maskImage};
} else {
// scale the mask to match the smaller image.
if (width < maskWidth || height < maskHeight) {
// calculate scale factors.
maskImage = scale(width, height, maskWidth, maskHeight, maskImage);
}
return new BufferedImage[]{baseImage, maskImage};
}
}
private BufferedImage scale(int width, int height, int width2, int height2, BufferedImage image) {
double scaleX = width / (double) width2;
double scaleY = height / (double) height2;
AffineTransform tx = new AffineTransform();
tx.scale(scaleX, scaleY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
BufferedImage bim = op.filter(image, null);
image.flush();
return bim;
}
}