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.AbstractImageParser;
032import org.apache.commons.imaging.ImageFormat;
033import org.apache.commons.imaging.ImageFormats;
034import org.apache.commons.imaging.ImageInfo;
035import org.apache.commons.imaging.ImagingException;
036import org.apache.commons.imaging.bytesource.ByteSource;
037import org.apache.commons.imaging.common.AbstractBinaryOutputStream;
038import org.apache.commons.imaging.common.Allocator;
039import org.apache.commons.imaging.common.ImageMetadata;
040import org.apache.commons.imaging.formats.pcx.PcxImageParser;
041import org.apache.commons.imaging.formats.pcx.PcxImagingParameters;
042
043public class DcxImageParser extends AbstractImageParser<PcxImagingParameters> {
044    private static final class DcxHeader {
045
046        public static final int DCX_ID = 0x3ADE68B1;
047        public final int id;
048        public final long[] pageTable;
049
050        DcxHeader(final int id, final long[] pageTable) {
051            this.id = id;
052            this.pageTable = pageTable;
053        }
054
055        public void dump(final PrintWriter pw) {
056            pw.println("DcxHeader");
057            pw.println("Id: 0x" + Integer.toHexString(id));
058            pw.println("Pages: " + pageTable.length);
059            pw.println();
060        }
061    }
062
063    // See [BROEKN URL] http://www.fileformat.fine/format/pcx/egff.htm for documentation
064    private static final String DEFAULT_EXTENSION = ImageFormats.DCX.getDefaultExtension();
065
066    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.DCX.getExtensions();
067
068    /**
069     * Constructs a new instance with the little-endian byte order.
070     */
071    public DcxImageParser() {
072        super(ByteOrder.LITTLE_ENDIAN);
073    }
074
075    @Override
076    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
077        readDcxHeader(byteSource).dump(pw);
078        return true;
079    }
080
081    @Override
082    protected String[] getAcceptedExtensions() {
083        return ACCEPTED_EXTENSIONS;
084    }
085
086    @Override
087    protected ImageFormat[] getAcceptedTypes() {
088        return new ImageFormat[] { ImageFormats.DCX };
089    }
090
091    @Override
092    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
093        final DcxHeader dcxHeader = readDcxHeader(byteSource);
094        final List<BufferedImage> images = new ArrayList<>();
095        final PcxImageParser pcxImageParser = new PcxImageParser();
096        for (final long element : dcxHeader.pageTable) {
097            try (InputStream stream = ByteSource.getInputStream(byteSource, element)) {
098                images.add(pcxImageParser.getBufferedImage(ByteSource.inputStream(stream, null), new PcxImagingParameters()));
099            }
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}