I'm trying fill my Ellipse, the code works although I was wondering if there is a more efficient approach.
This will fill the first half of the circle given a percentage. And then it will fill the second half of the circle.
Let me know if you want to see any other functions. I was mainly concerned about filling it.
public void drawOrb() {
this.icon.drawSprite(this.xPos - this.icon.getWidth() / 2, 29 - this.icon.getHeight() / 2);
int radius = 19;
fillCircleAlpha(this.xPos, this.yPos, radius, 0, 35); // Draws a filled circle given a radius and alpha value.
Ellipse2D.Double circleToAvoid = drawCircle(this.xPos - radius, this.yPos - radius, radius * 2, 0, //The inner circle.
125);
Ellipse2D.Double circleToStart = drawCircle(this.xPos - (radius + 4), this.yPos - (radius + 4),
radius * 2 + 8, 0, 150); // The outer circle.
radius = 23;
int r2 = radius * radius;
int area = r2 << 2;
int rr = radius << 1;
for (int area2 = (int) (area * progress * 2.0), i = 0; i < area2; ++i) { //
int tx = i % rr;
int ty = i / rr;
if (!circleToAvoid.contains(circleToStart.getCenterX() + tx, circleToStart.getY() + ty) //If the index is inside the circle.
&& circleToStart.contains(circleToStart.getCenterX() + tx, circleToStart.getY() + ty)) {
drawPixelsWithOpacity(16777215, this.yPos + ty - radius, 1, 1, 75, this.xPos + tx); // Used to fill each pixel within the circle.
}
}
if (progress > 0.5) {
for (int area3 = (int) (area * (progress - 0.5) * 2.0), j = 0; j < area3; ++j) {
int tx2 = j % rr;
int ty2 = j / rr;
if (!circleToAvoid.contains(circleToStart.getCenterX() - tx2, circleToStart.getMaxY() - ty2)
&& circleToStart.contains(circleToStart.getCenterX() - tx2 - 1.0,
circleToStart.getMaxY() - ty2)) {
drawPixelsWithOpacity(16777215, (int) circleToStart.getMaxY() - ty2, 1, 1, 75,
(int) circleToStart.getCenterX() - tx2 - 1);
}
}
}
radius = 19;
drawCircle(this.xPos - (radius + 4), this.yPos - (radius + 4), radius * 2 + 8, 0, 150);
}
public static void drawPixelsWithOpacity(int color, int yPos, int pixelWidth, int pixelHeight, int opacityLevel, int xPos) {
if (xPos < topX) {
pixelWidth -= topX - xPos;
xPos = topX;
}
if (yPos < topY) {
pixelHeight -= topY - yPos;
yPos = topY;
}
if (xPos + pixelWidth > bottomX)
pixelWidth = bottomX - xPos;
if (yPos + pixelHeight > bottomY)
pixelHeight = bottomY - yPos;
int l1 = 256 - opacityLevel;
int i2 = (color >> 16 & 0xff) * opacityLevel;
int j2 = (color >> 8 & 0xff) * opacityLevel;
int k2 = (color & 0xff) * opacityLevel;
int k3 = width - pixelWidth;
int l3 = xPos + yPos * width;
if (l3 > pixels.length - 1) {
l3 = pixels.length - 1;
}
for (int i4 = 0; i4 < pixelHeight; i4++) {
for (int j4 = -pixelWidth; j4 < 0; j4++) {
int l2 = (pixels[l3] >> 16 & 0xff) * l1;
int i3 = (pixels[l3] >> 8 & 0xff) * l1;
int j3 = (pixels[l3] & 0xff) * l1;
int k4 = ((i2 + l2 >> 8) << 16) + ((j2 + i3 >> 8) << 8) + (k2 + j3 >> 8);
pixels[l3++] = k4;
}
l3 += k3;
}
}
public static Ellipse2D.Double drawCircle(final int x, final int y, final int diameter, final int color, final int opacity) {
final Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
for (int i = 0; i < diameter; ++i) {
for (int i2 = 0; i2 < diameter; ++i2) {
if (circle.contains(i + x, i2 + y) && (!circle.contains(i + x - 1, i2 + y - 1) || !circle.contains(i + x + 1, i2 + y + 1) || !circle.contains(i + x - 1, i2 + y + 1) || !circle.contains(i + x + 1, i2 + y - 1))) {
drawPixelsWithOpacity(color, i2 + y, 1, 1, opacity, i + x);
}
}
}
return circle;
}
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
As you can see in the picture, the bars in the chart starts below the x-axis. How do I fix it so that it starts from the x-axis line? Please suggest me a fix and also I would be helpful to know where I am going wrong.
private int PAD = 20;
private int LPAD = 35;
private int DPAD = 25;
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
String DataString[] = dataset.split(", ");
double DataArray[] = new double[DataString.length];
double MaxValue = 0;
int w = getWidth();
int h = getHeight();
for (int i = 0; i < DataString.length; i = i + 1) {
try {
double out = Double.parseDouble(DataString[i]);
DataArray[i] = out;
if (out > MaxValue) {
MaxValue = out;
}
} catch (NumberFormatException e) {
}
}
double xScale = (w - 1 * (PAD + LPAD)) / (DataArray.length) - 1;
double yScale = (h - 3 * DPAD) / MaxValue;
// The initial x,y (0).
int x0 = LPAD;
try {
if (DataArray.length > 1) {
// Draw Graph
g2.drawLine(PAD + LPAD, PAD, PAD + LPAD, h - (PAD + LPAD));
g2.drawLine(PAD + LPAD, h - PAD - LPAD, x0 + (int) (DataArray.length * xScale), h - (PAD + LPAD));
Color cd = new Color(135, 90, 185);
g2.setPaint(cd);
g2.drawString(title, w / 4, padding - 15);
g2.drawString(xAxis, w / 2 - padding, h - (padding - 10));
g2.rotate(Math.toRadians(270));
g2.drawString(yAxis, -(h / 2 + padding) - LPAD, PAD - 5);
g2.rotate(-Math.toRadians(270));
g2.setPaint(Color.BLACK);
double intin = Double.parseDouble(interval);
double sin = Double.parseDouble(start);
for (int j = 0; j < DataArray.length; j++) {
int x1 = x0 + (int) (j * xScale) + PAD;
double y1 = (int) ((MaxValue - DataArray[j]) * yScale + PAD) - 2;
// g2.drawLine(x1-3, (int) y1-3, x1+3, (int) y1+3);
// g2.drawLine(x1-3,(int) y1+3,x1+3,(int) y1-3);
// g2.drawLine(x1, h - (PAD + LPAD), x1, h - (PAD +
// LPAD)+5);
double intout = (sin + (intin * j));
String interval = String.valueOf(intout);
g2.drawString(interval, x1, h - LPAD);
}
for (int i = 0; i < 9; i++) {
int x3 = PAD + LPAD;
int x1 = x3 - 5;
int y0 = (h - (PAD)) - (((i + 1) * (h - PAD * 3)) / 10 + DPAD) - 2;
int y3 = y0;
g2.drawLine(x3, y0, x1, y3);
}
// g2.drawLine(PAD + LPAD, h - (PAD + LPAD), PAD + LPAD - 5,
// h - (PAD + LPAD));
g2.drawLine(PAD + LPAD, PAD, PAD + LPAD - 5, PAD);
g2.drawString(start, LPAD - 20, h - (LPAD) - 20);
Long L = Math.round(MaxValue);
int MVi = Integer.valueOf(L.intValue());
String MVstring = String.valueOf(MVi);
int MVmid = MVi / 2;
String MVmidstring = String.valueOf(MVmid);
g2.drawString(MVmidstring, PAD, (h - PAD) / 2);
g2.drawString(MVstring, PAD, PAD);
g2.setColor(Color.red);
for (int j = 0; j < DataArray.length; j++) {
int x1 = x0 + (int) (j * xScale) + PAD;
double y1 = (int) ((MaxValue - DataArray[j]) * yScale + PAD) - 2;
g2.fillRect(x1 + 2, (int) y1 + 1 , w - (PAD * 42), h);
}
}
} catch (Exception e) {
}
}
repaint();
}
How do you normally position a bar over the x-axis?
A bar's fillRect() parameters should be left, top, width and height. Focusing on just the vertical size and position, as shown here, you can scale the bar height to the plot area height using a proportion:
barHeightInPixels : plotHeightInPixels :: value : maxDataValue
Solving for barHeightInPixels,
panelHeightInPixels = panel.getHeight();
plotHeightInPixels = panelHeightInPixels - axisOffset;
barHeightInPixels = value * plotHeightInPixels / maxDataValue;
Now use the scaled height:
g2.fillRect(
barCenter - (barWidth / 2), //left
panelHeightInPixels - barHeightInPixels - axisOffset, //top
barWidth, //width
barHeightInPixels); //height
To see how additional features are implemented, study the code provided by a chart library, as suggested in this answer to your previous question on this topic.
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 have been working on a java game engine but my render keeps getting the unreachable code error.The error appears at the setPixels method.
public class Renderer {
private int width, height;
private byte[] pixels;
public Renderer(GameContainer gc){
width = gc.getWidth();
height = gc.getHeight();
pixels = ((DataBufferByte)gc.getWindow().getImage().getRaster().getDataBuffer()).getData();
}
public void setPixel(int x, int y, float a, float r, float g, float b){
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
return;
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
}
public void clear(){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
setPixel(x,y,1,0,1,1);
}
}
}
}
I think this is what you are trying to do?
Your if statement should not be enclosing all the statements in your function.
public void setPixel(int x, int y, float a, float r, float g, float b){
// Check for invalid values
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
// Break out of function if invalid values detected
return;
}
// Update pixel
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
The return statement ends the execution of a method. In the event that the if statement is run in the code below, the method will hit the return and end before doing all the other stuff. You don't seem to need a return statement in setPixel since there isn't a need to end the method prematurely.
public void setPixel(int x, int y, float a, float r, float g, float b) {
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
//return;
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
}
I have coded a heightmap but it seems to lag the client. I just don't know how to increase the fps. I get about 3-6fps with the heightmap. Im using a quite large bmp for the heightmap, I think its 1024x1024. When i use a smaller on its fine, maybe im just not using the code effectively. Is there a better way to code this heightmap or did I just code it wrong. It is my first time I have worked on a heightmap. Thanks
public class HeightMap {
private final float xScale, yScale, zScale;
private float[][] heightMap;
private FloatBuffer vertices, normals, texCoords;
private IntBuffer indices;
private Vector3f[] verticesArray, normalsArray;
private int[] indicesArray;
private int width;
private int height;
public float getHeight(int x, int y) {
return heightMap[x][y] * yScale;
}
public HeightMap(String path, int resolution) {
heightMap = loadHeightmap("heightmap.bmp");
xScale = 1000f / resolution;
yScale = 8;
zScale = 1000f / resolution;
verticesArray = new Vector3f[width * height];
vertices = BufferUtils.createFloatBuffer(3 * width * height);
texCoords = BufferUtils.createFloatBuffer(2 * width * height);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int pos = height * x + y;
final Vector3f vertex = new Vector3f(xScale * x, yScale * heightMap[x][y], zScale * y);
verticesArray[pos] = vertex;
vertex.store(vertices);
texCoords.put(x / (float) width);
texCoords.put(y / (float) height);
}
}
vertices.flip();
texCoords.flip();
normalsArray = new Vector3f[height * width];
normals = BufferUtils.createFloatBuffer(3 * width * height);
final float xzScale = xScale;
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
final int nextX = x < width - 1 ? x + 1 : x;
final int prevX = x > 0 ? x - 1 : x;
float sx = heightMap[nextX][y] - heightMap[prevX][y];
if (x == 0 || x == width - 1) {
sx *= 2;
}
final int nextY = y < height - 1 ? y + 1 : y;
final int prevY = y > 0 ? y - 1 : y;
float sy = heightMap[x][nextY] - heightMap[x][prevY];
if (y == 0 || y == height - 1) {
sy *= 2;
}
final Vector3f normal = new Vector3f(-sx * yScale, 2 * xzScale, sy * yScale).normalise(null);
normalsArray[height * x + y] = normal;
normal.store(normals);
}
}
normals.flip();
indicesArray = new int[6 * (height - 1) * (width - 1)];
indices = BufferUtils.createIntBuffer(6 * (width - 1) * (height - 1));
for (int i = 0; i < width - 1; i++) {
for (int j = 0; j < height - 1; j++) {
int pos = (height - 1) * i + j;
indices.put(height * i + j);
indices.put(height * (i + 1) + j);
indices.put(height * (i + 1) + (j + 1));
indicesArray[6 * pos] = height * i + j;
indicesArray[6 * pos + 1] = height * (i + 1) + j;
indicesArray[6 * pos + 2] = height * (i + 1) + (j + 1);
indices.put(height * i + j);
indices.put(height * i + (j + 1));
indices.put(height * (i + 1) + (j + 1));
indicesArray[6 * pos + 3] = height * i + j;
indicesArray[6 * pos + 4] = height * i + (j + 1);
indicesArray[6 * pos + 5] = height * (i + 1) + (j + 1);
}
}
indices.flip();
}
private float[][] loadHeightmap(String fileName) {
try {
BufferedImage img = ImageIO.read(ResourceLoader.getResourceAsStream(fileName));
width = img.getWidth();
height = img.getHeight();
float[][] heightMap = new float[width][height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
heightMap[x][y] = 0xFF & img.getRGB(x, y);
}
}
return heightMap;
} catch (IOException e) {
System.out.println("Nincs meg a heightmap!");
return null;
}
}
public void render() {
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glNormalPointer(0, normals);
glVertexPointer(3, 0, vertices);
glTexCoordPointer(2, 0, texCoords);
glDrawElements(GL_TRIANGLE_STRIP, indices);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
}
Sorry to bring up an old topic, however i see a lot of people ask this:
Use a display list, instead of re-making the heightmap every time.
TheCodingUniverse has a good tutorial on how to do this.
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.