Why this code work only once [Android-canvas] - java

I want to draw circle in canvas. I use function to do id:
public static void add()
{
float a = 20 + (new Random()).nextInt(width-40);
float b = 20 + (new Random()).nextInt(height-40);
paint.setColor(Color.rgb(13, 13, 13));
c.drawCircle(a, b, r, paint);
paint.setColor(Color.rgb(119, 119, 119));
c.drawCircle(a, b, r-3, paint);
}
It works only once, when it called from "onDraw".
p.s.
paint, width, height, c - public varibles.
UPD.:
protected void onDraw(Canvas canv)
{
super.onDraw(canv);
c = canv;
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
c.drawPaint(paint);
add();
}

onDraw() will be called whenever the view needs to re-draw itself. This can be due to many reasons, like layout changing, scrolling etc.
You can also call invalidate() on a View to cause a re-draw.
If you are going to draw at a very high rate, like touch painting or some game etc, consider using a TextureView instead.

I did it as follows.
1) Define a custom View.
2) on its onDraw method, do this creating/showing circle.
3) call invalidate() method in the last line of onDraw method of custom View.
let me know if it works

Related

RadialGradientPaint Equivalent in Android

I am drawing a radial gradient circle on an image like this
I have java code for this
private void drawRadialGradientCircleJava(String imagePath, double posX, double posY, float radius, String outputPath) throws IOException{
BufferedImage city = ImageIO.read(new File(imagePath));
BufferedImage mask = new BufferedImage(city.getWidth(), city.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = mask.createGraphics();
Color transparent = new Color(255, 0, 0, 0);
Color fill = Color.RED;
RadialGradientPaint rgp = new RadialGradientPaint(
new Point2D.Double(posX, posY),
radius,
new float[]{0f, 0.75f, 1f},
new Color[]{transparent, transparent, fill});
g2d.setPaint(rgp);
g2d.fill(new Rectangle(0, 0, mask.getWidth(), mask.getHeight()));
g2d.dispose();
BufferedImage masked = new BufferedImage(city.getWidth(), city.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = masked.createGraphics();
g2d.setColor(Color.RED);
g2d.fillRect(0, 0, masked.getWidth(), masked.getHeight());
g2d.drawImage(city, 0, 0, null);
g2d.setComposite(AlphaComposite.DstAtop);
g2d.drawImage(mask, 0, 0, null);
g2d.dispose();
ImageIO.write(masked,"png", new File(outputPath));
}
I want to do same thing in Android, I have an image view in which I have an image, now I want to touch a point in image and draw this transparent circle around that point
I have following Android code as well but id doesn't draw anything on the image
private void drawRadialGradientCircleAndroid(ImageView imageView, float posX,
float posY, float radius) throws IOException {
RadialGradient gradient = new RadialGradient(posX, posY, radius, Color.TRANSPARENT,
Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP);
Paint p = new Paint();
p.setDither(true);
p.setShader(gradient);
Bitmap bm = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Bitmap bmOverlay = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bm, new Matrix(), null);
canvas.drawCircle(posY, posX, radius, p);
imageView.setImageBitmap(bmOverlay);
}
Please help how can I achieve this in Android.
We should migrate this to the answer boxes.
OP has basically got it here- and in fact the OP's revised gist is brilliant.
Some general tips regarding the first attempt in the question:
1) In protected void onSizeChanged(int w, int h, int oldw, int oldh):
width = w; there is no reason why you can't call getWidth() when you require this. The reason it's advisable is because the View's internal width is set quite late after onMeasure. Consequently, onDraw may be the next time you want a most up to date version, so use the getter there.
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);. Creating a bitmap is an expensive and memory intensive operation. Unless you want to write a bitmap to a file, or send it to a BitmapDrawable for an ImageView or something, you don't need to do this. Especially with effects drawn onto the UI with android's graphics library.
mCanvas = new Canvas(mBitmap); followed by a draw operation onto the new canvas. This is never needed. And yet I've seen it (not work) in many code bases and attempts. I think it's the fault of an old stack overflow post that got people doing this so that they could transform a canvas on a custom view without effecting the drawing onto the rest of the canvas. Incidentally, if you need this, use .restore() and .save() instead. If you see new Canvas, be suspicious.
2) onDraw(...):
Yes, you need to avoid doing things in onDraw, like, creating objects, or any heavy processing. But you still need to do the things in onDraw you need to do in onDraw!
So here you simply need to call : canvas.drawCircle(float cx, float cy, float radius, Paint paint) with arguments as per the docs.
This really isn't that sinful for onDraw. If you're worried about calling this too much, as might be the case if your entire button is animating across the screen, you need to use hardware acceleration available in later API versions, as will be detailed in an article called Optimizing the View; very helpful reading if you're using lots of custom drawn views.
3) That pesky radial gradient. The next issue you had is that you quite rightly created your paint in an initmethod so that the object creation was off the draw. But then quite rightly it will have IllegalArgumentExceptioned (I think) on you because at that stage the getHeight() of the view was 0. You tried passing in small pixel values- that won't work unless you know some magic about screen sizes.
This isn't your issue as much as the annoying view cycle at the heart of Android's design patterns. The fix though is easy enough: simply use a later part of the view's drawing process after the onMeasure call to set the paint filter.
But there are some issues with getting this right, namely that sometimes, annoyingly, onDraw gets called before the point at which you'd expect it. The result would be your paint is null and you wouldn't get the desired behavior.
I have found a more robust solution is simply to do a cheeky and naughty little null check in the onDraw and then once only construct the paint object there. It's not strictly speaking optimal, but given the complex way in which the Paint objects hook up with Android's graphics native layer better than trying to straddle the paint configuration and construction in many frequently called places. And it makes for darn clearer code.
This would look like (amending your gist):
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (mPaint == null) {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(1);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setShader(new RadialGradient(getWidth() / 2, getHeight() / 2,
getHeight() / 3, Color.TRANSPARENT, Color.BLACK, TileMode.MIRROR));
}
width = getWidth();
height = getHeight();
canvas.drawCircle(width / 2, height / 2, height / 3, mPaint);
}
So note a few changes- I think from your description you want the two colours swapped round in the arguments, also don't forget to center the center of your gradient in your view: width/2 and height/2 arguments.
Best of luck!

Android: Draw Rectangle Over Another Rectangle on Canvas

I'm trying to draw one rectangle that's light gray over another rectangle that's white on a canvas, but it doesn't seem to do anything. Here's what I've got:
public void onDraw(Canvas c) {
Paint paint = new Paint();
paint.setColor(Color.LTGRAY);
c.drawRect(0, 0, width, height, paint);
paint.setColor(Color.WHITE);
c.drawRect(10, 10, width - 10, height - 10, paint); //This is slightly smaller than the gray rectangle, so it looks kinda like a border.
}
To display this, I use setContentView() in the main activity to set the view to a new class extending SurfaceView. When the surface is created in the custom SurfaceView, it starts a thread that executes onDraw() every 100 milliseconds. I got the Canvas by using holder.lockCanvas(), then onDraw()ing and holder.unlockCanvasAndPost(c).
Where are you initializing your width and height? Are they set to 0?
Try calling canvas.getWidth() or canvas.getHeight().

Where to init Point variables/ Draw method gives warning

I have a Draw method which is as follows:
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
a = new Point(0, 0);
b = new Point(canvas.getWidth(), canvas.getHeight()/2);
c = new Point(canvas.getWidth(),0);
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.lineTo(a.x, a.y);
path.close();
canvas.drawPath(path, paint);
canvas.rotate(36);
paint.setColor(Color.WHITE);
canvas.drawText(text_to_disp, (canvas.getHeight()/4),0- 15, paint_text);
}
The contention here is the init of Point variables, I get a warning in eclipse: Avoid object allocations during draw/layout operations (preallocate and reuse instead)
However I don't get reference to canvas in any other method in this class, how do I init these variables elsewhere?
I also have this method:
public void init(){
paint = new Paint();
paint.setStrokeWidth(4);
paint.setColor(android.graphics.Color.RED);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setAntiAlias(true);
paint.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK);
setLayerType(LAYER_TYPE_SOFTWARE, paint);
path = new Path();
path.setFillType(FillType.EVEN_ODD);
paint_text = new Paint();
helv_light = Typeface.createFromAsset(MainAct_Demo.con.getAssets(), pathHelv_light);
paint_text.setTypeface(helv_light);
paint_text.setTextSize(32);
paint_text.setAntiAlias(true);
paint_text.setColor(android.graphics.Color.WHITE);
}
The reason you are getting a warning to avoid allocations in a draw method is because the draw method could potentially be called many times in succession, such as during a scrolling or animation operation. Object allocation is a fairly expensive process and is generally unnecessary in a primitive draw routine.
Allocate your Point objects in the constructor of your controller and store them as member variables. Then in the draw method, you can assign their respective coordinates as required before using them
However, consider wether you actually need Point objects at all, considering you are just breaking them up into the primitive coordinates anyway. Use simple float variables to calculate your width and height relative coordinates.

Translate/Rotate/Move a graphics object without messing up the whole screen

I'm coding a GUI that will be doing some graphics translations/rotations, etc.
My problem is that when I try to translate my graphics,
(a) The entire screen translates instead of my one little painted area
(b) The old paint stays there, leaving a big paint blob instead of a translated image
(c) If I use the clearRect method to allow me to avoid (b), the entire screen goes white and (a) is still a problem
my DrawPanel class (I called it "LaunchTubeImage" for whatever reason)
private class LaunchTubeImage extends JPanel {
private Color colour;
public LaunchTubeImage(Color color) {
super();
this.colour = color;
}
public void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,
tubeImage.getSize().height - 50);
g.setColor(colour);
g.clearRect(0,0,getWidth(),getHeight());
g.fillRect(tubeImage.getSize().width/2,
tubeImage.getSize().height - 100 , 10, 50);
}
}
where this is called in my code
tubeImage = new LaunchTubeImage(Color.MAGENTA);
angle.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
tubeImage.repaint();
}
});
Case 1: Comment out clearRect in that 1st block of code I posted
http://i58.tinypic.com/2d1l5w2_th.png
Black background as desired. Not rotated yet. Looks good so far.
http://oi60.tinypic.com/1zw1sm.jpg
Rotated it with my JSpinner... you see that the previous location was not removed (and note how my buttons randomly doubled and put themselves at the top of the screen).
Case 2: Keeping in the clearRect method
oi57.tinypic.com/2s84307.jpg
Layout is fine so far, but I wanted the background to be black
oi57.tinypic.com/4rde8x.jpg
Yay! It rotated. But note the weird behavior of that random "15" that appeared in my top right corner
oi58.tinypic.com/vymljm.jpg
And finally... when I resize the window you see that my entire screen was rotated - not just the pink image I wanted to rotate
Tips/fixes/advice? Thanks!! I hope I've provided enough information
(P.s. if you insist on us asking clear/useful questions.... then DON'T limit the number of images you can post... :/ )
The first line of an overridden paintComponent method should usually be super.paintComponent(g). On a JPanel, this will cause the drawing area to be cleared with the background color. If you want to clear the background with a different color, you can do this by manually filling a rectangle (clearRect is discouraged, see the JavaDoc), but of course, this has to be done before applying any transform.
So your method should probably look like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colour);
g.fillRect(0,0,getWidth(),getHeight());
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,tubeImage.getSize().height - 50);
gg.fillRect(tubeImage.getSize().width/2,tubeImage.getSize().height - 100 , 10, 50);
}

PorterDuffXfermode DST_IN not working as expected

So I'm trying to speed up some drawing we're doing (drawing a portion of an arc with alpha transparency) and was attempting to cache the entire arc into a separate bitmap, and show it selectively with an alpha mask.
From the research I've done (the Xfermodes API demo for Android, this example, and this tool), if I have for example the following two graphics:
and draw using the following:
Xfermode DST_IN = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
canvas.drawBitmap(circle, 0, 0, paint);
paint.setXfermode(DST_IN);
canvas.drawBitmap(arc, 0, 0, paint);
paint.setXfermode(null);
I should get this result:
Where the destination image (the circle) is clipped to the area where the source image (the arc) is drawn. Instead, I get the full circle. If I just draw the arc, it appears in the correct location, and if I use DST_OUT instead, I get the inverse of the expected result (the other three quadrants of the circle).
I've also made sure to disable hardware rendering for this view, in case there was an issue with this Xfermode, but it doesn't make a difference.
I broke it out into a separate project at the simplest level trying to get it to work, and using the following code, I still have the same problem:
public class ClippedView extends View {
private Xfermode DST_IN, DST_OUT;
private Paint paint;
public ClippedView(Context context) {
super(context);
init();
}
private void init() {
setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
DST_IN = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
DST_OUT = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.GREEN);
canvas.drawRect(0, 0, getWidth() / 2, getHeight() / 2, paint);
paint.setColor(Color.BLACK);
paint.setXfermode(DST_IN);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, paint);
paint.setXfermode(null);
}
}
Am I using it wrong? Am I just missing something? Have I found a bug? :)
There's a much cheaper and easier way to achieve this: use clipping. Canvas.clipRect() is enough. Your solution is burning a lot of fillrate. You can get the effect you want by using SRC_IN instead of DST_IN. Be careful though: it will work only in a transparent Bitmap or in layer. When you draw directly on screen, the destination is already filled by the window background.

Categories