001    /*
002     * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
003     *
004     * http://uio.imagero.com
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *
009     *  o Redistributions of source code must retain the above copyright notice,
010     *    this list of conditions and the following disclaimer.
011     *
012     *  o Redistributions in binary form must reproduce the above copyright notice,
013     *    this list of conditions and the following disclaimer in the documentation
014     *    and/or other materials provided with the distribution.
015     *
016     *  o Neither the name of Andrey Kuznetsov nor the names of
017     *    its contributors may be used to endorse or promote products derived
018     *    from this software without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
023     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
027     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
028     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
029     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
030     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031     */
032    
033    package com.imagero.uio.io;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.ByteArrayOutputStream;
037    import java.io.File;
038    import java.io.FileInputStream;
039    import java.io.FileOutputStream;
040    import java.io.FilterInputStream;
041    import java.io.IOException;
042    import java.io.InputStream;
043    import java.io.OutputStream;
044    
045    
046    /**
047     * remove all App blocks from jpeg file
048     * <br>
049     * how to use:
050     * <br>
051     * <pre>
052     *              //read data from file and save filtered data to another file
053     *              File fs = new File("source.jpg");
054     *              File fd = new File("dest.jpg");
055     *              JpegFilterInputStream.filter(fs, fd);
056     *
057     *              or
058     *
059     *              //filter data in byte array
060     *              byte [] source = ...
061     *              byte [] dest = JpegFilterInputStream.filter(source);
062     *
063     *              or
064     *
065     *              //filter data from InputStream
066     *              InputStream in;
067     *              OutputStream out;
068     *              JpegFilterInputStream.filter(in, out);
069     *
070     *      or
071     *
072     *      //just wrap InputStream
073     *      JpegFilterInputStream jfis = new JpegFilterInputStream(in);
074     *
075     * </pre>
076     * @author Andrey Kuznetsov
077     */
078    public class JpegFilterInputStream extends FilterInputStream {
079    
080        public static final int APP_0 = 0xE0;
081        public static final int APP_1 = 0xE1;
082        public static final int APP_2 = 0xE2;
083        public static final int APP_3 = 0xE3;
084        public static final int APP_4 = 0xE4;
085        public static final int APP_5 = 0xE5;
086        public static final int APP_6 = 0xE6;
087        public static final int APP_7 = 0xE7;
088        public static final int APP_8 = 0xE8;
089        public static final int APP_9 = 0xE9;
090        public static final int APP_10 = 0xEA;
091        public static final int APP_11 = 0xEB;
092        public static final int APP_12 = 0xEC;
093        public static final int APP_13 = 0xED;
094        public static final int APP_14 = 0xEE;
095        public static final int APP_15 = 0xEF;
096    
097        public static final int[] allMarkers = new int[]{
098            APP_0, APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
099            APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
100        };
101    
102        public static final int[] defaultMarkers = new int[]{
103            APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
104            APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
105        };
106    
107        public static void filterFile(File src, File dest) throws IOException {
108            FileInputStream in = new FileInputStream(src);
109            FileOutputStream out = new FileOutputStream(dest);
110    
111            filter(in, out);
112            IOutils.closeStream(out);
113            IOutils.closeStream(in);
114        }
115    
116        /**
117         * filter out all markers (except App0)
118         * @param in InputStream
119         * @param out
120         * @throws java.io.IOException
121         */
122        public static void filter(InputStream in, OutputStream out) throws IOException {
123            filter(in, out, defaultMarkers);
124        }
125    
126        public static void filter(InputStream in0, OutputStream out, int[] markers) throws IOException {
127            JpegFilterInputStream in = new JpegFilterInputStream(in0, markers);
128            int a = 0;
129            while (a >= 0) {
130                a = in.read();
131                out.write(a);
132            }
133        }
134    
135        /**
136         * filter out all markers (except App0)
137         * @param data
138         * @return byte array
139         * @throws IOException
140         */
141        public static byte[] filter(byte[] data) throws IOException {
142            return filter(data, defaultMarkers);
143        }
144    
145        public static byte[] filter(byte[] data, int[] markers) throws IOException {
146            final ByteArrayInputStream in0 = new ByteArrayInputStream(data);
147            ByteArrayOutputStream bout = new ByteArrayOutputStream();
148            filter(in0, bout, markers);
149            return bout.toByteArray();
150        }
151    
152        int[] markers;
153    
154        /**
155         * create JpegFilterInputStream (filter out all markers except App0)
156         * @param in InputStream (with valid JPEG stream)
157         */
158        public JpegFilterInputStream(InputStream in) {
159            this(in, JpegFilterInputStream.defaultMarkers);
160        }
161    
162        /**
163         * create JpegFilterInputStream
164         * @param in InputStream (with valid JPEG stream)
165         * @param markers markers to filter out
166         */
167        public JpegFilterInputStream(InputStream in, int[] markers) {
168            super(in);
169            this.markers = markers;
170        }
171    
172        private boolean markerOn;
173    
174        public int read() throws IOException {
175            int a = in.read();
176            if (!markerOn) {
177                if (a == 0xFF) {
178                    markerOn = true;
179                }
180                return a;
181            }
182            else {
183                if (isAppMarker(a)) {
184                    int length = (in.read() << 8) + in.read() - 2;
185    //                                      Sys.out.println("length:" + length);
186                    in.skip(length);
187                    int b = in.read();
188                    if (b != 0xFF) {
189                        throw new IOException("marker???");
190                    }
191                    return read();
192                }
193                else {
194                    markerOn = false;
195                    return a;
196                }
197            }
198        }
199    
200        public int read(byte b[]) throws IOException {
201            return read(b, 0, b.length);
202        }
203    
204        public int read(byte b[], int off, int len) throws IOException {
205            if (b == null) {
206                throw new NullPointerException();
207            }
208            if (off + len > b.length || off < 0) {
209                throw new ArrayIndexOutOfBoundsException();
210            }
211            int read = 1;
212            int a = read();
213            if (a == -1) {
214                return -1;
215            }
216            b[off] = (byte) a;
217            for (int i = off + 1; i < len; i++) {
218                a = read();
219                if (a == -1) {
220                    break;
221                }
222                read++;
223                b[i] = (byte) a;
224            }
225            return read;
226        }
227    
228        public boolean isAppMarker(int a) {
229            for (int i = 0; i < markers.length; i++) {
230                if (a == markers[i]) {
231                    return true;
232                }
233            }
234            return false;
235        }
236    }