    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}