Please see the image attached.
I would like to draw a gradient on the edges of the blue force field around the car, as seen in the image below.
The problem is that the gradient can appear on any edge of the blue square (depending on user input) and has to be oriented towards the center. In addition, it is also possible only small sections on the edge will have a gradient.
I have tried several different approaches and cannot get a decent solution.
Currently I am using a gradient defined in xml like this:
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:type="radial" android:gradientRadius="75"
android:startColor="#color/warningColor"
android:endColor="#00d8345a"/>
</shape>
and then using it in java and drawing it using a bitmap on a SurfaceView canvas:
Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.circle_gradient, null);
mGradientBmp = Bitmap.createBitmap(250, 250, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mGradientBmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
The input to the application is currently received through touch input where the user can touch anywhere on the screen. I am keeping a list of all touch points while event.getAction == MotionEvent.ACTION_MOVE and draw the bitmap above in each of those locations, like so:
public void drawScreen(Canvas canvas) {
long c = System.currentTimeMillis();
canvas.drawColor(Color.WHITE);
canvas.setMatrix(mPerspectiveMatrix);
canvas.drawBitmap(mRoadBmp, 0,0, null);
canvas.drawBitmap(mForceFieldBmp, null, mForceFieldRect, null);
synchronized (mTouchPointsList) {
for (PointF p : mTouchPointsList) {
canvas.drawBitmap(mGradientBmp, p.x, p.y, null);
}
}
}
I have a couple of questions.
How would you suggest drawing the gradient (only around the edge of the blue force field) and keeping the gradient oriented towards the center?
How do I keep track of the touch points efficiently? I am currently using a CopyOnWriteArrayList because the input and drawing are on separate threads.
My current solution almost works (besides for being inefficient with the list so it slows down after a while) but the drawing needs to be limited to inside the blue force field.
Any suggestions on how to approach this would be greatly appreciated.
Thanks
Related
I have a custom view. I create a fullscreen transparent overlay using full screen rectangle by calling this method in onDraw:
private void drawOverlay(Canvas canvas) {
canvas.drawRect(0, 0, this.getWidth(), this.getMeasuredHeight(), backgroundPaint);
}
My screen is now overlayed with transparent background I defined earlier.
Now, I want to draw a Drawable on the screen. A drawable can be have a transparent parts on it. It can be, for example a shape , defined like this:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#color/transparent"></solid>
<stroke android:width="10dp" android:color="#ffff0716"/>
</shape>
Now I want to draw that drawable on the screen. I draw it using this method:
private void drawDrawableOverTargetView(Canvas canvas) {
Rect rect = new Rect(200, 200, 450, 400); //TODO
Drawable drawable = tutorial.getHighlightDrawable();
drawable.setBounds(rect);
drawable.draw(canvas);
}
The inner part of my oval is defined transparent in xml. When displayed, the inner part of the oval will be the overlay color, because the overlay rectangle is below it.
The question is: Is it possible to define that drawable should override the color of background rectangle, meaning that the inner part of it becomes transparent ? Is it possible to remove the part of overlay rectangle which is below my drawable?
I tried using
drawable.setColorFilter(Color.Transparent, PorterDuff.Mode.Clear);
But it doesn't seem to work :/
You're making it way harder than you need to. Canvases maintain what is on them in pixels not objects. When a part of that canvas is invalidated it calls all the draw code relevant there and asks for the new pixels. Just code it up so that it draws the way you'd like it to, and invalidate the area. It'll call up the draw routine again and put them in whatever order you draw them in.
The canvas doesn't recall the background color under objects because there's just pixels not objects. The only sense wherein there are objects is where you draw objections, which causes a rendering of that on the canvas. But you can't remove it. You can however declare the area where that object was drawn to be invalid and upon refresh have it not draw that object the next time draw gets called.
Just make sure you call up the stuff in the order you'd like, as what you'd like changes, have the code draw it in that order.
You can't change the stuff already on the screen after the fact except by one of the blending operations, the Porter-Duff operations simply deal with all the logical ways you can manipulate pixels on a screen. CLEAR for example will restore that area to pure transparent. Which is basically just clearing that section of the screen. There's no circle on the screen. You are writing transparent pixels to a set of pixels that looks like a circle. Don't do that. When you don't want that circle thing, declare the picture invalid and when it calls the draw routine again, do not draw that circle.
After they are drawn, you're modifying pixels not things.
I am working on a program that lets you draw highlights, pen and text comment mark-up. I have a working version where I recycle a bmp 3 times then draw it to the canvas.
The issue is my app has 3 of these views in memory, so that is a lot of bmps being created.
In my current solution, I am trying to avoid bmps. I have extended an ImageView and added the following to the Draw event:
`
public override void Draw (Canvas canvas)
{
base.Draw (canvas);
HighlightLayer.Draw(canvas, Width, Height);
PenLayer.Draw(canvas, Width, Height);
TextLayer.Draw(canvas, Width, Height);
}
`
Note that I am using monodroid so it's C# (but nearly the same as Java) and all the "Layer" objects are a subclass of Drawable. Just assume they have draw path commands etc.
Anyway, it sort of works except all the highlights / pen have black boxes around them.
When I draw a line (that's not an eraser), I use PorterDuff.Mode.Src.
I tried to combine the 3 into one bmp instead of recycling it but the issue was that if I had an eraser vector it would erase anything that is already drawn. So an erased pen line would also erase the highlights ...
Is it possible to "freeze" a drawing so that as you continue to draw paths on the canvas and change the PorterDuff mode to clear it will not effect anything marked as freeze?
I know enough to know that the black box I am seeing around my lines on my Drawables is due to the background bmp not being set. The problem is I want them to be layers on top of the ImageView. If they each retained a background then you could not see the Drawables underneath. How can I use a drawable with the background of the "parent ImageView" and avoid the black background it assumes to be there.
Somehow I need the background of the "parent" ImageView to be applied to the Drawables when it's choosing the transparent fill to avoid the default black.
SOLUTION:
Using the suggestion below I created a LayerDrawable and added Drawables for each mark-up tool. In the Drawable, before drawing the lines I added the following code:
public override void Draw (Canvas canvas)
{
canvas.SaveLayer(new RectF(0,0,Bounds.Width(),Bounds.Height()), null, SaveFlags.All);
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
GraphicsUtil.DrawVector(canvas, Lines, ToolID, Bounds.Width(), Bounds.Height());
}
I want to make in my app possibilty to draw circles by user. The drawing is quite simple - user just press on canvas somewhere and then predefined circle
The difficult part here is to draw it with some drawable (picture) as a fill. It is quite simple when it is about rectangle. Then you just need to write:
Drawable drawable = getResources().getDrawable(R.drawable.my_background_picture);
drawable.setBounds(myRectangle);
drawable.draw(myCanvas);
Everything is done on onDraw() method of my custom view.
Unfortunatelly there isn't such simple method to make it with circle. The one that I've found is slight modification from Vogella's tutorial:
InputStream resource = getResources().openRawResource(R.drawable.sand);
Bitmap bitmap = BitmapFactory.decodeStream(resource);
BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
myCanvas.drawRoundRect(myRectangle, 120, 120, paint);
At first sight it looks ok, but it's not. This commands make just something like frame on a picture below, so you move hollow circle on a picture and that's all. Not as with rectangle where you actually move rectangular bitmap.
So, my question is - Is there a way to make circle drawable that can be also moved/resized?
Why make a drawable? You can easily draw a circle via the canvas.drawCircle command. You can also easily make one via a Path object.
Edit:
If you need a drawable, try making a ShapeDrawable based off an OvalShape.
Is it possible to realize the following picture in Android with canvas?
I want to have a hole and not only a Circle over the red layer which is yellow colored. I tried this (and failed) with the following Code in my onDraw()-Method:
canvas.drawBitmap(yellow, 0, 0, paint);
canvas.drawBitmap(red, 0, 200, paint);
Paint p = new Paint();
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawCircle(300, 300, radius, p);
But when I use this code, it makes a hole through both bitmap's. At the end, this App should be a Maze with a ball, holes and other stuff. When the ball would fall into a hole it should appear under the red-Bitmap. Is it possible to realize this?
Answer:
If someone should have the same problem: use View and not SurfaceView. That was my fault, because the bg of a SurfaceView could not be set transparent.
I think you're misunderstanding how the canvas/bitmaps work. There aren't layers or objects stored (unless you store them). It's just a pixel by pixel representation of the image displayed. A yellow circle over a red square is what you have shown in the above picture.
If you truly want a red layer, you have to composite two bitmaps. Draw the hole over the red square in one bitmap, draw the yellow layer in one bitmap. On the canvas, draw the yellow bitmap, then the "red square with a hole" bitmap on top.
I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.