I have been trying to implement a box blur algorithm in android.
The code seems to be fine but when trying to apply it, some areas in the blurred image have big yellow and white smudges all over the blurred photo.
Can anyone help me find out what i'm doing wrong?
Thanks:
Here is what i have:
public static Bitmap boxBlur(Bitmap bmp, int range) {
assert (range & 1) == 0 : "Range must be odd.";
Bitmap blurred = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(),
Config.ARGB_8888);
Canvas c = new Canvas(blurred);
int w = bmp.getWidth();
int h = bmp.getHeight();
int[] pixels = new int[bmp.getWidth() * bmp.getHeight()];
bmp.getPixels(pixels, 0, w, 0, 0, w, h);
boxBlurHorizontal(pixels, w, h, range / 2);
boxBlurVertical(pixels, w, h, range / 2);
c.drawBitmap(pixels, 0, w, 0.0F, 0.0F, w, h, true, null);
return blurred;
}
private static void boxBlurHorizontal(int[] pixels, int w, int h,
int halfRange) {
int index = 0;
int[] newColors = new int[w];
for (int y = 0; y < h; y++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
for (int x = -halfRange; x < w; x++) {
int oldPixel = x - halfRange - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixel];
if (color != 0) {
r -= Color.red(color);
g -= Color.green(color);
b -= Color.blue(color);
}
hits--;
}
int newPixel = x + halfRange;
if (newPixel < w) {
int color = pixels[index + newPixel];
if (color != 0) {
r += Color.red(color);
g += Color.green(color);
b += Color.blue(color);
}
hits++;
}
if (x >= 0) {
newColors[x] = Color.argb(0xFF, (byte) (r / hits),
(byte) (g / hits), (byte) (b / hits));
}
}
for (int x = 0; x < w; x++) {
pixels[index + x] = newColors[x];
}
index += w;
}
}
private static void boxBlurVertical(int[] pixels, int w, int h,
int halfRange) {
int[] newColors = new int[h];
int oldPixelOffset = -(halfRange + 1) * w;
int newPixelOffset = (halfRange) * w;
for (int x = 0; x < w; x++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
int index = -halfRange * w + x;
for (int y = -halfRange; y < h; y++) {
int oldPixel = y - halfRange - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixelOffset];
if (color != 0) {
r -= Color.red(color);
g -= Color.green(color);
b -= Color.blue(color);
}
hits--;
}
int newPixel = y + halfRange;
if (newPixel < h) {
int color = pixels[index + newPixelOffset];
if (color != 0) {
r += Color.red(color);
g += Color.green(color);
b += Color.blue(color);
}
hits++;
}
if (y >= 0) {
newColors[y] = Color.argb(0xFF, (byte) (r / hits),
(byte) (g / hits), (byte) (b / hits));
}
index += w;
}
for (int y = 0; y < h; y++) {
pixels[y * w + x] = newColors[y];
}
}
}
Found the problem!
The line:
newColors[x] = Color.argb(0xFF, (byte) (r / hits), (byte) (g / hits), (byte) (b / hits));
I converted the averages to bytes, where they should have been ints.
Changed it to:
newColors[x] = Color.argb(0xFF, (int) (r / hits), (int) (g / hits), (int) (b / hits));
Related
I have blured an image. But it is not smooth.
I heard that if I use the gaussian blur technique then I can remove the box effect.
But I don't know how to implement it with my code (I did some random technique but it messes with the color). Can you suggest me how to do gaussian blur with my code?
public class BlurImageDemo {
Color c[];
BlurImageDemo() throws IOException, InterruptedException {
File f = new File("D:\\x.jpg");
BufferedImage im = ImageIO.read(f);
BufferedImage bi = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
int i = 0;
int max = 400, radius = 10;
int a1 = 0, r1 = 0, g1 = 0, b1 = 0;
c = new Color[max];
int x = 1, y = 1, x1, y1, ex = 5, d = 0;
for (x = radius; x < im.getHeight() - radius; x++) {
for (y = radius; y < im.getWidth() - radius; y++) {
//20x20 matrix
for (x1 = x - radius; x1 < x + radius; x1++) {
for (y1 = y - radius; y1 < y + radius; y1++) {
c[i++] = new Color(im.getRGB(y1, x1));
//System.out.println(i);
}
}
i = 0;
for (d = 0; d < max; d++) {
a1 = a1 + c[d].getAlpha();
}
a1 = a1 / (max);
for (d = 0; d < max; d++) {
r1 = r1 + c[d].getRed();
}
r1 = r1 / (max);
for (d = 0; d < max; d++) {
g1 = g1 + c[d].getGreen();
}
g1 = g1 / (max);
for (d = 0; d < max; d++) {
b1 = b1 + c[d].getBlue();
}
b1 = b1 / (max);
int sum1 = (a1 << 24) + (r1 << 16) + (g1 << 8) + b1;
bi.setRGB(y, x, (int) (sum1));
}
}
ImageIO.write(bi, "jpg", new File("D:\\x1.jpg"));
}
public static void main(String[] args) throws IOException, InterruptedException {
new BlurImageDemo();
}
}
One really nice property of Gaussian blur is that it is separable, meaning that it can be expressed as the composition of a purely horizontal and a purely vertical blur. The advantage of doing that is that, per pixel, it them takes 2N multiplications (N is the size of the kernel), whereas the 2D non-separated version takes N2 multiplications. For N=7 (as you have), that's already a decent difference. It also has a small downside, the intermediate result is either rounded (losing some precision) or big (3 floats per pixel instead of 1 int), usually a little rounding is not a problem though.
An other thing, more of an implementation detail, is that the division by the total weight of the kernel can be put into the kernel itself, saving a whole bunch of (pretty slow) divisions.
Also your kernel doesn't actually look like a Gaussian, it's too "pointy". That's up to you, but the Gaussian kernel is the only circularly symmetric one that is also separable (if you only look at real-valued kernels) and generally has nice properties, so I would recommend only deviating from it if there is a good reason for it.
Anyway I'll write some example code now, not tested:
BufferedImage transposedHBlur(BufferedImage im) {
int height = im.getHeight();
int width = im.getWidth();
// result is transposed, so the width/height are swapped
BufferedImage temp = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
float[] k = new float[7] { 0.00598, 0.060626, 0.241843, 0.383103, 0.241843, 0.060626, 0.00598 };
// horizontal blur, transpose result
for (int y = 0; y < height; y++) {
for (int x = 3; x < width - 3; x++) {
float r = 0, g = 0, b = 0;
for (int i = 0; i < 7; i++) {
int pixel = im.getRGB(x + i - 3, y);
b += (pixel & 0xFF) * k[i];
g += ((pixel >> 8) & 0xFF) * k[i];
r += ((pixel >> 16) & 0xFF) * k[i];
}
int p = (int)b + ((int)g << 8) + ((int)r << 16);
// transpose result!
temp.setRGB(y, x, p);
}
}
return temp;
}
Since it also transposes, you can simply call it twice and the second time will effectively be a vertical blur that also restores the orientation:
temp = transposedHBlur(input);
result = transposedHBlur(temp);
This is the best article I ever found:
Java Image Processing
I got the answer. here it is.
public class BlurImageDemo {
Color c[];
BlurImageDemo() throws IOException, InterruptedException {
File f = new File("D:\\p.jpg");
BufferedImage im = ImageIO.read(f);
BufferedImage bi = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
int i = 0;
int max = 49, radius = 3;
int a1 = 0, r1 = 0, g1 = 0, b1 = 0,t=0;
c = new Color[max];
float xx[] = {1,1,1,1,1,1,1,
1,3,3,3,3,3,1,
1,3,4,4,4,3,1,
1,3,4,15,4,3,1,
1,3,4,4,4,3,1,
1,3,3,3,3,3,1,
1,1,1,1,1,1,1,
};
float h=0;
for(t=0;t<xx.length;t++){
h+=xx[t];
}
System.out.println(h);
int x = 1, y = 1, x1, y1, ex = 5, d = 0, ll = 0;
for (x = radius;x < im.getHeight()- radius; x++) {
for (y = radius; y < im.getWidth() - radius; y++) {
for (x1 = x - radius; x1 <= x + radius; x1++) {
for (y1 = y - radius; y1 <= y + radius; y1++) {
c[i] = new Color(im.getRGB(y1, x1));
//System.out.println(i);
//ll+=xx[i];
//System.out.println(ll);
i++;
}
}
i = 0;
ll = 0;
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getAlpha();
a1 = (int) (a1 + o);
}
a1 = (int) (a1 / h);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getRed();
r1 = (int) (r1 + o);
}
r1 = (int) (r1 / h);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getGreen();
g1 = (int) (g1 + o);
}
g1 = (int) (g1 / h);
//System.out.println(g1);
for (d = 0; d < max; d++) {
float o = xx[d] * c[d].getBlue();
//System.out.println(o);
b1 = (int) (b1 + o);
}
b1 = (int) (b1 / h);
int sum1 = (r1 << 16) + (g1 << 8) + b1;
bi.setRGB(y, x, sum1);
r1 = g1 = b1 = 0;
//System.out.println(new Color(sum1));
}
}
ImageIO.write(bi,
"jpg", new File("D:\\p2.jpg"));
}
public static void main(String[] args) throws IOException, InterruptedException {
new BlurImageDemo();
}
}
I want a 16x16 monochromatic sprite to be displayed on my screen. The sprite has 8 different shades, which correspond to 8 different colours. Each color can have his own shade as well, but I want to limit that to 8. So instead of FF00FF, I want it to be like 707.
I've got the project on github
What I've got so far is as follows:
This is where I create all the possible colors:
int[] colours = new int[8 * 8 * 8];
int index = 0;
for(int r = 0; r < 8; r++) {
for(int g = 0; g < 8; g++) {
for(int b = 0; b < 8; b++) {
int rr = r * 255 / 7;
int gg = g * 255 / 7;
int bb = b * 255 / 7;
colours[index++] = rr << 16 | gg << 8 | bb;
}
}
}
This is where I want to call a long int containing all the color information:
public class Colours {
public static int get(int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8) {
//bit shifting with 56 on an integer removes data
return((get(c8) << 28) + (get(c7) << 24) + (get(c6) << 20) + (get(c5) >> 16) +
(get(c4) << 12) + (get(c3) << 8) + (get(c2) << 4) + (get(c1)));
}
private static int get(int colour) {
if(colour < 0) {
return 255;
}
int r = colour / 100 % 10;
int g = colour / 10 % 10;
int b = colour % 10;
return r * 64 + g * 8 + b;
}
}
This is where the screen gets rendered:
public void render(int xPos, int yPos, int tile, int colour) {
xPos -= xOffset;
yPos -= yOffset;
int xTile = tile % 32;
int yTile = tile / 32;
int tileOffset = (xTile << 4) + (yTile << 4) * sheet.width;
for (int y = 0; y < 16; y++) {
if (y + yPos < 0 || y + yPos >= height) {
continue;
}
int ySheet = y;
for (int x = 0; x < 16; x++) {
if (x + xPos < 0 || x + xPos >= width) {
continue;
}
int xSheet = x;
int col = (colour >> (sheet.pixels[xSheet + ySheet * sheet.width + tileOffset] * 16)) & 255;
if (col < 255) {
pixels[(x + xPos) + (y + yPos) * width] = col;
}
}
}
}
I fixed it by returning an integer array.
I am currently making a game in Java and I am trying to draw an image on my screen, but nothing show up ( only a black screen but no errors ) :(
Here is the code to import the image:
public static Bitmap loadBitmap(String fileName) {
try {
BufferedImage img = ImageIO.read(Art.class.getResource(fileName));
int w = img.getWidth();
int h = img.getHeight();
Bitmap result = new Bitmap(w, h);
img.getRGB(0, 0, w, h, result.pixels, 0, w);
for (int I = 0; I < result.pixels.length; i++) {
int in = result.pixels[i];
int col = (in & 0xf) >> 2;
if (in == 0xffff00ff) col = -1;
result.pixels[i] = col;
}
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
And the Bitmap class:
public void draw(Bitmap bitmap, int xOffs, int yOffs)
{
for(int y = 0; y < bitmap.height; y++)
{
int yPix = y + yOffs;
if(yPix < 0 || yPix >= height) continue;
for(int x = 0; x < bitmap.width; x++)
{
int xPix = x + xOffs;
if(xPix < 0 || xPix >= width) continue;
int alpha = bitmap.pixels[x + y * bitmap.width];
if(alpha > 0)
pixels[xPix + yPix * width] = bitmap.pixels[x + y * bitmap.width];
}
}
}
And to draw all of this :
public void render(Game game)
{
for(int y = 0; y < height; y++)
{
float yd = ((y + 0.5f) - height / 2.0f) / height;
if(yd < 0) yd *= -1;
float z = 10 / yd;
for(int x = 0; x < width; x++)
{
float xd = (x - width / 2.0f) / height;
xd *= z;
int xx = (int) (xd) & 7;
int yy = (int) (z + game.time * 0.1f) & 7;
pixels[x + y * width] = Art.floors.pixels[xx + yy * 64];
}
}
}
I have no errors! I don't really understand.. is this a bug caused by alpha or something? Ho and my image.png is 64x64 made in paint.net
I am looking for a copy paste implementation of Canny Edge Detection in the processing language. I have zero idea about Image processing and very little clue about Processing, though I understand java pretty well.
Can some processing expert tell me if there is a way of implementing this http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java in processing?
I think if you treat processing in lights of Java then some of the problems could be solved very easily. What it means is that you can use Java classes as such.
For the demo I am using the implementation which you have shared.
>>Original Image
>>Changed Image
>>Code
import java.awt.image.BufferedImage;
import java.util.Arrays;
PImage orig;
PImage changed;
void setup() {
orig = loadImage("c:/temp/image.png");
size(250, 166);
CannyEdgeDetector detector = new CannyEdgeDetector();
detector.setLowThreshold(0.5f);
detector.setHighThreshold(1f);
detector.setSourceImage((java.awt.image.BufferedImage)orig.getImage());
detector.process();
BufferedImage edges = detector.getEdgesImage();
changed = new PImage(edges);
noLoop();
}
void draw()
{
//image(orig, 0,0, width, height);
image(changed, 0,0, width, height);
}
// The code below is taken from "http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java"
// I have stripped the comments for conciseness
public class CannyEdgeDetector {
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
public BufferedImage getSourceImage() {
return sourceImage;
}
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
public BufferedImage getEdgesImage() {
return edgesImage;
}
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
public float getLowThreshold() {
return lowThreshold;
}
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
public float getHighThreshold() {
return highThreshold;
}
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
public boolean isContrastNormalized() {
return contrastNormalized;
}
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
}
I've been spending some time with the Gibara Canny implementation and I'm inclined to agree with Settembrini's comment above; further to this one needs to change the implementation of the Gaussian Kernel generation.
The Gibara Canny uses:
(g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius)
The averaging across a pixel (+-0.5 pixels) in (g1 + g2 + g3) / 3f is great, but the correct variance calculation on the bottom half of the equation for single dimensions is:
(g1 + g2 + g3) / 3f / (Math.sqrt(2f * (float) Math.PI) * kernelRadius)
The standard deviation kernelRadius is sigma in the following equation:
Single direction gaussian
I'm assuming that Gibara is attempting to implement the two dimensional gaussian from the following equation: Two dimensional gaussian where the convolution is a direct product of each gaussian. Whilst this is probably possible and more concise, the following code will correctly convolve in two directions with the above variance calculation:
// First Convolution
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
int index = x + y;
float sumX = data[index] * kernel[0];
int xOffset = 1;
int yOffset = sourceImage.width;
for(; xOffset < k ;) {;
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += sourceImage.width;
xOffset++;
}
xConv[index] = sumX;
}
}
// Second Convolution
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
int index = x + y;
float sumY = xConv[index] * kernel[0];
int xOffset = 1;
int yOffset = sourceImage.width;
for(; xOffset < k ;) {;
sumY += xConv[xOffset] * (xConv[index - xOffset] + xConv[index + xOffset]);
yOffset += sourceImage.width;
xOffset++;
}
yConv[index] = sumY;
}
}
NB the yConv[] is now the bidirectional convolution, so the following gradient Sobel calculations are as follows:
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < k; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = k; x < sourceImage.width - k; x++) {
for (int y = initY; y < maxY; y += sourceImage.width) {
float sum = 0.0f;
int index = x + y;
int yOffset = sourceImage.width;
for (int i = 1; i < k; i++) {
sum += diffKernel[i] * (yConv[index - yOffset] - yConv[index + yOffset]);
yOffset += sourceImage.width;
}
yGradient[index] = sum;
}
}
Gibara's very neat implementation of non-maximum suppression requires that these gradients be calculated seperately, however if you want to output an image with these gradients one can sum them using either Euclidean or Manhattan distances, the Euclidean would look like so:
// Calculate the Euclidean distance between x & y gradients prior to suppression
int [] gradients = new int [picsize];
for (int i = 0; i < xGradient.length; i++) {
gradients[i] = Math.sqrt(Math.sq(xGradient[i]) + Math.sq(yGradient[i]));
}
Hope this helps, is all in order and apologies for my code! Critique most welcome
In addition to Favonius' answer, you might want to try Greg's OpenCV Processing library which you can now easily install via Sketch > Import Library... > Add Library... and select OpenCV for Processing
After you install the library, you can have a play with the FindEdges example:
import gab.opencv.*;
OpenCV opencv;
PImage src, canny, scharr, sobel;
void setup() {
src = loadImage("test.jpg");
size(src.width, src.height);
opencv = new OpenCV(this, src);
opencv.findCannyEdges(20,75);
canny = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findScharrEdges(OpenCV.HORIZONTAL);
scharr = opencv.getSnapshot();
opencv.loadImage(src);
opencv.findSobelEdges(1,0);
sobel = opencv.getSnapshot();
}
void draw() {
pushMatrix();
scale(0.5);
image(src, 0, 0);
image(canny, src.width, 0);
image(scharr, 0, src.height);
image(sobel, src.width, src.height);
popMatrix();
text("Source", 10, 25);
text("Canny", src.width/2 + 10, 25);
text("Scharr", 10, src.height/2 + 25);
text("Sobel", src.width/2 + 10, src.height/2 + 25);
}
Just as I side note. I studied the Gibara Canny implementation some time ago and found some flaws. E.g. he separates the Gauss-Filtering in 1d filters in x and y direction (which is ok and efficient as such), but then he doesn't apply two passes of those filters (one after another) but just applies SobelX to the x-first-pass-Gauss and SobelY to the y-first-pass-Gauss, which of course leads to low quality gradients. Thus be careful just by copy-past such code.
I'm working on some code to colorize an image in Java. Basically what I'd like to do is something along the lines of GIMP's colorize command, so that if I have a BufferedImage and a Color, I can colorize the Image with the given color. Anyone got any ideas? My current best guess at doing something like this is to get the rgb value of each pixel in the BufferedImage and add the RGB value of the Color to it with some scaling factor.
Let Y = 0.3*R + 0.59*G + 0.11*B for each pixel in the image, then set them to be
((R1+Y)/2,(G1+Y)/2,(B1+Y)/2)
if (R1,G1,B1) is what you are colorizing with.
I have never used GIMP's colorize command. However, if your getting the RGB value of each pixel and adding RGB value to it you should really use a LookupOp. Here is some code that I wrote to apply a BufferedImageOp to a BufferedImage.
Using Nicks example from above heres how I would do it.
Let Y = 0.3*R + 0.59*G + 0.11*B for
each pixel
(R1,G1,B1) is what you are colorizing
with
protected LookupOp createColorizeOp(short R1, short G1, short B1) {
short[] alpha = new short[256];
short[] red = new short[256];
short[] green = new short[256];
short[] blue = new short[256];
int Y = 0.3*R + 0.59*G + 0.11*B
for (short i = 0; i < 256; i++) {
alpha[i] = i;
red[i] = (R1 + i*.3)/2;
green[i] = (G1 + i*.59)/2;
blue[i] = (B1 + i*.11)/2;
}
short[][] data = new short[][] {
red, green, blue, alpha
};
LookupTable lookupTable = new ShortLookupTable(0, data);
return new LookupOp(lookupTable, null);
}
It creates a BufferedImageOp that will mask out each color if the mask boolean is true.
Its simple to call too.
BufferedImageOp colorizeFilter = createColorizeOp(R1, G1, B1);
BufferedImage targetImage = colorizeFilter.filter(sourceImage, null);
If this is not what your looking for I suggest you look more into BufferedImageOp's.
This is would also be more efficient since you would not need to do the calculations multiple times on different images. Or do the calculations over again on different BufferedImages as long as the R1,G1,B1 values don't change.
This works exactly like the Colorize function in GIMP and it preserves the transparency. I've also added a few things like Contrast and Brightness, Hue, Sat, and Luminosity - 0circle0 Google Me --> ' Sprite Creator 3'
import java.awt.Color;
import java.awt.image.BufferedImage;
public class Colorizer
{
public static final int MAX_COLOR = 256;
public static final float LUMINANCE_RED = 0.2126f;
public static final float LUMINANCE_GREEN = 0.7152f;
public static final float LUMINANCE_BLUE = 0.0722f;
double hue = 180;
double saturation = 50;
double lightness = 0;
int[] lum_red_lookup;
int[] lum_green_lookup;
int[] lum_blue_lookup;
int[] final_red_lookup;
int[] final_green_lookup;
int[] final_blue_lookup;
public Colorizer()
{
doInit();
}
public void doHSB(double t_hue, double t_sat, double t_bri, BufferedImage image)
{
hue = t_hue;
saturation = t_sat;
lightness = t_bri;
doInit();
doColorize(image);
}
private void doInit()
{
lum_red_lookup = new int[MAX_COLOR];
lum_green_lookup = new int[MAX_COLOR];
lum_blue_lookup = new int[MAX_COLOR];
double temp_hue = hue / 360f;
double temp_sat = saturation / 100f;
final_red_lookup = new int[MAX_COLOR];
final_green_lookup = new int[MAX_COLOR];
final_blue_lookup = new int[MAX_COLOR];
for (int i = 0; i < MAX_COLOR; ++i)
{
lum_red_lookup[i] = (int) (i * LUMINANCE_RED);
lum_green_lookup[i] = (int) (i * LUMINANCE_GREEN);
lum_blue_lookup[i] = (int) (i * LUMINANCE_BLUE);
double temp_light = (double) i / 255f;
Color color = new Color(Color.HSBtoRGB((float) temp_hue, (float) temp_sat, (float) temp_light));
final_red_lookup[i] = (int) (color.getRed());
final_green_lookup[i] = (int) (color.getGreen());
final_blue_lookup[i] = (int) (color.getBlue());
}
}
public void doColorize(BufferedImage image)
{
int height = image.getHeight();
int width;
while (height-- != 0)
{
width = image.getWidth();
while (width-- != 0)
{
Color color = new Color(image.getRGB(width, height), true);
int lum = lum_red_lookup[color.getRed()] + lum_green_lookup[color.getGreen()] + lum_blue_lookup[color.getBlue()];
if (lightness > 0)
{
lum = (int) ((double) lum * (100f - lightness) / 100f);
lum += 255f - (100f - lightness) * 255f / 100f;
}
else if (lightness < 0)
{
lum = (int) (((double) lum * (lightness + 100f)) / 100f);
}
Color final_color = new Color(final_red_lookup[lum], final_green_lookup[lum], final_blue_lookup[lum], color.getAlpha());
image.setRGB(width, height, final_color.getRGB());
}
}
}
public BufferedImage changeContrast(BufferedImage inImage, float increasingFactor)
{
int w = inImage.getWidth();
int h = inImage.getHeight();
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color color = new Color(inImage.getRGB(i, j), true);
int r, g, b, a;
float fr, fg, fb;
r = color.getRed();
fr = (r - 128) * increasingFactor + 128;
r = (int) fr;
r = keep256(r);
g = color.getGreen();
fg = (g - 128) * increasingFactor + 128;
g = (int) fg;
g = keep256(g);
b = color.getBlue();
fb = (b - 128) * increasingFactor + 128;
b = (int) fb;
b = keep256(b);
a = color.getAlpha();
outImage.setRGB(i, j, new Color(r, g, b, a).getRGB());
}
}
return outImage;
}
public BufferedImage changeGreen(BufferedImage inImage, int increasingFactor)
{
int w = inImage.getWidth();
int h = inImage.getHeight();
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color color = new Color(inImage.getRGB(i, j), true);
int r, g, b, a;
r = color.getRed();
g = keep256(color.getGreen() + increasingFactor);
b = color.getBlue();
a = color.getAlpha();
outImage.setRGB(i, j, new Color(r, g, b, a).getRGB());
}
}
return outImage;
}
public BufferedImage changeBlue(BufferedImage inImage, int increasingFactor)
{
int w = inImage.getWidth();
int h = inImage.getHeight();
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color color = new Color(inImage.getRGB(i, j), true);
int r, g, b, a;
r = color.getRed();
g = color.getGreen();
b = keep256(color.getBlue() + increasingFactor);
a = color.getAlpha();
outImage.setRGB(i, j, new Color(r, g, b, a).getRGB());
}
}
return outImage;
}
public BufferedImage changeRed(BufferedImage inImage, int increasingFactor)
{
int w = inImage.getWidth();
int h = inImage.getHeight();
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color color = new Color(inImage.getRGB(i, j), true);
int r, g, b, a;
r = keep256(color.getRed() + increasingFactor);
g = color.getGreen();
b = color.getBlue();
a = color.getAlpha();
outImage.setRGB(i, j, new Color(r, g, b, a).getRGB());
}
}
return outImage;
}
public BufferedImage changeBrightness(BufferedImage inImage, int increasingFactor)
{
int w = inImage.getWidth();
int h = inImage.getHeight();
BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color color = new Color(inImage.getRGB(i, j), true);
int r, g, b, a;
r = keep256(color.getRed() + increasingFactor);
g = keep256(color.getGreen() + increasingFactor);
b = keep256(color.getBlue() + increasingFactor);
a = color.getAlpha();
outImage.setRGB(i, j, new Color(r, g, b, a).getRGB());
}
}
return outImage;
}
public int keep256(int i)
{
if (i <= 255 && i >= 0)
return i;
if (i > 255)
return 255;
return 0;
}
}
I wanted to do the exact same thing as the question poster wanted to do but the above conversion did not remove colors like the GIMP does (ie green with a red overlay made an unpleasant brown color etc). So I downloaded the source code for GIMP and converted the c code over to Java.
Posting it in this thread just in case anyone else wants to do the same (since it is the first thread that comes up in Google). The conversion still changes the white color when it should not, it's probably a casting issue from double to int. The class converts a BufferedImage in-place.
public class Colorize {
public static final int MAX_COLOR = 256;
public static final float LUMINANCE_RED = 0.2126f;
public static final float LUMINANCE_GREEN = 0.7152f;
public static final float LUMINANCE_BLUE = 0.0722f;
double hue = 180;
double saturation = 50;
double lightness = 0;
int [] lum_red_lookup;
int [] lum_green_lookup;
int [] lum_blue_lookup;
int [] final_red_lookup;
int [] final_green_lookup;
int [] final_blue_lookup;
public Colorize( int red, int green, int blue )
{
doInit();
}
public Colorize( double t_hue, double t_sat, double t_bri )
{
hue = t_hue;
saturation = t_sat;
lightness = t_bri;
doInit();
}
public Colorize( double t_hue, double t_sat )
{
hue = t_hue;
saturation = t_sat;
doInit();
}
public Colorize( double t_hue )
{
hue = t_hue;
doInit();
}
public Colorize()
{
doInit();
}
private void doInit()
{
lum_red_lookup = new int [MAX_COLOR];
lum_green_lookup = new int [MAX_COLOR];
lum_blue_lookup = new int [MAX_COLOR];
double temp_hue = hue / 360f;
double temp_sat = saturation / 100f;
final_red_lookup = new int [MAX_COLOR];
final_green_lookup = new int [MAX_COLOR];
final_blue_lookup = new int [MAX_COLOR];
for( int i = 0; i < MAX_COLOR; ++i )
{
lum_red_lookup [i] = ( int )( i * LUMINANCE_RED );
lum_green_lookup[i] = ( int )( i * LUMINANCE_GREEN );
lum_blue_lookup [i] = ( int )( i * LUMINANCE_BLUE );
double temp_light = (double)i / 255f;
Color color = new Color( Color.HSBtoRGB( (float)temp_hue,
(float)temp_sat,
(float)temp_light ) );
final_red_lookup [i] = ( int )( color.getRed() );
final_green_lookup[i] = ( int )( color.getGreen() );
final_blue_lookup [i] = ( int )( color.getBlue() );
}
}
public void doColorize( BufferedImage image )
{
int height = image.getHeight();
int width;
while( height-- != 0 )
{
width = image.getWidth();
while( width-- != 0 )
{
Color color = new Color( image.getRGB( width, height ) );
int lum = lum_red_lookup [color.getRed ()] +
lum_green_lookup[color.getGreen()] +
lum_blue_lookup [color.getBlue ()];
if( lightness > 0 )
{
lum = (int)((double)lum * (100f - lightness) / 100f);
lum += 255f - (100f - lightness) * 255f / 100f;
}
else if( lightness < 0 )
{
lum = (int)(((double)lum * lightness + 100f) / 100f);
}
Color final_color = new Color( final_red_lookup[lum],
final_green_lookup[lum],
final_blue_lookup[lum],
color.getAlpha() );
image.setRGB( width, height, final_color.getRGB() );
}
}
}