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.ImagingConstants.PARAM_KEY_FORMAT; 020import static org.apache.commons.imaging.ImagingConstants.PARAM_KEY_PIXEL_DENSITY; 021import static org.apache.commons.imaging.common.BinaryFunctions.read4Bytes; 022 023import java.awt.Dimension; 024import java.awt.image.BufferedImage; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.OutputStream; 028import java.io.PrintWriter; 029import java.nio.ByteOrder; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034 035import org.apache.commons.imaging.ImageFormat; 036import org.apache.commons.imaging.ImageFormats; 037import org.apache.commons.imaging.ImageInfo; 038import org.apache.commons.imaging.ImageParser; 039import org.apache.commons.imaging.ImageReadException; 040import org.apache.commons.imaging.ImageWriteException; 041import org.apache.commons.imaging.PixelDensity; 042import org.apache.commons.imaging.common.BinaryOutputStream; 043import org.apache.commons.imaging.common.ImageMetadata; 044import org.apache.commons.imaging.common.bytesource.ByteSource; 045import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream; 046import org.apache.commons.imaging.formats.pcx.PcxConstants; 047import org.apache.commons.imaging.formats.pcx.PcxImageParser; 048 049public class DcxImageParser extends ImageParser { 050 // See http://www.fileformat.fine/format/pcx/egff.htm for documentation 051 private static final String DEFAULT_EXTENSION = ".dcx"; 052 private static final String[] ACCEPTED_EXTENSIONS = { ".dcx", }; 053 054 public DcxImageParser() { 055 super.setByteOrder(ByteOrder.LITTLE_ENDIAN); 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[] { 076 ImageFormats.DCX, // 077 }; 078 } 079 080 // FIXME should throw UOE 081 @Override 082 public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params) 083 throws ImageReadException, IOException { 084 return null; 085 } 086 087 // FIXME should throw UOE 088 @Override 089 public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params) 090 throws ImageReadException, IOException { 091 return null; 092 } 093 094 // FIXME should throw UOE 095 @Override 096 public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params) 097 throws ImageReadException, IOException { 098 return null; 099 } 100 101 // FIXME should throw UOE 102 @Override 103 public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params) 104 throws ImageReadException, IOException { 105 return null; 106 } 107 108 private static class DcxHeader { 109 110 public static final int DCX_ID = 0x3ADE68B1; 111 public final int id; 112 public final long[] pageTable; 113 114 DcxHeader(final int id, final long[] pageTable) { 115 this.id = id; 116 this.pageTable = pageTable; 117 } 118 119 public void dump(final PrintWriter pw) { 120 pw.println("DcxHeader"); 121 pw.println("Id: 0x" + Integer.toHexString(id)); 122 pw.println("Pages: " + pageTable.length); 123 pw.println(); 124 } 125 } 126 127 private DcxHeader readDcxHeader(final ByteSource byteSource) 128 throws ImageReadException, IOException { 129 try (InputStream is = byteSource.getInputStream()) { 130 final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder()); 131 final List<Long> pageTable = new ArrayList<>(1024); 132 for (int i = 0; i < 1024; i++) { 133 final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is, 134 "Not a Valid DCX File", getByteOrder()); 135 if (pageOffset == 0) { 136 break; 137 } 138 pageTable.add(pageOffset); 139 } 140 141 if (id != DcxHeader.DCX_ID) { 142 throw new ImageReadException( 143 "Not a Valid DCX File: file id incorrect"); 144 } 145 if (pageTable.size() == 1024) { 146 throw new ImageReadException( 147 "DCX page table not terminated by zero entry"); 148 } 149 150 final Object[] objects = pageTable.toArray(); 151 final long[] pages = new long[objects.length]; 152 for (int i = 0; i < objects.length; i++) { 153 pages[i] = ((Long) objects[i]); 154 } 155 156 final DcxHeader ret = new DcxHeader(id, pages); 157 return ret; 158 } 159 } 160 161 @Override 162 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) 163 throws ImageReadException, IOException { 164 readDcxHeader(byteSource).dump(pw); 165 return true; 166 } 167 168 @Override 169 public final BufferedImage getBufferedImage(final ByteSource byteSource, 170 final Map<String, Object> params) throws ImageReadException, IOException { 171 final List<BufferedImage> list = getAllBufferedImages(byteSource); 172 if (list.isEmpty()) { 173 return null; 174 } 175 return list.get(0); 176 } 177 178 @Override 179 public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) 180 throws ImageReadException, IOException { 181 final DcxHeader dcxHeader = readDcxHeader(byteSource); 182 final List<BufferedImage> images = new ArrayList<>(); 183 final PcxImageParser pcxImageParser = new PcxImageParser(); 184 for (final long element : dcxHeader.pageTable) { 185 try (InputStream stream = byteSource.getInputStream(element)) { 186 final ByteSourceInputStream pcxSource = new ByteSourceInputStream( 187 stream, null); 188 final BufferedImage image = pcxImageParser.getBufferedImage( 189 pcxSource, new HashMap<String, Object>()); 190 images.add(image); 191 } 192 } 193 return images; 194 } 195 196 @Override 197 public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params) 198 throws ImageWriteException, IOException { 199 // make copy of params; we'll clear keys as we consume them. 200 params = (params == null) ? new HashMap<>() : new HashMap<>(params); 201 202 final HashMap<String, Object> pcxParams = new HashMap<>(); 203 204 // clear format key. 205 if (params.containsKey(PARAM_KEY_FORMAT)) { 206 params.remove(PARAM_KEY_FORMAT); 207 } 208 209 if (params.containsKey(PcxConstants.PARAM_KEY_PCX_COMPRESSION)) { 210 final Object value = params.remove(PcxConstants.PARAM_KEY_PCX_COMPRESSION); 211 pcxParams.put(PcxConstants.PARAM_KEY_PCX_COMPRESSION, value); 212 } 213 214 if (params.containsKey(PARAM_KEY_PIXEL_DENSITY)) { 215 final Object value = params.remove(PARAM_KEY_PIXEL_DENSITY); 216 if (value != null) { 217 if (!(value instanceof PixelDensity)) { 218 throw new ImageWriteException( 219 "Invalid pixel density parameter"); 220 } 221 pcxParams.put(PARAM_KEY_PIXEL_DENSITY, value); 222 } 223 } 224 225 226 if (!params.isEmpty()) { 227 final Object firstKey = params.keySet().iterator().next(); 228 throw new ImageWriteException("Unknown parameter: " + firstKey); 229 } 230 231 final int headerSize = 4 + 1024 * 4; 232 233 final BinaryOutputStream bos = new BinaryOutputStream(os, 234 ByteOrder.LITTLE_ENDIAN); 235 bos.write4Bytes(DcxHeader.DCX_ID); 236 // Some apps may need a full 1024 entry table 237 bos.write4Bytes(headerSize); 238 for (int i = 0; i < 1023; i++) { 239 bos.write4Bytes(0); 240 } 241 final PcxImageParser pcxImageParser = new PcxImageParser(); 242 pcxImageParser.writeImage(src, bos, pcxParams); 243 } 244}