How can i improve the quality of line drawn using openGL. I dont know if im doing this the best way . Im using ShareRenderer and line. But as you can see in the image round corners dont render very well.
Im getting points in touch down and drag, i thought this would be easy to draw a line but i guess this needs to done differently in openGL. I want to trace over letters with a finger for learning to write. I can use openGL-es 1 or 2 , doesn't matter. The device used here for the image was Samsung S3.
#Override
public void render(float delta) {
super.render(delta);
if(mPoints.size() < maxPoints) return;
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
for(int i=0; i < maxPoints-1; i++) {
Vector3 pnt = mPoints.get(i);
if(pnt.x == 0 && pnt.y == 0) continue;
Vector3 pnt2 = mPoints.get(i+1);
if(pnt2.x == 0 && pnt2.y == 0) continue;
shapeRenderer.line(pnt.x, pnt.y, pnt2.x, pnt2.y);
}
shapeRenderer.end();
}
#Override
public void resize(int width, int height) {
super.resize(width, height);
shapeRenderer.setProjectionMatrix(getCamera().combined);
Gdx.gl20.glLineWidth(200);
Gdx.gl.glEnable(GL20.GL_BLEND);
}
#Override
public boolean touchDown(int x, int y, int pointer, int button) {
currentFinger = pointer;
if (++numStrokes >= 2) { //number of strokes for a letter
numStrokes = 0;
clearAllPoints();
currentPointIndex = 0;
}
setPoint(0, 0);
return false;
}
#Override
public boolean touchDragged(int x, int y, int pointer) {
if(currentFinger!=pointer) return false;
Vector3 pnt = mPoints.get(currentPointIndex);
tempVector.set(x, y, 0);
getCamera().unproject(tempVector);
float dx = Math.abs(tempVector.x - pnt.x);
float dy = Math.abs(tempVector.y - pnt.y);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
setPoint(x, y);
}
return false;
}
UPDATED render method below to draw filled rectangles and circles, filled circles works much better.
public void render(float delta) {
super.render(delta);
if(mPoints.size() < maxPoints) return;
shapeRenderer.begin(ShapeRenderer.ShapeType.FilledRectangle);
for(int i=0; i < maxPoints-1; i++) {
Vector3 pnt = mPoints.get(i);
if(pnt.x == 0 && pnt.y == 0) continue;
Vector3 pnt2 = mPoints.get(i+1);
if(pnt2.x == 0 && pnt2.y == 0) continue;
//This creates a rectangle from the set of points see : http://stackoverflow.com/questions/7854043/drawing-rectangle-between-two-points-with-arbitrary-width
dist.set(pnt2.x - pnt.x, pnt2.y - pnt.y, 0);
pVector.set(dist.y, -dist.x, 0); //Find perpendicular (right angle) to points , basically swap x and y , make one negative
float length = (float) Math.sqrt(pVector.x * pVector.x + pVector.y * pVector.y); //Thats length of perpendicular
normVector.set(pVector.x - length, pVector.y-length, 0);
//shapeRenderer.filledCircle(pnt.x, pnt.y, lineWidth);
shapeRenderer.filledRect(pnt.x - pVector.x * rectWidth/2 , pnt.y - pVector.y * rectWidth /2, rectWidth, rectWidth);
}
shapeRenderer.end();
}
OpenGL line width other than 1.0 is not well supported in OpenGL ES, so you should avoid it on Android (See Libgdx gl10.glLineWidth()).
To get a smoother, more rounded looking "fat lines", draw each touch point as a circle, and connect points that are more than a couple pixels apart with a rectangle.
See:
libgdx- pixmap: can I somehow change the width of the line?
Drawing rectangle between two points with arbitrary width
TO smoother any line or shape you can use feature of Multisample_anti-aliasing
to do this what you have to do is:
replace the parameters in Gdx.gl.glClear();
use this:
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT | (Gdx.graphics.getBufferFormat().coverageSampling?GL20.GL_COVERAGE_BUFFER_BIT_NV:0));
and in addition to this go to default packages like android html desktop etc and add this line
for android:
config.numSamples =2;
for ios:
config.multisample = GLKViewDrawableMultisample.valueOf("2");
for desktop:
config.samples = 3;
I am new to libGDX so i found this the easiest way to do it with any shape..
Related
I am developing a game in AndEngine and I want to attach a sprite to a parallax background (on my main menu) BUT I don't want the sprite to be repeated (which is what is currently happening).
I have tried this (below) which works but I use the sprites in the game so when I come back to the main menu, the sprites will have moved (I tried resetting the sprites but doesn't seem to be working).
Sprite playerCar = new Sprite(playerX, playerY,
mResourceManager.mPlayerCarTextureRegion,
mVertexBufferObjectManager);
playerCar.setRotation(-15);
attachChild(playerCar);
What I want to do is the following:
Define my sprite as normal:
Sprite playerCar = new Sprite(playerX, playerY,
mResourceManager.mPlayerCarTextureRegion,
mVertexBufferObjectManager);
playerCar.setRotation(-15);
Then attach it to my background:
ParallaxBackground menuParallaxBackground = new ParallaxBackground(0,
0, 0);
menuParallaxBackground.attachParallaxEntity(new ParallaxEntity(0,
new Sprite(0, SCREEN_HEIGHT
- mResourceManager.mParallaxLayerRoad.getHeight(),
mResourceManager.mParallaxLayerRoad,
mVertexBufferObjectManager)));
menuParallaxBackground.attachParallaxEntity(new ParallaxEntity(0,
playerCar));
Which also works but the car keeps on repeating which I do not want.
Any help would be appreciated! Thanks.
Problem was because of the onDraw method on the ParallaxEntity class in the ParallaxBackground class. There was a loop around where the sprites get drawn that keeps going until the sprites fill up the width of the screen. I simply created a custom class and removed the while loop so I could use it for my Main Menu.
ParallaxEntity onDraw code before:
public void onDraw(final GLState pGLState, final Camera pCamera, final float pParallaxValue) {
pGLState.pushModelViewGLMatrix();
{
final float cameraWidth = pCamera.getWidth();
final float shapeWidthScaled = this.mAreaShape.getWidthScaled();
float baseOffset = (pParallaxValue * this.mParallaxFactor) % shapeWidthScaled;
while(baseOffset > 0) {
baseOffset -= shapeWidthScaled;
}
pGLState.translateModelViewGLMatrixf(baseOffset, 0, 0);
float currentMaxX = baseOffset;
do {
this.mAreaShape.onDraw(pGLState, pCamera);
pGLState.translateModelViewGLMatrixf(shapeWidthScaled, 0, 0);
currentMaxX += shapeWidthScaled;
} while(currentMaxX < cameraWidth);
}
pGLState.popModelViewGLMatrix();
}
my CustomParallaxEntity onDraw code after (notice the last while loop removed):
public void onDraw(final GLState pGLState, final Camera pCamera,
final float pParallaxValue) {
pGLState.pushModelViewGLMatrix();
{
final float shapeWidthScaled = this.mAreaShape.getWidthScaled();
float baseOffset = (pParallaxValue * this.mParallaxFactor)
% shapeWidthScaled;
while (baseOffset > 0) {
baseOffset -= shapeWidthScaled;
}
pGLState.translateModelViewGLMatrixf(baseOffset, 0, 0);
this.mAreaShape.onDraw(pGLState, pCamera);
pGLState.translateModelViewGLMatrixf(shapeWidthScaled, 0, 0);
}
pGLState.popModelViewGLMatrix();
}
Thanks to the comments on my question for helping me figure out a solution.
I have made a sketch in processing using saitoobjloader and controlP5 librarys, and it works just fine, and after that i have exported as an android app to import it in eclipse. I have runned it then on my phone, from eclipse, and it works, except that, everything is black and white.I have tried model.disableMaterial(), model.disableTexture() and it does not work, I have seted for android 2.3.3, 4.2.2, 4.4 and it also don't work...stil black and white. I have tried primitive debug by disabeling everything i can one by one: the animation, the boject loaded, screen orientation and stil black and white. Idon't have any permisions aded, and the only change i have made is for screen orientation in manifes aplication: Debuggable --> null, Screen orientation --> unspecified, Config changes --> orientation.
Below you can see my code from eclipse:
package processing.test.my_cnc_obj_with_gui_mooving_animation;
import processing.core.*;
import android.content.res.Configuration;
import saito.objloader.*;
import controlP5.*;
public class My_cnc_obj_with_GUI_mooving_animation extends PApplet {
OBJModel model;
ControlP5 sb;
float rotX;
float rotY;
float zoom = 1;
float translateX;
float translateY;
float k;
int i;
float initlength = 250;
int initlengthX = 150;
int initlengthY = 150;
int initlengthZ = 80;
float moveIndex;
float AXA_X;
float AXA_Y;
float AXA_Z;
public void setup() {
model = new OBJModel(this, "CNC colorat pentru processing.obj");
model.scale(400);
model.translateToCenter();
//model.disableTexture();
model.disableMaterial();
//model.disableDebug();
controlereGUI();
noStroke();
}
public void draw() {
background(50, 34, 32);
lights();
// fill(255,0,255);
pushMatrix();
translate(width/2 - 50, height/2, 0);
rotateX(rotY);
rotateY(rotX);
model.draw();
model.disableMaterial();
popMatrix();
animation();
}
public void mouseDragged()
{
rotX += (mouseX - pmouseX) * 0.01f;
rotY -= (mouseY - pmouseY) * 0.01f;
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
int orientation = newConfig.orientation;
model.disableMaterial();
//Something strange happened... bail out!
if(orientation == Configuration.ORIENTATION_UNDEFINED)
return;
orientation = Configuration.ORIENTATION_PORTRAIT;
if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
//Landscape config
}
if(orientation == Configuration.ORIENTATION_PORTRAIT) {
//Portrait config
}
}
/*public void mouseWheel(MouseEvent e) {
translateX = translateX-e.getAmount()*(mouseX)/100;
translateY = translateY-e.getAmount()*(mouseY)/100;
zoom += e.getAmount() / 100;
model.scale(zoom);
}*/
public void controlereGUI() {
sb = new ControlP5(this);
sb.getTab("default")
//.activateTab("Assisted_controll")
//.activateEvent(true)
.setHeight(40)
.setLabel("Assisted controll")
.setId(3)
;
sb.getTab("Manual_controll")
//.activateTab("Manual_controll")
//.activateEvent(true)
.setHeight(40)
.setLabel("Manual controll")
.setId(1)
;
sb.addTab("Settings")
//.activateTab("Settings")
.setHeight(40)
.setWidth(60)
.setLabel("Settings")
.setId(2)
.setColorBackground(color(0, 160, 100))
//.activateEvent(true)
.setColorLabel(color(255))
.setColorActive(color(255, 128, 0))
;
sb.addSlider("AXA_X")
.setPosition(10, 70)
//.setWidth(300)
.setSize(300, 30)
.setRange(initlength, 0)
//.setValue(128)
.setSliderMode(Slider.FLEXIBLE)
;
sb.addSlider("AXA_Y")
.setPosition(10, 110)
//.setWidth(300)
.setSize(300, 30)
.setRange(initlengthY, -initlengthY)
//.setValue(128)
.setSliderMode(Slider.FLEXIBLE)
.setColorForeground(0xffFC0000)
.setColorBackground(color(150, 0, 0))
;
sb.addSlider("AXA_Z")
.setPosition(10, 150)
//.setWidth(300)
.setSize(300, 30)
.setRange(initlengthZ + 120, 70)
//.setValue(128)
.setSliderMode(Slider.FLEXIBLE)
.setColorForeground(0xff0BFC00)
.setColorBackground(color(100, 150, 0))
;
sb.addButton("Start_movement")
.setBroadcast(false)
.setValue(128)
.setPosition(120, height-160)
//.setImages(imgs)
.setSize(90, 50)
.setCaptionLabel("Start movement")
.setColorBackground(color(100, 150, 0))
//.setVisible(false)
.setBroadcast(true)
;
sb.addButton("Stop_movement")
.setValue(128)
.setPosition(120, height-100)
//.setImages(imgs)
.setSize(90, 50)
.setCaptionLabel("Stop movement")
.setColorBackground(color(150, 0, 0))
//.setVisible(false)
;
}
public void animation() {
for (int i = 0; i < model.getVertexCount () - 6090; i++) {
PVector orgv = model.getVertex(i);
PVector tmpv = new PVector();
tmpv.x = orgv.x;
tmpv.y = orgv.y;
tmpv.z = orgv.z + (k * AXA_Y);
model.setVertex(i, tmpv);
}
if ((AXA_Y != 0)) {
if (moveIndex < AXA_Y)
moveIndex ++;
else
if (moveIndex > AXA_Y)
moveIndex --;
else
k = 0;
k = 1;
} else {
moveIndex = AXA_Y;
k = 0;
}
}
public int sketchWidth() { return 800; }
public int sketchHeight() { return 700; }
public String sketchRenderer() { return P3D; }
}
I don'tknow why is stil black and white.
Thanks in advance.
Solved.
It seems that i must use a combination of use of material (not disable it) and after drawing the model to stop the lights. So, it should be like in the code above, with this modifications:
public void setup() {
model = new OBJModel(this, "CNC colorat pentru processing.obj");
model.scale(400);
model.translateToCenter();
controlereGUI();
noStroke();
}
public void draw() {
background(50, 34, 32);
lights();
// fill(255,0,255);
pushMatrix();
translate(width/2 - 50, height/2, 0);
rotateX(rotY);
rotateY(rotX);
model.draw();
popMatrix();
noLights();
animation();
}
However, it seems that the 3D model remains still black and white even if is coloured. The 3D model was modified with Blender v2.71 (imported as .wrl file and exported as .obj with .mtl file). The .wrl file has been initial colored and when it was imported in Blender everything was ok, the model was colored. It seems that processing can't get the material. The debug shows: Material 'Shape.1687' not defined, ..., Material 'Shape.001' not defined,
Material 'Shape' not defined
I don't know where the problem is: inside saitoobjloader or the .obj and .mtl files....
Solved the second problem too. The problem is in file's name "CNC colorat pentru processing".
It seems that saito's objloader parser does not read spaces caracter (" ") in files name, so when i am making a new obj file (in blender or any other), i should have save it with the name "CNC_colorat_pentru_processing", or any other name that does not contain space caracter.
I have tried in OpenCV- Sample-Color-blob-detection code as well ,but What I actually want is this to work on a Bitmap, not camera view.
I have tried the below code ,
public boolean onTouch(View v, MotionEvent event)
{
int x = (int)event.getX();
int y = (int)event.getY();
Log.e("Test", "Touch image coordinates:"+x+" , "+y);
Log.e("Test", "color:"+bimp.getPixel(x, y));
int xclear = bimp.getPixel(x, y) ;
int xclear_red = Color.red(xclear) ;
int xclear_blue = Color.blue(xclear) ;
int xclear_green = Color.green(xclear) ;
if ((x < 0) || (y < 0) || (x > bimp.getWidth()) || (y > bimp.getHeight()))
return false;
for(int x1=0 ; x1<bimp.getWidth() ; x1++)
{
for(int y1=0 ; y1<bimp.getHeight() ; y1++)
{
int px = bimp.getPixel(x1, y1);
int px_red = Color.red(px) ;
int px_blue = Color.blue(px) ;
int px_green = Color.green(px) ;
if((px_red+10 > xclear_red) && (px_red -10 < xclear_red))
{
if((px_blue > xclear_blue) && (px_blue-10 < xclear_blue))
{
if((px_green+10 > xclear_green) && (px_green-10 < xclear_green))
{
bimp.setPixel(x1, y1, Color.TRANSPARENT);
}
}
}
}
if(x1 == bimp.getWidth()-1 )
img.setImageBitmap(bimp);
}
return false; // don't need subsequent touch events
}
it is making the pixel which resembles ( + or - 10) of the pixel color , I have touched on transparent .
But what I actually want is (shown in the image below).
that is to select the similar coloured pixels(shown with red coloured border) as the action of wand tool in photoshop . So that i could make the selected portion transparent or crop.
Please suggest me some ideas .Thanks in Advance .
I think what you need is flood fill algorithm. It may be of some use to you.
http://en.wikipedia.org/wiki/Flood_fill
I Have Some static images like below:
Now, I want is, when i touch on the face or hand, then the selected color should be fill on that skin portion.
See below image of result:
So how to get the result like above ??
Redo and Undo Functionality Should be also there.
I have try with the FloodFill color but doing that i can only able to do color in to the perticular portion. as FloodFill only fill the color till the same pixwl color comes. If the touch place pixel color get change the it will not fill color on it.
So Usinf FloodFill i got the result like below image, If i press on the hand, then only hand portion will fill with color, instead of it i want to fill color to the other hand and face also.
So Please help me in this case.
EDITED
After some reply i got the solution like this one.
But still there is a memory issue. It consume lots of memory to draw the color. So please can anyone help me for it ?
You can have a complete image colored the actual way and when you fill a certain region with a color, it will replace all the regions that is specified by that color to be filled in.
Layman's terms:
User will click on the hand of the OUTLINE
That click location will be checked with another image with perfectly color coded regions. Lets call it a MASK for this case. All the skin regions will have the same color. The shirt areas will be another color.
Wherever the user clicks, the selected color by the user will be applied to every pixel that has that similar color in the MASK, but instead of painting directly on the MASK, you paint onto the pixels of the the OUTLINE.
I hope this helps.
Feel free to comment if you want an example and then I can update the answer with that, but I think you can get it from here.
EDIT:
Basically start off with a simple image like this. This we can call as OUTLINE
Then as the developer, you have to do some work. Here, you color code the OUTLINE. The result we call a MASK. To make this we, color code the regions with the same color that you want. This can be done on paint or whatever. I used Photoshop to be cool lol :D.
Then there is the ALGORITHM to get it working on the phone. Before you read the code, look at this variable.
int ANTILAISING_TOLERANCE = 70; //Larger better coloring, reduced sensing
If you zoom up on the image specifically noting the black regions of the border, you can actually see that sometimes, the computer blends the colors a little bit. In order to account for that change, we use this tolerance value.
COLORINGANDROIDACTIVITY.JAVA
package mk.coloring;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.view.View.OnTouchListener;
public class ColoringAndroidActivity extends Activity implements OnTouchListener{
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.imageView1).setOnTouchListener(this);
}
int ANTILAISING_TOLERANCE = 70;
public boolean onTouch(View arg0, MotionEvent arg1) {
Bitmap mask = BitmapFactory.decodeResource(getResources(), R.drawable.mask);
int selectedColor = mask.getPixel((int)arg1.getX(),(int)arg1.getY());
int sG = (selectedColor & 0x0000FF00) >> 8;
int sR = (selectedColor & 0x00FF0000) >> 16;
int sB = (selectedColor & 0x000000FF);
Bitmap original = BitmapFactory.decodeResource(getResources(), R.drawable.empty);
Bitmap colored = Bitmap.createBitmap(mask.getWidth(), mask.getHeight(), Config.ARGB_8888);
Canvas cv = new Canvas(colored);
cv.drawBitmap(original, 0,0, null);
for(int x = 0; x<mask.getWidth();x++){
for(int y = 0; y<mask.getHeight();y++){
int g = (mask.getPixel(x,y) & 0x0000FF00) >> 8;
int r = (mask.getPixel(x,y) & 0x00FF0000) >> 16;
int b = (mask.getPixel(x,y) & 0x000000FF);
if(Math.abs(sR - r) < ANTILAISING_TOLERANCE && Math.abs(sG - g) < ANTILAISING_TOLERANCE && Math.abs(sB - b) < ANTILAISING_TOLERANCE)
colored.setPixel(x, y, (colored.getPixel(x, y) & 0xFF000000) | 0x00458414);
}
}
((ImageView)findViewById(R.id.imageView1)).setImageBitmap(colored);
return true;
}
}
This code doesn't provide the user with much of color choices. Instead, if the user touches a region, it will look at the MASK and paint the OUTLINE accordingly. But, you can make really interesting and interactive.
RESULT
When I touched the man's hair, it not only colored the hair, but colored his shirt and hand with the same color. Compare it with the MASK to get a good idea of what happened.
This is just a basic idea. I have created multiple Bitmaps but there is not really a need for that. I had used it for testing purposes and takes up unnecessary memory. And you don't need to recreate the mask on every click, etc.
I hope this helps you :D
Good luck
Use a FloodFill Algorithm. Fill the complete canvas but keep the bound fill area as it is like circle, rectangle. You can also check this link. Android: How to fill color to the specific part of the Image only?. The general idea get the x and y co-ordinates on click.
final Point p1 = new Point();
p1.x=(int) x; p1.y=(int) y; X and y are co-ordinates when user clicks on the screen
final int sourceColor= mBitmap.getPixel((int)x,(int) y);
final int targetColor =mPaint.getColor();
new TheTask(mDrawingManager.mDrawingUtilities.mBitmap, p1, sourceColor, targetColor).execute(); //Use AsyncTask and do floodfillin the doinBackground().
Check the above links for floodfill algorithmin android. This should help you achieve what you want. Android FingerPaint Undo/Redo implementation. This should help you modify according to your needs regarding undo and redo.
Edit:
A post on stackoverflow led me to a efficient way of using flood fill algorithm without delay and OOM.
Picking from the SO Post
Filling a small closed area works fine with the above flood fill algorithm. However for large area the algorithm works slow and consumes lot of memory. Recently i came across a post which uses QueueLinear Flood Fill which is way faster that the above.
Source :
http://www.codeproject.com/Articles/16405/Queue-Linear-Flood-Fill-A-Fast-Flood-Fill-Algorith
Code :
public class QueueLinearFloodFiller {
protected Bitmap image = null;
protected int[] tolerance = new int[] { 0, 0, 0 };
protected int width = 0;
protected int height = 0;
protected int[] pixels = null;
protected int fillColor = 0;
protected int[] startColor = new int[] { 0, 0, 0 };
protected boolean[] pixelsChecked;
protected Queue<FloodFillRange> ranges;
// Construct using an image and a copy will be made to fill into,
// Construct with BufferedImage and flood fill will write directly to
// provided BufferedImage
public QueueLinearFloodFiller(Bitmap img) {
copyImage(img);
}
public QueueLinearFloodFiller(Bitmap img, int targetColor, int newColor) {
useImage(img);
setFillColor(newColor);
setTargetColor(targetColor);
}
public void setTargetColor(int targetColor) {
startColor[0] = Color.red(targetColor);
startColor[1] = Color.green(targetColor);
startColor[2] = Color.blue(targetColor);
}
public int getFillColor() {
return fillColor;
}
public void setFillColor(int value) {
fillColor = value;
}
public int[] getTolerance() {
return tolerance;
}
public void setTolerance(int[] value) {
tolerance = value;
}
public void setTolerance(int value) {
tolerance = new int[] { value, value, value };
}
public Bitmap getImage() {
return image;
}
public void copyImage(Bitmap img) {
// Copy data from provided Image to a BufferedImage to write flood fill
// to, use getImage to retrieve
// cache data in member variables to decrease overhead of property calls
width = img.getWidth();
height = img.getHeight();
image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(image);
canvas.drawBitmap(img, 0, 0, null);
pixels = new int[width * height];
image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
public void useImage(Bitmap img) {
// Use a pre-existing provided BufferedImage and write directly to it
// cache data in member variables to decrease overhead of property calls
width = img.getWidth();
height = img.getHeight();
image = img;
pixels = new int[width * height];
image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
protected void prepare() {
// Called before starting flood-fill
pixelsChecked = new boolean[pixels.length];
ranges = new LinkedList<FloodFillRange>();
}
// Fills the specified point on the bitmap with the currently selected fill
// color.
// int x, int y: The starting coords for the fill
public void floodFill(int x, int y) {
// Setup
prepare();
if (startColor[0] == 0) {
// ***Get starting color.
int startPixel = pixels[(width * y) + x];
startColor[0] = (startPixel >> 16) & 0xff;
startColor[1] = (startPixel >> 8) & 0xff;
startColor[2] = startPixel & 0xff;
}
// ***Do first call to floodfill.
LinearFill(x, y);
// ***Call floodfill routine while floodfill ranges still exist on the
// queue
FloodFillRange range;
while (ranges.size() > 0) {
// **Get Next Range Off the Queue
range = ranges.remove();
// **Check Above and Below Each Pixel in the Floodfill Range
int downPxIdx = (width * (range.Y + 1)) + range.startX;
int upPxIdx = (width * (range.Y - 1)) + range.startX;
int upY = range.Y - 1;// so we can pass the y coord by ref
int downY = range.Y + 1;
for (int i = range.startX; i <= range.endX; i++) {
// *Start Fill Upwards
// if we're not above the top of the bitmap and the pixel above
// this one is within the color tolerance
if (range.Y > 0 && (!pixelsChecked[upPxIdx])
&& CheckPixel(upPxIdx))
LinearFill(i, upY);
// *Start Fill Downwards
// if we're not below the bottom of the bitmap and the pixel
// below this one is within the color tolerance
if (range.Y < (height - 1) && (!pixelsChecked[downPxIdx])
&& CheckPixel(downPxIdx))
LinearFill(i, downY);
downPxIdx++;
upPxIdx++;
}
}
image.setPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
// Finds the furthermost left and right boundaries of the fill area
// on a given y coordinate, starting from a given x coordinate, filling as
// it goes.
// Adds the resulting horizontal range to the queue of floodfill ranges,
// to be processed in the main loop.
// int x, int y: The starting coords
protected void LinearFill(int x, int y) {
// ***Find Left Edge of Color Area
int lFillLoc = x; // the location to check/fill on the left
int pxIdx = (width * y) + x;
while (true) {
// **fill with the color
pixels[pxIdx] = fillColor;
// **indicate that this pixel has already been checked and filled
pixelsChecked[pxIdx] = true;
// **de-increment
lFillLoc--; // de-increment counter
pxIdx--; // de-increment pixel index
// **exit loop if we're at edge of bitmap or color area
if (lFillLoc < 0 || (pixelsChecked[pxIdx]) || !CheckPixel(pxIdx)) {
break;
}
}
lFillLoc++;
// ***Find Right Edge of Color Area
int rFillLoc = x; // the location to check/fill on the left
pxIdx = (width * y) + x;
while (true) {
// **fill with the color
pixels[pxIdx] = fillColor;
// **indicate that this pixel has already been checked and filled
pixelsChecked[pxIdx] = true;
// **increment
rFillLoc++; // increment counter
pxIdx++; // increment pixel index
// **exit loop if we're at edge of bitmap or color area
if (rFillLoc >= width || pixelsChecked[pxIdx] || !CheckPixel(pxIdx)) {
break;
}
}
rFillLoc--;
// add range to queue
FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);
ranges.offer(r);
}
// Sees if a pixel is within the color tolerance range.
protected boolean CheckPixel(int px) {
int red = (pixels[px] >>> 16) & 0xff;
int green = (pixels[px] >>> 8) & 0xff;
int blue = pixels[px] & 0xff;
return (red >= (startColor[0] - tolerance[0])
&& red <= (startColor[0] + tolerance[0])
&& green >= (startColor[1] - tolerance[1])
&& green <= (startColor[1] + tolerance[1])
&& blue >= (startColor[2] - tolerance[2]) && blue <= (startColor[2] + tolerance[2]));
}
// Represents a linear range to be filled and branched from.
protected class FloodFillRange {
public int startX;
public int endX;
public int Y;
public FloodFillRange(int startX, int endX, int y) {
this.startX = startX;
this.endX = endX;
this.Y = y;
}
}
}
One basic way would be something like the floodfill algorythm.
The Wikipedia article describes the algorythm and its variations pretty well.
Here you can find a implementation on SO. But depending on your specific needs this one has to be modified.
Following this : Best approach for oldschool 2D zelda-like game
I got a simple 2D tiles generator working, im reading an int map[100][100] filled with either 1's or 0's and draw tiles according to their tile id, 0 is water, 1 grass.
Im using some basic Numpad control handler, using a camIncr (32.0f), i set the camera position according to the movement :
case KeyEvent.KEYCODE_DPAD_RIGHT:
cameraPosX = (float)(cameraPosX + camIncr);
break;
In my draw loop, im just drawing enough tiles to fit on my screen, and track the top left tile using cameraOffsetX and cameraOffsetY (its the camera position / tile size )
Im using a GLU.gluOrtho2D for my projection.
Here is the draw loop inside my custom renderer :
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glMatrixMode( GL10.GL_PROJECTION );
gl.glLoadIdentity( );
GLU.gluOrtho2D(gl, 0, scrWidth, scrHeight, 0);
repere.draw(gl, 100.0f); // this is just a helper, draw 2 lines at the origin
//Call the drawing methods
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
tiledBackground.draw(gl, filtering);
my tiledBackground draw function :
int cols = (569 / 32) + 2; // how many columns can fit on the screen
int rows = (320 / 32) + 1; // haw many rows can fit on the screen
int cameraPosX = (int) Open2DRenderer.getCameraPosX();
int cameraPosY = (int) Open2DRenderer.getCameraPosY();
tileOffsetX = (int) (cameraPosX / 32);
tileOffsetY = (int) (cameraPosY / -32);
gl.glPushMatrix();
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
try {
tile = map[y + tileOffsetY][x + tileOffsetX];
} catch (Exception e) {
e.printStackTrace(); //when out of array
tile = 0;
}
gl.glPushMatrix();
if (tile==0){
waterTile.draw(gl, filter);
}
if (tile==4) {
grassTile.draw(gl, filter);
}
gl.glTranslatef(32.0f, 0.0f, 0.0f);
}//
gl.glPopMatrix();
gl.glTranslatef(0.0f, 32.0f, 0.0f);
}
gl.glPopMatrix();
}
the waterTile and grassTile .draw function draw a 32x32 textured tile, might post the code if relevant.
Everything is fine, i can move using numpad arrows, and my map 'moves' with me, since im only drawing what i can see, its fast (see android OpenGL ES simple Tile generator performance problem where Aleks pointed me to a simple 'culling' idea)
I would like my engine to 'smooth scroll' now. I've tried tweaking the camIncr variable, the GLU.gluOrtho2D etc, nothing worked.
Any ideas ? :)
I finally found out.
i added a glTranslatef method right before entering the loop :
gl.glPushMatrix();
gl.glTranslatef(-cameraPosX%32, -cameraPosY%32, 0);
for (int y = 0; y < rows; y++) {
...
First, i was unsuccessfully trying to translate the scene using a brute cameraPosX / TILE_HEIGHT division, didn't work.
We have to translate the offset by which the tile extends beyond the screen, not the total cameraPosX offset, so we're using the Mod (%) operator instead of division.
Sorry for my bad english ^^