Skip to content

Commit 5e73afb

Browse files
committed
required shims
1 parent 0412c61 commit 5e73afb

File tree

2 files changed

+958
-0
lines changed

2 files changed

+958
-0
lines changed
Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2+
3+
/*
4+
Part of the Processing project - http://processing.org
5+
6+
Copyright (c) 2015 The Processing Foundation
7+
8+
This library is free software; you can redistribute it and/or
9+
modify it under the terms of the GNU Lesser General Public
10+
License version 2.1 as published by the Free Software Foundation.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General
18+
Public License along with this library; if not, write to the
19+
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20+
Boston, MA 02111-1307 USA
21+
*/
22+
23+
package processing.awt;
24+
25+
import java.awt.Graphics2D;
26+
import java.awt.Image;
27+
import java.awt.RenderingHints;
28+
import java.awt.Transparency;
29+
import java.awt.image.BufferedImage;
30+
import java.awt.image.DataBuffer;
31+
import java.awt.image.DataBufferInt;
32+
import java.awt.image.PixelGrabber;
33+
import java.awt.image.WritableRaster;
34+
import java.io.BufferedOutputStream;
35+
import java.io.File;
36+
import java.io.IOException;
37+
import java.util.Iterator;
38+
39+
import javax.imageio.IIOImage;
40+
import javax.imageio.ImageIO;
41+
import javax.imageio.ImageTypeSpecifier;
42+
import javax.imageio.ImageWriteParam;
43+
import javax.imageio.ImageWriter;
44+
import javax.imageio.metadata.IIOInvalidTreeException;
45+
import javax.imageio.metadata.IIOMetadata;
46+
import javax.imageio.metadata.IIOMetadataNode;
47+
48+
import processing.core.PApplet;
49+
import processing.core.PImage;
50+
51+
52+
public class PImageAWT extends PImage {
53+
54+
/**
55+
* Construct a new PImage from a java.awt.Image. This constructor assumes
56+
* that you've done the work of making sure a MediaTracker has been used
57+
* to fully download the data and that the img is valid.
58+
*
59+
* @nowebref
60+
* @param img assumes a MediaTracker has been used to fully download
61+
* the data and the img is valid
62+
*/
63+
public PImageAWT(Image img) {
64+
format = RGB;
65+
if (img instanceof BufferedImage) {
66+
BufferedImage bi = (BufferedImage) img;
67+
width = bi.getWidth();
68+
height = bi.getHeight();
69+
int type = bi.getType();
70+
if (type == BufferedImage.TYPE_3BYTE_BGR ||
71+
type == BufferedImage.TYPE_4BYTE_ABGR) {
72+
pixels = new int[width * height];
73+
bi.getRGB(0, 0, width, height, pixels, 0, width);
74+
if (type == BufferedImage.TYPE_4BYTE_ABGR) {
75+
format = ARGB;
76+
} else {
77+
opaque();
78+
}
79+
} else {
80+
DataBuffer db = bi.getRaster().getDataBuffer();
81+
if (db instanceof DataBufferInt) {
82+
pixels = ((DataBufferInt) db).getData();
83+
if (type == BufferedImage.TYPE_INT_ARGB) {
84+
format = ARGB;
85+
} else if (type == BufferedImage.TYPE_INT_RGB) {
86+
opaque();
87+
}
88+
}
89+
}
90+
}
91+
// Implements fall-through if not DataBufferInt above, or not a
92+
// known type, or not DataBufferInt for the data itself.
93+
if (pixels == null) { // go the old school Java 1.0 route
94+
width = img.getWidth(null);
95+
height = img.getHeight(null);
96+
pixels = new int[width * height];
97+
PixelGrabber pg =
98+
new PixelGrabber(img, 0, 0, width, height, pixels, 0, width);
99+
try {
100+
pg.grabPixels();
101+
} catch (InterruptedException e) { }
102+
}
103+
pixelDensity = 1;
104+
pixelWidth = width;
105+
pixelHeight = height;
106+
}
107+
108+
109+
/**
110+
* Use the getNative() method instead, which allows library interfaces to be
111+
* written in a cross-platform fashion for desktop, Android, and others.
112+
* This is still included for PGraphics objects, which may need the image.
113+
*/
114+
public Image getImage() { // ignore
115+
return (Image) getNative();
116+
}
117+
118+
119+
/**
120+
* Returns a native BufferedImage from this PImage.
121+
*/
122+
@Override
123+
public Object getNative() { // ignore
124+
loadPixels();
125+
int type = (format == RGB) ?
126+
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
127+
BufferedImage image = new BufferedImage(pixelWidth, pixelHeight, type);
128+
WritableRaster wr = image.getRaster();
129+
wr.setDataElements(0, 0, pixelWidth, pixelHeight, pixels);
130+
return image;
131+
}
132+
133+
134+
@Override
135+
public void resize(int w, int h) { // ignore
136+
if (w <= 0 && h <= 0) {
137+
throw new IllegalArgumentException("width or height must be > 0 for resize");
138+
}
139+
140+
if (w == 0) { // Use height to determine relative size
141+
float diff = (float) h / (float) height;
142+
w = (int) (width * diff);
143+
} else if (h == 0) { // Use the width to determine relative size
144+
float diff = (float) w / (float) width;
145+
h = (int) (height * diff);
146+
}
147+
148+
BufferedImage img =
149+
shrinkImage((BufferedImage) getNative(), w*pixelDensity, h*pixelDensity);
150+
151+
PImage temp = new PImageAWT(img);
152+
this.pixelWidth = temp.width;
153+
this.pixelHeight = temp.height;
154+
155+
// Get the resized pixel array
156+
this.pixels = temp.pixels;
157+
158+
this.width = pixelWidth / pixelDensity;
159+
this.height = pixelHeight / pixelDensity;
160+
161+
// Mark the pixels array as altered
162+
updatePixels();
163+
}
164+
165+
166+
// Adapted from getFasterScaledInstance() method from page 111 of
167+
// "Filthy Rich Clients" by Chet Haase and Romain Guy
168+
// Additional modifications and simplifications have been added,
169+
// plus a fix to deal with an infinite loop if images are expanded.
170+
// http://code.google.com/p/processing/issues/detail?id=1463
171+
static private BufferedImage shrinkImage(BufferedImage img,
172+
int targetWidth, int targetHeight) {
173+
int type = (img.getTransparency() == Transparency.OPAQUE) ?
174+
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
175+
BufferedImage outgoing = img;
176+
BufferedImage scratchImage = null;
177+
Graphics2D g2 = null;
178+
int prevW = outgoing.getWidth();
179+
int prevH = outgoing.getHeight();
180+
boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
181+
182+
// Use multi-step technique: start with original size, then scale down in
183+
// multiple passes with drawImage() until the target size is reached
184+
int w = img.getWidth();
185+
int h = img.getHeight();
186+
187+
do {
188+
if (w > targetWidth) {
189+
w /= 2;
190+
// if this is the last step, do the exact size
191+
if (w < targetWidth) {
192+
w = targetWidth;
193+
}
194+
} else if (targetWidth >= w) {
195+
w = targetWidth;
196+
}
197+
if (h > targetHeight) {
198+
h /= 2;
199+
if (h < targetHeight) {
200+
h = targetHeight;
201+
}
202+
} else if (targetHeight >= h) {
203+
h = targetHeight;
204+
}
205+
if (scratchImage == null || isTranslucent) {
206+
// Use a single scratch buffer for all iterations and then copy
207+
// to the final, correctly-sized image before returning
208+
scratchImage = new BufferedImage(w, h, type);
209+
g2 = scratchImage.createGraphics();
210+
}
211+
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
212+
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
213+
g2.drawImage(outgoing, 0, 0, w, h, 0, 0, prevW, prevH, null);
214+
prevW = w;
215+
prevH = h;
216+
outgoing = scratchImage;
217+
} while (w != targetWidth || h != targetHeight);
218+
g2.dispose();
219+
220+
221+
// If we used a scratch buffer that is larger than our target size,
222+
// create an image of the right size and copy the results into it
223+
if (targetWidth != outgoing.getWidth() ||
224+
targetHeight != outgoing.getHeight()) {
225+
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
226+
g2 = scratchImage.createGraphics();
227+
g2.drawImage(outgoing, 0, 0, null);
228+
g2.dispose();
229+
outgoing = scratchImage;
230+
}
231+
return outgoing;
232+
}
233+
234+
235+
@Override
236+
protected boolean saveImpl(String filename) {
237+
if (saveImageFormats == null) {
238+
saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames();
239+
}
240+
try {
241+
if (saveImageFormats != null) {
242+
for (String saveImageFormat : saveImageFormats) {
243+
if (filename.endsWith("." + saveImageFormat)) {
244+
if (!saveImageIO(filename)) {
245+
System.err.println("Error while saving image.");
246+
return false;
247+
}
248+
return true;
249+
}
250+
}
251+
}
252+
} catch (IOException e) {
253+
}
254+
return false;
255+
}
256+
257+
258+
protected String[] saveImageFormats;
259+
260+
261+
/**
262+
* Use ImageIO functions from Java 1.4 and later to handle image save.
263+
* Various formats are supported, typically jpeg, png, bmp, and wbmp.
264+
* To get a list of the supported formats for writing, use: <BR>
265+
* <TT>println(javax.imageio.ImageIO.getReaderFormatNames())</TT>
266+
*
267+
* @path The path to which the file should be written.
268+
*/
269+
protected boolean saveImageIO(String path) throws IOException {
270+
try {
271+
int outputFormat = (format == ARGB) ?
272+
BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
273+
274+
String extension =
275+
path.substring(path.lastIndexOf('.') + 1).toLowerCase();
276+
277+
// JPEG and BMP images that have an alpha channel set get pretty unhappy.
278+
// BMP just doesn't write, and JPEG writes it as a CMYK image.
279+
// http://code.google.com/p/processing/issues/detail?id=415
280+
if (extension.equals("bmp") || extension.equals("jpg") || extension.equals("jpeg")) {
281+
outputFormat = BufferedImage.TYPE_INT_RGB;
282+
}
283+
284+
BufferedImage bimage = new BufferedImage(pixelWidth, pixelHeight, outputFormat);
285+
bimage.setRGB(0, 0, pixelWidth, pixelHeight, pixels, 0, pixelWidth);
286+
287+
File file = new File(path);
288+
289+
ImageWriter writer = null;
290+
ImageWriteParam param = null;
291+
IIOMetadata metadata = null;
292+
293+
if (extension.equals("jpg") || extension.equals("jpeg")) {
294+
if ((writer = imageioWriter("jpeg")) != null) {
295+
// Set JPEG quality to 90% with baseline optimization. Setting this
296+
// to 1 was a huge jump (about triple the size), so this seems good.
297+
// Oddly, a smaller file size than Photoshop at 90%, but I suppose
298+
// it's a completely different algorithm.
299+
param = writer.getDefaultWriteParam();
300+
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
301+
param.setCompressionQuality(0.9f);
302+
}
303+
}
304+
305+
if (extension.equals("png")) {
306+
if ((writer = imageioWriter("png")) != null) {
307+
param = writer.getDefaultWriteParam();
308+
if (false) {
309+
metadata = imageioDPI(writer, param, 100);
310+
}
311+
}
312+
}
313+
314+
if (writer != null) {
315+
try (BufferedOutputStream output = new BufferedOutputStream(PApplet.createOutput(file))) {
316+
writer.setOutput(ImageIO.createImageOutputStream(output));
317+
// writer.write(null, new IIOImage(bimage, null, null), param);
318+
writer.write(metadata, new IIOImage(bimage, null, metadata), param);
319+
writer.dispose();
320+
321+
output.flush();
322+
}
323+
return true;
324+
}
325+
// If iter.hasNext() somehow fails up top, it falls through to here
326+
return javax.imageio.ImageIO.write(bimage, extension, file);
327+
328+
} catch (IOException e) {
329+
throw new IOException("image save failed.");
330+
}
331+
}
332+
333+
334+
private ImageWriter imageioWriter(String extension) {
335+
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(extension);
336+
if (iter.hasNext()) {
337+
return iter.next();
338+
}
339+
return null;
340+
}
341+
342+
343+
private IIOMetadata imageioDPI(ImageWriter writer, ImageWriteParam param, double dpi) {
344+
// http://stackoverflow.com/questions/321736/how-to-set-dpi-information-in-an-image
345+
ImageTypeSpecifier typeSpecifier =
346+
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
347+
IIOMetadata metadata =
348+
writer.getDefaultImageMetadata(typeSpecifier, param);
349+
350+
if (!metadata.isReadOnly() && metadata.isStandardMetadataFormatSupported()) {
351+
// for PNG, it's dots per millimeter
352+
double dotsPerMilli = dpi / 25.4;
353+
354+
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
355+
horiz.setAttribute("value", Double.toString(dotsPerMilli));
356+
357+
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
358+
vert.setAttribute("value", Double.toString(dotsPerMilli));
359+
360+
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
361+
dim.appendChild(horiz);
362+
dim.appendChild(vert);
363+
364+
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
365+
root.appendChild(dim);
366+
367+
try {
368+
metadata.mergeTree("javax_imageio_1.0", root);
369+
return metadata;
370+
371+
} catch (IIOInvalidTreeException e) {
372+
System.err.println("Could not set the DPI of the output image");
373+
}
374+
}
375+
return null;
376+
}
377+
}

0 commit comments

Comments
 (0)