View Javadoc
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.commons.imaging.formats.png;
18  
19  import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
20  
21  import java.awt.image.BufferedImage;
22  import java.io.IOException;
23  import java.io.InputStream;
24  
25  import org.apache.commons.imaging.ImagingException;
26  import org.apache.commons.imaging.common.Allocator;
27  import org.apache.commons.imaging.formats.png.chunks.PngChunkPlte;
28  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilter;
29  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilterAverage;
30  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilterNone;
31  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilterPaeth;
32  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilterSub;
33  import org.apache.commons.imaging.formats.png.scanlinefilters.ScanlineFilterUp;
34  import org.apache.commons.imaging.formats.png.transparencyfilters.AbstractTransparencyFilter;
35  
36  abstract class AbstractScanExpediter {
37  
38      final int width;
39      final int height;
40      final InputStream is;
41      final BufferedImage bi;
42      final PngColorType pngColorType;
43      final int bitDepth;
44      final int bytesPerPixel;
45      final int bitsPerPixel;
46      final PngChunkPlte pngChunkPlte;
47      final GammaCorrection gammaCorrection;
48      final AbstractTransparencyFilter abstractTransparencyFilter;
49  
50      AbstractScanExpediter(final int width, final int height, final InputStream is, final BufferedImage bi, final PngColorType pngColorType, final int bitDepth,
51              final int bitsPerPixel, final PngChunkPlte pngChunkPLTE, final GammaCorrection gammaCorrection,
52              final AbstractTransparencyFilter abstractTransparencyFilter) {
53          this.width = width;
54          this.height = height;
55          this.is = is;
56          this.bi = bi;
57          this.pngColorType = pngColorType;
58          this.bitDepth = bitDepth;
59          this.bytesPerPixel = this.getBitsToBytesRoundingUp(bitsPerPixel);
60          this.bitsPerPixel = bitsPerPixel;
61          this.pngChunkPlte = pngChunkPLTE;
62          this.gammaCorrection = gammaCorrection;
63          this.abstractTransparencyFilter = abstractTransparencyFilter;
64      }
65  
66      public abstract void drive() throws ImagingException, IOException;
67  
68      final int getBitsToBytesRoundingUp(final int bits) {
69          return (bits + 7) / 8;
70      }
71  
72      byte[] getNextScanline(final InputStream is, final int length, final byte[] prev, final int bytesPerPixel) throws ImagingException, IOException {
73          final int filterType = is.read();
74          if (filterType < 0) {
75              throw new ImagingException("PNG: missing filter type");
76          }
77          if (filterType >= FilterType.values().length) {
78              throw new ImagingException("PNG: unknown filterType: " + filterType);
79          }
80  
81          final byte[] scanline = readBytes("scanline", is, length, "PNG: missing image data");
82  
83          return unfilterScanline(FilterType.values()[filterType], scanline, prev, bytesPerPixel);
84      }
85  
86      final int getPixelArgb(final int alpha, final int red, final int green, final int blue) {
87          return (0xff & alpha) << 24 | (0xff & red) << 16 | (0xff & green) << 8 | (0xff & blue) << 0;
88      }
89  
90      final int getPixelRgb(final int red, final int green, final int blue) {
91          return getPixelArgb(0xff, red, green, blue);
92      }
93  
94      int getRgb(final BitParser bitParser, final int pixelIndexInScanline) throws ImagingException, IOException {
95  
96          switch (pngColorType) {
97          case GREYSCALE: {
98              // 1,2,4,8,16 Each pixel is a grayscale sample.
99              int sample = bitParser.getSampleAsByte(pixelIndexInScanline, 0);
100 
101             if (gammaCorrection != null) {
102                 sample = gammaCorrection.correctSample(sample);
103             }
104 
105             int rgb = getPixelRgb(sample, sample, sample);
106 
107             if (abstractTransparencyFilter != null) {
108                 rgb = abstractTransparencyFilter.filter(rgb, sample);
109             }
110 
111             return rgb;
112 
113         }
114         case TRUE_COLOR: {
115             // 8,16 Each pixel is an R,G,B triple.
116             int red = bitParser.getSampleAsByte(pixelIndexInScanline, 0);
117             int green = bitParser.getSampleAsByte(pixelIndexInScanline, 1);
118             int blue = bitParser.getSampleAsByte(pixelIndexInScanline, 2);
119 
120             int rgb = getPixelRgb(red, green, blue);
121 
122             if (abstractTransparencyFilter != null) {
123                 rgb = abstractTransparencyFilter.filter(rgb, -1);
124             }
125 
126             if (gammaCorrection != null) {
127                 final int alpha = (0xff000000 & rgb) >> 24; // make sure to preserve
128                 // transparency
129                 red = gammaCorrection.correctSample(red);
130                 green = gammaCorrection.correctSample(green);
131                 blue = gammaCorrection.correctSample(blue);
132                 rgb = getPixelArgb(alpha, red, green, blue);
133             }
134 
135             return rgb;
136         }
137         //
138         case INDEXED_COLOR: {
139             // 1,2,4,8 Each pixel is a palette index;
140             // a PLTE chunk must appear.
141             if (pngChunkPlte == null) {
142                 throw new ImagingException("A PLTE chunk is required for an indexed color type.");
143             }
144             final int index = bitParser.getSample(pixelIndexInScanline, 0);
145 
146             int rgb = pngChunkPlte.getRgb(index);
147 
148             if (abstractTransparencyFilter != null) {
149                 rgb = abstractTransparencyFilter.filter(rgb, index);
150             }
151 
152             return rgb;
153         }
154         case GREYSCALE_WITH_ALPHA: {
155             // 8,16 Each pixel is a grayscale sample,
156             // followed by an alpha sample.
157             int sample = bitParser.getSampleAsByte(pixelIndexInScanline, 0);
158             final int alpha = bitParser.getSampleAsByte(pixelIndexInScanline, 1);
159 
160             if (gammaCorrection != null) {
161                 sample = gammaCorrection.correctSample(sample);
162             }
163 
164             return getPixelArgb(alpha, sample, sample, sample);
165         }
166         case TRUE_COLOR_WITH_ALPHA: {
167             // 8,16 Each pixel is an R,G,B triple,
168             int red = bitParser.getSampleAsByte(pixelIndexInScanline, 0);
169             int green = bitParser.getSampleAsByte(pixelIndexInScanline, 1);
170             int blue = bitParser.getSampleAsByte(pixelIndexInScanline, 2);
171             final int alpha = bitParser.getSampleAsByte(pixelIndexInScanline, 3);
172 
173             if (gammaCorrection != null) {
174                 red = gammaCorrection.correctSample(red);
175                 green = gammaCorrection.correctSample(green);
176                 blue = gammaCorrection.correctSample(blue);
177             }
178 
179             return getPixelArgb(alpha, red, green, blue);
180         }
181         default:
182             throw new ImagingException("PNG: unknown color type: " + pngColorType);
183         }
184     }
185 
186     ScanlineFilter getScanlineFilter(final FilterType filterType, final int bytesPerPixel) {
187         switch (filterType) {
188         case NONE:
189             return new ScanlineFilterNone();
190         case SUB:
191             return new ScanlineFilterSub(bytesPerPixel);
192         case UP:
193             return new ScanlineFilterUp();
194         case AVERAGE:
195             return new ScanlineFilterAverage(bytesPerPixel);
196         case PAETH:
197             return new ScanlineFilterPaeth(bytesPerPixel);
198         }
199 
200         return null;
201     }
202 
203     byte[] unfilterScanline(final FilterType filterType, final byte[] src, final byte[] prev, final int bytesPerPixel) throws ImagingException, IOException {
204         final ScanlineFilter filter = getScanlineFilter(filterType, bytesPerPixel);
205 
206         final byte[] dst = Allocator.byteArray(src.length);
207         filter.unfilter(src, dst, prev);
208         return dst;
209     }
210 
211 }