Background
I'm trying to figure out how to best convert some old Java code to C/C++ (here) of resizing a bitmap using Bilinear Interpolation.
For this, I've decided to also make the code usable on Android itself using Java (on Android), to check that it still works as I've left it, and maybe also allow others to use it.
The problem
Even though everything seem to work nicely, I can see some weird black artifacts when resizing. I'm almost sure why they appear:
notice the gray color on the left of the yellow box and on the right of the red one.
I'd assume the color should be between yellow and white for the first case, and between red and white on the second one.
However, I think the color I get is because of the alpha values aren't handled correctly (the white area is actually transparent).
That's because if I set real white color to the bitmap, I get the correct result.
The original code (that works on PC)
/** class for resizing imageData using the Bilinear Interpolation method */
public class BilinearInterpolation
{
/** the method for resizing the imageData using the Bilinear Interpolation algorithm */
public static void resize(final ImageData inputImageData,final ImageData newImageData,final int oldWidth,final int oldHeight,final int newWidth,final int newHeight)
{
// position of the top left pixel of the 4 pixels to use interpolation on
int xTopLeft,yTopLeft;
int x,y,lastTopLefty;
final float xRatio=(float)newWidth/(float)oldWidth,yratio=(float)newHeight/(float)oldHeight;
// Y color ratio to use on left and right pixels for interpolation
float ycRatio2=0,ycRatio1=0;
// pixel target in the src
float xt,yt;
// X color ratio to use on left and right pixels for interpolation
float xcRatio2=0,xcratio1=0;
// copy data from source image to RGB values:
RGB rgbTopLeft,rgbTopRight,rgbBottomLeft=null,rgbBottomRight=null,rgbTopMiddle=null,rgbBottomMiddle=null;
RGB[][] startingImageData;
startingImageData=new RGB[oldWidth][oldHeight];
for(x=0;x<oldWidth;++x)
for(y=0;y<oldHeight;++y)
{
rgbTopLeft=inputImageData.palette.getRGB(inputImageData.getPixel(x,y));
startingImageData[x][y]=new RGB(rgbTopLeft.red,rgbTopLeft.green,rgbTopLeft.blue);
}
// do the resizing:
for(x=0;x<newWidth;x++)
{
xTopLeft=(int)(xt=x/xRatio);
// when meeting the most right edge, move left a little
if(xTopLeft>=oldWidth-1)
xTopLeft--;
if(xt<=xTopLeft+1)
{
// we are between the left and right pixel
xcratio1=xt-xTopLeft;
// color ratio in favor of the right pixel color
xcRatio2=1-xcratio1;
}
for(y=0,lastTopLefty=Integer.MIN_VALUE;y<newHeight;y++)
{
yTopLeft=(int)(yt=y/yratio);
// when meeting the most bottom edge, move up a little
if(yTopLeft>=oldHeight-1)
yTopLeft--;
// we went down only one rectangle
if(lastTopLefty==yTopLeft-1)
{
rgbTopLeft=rgbBottomLeft;
rgbTopRight=rgbBottomRight;
rgbTopMiddle=rgbBottomMiddle;
rgbBottomLeft=startingImageData[xTopLeft][yTopLeft+1];
rgbBottomRight=startingImageData[xTopLeft+1][yTopLeft+1];
rgbBottomMiddle=new RGB((int)(rgbBottomLeft.red*xcRatio2+rgbBottomRight.red*xcratio1),(int)(rgbBottomLeft.green*xcRatio2+rgbBottomRight.green*xcratio1),(int)(rgbBottomLeft.blue*xcRatio2+rgbBottomRight.blue*xcratio1));
}
else if(lastTopLefty!=yTopLeft)
{
// we went to a totally different rectangle (happens in every loop start,and might happen more when making the picture smaller)
rgbTopLeft=startingImageData[xTopLeft][yTopLeft];
rgbTopRight=startingImageData[xTopLeft+1][yTopLeft];
rgbTopMiddle=new RGB((int)(rgbTopLeft.red*xcRatio2+rgbTopRight.red*xcratio1),(int)(rgbTopLeft.green*xcRatio2+rgbTopRight.green*xcratio1),(int)(rgbTopLeft.blue*xcRatio2+rgbTopRight.blue*xcratio1));
rgbBottomLeft=startingImageData[xTopLeft][yTopLeft+1];
rgbBottomRight=startingImageData[xTopLeft+1][yTopLeft+1];
rgbBottomMiddle=new RGB((int)(rgbBottomLeft.red*xcRatio2+rgbBottomRight.red*xcratio1),(int)(rgbBottomLeft.green*xcRatio2+rgbBottomRight.green*xcratio1),(int)(rgbBottomLeft.blue*xcRatio2+rgbBottomRight.blue*xcratio1));
}
lastTopLefty=yTopLeft;
if(yt<=yTopLeft+1)
{
// color ratio in favor of the bottom pixel color
ycRatio1=yt-yTopLeft;
ycRatio2=1-ycRatio1;
}
// prepared all pixels to look at, so finally set the new pixel data
newImageData.setPixel(x,y,inputImageData.palette.getPixel(new RGB((int)(rgbTopMiddle.red*ycRatio2+rgbBottomMiddle.red*ycRatio1),(int)(rgbTopMiddle.green*ycRatio2+rgbBottomMiddle.green*ycRatio1),(int)(rgbTopMiddle.blue*ycRatio2+rgbBottomMiddle.blue*ycRatio1))));
}
}
}
}
The converted code (for Android)
/** class for resizing imageData using the Bilinear Interpolation method */
public class BilinearInterpolation
{
/** the method for resizing the imageData using the Bilinear Interpolation algorithm */
public static void resize(final Bitmap input,final Bitmap output)
{
final int oldHeight=input.getHeight(),oldWidth=input.getWidth();
final int newHeight=output.getHeight(),newWidth=output.getWidth();
// position of the top left pixel of the 4 pixels to use interpolation on
int xTopLeft,yTopLeft;
int x,y,lastTopLefty;
final float xRatio=(float)newWidth/(float)oldWidth,yratio=(float)newHeight/(float)oldHeight;
// Y color ratio to use on left and right pixels for interpolation
float ycRatio2=0,ycRatio1=0;
// pixel target in the src
float xt,yt;
// X color ratio to use on left and right pixels for interpolation
float xcRatio2=0,xcratio1=0;
int rgbTopLeft=0,rgbTopRight=0,rgbBottomLeft=0,rgbBottomRight=0,rgbTopMiddle=0,rgbBottomMiddle=0;
// do the resizing:
for(x=0;x<newWidth;x++)
{
xTopLeft=(int)(xt=x/xRatio);
// when meeting the most right edge, move left a little
if(xTopLeft>=oldWidth-1)
xTopLeft--;
if(xt<=xTopLeft+1)
{
// we are between the left and right pixel
xcratio1=xt-xTopLeft;
// color ratio in favor of the right pixel color
xcRatio2=1-xcratio1;
}
for(y=0,lastTopLefty=Integer.MIN_VALUE;y<newHeight;y++)
{
yTopLeft=(int)(yt=y/yratio);
// when meeting the most bottom edge, move up a little
if(yTopLeft>=oldHeight-1)
yTopLeft--;
// we went down only one rectangle
if(lastTopLefty==yTopLeft-1)
{
rgbTopLeft=rgbBottomLeft;
rgbTopRight=rgbBottomRight;
rgbTopMiddle=rgbBottomMiddle;
rgbBottomLeft=input.getPixel(xTopLeft,yTopLeft+1);
rgbBottomRight=input.getPixel(xTopLeft+1,yTopLeft+1);
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
else if(lastTopLefty!=yTopLeft)
{
// we went to a totally different rectangle (happens in every loop start,and might happen more when making the picture smaller)
rgbTopLeft=input.getPixel(xTopLeft,yTopLeft);
rgbTopRight=input.getPixel(xTopLeft+1,yTopLeft);
rgbTopMiddle=Color.argb((int)(Color.alpha(rgbTopLeft)*xcRatio2+Color.alpha(rgbTopRight)*xcratio1),//
(int)(Color.red(rgbTopLeft)*xcRatio2+Color.red(rgbTopRight)*xcratio1),//
(int)(Color.green(rgbTopLeft)*xcRatio2+Color.green(rgbTopRight)*xcratio1),//
(int)(Color.blue(rgbTopLeft)*xcRatio2+Color.blue(rgbTopRight)*xcratio1));
rgbBottomLeft=input.getPixel(xTopLeft,yTopLeft+1);
rgbBottomRight=input.getPixel(xTopLeft+1,yTopLeft+1);
rgbBottomMiddle=Color.argb((int)(Color.alpha(rgbBottomLeft)*xcRatio2+Color.alpha(rgbBottomRight)*xcratio1),//
(int)(Color.red(rgbBottomLeft)*xcRatio2+Color.red(rgbBottomRight)*xcratio1),//
(int)(Color.green(rgbBottomLeft)*xcRatio2+Color.green(rgbBottomRight)*xcratio1),//
(int)(Color.blue(rgbBottomLeft)*xcRatio2+Color.blue(rgbBottomRight)*xcratio1));
}
lastTopLefty=yTopLeft;
if(yt<=yTopLeft+1)
{
// color ratio in favor of the bottom pixel color
ycRatio1=yt-yTopLeft;
ycRatio2=1-ycRatio1;
}
// prepared all pixels to look at, so finally set the new pixel data
output.setPixel(x,y,Color.argb(//
(int)(Color.alpha(rgbTopMiddle)*ycRatio2+Color.alpha(rgbBottomMiddle)*ycRatio1),//
(int)(Color.red(rgbTopMiddle)*ycRatio2+Color.red(rgbBottomMiddle)*ycRatio1),//
(int)(Color.green(rgbTopMiddle)*ycRatio2+Color.green(rgbBottomMiddle)*ycRatio1),//
(int)(Color.blue(rgbTopMiddle)*ycRatio2+Color.blue(rgbBottomRight)*ycRatio1)));
}
}
}
}
The question:
How should I handle the alpha channel?
What should be done to the algorithm in this case?
Should I multiple the (combined) alpha channel (and divide by 255) on each pixel I output?
By the way, I know there is already a built in solution for this using the framework, but the purpose here is to learn from my mistakes, and also provide something that works well on the C/C++ solution I'm making.
Related
So my ultimate goal is to draw 50 circles(with processing.core.PApplet) structured around a ring that transitions colors like a neon sign so that it would look like Psychadelics
The circles have to be random sizes and with a diameter of under 210 pixels the circles have to have 8 bars with each "shells" changing colors in order, and at the center, there must be an empty circle with the same color as the background.
Right now I am trying to break this problem into lots of small problems, and currently, I am struggling to make the spaces between the bars to be equally spaced.
Culprit:
(I found the problem to be the ratio between the bar size and the circle size, due to the undefined range random sizing it made seemingly empty circles)
Here is my next problem, The circles seem to be vibrating instead of remaining static, I want the 50 circles to be static while the colors on each shell change each frame.
Here is the part where I tell it to draw smaller and smaller circles
This is the "Drawing component"
public Donut(float x, float y , float d) { //constructor
this.x =x;
this.y =y;
diameter=d;
}
public void draw(PApplet p) {
p.circle(x, y, diameter);
float bar=(float)(Math.random()*(1-10)-1)+1;
for(int i =0; i<8; i++) {
bar+=10;
p.fill(REDS[i],GREENS[i],BLUES[i]);
p.circle(x, y, diameter-bar);
}
}
And here is the part where I tell it to have random sizes and positions(still haven't told it to be placed around a ring yet) //This is the Main Class
public class Psychadelics extends PApplet{
Donut [] DonutList = new Donut [50];
public static void main(String[] args) {
PApplet.main("test.Psychadelics");
}
public void settings() {
size(SCR_W, SCR_H);
}
public void setup() {
for(int i =0; i<DonutList.length;i++) {
float x = (float)(Math.random()*600);
float y = (float)(Math.random()*400);
float diameter = (float)(Math.random()*210);
DonutList [i]= new Donut(x,y,diameter);
}
I have another drawing method inside the main class to tell the Donut class to keep drawing and to keep updating it.
I expect each circle to remain static and to transition colors, each frame but my actual results were the circles each with different colors on each shell vibrating on their specified coordinates
The vibration is caused by float bar=(float)(Math.random()*(1-10)-1)+1;. Because bar is determined inside the draw function, the exact diameter will be slightly different on each frame. Instead, you should create an array of these random floats in the constructor and use random_diameter[i] in the draw loop, so the sizes of the inner circles are created random, but remain constant.
The color remaining constant is caused by p.fill(REDS[i],GREENS[i],BLUES[i]);. You assign specific colors, based on the index, to a circle that is also based on the index. This is where you should use random. For red, green and blue use int(Math.random(0,255));. That way, on each frame, a random color is generated for each circle. If you want more gradual color changes, you'll need to store the color for each circle and add/subtract a small random number. If you want to limit the number of colors, you can use the function you have now, but instead of i, use a random number with the size of the array.
I hope you see that, interestingly, the problems/solutions are each others inverse :)
I am using Processing and am copying the pixels[] array pixel by pixel in a for loop to a PGraphics object.
I am interested in using pushMatrix() and popMatrix() along with some transformations, but I cannot find any info on how the translate(), rotate(), and scale() functions affect how the pixels[] array is organized.
Also, in the info I could find, it says to push the matrix, then draw, and then pop the matrix back to its original state. I am curious if copying pixels pixel by pixel would count as drawing. I know that image() is affected, but what else? Where can I find a list? What are all the types of drawing and editing of pixels that the matrix transformations affect?
Thanks
If you want to render an image into a PGraphics instance, there's no need to manually access the pixels[] array, pixel by pixel.
Notice that PGraphics provides image() which can take prior transformations (translation/rotation/scale) into account.
Here's a basic example:
PImage testImage;
PGraphics buffer;
void setup(){
size(400,400);
testImage = createImage(100,100,RGB);
//make some noise
for(int i = 0; i < testImage.pixels.length; i++){
testImage.pixels[i] = color(random(255),random(255),random(255));
}
testImage.updatePixels();
//setup PGraphics
buffer = createGraphics(width,height);
buffer.beginDraw();
//apply localised transformations
buffer.pushMatrix();
buffer.translate(width / 2, height / 2);
buffer.rotate(radians(45));
buffer.scale(1.5);
//render transformed image
buffer.image(testImage,0,0);
buffer.popMatrix();
//draw the image with no transformations
buffer.image(testImage,0,0);
buffer.endDraw();
}
void draw(){
image(buffer,0,0);
}
I have a Jpeg image with a "seeming" uniform background and an actual object in it. I want to crop the image to extract only the object.
I started with following code where I am trying to find the (x,y) on left side to start cropping.
I found out that bottom left corner of the image has (0,0) coordinates. I am looping through and trying to find out the exact point where the color changes.
The problem is during first iteration itself : when x=0 and y is incrementing, though there is no change in color it is giving different RGB values for few pixels.(like pixel 240 , 241, 242)
BufferedImage bf =ImageIO.read.file(imagePath);
for(int x= 0;x<bf.getWidth();x++)
{
for(int y=0; j<bf.getHeight();y++)
{
int color = bf.RGB(x,y);
int adjacentColor = bf.(x,y+1);
if(color !=adjacentColor)
{
LeftBoundaryPixels[count]=y;
count++;
break;
}
}
}
You can use a color distance formula (like the distance formula sqrt(x^2 + y^2)) with a threshold distance to dismiss pixels that are close but not completely the same. For example: sqrt(r^2 + g^2 + b^2);.
Few days ago I figured out how to do some scrolling in LibGdx. Now I'm triying to do something related. I want to repeat the background. My scrolling follows a ship (Is an s[ace ship game). In the background there is a space photo loaded as a Texture. When the ship reach the end of the backgorund, It keeps going and there's no background anymore. I have read about wrap but I don't really understand How It works. I did that:
px=new Pixmap(Gdx.files.internal("fondo.jpg"));
background=new Texture(px);
background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
And then, in my render method
spriteBatch.begin();
spriteBatch.draw(background,0,0,500,50);
drawShip();
spriteBatch.end();
Of course It doesn't work, It only draws the background once. I don't know how make this wrap method work. Any help?
SOLUTION
I figured It out. It's not a nice code but It works.
First I declare two Textures with the same image
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
Also I declare two variables like this to specify the X value of the position of each bck
int posXBck1=0,posXBck2=0;
Then I use that in Render()
public void calculoPosicionFondos(){
posXBck2=posXBck1+ANCHODEFONDO;
if(cam.position.x>=posXBck2+cam.viewportWidth/2){
posXBck1=posXBck2;
}
}
Where:
ANCHODEFONDO is the width of my background
Cam is an OtrhoCam.
So I said that if the cam is in bck2 (wich means that you can't see bck1 anymore) It change positions, giving bck1 de position of bck2 and, in the next render loop, recalculating bck2
Then just paint both bck in your render mode.
Like Teitus said, do not load your texture multiple times, ever! Anyway, you where on the right track with the wrapper:
texture.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
Now you can just use the draw method with the source location. The source location is the area you choose to draw on the texture.
batch.draw(texture, x, y, srcX, srcY, srcWidth, srcHeight)
To scroll your texture from right to left all you have to do is increase srcX incrementally. So create a int that increments in the update/render method.
int sourceX = 0;
//render() method
//Increment the variable where to draw from on the image.
sourceX += 10;
//Simply draw it using that variable in the srcX.
batch.draw(YourTexture, 0, 0, sourceX, 0, screenWidth, screenHeight);
Because you are wrapping the texture it will wrap/loop and scroll indefinitely. There might be a issue with the sourceX int if the game runs for a very long time because a int can only hold 2147483647. It takes a while but you can fix it by subtracting the image width each time the number goes over the total image width.
Don't to this, please:
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
That will load your big background texture twice. That's a complete waste. If you want to keep your solution at least do:
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=bkg1;
Regarding the texture Wrapping. If your texture is 500px wide, and you draw a 500px sprite, you won't see any repetition. If you want it repeated 2 times, draw it 1000px wide with 0-2 texture coordinates.
I'm not sure how spriteBatch handles the call you posted, you could try that one, or may be use the overload that uses a texture region and set your region manually.
I see this is a pretty old question, but I think there is an easier way to accomplish background scrolling. Just use the Sprite class. Here is a snippet I use for layered background images that scroll from right to left.
public class LevelLayer
{
public float speedScalar = 1;
private List<Sprite> backgroundSprites = new ArrayList<Sprite>();
public LevelLayer()
{
}
public void addSpriteLayer(Texture texture, float startingPointX, float y, int repeats)
{
for (int k = 0; k < repeats; k++)
{
Sprite s = new Sprite(texture);
s.setX(startingPointX + (k*texture.getWidth()));
s.setY(y);
backgroundSprites.add(s);
}
}
public void render(SpriteBatch spriteBatch, float speed)
{
for (Sprite s : backgroundSprites)
{
float delta = s.getX() - (speed * speedScalar);
s.setX(delta);
s.draw(spriteBatch);
}
}
}
Then you can use the same texture or series of textures like so:
someLayer.addSpriteLayer(sideWalkTexture1, 0, 0, 15);
someLayer.addSpriteLayer(sideWalkTexture2, 15 * sideWalkTexture1.getWidth(), 0, 7);
I change background repeating sections randomly in code and make new ones or reset existing sets when they go off screen. All the layers go to a pool and get pulled randomly when a new one is needed.
SOLUTION
I figured It out. It's not a nice code but It works.
First I declare two Textures with the same image
bck1=new Texture(Gdx.files.internal("fondo.jpg"));
bck2=new Texture(Gdx.files.internal("fondo.jpg"));
Also I declare two variables like this to specify the X value of the position of each bck
int posXBck1=0,posXBck2=0;
Then I use that in Render()
public void calculoPosicionFondos(){
posXBck2=posXBck1+ANCHODEFONDO;
if(cam.position.x>=posXBck2+cam.viewportWidth/2){
posXBck1=posXBck2;
}
}
Where:
ANCHODEFONDO is the width of my background
Cam is an OtrhoCam.
So I said that if the cam is in bck2 (wich means that you can't see bck1 anymore) It change positions, giving bck1 de position of bck2 and, in the next render loop, recalculating bck2
Then just draw both bck in your render()
For college, we have been given an assignment where, given an image, we have to identify the "figures", their color, and the amount of "pixel-groups" inside them. Let me explain:
The image above has one figure (in the image there can be multiple figures, but let us forget about that for now).
The background color of the canvas is the pixel at 0,0 (in this case, yellow)
The border color of the figure is black (it can be any color other than the canvas' background color).
The figure's background color is white (it can also be the same as the canvas' background color).
A figure can only have one background color.
There are two pixel groups in the figure. One is a pool of blue pixels, and the other is a pool of red with some green inside. As you can see, it doesn't matter the color of the pixel group's pixels (it is just different than the figure's background color). What matters is the fact that they're in contact (even diagonally). So despite having two different colors, such group is considered as just one anyway.
As you can see, the border can be as irregular as you wish. It only has, however, one color.
It is known that a pixel group will not touch the border.
I was told that a pixel group's colors can be any except the figure's background color. I assume that then it can be the same as the figure's border color (black).
We have been given a class capable of taking images and converting them to a matrix (each element being an integer representing the color of the pixel).
And that's it. I'm doing it with Java.
WHAT HAVE I DONE SO FAR
Iterate through each pixel in the matrix
If I find a pixel that is different from the background color, I will assume it belongs to the border of the figure. I will call this pixel initialPixel from now on.
Note that the initialPixel in the image I provided is that black pixel in the top-left corner of the figure. I made a sharp cut there purposefully to illustrate it.
My mission now is to find the background color of the figure (in this case white).
But I'm having quite a great deal of trouble to find such background color (white). This is the closest method I did, which worked for some cases - but not with this image:
Since I know the color of the border, I could find the first different color that is to the south of the initialPixel. Did sound like a good idea - it did work sometimes, but it would not work with the image provided: it will return yellow in this case, since initialPixel is quite away from the figure's contents.
Assuming I did find the figure's background color (white), my next task would be to realize that there exist two pixel groups within the figure. This one seems easier:
Since I now know the figure's background color (white), I can try iterating through each pixel within the figure and, if I find one that does not belong to the border and is not part of the figure's background, I can already tell there is one pixel group. I can begin a recursive function to find all pixels related to such group and "flag" them so that in the future iterations I can completely ignore such pixels.
WHAT I NEED
Yes, my problem is about how to find the figure's background color (keep in mind it can be the same as the whole image's background color - for now it is yellow, but it can be white as well) based on what I described before.
I don't need any code - I'm just having trouble thinking a proper algorithm for such. The fact that the border can have such weird irregular lines is killing me.
Or even better: have I been doing it wrong all along? Maybe I shouldn't have focused so much on that initialPixel at all. Maybe a different kind of initial method would have worked? Are there any documents/examples about topics like this? I realize there is a lot of research on "computer vision" and such, but I can't find much about this particular problem.
SOME CODE
My function to retrieve a vector with all the figures:
*Note: Figure is just a class that contains some values like the background color and the number of elements.
public Figure[] getFiguresFromImage(Image image) {
Figure[] tempFigures = new Figure[100];
int numberOfFigures = 0;
matrixOfImage = image.getMatrix();
int imageBackgroundColor = matrixOfImage[0][0];
int pixel = 0;
for (int y = 0; y < matrixOfImage.length; ++y) {
for (int x = 0; x < matrixOfImage[0].length; ++x) {
pixel = matrixOfImage[y][x];
if (!exploredPixels[y][x]) {
// This pixel has not been evaluated yet
if (pixel != imageBackgroundColor ) {
// This pixel is different than the background color
// Since it is a new pixel, I assume it is the initial pixel of a new figure
// Get the figure based on the initial pixel found
tempFigures[numberOfFigures] = retrieveFigure(y,x);
++numberOfFigures;
}
}
}
}
// ** Do some work here after getting my figures **
return null;
}
Then, clearly, the function retrieveFigure(y,x) is what I am being unable to do.
Notes:
For learning purposes, I should not be using any external libraries.
A good way to solve this problem is to treat the image as a graph, where there is one node ('component' in this answer) for each color filled area.
Here is one way to implement this approach:
Mark all pixels as unvisited.
For each pixel, if the pixel is unvisited, perform the flood fill algorithm on it. During the flood fill mark each connected pixel as visited.
Now you should have a list of solid color areas in your image (or 'components'), so you just have to figure out how they are connected to each other:
Find the component that has pixels adjacent to the background color component - this is your figure border. Note that you can find the background color component by finding the component with the 0,0 pixel.
Now find the components with pixels adjacent to the newly found 'figure border' component. There will be two such components - pick the one that isn't the background (ie that doesn't have the 0,0 pixel). This is your figure background.
To find the pixel groups, simply count the number of components with pixels adjacent to the figure background component (ignoring of course the figure border component)
Advantages of this approach:
runs in O(# pixels) time.
easy to understand and implement.
doesn't assume the background color and figure background color are different.
To make sure you understand how iterating through the components and their neighbors might work, here's an example pseudocode implementation for step 5:
List<Component> allComponents; // created in step 2
Component background; // found in step 3 (this is the component with the 0,0 pixel)
Component figureBorder; // found in step 4
List<Component> pixelGroups = new List<Component>(); // list of pixel groups
for each Component c in allComponents:
if c == background:
continue;
for each Pixel pixel in c.pixelList:
for each Pixel neighbor in pixel.neighbors:
if neighbor.getComponent() == figureBorder:
c.isPixelGroup = true;
int numPixelGroups = 0;
for each Component c in allComponents:
if (c.isPixelGroup)
numPixelGroups++;
Try this code :
import java.util.Scanner;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
class Analyzer{
private int pixdata[][];
private int rgbdata[][];
private BufferedImage image;
int background_color;
int border_color;
int imagebg_color;
private void populateRGB(){
rgbdata = new int[image.getWidth()][image.getHeight()];
for(int i = 0; i < image.getWidth(); i++){
for(int j = 0; j < image.getHeight(); j++){
rgbdata[i][j] = image.getRGB(i, j);
}
}
int howmanydone = 0;
int prevcolor,newcolor;
prevcolor = rgbdata[0][0];
/*
for(int i = 0; i < image.getWidth(); i++){
for(int j = 0; j < image.getHeight(); j++){
System.out.print(rgbdata[i][j]);
}
System.out.println("");
}*/
for(int i = 0; i < image.getWidth(); i++){
for(int j = 0; j < image.getHeight(); j++){
newcolor = rgbdata[i][j];
if((howmanydone == 0) && (newcolor != prevcolor)){
background_color = prevcolor;
border_color = newcolor;
prevcolor = newcolor;
howmanydone = 1;
}
if((newcolor != prevcolor) && (howmanydone == 1)){
imagebg_color = newcolor;
}
}
}
}
public Analyzer(){ background_color = 0; border_color = 0; imagebg_color = 0;}
public int background(){ return background_color; }
public int border() { return border_color;}
public int imagebg() {return imagebg_color;}
public int analyze(String filename,String what) throws IOException{
image = ImageIO.read(new File(filename));
pixdata = new int[image.getHeight()][image.getWidth()];
populateRGB();
if(what.equals("background"))return background();
if(what.equals("border"))return border();
if(what.equals("image-background"))return imagebg();
else return 0;
}
}
public class ImageAnalyze{
public static void main(String[] args){
Analyzer an = new Analyzer();
String imageName;
Scanner scan = new Scanner(System.in);
System.out.print("Enter image name:");
imageName = scan.nextLine();
try{
int a = an.analyze(imageName,"border");//"border","image-background","background" will get you different colors
System.out.printf("Color bg: %x",a);
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
the color returned is ARGB format. You will need to extract R,G and B from it.
There is a bug in this code. Working on implementation using Finite State machine. in the first state you're inside the image, hence 0,0 is the background color, then when there is a change, the change is the border color, then the third state is when inside the image + inside the border and the color changes.