Reputation: 43
I have two images. The second one is the result of applying some sort of mask to the first one. What I need is to get that mask and be able to apply it to other images. Here are the two images: normal, tattered.
As you can see, the second one has tattered edges, they are transparent, not white. (There's also some kind of blur going on, if there's a way I can find out what blur it is exactly, then great, but it's not really necessary here)
What I need is to be able to create the second image from the first one.
Theoretically, I should create a mask - an image of the same size with any color and each pixel having transparency of either 0 or 255, depending on the transparency value of the same pixel in the second image from above. Then I can just set alpha of pixels of any input image to alpha values from this mask.
However, I have no idea how to do it practically. I tried it in java using BufferedImage, however, it does not work. When I try to getAlpha from the color of the selected pixel, it is always 255, even for pixels which are supposed to be transparent. I did manage to get alpha values in Processing (they are actually not just 0 or 255, lots of values inbetween), however, when I try to apply this value to a new image and save it, it saves as fully opaque image, when I load it, alpha values are all 255.
PImage mask = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
color maskColor = mask.get(x, y);
if (alpha(maskColor) < 255) {
color imgColor = img.get(x, y);
img.pixels[y*img.width + x] = color(red(imgColor), green(imgColor), blue(imgColor), alpha(maskColor));
}
}
}
img.updatePixels();
img.save("some/path/3.png");
Upvotes: 3
Views: 1471
Reputation: 51837
I'm not sure why the check is necessary in your particular example. If the destination image doesn't use the alpha channel (it's all opaque), you can simply overwrite the data using the source image's alpha channel.
If you're using pixels[]
by the way, a single loop should do:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
copyAlphaChannel(withAlpha, noAlpha);
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
Update Thank you for pointing that out: I've missed this detail.
PImage
can have three formats:
RGB (=1)
ARGB (=2)
ALPHA(=4)
One workaround to convert an RGB
format PImage
into ARGB
format is to apply an opaque mask()
:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
forceAlphaChannel(noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
copyAlphaChannel(withAlpha, noAlpha);
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void forceAlphaChannel(PImage src){
// make an opaque mask
PImage mask = createImage(src.width, src.height, ALPHA);
java.util.Arrays.fill(mask.pixels, color(255));
mask.updatePixels();
// apply the mask force the RGB image into ARGB format
src.mask(mask);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
Since the above loops through pixels a bunch of times (once to create the mask and again to apply it) it may be more efficient to create an ARGB
PImage
in the first place, then copy RGB
data from one PImage
and ALPHA
from another:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
noAlpha = getAlphaChannelCopy(withAlpha, noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
// copy src alpha and dst rgb into new ARGB PImage
PImage getAlphaChannelCopy(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return null;
}
PImage out = createImage(src.width, src.height, ARGB);
// load pixel data
src.loadPixels();
dst.loadPixels();
out.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
out.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
out.updatePixels();
return out;
}
(The only minor downside here is you'd loadPixels()
thrice: once per image.)
Upvotes: 0
Reputation: 27054
Using BufferedImage
, Graphics2D
and AlphaComposite
, you can compose your image like this:
BufferedImage image = ImageIO.read(new File("image.png"));
BufferedImage mask = ImageIO.read(new File("mask.png"));
BufferedImage composed = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = composed.createGraphics();
try {
g.setComposite(AlphaComposite.Src); // Possibly faster than SrcOver
g.drawImage(image, 0, 0, null);
// Clear out the transparent parts from mask
g.setComposite(AlphaComposite.DstIn);
g.drawImage(mask, 0, 0, null);
}
finally {
g.dispose();
}
if (!ImageIO.write(composed, "PNG", new File("composed.png"))) {
throw new IIOException("Could not write image using PNG format: " + composed);
}
PS: If you know that your source image (image
in the code above) contains transparency and don't need the original afterwards, you could compose the mask directly onto it. This will be faster and use less memory, as you skip the memory allocation and extra composing.
Upvotes: 1
Reputation: 18398
You can use the tattered image as mask for other images as well. You just need the alpha information from the mask.
Implementation of creating tattered borders using BufferedImage:
public class Test {
public static void main(String[] args) throws IOException {
BufferedImage mask = loadImage("e:\\mask.png");
BufferedImage img = loadImage("e:\\1.png");
int width = mask.getWidth();
int height = mask.getHeight();
BufferedImage processed = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int rgb = mask.getRGB(x,y);
int maskAlpha = alpha(rgb);
int imgColor = img.getRGB(x, y);
if (maskAlpha < 255) {
processed.setRGB(x,y,maskAlpha(imgColor, maskAlpha));
} else {
processed.setRGB(x,y,imgColor);
}
}
}
writeImage(processed, "e:\\2.png");
}
static BufferedImage loadImage(String imagePath) {
File file = new File(imagePath);
BufferedImage image = null;
try {
image = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
static void writeImage(BufferedImage img,String filePath){
String format = filePath.substring(filePath.indexOf('.')+1);
//Get Picture Format
System.out.println(format);
try {
ImageIO.write(img,format,new File(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
static int maskAlpha(int rgb, int alpha) {
//strip alpha from original color
int color = (0x00ffffff&rgb);
return color + ((alpha)<<24);
}
static int alpha(int rgb) {
return (0xff&(rgb>>24));
}
static int red(int rgb) {
return (0xff&rgb);
}
static int green(int rgb) {
return (0xff&(rgb>>8));
}
static int blue(int rgb) {
return (0xff&(rgb>>16));
}
}
Here BufferedImage.TYPE_4BYTE_ABGR means
Represents an image with 8-bit RGBA color components with the colors Blue, Green, and Red stored in 3 bytes and 1 byte of alpha. The image has a ComponentColorModel with alpha. The color data in this image is considered not to be premultiplied with alpha. The byte data is interleaved in a single byte array in the order A, B, G, R from lower to higher byte addresses within each pixel.
That means color integer is 32 bits, which is stored in java in the order of abgr, i.e., alpha is the first 8 bits, and r is the last 8 bits, so you can get the argb value as follows:
int r = (0xff&rgb);
int g = (0xff&(rgb>>8));
int b = (0xff&(rgb>>16));
int a = (0xff&(rgb>>24));
Upvotes: 1
Reputation: 1269
You could try taking the difference between the alpha channels of the original image and the tattered image.
PImage tattered = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
PImage mask = image.copy();
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
mask[x][y] = abs(alpha(img.get(x, y)) - alpha(tattered.get(x, y)));
}
}
mask.updatePixels();
mask.save("some/path/3.png");
Upvotes: 1