001: /*
002: * $RCSfile: PCXImageWriter.java,v $
003: *
004: *
005: * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.2 $
042: * $Date: 2007/09/11 20:45:42 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageioimpl.plugins.pcx;
046:
047: import java.awt.Rectangle;
048: import java.awt.color.ColorSpace;
049: import java.awt.image.*;
050: import java.io.IOException;
051: import java.nio.ByteOrder;
052:
053: import javax.imageio.*;
054: import javax.imageio.metadata.IIOMetadata;
055: import javax.imageio.stream.ImageOutputStream;
056:
057: import com.sun.media.imageioimpl.common.ImageUtil;
058:
059: public class PCXImageWriter extends ImageWriter implements PCXConstants {
060:
061: private ImageOutputStream ios;
062: private Rectangle sourceRegion;
063: private Rectangle destinationRegion;
064: private int colorPlanes, bytesPerLine;
065: private Raster inputRaster = null;
066: private int scaleX, scaleY;
067:
068: public PCXImageWriter(PCXImageWriterSpi imageWriterSpi) {
069: super (imageWriterSpi);
070: }
071:
072: public void setOutput(Object output) {
073: super .setOutput(output); // validates output
074: if (output != null) {
075: if (!(output instanceof ImageOutputStream))
076: throw new IllegalArgumentException(
077: "output not instance of ImageOutputStream");
078: ios = (ImageOutputStream) output;
079: ios.setByteOrder(ByteOrder.LITTLE_ENDIAN);
080: } else
081: ios = null;
082: }
083:
084: public IIOMetadata convertImageMetadata(IIOMetadata inData,
085: ImageTypeSpecifier imageType, ImageWriteParam param) {
086: if (inData instanceof PCXMetadata)
087: return inData;
088: return null;
089: }
090:
091: public IIOMetadata convertStreamMetadata(IIOMetadata inData,
092: ImageWriteParam param) {
093: return null;
094: }
095:
096: public IIOMetadata getDefaultImageMetadata(
097: ImageTypeSpecifier imageType, ImageWriteParam param) {
098: PCXMetadata md = new PCXMetadata();
099: md.bitsPerPixel = (byte) imageType.getSampleModel()
100: .getSampleSize()[0];
101: return md;
102: }
103:
104: public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
105: return null;
106: }
107:
108: public void write(IIOMetadata streamMetadata, IIOImage image,
109: ImageWriteParam param) throws IOException {
110: if (ios == null) {
111: throw new IllegalStateException("output stream is null");
112: }
113:
114: if (image == null) {
115: throw new IllegalArgumentException("image is null");
116: }
117:
118: clearAbortRequest();
119: processImageStarted(0);
120: if (param == null)
121: param = getDefaultWriteParam();
122:
123: boolean writeRaster = image.hasRaster();
124:
125: sourceRegion = param.getSourceRegion();
126:
127: SampleModel sampleModel = null;
128: ColorModel colorModel = null;
129:
130: if (writeRaster) {
131: inputRaster = image.getRaster();
132: sampleModel = inputRaster.getSampleModel();
133: colorModel = ImageUtil.createColorModel(null, sampleModel);
134: if (sourceRegion == null)
135: sourceRegion = inputRaster.getBounds();
136: else
137: sourceRegion = sourceRegion.intersection(inputRaster
138: .getBounds());
139: } else {
140: RenderedImage input = image.getRenderedImage();
141: inputRaster = input.getData();
142: sampleModel = input.getSampleModel();
143: colorModel = input.getColorModel();
144: Rectangle rect = new Rectangle(input.getMinX(), input
145: .getMinY(), input.getWidth(), input.getHeight());
146: if (sourceRegion == null)
147: sourceRegion = rect;
148: else
149: sourceRegion = sourceRegion.intersection(rect);
150: }
151:
152: if (sourceRegion.isEmpty())
153: throw new IllegalArgumentException("source region is empty");
154:
155: IIOMetadata imageMetadata = image.getMetadata();
156: PCXMetadata pcxImageMetadata = null;
157:
158: ImageTypeSpecifier imageType = new ImageTypeSpecifier(
159: colorModel, sampleModel);
160: if (imageMetadata != null) {
161: // Convert metadata.
162: pcxImageMetadata = (PCXMetadata) convertImageMetadata(
163: imageMetadata, imageType, param);
164: } else {
165: // Use default.
166: pcxImageMetadata = (PCXMetadata) getDefaultImageMetadata(
167: imageType, param);
168: }
169:
170: scaleX = param.getSourceXSubsampling();
171: scaleY = param.getSourceYSubsampling();
172:
173: int xOffset = param.getSubsamplingXOffset();
174: int yOffset = param.getSubsamplingYOffset();
175:
176: // cache the data type;
177: int dataType = sampleModel.getDataType();
178:
179: sourceRegion.translate(xOffset, yOffset);
180: sourceRegion.width -= xOffset;
181: sourceRegion.height -= yOffset;
182:
183: int minX = sourceRegion.x / scaleX;
184: int minY = sourceRegion.y / scaleY;
185: int w = (sourceRegion.width + scaleX - 1) / scaleX;
186: int h = (sourceRegion.height + scaleY - 1) / scaleY;
187:
188: xOffset = sourceRegion.x % scaleX;
189: yOffset = sourceRegion.y % scaleY;
190:
191: destinationRegion = new Rectangle(minX, minY, w, h);
192:
193: boolean noTransform = destinationRegion.equals(sourceRegion);
194:
195: // Raw data can only handle bytes, everything greater must be ASCII.
196: int[] sourceBands = param.getSourceBands();
197: boolean noSubband = true;
198: int numBands = sampleModel.getNumBands();
199:
200: if (sourceBands != null) {
201: sampleModel = sampleModel
202: .createSubsetSampleModel(sourceBands);
203: colorModel = null;
204: noSubband = false;
205: numBands = sampleModel.getNumBands();
206: } else {
207: sourceBands = new int[numBands];
208: for (int i = 0; i < numBands; i++)
209: sourceBands[i] = i;
210: }
211:
212: ios.writeByte(MANUFACTURER);
213: ios.writeByte(VERSION_3_0);
214: ios.writeByte(ENCODING);
215:
216: int bitsPerPixel = sampleModel.getSampleSize(0);
217: ios.writeByte(bitsPerPixel);
218:
219: ios.writeShort(destinationRegion.x); // xmin
220: ios.writeShort(destinationRegion.y); // ymin
221: ios.writeShort(destinationRegion.x + destinationRegion.width
222: - 1); // xmax
223: ios.writeShort(destinationRegion.y + destinationRegion.height
224: - 1); // ymax
225:
226: ios.writeShort(pcxImageMetadata.hdpi);
227: ios.writeShort(pcxImageMetadata.vdpi);
228:
229: byte[] smallpalette = createSmallPalette(colorModel);
230: ios.write(smallpalette);
231: ios.writeByte(0); // reserved
232:
233: colorPlanes = sampleModel.getNumBands();
234:
235: ios.writeByte(colorPlanes);
236:
237: bytesPerLine = destinationRegion.width * bitsPerPixel / 8;
238: bytesPerLine += bytesPerLine % 2;
239:
240: ios.writeShort(bytesPerLine);
241:
242: if (colorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY)
243: ios.writeShort(PALETTE_GRAYSCALE);
244: else
245: ios.writeShort(PALETTE_COLOR);
246:
247: ios.writeShort(pcxImageMetadata.hsize);
248: ios.writeShort(pcxImageMetadata.vsize);
249:
250: for (int i = 0; i < 54; i++)
251: ios.writeByte(0);
252:
253: // write image data
254:
255: if (colorPlanes == 1 && bitsPerPixel == 1) {
256: write1Bit();
257: } else if (colorPlanes == 1 && bitsPerPixel == 4) {
258: write4Bit();
259: } else {
260: write8Bit();
261: }
262:
263: // write 256 color palette if needed
264: if (colorPlanes == 1
265: && bitsPerPixel == 8
266: && colorModel.getColorSpace().getType() != ColorSpace.TYPE_GRAY) {
267: ios.writeByte(12); // Magic number preceding VGA 256 Color Palette Information
268: ios.write(createLargePalette(colorModel));
269: }
270:
271: if (abortRequested()) {
272: processWriteAborted();
273: } else {
274: processImageComplete();
275: }
276: }
277:
278: private void write4Bit() throws IOException {
279: int[] unpacked = new int[sourceRegion.width];
280: int[] samples = new int[bytesPerLine];
281:
282: for (int line = 0; line < sourceRegion.height; line += scaleY) {
283: inputRaster.getSamples(sourceRegion.x, line
284: + sourceRegion.y, sourceRegion.width, 1, 0,
285: unpacked);
286:
287: int val = 0, dst = 0;
288: for (int x = 0, nibble = 0; x < sourceRegion.width; x += scaleX) {
289: val = val | (unpacked[x] & 0x0F);
290: if (nibble == 1) {
291: samples[dst++] = val;
292: nibble = 0;
293: val = 0;
294: } else {
295: nibble = 1;
296: val = val << 4;
297: }
298: }
299:
300: int last = samples[0];
301: int count = 0;
302:
303: for (int x = 0; x < bytesPerLine; x += scaleX) {
304: int sample = samples[x];
305: if (sample != last || count == 63) {
306: writeRLE(last, count);
307: count = 1;
308: last = sample;
309: } else
310: count++;
311: }
312: if (count >= 1) {
313: writeRLE(last, count);
314: }
315:
316: processImageProgress(100.0F * line / sourceRegion.height);
317: }
318: }
319:
320: private void write1Bit() throws IOException {
321: int[] unpacked = new int[sourceRegion.width];
322: int[] samples = new int[bytesPerLine];
323:
324: for (int line = 0; line < sourceRegion.height; line += scaleY) {
325: inputRaster.getSamples(sourceRegion.x, line
326: + sourceRegion.y, sourceRegion.width, 1, 0,
327: unpacked);
328:
329: int val = 0, dst = 0;
330: for (int x = 0, bit = 1 << 7; x < sourceRegion.width; x += scaleX) {
331: if (unpacked[x] > 0)
332: val = val | bit;
333: if (bit == 1) {
334: samples[dst++] = val;
335: bit = 1 << 7;
336: val = 0;
337: } else {
338: bit = bit >> 1;
339: }
340: }
341:
342: int last = samples[0];
343: int count = 0;
344:
345: for (int x = 0; x < bytesPerLine; x += scaleX) {
346: int sample = samples[x];
347: if (sample != last || count == 63) {
348: writeRLE(last, count);
349: count = 1;
350: last = sample;
351: } else
352: count++;
353: }
354: if (count >= 1) {
355: writeRLE(last, count);
356: }
357:
358: processImageProgress(100.0F * line / sourceRegion.height);
359: }
360: }
361:
362: private void write8Bit() throws IOException {
363: int[][] samples = new int[colorPlanes][bytesPerLine];
364:
365: for (int line = 0; line < sourceRegion.height; line += scaleY) {
366: for (int band = 0; band < colorPlanes; band++) {
367: inputRaster.getSamples(sourceRegion.x, line
368: + sourceRegion.y, sourceRegion.width, 1, band,
369: samples[band]);
370: }
371:
372: int last = samples[0][0];
373: int count = 0;
374:
375: for (int band = 0; band < colorPlanes; band++) {
376: for (int x = 0; x < bytesPerLine; x += scaleX) {
377: int sample = samples[band][x];
378: if (sample != last || count == 63) {
379: writeRLE(last, count);
380: count = 1;
381: last = sample;
382: } else
383: count++;
384: }
385: }
386: if (count >= 1) {
387: writeRLE(last, count);
388: }
389:
390: processImageProgress(100.0F * line / sourceRegion.height);
391: }
392: }
393:
394: private void writeRLE(int val, int count) throws IOException {
395: if (count == 1 && (val & 0xC0) != 0xC0) {
396: ios.writeByte(val);
397: } else {
398: ios.writeByte(0xC0 | count);
399: ios.writeByte(val);
400: }
401: }
402:
403: private byte[] createSmallPalette(ColorModel cm) {
404: byte[] palette = new byte[16 * 3];
405:
406: if (!(cm instanceof IndexColorModel))
407: return palette;
408:
409: IndexColorModel icm = (IndexColorModel) cm;
410: if (icm.getMapSize() > 16)
411: return palette;
412:
413: for (int i = 0, offset = 0; i < icm.getMapSize(); i++) {
414: palette[offset++] = (byte) icm.getRed(i);
415: palette[offset++] = (byte) icm.getGreen(i);
416: palette[offset++] = (byte) icm.getBlue(i);
417: }
418:
419: return palette;
420: }
421:
422: private byte[] createLargePalette(ColorModel cm) {
423: byte[] palette = new byte[256 * 3];
424:
425: if (!(cm instanceof IndexColorModel))
426: return palette;
427:
428: IndexColorModel icm = (IndexColorModel) cm;
429:
430: for (int i = 0, offset = 0; i < icm.getMapSize(); i++) {
431: palette[offset++] = (byte) icm.getRed(i);
432: palette[offset++] = (byte) icm.getGreen(i);
433: palette[offset++] = (byte) icm.getBlue(i);
434: }
435:
436: return palette;
437: }
438: }
|