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.dcx;
18  
19  import static org.apache.commons.imaging.ImagingConstants.PARAM_KEY_FORMAT;
20  import static org.apache.commons.imaging.ImagingConstants.PARAM_KEY_PIXEL_DENSITY;
21  import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
22  
23  import java.awt.Dimension;
24  import java.awt.image.BufferedImage;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.io.PrintWriter;
29  import java.nio.ByteOrder;
30  import java.util.ArrayList;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  import org.apache.commons.imaging.ImageFormat;
36  import org.apache.commons.imaging.ImageFormats;
37  import org.apache.commons.imaging.ImageInfo;
38  import org.apache.commons.imaging.ImageParser;
39  import org.apache.commons.imaging.ImageReadException;
40  import org.apache.commons.imaging.ImageWriteException;
41  import org.apache.commons.imaging.PixelDensity;
42  import org.apache.commons.imaging.common.BinaryOutputStream;
43  import org.apache.commons.imaging.common.ImageMetadata;
44  import org.apache.commons.imaging.common.bytesource.ByteSource;
45  import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
46  import org.apache.commons.imaging.formats.pcx.PcxConstants;
47  import org.apache.commons.imaging.formats.pcx.PcxImageParser;
48  
49  public class DcxImageParser extends ImageParser {
50      // See http://www.fileformat.info/format/pcx/egff.htm for documentation
51      private static final String DEFAULT_EXTENSION = ".dcx";
52      private static final String[] ACCEPTED_EXTENSIONS = { ".dcx", };
53  
54      public DcxImageParser() {
55          super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
56      }
57  
58      @Override
59      public String getName() {
60          return "Dcx-Custom";
61      }
62  
63      @Override
64      public String getDefaultExtension() {
65          return DEFAULT_EXTENSION;
66      }
67  
68      @Override
69      protected String[] getAcceptedExtensions() {
70          return ACCEPTED_EXTENSIONS;
71      }
72  
73      @Override
74      protected ImageFormat[] getAcceptedTypes() {
75          return new ImageFormat[] {
76                  ImageFormats.DCX, //
77          };
78      }
79  
80      // FIXME should throw UOE
81      @Override
82      public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params)
83              throws ImageReadException, IOException {
84          return null;
85      }
86  
87      // FIXME should throw UOE
88      @Override
89      public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params)
90              throws ImageReadException, IOException {
91          return null;
92      }
93  
94      // FIXME should throw UOE
95      @Override
96      public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params)
97              throws ImageReadException, IOException {
98          return null;
99      }
100 
101     // FIXME should throw UOE
102     @Override
103     public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params)
104             throws ImageReadException, IOException {
105         return null;
106     }
107 
108     private static class DcxHeader {
109 
110         public static final int DCX_ID = 0x3ADE68B1;
111         public final int id;
112         public final long[] pageTable;
113 
114         public DcxHeader(final int id, final long[] pageTable) {
115             this.id = id;
116             this.pageTable = pageTable;
117         }
118 
119         public void dump(final PrintWriter pw) {
120             pw.println("DcxHeader");
121             pw.println("Id: 0x" + Integer.toHexString(id));
122             pw.println("Pages: " + pageTable.length);
123             pw.println();
124         }
125     }
126 
127     private DcxHeader readDcxHeader(final ByteSource byteSource)
128             throws ImageReadException, IOException {
129         try (InputStream is = byteSource.getInputStream()) {
130             final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
131             final List<Long> pageTable = new ArrayList<>(1024);
132             for (int i = 0; i < 1024; i++) {
133                 final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is,
134                         "Not a Valid DCX File", getByteOrder());
135                 if (pageOffset == 0) {
136                     break;
137                 }
138                 pageTable.add(pageOffset);
139             }
140 
141             if (id != DcxHeader.DCX_ID) {
142                 throw new ImageReadException(
143                         "Not a Valid DCX File: file id incorrect");
144             }
145             if (pageTable.size() == 1024) {
146                 throw new ImageReadException(
147                         "DCX page table not terminated by zero entry");
148             }
149 
150             final Object[] objects = pageTable.toArray();
151             final long[] pages = new long[objects.length];
152             for (int i = 0; i < objects.length; i++) {
153                 pages[i] = ((Long) objects[i]);
154             }
155 
156             final DcxHeader ret = new DcxHeader(id, pages);
157             return ret;
158         }
159     }
160 
161     @Override
162     public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
163             throws ImageReadException, IOException {
164         readDcxHeader(byteSource).dump(pw);
165         return true;
166     }
167 
168     @Override
169     public final BufferedImage getBufferedImage(final ByteSource byteSource,
170             final Map<String, Object> params) throws ImageReadException, IOException {
171         final List<BufferedImage> list = getAllBufferedImages(byteSource);
172         if (list.isEmpty()) {
173             return null;
174         }
175         return list.get(0);
176     }
177 
178     @Override
179     public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
180             throws ImageReadException, IOException {
181         final DcxHeader dcxHeader = readDcxHeader(byteSource);
182         final List<BufferedImage> images = new ArrayList<>();
183         final PcxImageParser pcxImageParser = new PcxImageParser();
184         for (final long element : dcxHeader.pageTable) {
185             try (InputStream stream = byteSource.getInputStream(element)) {
186                 final ByteSourceInputStream pcxSource = new ByteSourceInputStream(
187                         stream, null);
188                 final BufferedImage image = pcxImageParser.getBufferedImage(
189                         pcxSource, new HashMap<String, Object>());
190                 images.add(image);
191             }
192         }
193         return images;
194     }
195 
196     @Override
197     public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params)
198             throws ImageWriteException, IOException {
199         // make copy of params; we'll clear keys as we consume them.
200         params = (params == null) ? new HashMap<String, Object>() : new HashMap<>(params);
201 
202         final HashMap<String, Object> pcxParams = new HashMap<>();
203 
204         // clear format key.
205         if (params.containsKey(PARAM_KEY_FORMAT)) {
206             params.remove(PARAM_KEY_FORMAT);
207         }
208 
209         if (params.containsKey(PcxConstants.PARAM_KEY_PCX_COMPRESSION)) {
210             final Object value = params.remove(PcxConstants.PARAM_KEY_PCX_COMPRESSION);
211             pcxParams.put(PcxConstants.PARAM_KEY_PCX_COMPRESSION, value);
212         }
213         
214         if (params.containsKey(PARAM_KEY_PIXEL_DENSITY)) {
215             final Object value = params.remove(PARAM_KEY_PIXEL_DENSITY);
216             if (value != null) {
217                 if (!(value instanceof PixelDensity)) {
218                     throw new ImageWriteException(
219                             "Invalid pixel density parameter");
220                 }
221                 pcxParams.put(PARAM_KEY_PIXEL_DENSITY, value);
222             }
223         }
224 
225 
226         if (!params.isEmpty()) {
227             final Object firstKey = params.keySet().iterator().next();
228             throw new ImageWriteException("Unknown parameter: " + firstKey);
229         }
230 
231         final int headerSize = 4 + 1024 * 4;
232 
233         final BinaryOutputStream bos = new BinaryOutputStream(os,
234                 ByteOrder.LITTLE_ENDIAN);
235         bos.write4Bytes(DcxHeader.DCX_ID);
236         // Some apps may need a full 1024 entry table
237         bos.write4Bytes(headerSize);
238         for (int i = 0; i < 1023; i++) {
239             bos.write4Bytes(0);
240         }
241         final PcxImageParser pcxImageParser = new PcxImageParser();
242         pcxImageParser.writeImage(src, bos, pcxParams);
243     }
244 
245     /**
246      * Extracts embedded XML metadata as XML string.
247      * <p>
248      * 
249      * @param byteSource
250      *            File containing image data.
251      * @param params
252      *            Map of optional parameters, defined in ImagingConstants.
253      * @return Xmp Xml as String, if present. Otherwise, returns null.
254      */
255     @Override
256     public String getXmpXml(final ByteSource byteSource, final Map<String, Object> params)
257             throws ImageReadException, IOException {
258         return null;
259     }
260 }