001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.dcx;
018
019import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes;
020
021import java.awt.Dimension;
022import java.awt.image.BufferedImage;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.io.PrintWriter;
027import java.nio.ByteOrder;
028import java.util.ArrayList;
029import java.util.List;
030
031import org.apache.commons.imaging.ImageFormat;
032import org.apache.commons.imaging.ImageFormats;
033import org.apache.commons.imaging.ImageInfo;
034import org.apache.commons.imaging.ImageParser;
035import org.apache.commons.imaging.ImageReadException;
036import org.apache.commons.imaging.ImageWriteException;
037import org.apache.commons.imaging.common.BinaryOutputStream;
038import org.apache.commons.imaging.common.ImageMetadata;
039import org.apache.commons.imaging.common.bytesource.ByteSource;
040import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
041import org.apache.commons.imaging.formats.pcx.PcxImageParser;
042import org.apache.commons.imaging.formats.pcx.PcxImagingParameters;
043
044public class DcxImageParser extends ImageParser<PcxImagingParameters> {
045    // See http://www.fileformat.fine/format/pcx/egff.htm for documentation
046    private static final String DEFAULT_EXTENSION = ImageFormats.DCX.getDefaultExtension();
047    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.DCX.getExtensions();
048
049    public DcxImageParser() {
050        super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
051    }
052
053    @Override
054    public PcxImagingParameters getDefaultParameters() {
055        return new PcxImagingParameters();
056    }
057
058    @Override
059    public String getName() {
060        return "Dcx-Custom";
061    }
062
063    @Override
064    public String getDefaultExtension() {
065        return DEFAULT_EXTENSION;
066    }
067
068    @Override
069    protected String[] getAcceptedExtensions() {
070        return ACCEPTED_EXTENSIONS;
071    }
072
073    @Override
074    protected ImageFormat[] getAcceptedTypes() {
075        return new ImageFormat[] { ImageFormats.DCX };
076    }
077
078    // FIXME should throw UOE
079    @Override
080    public ImageMetadata getMetadata(final ByteSource byteSource, final PcxImagingParameters params)
081            throws ImageReadException, IOException {
082        return null;
083    }
084
085    // FIXME should throw UOE
086    @Override
087    public ImageInfo getImageInfo(final ByteSource byteSource, final PcxImagingParameters params)
088            throws ImageReadException, IOException {
089        return null;
090    }
091
092    // FIXME should throw UOE
093    @Override
094    public Dimension getImageSize(final ByteSource byteSource, final PcxImagingParameters params)
095            throws ImageReadException, IOException {
096        return null;
097    }
098
099    // FIXME should throw UOE
100    @Override
101    public byte[] getICCProfileBytes(final ByteSource byteSource, final PcxImagingParameters params)
102            throws ImageReadException, IOException {
103        return null;
104    }
105
106    private static class DcxHeader {
107
108        public static final int DCX_ID = 0x3ADE68B1;
109        public final int id;
110        public final long[] pageTable;
111
112        DcxHeader(final int id, final long[] pageTable) {
113            this.id = id;
114            this.pageTable = pageTable;
115        }
116
117        public void dump(final PrintWriter pw) {
118            pw.println("DcxHeader");
119            pw.println("Id: 0x" + Integer.toHexString(id));
120            pw.println("Pages: " + pageTable.length);
121            pw.println();
122        }
123    }
124
125    private DcxHeader readDcxHeader(final ByteSource byteSource)
126            throws ImageReadException, IOException {
127        try (InputStream is = byteSource.getInputStream()) {
128            final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
129            final List<Long> pageTable = new ArrayList<>(1024);
130            for (int i = 0; i < 1024; i++) {
131                final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is,
132                        "Not a Valid DCX File", getByteOrder());
133                if (pageOffset == 0) {
134                    break;
135                }
136                pageTable.add(pageOffset);
137            }
138
139            if (id != DcxHeader.DCX_ID) {
140                throw new ImageReadException(
141                        "Not a Valid DCX File: file id incorrect");
142            }
143            if (pageTable.size() == 1024) {
144                throw new ImageReadException(
145                        "DCX page table not terminated by zero entry");
146            }
147
148            final Object[] objects = pageTable.toArray();
149            final long[] pages = new long[objects.length];
150            for (int i = 0; i < objects.length; i++) {
151                pages[i] = ((Long) objects[i]);
152            }
153
154            return new DcxHeader(id, pages);
155        }
156    }
157
158    @Override
159    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
160            throws ImageReadException, IOException {
161        readDcxHeader(byteSource).dump(pw);
162        return true;
163    }
164
165    @Override
166    public final BufferedImage getBufferedImage(final ByteSource byteSource,
167            final PcxImagingParameters params) throws ImageReadException, IOException {
168        final List<BufferedImage> list = getAllBufferedImages(byteSource);
169        if (list.isEmpty()) {
170            return null;
171        }
172        return list.get(0);
173    }
174
175    @Override
176    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
177            throws ImageReadException, IOException {
178        final DcxHeader dcxHeader = readDcxHeader(byteSource);
179        final List<BufferedImage> images = new ArrayList<>();
180        final PcxImageParser pcxImageParser = new PcxImageParser();
181        for (final long element : dcxHeader.pageTable) {
182            try (InputStream stream = byteSource.getInputStream(element)) {
183                final ByteSourceInputStream pcxSource = new ByteSourceInputStream(
184                        stream, null);
185                final BufferedImage image = pcxImageParser.getBufferedImage(
186                        pcxSource, new PcxImagingParameters());
187                images.add(image);
188            }
189        }
190        return images;
191    }
192
193    @Override
194    public void writeImage(final BufferedImage src, final OutputStream os, final PcxImagingParameters params)
195            throws ImageWriteException, IOException {
196        final int headerSize = 4 + 1024 * 4;
197
198        final BinaryOutputStream bos = new BinaryOutputStream(os,
199                ByteOrder.LITTLE_ENDIAN);
200        bos.write4Bytes(DcxHeader.DCX_ID);
201        // Some apps may need a full 1024 entry table
202        bos.write4Bytes(headerSize);
203        for (int i = 0; i < 1023; i++) {
204            bos.write4Bytes(0);
205        }
206        final PcxImageParser pcxImageParser = new PcxImageParser();
207        pcxImageParser.writeImage(src, bos, params);
208    }
209}