Assume the following matrix acts as both an image and a kernel in a matrix convolution operation:
0 1 2
3 4 5
6 7 8
To calculate the neighbour pixel index you would use the following formula:
neighbourColumn = imageColumn + (maskColumn - centerMaskColumn);
neighbourRow = imageRow + (maskRow - centerMaskRow);
Thus the output of convolution would be:
output1 = {0,1,3,4} x {4,5,7,8} = 58
output2 = {0,1,2,3,4,5} x {3,4,5,6,7,8} = 100
output2 = {1,2,4,5} x {3,4,6,7} = 70
output3 = {0,1,3,4,6,7} x {1,2,4,5,7,8} = 132
output4 = {0,1,2,3,4,5,6,7,8} x {0,1,2,3,4,5,6,7,8} = 204
output5 = {1,2,4,5,7,8} x {0,1,3,4,6,7} = 132
output6 = {3,4,6,7} x {1,2,4,5} = 70
output7 = {3,4,5,6,7,8} x {0,1,2,3,4,5} = 100
output8 = {4,5,7,8} x {0,1,3,4} = 58
Thus the output matrix would be:
58 100 70
132 204 132
70 100 58
Now assume the matrix is flattened to give the following vector:
0 1 2 3 4 5 6 7 8
This vector now acts as an image and a kernel in a vector convolution operation for which the ouput should be:
58 100 70 132 204 132 70 100 58
Given the code below how do you calculate the neighbour element index for the vector such that it corresponds with the same neighbour element in the matrix?
public int[] convolve(int[] image, int[] kernel)
{
int imageValue;
int kernelValue;
int outputValue;
int[] outputImage = new int[image.length()];
// loop through image
for(int i = 0; i < image.length(); i++)
{
outputValue = 0;
// loop through kernel
for(int j = 0; j < kernel.length(); j++)
{
neighbour = ?;
// discard out of bound neighbours
if (neighbour >= 0 && neighbour < imageSize)
{
imageValue = image[neighbour];
kernelValue = kernel[j];
outputValue += imageValue * kernelValue;
}
}
outputImage[i] = outputValue;
}
return output;
}
The neighbour index is computed by offsetting the original pixel index by the difference between the index of the current element and half the size of the matrix. For example, to compute the column index:
int neighbourCol = imageCol + col - (size / 2);
I put a working demo on GitHub, trying to keep the whole convolution algorithm as readable as possible:
int[] dstImage = new int[srcImage.width() * srcImage.height()];
srcImage.forEachElement((image, imageCol, imageRow) -> {
Pixel pixel = new Pixel();
forEachElement((filter, col, row) -> {
int neighbourCol = imageCol + col - (size / 2);
int neighbourRow = imageRow + row - (size / 2);
if (srcImage.hasElementAt(neighbourCol, neighbourRow)) {
int color = srcImage.at(neighbourCol, neighbourRow);
int weight = filter.at(col, row);
pixel.addWeightedColor(color, weight);
}
});
dstImage[(imageRow * srcImage.width() + imageCol)] = pixel.rgb();
});
As you are dealing with 2D images, you will have to retain some information about the images, in addition the the plain 1D pixel array. Particularly, you at least need the width of the image (and the mask) in order to find out which indices in the 1D array correspond to which indices in the original 2D image. And as already pointed out by Raffaele in his answer, there are general rules for the conversions between these ("virtual") 2D coordinates and 1D coordinates in such a pixel array:
int pixelX = ...;
int pixelY = ...;
int index = pixelX + pixelY * imageSizeX;
Based on this, you can do your convolution simply on a 2D image. The limits for the pixels that you may access may easily be checked. The loops are simple 2D loops over the image and the mask. It all boils down to the point where you access the 1D data with the 2D coordinates, as described above.
Here is an example. It applies a Sobel filter to the input image. (There may still be something odd with the pixel values, but the convolution itself and the index computations should be right)
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class ConvolutionWithArrays1D
{
public static void main(String[] args) throws IOException
{
final BufferedImage image =
asGrayscaleImage(ImageIO.read(new File("lena512color.png")));
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI(image);
}
});
}
private static void createAndShowGUI(BufferedImage image0)
{
JFrame f = new JFrame();
f.getContentPane().setLayout(new GridLayout(1,2));
f.getContentPane().add(new JLabel(new ImageIcon(image0)));
BufferedImage image1 = compute(image0);
f.getContentPane().add(new JLabel(new ImageIcon(image1)));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static BufferedImage asGrayscaleImage(BufferedImage image)
{
BufferedImage gray = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = gray.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return gray;
}
private static int[] obtainGrayscaleIntArray(BufferedImage image)
{
BufferedImage gray = new BufferedImage(
image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g = gray.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
DataBuffer dataBuffer = gray.getRaster().getDataBuffer();
DataBufferByte dataBufferByte = (DataBufferByte)dataBuffer;
byte data[] = dataBufferByte.getData();
int result[] = new int[data.length];
for (int i=0; i<data.length; i++)
{
result[i] = data[i];
}
return result;
}
private static BufferedImage createImageFromGrayscaleIntArray(
int array[], int imageSizeX, int imageSizeY)
{
BufferedImage gray = new BufferedImage(
imageSizeX, imageSizeY, BufferedImage.TYPE_BYTE_GRAY);
DataBuffer dataBuffer = gray.getRaster().getDataBuffer();
DataBufferByte dataBufferByte = (DataBufferByte)dataBuffer;
byte data[] = dataBufferByte.getData();
for (int i=0; i<data.length; i++)
{
data[i] = (byte)array[i];
}
return gray;
}
private static BufferedImage compute(BufferedImage image)
{
int imagePixels[] = obtainGrayscaleIntArray(image);
int mask[] =
{
1,0,-1,
2,0,-2,
1,0,-1,
};
int outputPixels[] =
Convolution.filter(imagePixels, image.getWidth(), mask, 3);
return createImageFromGrayscaleIntArray(
outputPixels, image.getWidth(), image.getHeight());
}
}
class Convolution
{
public static final int[] filter(
final int[] image, int imageSizeX,
final int[] mask, int maskSizeX)
{
int imageSizeY = image.length / imageSizeX;
int maskSizeY = mask.length / maskSizeX;
int output[] = new int[image.length];
for (int y=0; y<imageSizeY; y++)
{
for (int x=0; x<imageSizeX; x++)
{
int outputPixelValue = 0;
for (int my=0; my< maskSizeY; my++)
{
for (int mx=0; mx< maskSizeX; mx++)
{
int neighborX = x + mx -maskSizeX / 2;
int neighborY = y + my -maskSizeY / 2;
if (neighborX >= 0 && neighborX < imageSizeX &&
neighborY >= 0 && neighborY < imageSizeY)
{
int imageIndex =
neighborX + neighborY * imageSizeX;
int maskIndex = mx + my * maskSizeX;
int imagePixelValue = image[imageIndex];
int maskPixelValue = mask[maskIndex];
outputPixelValue +=
imagePixelValue * maskPixelValue;
}
}
}
outputPixelValue = truncate(outputPixelValue);
int outputIndex = x + y * imageSizeX;
output[outputIndex] = outputPixelValue;
}
}
return output;
}
private static final int truncate(final int pixelValue)
{
return Math.min(255, Math.max(0, pixelValue));
}
}
Related
I have a code that puts an image in a pixel matrix.I have to split this image into four parts and get 4 different image files for these parts.
Then I have to do some image processing on it and then join those parts back together in a single image.
Please help me in achieving this.
Note:the image is colored and we want to just split it into 4 equal parts and then get it back as one.No changes needed.Here is the code to get four matrices of intensities.But I don't know what to do with it.It might be that there is no need of it at all.
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
class Optimization
{
public static void main(String[] args) throws IOException
{
BufferedImage hugeImage = ImageIO.read(new File("comp.jpg"));
final byte[] pixels = ((DataBufferByte) hugeImage.getRaster().getDataBuffer()).getData();
int width = hugeImage.getWidth();
int height = hugeImage.getHeight();
if(width%2!=0)
width=width-1;
if(height%2!=0)
height=height-1;
//System.out.print(width+" "+height);
int intensity[][]=new int[width][height];
int b1[][]=new int[width/2][height/2];
int b2[][]=new int[width/2][height/2];
int b3[][]=new int[width/2][height/2];
int b4[][]=new int[width/2][height/2];
int x1=0,y1=0,x2=0,y2=0,x3=0,x4=0,y3=0,y4=0;
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength)
{
int a1,a2,a3;
a3= ((int) pixels[pixel] & 0xff); // blue
a2= (((int) pixels[pixel + 1] & 0xff)); // green
a1= (((int) pixels[pixel + 2] & 0xff)); // red
int i=(a1+a2+a3)/3;
intensity[col][row]=i;
if((col<=width/2-1)&&(row<=height/2-1))
{
b1[x1][y1]=i;
x1++;
if(col==width/2-1)
{
x1=0;
y1++;
}
}
if((col<width)&&(row<=height/2-1)&&(col>width/2-1))
{
b2[x2][y2]=i;
x2++;
if(col==width-1)
{
x2=0;
y2++;
}
}
if((col<width/2)&&(row<height)&&(row>=height/2))
{
b3[x3][y3]=i;
x3++;
if(col==width/2-1)
{
x3=0;
y3++;
}
}
if((col>width/2-1)&&(row>height/2-1))
{
b4[x4][y4]=i;
x4++;
if(col==width-1)
{
x4=0;
y4++;
}
}
col++;
if (col == width)
{
col = 0;
row++;
}
}
for(int m=0;m<height/2;m++)
{
for(int n=0;n<width/2;n++)
{
System.out.print(b1[n][m]+" ");
}
System.out.println();
}
}
}
java.awt.Image provides you getSubimage(int x, int y, int w, int h)
"Returns a subimage defined by a specified rectangular region." You just return four equal regions and thats it.
You can use getSubImage(int x, int y, int w, int h), but you will get an image that shares the same raster than the original image, meaning that if you modify the new sub-image, then you also modify the original image. If it's not problematic for you, then use it.
Else, as you have already accessed the DataBuffer (good idea), here is a simple code to do what you want (I just make DataBuffer copies, and it works when the image dimensions are odd):
BufferedImage image = ... ;
BufferedImage q1 = new BufferedImage(image.getWidth()/2, image.getHeight()/2, image.getType()) ;
BufferedImage q2 = new BufferedImage(image.getWidth()-image.getWidth()/2, image.getHeight()/2, image.getType()) ;
BufferedImage q3 = new BufferedImage(image.getWidth()/2, image.getHeight()-image.getHeight()/2, image.getType()) ;
BufferedImage q4 = new BufferedImage(image.getWidth()-image.getWidth()/2, image.getHeight()-image.getHeight()/2, image.getType()) ;
byte[] bb = ((DataBufferByte)image.getRaster().getDataBuffer()).getData() ;
byte[] bbq1 = ((DataBufferByte)q1.getRaster().getDataBuffer()).getData() ;
byte[] bbq2 = ((DataBufferByte)q2.getRaster().getDataBuffer()).getData() ;
byte[] bbq3 = ((DataBufferByte)q3.getRaster().getDataBuffer()).getData() ;
byte[] bbq4 = ((DataBufferByte)q4.getRaster().getDataBuffer()).getData() ;
for (int y=0 ; y < q1.getHeight() ; y++) // Fill Q1 and Q2
{
System.arraycopy(bb, y*image.getWidth(), bbq1, y*q1.getWidth(), q1.getWidth()) ;
System.arraycopy(bb, y*image.getWidth()+q1.getWidth(), bbq2, y*q2.getWidth(), q2.getWidth()) ;
}
for (int y=0 ; y < q3.getHeight() ; y++) // Fill Q3 and Q4
{
System.arraycopy(bb, (y+q1.getHeight())*image.getWidth(), bbq3, y*q3.getWidth(), q3.getWidth()) ;
System.arraycopy(bb, (y+q1.getHeight())*image.getWidth()+q3.getWidth(), bbq4, y*q4.getWidth(), q4.getWidth()) ;
}
In the following code am trying to read a grayscale image, store the pixel values in a 2D array and rewrite the image with a different name.
The code is
package dct;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.File;
import javax.imageio.ImageIO;
public class writeGrayScale
{
public static void main(String[] args)
{
File file = new File("lightning.jpg");
BufferedImage img = null;
try
{
img = ImageIO.read(file);
}
catch(Exception e)
{
e.printStackTrace();
}
int width = img.getWidth();
int height = img.getHeight();
int[][] arr = new int[width][height];
Raster raster = img.getData();
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
arr[i][j] = raster.getSample(i, j, 0);
}
}
BufferedImage image = new BufferedImage(256, 256, BufferedImage.TYPE_BYTE_GRAY);
byte[] raster1 = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(arr,0,raster1,0,raster1.length);
//
BufferedImage image1 = image;
try
{
File ouptut = new File("grayscale.jpg");
ImageIO.write(image1, "jpg", ouptut);
}
catch(Exception e)
{
e.printStackTrace();
}
}// main
}// class
For this code , the error is
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at dct.writeGrayScale.main(writeGrayScale.java:49)
Java Result: 1
How to remove this error to write the grayscale image?
I found this: "ArrayStoreException -- if an element in the src array could not be stored into the dest array because of a type mismatch." http://www.tutorialspoint.com/java/lang/system_arraycopy.htm
Two thoughts:
You're copying an int-array into a byte-array.
That's not part of the exceptions, but are the dimensions right? arr is a two-dimensional array, raster1 is a one-dimensional array.
And you can't just change the byte-array in a two-dimensional one ignoring the output of the method you're calling.
Change int[][] arr to byte[] arr like this.
byte[] arr = new byte[width * height * 4];
for (int i = 0, z = 0; i < width; i++) {
for (int j = 0; j < height; j++, z += 4) {
int v = getSample(i, j, 0);
for (int k = 3; k >= 0; --k) {
arr[z + k] = (byte)(v & 0xff);
v >>= 8;
}
}
}
I'm trying to implement averaging filter with different sizes 3x3 , 5x5 , 7x7 and 11x11 .. I did the calculations and the results are correct while debugging but the problem is that it is saved in the writable raster in negative, so I'm getting weird results. The second weird thing is that when I want to get the value of the same pixel that was saved in negative, it is retrieved with positive value !!
I'm using int.
What's wrong? any help ?!!
Here is my code for the 5x5 averaging filter.
public static BufferedImage filter5x5_2D(BufferedImage paddedBI , BufferedImage bi , double[][]filter)
{
WritableRaster myImage = paddedBI.copyData(null);
BufferedImage img = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
WritableRaster myImage2 = img.copyData(null);
for(int i =2; i< myImage.getHeight()-2; i++)
{
for(int j =2; j< myImage.getWidth()-2; j++)
{
int value = 0;
int copyi = i-2;
for (int m = 0 ; m<5 ; m++)
{
int copyj = j-2;
for (int n = 0; n<5; n++)
{
int result = myImage.getSample(copyj , copyi, 0);
double f = filter[m][n];
double add = result * filter[m][n];
value += (int) (filter[m][n] * myImage.getSample(copyj , copyi, 0));
copyj ++;
}
copyi++;
//myImage2.setSample(j-1 , i-1, 0, value);
}
myImage2.setSample(j-2 , i-2, 0, value);
//int checkResult = myImage2.getSample(j-1,i-1,0);
}
}
BufferedImage res= new BufferedImage(bi.getWidth(),bi.getHeight(),BufferedImage.TYPE_BYTE_GRAY);
res.setData(myImage2);
return res;
}
I do not find any negative values. Here is my main with what I have tested this code:
public static void main(String[] args) throws IOException {
BufferedImage bi = ImageIO.read(new File("C:/Tmp/test.bmp"));
BufferedImage newImage = new BufferedImage(bi.getWidth()+4, bi.getHeight()+4, bi.getType());
Graphics g = newImage.getGraphics();
g.setColor(Color.white);
g.fillRect(0,0,bi.getWidth()+4,bi.getHeight()+4);
g.drawImage(bi, 2, 2, null);
g.dispose();
double[][] filter = new double[5][5];
for( int i = 0; i < 5; ++i){
for( int j = 0; j < 5; ++j){
filter[i][j] = 1.0/(5*5);
}
}
BufferedImage filtered = filter5x5_2D(newImage, bi, filter);
ImageIO.write(filtered, "bmp", new File("C:/tmp/filtered.bmp"));
}
You should consider that your variables result, f and add are unused. Also it would be better if value would be of type double instead of int. In a worst case, you would get 25 times a value of 11 which will be rounded to zero after multiplying with 1/25. This would result in your code as a grey value of zero while it should result in 11.
In my 2D game I'm using graphic tools to create nice, smooth terrain represented by black color:
Simple algorithm written in java looks for black color every 15 pixels, creating following set of lines (gray):
As you can see, there's some places that are mapped very bad, some are pretty good. In other case it would be not necessary to sample every 15 pixels, eg. if terrain is flat.
What's the best way to covert this curve to set of points [lines], using as little points as possible?
Sampling every 15 pixels = 55 FPS, 10 pixels = 40 FPS
Following algorithm is doing that job, sampling from right to left, outputting pasteable into code array:
public void loadMapFile(String path) throws IOException {
File mapFile = new File(path);
image = ImageIO.read(mapFile);
boolean black;
System.out.print("{ ");
int[] lastPoint = {0, 0};
for (int x = image.getWidth()-1; x >= 0; x -= 15) {
for (int y = 0; y < image.getHeight(); y++) {
black = image.getRGB(x, y) == -16777216 ? true : false;
if (black) {
lastPoint[0] = x;
lastPoint[1] = y;
System.out.print("{" + (x) + ", " + (y) + "}, ");
break;
}
}
}
System.out.println("}");
}
Im developing on Android, using Java and AndEngine
This problem is nearly identical to the problem of digitization of a signal (such as sound), where the basic law is that the signal in the input that had the frequency too high for the sampling rate will not be reflected in the digitized output. So the concern is that if you check ever 30 pixels and then test the middle as bmorris591 suggests, you might miss that 7 pixel hole between the sampling points. This suggests that if there are 10 pixel features you cannot afford to miss, you need to do scanning every 5 pixels: your sample rate should be twice the highest frequency present in the signal.
One thing that can help improve your algorithm is a better y-dimension search. Currently you are searching for the intersection between sky and terrain linearly, but a binary search should be faster
int y = image.getHeight()/2; // Start searching from the middle of the image
int yIncr = y/2;
while (yIncr>0) {
if (image.getRGB(x, y) == -16777216) {
// We hit the terrain, to towards the sky
y-=yIncr;
} else {
// We hit the sky, go towards the terrain
y+=yIncr;
}
yIncr = yIncr/2;
}
// Make sure y is on the first terrain point: move y up or down a few pixels
// Only one of the following two loops will execute, and only one or two iterations max
while (image.getRGB(x, y) != -16777216) y++;
while (image.getRGB(x, y-1) == -16777216) y--;
Other optimizations are possible. If you know that your terrain has no cliffs, then you only need to search the window from lastY+maxDropoff to lastY-maxDropoff. Also, if your terrain can never be as tall as the entire bitmap, you don't need to search the top of the bitmap either. This should help to free some CPU cycles you can use for higher-resolution x-scanning of the terrain.
I propose to find border points which exists on the border between white and dark pixels. After that we can digitize those points. To do that, we should define DELTA which specify which point we should skip and which we should add to result list.
DELTA = 3, Number of points = 223
DELTA = 5, Number of points = 136
DELTA = 10, Number of points = 70
Below, I have put source code, which prints image and looking for points. I hope, you will be able to read it and find a way to solve your problem.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Program {
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("/home/michal/Desktop/FkXG1.png"));
PathFinder pathFinder = new PathFinder(10);
List<Point> borderPoints = pathFinder.findBorderPoints(image);
System.out.println(Arrays.toString(borderPoints.toArray()));
System.out.println(borderPoints.size());
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageBorderPanel(image, borderPoints));
frame.pack();
frame.setMinimumSize(new Dimension(image.getWidth(), image.getHeight()));
frame.setVisible(true);
}
}
class PathFinder {
private int maxDelta = 3;
public PathFinder(int delta) {
this.maxDelta = delta;
}
public List<Point> findBorderPoints(BufferedImage image) {
int width = image.getWidth();
int[][] imageInBytes = convertTo2DWithoutUsingGetRGB(image);
int[] borderPoints = findBorderPoints(width, imageInBytes);
List<Integer> indexes = dwindlePoints(width, borderPoints);
List<Point> points = new ArrayList<Point>(indexes.size());
for (Integer index : indexes) {
points.add(new Point(index, borderPoints[index]));
}
return points;
}
private List<Integer> dwindlePoints(int width, int[] borderPoints) {
List<Integer> indexes = new ArrayList<Integer>(width);
indexes.add(borderPoints[0]);
int delta = 0;
for (int index = 1; index < width; index++) {
delta += Math.abs(borderPoints[index - 1] - borderPoints[index]);
if (delta >= maxDelta) {
indexes.add(index);
delta = 0;
}
}
return indexes;
}
private int[] findBorderPoints(int width, int[][] imageInBytes) {
int[] borderPoints = new int[width];
int black = Color.BLACK.getRGB();
for (int y = 0; y < imageInBytes.length; y++) {
int maxX = imageInBytes[y].length;
for (int x = 0; x < maxX; x++) {
int color = imageInBytes[y][x];
if (color == black && borderPoints[x] == 0) {
borderPoints[x] = y;
}
}
}
return borderPoints;
}
private int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) {
final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
final int width = image.getWidth();
final int height = image.getHeight();
final boolean hasAlphaChannel = image.getAlphaRaster() != null;
int[][] result = new int[height][width];
if (hasAlphaChannel) {
final int pixelLength = 4;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
argb += ((int) pixels[pixel + 1] & 0xff); // blue
argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
} else {
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += -16777216; // 255 alpha
argb += ((int) pixels[pixel] & 0xff); // blue
argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}
return result;
}
}
class ImageBorderPanel extends JPanel {
private static final long serialVersionUID = 1L;
private BufferedImage image;
private List<Point> borderPoints;
public ImageBorderPanel(BufferedImage image, List<Point> borderPoints) {
this.image = image;
this.borderPoints = borderPoints;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
Graphics2D graphics2d = (Graphics2D) g;
g.setColor(Color.YELLOW);
for (Point point : borderPoints) {
graphics2d.fillRect(point.x, point.y, 3, 3);
}
}
}
In my source code I have used example from this question:
Java - get pixel array from image
The most efficient solution (with respect to points required) would be to allow for variable spacing between points along the X axis. This way, a large flat part would require very few points/samples and complex terrains would use more.
In 3D mesh processing, there is a nice mesh simplification algorithm named "quadric edge collapse", which you can adapt to your problem.
Here is the idea, translated to your problem - it actually gets much simpler than the original 3D algorithm:
Represent your curve with way too many points.
For each point, measure the error (i.e. difference to the smooth terrain) if you remove it.
Remove the point that gives the smallest error.
Repeat until you have reduced the number of points far enough or errors get too large.
To be more precise regarding step 2: Given points P, Q, R, the error of Q is the difference between the approximation of your terrain by two straight lines, P->Q and Q->R, and the approximation of your terrain by just one line P->R.
Note that when a point is removed only its neighbors need an update of their error value.
What I'm trying to do is to compute 2D DCT of an image in Java and then save the result back to file.
Read file:
coverImage = readImg(coverPath);
private BufferedImage readImg(String path) {
BufferedImage destination = null;
try {
destination = ImageIO.read(new File(path));
} catch (IOException e) {
e.printStackTrace();
}
return destination;
}
Convert to float array:
cover = convertToFloatArray(coverImage);
private float[] convertToFloatArray(BufferedImage source) {
securedImage = (WritableRaster) source.getData();
float[] floatArray = new float[source.getHeight() * source.getWidth()];
floatArray = securedImage.getPixels(0, 0, source.getWidth(), source.getHeight(), floatArray);
return floatArray;
}
Run the DCT:
runDCT(cover, coverImage.getHeight(), coverImage.getWidth());
private void runDCT(float[] floatArray, int rows, int cols) {
dct = new FloatDCT_2D(rows, cols);
dct.forward(floatArray, false);
securedImage.setPixels(0, 0, cols, rows, floatArray);
}
And then save it as image:
convertDctToImage(securedImage, coverImage.getHeight(), coverImage.getWidth());
private void convertDctToImage(WritableRaster secured, int rows, int cols) {
coverImage.setData(secured);
File file = new File(securedPath);
try {
ImageIO.write(coverImage, "png", file);
} catch (IOException ex) {
Logger.getLogger(DCT2D.class.getName()).log(Level.SEVERE, null, ex);
}
}
But what I get is: http://kyle.pl/up/2012/05/29/dct_stack.png
Can anyone tell me what I'm doing wrong? Or maybe I don't understand something here?
This is a piece of code, that works for me:
//reading image
BufferedImage image = javax.imageio.ImageIO.read(new File(filename));
//width * 2, because DoubleFFT_2D needs 2x more space - for Real and Imaginary parts of complex numbers
double[][] brightness = new double[img.getHeight()][img.getWidth() * 2];
//convert colored image to grayscale (brightness of each pixel)
for ( int y = 0; y < image.getHeight(); y++ ) {
raster.getDataElements( 0, y, image.getWidth(), 1, dataElements );
for ( int x = 0; x < image.getWidth(); x++ ) {
//notice x and y swapped - it's JTransforms format of arrays
brightness[y][x] = brightnessRGB(dataElements[x]);
}
}
//do FT (not FFT, because FFT is only* for images with width and height being 2**N)
//DoubleFFT_2D writes data to the same array - to brightness
new DoubleFFT_2D(img.getHeight(), img.getWidth()).realForwardFull(brightness);
//visualising frequency domain
BufferedImage fd = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
outRaster = fd.getRaster();
for ( int y = 0; y < img.getHeight(); y++ ) {
for ( int x = 0; x < img.getWidth(); x++ ) {
//we calculate complex number vector length (sqrt(Re**2 + Im**2)). But these lengths are to big to
//fit in 0 - 255 scale of colors. So I divide it on 223. Instead of "223", you may want to choose
//another factor, wich would make you frequency domain look best
int power = (int) (Math.sqrt(Math.pow(brightness[y][2 * x], 2) + Math.pow(brightness[y][2 * x + 1], 2)) / 223);
power = power > 255 ? 255 : power;
//draw a grayscale color on image "fd"
fd.setRGB(x, y, new Color(c, c, c).getRGB());
}
}
draw(fd);
Resulting image should look like big black space in the middle and white spots in all four corners. Usually people visualise FD so, that zero frequency appears in the center of the image. So, if you need classical FD (one, that looks like star for reallife images), you need to upgrade "fd.setRGB(x, y..." a bit:
int w2 = img.getWidth() / 2;
int h2 = img.getHeight() / 2;
int newX = x + w2 >= img.getWidth() ? x - w2 : x + w2;
int newY = y + h2 >= img.getHeight() ? y - h2 : y + h2;
fd.setRGB(newX, newY, new Color(power, power, power).getRGB());
brightnessRGB and draw methods for the lazy:
public static int brightnessRGB(int rgb) {
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
return (r+g+b)/3;
}
private static void draw(BufferedImage img) {
JLabel picLabel = new JLabel(new ImageIcon(img));
JPanel jPanelMain = new JPanel();
jPanelMain.add(picLabel);
JFrame jFrame = new JFrame();
jFrame.add(jPanelMain);
jFrame.pack();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
}
I know, I'm a bit late, but I just did all that for my program. So, let it be here for those, who'll get here from googling.