/* * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * "The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations under * the License. * * The Original Code is ICEpdf 3.0 open source software code, released * May 1st, 2009. The Initial Developer of the Original Code is ICEsoft * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C) * 2004-2011 ICEsoft Technologies Canada, Corp. All Rights Reserved. * * Contributor(s): _____________________. * * Alternatively, the contents of this file may be used under the terms of * the GNU Lesser General Public License Version 2.1 or later (the "LGPL" * License), in which case the provisions of the LGPL License are * applicable instead of those above. If you wish to allow use of your * version of this file only under the terms of the LGPL License and not to * allow others to use your version of this file under the MPL, indicate * your decision by deleting the provisions above and replace them with * the notice and other provisions required by the LGPL License. If you do * not delete the provisions above, a recipient may use your version of * this file under either the MPL or the LGPL License." * */ package org.icepdf.core.pobjects; import com.sun.image.codec.jpeg.JPEGCodec; import com.sun.image.codec.jpeg.JPEGImageDecoder; import org.icepdf.core.io.BitStream; import org.icepdf.core.io.ConservativeSizingByteArrayOutputStream; import org.icepdf.core.io.SeekableInputConstrainedWrapper; import org.icepdf.core.pobjects.filters.*; import org.icepdf.core.pobjects.functions.Function; import org.icepdf.core.pobjects.graphics.*; import org.icepdf.core.tag.Tagger; import org.icepdf.core.util.Defs; import org.icepdf.core.util.ImageCache; import org.icepdf.core.util.Library; import javax.imageio.ImageIO; import javax.imageio.ImageReader; 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.awt.image.renderable.ParameterBlock; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /** * The Stream class is responsible for decoding stream contents and returning * either an images object or a byte array depending on the content. The Stream * object worker method is decode which is responsible for decoding the content * stream, which is if the first step of the rendering process. Once a Stream * is decoded it is either returned as an image object or a byte array that is * then processed further by the ContentParser. * * @since 1.0 */ public class Stream extends Dictionary { private static final Logger logger = Logger.getLogger(Stream.class.toString()); // original byte stream that has not been decoded private SeekableInputConstrainedWrapper streamInput; // Images object created from stream private ImageCache image = null; private final Object imageLock = new Object(); // reference of stream, needed for encryption support private Reference pObjectReference = null; // Inline image, from a content stream private boolean inlineImage; // minimum dimension which will enable image scaling private static boolean scaleImages; static { // decide if large images will be scaled scaleImages = Defs.sysPropertyBoolean("org.icepdf.core.scaleImages", true); } private static final int[] GRAY_1_BIT_INDEX_TO_RGB_REVERSED = new int[]{ 0xFFFFFFFF, 0xFF000000 }; private static final int[] GRAY_1_BIT_INDEX_TO_RGB = new int[]{ 0xFF000000, 0xFFFFFFFF }; private 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 private 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 }; private static final int JPEG_ENC_UNKNOWN_PROBABLY_YCbCr = 0; private static final int JPEG_ENC_RGB = 1; private static final int JPEG_ENC_CMYK = 2; private static final int JPEG_ENC_YCbCr = 3; private static final int JPEG_ENC_YCCK = 4; private static final int JPEG_ENC_GRAY = 5; private static String[] JPEG_ENC_NAMES = new String[]{ "JPEG_ENC_UNKNOWN_PROBABLY_YCbCr", "JPEG_ENC_RGB", "JPEG_ENC_CMYK", "JPEG_ENC_YCbCr", "JPEG_ENC_YCCK", "JPEG_ENC_GRAY" }; /** * Create a new instance of a Stream. * * @param l library containing a hash of all document objects * @param h hashtable of parameters specific to the Stream object. * @param streamInputWrapper Accessor to stream byte data */ public Stream(Library l, Hashtable h, SeekableInputConstrainedWrapper streamInputWrapper) { super(l, h); streamInput = streamInputWrapper; } /** * Sets the PObject referece for this stream. The reference number and * generation is need by the encryption algorithm. */ public void setPObjectReference(Reference reference) { pObjectReference = reference; } /** * Gets the parent PObject reference for this stream. * * @return Reference number of parent PObject. * @see #setPObjectReference(org.icepdf.core.pobjects.Reference) */ public Reference getPObjectReference() { return pObjectReference; } /** * Marks this stream as being constructed from an inline image * definition in a content stream * * @param inlineImage true indicate inline image. */ public void setInlineImage(boolean inlineImage) { this.inlineImage = inlineImage; } /** * @return Whether this stream was constructed from an inline image * definition in a content stream */ public boolean isInlineImage() { return inlineImage; } boolean isImageSubtype() { Object subtype = library.getObject(entries, "Subtype"); return subtype != null && subtype.equals("Image"); } /** * Utility Method to check if the memoryNeeded can be allocated, * and if not, try and free up the needed amount of memory * * @param memoryNeeded amount of memory to check for availability. * @return true if the request number of bytes are free. */ private boolean checkMemory(int memoryNeeded) { return library.memoryManager.checkMemory(memoryNeeded); } /** * Utility method for decoding the byte stream using the decode algorithem * specified by the filter parameter *

* The memory manger is called every time a stream is being decoded with an * estimated size of the decoded stream. Because many of the Filter * algorithms use compression, further research must be done to try and * find the average amount of memory used by each of the algorithms. * * @return inputstream that has been decoded as defined by the streams filters. */ public InputStream getInputStreamForDecodedStreamBytes() { // Make sure that the stream actually has data to decode, if it doesn't // make it null and return. if (streamInput == null || streamInput.getLength() < 1) { return null; } long streamLength = streamInput.getLength(); int memoryNeeded = (int) streamLength; checkMemory(memoryNeeded); streamInput.prepareForCurrentUse(); InputStream input = streamInput; int bufferSize = Math.min(Math.max((int) streamLength, 64), 16 * 1024); input = new java.io.BufferedInputStream(input, bufferSize); if (library.securityManager != null) { input = library.getSecurityManager().getEncryptionInputStream( getPObjectReference(), library.getSecurityManager().getDecryptionKey(), input, true); } // Get the filter name for the encoding type, which can be either // a Name or Vector. Vector filterNames = getFilterNames(); if (filterNames == null) return input; // Decode the stream data based on the filter names. // Loop through the filterNames and apply the filters in the order // in which they where found. for (int i = 0; i < filterNames.size(); i++) { // grab the name of the filter String filterName = filterNames.elementAt(i).toString(); //System.out.println(" Decoding: " + filterName); if (filterName.equals("FlateDecode") || filterName.equals("/Fl") || filterName.equals("Fl")) { input = new FlateDecode(library, entries, input); memoryNeeded *= 2; } else if ( filterName.equals("LZWDecode") || filterName.equals("/LZW") || filterName.equals("LZW")) { input = new LZWDecode(new BitStream(input), library, entries); memoryNeeded *= 2; } else if ( filterName.equals("ASCII85Decode") || filterName.equals("/A85") || filterName.equals("A85")) { input = new ASCII85Decode(input); memoryNeeded *= 2; } else if ( filterName.equals("ASCIIHexDecode") || filterName.equals("/AHx") || filterName.equals("AHx")) { input = new ASCIIHexDecode(input); memoryNeeded /= 2; } else if ( filterName.equals("RunLengthDecode") || filterName.equals("/RL") || filterName.equals("RL")) { input = new RunLengthDecode(input); memoryNeeded *= 2; } else if ( filterName.equals("CCITTFaxDecode") || filterName.equals("/CCF") || filterName.equals("CCF")) { // Leave empty so our else clause works } else if ( filterName.equals("DCTDecode") || filterName.equals("/DCT") || filterName.equals("DCT")) { // Leave empty so our else clause works } else if ( // No short name, since no JBIG2 for inline images filterName.equals("JBIG2Decode")) { // Leave empty so our else clause works } else if ( // No short name, since no JPX for inline images filterName.equals("JPXDecode")) { // Leave empty so our else clause works } else { if (logger.isLoggable(Level.FINE)) { logger.fine("UNSUPPORTED:" + filterName + " " + entries); } } } checkMemory(memoryNeeded); return input; } private byte[] getDecodedStreamBytes() { InputStream input = getInputStreamForDecodedStreamBytes(); if (input == null) return null; try { int outLength = Math.max(1024, (int) streamInput.getLength()); ConservativeSizingByteArrayOutputStream out = new ConservativeSizingByteArrayOutputStream(outLength, library.memoryManager); byte[] buffer = new byte[(outLength > 1024) ? 4096 : 1024]; while (true) { int read = input.read(buffer); if (read <= 0) break; out.write(buffer, 0, read); } out.flush(); out.close(); // removes this thread from current read, pottential entry for other thread input.close(); byte[] ret = out.toByteArray(); return ret; } catch (IOException e) { logger.log(Level.FINE, "Problem decoding stream bytes: ", e); } return null; } /** * This is similar to getDecodedStreamBytes(), except that the returned byte[] * is not necessarily exactly sized, and may be larger. Therefore the returned * Integer gives the actual valid size * * @param presize potencial size to associate with byte array. * @return Object[] { byte[] data, Integer sizeActualData } */ private Object[] getDecodedStreamBytesAndSize(int presize) { InputStream input = getInputStreamForDecodedStreamBytes(); if (input == null) return null; try { int outLength; if (presize > 0) outLength = presize; else outLength = Math.max(1024, (int) streamInput.getLength()); ConservativeSizingByteArrayOutputStream out = new ConservativeSizingByteArrayOutputStream(outLength, library.memoryManager); byte[] buffer = new byte[(outLength > 1024) ? 4096 : 1024]; while (true) { int read = input.read(buffer); if (read <= 0) break; out.write(buffer, 0, read); } out.flush(); out.close(); input.close(); int size = out.size(); boolean trimmed = out.trim(); byte[] data = out.relinquishByteArray(); Object[] ret = new Object[]{data, size}; return ret; } catch (IOException e) { logger.log(Level.FINE, "Problem decoding stream bytes: ", e); } return null; } private void copyDecodedStreamBytesIntoRGB(int[] pixels) { byte[] rgb = new byte[3]; try { InputStream input = getInputStreamForDecodedStreamBytes(); for (int pixelIndex = 0; pixelIndex < pixels.length; pixelIndex++) { int argb = 0xFF000000; if (input != null) { 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; } if (input != null) input.close(); } catch (IOException e) { logger.log(Level.FINE, "Problem copying decoding stream bytes: ", e); } } private boolean shouldUseCCITTFaxDecode() { return containsFilter(new String[]{"CCITTFaxDecode", "/CCF", "CCF"}); } private boolean shouldUseDCTDecode() { return containsFilter(new String[]{"DCTDecode", "/DCT", "DCT"}); } private boolean shouldUseJBIG2Decode() { return containsFilter(new String[]{"JBIG2Decode"}); } private boolean shouldUseJPXDecode() { return containsFilter(new String[]{"JPXDecode"}); } private boolean containsFilter(String[] searchFilterNames) { Vector filterNames = getFilterNames(); if (filterNames == null) return false; for (int i = 0; i < filterNames.size(); i++) { String filterName = filterNames.elementAt(i).toString(); for (String search : searchFilterNames) { if (search.equals(filterName)) { return true; } } } return false; } private Vector getFilterNames() { Vector filterNames = null; Object o = library.getObject(entries, "Filter"); if (o instanceof Name) { filterNames = new Vector(1); filterNames.addElement(o); } else if (o instanceof Vector) { filterNames = (Vector) o; } return filterNames; } private Vector getNormalisedFilterNames() { Vector filterNames = getFilterNames(); if (filterNames == null) return null; for (int i = 0; i < filterNames.size(); i++) { String filterName = filterNames.elementAt(i).toString(); if (filterName.equals("FlateDecode") || filterName.equals("/Fl") || filterName.equals("Fl")) { filterName = "FlateDecode"; } else if ( filterName.equals("LZWDecode") || filterName.equals("/LZW") || filterName.equals("LZW")) { filterName = "LZWDecode"; } else if ( filterName.equals("ASCII85Decode") || filterName.equals("/A85") || filterName.equals("A85")) { filterName = "ASCII85Decode"; } else if ( filterName.equals("ASCIIHexDecode") || filterName.equals("/AHx") || filterName.equals("AHx")) { filterName = "ASCIIHexDecode"; } else if ( filterName.equals("RunLengthDecode") || filterName.equals("/RL") || filterName.equals("RL")) { filterName = "RunLengthDecode"; } else if ( filterName.equals("CCITTFaxDecode") || filterName.equals("/CCF") || filterName.equals("CCF")) { filterName = "CCITTFaxDecode"; } else if ( filterName.equals("DCTDecode") || filterName.equals("/DCT") || filterName.equals("DCT")) { filterName = "DCTDecode"; } // There aren't short names for JBIG2Decode or JPXDecode filterNames.set(i, filterName); } return filterNames; } /** * Despose of references to images and decoded byte streams. * Memory optimization */ public void dispose(boolean cache) { if (streamInput != null) { if (!cache) { try { streamInput.dispose(); } catch (IOException e) { logger.log(Level.FINE, "Error disposing stream.", e); } streamInput = null; }else{ library.removeObject(this.getPObjectReference()); } } synchronized (imageLock) { if (image != null) { image.dispose(cache, (streamInput != null)); if (!cache || !image.isCachedSomehow()) { image = null; } } } } /** * The DCTDecode filter decodes grayscale or color image data that has been * encoded in the JPEG baseline format. Because DCTDecode only deals * with images, the instance of image is update instead of decoded * stream. * * @return buffered images representation of the decoded JPEG data. Null * if the image could not be properly decoded. */ private BufferedImage dctDecode( int width, int height, PColorSpace colourSpace, int bitspercomponent, BufferedImage smaskImage, BufferedImage maskImage, int[] maskMinRGB, int[] maskMaxRGB, Vector decode) { // BIS's buffer size should be equal to mark() size, and greater than data size (below) InputStream input = getInputStreamForDecodedStreamBytes(); // Used to just read 1000, but found a PDF that included thumbnails first final int MAX_BYTES_TO_READ_FOR_ENCODING = 2048; BufferedInputStream bufferedInput = new BufferedInputStream( input, MAX_BYTES_TO_READ_FOR_ENCODING); bufferedInput.mark(MAX_BYTES_TO_READ_FOR_ENCODING); // We don't use the PColorSpace to determine how to decode the JPEG, because it tends to be wrong // Some files say DeviceCMYK, or ICCBased, when neither would work, because it's really YCbCrA // What does work though, is to look into the JPEG headers themself, via getJPEGEncoding() int jpegEncoding = Stream.JPEG_ENC_UNKNOWN_PROBABLY_YCbCr; try { byte[] data = new byte[MAX_BYTES_TO_READ_FOR_ENCODING]; int dataRead = bufferedInput.read(data); bufferedInput.reset(); if (dataRead > 0) jpegEncoding = getJPEGEncoding(data, dataRead); } catch (IOException ioe) { logger.log(Level.FINE, "Problem determining JPEG type: ", ioe); } if (Tagger.tagging) Tagger.tagImage("DCTDecode_JpegEncoding=" + JPEG_ENC_NAMES[jpegEncoding]); //System.out.println("Stream.dctDecode() objectNumber: " + getPObjectReference().getObjectNumber()); //System.out.println("Stream.dctDecode() jpegEncoding: " + JPEG_ENC_NAMES[jpegEncoding]); //System.out.println("Stream.dctDecode() smask: " + smaskImage); //System.out.println("Stream.dctDecode() mask: " + maskImage); //System.out.println("Stream.dctDecode() maskMinRGB: " + maskMinRGB); //System.out.println("Stream.dctDecode() maskMaxRGB: " + maskMaxRGB); //long beginUsedMem = (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()); //long beginTime = System.currentTimeMillis(); BufferedImage tmpImage = null; if (tmpImage == null) { try { //System.out.println("Stream.dctDecode() JPEGImageDecoder"); JPEGImageDecoder imageDecoder = JPEGCodec.createJPEGDecoder(bufferedInput); int bizarreFudge = 64 * 1024 + (int) streamInput.getLength(); checkMemory(width * height * 8 + bizarreFudge); if (jpegEncoding == JPEG_ENC_RGB && bitspercomponent == 8) { //System.out.println("Stream.dctDecode() JPEG_ENC_RGB"); Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); alterRasterRGB2PColorSpace(wr, colourSpace); tmpImage = makeRGBBufferedImage(wr); } else if (jpegEncoding == JPEG_ENC_CMYK && bitspercomponent == 8) { //System.out.println("Stream.dctDecode() JPEG_ENC_CMYK"); Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); alterRasterCMYK2BGRA(wr, smaskImage, maskImage); //TODO Use maskMinRGB, maskMaxRGB or orig comp version here tmpImage = makeRGBABufferedImage(wr); } else if (jpegEncoding == JPEG_ENC_YCbCr && bitspercomponent == 8) { //System.out.println("Stream.dctDecode() JPEG_ENC_YCbCr"); // todo: ContractPal - // this is not looking good // why do they produce output without alpha? // this won't allow alpha with maskImages // go with quick-fix: // repaint results of existing code over new buffered image with alpha // then apply smask or mask using acceptable raster with alpha // this is acceptable with our document sizes Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); // XXX: this method doesn't seem to use masks alterRasterYCbCr2RGB(wr, smaskImage, maskImage, decode, bitspercomponent); if (smaskImage != null) { tmpImage = makeRGBAfromRGBBufferedImage(wr, smaskImage, false); } else if (maskImage != null) { tmpImage = makeRGBAfromRGBBufferedImage(wr, maskImage, true); } else { tmpImage = makeRGBBufferedImage(wr); } } else if (jpegEncoding == JPEG_ENC_YCCK && bitspercomponent == 8) { //System.out.println("Stream.dctDecode() JPEG_ENC_YCCK"); Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); alterRasterYCCK2BGRA(wr, smaskImage, maskImage, decode, bitspercomponent); //TODO Use maskMinRGB, maskMaxRGB or orig comp version here tmpImage = makeRGBABufferedImage(wr); } else if (jpegEncoding == JPEG_ENC_GRAY && bitspercomponent == 8) { //System.out.println("Stream.dctDecode() JPEG_ENC_GRAY"); Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); // In DCTDecode with ColorSpace=DeviceGray, the samples are gray values (2000_SID_Service_Info.core) // In DCTDecode with ColorSpace=Separation, the samples are Y values (45-14550BGermanForWeb.core AKA 4570.core) // Instead of assuming that Separation is special, I'll assume that DeviceGray is if (!(colourSpace instanceof DeviceGray)) { if (Tagger.tagging) Tagger.tagImage("DCTDecode_JpegSubEncoding=Y"); alterRasterY2Gray(wr); //TODO Use smaskImage, maskImage, maskMinRGB, maskMaxRGB or orig comp version here } tmpImage = makeGrayBufferedImage(wr); } else { //System.out.println("Stream.dctDecode() Other"); //tmpImage = imageDecoder.decodeAsBufferedImage(); Raster r = imageDecoder.decodeAsRaster(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); //System.out.println("Stream.dctDecode() EncodedColorID: " + imageDecoder.getJPEGDecodeParam().getEncodedColorID()); if (imageDecoder.getJPEGDecodeParam().getEncodedColorID() == com.sun.image.codec.jpeg.JPEGDecodeParam.COLOR_ID_YCbCrA) { if (Tagger.tagging) Tagger.tagImage("DCTDecode_JpegSubEncoding=YCbCrA"); // YCbCrA, which is slightly different than YCCK alterRasterYCbCrA2RGBA_new(wr, smaskImage, maskImage, decode, bitspercomponent); //TODO Use maskMinRGB, maskMaxRGB or orig comp version here tmpImage = makeRGBABufferedImage(wr); } else { if (Tagger.tagging) Tagger.tagImage("DCTDecode_JpegSubEncoding=YCbCr"); alterRasterYCbCr2RGB(wr, smaskImage, maskImage, decode, bitspercomponent); tmpImage = makeRGBBufferedImage(wr); // special case to handle an smask on an RGB image. In // such a case we need to copy the rgb and soft mask effect // to th new ARGB image. if (smaskImage != null) { BufferedImage argbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); int[] srcBand = new int[width]; int[] sMaskBand = new int[width]; // iterate over each band to apply the mask for (int i = 0; i < height; i++) { tmpImage.getRGB(0, i, width, 1, srcBand, 0, width); smaskImage.getRGB(0, i, width, 1, sMaskBand, 0, width); // apply the soft mask blending for (int j = 0; j < width; j++) { sMaskBand[j] = ((sMaskBand[j] & 0xff) << 24) | (srcBand[j] & ~0xff000000); } argbImage.setRGB(0, i, width, 1, sMaskBand, 0, width); } tmpImage.flush(); tmpImage = argbImage; } } } } catch (Exception e) { logger.log(Level.FINE, "Problem loading JPEG image via JPEGImageDecoder: ", e); } if (tmpImage != null) { if (Tagger.tagging) Tagger.tagImage("HandledBy=DCTDecode_SunJPEGImageDecoder"); } } try { bufferedInput.close(); } catch (IOException e) { logger.log(Level.FINE, "Error closing image stream.", e); } if (tmpImage == null) { try { //System.out.println("Stream.dctDecode() JAI"); Object javax_media_jai_RenderedOp_op = null; try { // Have to reget the data input = getInputStreamForDecodedStreamBytes(); /* com.sun.media.jai.codec.SeekableStream s = com.sun.media.jai.codec.SeekableStream.wrapInputStream( new ByteArrayInputStream(data), true ); ParameterBlock pb = new ParameterBlock(); pb.add( s ); javax.media.jai.RenderedOp op = javax.media.jai.JAI.create( "jpeg", pb ); */ Class ssClass = Class.forName("com.sun.media.jai.codec.SeekableStream"); Method ssWrapInputStream = ssClass.getMethod("wrapInputStream", InputStream.class, Boolean.TYPE); Object com_sun_media_jai_codec_SeekableStream_s = ssWrapInputStream.invoke(null, input, Boolean.TRUE); ParameterBlock pb = new ParameterBlock(); pb.add(com_sun_media_jai_codec_SeekableStream_s); Class jaiClass = Class.forName("javax.media.jai.JAI"); Method jaiCreate = jaiClass.getMethod("create", String.class, ParameterBlock.class); javax_media_jai_RenderedOp_op = jaiCreate.invoke(null, "jpeg", pb); } catch (Exception e) { } if (javax_media_jai_RenderedOp_op != null) { if (jpegEncoding == JPEG_ENC_CMYK && bitspercomponent == 8) { /* * With or without alterRasterCMYK2BGRA(), give blank image Raster r = op.copyData(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); alterRasterCMYK2BGRA( wr ); tmpImage = makeRGBABufferedImage( wr ); */ /* * With alterRasterCMYK2BGRA() colors gibbled, without is blank * Slower, uses more memory, than JPEGImageDecoder BufferedImage img = op.getAsBufferedImage(); WritableRaster wr = img.getRaster(); alterRasterCMYK2BGRA( wr ); tmpImage = img; */ } else if (jpegEncoding == JPEG_ENC_YCCK && bitspercomponent == 8) { /* * This way, with or without alterRasterYCbCrA2BGRA(), give blank image Raster r = op.getData(); WritableRaster wr = (r instanceof WritableRaster) ? (WritableRaster) r : r.createCompatibleWritableRaster(); alterRasterYCbCrA2BGRA( wr ); tmpImage = makeRGBABufferedImage( wr ); */ /* * With alterRasterYCbCrA2BGRA() colors gibbled, without is blank * Slower, uses more memory, than JPEGImageDecoder BufferedImage img = op.getAsBufferedImage(); WritableRaster wr = img.getRaster(); alterRasterYCbCrA2BGRA( wr ); tmpImage = img; */ } else { //System.out.println("Stream.dctDecode() Other"); /* tmpImage = op.getAsBufferedImage(); */ Class roClass = Class.forName("javax.media.jai.RenderedOp"); Method roGetAsBufferedImage = roClass.getMethod("getAsBufferedImage"); tmpImage = (BufferedImage) roGetAsBufferedImage.invoke(javax_media_jai_RenderedOp_op); if (tmpImage != null) { if (Tagger.tagging) Tagger.tagImage("HandledBy=DCTDecode_JAI"); } } } } catch (Exception e) { logger.log(Level.FINE, "Problem loading JPEG image via JAI: ", e); } try { input.close(); } catch (IOException e) { logger.log(Level.FINE, "Problem closing image stream. ", e); } } if (tmpImage == null) { try { //System.out.println("Stream.dctDecode() Toolkit"); byte[] data = getDecodedStreamBytes(); if (data != null) { Image img = Toolkit.getDefaultToolkit().createImage(data); if (img != null) { tmpImage = makeRGBABufferedImageFromImage(img); if (Tagger.tagging) Tagger.tagImage("HandledBy=DCTDecode_ToolkitCreateImage"); } } } catch (Exception e) { logger.log(Level.FINE, "Problem loading JPEG image via Toolkit: ", e); } } //long endUsedMem = (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()); //long endTime = System.currentTimeMillis(); //System.out.println("Mem used: " + (endUsedMem-beginUsedMem) + ",\ttime: " + (endTime-beginTime)); return tmpImage; } /** * Utility method to decode JBig2 images. * * @param width width of image * @param height height of image * @return buffered image of decoded jbig2 image stream. Null if an error * occured during decode. */ private BufferedImage jbig2Decode(int width, int height) { BufferedImage tmpImage = null; try { checkMemory(108 * 1024); org.jpedal.jbig2.JBIG2Decoder decoder = new org.jpedal.jbig2.JBIG2Decoder(); Hashtable decodeparms = library.getDictionary(entries, "DecodeParms"); if (decodeparms != null) { Stream globalsStream = (Stream) library.getObject(decodeparms, "JBIG2Globals"); if (globalsStream != null){ byte[] globals = globalsStream.getDecodedStreamBytes(); if (globals != null && globals.length > 0) { decoder.setGlobalData(globals); globals = null; } } } byte[] data = getDecodedStreamBytes(); checkMemory((width + 8) * height * 22 / 10); // Between 0.5 and 2.2 decoder.decodeJBIG2(data); data = null; // 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(); checkMemory((width + 8) * height / 8); tmpImage = decoder.getPageAsBufferedImage(0); decoder = null; } catch (IOException e) { logger.log(Level.FINE, "Problem loading JBIG2 image: ", e); } catch (org.jpedal.jbig2.JBIG2Exception e) { logger.log(Level.FINE, "Problem loading JBIG2 image: ", e); } return tmpImage; } /** * Creates a new instance of a Dictionary. * * @param library document library. * @param entries dictionary entries. */ public Stream(Library library, Hashtable entries) { super(library, entries); //To change body of overridden methods use File | Settings | File Templates. } /** * Utility method to decode JPEG2000 images. * * @param width width of image. * @param height height of image. * @param colourSpace colour space to apply to image. * @param bitsPerComponent bits used to represent a colour * @param fill fill colour used in last draw operand. * @param maskImage image mask if any, can be null. * @param sMaskImage image smask if any, can be null. * @param maskMinRGB mask minimum rgb value, optional. * @param maskMaxRGB mask maximum rgb value, optional. * @return buffered image of the jpeg2000 image stream. Null if a problem * occurred during the decode. */ private BufferedImage jpxDecode(int width, int height, PColorSpace colourSpace, int bitsPerComponent, Color fill, BufferedImage sMaskImage, BufferedImage maskImage, int[] maskMinRGB, int[] maskMaxRGB) { BufferedImage tmpImage = null; try { // Verify that ImageIO can read JPEG2000 Iterator iterator = ImageIO.getImageReadersByFormatName("JPEG2000"); if (!iterator.hasNext()) { logger.info( "ImageIO missing required plug-in to read JPEG 2000 images. " + "You can download the JAI ImageIO Tools from: " + "https://jai-imageio.dev.java.net/"); return null; } // decode the image. byte[] data = getDecodedStreamBytes(); ImageInputStream imageInputStream = ImageIO.createImageInputStream( new ByteArrayInputStream(data)); tmpImage = ImageIO.read(imageInputStream); // check for an instance of ICCBased, we don't currently support // this colour mode well so we'll used the alternative colour if (colourSpace instanceof ICCBased) { ICCBased iccBased = (ICCBased) colourSpace; if (iccBased.getAlternate() != null) { // set the alternate as the current colourSpace = iccBased.getAlternate(); } // try to process the ICC colour space else { ColorSpace cs = iccBased.getColorSpace(); ColorConvertOp cco = new ColorConvertOp(cs, null); tmpImage = cco.filter(tmpImage, null); } } // apply respective colour models to the JPEG2000 image. if (colourSpace instanceof DeviceRGB && bitsPerComponent == 8) { WritableRaster wr = tmpImage.getRaster(); alterRasterRGB2PColorSpace(wr, colourSpace); tmpImage = makeRGBBufferedImage(wr); } else if (colourSpace instanceof DeviceCMYK && bitsPerComponent == 8) { WritableRaster wr = tmpImage.getRaster(); alterRasterCMYK2BGRA(wr, sMaskImage, maskImage); tmpImage = makeRGBABufferedImage(wr); } else if ((colourSpace instanceof DeviceGray || colourSpace instanceof Indexed) && bitsPerComponent == 8) { WritableRaster wr = tmpImage.getRaster(); tmpImage = makeGrayBufferedImage(wr); } // check for a mask value if (maskImage != null) { applyExplicitMask(tmpImage, maskImage); } } catch (IOException e) { logger.log(Level.FINE, "Problem loading JPEG2000 image: ", e); } return tmpImage; } private static void alterRasterCMYK2BGRA(WritableRaster wr, BufferedImage smaskImage, BufferedImage maskImage) { 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(); } // this convoluted cymk->rgba method is from DeviceCMYK class. float inCyan, inMagenta, inYellow, inBlack; double c, m, y2, aw, ac, am, ay, ar, ag, ab; float outRed, outGreen, outBlue; int rValue, gValue, bValue, alpha; int[] values = 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, values); inCyan = values[0] / 255.0f; inMagenta = values[1] / 255.0f; inYellow = values[2] / 255.0f; // lessen the amount of black, standard 255 fraction is too dark // increasing the denominator has the same affect of lighting up // the image. inBlack = (values[3] / 425.0f); c = Math.min(1.0, inCyan + inBlack); m = Math.min(1.0, inMagenta + inBlack); y2 = Math.min(1.0, inYellow + inBlack); aw = (1 - c) * (1 - m) * (1 - y2); ac = c * (1 - m) * (1 - y2); am = (1 - c) * m * (1 - y2); ay = (1 - c) * (1 - m) * y2; ar = (1 - c) * m * y2; ag = c * (1 - m) * y2; ab = c * m * (1 - y2); outRed = (float) (aw + 0.9137 * am + 0.9961 * ay + 0.9882 * ar); outGreen = (float) (aw + 0.6196 * ac + ay + 0.5176 * ag); outBlue = (float) (aw + 0.7804 * ac + 0.5412 * am + 0.0667 * ar + 0.2118 * ag + 0.4863 * ab); rValue = (int) (outRed * 255); gValue = (int) (outGreen * 255); bValue = (int) (outBlue * 255); alpha = 0xFF; if (y < smaskHeight && x < smaskWidth && smaskRaster != null) alpha = (smaskRaster.getSample(x, y, 0) & 0xFF); else if (y < maskHeight && x < maskWidth && maskRaster != null) { // When making an ImageMask, the alpha channel 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 } values[0] = bValue; values[1] = gValue; values[2] = rValue; values[3] = alpha; wr.setPixel(x, y, values); } } } private static void alterRasterYCbCr2RGB(WritableRaster wr, BufferedImage smaskImage, BufferedImage maskImage, Vector decode, int bitsPerComponent) { float[] values; int width = wr.getWidth(); int height = wr.getHeight(); int maxValue = ((int) Math.pow(2, bitsPerComponent)) - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // apply decode param. values = getNormalizedComponents( (byte[])wr.getDataElements(x,y,null), decode, maxValue); float Y = values[0] * 255; float Cb = values[1] * 255; float Cr = values[2] * 255; float Cr_128 = Cr - 128; float Cb_128 = Cb - 128; float rVal = Y + (1370705 * Cr_128 / 1000000); float gVal = Y - (337633 * Cb_128 / 1000000) - (698001 * Cr_128 / 1000000); float bVal = Y + (1732446 * Cb_128 / 1000000); byte rByte = (rVal < 0) ? (byte) 0 : (rVal > 255) ? (byte) 0xFF : (byte) rVal; byte gByte = (gVal < 0) ? (byte) 0 : (gVal > 255) ? (byte) 0xFF : (byte) gVal; byte bByte = (bVal < 0) ? (byte) 0 : (bVal > 255) ? (byte) 0xFF : (byte) bVal; // apply mask and smask values. values[0] = rByte; values[1] = gByte; values[2] = bByte; wr.setPixel(x, y, values); } } } /** * The basic idea is that we do a fuzzy colour conversion from YCCK to * BGRA. The conversion is not perfect giving a bit of a greenish hue to the * image in question. I've tweaked the core Adobe algorithm ot give slightly * "better" colour representation but it does seem to make red a little light. * @param wr image stream to convert colour space. * @param smaskImage smask used to apply alpha values. * @param maskImage maks image for drop out. */ private static void alterRasterYCCK2BGRA(WritableRaster wr, BufferedImage smaskImage, BufferedImage maskImage, Vector decode, int bitsPerComponent) { 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(); } float[] origValues; double[] rgbaValues = new double[4]; int width = wr.getWidth(); int height = wr.getHeight(); int maxValue = ((int) Math.pow(2, bitsPerComponent)) - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // apply decode param. origValues = getNormalizedComponents( (byte[])wr.getDataElements(x,y,null), decode, maxValue); float Y = origValues[0] * 255; float Cb = origValues[1] * 255; float Cr = origValues[2] * 255; float K = origValues[3] * 255; Y = Y - K; // gives a darker image, instead of just Y. float Cr_128 = Cr - 128; float Cb_128 = Cb - 128; // adobe conversion for CCIR Rec. 601-1 standard. // http://partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf // double rVal = Y + (1.4020 * Cr_128); // double gVal = Y - (.3441363 * Cb_128) - (.71413636 * Cr_128); // double bVal = Y + (1.772 * Cb_128); // intel codecs, http://software.intel.com/sites/products/documentation/hpc/ipp/ippi/ippi_ch6/ch6_color_models.html // Intel IPP conversion for JPEG codec. // double rVal = Y + (1.402 * Cr) - 179.456; // double gVal = Y - (0.34414 * Cb) - (.71413636 * Cr) + 135.45984; // double bVal = Y + (1.772 * Cb) - 226.816; // ICEsoft custom algorithm, results may vary, res are a little // off but over all a better conversion/ then the stoke algorithms. double rVal = Y + (1.4020 * Cr_128); double gVal = Y + (.14414 * Cb_128) + (.11413636 * Cr_128); double bVal = Y + (1.772 * Cb_128); // Intel IPP conversion for ITU-R BT.601 for video // default 16, higher more green and darker blacks, lower less // green hue and lighter blacks. // double kLight = (1.164 * (Y -16 )); // double rVal = kLight + (1.596 * Cr_128); // double gVal = kLight - (0.392 * Cb_128) - (0.813 * Cr_128); // double bVal = kLight + (1.017 * Cb_128); // intel PhotoYCC Color Model [0.1], not a likely candidate for jpegs. // double y1 = Y/255.0; // double c1 = Cb/255.0; // double c2 = Cr/255.0; // double rVal = ((0.981 * y1) + (1.315 * (c2 - 0.537))) *255.0; // double gVal = ((0.981 * y1) - (0.311 * (c1 - 0.612))- (0.669 * (c2 - 0.537))) *255.0; // double bVal = ((0.981 * y1) + (1.601 * (c1 - 0.612))) *255.0; // check the range an convert as needed. byte rByte = (rVal < 0) ? (byte) 0 : (rVal > 255) ? (byte) 0xFF : (byte) rVal; byte gByte = (gVal < 0) ? (byte) 0 : (gVal > 255) ? (byte) 0xFF : (byte) gVal; byte bByte = (bVal < 0) ? (byte) 0 : (bVal > 255) ? (byte) 0xFF : (byte) bVal; int alpha = 0xFF; if (y < smaskHeight && x < smaskWidth && smaskRaster != null){ alpha = (smaskRaster.getSample(x, y, 0) & 0xFF); }else if (y < maskHeight && x < maskWidth && maskRaster != null) { // When making an ImageMask, the alpha channel 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 } rgbaValues[0] = bByte; rgbaValues[1] = gByte; rgbaValues[2] = rByte; rgbaValues[3] = alpha; wr.setPixel(x, y, rgbaValues); } } } /** * Apply the Decode Array domain for each colour component. * @param pixels colour to process by decode * @param decode decode array for colour space * @param xMax domain max for the second point on the interpolation line * always (2bitsPerComponent - 1). * @return */ private static float[] getNormalizedComponents( byte[] pixels, Vector decode, int xMax) { float[] normComponents = new float[pixels.length]; int val; float yMin; float yMax; // interpolate each colour component for the given decode domain. for (int i = 0; i < pixels.length; i++) { val = pixels[i] & 0xff; yMin = ((Number)decode.get(i * 2)).floatValue(); yMax = ((Number)decode.get((i * 2) + 1)).floatValue(); normComponents[i] = Function.interpolate(val, 0, xMax, yMin, yMax); } return normComponents; } private static void alterRasterYCbCrA2RGBA_new(WritableRaster wr, BufferedImage smaskImage, BufferedImage maskImage, Vector decode, int bitsPerComponent) { 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(); } float[] origValues = new float[4]; int[] rgbaValues = new int[4]; int width = wr.getWidth(); int height = wr.getHeight(); int maxValue = ((int) Math.pow(2, bitsPerComponent)) - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { wr.getPixel(x, y, origValues); // apply decode param. // couldn't quite get this one right, doesn't decode // as I would have thought. // origValues = getNormalizedComponents( // (byte[])wr.getDataElements(x,y,null), // decode, // maxValue); float Y = origValues[0]; float Cb = origValues[1]; float Cr = origValues[2]; float K = origValues[3]; Y = K - Y; float Cr_128 = Cr - 128; float Cb_128 = Cb - 128; float rVal = Y + (1370705 * Cr_128 / 1000000); float gVal = Y - (337633 * Cb_128 / 1000000) - (698001 * Cr_128 / 1000000); float bVal = Y + (1732446 * Cb_128 / 1000000); /* // Formula used in JPEG standard. Gives pretty similar results //int rVal = Y + (1402000 * Cr_128/ 1000000); //int gVal = Y - (344140 * Cb_128 / 1000000) - (714140 * Cr_128 / 1000000); //int bVal = Y + (1772000 * Cb_128 / 1000000); */ byte rByte = (rVal < 0) ? (byte) 0 : (rVal > 255) ? (byte) 0xFF : (byte) rVal; byte gByte = (gVal < 0) ? (byte) 0 : (gVal > 255) ? (byte) 0xFF : (byte) gVal; byte bByte = (bVal < 0) ? (byte) 0 : (bVal > 255) ? (byte) 0xFF : (byte) bVal; float alpha = K; if (y < smaskHeight && x < smaskWidth && smaskRaster != null) alpha = (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 } rgbaValues[0] = rByte; rgbaValues[1] = gByte; rgbaValues[2] = bByte; rgbaValues[3] = (int)alpha; wr.setPixel(x, y, rgbaValues); } } } /** * 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 conincide; that is, they will overlay each other. *

* 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. */ private static void 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 = baseImage.getWidth(); int baseHeight = baseImage.getHeight(); int maskWidth = maskImage.getWidth(); int maskHeight = maskImage.getHeight(); if (baseWidth != maskWidth || baseHeight != maskHeight) { // calculate scale factors. double scaleX = baseWidth / (double) maskWidth; double scaleY = baseHeight / (double) maskHeight; // scale the mask to match the base image. AffineTransform tx = new AffineTransform(); tx.scale(scaleX, scaleY); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR); BufferedImage sbim = op.filter(maskImage, null); maskImage.flush(); maskImage = sbim; } // apply the mask by simply painting white to the base image where // the mask specified no colour. for (int y = 0; y < baseHeight; y++) { for (int x = 0; x < baseWidth; x++) { int maskPixel = maskImage.getRGB(x, y); if (maskPixel == -1) { baseImage.setRGB(x, y, Color.WHITE.getRGB()); } } } // int width = maskImage.getWidth(); // int height = maskImage.getHeight(); // final BufferedImage bi = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB); // for (int y = 0; y < height; y++) { // for (int x = 0; x < width; x++) { // if (maskImage.getRGB(x,y) != -1){ // bi.setRGB(x, y, Color.red.getRGB()); // } // else{ // bi.setRGB(x, y, Color.green.getRGB()); // } // } // } // final JFrame f = new JFrame("Test"); // f.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); // // JComponent image = new JComponent() { // @Override // public void paint(Graphics g_) { // super.paint(g_); // g_.drawImage(bi, 0, 0, f); // } // }; // image.setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight())); // image.setSize(new Dimension(bi.getWidth(), bi.getHeight())); // // JPanel test =new JPanel(); // test.setPreferredSize(new Dimension(1200,1200)); // JScrollPane tmp = new JScrollPane(image); // tmp.revalidate(); // f.setSize(new Dimension(800, 800)); // f.getContentPane().add(tmp); // f.validate(); // f.setVisible(true); } private static void alterBufferedImage(BufferedImage bi, 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; int width = bi.getWidth(); int height = bi.getHeight(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { boolean gotARBG = false; int argb = 0; int alpha = 0xFF; if (y < smaskHeight && x < smaskWidth && smaskRaster != null) { // Alpha equals greyscale value of smask alpha = (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 { gotARBG = true; 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) { if (!gotARBG) argb = bi.getRGB(x, y); argb &= 0x00FFFFFF; argb |= ((alpha << 24) & 0xFF000000); bi.setRGB(x, y, argb); } } } } private static void 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; 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 = (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); } } } } private static void alterRasterRGB2PColorSpace(WritableRaster wr, PColorSpace colorSpace) { //System.out.println("alterRasterRGB2PColorSpace() colorSpace: " + colorSpace); if (colorSpace instanceof DeviceRGB) return; float[] values = new float[3]; int[] rgbValues = new int[3]; 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, rgbValues); PColorSpace.reverseInPlace(rgbValues); colorSpace.normaliseComponentsToFloats(rgbValues, values, 255.0f); Color c = colorSpace.getColor(values); rgbValues[0] = c.getRed(); rgbValues[1] = c.getGreen(); rgbValues[2] = c.getBlue(); wr.setPixel(x, y, rgbValues); } } } private static void alterRasterY2Gray(WritableRaster wr) { int[] values = new int[1]; 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, values); int Y = values[0]; byte yByte = (Y < 0) ? (byte) 0 : (Y > 255) ? (byte) 0xFF : (byte) Y; values[0] = 255 - (int) yByte; wr.setPixel(x, y, values); } } } private static 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 } return jpegEncoding; } /** * 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. */ private static 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 static 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()); BufferedImage img = new BufferedImage(cm, wr, false, null); return img; } private static BufferedImage makeRGBAfromRGBBufferedImage(WritableRaster wr, BufferedImage smaskImage, boolean useMaskAlpha) { BufferedImage orig = makeRGBBufferedImage(wr); BufferedImage img; if (smaskImage != null) { img = new BufferedImage(orig.getWidth(), orig.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); img.getGraphics().drawImage(orig, 0,0,null); Raster smaskRaster = smaskImage.getRaster(); int smaskWidth = smaskRaster.getWidth(); int smaskHeight = smaskRaster.getHeight(); wr = img.getRaster(); int alpha; int[] values = 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, values); if (y < smaskHeight && x < smaskWidth) { if (useMaskAlpha) { alpha = (smaskImage.getRGB(x, y) >>> 24) & 0xFF; } else { alpha = (smaskRaster.getSample(x, y, 0) & 0xFF); } values[3] = alpha; } wr.setPixel(x, y, values); } } } else { img = orig; } return img; } private static 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()); BufferedImage img = new BufferedImage(cm, wr, false, null); return img; } private static 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()); BufferedImage img = new BufferedImage(cm, wr, false, null); return img; } // This method returns a buffered image with the contents of an image from // java almanac private 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 type = BufferedImage.TYPE_INT_RGB; if (hasAlpha) { type = BufferedImage.TYPE_INT_ARGB; } int width = image.getWidth(null); int height = image.getHeight(null); if (width == -1 || height == -1) { return null; } bImage = new BufferedImage(width, height, type); } // 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 private static 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(); } /** * CCITT fax decode algorithm, decodes the stream into a valid image * stream that can be used to create a BufferedImage. * * @param width of image * @param height height of image. * @return decoded stream bytes. */ private byte[] ccittFaxDecode(int width, int height) { byte[] streamData = getDecodedStreamBytes(); Hashtable decodeParms = library.getDictionary(entries, "DecodeParms"); float k = library.getFloat(decodeParms, "K"); // default value is always false boolean blackIs1 = getBlackIs1(library, decodeParms); // get value of key if it is available. boolean encodedByteAlign = false; Object encodedByteAlignObject = library.getObject(decodeParms, "EncodedByteAlign"); if (encodedByteAlignObject instanceof Boolean) { encodedByteAlign = (Boolean) encodedByteAlignObject; } int columns = library.getInt(decodeParms, "Columns"); int rows = library.getInt(decodeParms, "Rows"); if (columns == 0) { columns = width; } if (rows == 0) { rows = height; } int size = rows * ((columns + 7) >> 3); byte[] decodedStreamData = new byte[size]; CCITTFaxDecoder decoder = new CCITTFaxDecoder(1, columns, rows); decoder.setAlign(encodedByteAlign); // pick three three possible fax encoding. try { if (k == 0) { decoder.decodeT41D(decodedStreamData, streamData, 0, rows); } else if (k > 0) { decoder.decodeT42D(decodedStreamData, streamData, 0, rows); } else if (k < 0) { decoder.decodeT6(decodedStreamData, streamData, 0, rows); } } catch (Exception e) { logger.warning("Error decoding CCITTFax image k: " + k ); // IText 5.03 doesn't correctly assign a k value for the deocde, // as result we can try one more time using the T6. decoder.decodeT6(decodedStreamData, streamData, 0, rows); } // check the black is value flag, no one likes inverted colours. if (!blackIs1) { // toggle the byte data invert colour, not bit operand. for (int i = 0; i < decodedStreamData.length; i++) { decodedStreamData[i] = (byte) ~decodedStreamData[i]; } } return decodedStreamData; } /** * Gets the decoded Byte stream of the Stream object. * * @return decoded Byte stream */ public byte[] getBytes() { byte[] data = getDecodedStreamBytes(); if (data == null) data = new byte[0]; return data; } /** * Gets the image object for the given resource. This method can optionally * scale an image to reduce the total memory foot print or to increase the * perceived render quality on screen at low zoom levels. * * @param fill color value of image * @param resources resouces containing image reference * @param allowScaling true indicates that the image will be scaled, fals * no scaling. * @return new image object */ // was synchronized, not think it is needed? public BufferedImage getImage(Color fill, Resources resources, boolean allowScaling) { if (Tagger.tagging) Tagger.beginImage(pObjectReference, inlineImage); //String debugFill = (fill == null) ? "null" : Integer.toHexString(fill.getRGB()); //System.out.println("Stream.getImage() for: " + pObjectReference + " fill: " + debugFill + "\n stream: " + this); if (Tagger.tagging) Tagger.tagImage("Filter=" + getNormalisedFilterNames()); // parse colour space PColorSpace colourSpace = null; Object o = library.getObject(entries, "ColorSpace"); if (resources != null) { colourSpace = resources.getColorSpace(o); } //colorSpace = PColorSpace.getColorSpace(library, o); // assume b&w image is no colour space if (colourSpace == null) { colourSpace = new DeviceGray(library, null); if (Tagger.tagging) Tagger.tagImage("ColorSpace_Implicit_DeviceGray"); } if (Tagger.tagging) Tagger.tagImage("ColorSpace=" + colourSpace.getDescription()); boolean imageMask = isImageMask(); if (Tagger.tagging) Tagger.tagImage("ImageMask=" + imageMask); if (imageMask) allowScaling = false; // find out the number of bits in the image int bitspercomponent = library.getInt(entries, "BitsPerComponent"); if (imageMask && bitspercomponent == 0) { bitspercomponent = 1; if (Tagger.tagging) Tagger.tagImage("BitsPerComponent_Implicit_1"); } if (Tagger.tagging) Tagger.tagImage("BitsPerComponent=" + bitspercomponent); // get dimension of image stream int width = library.getInt(entries, "Width"); int height = library.getInt(entries, "Height"); // check for available memory, get colour space and bit count // to better estimate size of image in memory int colorSpaceCompCount = colourSpace.getNumComponents(); // parse decode information Vector decode = (Vector) library.getObject(entries, "Decode"); if (decode == null) { int depth = colourSpace.getNumComponents(); decode = new Vector(depth); // add a decode param for each colour channel. for (int i= 0; i < depth; i++){ decode.addElement(0); decode.addElement(1); } if (Tagger.tagging) Tagger.tagImage("Decode_Implicit_01"); } if (Tagger.tagging) Tagger.tagImage("Decode=" + decode); BufferedImage smaskImage = null; BufferedImage maskImage = null; int[] maskMinRGB = null; int[] maskMaxRGB = null; int maskMinIndex = -1; int maskMaxIndex = -1; Object smaskObj = library.getObject(entries, "SMask"); Object maskObj = library.getObject(entries, "Mask"); if (smaskObj instanceof Stream) { if (Tagger.tagging) Tagger.tagImage("SMaskStream"); Stream smaskStream = (Stream) smaskObj; if (smaskStream.isImageSubtype()) smaskImage = smaskStream.getImage(fill, resources, false); } if (smaskImage != null) { if (Tagger.tagging) Tagger.tagImage("SMaskImage"); allowScaling = false; } if (maskObj != null && smaskImage == null) { if (maskObj instanceof Stream) { if (Tagger.tagging) Tagger.tagImage("MaskStream"); Stream maskStream = (Stream) maskObj; if (maskStream.isImageSubtype()) { maskImage = maskStream.getImage(fill, resources, false); if (maskImage != null) { if (Tagger.tagging) Tagger.tagImage("MaskImage"); } } } else if (maskObj instanceof Vector) { if (Tagger.tagging) Tagger.tagImage("MaskVector"); Vector maskVector = (Vector) maskObj; int[] maskMinOrigCompsInt = new int[colorSpaceCompCount]; int[] maskMaxOrigCompsInt = new int[colorSpaceCompCount]; for (int i = 0; i < colorSpaceCompCount; i++) { if ((i * 2) < maskVector.size()) maskMinOrigCompsInt[i] = ((Number) maskVector.get(i * 2)).intValue(); if ((i * 2 + 1) < maskVector.size()) maskMaxOrigCompsInt[i] = ((Number) maskVector.get(i * 2 + 1)).intValue(); } if (colourSpace instanceof Indexed) { Indexed icolourSpace = (Indexed) colourSpace; Color[] colors = icolourSpace.accessColorTable(); if (colors != null && maskMinOrigCompsInt.length >= 1 && maskMaxOrigCompsInt.length >= 1) { maskMinIndex = maskMinOrigCompsInt[0]; maskMaxIndex = maskMaxOrigCompsInt[0]; if (maskMinIndex >= 0 && maskMinIndex < colors.length && maskMaxIndex >= 0 && maskMaxIndex < colors.length) { Color minColor = colors[maskMinOrigCompsInt[0]]; Color maxColor = colors[maskMaxOrigCompsInt[0]]; maskMinRGB = new int[]{minColor.getRed(), minColor.getGreen(), minColor.getBlue()}; maskMaxRGB = new int[]{maxColor.getRed(), maxColor.getGreen(), maxColor.getBlue()}; } } } else { PColorSpace.reverseInPlace(maskMinOrigCompsInt); PColorSpace.reverseInPlace(maskMaxOrigCompsInt); float[] maskMinOrigComps = new float[colorSpaceCompCount]; float[] maskMaxOrigComps = new float[colorSpaceCompCount]; colourSpace.normaliseComponentsToFloats(maskMinOrigCompsInt, maskMinOrigComps, (1 << bitspercomponent) - 1); colourSpace.normaliseComponentsToFloats(maskMaxOrigCompsInt, maskMaxOrigComps, (1 << bitspercomponent) - 1); Color minColor = colourSpace.getColor(maskMinOrigComps); Color maxColor = colourSpace.getColor(maskMaxOrigComps); PColorSpace.reverseInPlace(maskMinOrigComps); PColorSpace.reverseInPlace(maskMaxOrigComps); maskMinRGB = new int[]{minColor.getRed(), minColor.getGreen(), minColor.getBlue()}; maskMaxRGB = new int[]{maxColor.getRed(), maxColor.getGreen(), maxColor.getBlue()}; } } } BufferedImage img = getImage( colourSpace, fill, width, height, colorSpaceCompCount, bitspercomponent, imageMask, decode, smaskImage, maskImage, maskMinRGB, maskMaxRGB, maskMinIndex, maskMaxIndex); if (img != null) { img = putIntoImageCache(img, width, height, allowScaling); } //String title = "Image: " + getPObjectReference(); //CCITTFax.showRenderedImage(img, title); if (Tagger.tagging) Tagger.endImage(pObjectReference); return img; } /** * Utility to to the image work, the public version pretty much just * parses out image dictionary parameters. This method start the actual * image decoding. * * @param colourSpace colour space of image. * @param fill fill color to aply to image from current graphics context. * @param width width of image. * @param height heigth of image * @param colorSpaceCompCount colour space component count, 1, 3, 4 etc. * @param bitsPerComponent number of bits that represent one component. * @param imageMask boolean flag to use image mask or not. * @param decode decode array, 1,0 or 0,1 can effect colour interpretation. * @param smaskImage smaask image value, optional. * @param maskImage buffered image image mask to apply to decoded image, optional. * @param maskMinRGB max rgb values for the mask * @param maskMaxRGB min rgb values for the mask. * @param maskMinIndex max indexed colour values for the mask. * @param maskMaxIndex min indexed colour values for the mask. * @return buffered image of decoded image stream, null if an error occured. */ private BufferedImage getImage( PColorSpace colourSpace, Color fill, int width, int height, int colorSpaceCompCount, int bitsPerComponent, boolean imageMask, Vector decode, BufferedImage smaskImage, BufferedImage maskImage, int[] maskMinRGB, int[] maskMaxRGB, int maskMinIndex, int maskMaxIndex) { // return cached image if we have already. if (image != null) { // If Stream.dispose(true) was called since last call to Stream.getPageImage(), // then might have to read from file checkMemory(width * height * Math.max(colorSpaceCompCount, 4)); //TODO There's a race-ish condition here // checkMemory() could have called Page.reduceMemory(), which // could have called Stream.dispose(), which could have nulled // out our "image" field BufferedImage img = null; synchronized (imageLock) { if (image != null) { img = image.readImage(); } } if (img != null) { return img; } } // No image cash yet, so we decode known image types. if (image == null) { BufferedImage decodedImage = null; // JPEG writes out image if successful if (shouldUseDCTDecode()) { if (Tagger.tagging) Tagger.tagImage("DCTDecode"); decodedImage = dctDecode(width, height, colourSpace, bitsPerComponent, smaskImage, maskImage, maskMinRGB, maskMaxRGB, decode); } // JBIG2 writes out image if successful else if (shouldUseJBIG2Decode()) { if (Tagger.tagging) Tagger.tagImage("JBIG2Decode"); decodedImage = jbig2Decode(width, height); } // JPEG2000 writes out image if successful else if (shouldUseJPXDecode()) { if (Tagger.tagging) Tagger.tagImage("JPXDecode"); decodedImage = jpxDecode(width, height, colourSpace, bitsPerComponent, fill, smaskImage, maskImage, maskMinRGB, maskMaxRGB); } // CCITTFax load via JAI, if this fails we have other non JAI // CCITTFax code to fall back on. else if (shouldUseCCITTFaxDecode()){ if (Tagger.tagging) Tagger.tagImage("CCITTFaxDecode JAI"); decodedImage = CCITTFax.attemptDeriveBufferedImageFromBytes(this, library, entries, fill); } // finally if we have something then we return it. if (decodedImage != null) { // write tmpImage to the cache return decodedImage; } } byte[] data; int dataLength; // CCITTfax data is raw byte decode. if (shouldUseCCITTFaxDecode()) { if (Tagger.tagging) Tagger.tagImage("CCITTFaxDecode"); data = ccittFaxDecode(width, height); dataLength = data.length; } // no further processing needed we can just get the stream bytes using // normal filter decompression. else { Object[] dataAndSize = getDecodedStreamBytesAndSize( width * height * colourSpace.getNumComponents() * bitsPerComponent / 8); data = (byte[]) dataAndSize[0]; dataLength = (Integer) dataAndSize[1]; } // finally push the bytes though the common image processor if (data != null) { try { BufferedImage decodedImage = makeImageWithRasterFromBytes( colourSpace, fill, width, height, colorSpaceCompCount, bitsPerComponent, imageMask, decode, smaskImage, maskImage, maskMinRGB, maskMaxRGB, maskMinIndex, maskMaxIndex, data, dataLength); // if we have something then we can decode it. if (decodedImage != null) { return decodedImage; } } catch (Exception e) { logger.log(Level.FINE, "Error building image raster.", e); } } // decodes the image stream and returns an image object. Legacy fallback // code, should never get here, put there are always corner cases. . BufferedImage decodedImage = parseImage( width, height, colourSpace, imageMask, fill, bitsPerComponent, decode, data, smaskImage, maskImage, maskMinRGB, maskMaxRGB); return decodedImage; } private BufferedImage makeImageWithRasterFromBytes( PColorSpace colourSpace, Color fill, int width, int height, int colorSpaceCompCount, int bitspercomponent, boolean imageMask, Vector 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) { //System.out.println("Stream.makeImageWithRasterFromBytes() DeviceGray"); if (imageMask && bitspercomponent == 1) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_DeviceGray_1_ImageMask"); //byte[] data = getDecodedStreamBytes(); //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: // todo we nee dot apply the decode method generically as we do // 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 = (0.0f == ((Number) decode.elementAt(0)).floatValue()); //int a = Color.white.getRGB(); int a = 0x00FFFFFF; // Clear if alpha supported, else white 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 true, // 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) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_DeviceGray_124"); //byte[] data = getDecodedStreamBytes(); //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 = null; if (bitspercomponent == 1) { boolean defaultDecode = (0.0f == ((Number) decode.elementAt(0)).floatValue()); 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, db.getDataType()); img = new BufferedImage(cm, wr, false, null); } else if (bitspercomponent == 8) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_DeviceGray_8"); //byte[] data = getDecodedStreamBytes(); //int data_length = data.length; 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)); // apply decode array manually float[] origValues = new float[3]; int maxValue = ((int) Math.pow(2, bitspercomponent)) - 1; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { wr.getPixel(x, y, origValues); origValues = getNormalizedComponents( (byte[])wr.getDataElements(x,y,null), decode, maxValue); float gray = origValues[0] *255; byte rByte = (gray < 0) ? (byte) 0 : (gray > 255) ? (byte) 0xFF : (byte) gray; origValues[0] = rByte; wr.setPixel(x, y, origValues); } } ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); ColorModel cm = new ComponentColorModel(cs, new int[]{bitspercomponent}, false, false, ColorModel.OPAQUE, db.getDataType()); img = new BufferedImage(cm, wr, false, null); } } else if (colourSpace instanceof DeviceRGB) { //System.out.println("Stream.makeImageWithRasterFromBytes() DeviceRGB"); //System.out.println("Mem BEGIN free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); if (bitspercomponent == 8) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_DeviceRGB_8"); //System.out.println("Mem bpc8 free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); checkMemory(width * height * 4); boolean usingAlpha = smaskImage != null || maskImage != null || ((maskMinRGB != null) && (maskMaxRGB != null)); if (Tagger.tagging) Tagger.tagImage("RasterFromBytes_DeviceRGB_8_alpha=" + usingAlpha); int type = usingAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB; img = new BufferedImage(width, height, type); int[] dataToRGB = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); copyDecodedStreamBytesIntoRGB(dataToRGB); if (usingAlpha) alterBufferedImage(img, smaskImage, maskImage, maskMinRGB, maskMaxRGB); } } else if (colourSpace instanceof DeviceCMYK) { //System.out.println("Stream.makeImageWithRasterFromBytes() DeviceCMYK"); //System.out.println("Mem BEGIN free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); if (false && bitspercomponent == 8) {//TODO Look at doing CMYK properly if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_DeviceCMYK_8"); //System.out.println("Mem bpc8 free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); //byte[] data = getDecodedStreamBytes(); //int data_length = data.length; //System.out.println("data_length: " + data_length); //System.out.println("Mem tba free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); DataBuffer db = new DataBufferByte(data, dataLength); //System.out.println("Mem db free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); 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); //System.out.println("Mem sm free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); 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) ); //System.out.println("Mem wr free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); ColorSpace cs = null; // try { //cs = new ColorSpaceCMYK(); //ColorSpace.getInstance( ColorSpace.CS_PYCC );//ColorSpace.TYPE_CMYK ); ///cs = ColorSpaceWrapper.getICCColorSpaceInstance("C:\\Documents and Settings\\Mark Collette\\IdeaProjects\\TestJAI\\CMYK.pf"); // } // catch (Exception csex) { // if (logger.isLoggable(Level.FINE)) { // logger.fine("Problem loading CMYK ColorSpace"); // } // } //System.out.println("Mem cs free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); 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()); //System.out.println("Mem cm free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); img = new BufferedImage(cm, wr, false, null); //System.out.println("Mem img free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); } } else if (colourSpace instanceof Indexed) { //System.out.println("Stream.makeImageWithRasterFromBytes() Indexed"); //System.out.println("Mem BEGIN free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); if (bitspercomponent == 1 || bitspercomponent == 2 || bitspercomponent == 4) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_Indexed_124"); //System.out.println("Mem bpc< free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); //byte[] data = getDecodedStreamBytes(); //int data_length = data.length; //System.out.println(" data_length: " + data_length); //System.out.println("Mem tba free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); DataBuffer db = new DataBufferByte(data, dataLength); //System.out.println("Mem db free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); WritableRaster wr = Raster.createPackedRaster(db, width, height, bitspercomponent, new Point(0, 0)); //System.out.println("Mem wr free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); colourSpace.init(); Color[] colors = ((Indexed) colourSpace).accessColorTable(); //System.out.println("cmap: " + ((colors == null) ? -1 : colors.length)); int[] cmap = new int[(colors == null) ? 0 : colors.length]; for (int i = 0; i < cmap.length; i++) { cmap[i] = colors[i].getRGB(); //System.out.println("cmap["+i+"]: " + Integer.toHexString(cmap[i])); } int cmapMaxLength = 1 << bitspercomponent; if (cmap.length > cmapMaxLength) { int[] cmapTruncated = new int[cmapMaxLength]; System.arraycopy(cmap, 0, cmapTruncated, 0, cmapMaxLength); cmap = cmapTruncated; } //System.out.println(" cmap.length: " + cmap.length); ColorModel cm = new IndexColorModel(bitspercomponent, cmap.length, cmap, 0, false, -1, db.getDataType()); //System.out.println("Mem cm free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); img = new BufferedImage(cm, wr, false, null); //System.out.println("Mem img free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); } else if (bitspercomponent == 8) { if (Tagger.tagging) Tagger.tagImage("HandledBy=RasterFromBytes_Indexed_8"); //System.out.println("Stream.makeImageWithRasterFromBytes() Indexed 8"); //System.out.println("Mem bpc8 free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); //System.out.println("data_length: " + data_length); //System.out.println( org.icepdf.core.util.Utils.convertByteArrayToHexString(data, true) ); //byte[] data = getDecodedStreamBytes(); //int data_length = data.length; colourSpace.init(); Color[] colors = ((Indexed) colourSpace).accessColorTable(); int colorsLength = (colors == null) ? 0 : colors.length; //System.out.println("colorsLength: " + ((colors == null) ? -1 : colors.length)); int[] cmap = new int[256]; for (int i = 0; i < colorsLength; i++) { cmap[i] = colors[i].getRGB(); //System.out.println("cmap["+i+"]: " + Integer.toHexString(cmap[i])); } 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 (Tagger.tagging) Tagger.tagImage("RasterFromBytes_Indexed_8_alpha=" + (usingIndexedAlpha ? "indexed" : (usingAlpha ? "alpha" : "false"))); 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) { checkMemory(width * height * 4); int[] rgbaData = new int[width * height]; for (int index = 0; index < dataLength; 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)); alterRasterRGBA(wr, smaskImage, maskImage, maskMinRGB, maskMaxRGB); 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 { //System.out.println(" data.length: " + data.length); //System.out.println("Mem tba free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); DataBuffer db = new DataBufferByte(data, dataLength); //System.out.println("Mem db free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); SampleModel sm = new PixelInterleavedSampleModel(db.getDataType(), width, height, 1, width, new int[]{0}); WritableRaster wr = Raster.createWritableRaster(sm, db, new Point(0, 0)); //System.out.println("Mem wr free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); //System.out.println(" cmap.length: " + cmap.length); ColorModel cm = new IndexColorModel(bitspercomponent, cmap.length, cmap, 0, false, -1, db.getDataType()); //System.out.println("Mem cm free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); img = new BufferedImage(cm, wr, false, null); //System.out.println("Mem img free: " + Runtime.getRuntime().freeMemory() + ",\ttotal:" + Runtime.getRuntime().totalMemory() + ",\tused: " + (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()) + ",\ttime: " + System.currentTimeMillis()); } } } return img; } /** * Parses the image stream and creates a Java Images object based on the * the given stream and the supporting paramaters. * * @param width dimension of new image * @param height dimension of new image * @param colorSpace colour space of image * @param imageMask true if the image has a imageMask, false otherwise * @param fill colour pased in via graphic state, used to fill in background * @param bitsPerColour number of bits used in a colour * @param decode Decode attribute values from PObject * @return valid java image from the PDF stream */ private BufferedImage parseImage( int width, int height, PColorSpace colorSpace, boolean imageMask, Color fill, int bitsPerColour, Vector decode, byte[] baCCITTFaxData, BufferedImage smaskImage, BufferedImage maskImage, int[] maskMinRGB, int[] maskMaxRGB) { if (Tagger.tagging) Tagger.tagImage("HandledBy=ParseImage"); // store for manipulating bits in image int[] imageBits = new int[width]; // RGB value for colour used as fill for image int fillRGB = fill.getRGB(); // Number of colour components in image, should be 3 for RGB or 4 // for ARGB. int colorSpaceCompCount = colorSpace.getNumComponents(); boolean isDeviceRGB = colorSpace instanceof DeviceRGB; boolean isDeviceGray = colorSpace instanceof DeviceGray; // Max value used to represent a colour, usually 255, min is 0 int maxColourValue = ((1 << bitsPerColour) - 1); int f[] = new int[colorSpaceCompCount]; float ff[] = new float[colorSpaceCompCount]; // image mask from int imageMaskValue = ((Number) decode.elementAt(0)).intValue(); // decode decode. float[] decodeArray = null; if (decode != null) { decodeArray = new float[decode.size()]; for (int i = 0; i < decodeArray.length; i++) { decodeArray[i] = ((Number) decode.elementAt(i)).floatValue(); } } // System.out.println("image size " + width + "x" + height + // "\n\tBitsPerComponent " + bitsPerColour + // "\n\tColorSpace " + colorSpaceCompCount + // "\n\tFill " + fillRGB + // "\n\tDecode " + decode + // "\n\tImageMask " + imageMask + // "\n\tMaxColorValue " + maxColourValue + // "\n\tColourSpace " + colorSpace.toString() + // "\n\tLength " + streamInput.getLength()); // Do a rough check for memory need for this image, we want to over // estimate the memory needed so that we minimize the change of // a out of memory error. Because of image caching, there is no // significant performance hit, as loading the image from file is much // faster then parsing it from the decoded byte stream. int memoryNeeded = (width * height * 4); // ARGB checkMemory(memoryNeeded); // Create the memory hole where where the buffered image will be writen // too, bit by painfull bit. BufferedImage bim = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // create the buffer and get the first series of bytes from the cached // stream BitStream in; if (baCCITTFaxData != null) { in = new BitStream(new ByteArrayInputStream(baCCITTFaxData)); } else { InputStream dataInput = getInputStreamForDecodedStreamBytes(); if (dataInput == null) return null; in = new BitStream(dataInput); } try { // Start encoding bit stream into an image, we work one pixel at // a time, and grap the need bit information for the images // colour space and bits per colour for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // if image has mask apply it if (imageMask) { int bit = in.getBits(bitsPerColour); bit = (bit == imageMaskValue) ? fillRGB : 0x00000000; imageBits[x] = bit; } // other wise start colour bit parsing else { // set some default values int red = 255; int blue = 255; int green = 255; int alpha = 255; // indexed colour if (colorSpaceCompCount == 1) { // get value used for this bit int bit = in.getBits(bitsPerColour); // check decode array if a colour inversion is needed if (decodeArray != null) { // if index 0 > index 1 then we have a need for ainversion if (decodeArray[0] > decodeArray[1]) { bit = (bit == maxColourValue) ? 0x00000000 : maxColourValue; } } if (isDeviceGray) { if (bitsPerColour == 1) bit = GRAY_1_BIT_INDEX_TO_RGB[bit]; else if (bitsPerColour == 2) bit = GRAY_2_BIT_INDEX_TO_RGB[bit]; else if (bitsPerColour == 4) bit = GRAY_4_BIT_INDEX_TO_RGB[bit]; else if (bitsPerColour == 8) { bit = ((bit << 24) | (bit << 16) | (bit << 8) | bit); } imageBits[x] = bit; } else { f[0] = bit; colorSpace.normaliseComponentsToFloats(f, ff, maxColourValue); Color color = colorSpace.getColor(ff); imageBits[x] = color.getRGB(); } } // normal RGB colour else if (colorSpaceCompCount == 3) { // We can have an ICCBased color space that has 3 components, // but where we can't assume it's RGB. // But, when it is DeviceRGB, we don't want the performance hit // of converting the pixels via the PColorSpace, so we'll // break this into the two cases if (isDeviceRGB) { red = in.getBits(bitsPerColour); green = in.getBits(bitsPerColour); blue = in.getBits(bitsPerColour); // combine the colour together imageBits[x] = (alpha << 24) | (red << 16) | (green << 8) | blue; } else { for (int i = 0; i < colorSpaceCompCount; i++) { f[i] = in.getBits(bitsPerColour); } PColorSpace.reverseInPlace(f); colorSpace.normaliseComponentsToFloats(f, ff, maxColourValue); Color color = colorSpace.getColor(ff); imageBits[x] = color.getRGB(); } } // normal aRGB colour, this could use some more // work for optimizing. else if (colorSpaceCompCount == 4) { for (int i = 0; i < colorSpaceCompCount; i++) { f[i] = in.getBits(bitsPerColour); } PColorSpace.reverseInPlace(f); colorSpace.normaliseComponentsToFloats(f, ff, maxColourValue); Color color = colorSpace.getColor(ff); imageBits[x] = color.getRGB(); } // else just set pixel with the default values else { // compine the colour together imageBits[x] = (alpha << 24) | (red << 16) | (green << 8) | blue; } } } // Assign the new bits for this pixel bim.setRGB(0, y, width, 1, imageBits, 0, 1); } // final clean up. in.close(); in = null; if (smaskImage != null || maskImage != null || maskMinRGB != null || maskMaxRGB != null) { WritableRaster wr = bim.getRaster(); alterRasterRGBA(wr, smaskImage, maskImage, maskMinRGB, maskMaxRGB); } } catch (IOException e) { logger.log(Level.FINE, "Error parsing image.", e); } return bim; } private BufferedImage putIntoImageCache(BufferedImage bim, int width, int height, boolean allowScaling) { // create new Image cache if needed. if (image == null) { image = new ImageCache(library); } boolean setIsScaledOnImageCache = false; if (allowScaling && scaleImages && !image.isScaled()) { boolean canScale = checkMemory( Math.max(width, bim.getWidth()) * Math.max(height, bim.getHeight()) * Math.max(4, bim.getColorModel().getPixelSize())); if (canScale) { // To limit the number of image file cache writes this scaling // algorithem works with the buffered image in memory bim = ImageCache.scaleBufferedImage(bim, width, height); setIsScaledOnImageCache = true; } } // The checkMemory call above can make us lose our ImageCache synchronized (imageLock) { if (image == null) { image = new ImageCache(library); } // cache the new image if (setIsScaledOnImageCache) image.setIsScaled(true); image.setImage(bim); // read the image from the cache and return to caller bim = image.readImage(); } return bim; } /** * Does the image have an ImageMask. */ public boolean isImageMask() { Object o = library.getObject(entries, "ImageMask"); return (o != null) ? (o.toString().equals("true") ? true : false) : false; } public boolean getBlackIs1(Library library, Hashtable decodeParmsDictionary) { Boolean blackIs1 = getBlackIs1OrNull(library, decodeParmsDictionary); if (blackIs1 != null) return blackIs1.booleanValue(); return false; } /** * If BlackIs1 was not specified, then return null, instead of the * default value of false, so we can tell if it was given or not */ public Boolean getBlackIs1OrNull(Library library, Hashtable decodeParmsDictionary) { Object blackIs1Obj = library.getObject(decodeParmsDictionary, "BlackIs1"); if (blackIs1Obj != null) { if (blackIs1Obj instanceof Boolean) { return (Boolean) blackIs1Obj; } else if (blackIs1Obj instanceof String) { String blackIs1String = (String) blackIs1Obj; if (blackIs1String.equalsIgnoreCase("true")) return Boolean.TRUE; else if (blackIs1String.equalsIgnoreCase("t")) return Boolean.TRUE; else if (blackIs1String.equals("1")) return Boolean.TRUE; else if (blackIs1String.equalsIgnoreCase("false")) return Boolean.FALSE; else if (blackIs1String.equalsIgnoreCase("f")) return Boolean.FALSE; else if (blackIs1String.equals("0")) return Boolean.FALSE; } } return null; } /** * Allow access to seakable intput so that pattern object can * be corrrectly created. * * @return stream istnaces SeekableInputConstrainedWrapper */ public SeekableInputConstrainedWrapper getStreamInput() { return streamInput; } /** * Return a string description of the object. Primarly used for debugging. */ public String toString() { StringBuilder sb = new StringBuilder(64); sb.append("STREAM= "); sb.append(entries); if (getPObjectReference() != null) { sb.append(" "); sb.append(getPObjectReference()); } return sb.toString(); } }