001: /*
002: * $Id: PDFImage.java,v 1.2 2007/12/20 18:17:41 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview;
023:
024: import java.awt.Color;
025: import java.awt.Point;
026: import java.awt.Transparency;
027: import java.awt.color.ColorSpace;
028: import java.awt.color.ICC_ColorSpace;
029: import java.awt.image.BufferedImage;
030: import java.awt.image.ColorConvertOp;
031: import java.awt.image.ColorModel;
032: import java.awt.image.ComponentColorModel;
033: import java.awt.image.DataBuffer;
034: import java.awt.image.DataBufferByte;
035: import java.awt.image.IndexColorModel;
036: import java.awt.image.MultiPixelPackedSampleModel;
037: import java.awt.image.Raster;
038: import java.awt.image.SampleModel;
039: import java.awt.image.WritableRaster;
040: import java.io.IOException;
041: import java.util.Map;
042:
043: import com.sun.pdfview.colorspace.IndexedColor;
044: import com.sun.pdfview.colorspace.PDFColorSpace;
045: import com.sun.pdfview.function.FunctionType0;
046:
047: /**
048: * Encapsulates a PDF Image
049: */
050: public class PDFImage {
051: /** the width of this image in pixels */
052: private int width;
053:
054: /** the height of this image in pixels */
055: private int height;
056:
057: /** the colorspace to interpret the samples in */
058: private PDFColorSpace colorSpace;
059:
060: /** the number of bits per sample component */
061: private int bpc;
062:
063: /** whether this image is a mask or not */
064: private boolean imageMask = false;
065:
066: /** the SMask image, if any */
067: private PDFImage sMask;
068:
069: /** the decode array */
070: private float[] decode;
071:
072: /** the actual image data */
073: private PDFObject imageObj;
074:
075: /**
076: * Create an instance of a PDFImage
077: */
078: protected PDFImage(PDFObject imageObj) {
079: this .imageObj = imageObj;
080: }
081:
082: /**
083: * Read a PDFImage from an image dictionary and stream
084: *
085: * @param obj the PDFObject containing the image's dictionary and stream
086: * @param resources the current resources
087: */
088: public static PDFImage createImage(PDFObject obj, Map resources)
089: throws IOException {
090: // create the image
091: PDFImage image = new PDFImage(obj);
092:
093: // get the width (required)
094: PDFObject widthObj = obj.getDictRef("Width");
095: if (widthObj == null) {
096: throw new PDFParseException("Unable to read image width: "
097: + obj);
098: }
099: image.setWidth(widthObj.getIntValue());
100:
101: // get the height (required)
102: PDFObject heightObj = obj.getDictRef("Height");
103: if (heightObj == null) {
104: throw new PDFParseException("Unable to get image height: "
105: + obj);
106: }
107: image.setHeight(heightObj.getIntValue());
108:
109: // figure out if we are an image mask (optional)
110: PDFObject imageMaskObj = obj.getDictRef("ImageMask");
111: if (imageMaskObj != null) {
112: image.setImageMask(imageMaskObj.getBooleanValue());
113: }
114:
115: // read the bpc and colorspace (required except for masks)
116: if (image.isImageMask()) {
117: image.setBitsPerComponent(1);
118:
119: // create the indexed color space for the mask
120: Color[] colors = { Color.WHITE, Color.BLACK };
121: image.setColorSpace(new IndexedColor(colors));
122: } else {
123: // get the bits per component (required)
124: PDFObject bpcObj = obj.getDictRef("BitsPerComponent");
125: if (bpcObj == null) {
126: throw new PDFParseException(
127: "Unable to get bits per component: " + obj);
128: }
129: image.setBitsPerComponent(bpcObj.getIntValue());
130:
131: // get the color space (required)
132: PDFObject csObj = obj.getDictRef("ColorSpace");
133: if (csObj == null) {
134: throw new PDFParseException("No ColorSpace for image: "
135: + obj);
136: }
137:
138: PDFColorSpace cs = PDFColorSpace.getColorSpace(csObj,
139: resources);
140: image.setColorSpace(cs);
141: }
142:
143: // read the decode array
144: PDFObject decodeObj = obj.getDictRef("Decode");
145: if (decodeObj != null) {
146: PDFObject[] decodeArray = decodeObj.getArray();
147:
148: float[] decode = new float[decodeArray.length];
149: for (int i = 0; i < decodeArray.length; i++) {
150: decode[i] = decodeArray[i].getFloatValue();
151: }
152:
153: image.setDecode(decode);
154: }
155:
156: // read the soft mask
157: PDFObject sMaskObj = obj.getDictRef("SMask");
158: if (sMaskObj == null) {
159: // try the explicit mask, if there is no SoftMask
160: sMaskObj = obj.getDictRef("Mask");
161: }
162:
163: if (sMaskObj != null) {
164: PDFImage sMaskImage = PDFImage.createImage(sMaskObj,
165: resources);
166: image.setSMask(sMaskImage);
167: }
168:
169: return image;
170: }
171:
172: /**
173: * Get the image that this PDFImage generates.
174: *
175: * @return a buffered image containing the decoded image data
176: */
177: public BufferedImage getImage() {
178: try {
179: BufferedImage bi = (BufferedImage) imageObj.getCache();
180:
181: if (bi == null) {
182: // parse the stream data into an actual image
183: bi = parseData(imageObj.getStream());
184: imageObj.setCache(bi);
185: }
186:
187: return bi;
188: } catch (IOException ioe) {
189: System.out.println("Error reading image");
190: ioe.printStackTrace();
191: return null;
192: }
193: }
194:
195: /**
196: * Parse the image stream into a buffered image. Note that this is
197: * guaranteed to be called after all the other setXXX methods have been
198: * called.
199: */
200: protected BufferedImage parseData(byte[] data) {
201: // create the data buffer
202: DataBuffer db = new DataBufferByte(data, data.length);
203:
204: // pick a color model, based on the number of components and
205: // bits per component
206: ColorModel cm = getColorModel();
207:
208: // create a compatible raster
209: SampleModel sm = cm.createCompatibleSampleModel(getWidth(),
210: getHeight());
211: WritableRaster raster = Raster.createWritableRaster(sm, db,
212: new Point(0, 0));
213:
214: /*
215: * Workaround for a bug on the Mac -- a class cast exception in
216: * drawImage() due to the wrong data buffer type (?)
217: */
218: BufferedImage bi = null;
219: if (cm instanceof IndexColorModel) {
220: IndexColorModel icm = (IndexColorModel) cm;
221:
222: // choose the image type based on the size
223: int type = BufferedImage.TYPE_BYTE_BINARY;
224: if (getBitsPerComponent() == 8) {
225: type = BufferedImage.TYPE_BYTE_INDEXED;
226: }
227:
228: // create the image with an explicit indexed color model.
229: bi = new BufferedImage(getWidth(), getHeight(), type, icm);
230:
231: // set the data explicitly as well
232: bi.setData(raster);
233: } else {
234: bi = new BufferedImage(cm, raster, true, null);
235: }
236:
237: // hack to avoid *very* slow conversion
238: ColorSpace cs = cm.getColorSpace();
239: ColorSpace rgbCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
240: if (!isImageMask() && cs instanceof ICC_ColorSpace
241: && !cs.equals(rgbCS)) {
242: ColorConvertOp op = new ColorConvertOp(cs, rgbCS, null);
243:
244: BufferedImage converted = new BufferedImage(getWidth(),
245: getHeight(), BufferedImage.TYPE_INT_ARGB);
246:
247: bi = op.filter(bi, converted);
248: }
249:
250: // add in the alpha data supplied by the SMask, if any
251: PDFImage sMaskImage = getSMask();
252: if (sMaskImage != null) {
253: BufferedImage si = sMaskImage.getImage();
254:
255: BufferedImage outImage = new BufferedImage(getWidth(),
256: getHeight(), BufferedImage.TYPE_INT_ARGB);
257:
258: int[] srcArray = new int[width];
259: int[] maskArray = new int[width];
260:
261: for (int i = 0; i < height; i++) {
262: bi.getRGB(0, i, width, 1, srcArray, 0, width);
263: si.getRGB(0, i, width, 1, maskArray, 0, width);
264:
265: for (int j = 0; j < width; j++) {
266: int ac = 0xff000000;
267:
268: maskArray[j] = ((maskArray[j] & 0xff) << 24)
269: | (srcArray[j] & ~ac);
270: }
271:
272: outImage.setRGB(0, i, width, 1, maskArray, 0, width);
273: }
274:
275: bi = outImage;
276: }
277:
278: return (bi);
279: }
280:
281: /**
282: * Get the image's width
283: */
284: public int getWidth() {
285: return width;
286: }
287:
288: /**
289: * Set the image's width
290: */
291: protected void setWidth(int width) {
292: this .width = width;
293: }
294:
295: /**
296: * Get the image's height
297: */
298: public int getHeight() {
299: return height;
300: }
301:
302: /**
303: * Set the image's height
304: */
305: protected void setHeight(int height) {
306: this .height = height;
307: }
308:
309: /**
310: * Get the colorspace associated with this image, or null if there
311: * isn't one
312: */
313: protected PDFColorSpace getColorSpace() {
314: return colorSpace;
315: }
316:
317: /**
318: * Set the colorspace associated with this image
319: */
320: protected void setColorSpace(PDFColorSpace colorSpace) {
321: this .colorSpace = colorSpace;
322: }
323:
324: /**
325: * Get the number of bits per component sample
326: */
327: protected int getBitsPerComponent() {
328: return bpc;
329: }
330:
331: /**
332: * Set the number of bits per component sample
333: */
334: protected void setBitsPerComponent(int bpc) {
335: this .bpc = bpc;
336: }
337:
338: /**
339: * Return whether or not this is an image mask
340: */
341: public boolean isImageMask() {
342: return imageMask;
343: }
344:
345: /**
346: * Set whether or not this is an image mask
347: */
348: public void setImageMask(boolean imageMask) {
349: this .imageMask = imageMask;
350: }
351:
352: /**
353: * Return the soft mask associated with this image
354: */
355: public PDFImage getSMask() {
356: return sMask;
357: }
358:
359: /**
360: * Set the soft mask image
361: */
362: protected void setSMask(PDFImage sMask) {
363: this .sMask = sMask;
364: }
365:
366: /**
367: * Get the decode array
368: */
369: protected float[] getDecode() {
370: return decode;
371: }
372:
373: /**
374: * Set the decode array
375: */
376: protected void setDecode(float[] decode) {
377: this .decode = decode;
378: }
379:
380: /**
381: * get a Java ColorModel consistent with the current color space,
382: * number of bits per component and decode array
383: *
384: * @param bpc the number of bits per component
385: */
386: private ColorModel getColorModel() {
387: PDFColorSpace cs = getColorSpace();
388:
389: if (cs instanceof IndexedColor) {
390: IndexedColor ics = (IndexedColor) cs;
391:
392: byte[] components = ics.getColorComponents();
393: int num = ics.getCount();
394:
395: // process the decode array
396: if (decode != null) {
397: byte[] normComps = new byte[components.length];
398:
399: // move the components array around
400: for (int i = 0; i < num; i++) {
401: byte[] orig = new byte[1];
402: orig[0] = (byte) i;
403:
404: float[] res = normalize(orig, null, 0);
405: int idx = (int) res[0];
406:
407: normComps[i * 3] = components[idx * 3];
408: normComps[(i * 3) + 1] = components[(idx * 3) + 1];
409: normComps[(i * 3) + 2] = components[(idx * 3) + 2];
410: }
411:
412: components = normComps;
413: }
414:
415: // make sure the size of the components array is 2 ^ numBits
416: // since if it's not, Java will complain
417: int correctCount = 1 << getBitsPerComponent();
418: if (correctCount < num) {
419: byte[] fewerComps = new byte[correctCount * 3];
420:
421: System.arraycopy(components, 0, fewerComps, 0,
422: correctCount * 3);
423:
424: components = fewerComps;
425: num = correctCount;
426: }
427:
428: return new IndexColorModel(getBitsPerComponent(), num,
429: components, 0, false);
430: } else {
431: int[] bits = new int[cs.getNumComponents()];
432: for (int i = 0; i < bits.length; i++) {
433: bits[i] = getBitsPerComponent();
434: }
435:
436: return new DecodeComponentColorModel(cs.getColorSpace(),
437: bits);
438: }
439: }
440:
441: /**
442: * Normalize an array of values to match the decode array
443: */
444: private float[] normalize(byte[] pixels, float[] normComponents,
445: int normOffset) {
446: if (normComponents == null) {
447: normComponents = new float[normOffset + pixels.length];
448: }
449:
450: float[] decodeArray = getDecode();
451:
452: for (int i = 0; i < pixels.length; i++) {
453: int val = pixels[i] & 0xff;
454: int pow = ((int) Math.pow(2, getBitsPerComponent())) - 1;
455: float ymin = decodeArray[i * 2];
456: float ymax = decodeArray[(i * 2) + 1];
457:
458: normComponents[normOffset + i] = FunctionType0.interpolate(
459: val, 0, pow, ymin, ymax);
460: }
461:
462: return normComponents;
463: }
464:
465: /**
466: * A wrapper for ComponentColorSpace which normalizes based on the
467: * decode array.
468: */
469: class DecodeComponentColorModel extends ComponentColorModel {
470: public DecodeComponentColorModel(ColorSpace cs, int[] bpc) {
471: super (cs, bpc, false, false, Transparency.OPAQUE,
472: DataBuffer.TYPE_BYTE);
473:
474: if (bpc != null) {
475: pixel_bits = bpc.length * bpc[0];
476: }
477: }
478:
479: @Override
480: public SampleModel createCompatibleSampleModel(int width,
481: int height) {
482: // workaround -- create a MultiPixelPackedSample models for
483: // single-sample, less than 8bpp color models
484: if (getNumComponents() == 1 && getPixelSize() < 8) {
485: return new MultiPixelPackedSampleModel(
486: getTransferType(), width, height,
487: getPixelSize());
488: }
489:
490: return super .createCompatibleSampleModel(width, height);
491: }
492:
493: @Override
494: public boolean isCompatibleRaster(Raster raster) {
495: if (getNumComponents() == 1 && getPixelSize() < 8) {
496: SampleModel sm = raster.getSampleModel();
497:
498: if (sm instanceof MultiPixelPackedSampleModel) {
499: return (sm.getSampleSize(0) == getPixelSize());
500: } else {
501: return false;
502: }
503: }
504:
505: return super .isCompatibleRaster(raster);
506: }
507:
508: @Override
509: public float[] getNormalizedComponents(Object pixel,
510: float[] normComponents, int normOffset) {
511: if (getDecode() == null) {
512: return super .getNormalizedComponents(pixel,
513: normComponents, normOffset);
514: }
515:
516: return normalize((byte[]) pixel, normComponents, normOffset);
517: }
518: }
519: }
|