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}