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.filter; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.io.EOFException; 25 import java.util.zip.DeflaterOutputStream; 26 import java.util.zip.InflaterInputStream; 27 import java.util.zip.ZipException; 28 29 import org.apache.commons.logging.Log; 30 import org.apache.commons.logging.LogFactory; 31 import org.apache.pdfbox.cos.COSArray; 32 import org.apache.pdfbox.cos.COSBase; 33 import org.apache.pdfbox.cos.COSDictionary; 34 35 /** 36 * This is the used for the FlateDecode filter. 37 * 38 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 39 * @author Marcel Kammer 40 * @version $Revision: 1.12 $ 41 */ 42 public class FlateFilter implements Filter 43 { 44 45 /** 46 * Log instance. 47 */ 48 private static final Log log = LogFactory.getLog(FlateFilter.class); 49 50 private static final int BUFFER_SIZE = 2048; 51 52 /** 53 * {@inheritDoc} 54 */ 55 public void decode(InputStream compressedData, OutputStream result, COSDictionary options, int filterIndex ) 56 throws IOException 57 { 58 COSBase baseObj = options.getDictionaryObject(new String[] {"DecodeParms","DP"}); 59 COSDictionary dict = null; 60 if( baseObj instanceof COSDictionary ) 61 { 62 dict = (COSDictionary)baseObj; 63 } 64 else if( baseObj instanceof COSArray ) 65 { 66 COSArray paramArray = (COSArray)baseObj; 67 if( filterIndex < paramArray.size() ) 68 { 69 dict = (COSDictionary)paramArray.getObject( filterIndex ); 70 } 71 } 72 else if( baseObj == null ) 73 { 74 //do nothing 75 } 76 else 77 { 78 throw new IOException( "Error: Expected COSArray or COSDictionary and not " 79 + baseObj.getClass().getName() ); 80 } 81 82 83 int predictor = -1; 84 int colors = -1; 85 int bitsPerPixel = -1; 86 int columns = -1; 87 InflaterInputStream decompressor = null; 88 ByteArrayInputStream bais = null; 89 ByteArrayOutputStream baos = null; 90 if (dict!=null) 91 { 92 predictor = dict.getInt("Predictor"); 93 if(predictor > 1) 94 { 95 colors = dict.getInt("Colors"); 96 bitsPerPixel = options.getInt("BitsPerComponent"); 97 columns = dict.getInt("Columns"); 98 } 99 } 100 101 try 102 { 103 // Decompress data to temporary ByteArrayOutputStream 104 decompressor = new InflaterInputStream(compressedData); 105 int amountRead; 106 int mayRead = compressedData.available(); 107 108 if (mayRead > 0) 109 { 110 byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)]; 111 112 // Decode data using given predictor 113 if (predictor==-1 || predictor == 1 || predictor == 10) 114 { 115 try 116 { 117 // decoding not needed 118 while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1) 119 { 120 result.write(buffer, 0, amountRead); 121 } 122 } 123 catch (OutOfMemoryError exception) 124 { 125 // if the stream is corrupt an OutOfMemoryError may occur 126 log.error("Stop reading corrupt stream"); 127 } 128 catch (ZipException exception) 129 { 130 // if the stream is corrupt an OutOfMemoryError may occur 131 log.error("Stop reading corrupt stream"); 132 } 133 catch (EOFException exception) 134 { 135 // if the stream is corrupt an OutOfMemoryError may occur 136 log.error("Stop reading corrupt stream"); 137 } 138 } 139 else 140 { 141 /* 142 * Reverting back to default values 143 */ 144 if( colors == -1 ) 145 { 146 colors = 1; 147 } 148 if( bitsPerPixel == -1 ) 149 { 150 bitsPerPixel = 8; 151 } 152 if( columns == -1 ) 153 { 154 columns = 1; 155 } 156 157 baos = new ByteArrayOutputStream(); 158 while ((amountRead = decompressor.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1) 159 { 160 baos.write(buffer, 0, amountRead); 161 } 162 baos.flush(); 163 164 // Copy data to ByteArrayInputStream for reading 165 bais = new ByteArrayInputStream(baos.toByteArray()); 166 baos.close(); 167 baos = null; 168 169 byte[] decodedData = decodePredictor(predictor, colors, bitsPerPixel, columns, bais); 170 bais.close(); 171 bais = new ByteArrayInputStream(decodedData); 172 173 // write decoded data to result 174 while ((amountRead = bais.read(buffer)) != -1) 175 { 176 result.write(buffer, 0, amountRead); 177 } 178 bais.close(); 179 bais = null; 180 } 181 } 182 183 result.flush(); 184 } 185 finally 186 { 187 if (decompressor != null) 188 { 189 decompressor.close(); 190 } 191 if (bais != null) 192 { 193 bais.close(); 194 } 195 if (baos != null) 196 { 197 baos.close(); 198 } 199 } 200 } 201 202 private byte[] decodePredictor(int predictor, int colors, int bitsPerComponent, int columns, InputStream data) 203 throws IOException 204 { 205 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 206 byte[] buffer = new byte[2048]; 207 208 if (predictor == 1 || predictor == 10) 209 { 210 // No prediction or PNG NONE 211 int i = 0; 212 while ((i = data.read(buffer)) != -1) 213 { 214 baos.write(buffer, 0, i); 215 } 216 } 217 else 218 { 219 // calculate sizes 220 int bpp = (colors * bitsPerComponent + 7) / 8; 221 int rowlength = (columns * colors * bitsPerComponent + 7) / 8 + bpp; 222 byte[] actline = new byte[rowlength]; 223 byte[] lastline = new byte[rowlength];// Initialize lastline with 224 // Zeros according to 225 // PNG-specification 226 boolean done = false; 227 int linepredictor = predictor; 228 229 while (!done && data.available() > 0) 230 { 231 if (predictor == 15) 232 { 233 linepredictor = data.read();// read per line predictor 234 if (linepredictor == -1) 235 { 236 done = true;// reached EOF 237 break; 238 } 239 else 240 { 241 linepredictor += 10; // add 10 to tread value 1 as 11 242 } 243 // (instead of PRED NONE) and 2 244 // as 12 (instead of PRED TIFF) 245 } 246 247 // read line 248 int i = 0; 249 int offset = bpp; 250 while (offset < rowlength && ((i = data.read(actline, offset, rowlength - offset)) != -1)) 251 { 252 offset += i; 253 } 254 255 // Do prediction as specified in PNG-Specification 1.2 256 switch (linepredictor) 257 { 258 case 2:// PRED TIFF SUB 259 /** 260 * @todo decode tiff 261 */ 262 throw new IOException("TIFF-Predictor not supported"); 263 case 11:// PRED SUB 264 for (int p = bpp; p < rowlength; p++) 265 { 266 int sub = actline[p] & 0xff; 267 int left = actline[p - bpp] & 0xff; 268 actline[p] = (byte) (sub + left); 269 } 270 break; 271 case 12:// PRED UP 272 for (int p = bpp; p < rowlength; p++) 273 { 274 int up = actline[p] & 0xff; 275 int prior = lastline[p] & 0xff; 276 actline[p] = (byte) (up + prior); 277 } 278 break; 279 case 13:// PRED AVG 280 for (int p = bpp; p < rowlength; p++) 281 { 282 int avg = actline[p] & 0xff; 283 int left = actline[p - bpp] & 0xff; 284 int up = lastline[p] & 0xff; 285 actline[p] = (byte) (avg + ((left + up) / 2)); 286 } 287 break; 288 case 14:// PRED PAETH 289 for (int p = bpp; p < rowlength; p++) 290 { 291 int paeth = actline[p] & 0xff; 292 int a = actline[p - bpp] & 0xff;// left 293 int b = lastline[p] & 0xff;// upper 294 int c = lastline[p - bpp] & 0xff;// upperleft 295 int value = a + b - c; 296 int absa = Math.abs(value - a); 297 int absb = Math.abs(value - b); 298 int absc = Math.abs(value - c); 299 300 if (absa <= absb && absa <= absc) 301 { 302 actline[p] = (byte) (paeth + absa); 303 } 304 else if (absb <= absc) 305 { 306 actline[p] += (byte) (paeth + absb); 307 } 308 else 309 { 310 actline[p] += (byte) (paeth + absc); 311 } 312 } 313 break; 314 default: 315 break; 316 } 317 318 lastline = (byte[])actline.clone(); 319 baos.write(actline, bpp, actline.length - bpp); 320 } 321 } 322 323 return baos.toByteArray(); 324 } 325 326 /** 327 * {@inheritDoc} 328 */ 329 public void encode(InputStream rawData, OutputStream result, COSDictionary options, int filterIndex ) 330 throws IOException 331 { 332 DeflaterOutputStream out = new DeflaterOutputStream(result); 333 int amountRead = 0; 334 int mayRead = rawData.available(); 335 if (mayRead > 0) 336 { 337 byte[] buffer = new byte[Math.min(mayRead,BUFFER_SIZE)]; 338 while ((amountRead = rawData.read(buffer, 0, Math.min(mayRead,BUFFER_SIZE))) != -1) 339 { 340 out.write(buffer, 0, amountRead); 341 } 342 } 343 out.close(); 344 result.flush(); 345 } 346 }