I have this code in onDraw().
radius = drawGmpImage(this.gmpImage, canvas);
canvas.drawCircle(kHorizontalOffset, kScreenVerticalOffset, radius , maskPaint);
drawGmpImage creates a complex graphic which is a circle with many lines drawn on it. It's a library function which I cannot change. The lines are polygons and can extend beyond the circumference of the circle.
The need is to "blank out" everything drawn outside the circle.
This is a port from iOS and the original developers solution is to use a simple bitmap mask, stored as a resource, with a transparent circle which matches the size of the circle drawn. Simply drawing the bitmap over the drawn circle has the desired effect but is not an option on Android as I need to support all possible resolutions and ratios.
Therefore, the canvas.drawCircle() call is the beginning of my attempt to mask out everything outside the circle. It works fine in that a filled circle is drawn over my drawn circle so that the only thing left are the polygon lines outside the drawn circles circumference. Radius is the radius of the drawn circle.
How can I invert this so that I am left with the contents of the circle?
Why is it that you can spend hours working on something, give up, ask the question, then stumble upon the answer 20 minutes later? The joys of life.
Path path = new Path();
path.addCircle(kHorizontalOffset, kScreenVerticalOffset, radius, Path.Direction.CW);
canvas.clipPath(path);
I'd missed the clipPath method which will take any path and use it as a clipping region. Adding my masking circle to the path does exactly what I need.
[EDIT]
This works well, but there is a problem. It doesn't work if hardware acceleration is turned on. I could turn acceleration off, but then I lose a lot of performance in the rest of the draw which is complex.
Here's how I finally solved it:
In onSizeChanged(), create a bitmap mask. I draw a transparent circle in the right place on the bitmap using this paint. The key to is to use a PorterDuffXfermode.
maskPaint = new Paint();
maskPaint.setColor(Color.TRANSPARENT);
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
maskPaint.setStyle(Paint.Style.FILL);
then create the bitmap
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
createMask(w,h,this.radius);
}
private void createMask(int w,int h, int radius){
if (mask!=null){mask.recycle();}
mask = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas maskCanvas = new Canvas(mask);
maskCanvas.drawCircle(w, h, radius, maskPaint);
}
Then in onDraw(), I simply draw the mask over the entire view:
#Override
protected void onDraw(Canvas canvas){
// draw the image();
setRadius(drawGmpImage(this.gmpImage, canvas));
canvas.drawCircle(kHorizontalOffset, kScreenVerticalOffset, radius , maskPaint);
// overlay the mask bitmap
if (mask != null) {
canvas.drawBitmap(mask, 0f, 0f, bitmapPaint);
}
If the radius changes, the mask is recreated:
private void setRadius(int radius){
this.radius = radius;
createMask(kHorizontalOffset, kScreenVerticalOffset, radius);
}
I don't know how to achieve this using masks, hence another approach :
you could draw the radius in a specific colour, say black.
Than Floodfill from one of the corners. I ve created a Floodfill algorithm before its not very difficult. You start in the upperleft corner, set that pixel to your desired colo. Then you look at the neighbouring pixels. If they are black, you stop in that direction, if not, you change the colour and look at the neighbouring pixels again.
Good luck
Related
I can't just seem to figure it out. I am trying to draw a segmented circle (what looks like circle inside a circle). However I want the segments to have specific colors and to be transparent inside the smaller circle. Preferably , I would like to make the color of the segmented lines different than the circle
Here are the solutions I had in mind:
1- Draw arc with fill color for the bigger circle and draw a circle for the small circle. 2 problems with this. First one is that the inner circle area is no longer transparent as it takes the color from the bigger one. Second problem is that the segmentation lines of the outer circle is going all the way to the center (not only to the inner circle perimeter)
2) Draw arcs for the bigger outer circle and draw circle for the inner circle. Set it to be color filled but don't show strokes. Then draw another outer circle on top with no fill just to show strokes. And then draw lines between the inner and outer circle using the calculations ( angle and radius) to determine where the lines are... Very convoluted solution, there has to be another way. Even with this solution, still have problem with the color showing in the center but maybe playing with gradient can help.
I read so much on SO but I couldn't figure the right answer as many answers would remove the control of circle parameters
HEELP!!!
#Override
public void draw(Canvas canvas) {
float size = Math.min(getWidth(),getHeight());
paint.setStrokeWidth(size/4);
paint.setStyle(Paint.Style.STROKE);
final RectF oval = new RectF(0, 0, getWidth(), getHeight());
oval.inset(size/8,size/8);
paint.setColor(Color.RED);
Path redPath = new Path();
redPath.arcTo(oval, 0, 120, true);
canvas.drawPath(redPath, paint);
paint.setColor(Color.GREEN);
Path greenPath = new Path();
greenPath.arcTo(oval, 120, 120, true);
canvas.drawPath(greenPath, paint);
paint.setColor(Color.BLUE);
Path bluePath = new Path();
bluePath.arcTo(oval, 240, 120, true);
canvas.drawPath(bluePath, paint);
paint.setStrokeWidth(2);
paint.setColor(0xff000000);
canvas.save();
for(int i=0;i<360;i+=40){
canvas.rotate(40,size/2,size/2);
canvas.drawLine(size*3/4,size/2,size,size/2,paint);
}
canvas.restore();
final RectF ovalOuter = new RectF(0, 0, getWidth(), getHeight());
ovalOuter.inset(1,1);
canvas.drawOval(ovalOuter,paint);
final RectF ovalInner = new RectF(size/4, size/4, size*3/4,size*3/4);
canvas.drawOval(ovalInner,paint);
}
I'm drawing arcs using the Path class and strokes. Style.STROKE gives arcs without filling. Stroke width is set to size/4 which is a quarter of the view. Half of that stroke width goes outside and the second half goes inside, like this:
xxxxxxxx outer border of the arc of width 5
xxxxxxxx
------------ stroke
xxxxxxxx
xxxxxxxx inner border of the arc
That's why I'm using insets - I need to offset the stroke a bit in order to fit it in the view. Without insets the arcs are cut by all four sides of the view.
And why canvas rotation? Because it's easier to rotate the canvas with built-in methods than calculate lines manually. Rotation uses trigonometric functions and quickly becomes quite complex, hard to read and error prone. Basically I'm rotating the paper and drawing straight lines.
Is the Android documentation for canvas.drawBitmap wrong? It says:
public void drawBitmap (Bitmap bitmap, float left, float top, Paint paint)
Draw the specified bitmap, with its top/left corner at (x,y), using the specified paint, transformed by the current matrix.
Well, x and y don’t seem to be floats, they’re ints; is that correct?
Say I want to overlay the bitmap (which is the size of the available screen, and is bound to a canvas of the same) over the whole available screen. It seems sensible I would:
canvas.drawBitmap(myBitmap, 0, 0, mPaint);
doesn’t it?
But that doesn’t work. What does seem to work is:
canvas.drawBitmap(myBitmap, 2000000, 1000000, mPaint).
Now that statement seems to me to tell the bitmap that it should draw itself a huge distance
Outside the screen! What am I missing here?
In this method x and y are floats, not ints. But like mentioned in the documentation, the x and y coordinates of the bitmaps will be affected by the matrix currently set on the Canvas. In the case of a ScrollView for instance, the matrix could very well contain a very large translation.
What this means is that the coordinates 0, 0 will draw the bitmap at the current origin of the Canvas. That origin is defined by the matrix you can query with getMatrix().
Background: Using Canvas, Paint and Path objects I draw several geometries on the canvas, mostly polygons and circles. They fill most of the Android screen.
Question: With Mathematica I can 'fast-copy' Graphics using Translate ( in x and y direction ), after which the resulting image is automatically zoomed out such that all copies are visible. ( For example. Draw a square that fills the entire screen, copy it using (2,2) and four squares appear. ) The premise is that copying is a faster operation. - Is a similar operation possible on Android?
There's nothing as convenient as that, but to achieve the effect you can draw directly to a Bitmap and re-use it - scaling and translating it yourself.
public void onDraw(Canvas canvas) {
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas bmpCanvas = new Canvas(bmp);
// draw into bmpCanvas
// ...
// draw bitmap using
// public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
canvas.drawBitmap(bmp, ...);
So this is what I have for a vignette style effect in Android (image is a Bitmap):
public void vignette() {
float radius = (float) (image.getWidth()/1.5);
RadialGradient gradient = new RadialGradient(image.getWidth()/2, image.getHeight()/2, radius, Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
Canvas canvas = new Canvas(image);
canvas.drawARGB(1, 0, 0, 0);
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setShader(gradient);
final Rect rect = new Rect(0, 0, image.getWidth(), image.getHeight());
final RectF rectf = new RectF(rect);
canvas.drawRect(rectf, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(image, rect, rect, paint);
}
This "works" but there are a couple of problems. First of all, this is not really a vignette, it's just a gradient so you can see bits of the black going nearly all the way to the center rather than feathering out closer to the edges.
The RadialGradient used also only allows for setting the radius of a circle rather than an ellipse. An ellipse would be able to more effectively match the dimensions of a non-square image than a circle.
The quality of the gradient is also not superb.
I'm trying to replicate the vignetteImage method from ImageMagick (I'm referring specifically to the php version). I have this code in PHP that produces the style of image that I want:
$im = new IMagick('city.png');
$im->vignetteImage($width/1.5, 350, 20, 20);
I've tried building ImageMagick with the NDK but have been unsuccessful in properly linking the various image libraries (I've only successfully built with gif support but no png, jpeg or tiff).
I've also attached an image comparing the two methods shown above. The image on the left was generated with ImageMagick through php and the image on the right was generated using the method shown above for Android.
If you look carefully at the image on left, tf uses exponential increase in Alpha (transparency) vs. image on right which is very linear.
Clearly Shader.TitleMode.CLAMP is a linear function. What you should do instead is use RadialGradient(float x, float y, float radius, int[] colors, float[] positions, Shader.TileMode tile) to define 10 or more points on the image with exponentially decreasing color values (black to transparent).
Alternatively, you can refer gallery 3d source for ICS gallery http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/4.0.1_r1/com/android/gallery3d/photoeditor/filters/VignetteFilter.java?av=h
I know this is an old discussion but it may help someone.
You can use AccelerateInterpolator to generate the points that Taranfx had mentioned and it will lock awesome.
I am looking at one of the sample applications from Google, which deals with touch drawing using canvas:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/TouchPaint.html
I have a few doubts:
I am not able to understand what's the role of Canvas versus the role
of the bitmap.
In the drawPoint function, I am not able to
understand this code snippet:
mCanvas.drawCircle(x, y, radius, mPaint);
mRect.set((int) (x - radius - 2), (int) (y - radius - 2),
(int) (x + radius + 2), (int) (y + radius + 2));
invalidate(mRect);
If the circle is already drawn into the canvas above, then what happens in the onDraw function where the following code is given:
canvas.drawBitmap(mBitmap, 0, 0, null);
Canvas vs Bitmap
A Bitmap is what the name suggests: A normal image as a bitmap. The Canvas class is an editor for bitmaps. You use it to change the bitmap data, it holds all drawing methods. This principle behaves similar to the shared preferences (if you already worked with them), you have a SharedPreferences class that holds the preferences, and an Editor class to change things.
Drawing the circles
This code does something similar to double buffering. drawPoint() basically draws a circle into the mBitmap object¹. But this bitmap object is not yet visible. It exists in the memory. When onDraw() is called, it has a Canvas argument that represents the drawing surface of the view. All that drawBitmap() does here is use the prepared bitmap from the memory and draw it inside the views graphical representation to make it visible.
¹ The used canvas mCanvas is tied to mBitmap inside onSizeChanged()
if you go to the developper refference:
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
Draw the
specified bitmap, with its top/left corner at (x,y), using the
specified paint, transformed by the current matrix.
Then if you see that mBitmap doesn't exist in the class , thats cause that var comes from the extend from another activity .
Canvas also has a setBitmap(Bitmap bitmap) function . Then the solution is that that paint in canvas if you have set into it a bitmap object.
From the Android SDK:
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
I'm assuming you're referring to this snippet:
#Override protected void onDraw(Canvas canvas) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
Well it looks like an override of an inherited onDraw method which by default probably 'does nothing', hence the override to actually give it some behaviour, in this case given a non-null Bitmap instance, make the canvas draw it.