1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.pdmodel.graphics.xobject; 18 19 import java.awt.image.DataBufferByte; 20 import java.awt.image.BufferedImage; 21 import java.awt.image.ColorModel; 22 import java.awt.image.IndexColorModel; 23 import java.awt.image.WritableRaster; 24 import java.io.IOException; 25 import java.io.OutputStream; 26 import java.util.List; 27 28 import javax.imageio.ImageIO; 29 30 import org.apache.commons.logging.Log; 31 import org.apache.commons.logging.LogFactory; 32 import org.apache.pdfbox.cos.COSArray; 33 import org.apache.pdfbox.cos.COSBase; 34 import org.apache.pdfbox.cos.COSDictionary; 35 import org.apache.pdfbox.cos.COSName; 36 import org.apache.pdfbox.pdmodel.common.PDStream; 37 38 import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace; 39 import org.apache.pdfbox.pdmodel.graphics.predictor.PredictorAlgorithm; 40 41 42 43 /** 44 * This class contains a PixelMap Image. 45 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 46 * @author mathiak 47 * @version $Revision: 1.10 $ 48 */ 49 public class PDPixelMap extends PDXObjectImage 50 { 51 /** 52 * Log instance. 53 */ 54 private static final Log log = LogFactory.getLog(PDPixelMap.class); 55 56 private BufferedImage image = null; 57 58 /** 59 * Standard constructor. Basically does nothing. 60 * @param pdStream The stream that holds the pixel map. 61 */ 62 public PDPixelMap(PDStream pdStream) 63 { 64 super(pdStream, "png"); 65 } 66 67 /** 68 * Construct a pixel map image from an AWT image. 69 * 70 * @param doc The PDF document to embed the image in. 71 * @param awtImage The image to read data from. 72 * 73 * @throws IOException If there is an error while embedding this image. 74 */ 75 /* 76 * This method is broken and needs to be implemented, any takers? 77 public PDPixelMap(PDDocument doc, BufferedImage awtImage) throws IOException 78 { 79 super( doc, "png"); 80 image = awtImage; 81 setWidth( image.getWidth() ); 82 setHeight( image.getHeight() ); 83 84 ColorModel cm = image.getColorModel(); 85 ColorSpace cs = cm.getColorSpace(); 86 PDColorSpace pdColorSpace = PDColorSpaceFactory.createColorSpace( doc, cs ); 87 setColorSpace( pdColorSpace ); 88 //setColorSpace( ) 89 90 PDStream stream = getPDStream(); 91 OutputStream output = null; 92 try 93 { 94 output = stream.createOutputStream(); 95 DataBuffer buffer = awtImage.getRaster().getDataBuffer(); 96 if( buffer instanceof DataBufferByte ) 97 { 98 DataBufferByte byteBuffer = (DataBufferByte)buffer; 99 byte[] data = byteBuffer.getData(); 100 output.write( data ); 101 } 102 setBitsPerComponent( cm.getPixelSize() ); 103 } 104 finally 105 { 106 if( output != null ) 107 { 108 output.close(); 109 } 110 } 111 }*/ 112 113 /** 114 * Returns a {@link java.awt.image.BufferedImage} of the COSStream 115 * set in the constructor or null if the COSStream could not be encoded. 116 * 117 * @return {@inheritDoc} 118 * 119 * @throws IOException {@inheritDoc} 120 */ 121 public BufferedImage getRGBImage() throws IOException 122 { 123 if( image != null ) 124 { 125 return image; 126 } 127 128 try 129 { 130 int width = getWidth(); 131 int height = getHeight(); 132 int bpc = getBitsPerComponent(); 133 int predictor = getPredictor(); 134 List filters = getPDStream().getFilters(); 135 ColorModel cm; 136 137 byte[] array = getPDStream().getByteArray(); 138 139 // Get the ColorModel right 140 PDColorSpace colorspace = getColorSpace(); 141 if (colorspace == null) 142 { 143 log.error("getColorSpace() returned NULL. Predictor = " + getPredictor()); 144 return null; 145 } 146 147 if (bpc == 1) 148 { 149 byte[] map = new byte[] {(byte)0x00, (byte)0xff}; 150 cm = new IndexColorModel(1, 2, map, map, map, 1); 151 } 152 else 153 { 154 cm = colorspace.createColorModel( bpc ); 155 } 156 157 log.info("ColorModel: " + cm.toString()); 158 WritableRaster raster = cm.createCompatibleWritableRaster( width, height ); 159 DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); 160 byte[] bufferData = buffer.getData(); 161 162 /** 163 * PDF Spec 1.6 3.3.3 LZW and Flate predictor function 164 * 165 * Basically if predictor > 10 and LZW or Flate is being used then the 166 * predictor is not used. 167 * 168 * "For LZWDecode and FlateDecode, a Predictor value greater than or equal to 10 169 * merely indicates that a PNG predictor is in use; the specific predictor function 170 * used is explicitly encoded in the incoming data. The value of Predictor supplied 171 * by the decoding filter need not match the value used when the data was encoded 172 * if they are both greater than or equal to 10." 173 */ 174 if( predictor < 10 || 175 filters == null || !(filters.contains( COSName.LZW_DECODE.getName()) || 176 filters.contains( COSName.FLATE_DECODE.getName()) ) ) 177 { 178 PredictorAlgorithm filter = PredictorAlgorithm.getFilter(predictor); 179 filter.setWidth(width); 180 filter.setHeight(height); 181 filter.setBpp((bpc * 3) / 8); 182 filter.decode(array, bufferData); 183 } 184 else 185 { 186 System.arraycopy( array, 0,bufferData, 0, 187 (array.length<bufferData.length?array.length: bufferData.length) ); 188 } 189 image = new BufferedImage(cm, raster, false, null); 190 191 return image; 192 } 193 catch (Exception exception) 194 { 195 log.error(exception, exception); 196 //A NULL return is caught in pagedrawer.Invoke.process() so don't re-throw. 197 //Returning the NULL falls through to Phlip Koch's TODO section. 198 return null; 199 } 200 } 201 202 /** 203 * Writes the image as .png. 204 * 205 * {@inheritDoc} 206 */ 207 public void write2OutputStream(OutputStream out) throws IOException 208 { 209 getRGBImage(); 210 if (image!=null) 211 { 212 ImageIO.write(image, "png", out); 213 } 214 } 215 216 /** 217 * DecodeParms is an optional parameter for filters. 218 * 219 * It is provided if any of the filters has nondefault parameters. If there 220 * is only one filter it is a dictionary, if there are multiple filters it 221 * is an array with an entry for each filter. An array entry can hold a null 222 * value if only the default values are used or a dictionary with 223 * parameters. 224 * 225 * @return The decoding parameters. 226 * 227 */ 228 public COSDictionary getDecodeParams() 229 { 230 COSBase decodeParms = getCOSStream().getDictionaryObject("DecodeParms"); 231 if (decodeParms != null) 232 { 233 if (decodeParms instanceof COSDictionary) 234 { 235 return (COSDictionary) decodeParms; 236 } 237 else if (decodeParms instanceof COSArray) 238 { 239 // not implemented yet, which index should we use? 240 return null;//(COSDictionary)((COSArray)decodeParms).get(0); 241 } 242 else 243 { 244 return null; 245 } 246 } 247 return null; 248 } 249 250 /** 251 * A code that selects the predictor algorithm. 252 * 253 * <ul> 254 * <li>1 No prediction (the default value) 255 * <li>2 TIFF Predictor 2 256 * <li>10 PNG prediction (on encoding, PNG None on all rows) 257 * <li>11 PNG prediction (on encoding, PNG Sub on all rows) 258 * <li>12 PNG prediction (on encoding, PNG Up on all rows) 259 * <li>13 PNG prediction (on encoding, PNG Average on all rows) 260 * <li>14 PNG prediction (on encoding, PNG Paeth on all rows) 261 * <li>15 PNG prediction (on encoding, PNG optimum) 262 * </ul> 263 * 264 * Default value: 1. 265 * 266 * @return predictor algorithm code 267 */ 268 public int getPredictor() 269 { 270 COSDictionary decodeParms = getDecodeParams(); 271 if (decodeParms != null) 272 { 273 int i = decodeParms.getInt("Predictor"); 274 if (i != -1) 275 { 276 return i; 277 } 278 } 279 return 1; 280 } 281 }