get less ambitious with PImage (fixes #169, #180, #200)

This commit is contained in:
Ben Fry
2021-06-15 07:06:22 -04:00
parent 45f632bd84
commit 5b42803804
5 changed files with 253 additions and 57 deletions
+9 -1
View File
@@ -92,6 +92,14 @@ public class PImageAWT extends PImage {
}
/** Set the high bits of all pixels to opaque. */
protected void opaque() {
for (int i = 0; i < pixels.length; i++) {
pixels[i] = 0xFF000000 | pixels[i];
}
}
/**
* Use the getNative() method instead, which allows library interfaces to be
* written in a cross-platform fashion for desktop, Android, and others.
@@ -153,7 +161,7 @@ public class PImageAWT extends PImage {
// "Filthy Rich Clients" by Chet Haase and Romain Guy
// Additional modifications and simplifications have been added,
// plus a fix to deal with an infinite loop if images are expanded.
// http://code.google.com/p/processing/issues/detail?id=1463
// https://github.com/processing/processing/issues/1501
static private BufferedImage shrinkImage(BufferedImage img,
int targetWidth, int targetHeight) {
int type = (img.getTransparency() == Transparency.OPAQUE) ?
+179 -13
View File
@@ -1,14 +1,8 @@
package processing.awt;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
@@ -16,10 +10,6 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.awt.DisplayMode;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import javax.imageio.IIOImage;
@@ -156,6 +146,182 @@ public class ShimAWT implements PConstants {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static public void fromNativeImage(Image img, PImage out) {
out.format = RGB;
out.pixels = null;
if (img instanceof BufferedImage) {
BufferedImage bi = (BufferedImage) img;
out.width = bi.getWidth();
out.height = bi.getHeight();
int type = bi.getType();
if (type == BufferedImage.TYPE_3BYTE_BGR ||
type == BufferedImage.TYPE_4BYTE_ABGR) {
out.pixels = new int[out.width * out.height];
bi.getRGB(0, 0, out.width, out.height, out.pixels, 0, out.width);
if (type == BufferedImage.TYPE_4BYTE_ABGR) {
out.format = ARGB;
// } else {
// opaque();
}
} else {
DataBuffer db = bi.getRaster().getDataBuffer();
if (db instanceof DataBufferInt) {
out.pixels = ((DataBufferInt) db).getData();
if (type == BufferedImage.TYPE_INT_ARGB) {
out.format = ARGB;
// } else if (type == BufferedImage.TYPE_INT_RGB) {
// opaque();
}
}
}
}
if ((out.pixels != null) && (out.format == RGB)) {
for (int i = 0; i < out.pixels.length; i++) {
out.pixels[i] |= 0xFF000000;
}
}
// Implements fall-through if not DataBufferInt above, or not a
// known type, or not DataBufferInt for the data itself.
if (out.pixels == null) { // go the old school Java 1.0 route
out.width = img.getWidth(null);
out.height = img.getHeight(null);
out.pixels = new int[out.width * out.height];
PixelGrabber pg =
new PixelGrabber(img, 0, 0, out.width, out.height, out.pixels, 0, out.width);
try {
pg.grabPixels();
} catch (InterruptedException e) { }
}
out.pixelDensity = 1;
out.pixelWidth = out.width;
out.pixelHeight = out.height;
}
// /** Set the high bits of all pixels to opaque. */
// protected void opaque() {
// for (int i = 0; i < pixels.length; i++) {
// pixels[i] = 0xFF000000 | pixels[i];
// }
// }
static public Object getNativeImage(PImage img) {
img.loadPixels();
int type = (img.format == RGB) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage image =
new BufferedImage(img.pixelWidth, img.pixelHeight, type);
WritableRaster wr = image.getRaster();
wr.setDataElements(0, 0, img.pixelWidth, img.pixelHeight, img.pixels);
return image;
}
static public void resizeImage(PImage img, int w, int h) { // ignore
if (w <= 0 && h <= 0) {
throw new IllegalArgumentException("width or height must be > 0 for resize");
}
if (w == 0) { // Use height to determine relative size
float diff = (float) h / (float) img.height;
w = (int) (img.width * diff);
} else if (h == 0) { // Use the width to determine relative size
float diff = (float) w / (float) img.width;
h = (int) (img.height * diff);
}
BufferedImage bimg =
shrinkImage((BufferedImage) img.getNative(), w*img.pixelDensity, h*img.pixelDensity);
PImage temp = new PImageAWT(bimg);
img.pixelWidth = temp.width;
img.pixelHeight = temp.height;
// Get the resized pixel array
img.pixels = temp.pixels;
img.width = img.pixelWidth / img.pixelDensity;
img.height = img.pixelHeight / img.pixelDensity;
// Mark the pixels array as altered
img.updatePixels();
}
// Adapted from getFasterScaledInstance() method from page 111 of
// "Filthy Rich Clients" by Chet Haase and Romain Guy
// Additional modifications and simplifications have been added,
// plus a fix to deal with an infinite loop if images are expanded.
// https://github.com/processing/processing/issues/1501
static private BufferedImage shrinkImage(BufferedImage img,
int targetWidth, int targetHeight) {
int type = (img.getTransparency() == Transparency.OPAQUE) ?
BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage outgoing = img;
BufferedImage scratchImage = null;
Graphics2D g2 = null;
int prevW = outgoing.getWidth();
int prevH = outgoing.getHeight();
boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
// Use multi-step technique: start with original size, then scale down in
// multiple passes with drawImage() until the target size is reached
int w = img.getWidth();
int h = img.getHeight();
do {
if (w > targetWidth) {
w /= 2;
// if this is the last step, do the exact size
if (w < targetWidth) {
w = targetWidth;
}
} else if (targetWidth >= w) {
w = targetWidth;
}
if (h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
} else if (targetHeight >= h) {
h = targetHeight;
}
if (scratchImage == null || isTranslucent) {
// Use a single scratch buffer for all iterations and then copy
// to the final, correctly-sized image before returning
scratchImage = new BufferedImage(w, h, type);
g2 = scratchImage.createGraphics();
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(outgoing, 0, 0, w, h, 0, 0, prevW, prevH, null);
prevW = w;
prevH = h;
outgoing = scratchImage;
} while (w != targetWidth || h != targetHeight);
if (g2 != null) {
g2.dispose();
}
// If we used a scratch buffer that is larger than our target size,
// create an image of the right size and copy the results into it
if (targetWidth != outgoing.getWidth() ||
targetHeight != outgoing.getHeight()) {
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
g2 = scratchImage.createGraphics();
g2.drawImage(outgoing, 0, 0, null);
g2.dispose();
outgoing = scratchImage;
}
return outgoing;
}
static protected String[] loadImageFormats; // list of ImageIO formats
@@ -509,7 +675,7 @@ public class ShimAWT implements PConstants {
/**
* @param display the display number to check
* display - the display number to check
* (1-indexed to match the Preferences dialog box)
*/
/*
+45 -26
View File
@@ -24,6 +24,7 @@
package processing.core;
import java.awt.Image;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -257,11 +258,27 @@ public class PImage implements PConstants, Cloneable {
this.width = width;
this.height = height;
this.format = format;
this.pixelDensity = factor;
pixelDensity = factor;
pixelWidth = width * pixelDensity;
pixelHeight = height * pixelDensity;
this.pixels = new int[pixelWidth * pixelHeight];
pixels = new int[pixelWidth * pixelHeight];
}
private void init(int width, int height, int format, int factor,
int[] pixels) { // ignore
this.width = width;
this.height = height;
this.format = format;
pixelDensity = factor;
// these weren't being set in 4.0a3, why? [fry 210615]
pixelWidth = width * pixelDensity;
pixelHeight = height * pixelDensity;
this.pixels = pixels;
}
@@ -287,7 +304,7 @@ public class PImage implements PConstants, Cloneable {
public PImage(int width, int height, int[] pixels,
boolean requiresCheckAlpha, PApplet parent) {
initFromPixels(width, height, pixels, RGB,1);
init(width, height, RGB,1, pixels);
this.parent = parent;
if (requiresCheckAlpha) {
@@ -298,8 +315,7 @@ public class PImage implements PConstants, Cloneable {
public PImage(int width, int height, int[] pixels,
boolean requiresCheckAlpha, PApplet parent,
int format, int factor) {
initFromPixels(width, height, pixels, format, factor);
init(width, height, format, factor, pixels);
this.parent = parent;
if (requiresCheckAlpha) {
@@ -307,17 +323,27 @@ public class PImage implements PConstants, Cloneable {
}
}
private void initFromPixels(int width, int height, int[] pixels, int format, int factor) {
this.width = width;
this.height = height;
this.format = format;
this.pixelDensity = factor;
this.pixels = pixels;
@Deprecated
public PImage(Image img) {
ShimAWT.fromNativeImage(img, this);
}
/**
* Use the getNative() method instead, which allows library interfaces to be
* written in a cross-platform fashion for desktop, Android, and others.
* This is still included for PGraphics objects, which may need the image.
*/
@Deprecated
public Image getImage() { // ignore
return (Image) getNative();
}
public Object getNative() { // ignore
return null;
// TODO temporary solution for maximum backwards compatibility
return ShimAWT.getNativeImage(this);
}
@@ -481,7 +507,8 @@ public class PImage implements PConstants, Cloneable {
* @see PImage#get(int, int, int, int)
*/
public void resize(int w, int h) { // ignore
throw new RuntimeException("resize() not implemented for this PImage type");
//throw new RuntimeException("resize() not implemented for this PImage type");
ShimAWT.resizeImage(this, w, h);
}
@@ -1033,14 +1060,6 @@ public class PImage implements PConstants, Cloneable {
}
/** Set the high bits of all pixels to opaque. */
protected void opaque() {
for (int i = 0; i < pixels.length; i++) {
pixels[i] = 0xFF000000 | pixels[i];
}
}
/**
* Optimized code for building the blur kernel.
* further optimized blur code (approx. 15% for radius=20)
@@ -3357,11 +3376,11 @@ int testFunction(int dst, int src) {
* @param path must be a full path (not relative or simply a filename)
*/
protected boolean saveImpl(String path) {
// TODO Imperfect/temporary solution for alpha 2.
// TODO Imperfect/temporary solution for current 4.x releases
// https://github.com/processing/processing4/wiki/Exorcising-AWT
if (!PApplet.disableAWT) {
return ShimAWT.saveImage(this, path);
}
return false;
//if (!PApplet.disableAWT) { // TODO necessary? will this trigger NEWT?
return ShimAWT.saveImage(this, path);
//}
//return false;
}
}