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.common.BinaryFunctions.read4Bytes;
20  
21  import java.awt.Dimension;
22  import java.awt.image.BufferedImage;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.nio.ByteOrder;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.commons.imaging.AbstractImageParser;
32  import org.apache.commons.imaging.ImageFormat;
33  import org.apache.commons.imaging.ImageFormats;
34  import org.apache.commons.imaging.ImageInfo;
35  import org.apache.commons.imaging.ImagingException;
36  import org.apache.commons.imaging.bytesource.ByteSource;
37  import org.apache.commons.imaging.common.AbstractBinaryOutputStream;
38  import org.apache.commons.imaging.common.Allocator;
39  import org.apache.commons.imaging.common.ImageMetadata;
40  import org.apache.commons.imaging.formats.pcx.PcxImageParser;
41  import org.apache.commons.imaging.formats.pcx.PcxImagingParameters;
42  
43  public class DcxImageParser extends AbstractImageParser<PcxImagingParameters> {
44      private static final class DcxHeader {
45  
46          public static final int DCX_ID = 0x3ADE68B1;
47          public final int id;
48          public final long[] pageTable;
49  
50          DcxHeader(final int id, final long[] pageTable) {
51              this.id = id;
52              this.pageTable = pageTable;
53          }
54  
55          public void dump(final PrintWriter pw) {
56              pw.println("DcxHeader");
57              pw.println("Id: 0x" + Integer.toHexString(id));
58              pw.println("Pages: " + pageTable.length);
59              pw.println();
60          }
61      }
62  
63      // See [BROEKN URL] http://www.fileformat.fine/format/pcx/egff.htm for documentation
64      private static final String DEFAULT_EXTENSION = ImageFormats.DCX.getDefaultExtension();
65  
66      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.DCX.getExtensions();
67  
68      /**
69       * Constructs a new instance with the little-endian byte order.
70       */
71      public DcxImageParser() {
72          super(ByteOrder.LITTLE_ENDIAN);
73      }
74  
75      @Override
76      public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
77          readDcxHeader(byteSource).dump(pw);
78          return true;
79      }
80  
81      @Override
82      protected String[] getAcceptedExtensions() {
83          return ACCEPTED_EXTENSIONS;
84      }
85  
86      @Override
87      protected ImageFormat[] getAcceptedTypes() {
88          return new ImageFormat[] { ImageFormats.DCX };
89      }
90  
91      @Override
92      public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
93          final DcxHeader dcxHeader = readDcxHeader(byteSource);
94          final List<BufferedImage> images = new ArrayList<>();
95          final PcxImageParser pcxImageParser = new PcxImageParser();
96          for (final long element : dcxHeader.pageTable) {
97              try (InputStream stream = ByteSource.getInputStream(byteSource, element)) {
98                  images.add(pcxImageParser.getBufferedImage(ByteSource.inputStream(stream, null), new PcxImagingParameters()));
99              }
100         }
101         return images;
102     }
103 
104     @Override
105     public final BufferedImage getBufferedImage(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
106         final List<BufferedImage> list = getAllBufferedImages(byteSource);
107         return list.isEmpty() ? null : list.get(0);
108     }
109 
110     @Override
111     public String getDefaultExtension() {
112         return DEFAULT_EXTENSION;
113     }
114 
115     @Override
116     public PcxImagingParameters getDefaultParameters() {
117         return new PcxImagingParameters();
118     }
119 
120     // FIXME should throw UOE
121     @Override
122     public byte[] getIccProfileBytes(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
123         return null;
124     }
125 
126     // FIXME should throw UOE
127     @Override
128     public ImageInfo getImageInfo(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
129         return null;
130     }
131 
132     // FIXME should throw UOE
133     @Override
134     public Dimension getImageSize(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
135         return null;
136     }
137 
138     // FIXME should throw UOE
139     @Override
140     public ImageMetadata getMetadata(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
141         return null;
142     }
143 
144     @Override
145     public String getName() {
146         return "Dcx-Custom";
147     }
148 
149     private DcxHeader readDcxHeader(final ByteSource byteSource) throws ImagingException, IOException {
150         try (InputStream is = byteSource.getInputStream()) {
151             final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
152             final int size = 1024;
153             final List<Long> pageTable = Allocator.arrayList(size);
154             for (int i = 0; i < size; i++) {
155                 final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is, "Not a Valid DCX File", getByteOrder());
156                 if (pageOffset == 0) {
157                     break;
158                 }
159                 pageTable.add(pageOffset);
160             }
161 
162             if (id != DcxHeader.DCX_ID) {
163                 throw new ImagingException("Not a Valid DCX File: file id incorrect");
164             }
165             if (pageTable.size() == size) {
166                 throw new ImagingException("DCX page table not terminated by zero entry");
167             }
168 
169             final long[] pages = pageTable.stream().mapToLong(Long::longValue).toArray();
170             return new DcxHeader(id, pages);
171         }
172     }
173 
174     @Override
175     public void writeImage(final BufferedImage src, final OutputStream os, final PcxImagingParameters params) throws ImagingException, IOException {
176         final int headerSize = 4 + 1024 * 4;
177 
178         @SuppressWarnings("resource") // Caller closes 'os'.
179         final AbstractBinaryOutputStream bos = AbstractBinaryOutputStream.littleEndian(os);
180         bos.write4Bytes(DcxHeader.DCX_ID);
181         // Some apps may need a full 1024 entry table
182         bos.write4Bytes(headerSize);
183         for (int i = 0; i < 1023; i++) {
184             bos.write4Bytes(0);
185         }
186         new PcxImageParser().writeImage(src, bos, params);
187     }
188 }