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: */
017:
018: /* $Id: PSImageUtils.java 512838 2007-02-28 16:42:28Z jeremias $ */
019:
020: package org.apache.xmlgraphics.ps;
021:
022: import java.awt.Dimension;
023: import java.awt.color.ColorSpace;
024: import java.awt.geom.Rectangle2D;
025: import java.awt.image.ColorModel;
026: import java.awt.image.DataBuffer;
027: import java.awt.image.Raster;
028: import java.awt.image.RenderedImage;
029: import java.io.IOException;
030: import java.io.OutputStream;
031:
032: import org.apache.xmlgraphics.util.io.ASCII85OutputStream;
033: import org.apache.xmlgraphics.util.io.Finalizable;
034: import org.apache.xmlgraphics.util.io.FlateEncodeOutputStream;
035: import org.apache.xmlgraphics.util.io.RunLengthEncodeOutputStream;
036:
037: /**
038: * Utility code for rendering images in PostScript.
039: */
040: public class PSImageUtils {
041:
042: /**
043: * Writes a bitmap image to the PostScript stream.
044: * @param img the bitmap image as a byte array
045: * @param imgDim the dimensions of the image
046: * @param imgDescription the name of the image
047: * @param targetRect the target rectangle to place the image in
048: * @param isJPEG true if "img" contains a DCT-encoded images, false if "img" contains the
049: * decoded bitmap
050: * @param colorSpace the color space of the image
051: * @param gen the PostScript generator
052: * @throws IOException In case of an I/O exception
053: */
054: public static void writeImage(byte[] img, Dimension imgDim,
055: String imgDescription, Rectangle2D targetRect,
056: boolean isJPEG, ColorSpace colorSpace, PSGenerator gen)
057: throws IOException {
058: gen.saveGraphicsState();
059: gen.writeln(gen.formatDouble(targetRect.getX()) + " "
060: + gen.formatDouble(targetRect.getY()) + " translate");
061: gen.writeln(gen.formatDouble(targetRect.getWidth()) + " "
062: + gen.formatDouble(targetRect.getHeight()) + " scale");
063:
064: gen.commentln("%AXGBeginBitmap: " + imgDescription);
065:
066: gen.writeln("{{");
067: // Template: (RawData is used for the EOF signal only)
068: // gen.write("/RawData currentfile <first filter> filter def");
069: // gen.write("/Data RawData <second filter> <third filter> [...] def");
070: if (isJPEG) {
071: gen
072: .writeln("/RawData currentfile /ASCII85Decode filter def");
073: gen.writeln("/Data RawData << >> /DCTDecode filter def");
074: } else {
075: if (gen.getPSLevel() >= 3) {
076: gen
077: .writeln("/RawData currentfile /ASCII85Decode filter def");
078: gen.writeln("/Data RawData /FlateDecode filter def");
079: } else {
080: gen
081: .writeln("/RawData currentfile /ASCII85Decode filter def");
082: gen
083: .writeln("/Data RawData /RunLengthDecode filter def");
084: }
085: }
086: writeImageCommand(imgDim, colorSpace, gen, "Data");
087: /* the following two lines could be enabled if something still goes wrong
088: * gen.write("Data closefile");
089: * gen.write("RawData flushfile");
090: */
091: gen.writeln("} stopped {handleerror} if");
092: gen.writeln(" RawData flushfile");
093: gen.writeln("} exec");
094:
095: encodeBitmap(img, isJPEG, gen);
096:
097: gen.writeln("");
098: gen.commentln("%AXGEndBitmap");
099: gen.restoreGraphicsState();
100: }
101:
102: private static void writeImageCommand(Dimension imgDim,
103: ColorSpace colorSpace, PSGenerator gen, String dataSource)
104: throws IOException {
105: boolean iscolor = colorSpace.getType() != ColorSpace.CS_GRAY;
106: prepareColorspace(colorSpace, gen);
107: gen.writeln("<< /ImageType 1");
108: gen.writeln(" /Width " + imgDim.width);
109: gen.writeln(" /Height " + imgDim.height);
110: gen.writeln(" /BitsPerComponent 8");
111: if (colorSpace.getType() == ColorSpace.TYPE_CMYK) {
112: if (false /*TODO img.invertImage()*/) {
113: gen.writeln(" /Decode [1 0 1 0 1 0 1 0]");
114: } else {
115: gen.writeln(" /Decode [0 1 0 1 0 1 0 1]");
116: }
117: } else if (iscolor) {
118: gen.writeln(" /Decode [0 1 0 1 0 1]");
119: } else {
120: gen.writeln(" /Decode [0 1]");
121: }
122: // Setup scanning for left-to-right and top-to-bottom
123: gen.writeln(" /ImageMatrix [" + imgDim.width + " 0 0 "
124: + imgDim.height + " 0 0]");
125:
126: gen.writeln(" /DataSource " + dataSource);
127: gen.writeln(">> image");
128: }
129:
130: /**
131: * Writes a bitmap image as a PostScript form enclosed by DSC resource wrappers to the
132: * PostScript file.
133: * @param img the raw bitmap data
134: * @param imgDim the dimensions of the image
135: * @param formName the name of the PostScript form to use
136: * @param imageDescription a description of the image added as a DSC Title comment
137: * @param isJPEG true if "img" contains a DCT-encoded images, false if "img" contains the
138: * decoded bitmap
139: * @param colorSpace the color space of the image
140: * @param gen the PostScript generator
141: * @return a PSResource representing the form for resource tracking
142: * @throws IOException In case of an I/O exception
143: */
144: public static PSResource writeReusableImage(byte[] img,
145: Dimension imgDim, String formName, String imageDescription,
146: boolean isJPEG, ColorSpace colorSpace, PSGenerator gen)
147: throws IOException {
148: if (gen.getPSLevel() < 2) {
149: throw new UnsupportedOperationException(
150: "Reusable images requires at least Level 2 PostScript");
151: }
152: String dataName = formName + ":Data";
153: gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, formName);
154: if (imageDescription != null) {
155: gen.writeDSCComment(DSCConstants.TITLE, imageDescription);
156: }
157:
158: String additionalFilters;
159: if (isJPEG) {
160: additionalFilters = "/ASCII85Decode filter /DCTDecode filter";
161: } else {
162: if (gen.getPSLevel() >= 3) {
163: additionalFilters = "/ASCII85Decode filter /FlateDecode filter";
164: } else {
165: additionalFilters = "/ASCII85Decode filter /RunLengthDecode filter";
166: }
167: }
168:
169: gen.writeln("/" + formName);
170: gen.writeln("<< /FormType 1");
171: gen.writeln(" /BBox [0 0 " + imgDim.width + " "
172: + imgDim.height + "]");
173: gen.writeln(" /Matrix [1 0 0 1 0 0]");
174: gen.writeln(" /PaintProc {");
175: gen.writeln(" pop");
176: gen.writeln(" gsave");
177: if (gen.getPSLevel() == 2) {
178: gen.writeln(" userdict /i 0 put"); //rewind image data
179: } else {
180: gen.writeln(" " + dataName + " 0 setfileposition"); //rewind image data
181: }
182: String dataSource;
183: if (gen.getPSLevel() == 2) {
184: dataSource = "{ " + dataName
185: + " i get /i i 1 add store } bind";
186: } else {
187: dataSource = dataName;
188: }
189: writeImageCommand(imgDim, colorSpace, gen, dataSource);
190: gen.writeln(" grestore");
191: gen.writeln(" } bind");
192: gen.writeln(">> def");
193: gen.writeln("/" + dataName + " currentfile");
194: gen.writeln(additionalFilters);
195: if (gen.getPSLevel() == 2) {
196: //Creates a data array from the inline file
197: gen
198: .writeln("{ /temp exch def ["
199: + " { temp 16384 string readstring not {exit } if } loop ] } exec");
200: } else {
201: gen.writeln("/ReusableStreamDecode filter");
202: }
203: encodeBitmap(img, isJPEG, gen);
204: gen.writeln("def");
205: gen.writeDSCComment(DSCConstants.END_RESOURCE);
206: PSResource res = new PSResource(PSResource.TYPE_FORM, formName);
207: gen.getResourceTracker().registerSuppliedResource(res);
208: return res;
209: }
210:
211: /**
212: * Paints a reusable image (previously added as a PostScript form).
213: * @param formName the name of the PostScript form implementing the image
214: * @param targetRect the target rectangle to place the image in
215: * @param gen the PostScript generator
216: * @throws IOException In case of an I/O exception
217: */
218: public static void paintReusableImage(String formName,
219: Rectangle2D targetRect, PSGenerator gen) throws IOException {
220: PSResource form = new PSResource(PSResource.TYPE_FORM, formName);
221: paintForm(form, targetRect, gen);
222: }
223:
224: /**
225: * Paints a reusable image (previously added as a PostScript form).
226: * @param form the PostScript form resource implementing the image
227: * @param targetRect the target rectangle to place the image in
228: * @param gen the PostScript generator
229: * @throws IOException In case of an I/O exception
230: */
231: public static void paintForm(PSResource form,
232: Rectangle2D targetRect, PSGenerator gen) throws IOException {
233: gen.saveGraphicsState();
234: gen.writeln(gen.formatDouble(targetRect.getX()) + " "
235: + gen.formatDouble(targetRect.getY()) + " translate");
236: gen.writeln(gen.formatDouble(targetRect.getWidth()) + " "
237: + gen.formatDouble(targetRect.getHeight()) + " scale");
238: gen.writeln(form.getName() + " execform");
239:
240: gen.getResourceTracker().notifyResourceUsageOnPage(form);
241: gen.restoreGraphicsState();
242: }
243:
244: private static void prepareColorspace(ColorSpace colorSpace,
245: PSGenerator gen) throws IOException {
246: if (colorSpace.getType() == ColorSpace.TYPE_CMYK) {
247: gen.writeln("/DeviceCMYK setcolorspace");
248: } else if (colorSpace.getType() == ColorSpace.CS_GRAY) {
249: gen.writeln("/DeviceGray setcolorspace");
250: } else {
251: gen.writeln("/DeviceRGB setcolorspace");
252: }
253: }
254:
255: private static void encodeBitmap(byte[] img, boolean isJPEG,
256: PSGenerator gen) throws IOException {
257: OutputStream out = gen.getOutputStream();
258: out = new ASCII85OutputStream(out);
259: if (isJPEG) {
260: //nop
261: } else {
262: if (gen.getPSLevel() >= 3) {
263: out = new FlateEncodeOutputStream(out);
264: } else {
265: out = new RunLengthEncodeOutputStream(out);
266: }
267: }
268: out.write(img);
269: if (out instanceof Finalizable) {
270: ((Finalizable) out).finalizeStream();
271: } else {
272: out.flush();
273: }
274: gen.newLine();
275: }
276:
277: /**
278: * Renders a bitmap image to PostScript.
279: * @param img image to render
280: * @param x x position
281: * @param y y position
282: * @param w width
283: * @param h height
284: * @param gen PS generator
285: * @throws IOException In case of an I/O problem while rendering the image
286: */
287: public static void renderBitmapImage(RenderedImage img, float x,
288: float y, float w, float h, PSGenerator gen)
289: throws IOException {
290: byte[] imgmap = getBitmapBytes(img);
291:
292: String imgName = img.getClass().getName();
293: Dimension imgDim = new Dimension(img.getWidth(), img
294: .getHeight());
295: Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);
296: boolean isJPEG = false;
297: writeImage(imgmap, imgDim, imgName, targetRect, isJPEG, img
298: .getColorModel().getColorSpace(), gen);
299: }
300:
301: private static byte[] getBitmapBytes(RenderedImage img) {
302: int[] tmpMap = getRGB(img, 0, 0, img.getWidth(), img
303: .getHeight(), null, 0, img.getWidth());
304: // Should take care of the ColorSpace and bitsPerPixel
305: byte[] bitmaps = new byte[img.getWidth() * img.getHeight() * 3];
306: for (int y = 0, my = img.getHeight(); y < my; y++) {
307: for (int x = 0, mx = img.getWidth(); x < mx; x++) {
308: int p = tmpMap[y * mx + x];
309: int r = (p >> 16) & 0xFF;
310: int g = (p >> 8) & 0xFF;
311: int b = (p) & 0xFF;
312: bitmaps[3 * (y * mx + x)] = (byte) (r & 0xFF);
313: bitmaps[3 * (y * mx + x) + 1] = (byte) (g & 0xFF);
314: bitmaps[3 * (y * mx + x) + 2] = (byte) (b & 0xFF);
315: }
316: }
317: return bitmaps;
318: }
319:
320: /**
321: * Extracts a packed RGB integer array of a RenderedImage.
322: * @param img the image
323: * @param startX the starting X coordinate
324: * @param startY the starting Y coordinate
325: * @param w the width of the cropped image
326: * @param h the height of the cropped image
327: * @param rgbArray the prepared integer array to write to
328: * @param offset offset in the target array
329: * @param scansize width of a row in the target array
330: * @return the populated integer array previously passed in as rgbArray parameter
331: */
332: public static int[] getRGB(RenderedImage img, int startX,
333: int startY, int w, int h, int[] rgbArray, int offset,
334: int scansize) {
335: Raster raster = img.getData();
336: int yoff = offset;
337: int off;
338: Object data;
339: int nbands = raster.getNumBands();
340: int dataType = raster.getDataBuffer().getDataType();
341: switch (dataType) {
342: case DataBuffer.TYPE_BYTE:
343: data = new byte[nbands];
344: break;
345: case DataBuffer.TYPE_USHORT:
346: data = new short[nbands];
347: break;
348: case DataBuffer.TYPE_INT:
349: data = new int[nbands];
350: break;
351: case DataBuffer.TYPE_FLOAT:
352: data = new float[nbands];
353: break;
354: case DataBuffer.TYPE_DOUBLE:
355: data = new double[nbands];
356: break;
357: default:
358: throw new IllegalArgumentException(
359: "Unknown data buffer type: " + dataType);
360: }
361:
362: if (rgbArray == null) {
363: rgbArray = new int[offset + h * scansize];
364: }
365:
366: ColorModel colorModel = img.getColorModel();
367: for (int y = startY; y < startY + h; y++, yoff += scansize) {
368: off = yoff;
369: for (int x = startX; x < startX + w; x++) {
370: rgbArray[off++] = colorModel.getRGB(raster
371: .getDataElements(x, y, data));
372: }
373: }
374:
375: return rgbArray;
376: }
377:
378: /**
379: * Places an EPS file in the PostScript stream.
380: * @param rawEPS byte array containing the raw EPS data
381: * @param name name for the EPS document
382: * @param x x-coordinate of viewport in millipoints
383: * @param y y-coordinate of viewport in millipoints
384: * @param w width of viewport in millipoints
385: * @param h height of viewport in millipoints
386: * @param bboxx x-coordinate of EPS bounding box in points
387: * @param bboxy y-coordinate of EPS bounding box in points
388: * @param bboxw width of EPS bounding box in points
389: * @param bboxh height of EPS bounding box in points
390: * @param gen the PS generator
391: * @throws IOException in case an I/O error happens during output
392: */
393: public static void renderEPS(byte[] rawEPS, String name, float x,
394: float y, float w, float h, float bboxx, float bboxy,
395: float bboxw, float bboxh, PSGenerator gen)
396: throws IOException {
397: gen.getResourceTracker().notifyResourceUsageOnPage(
398: PSProcSets.EPS_PROCSET);
399: gen.writeln("%AXGBeginEPS: " + name);
400: gen.writeln("BeginEPSF");
401:
402: gen.writeln(gen.formatDouble(x) + " " + gen.formatDouble(y)
403: + " translate");
404: gen.writeln("0 " + gen.formatDouble(h) + " translate");
405: gen.writeln("1 -1 scale");
406: float sx = w / bboxw;
407: float sy = h / bboxh;
408: if (sx != 1 || sy != 1) {
409: gen.writeln(gen.formatDouble(sx) + " "
410: + gen.formatDouble(sy) + " scale");
411: }
412: if (bboxx != 0 || bboxy != 0) {
413: gen.writeln(gen.formatDouble(-bboxx) + " "
414: + gen.formatDouble(-bboxy) + " translate");
415: }
416: gen.writeln(gen.formatDouble(bboxy) + " "
417: + gen.formatDouble(bboxy) + " "
418: + gen.formatDouble(bboxw) + " "
419: + gen.formatDouble(bboxh) + " re clip");
420: gen.writeln("newpath");
421:
422: PSResource res = new PSResource(PSResource.TYPE_FILE, name);
423: gen.getResourceTracker().notifyResourceUsageOnPage(res);
424: gen.writeDSCComment(DSCConstants.BEGIN_DOCUMENT, res.getName());
425: gen.writeByteArr(rawEPS);
426: gen.writeDSCComment(DSCConstants.END_DOCUMENT);
427: gen.writeln("EndEPSF");
428: gen.writeln("%AXGEndEPS");
429: }
430:
431: }
|