I'm creating a custom view on Android, but the rendered colour is always grey no matter how I try to change it.
private void init() {
Resources res = mContext.getResources();
float density = res.getDisplayMetrics().density;
mBackgroundWidth = (int)(DEFAULT_WIDTH * density); // default to 20dp
mPrimaryColor = gaugeColour;
mPrimaryWidth = (int)(DEFAULT_WIDTH * density); // default to 20dp
x_Corner=30*density;
y_Corner=30*density;
mRegularTextSize = (int)(mBackgroundWidth * 0.75); //Double the size of the width;
mRectPaintPrimary = new Paint() {
{
setDither(true);
setStyle(Style.FILL);
setStrokeCap(Cap.ROUND);
setAntiAlias(true);
}
};
mRectPaintPrimary.setColor(mPrimaryColor);
//code for text formatting followed
}
And this is the onDraw function
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);// bound our drawable Rect to stay fully within our canvas
float left=0,top=0,right=mDrawingRect.right,bottom=mDrawingRect.bottom;
mProgressRect=new RectF(left,top,(mProgressPercent/100)*right,bottom);
canvas.drawRoundRect(mProgressRect, x_Corner, y_Corner, mRectPaintPrimary);
//noinspection ResourceType
String newColor = getResources().getString(mRectPaintPrimary.getColor());
Log.d(TAG,"Rect colour while drawing is "+newColor);
String valueString=((int)mProgressPercent)+"%";
if(mProgressPercent<10)
valueString="";
canvas.drawText(valueString,mProgressRect.centerX(),mProgressRect.centerY()*1.5f,mRegularText);
}
My log actually says that programatically the colour has been modified. So I get a message on the lines of
D/GaugeView: Rect colour while drawing is #ffe64a19
But what I see on the android display is always the same grey...no matter how what I change the colour to be:
I seem to have got my required result by changing
mRectPaintPrimary.setColor(mPrimaryColor);
to
mRectPaintPrimary.setColor(getResources().getColor(mPrimaryColor));
Or even better
mRectPaintPrimary.setColor(ContextCompat.getColor(mContext,mPrimaryColor));
Related
How can we achieve the fade-out effect on the last line of a TextView, like in the "WHAT'S NEW" section in the Play Store app?
That fade effect can be accomplished by subclassing a TextView class to intercept its draw, and doing something like what the View class does to fade out edges, but only in the last stretch of the final text line.
In this example, we create a unit horizontal linear gradient that goes from transparent to solid black. As we prepare to draw, this unit gradient is scaled to a length calculated as a simple fraction of the TextView's final line length, and then positioned accordingly.
An off-screen buffer is created, and we let the TextView draw its content to that. We then draw the fade gradient over it with a transfer mode of PorterDuff.Mode.DST_OUT, which essentially clears the underlying content to a degree relative to the gradient's opacity at a given point. Drawing that buffer back on-screen results in the desired fade, no matter what is in the background.
public class FadingTextView extends AppCompatTextView {
private static final float FADE_LENGTH_FACTOR = .4f;
private final RectF drawRect = new RectF();
private final Rect realRect = new Rect();
private final Path selection = new Path();
private final Matrix matrix = new Matrix();
private final Paint paint = new Paint();
private final Shader shader =
new LinearGradient(0f, 0f, 1f, 0f, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP);
public FadingTextView(Context context) {
this(context, null);
}
public FadingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public FadingTextView(Context context, AttributeSet attrs, int defStyleAttribute) {
super(context, attrs, defStyleAttribute);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
#Override
protected void onDraw(Canvas canvas) {
// Locals
final RectF drawBounds = drawRect;
final Rect realBounds = realRect;
final Path selectionPath = selection;
final Layout layout = getLayout();
// Figure last line index, and text offsets there
final int lastLineIndex = getLineCount() - 1;
final int lastLineStart = layout.getLineStart(lastLineIndex);
final int lastLineEnd = layout.getLineEnd(lastLineIndex);
// Let the Layout figure a Path that'd cover the last line text
layout.getSelectionPath(lastLineStart, lastLineEnd, selectionPath);
// Convert that Path to a RectF, which we can more easily modify
selectionPath.computeBounds(drawBounds, false);
// Naive text direction determination; may need refinement
boolean isRtl =
layout.getParagraphDirection(lastLineIndex) == Layout.DIR_RIGHT_TO_LEFT;
// Narrow the bounds to just the fade length
if (isRtl) {
drawBounds.right = drawBounds.left + drawBounds.width() * FADE_LENGTH_FACTOR;
} else {
drawBounds.left = drawBounds.right - drawBounds.width() * FADE_LENGTH_FACTOR;
}
// Adjust for drawables and paddings
drawBounds.offset(getTotalPaddingLeft(), getTotalPaddingTop());
// Convert drawing bounds to real bounds to determine
// if we need to do the fade, or a regular draw
drawBounds.round(realBounds);
realBounds.offset(-getScrollX(), -getScrollY());
boolean needToFade = realBounds.intersects(getTotalPaddingLeft(), getTotalPaddingTop(),
getWidth() - getTotalPaddingRight(), getHeight() - getTotalPaddingBottom());
if (needToFade) {
// Adjust and set the Shader Matrix
final Matrix shaderMatrix = matrix;
shaderMatrix.reset();
shaderMatrix.setScale(drawBounds.width(), 1f);
if (isRtl) {
shaderMatrix.postRotate(180f, drawBounds.width() / 2f, 0f);
}
shaderMatrix.postTranslate(drawBounds.left, drawBounds.top);
shader.setLocalMatrix(shaderMatrix);
// Save, and start drawing to an off-screen buffer
final int saveCount;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveCount = canvas.saveLayer(null, null);
} else {
saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
}
// Let TextView draw itself to the buffer
super.onDraw(canvas);
// Draw the fade to the buffer, over the TextView content
canvas.drawRect(drawBounds, paint);
// Restore, and draw the buffer back to the Canvas
canvas.restoreToCount(saveCount);
} else {
// Regular draw
super.onDraw(canvas);
}
}
}
This is a drop-in replacement for TextView, and you'd use it in your layout similarly.
<com.example.app.FadingTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e2f3eb"
android:textColor="#0b8043"
android:lineSpacingMultiplier="1.2"
android:text="#string/umang" />
Notes:
The fade length calculation is based on a constant fraction of the final line's text length, here determined by FADE_LENGTH_FACTOR. This seems to be the same basic methodology of the Play Store component, as the absolute length of the fade appears to vary with line length. The FADE_LENGTH_FACTOR value can be altered as desired.
FadingTextView currently extends AppCompatTextView, but it works perfectly well as a plain TextView, if you should need that instead. I would think that it will work as a MaterialTextView too, though I've not tested that thoroughly.
This example is geared mainly toward relatively plain use; i.e., as a simple wrapped, static label. Though I've attempted to account for and test every TextView setting I could think of that might affect this – e.g., compound drawables, paddings, selectable text, scrolling, text direction and alignment, etc. – I can't guarantee that I've thought of everything.
I'm trying to add a custom Drawable which extends Drawable to my actionBar. This needs to be a custom drawable, since I want to draw on top of a supplied icon (but that's not the issue).
I've added the icon to the menu in my activity like so:
BadgedIconDrawable drawable = new BadgedIconDrawable(getContext())
.setIcon(icon);
mMenu.add(0, menuItemId, 0, "")
.setIcon(drawable)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
In the BadgedIconDrawable I take a bitmap for the Icon which I then draw on the canvas:
#Override
public void draw(#NonNull Canvas canvas) {
canvas.drawBitmap(mIcon.getBitmap(), null, new Rect(0, 0, mWidth, mHeight), mIconPaint);
}
Where the width and height are 24dp, which seems to be the right size for the icon.
The problem is that unlike a regular drawable that's passed to the setIcon for the mMenu, it doesn't seem to align correctly. I can't find how to get it aligned to the center. Image below illustrates the issue. The middle Icon is set through the BadgedIconDrawable.
EDIT
While the below snippet worked, I soon figured this would only work for the toolbar. Not anymore when used in a regular ImageView. So the trick is to use a Rect as to where the bitmap should be drawn. This would have the regular bounds, but when it should be translated, tell the custom Drawable to do so, by modifying the Rect like so:
// On the fragment/activity create the Drawable, translate it and add to the menu.
public void addBadgedActionBarButton(Drawable icon, int color, String badgeLabel, int menuItemId) {
if (mMenu == null) {
throw new IllegalStateException("mMenu = null, Are you sure you are calling addActionBarButton from initMenu()?");
} else {
BadgedIconDrawable drawable = new BadgedIconDrawable(getContext());
drawable.translate(-drawable.getWidth() / 2, -drawable.getHeight() / 2, drawable.getWidth() / 2, drawable.getHeight() / 2)
.setIcon(icon);
mMenu.add(Menu.NONE, menuItemId, Menu.NONE, "")
.setIcon(drawable)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
}
// In the BadgedIconDrawable have a function that sets the mDstRect to the translated Rect.
public BadgedIconDrawable translate(int left, int top, int right, int bottom) {
mDstRect = new Rect(left, top, right, bottom);
return this;
}
// In the BadgedIconDrawable draw function use the mDstRect to draw on the correct position.
#Override
public void draw(#NonNull Canvas canvas) {
canvas.drawBitmap(mIcon.getBitmap(), null, mDstRect, mIconPaint);
}
With some wild guessing I managed to try to add a negative top and left, which seems to place it in the correct spot. This seems to do the trick.
#Override
public void draw(#NonNull Canvas canvas) {
int halfWidth = mWidth / 2;
int halfHeight = mHeight / 2;
canvas.drawBitmap(mIcon.getBitmap(), null, new Rect(-halfWidth, -halfHeight, halfWidth, halfHeight), mIconPaint);
}
I have an ImageView and I am trying to fade from one image to the next using this code:
Drawable bgs[] = new Drawable[2];
public void redraw(int[][] grid) {
bgs[0] = bgs[1];
bgs[1] = new GameDrawable(grid, prefs.colors);
if (bgs[0] == null) {
gameField.setImageDrawable(bgs[1]);
} else {
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
}
gameField is correctly referenced as an ImageView.
gameDrawable simply extends Drawable and draws the grid.
On each move and action the new GameDrawable is being rendered correctly but there is no fading whatsoever. The new image is simply displayed instantaneously. I have tried lengthening the transition time and swapping the order of the drawables with no effect.
Any help on is appreciated.
Update: I have now set my transition to something ridiculously long like 500000. The first drawable shows for a few seconds and then suddenly the second drawable appears. So still no transition.
Update 2:
I think my Drawable might be implemented incorrectly, so I have attached the code.
public class GameDrawable extends Drawable {
private Paint paint = new Paint();
private float blockWidth = 1;
private int[][] myGrid;
private int myColor;
private List<Point> myPoints;
public GameDrawable(int[][] grid) {
super();
this.myGrid = grid;
this.myColor = colors[yourColor];
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(0);
this.myPoints = yourPoints;
}
#Override
public void draw(Canvas canvas) {
float height = getBounds().height();
float width = getBounds().width();
blockWidth = width / myGrid.length;
if (height / myGrid.length < blockWidth) {
blockWidth = height / myGrid.length;
}
for (int x = 0; x < myGrid.length; x++) {
for (int y = 0; y < myGrid[x].length; y++) {
paint.setColor(colors[myGrid[x][y]]);
canvas.drawRect(x * blockWidth, y * blockWidth, (x+1)*blockWidth, (y+1)*blockWidth, paint);
}
}
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
invalidateSelf();
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
invalidateSelf();
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Looking at your code, I see a problem at the line
bgs[0] = bgs[1];
bgs[1] has not yet been defined before this line and so bgs[0] is null for the first method call. Because of this, (bgs[0] == null) is true, and so the later defined bgs[1] is directly set to the gameField ImageView.
Use corrected code below.
Drawable bgs[] = new Drawable[2];
Drawable firstDrawable = getResources().getDrawable(R.drawable.transparent);
public void redraw(int[][] grid) {
bgs[0] = firstDrawable;
bgs[1] = new GameDrawable(grid, prefs.colors);
firstDrawable = bgs[1];
TransitionDrawable crossfader = new TransitionDrawable(bgs);
crossfader.setCrossFadeEnabled(true);
gameField.setImageDrawable(crossfader);
crossfader.startTransition(500);
}
Note that TransitionDrawable does not work properly when the Drawable sizes are different. So you may need to resize firstDrawable beforehand.
EXTRA: I would avoid setCrossFadeEnabled(true) since the whole TransitionDrawable becomes translucent during the transition, revealing the background. Sometimes, this creates a "blinking" effect and destroys the smoothness of the transition.
EDIT: Looking at your custom Drawable implementation, I think the problem lies in the line
canvas.drawColor(Color.WHITE);
in the draw() method.
I looked at TransitionDrawable.java source and found that setAlpha is called on the drawables to get the cross fade effect. However, your canvas has a solid white color and setAlpha() only affects the paint. Hope this is your answer.
EDIT 2: The actual problem, as pointed out by Michael, was that TransitionDrawable's setAlpha() calls on the Drawables were rendered ineffective due to paint.setColor() in the GameDrawable's draw() method overriding the paint's alpha value set by the TransitionDrawable.
I'm painting text over a background image on a canvas. I move the image interactively (like a Ouija board pointer). I've set the canvas to black, the pointer is red and I want to write white text over it so that the pointer has a player's name on it.
In Android 2.3.4 it appears as solid white text on top of the red pointer which is pretty clear, but I'd like to use any color. In Android 4.1.2 I can barely see the white text. Here's my code:
public Pointer(Context context) {
super(context);
paintBg = new Paint();
paintBg.setColor(Color.BLACK);
paintName = new Paint();
paintName.setColor(Color.WHITE);
paintName.setTextSize(50); // set text size
paintName.setStrokeWidth(5);
paintName.setTextAlign(Paint.Align.CENTER);
this.setImageResource(res); // pointer.png in res/drawable folder
Drawable d = getResources().getDrawable(res);
h = d.getIntrinsicHeight();
w = d.getIntrinsicWidth();
canvas = new Canvas();
canvas.drawPaint(paintBg);//make background black
// float imageScale = width / w; // how image size scales with screen
}
#Override
public void onDraw(Canvas canvas) {
y = this.getHeight() / 2; // center of screen
x = this.getWidth() / 2;
int left = Math.round(x - 0.8f * w);
int right = Math.round(x + 0.8f * w);
canvas.save();
canvas.rotate((direction + 180) % 360, x, y); // rotate to normal
canvas.drawText(s, x, y + 20, paintName); // draw name
canvas.restore();
canvas.rotate(direction, x, y); // rotate back
super.onDraw(canvas);
}
What changed in 4.1.2 that would affect this, or am I doning something incorrectly? Thanks for your help with this as it's driving me crazy.
Edit to include screen shots:
Android 2.3.4
Android 4.1.2
Note how the white text appears to be on top in 2.3.4 while it appears below or muddy in 4.1.2.
As free3dom pointes out it is related to alpha. I do change alpha because if I don't, the text does not appear on top of the arrow. It appears that the ImageView having the pointer image is always on top - could this be what's going on?
Here is how I handle setting alpha:
public static void setAlpha(View view, float alpha, int duration) {
if (Build.VERSION.SDK_INT < 11) {
final AlphaAnimation animation = new AlphaAnimation(alpha, alpha);
animation.setDuration(duration);
animation.setFillAfter(true);
view.startAnimation(animation);
} else //for 11 and above
view.setAlpha(alpha);
}
Maybe it has something to do with using this.setImageResource(res) to set the image resource? According to android developer guide, I can only set alpha to the single view and everything in the view is changed. Yet if I lower the alpha, the arrow image seems to become transparent enough to allow me to see the text.
You set a stroke width, but never indicate that stroke should be used for the Paint.
Try adding
paintName.setStyle( FILL_AND_STROKE );
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.