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                images.add(pcxImageParser.getBufferedImage(ByteSource.inputStream(stream, null), new PcxImagingParameters()));
096            }
097        }
098        return images;
099    }
100
101    @Override
102    public final BufferedImage getBufferedImage(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
103        final List<BufferedImage> list = getAllBufferedImages(byteSource);
104        return list.isEmpty() ? null : list.get(0);
105    }
106
107    @Override
108    public String getDefaultExtension() {
109        return DEFAULT_EXTENSION;
110    }
111
112    @Override
113    public PcxImagingParameters getDefaultParameters() {
114        return new PcxImagingParameters();
115    }
116
117    // FIXME should throw UOE
118    @Override
119    public byte[] getIccProfileBytes(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
120        return null;
121    }
122
123    // FIXME should throw UOE
124    @Override
125    public ImageInfo getImageInfo(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
126        return null;
127    }
128
129    // FIXME should throw UOE
130    @Override
131    public Dimension getImageSize(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
132        return null;
133    }
134
135    // FIXME should throw UOE
136    @Override
137    public ImageMetadata getMetadata(final ByteSource byteSource, final PcxImagingParameters params) throws ImagingException, IOException {
138        return null;
139    }
140
141    @Override
142    public String getName() {
143        return "Dcx-Custom";
144    }
145
146    private DcxHeader readDcxHeader(final ByteSource byteSource) throws ImagingException, IOException {
147        try (InputStream is = byteSource.getInputStream()) {
148            final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
149            final int size = 1024;
150            final List<Long> pageTable = Allocator.arrayList(size);
151            for (int i = 0; i < size; i++) {
152                final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is, "Not a Valid DCX File", getByteOrder());
153                if (pageOffset == 0) {
154                    break;
155                }
156                pageTable.add(pageOffset);
157            }
158
159            if (id != DcxHeader.DCX_ID) {
160                throw new ImagingException("Not a Valid DCX File: file id incorrect");
161            }
162            if (pageTable.size() == size) {
163                throw new ImagingException("DCX page table not terminated by zero entry");
164            }
165
166            final long[] pages = pageTable.stream().mapToLong(Long::longValue).toArray();
167            return new DcxHeader(id, pages);
168        }
169    }
170
171    @Override
172    public void writeImage(final BufferedImage src, final OutputStream os, final PcxImagingParameters params) throws ImagingException, IOException {
173        final int headerSize = 4 + 1024 * 4;
174
175        final BinaryOutputStream bos = BinaryOutputStream.littleEndian(os);
176        bos.write4Bytes(DcxHeader.DCX_ID);
177        // Some apps may need a full 1024 entry table
178        bos.write4Bytes(headerSize);
179        for (int i = 0; i < 1023; i++) {
180            bos.write4Bytes(0);
181        }
182        new PcxImageParser().writeImage(src, bos, params);
183    }
184}