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.Allocator;
038import org.apache.commons.imaging.common.BinaryOutputStream;
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    public DcxImageParser() {
069        super(ByteOrder.LITTLE_ENDIAN);
070    }
071
072    @Override
073    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
074        readDcxHeader(byteSource).dump(pw);
075        return true;
076    }
077
078    @Override
079    protected String[] getAcceptedExtensions() {
080        return ACCEPTED_EXTENSIONS;
081    }
082
083    @Override
084    protected ImageFormat[] getAcceptedTypes() {
085        return new ImageFormat[] { ImageFormats.DCX };
086    }
087
088    @Override
089    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
090        final DcxHeader dcxHeader = readDcxHeader(byteSource);
091        final List<BufferedImage> images = new ArrayList<>();
092        final PcxImageParser pcxImageParser = new PcxImageParser();
093        for (final long element : dcxHeader.pageTable) {
094            try (InputStream stream = byteSource.getInputStream(element)) {
095                final ByteSource pcxSource = ByteSource.inputStream(stream, null);
096                final BufferedImage image = pcxImageParser.getBufferedImage(pcxSource, new PcxImagingParameters());
097                images.add(image);
098            }
099        }
100        return images;
101    }
102
103    @Override
104    public final BufferedImage getBufferedImage(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
105        final List<BufferedImage> list = getAllBufferedImages(byteSource);
106        return list.isEmpty() ? null : list.get(0);
107    }
108
109    @Override
110    public String getDefaultExtension() {
111        return DEFAULT_EXTENSION;
112    }
113
114    @Override
115    public PcxImagingParameters getDefaultParameters() {
116        return new PcxImagingParameters();
117    }
118
119    // FIXME should throw UOE
120    @Override
121    public byte[] getIccProfileBytes(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
122        return null;
123    }
124
125    // FIXME should throw UOE
126    @Override
127    public ImageInfo getImageInfo(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
128        return null;
129    }
130
131    // FIXME should throw UOE
132    @Override
133    public Dimension getImageSize(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
134        return null;
135    }
136
137    // FIXME should throw UOE
138    @Override
139    public ImageMetadata getMetadata(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
140        return null;
141    }
142
143    @Override
144    public String getName() {
145        return "Dcx-Custom";
146    }
147
148    private DcxHeader readDcxHeader(final ByteSource byteSource) throws ImagingException, IOException {
149        try (InputStream is = byteSource.getInputStream()) {
150            final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
151            final int size = 1024;
152            final List<Long> pageTable = Allocator.arrayList(size);
153            for (int i = 0; i < size; i++) {
154                final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is, "Not a Valid DCX File", getByteOrder());
155                if (pageOffset == 0) {
156                    break;
157                }
158                pageTable.add(pageOffset);
159            }
160
161            if (id != DcxHeader.DCX_ID) {
162                throw new ImagingException("Not a Valid DCX File: file id incorrect");
163            }
164            if (pageTable.size() == size) {
165                throw new ImagingException("DCX page table not terminated by zero entry");
166            }
167
168            final long[] pages = pageTable.stream().mapToLong(Long::longValue).toArray();
169            return new DcxHeader(id, pages);
170        }
171    }
172
173    @Override
174    public void writeImage(final BufferedImage src, final OutputStream os, final PcxImagingParameters params) throws ImagingException, IOException {
175        final int headerSize = 4 + 1024 * 4;
176
177        final BinaryOutputStream bos = BinaryOutputStream.littleEndian(os);
178        bos.write4Bytes(DcxHeader.DCX_ID);
179        // Some apps may need a full 1024 entry table
180        bos.write4Bytes(headerSize);
181        for (int i = 0; i < 1023; i++) {
182            bos.write4Bytes(0);
183        }
184        new PcxImageParser().writeImage(src, bos, params);
185    }
186}