    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}