Android Canvas: Draw only part of precalculated Path - android

Is it possible to draw only part of a Path? Let's say my Path is from x = 0 to x = 2000, the whole Path is calculated on start, and the Canvas is placed on HorizontalScrollView. When scroll x = 500, I want to draw only from 500 to 1000 of that Path; when x = 0 draw 0 to 1000, when x = 1500, draw 1000 to 1500 and when x = 2000 draw 1000 to 2000.
Path is a bezier curve, so if the calculation needs to be done all the time, it's damaging performance.
Thanks.

You can use getSegment method for this purpose, like this (Kotlin):
private fun getSubPath(path: Path, start: Float, end: Float): Path {
val subPath = Path()
val pathMeasure = PathMeasure(path, false)
pathMeasure.getSegment(start * pathMeasure.length, end * pathMeasure.length, subPath, true)
return subPath
}
Usage:
val subPath = getSubPath(path = originalPath, start = 0.2f, end = 0.8f)

I may have an answer for you.
The Picture class is used to store pictures that do not change and then write them to a canvas.
For example, you could have 4 different Picture objects, each with part of the Bezier curve, then write them when you want them.
Some code might look something like this:
Picture b1 = new Picture();
Canvas c1 = b1.beginRecording(500, height);
// draw
b1.endRecording();
Picture b2 = new Picture();
Canvas c2 = b2.beginRecording(500, height);
c2.translate(-500, 0);
// draw
b2.endRecording();
Picture b3 = new Picture();
Canvas c3 = b3.beginRecording(500, height);
c3.translate(-1000, 0);
// draw
b3.endRecording();
Picture b4; = new Picture();
Canvas c4 = b4.beginRecording(500, height);
c4.translate(-1500, 0);
// draw
b4.endRecording();
(if x < 500) {
// draw c1
}
...
There's probably a way to only draw it once, rather than 4 times, but I'm just posting what I know works. If you can find a way to partition the canvas, then you only need to draw it once.

Got the solution by drawing the whole line, and after that calculate Rect to draw on top of it, to hide particular part on visible part.
This is to demonstrate how I did this, I am drawing the whole Path, and to get it clip, I draw a rectangle on top of it to right position of screen, so it looks like the path is clipping.

Related

Can I render a texture at pixel resolution in Android?

I am trying to make a test image to check the tone curve on Android displays. The kotlin code below makes a Bitmap that fits the ImageView. The canvas is filled with the background colour. The central 50% is filled with alternate lightLine (white) and darkLine (black) 2-pixel wide horizontal stripes.
All the features are in the right places. The stripes should appear the same grey as the background. They sort-of do, but I think they are the wrong size, as though the BitmapShader was scaling from dp to px.
I could draw the stripes one at a time using myCanvas.drawRect(). If I am stuck, that is what I will do. But the BitmapShader would let me render general patterns if I could fix it.
class MainActivity : Activity() {
// Here are all the objects(instances)
// of classes that we need to do some drawing
lateinit var myImageView: ImageView
var background = Color.rgb(180, 180, 180)
var darkStripe = Color.rgb(0, 0, 0)
var lightStripe = Color.rgb(255, 255, 255)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the ImageView
myImageView = findViewById(R.id.imageView)
myImageView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val w = (it.measuredWidth/4).toFloat()
val h = (it.measuredHeight/4).toFloat()
val myCanvas = Canvas(myBitmap)
myCanvas.drawColor(background)
val tex = createBitmap(
intArrayOf(lightStripe, lightStripe, darkStripe, darkStripe),
0, 1, 1, 4, Bitmap.Config.ARGB_8888
)
val shader: Shader = BitmapShader(tex, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
val myPaint = Paint()
myPaint.setAntiAlias(false)
myPaint.shader = shader
myCanvas.drawRect(w, h,w*3.0f,h*3.0f, myPaint)
myImageView.setImageBitmap(myBitmap)
}
}
(two hours later)
I replaced the BitmapShader with drawRect() calls like this...
myPaint.setAntiAlias(false)
myPaint.color = darkStripe
myCanvas.drawRect(xMin, yMin, xMax, yMax, myPaint)
myPaint.color = lightStripe
var y = yMin
while (y < yMax) {
myCanvas.drawRect(xMin, y, xMax, y+myStep, myPaint)
y += 2*myStep;
}
This still gave soft edges to my stripes. Maybe I was working in dp and not px. So I set the stripe width with....
myStep = resources.getDisplayMetrics().density
The stripes all look nice an uniform with hard edges. The step is 3.0, but if I look at the display through a microscope, I see its matrix has green dots on a square grid with alternate blue and red pixels at the square centres. The 3.0 step rectangle is four green dots high. I don't see how that translates into 3 of anything but it looks smooth. I expect the BitMap shader would have worked with a repeat of 3.

Android fill path of arc is on wrong side

I have a path that is a serie of arc with first arc clockwise, next arc
is counterclockwise, etc.... The last arc join the first in a circle way.
Somthing like this:
(
)
(
)
(
But in circle and each arc touch perfectly the next.
When i use fill, it fill only aproximately the half part of each circle, like if i stoke a line between the start and end point of each circle. The filled part is internal to every arc.
What i want is to fill the inner part of this shape composed of all this arc. Is there some params i have miss ?
Some code:
Path path = new Path();
// for simplicity let's say i have a couple of
path.addArc(rect, (float)startingAngle, sweepAngle);
Paint paintPath = new Paint();
paintPath.setColor(0xFFFFFFFF);
paintPath.setStyle(Paint.Style.FILL);
Bitmap tempBitmap = Bitmap.createBitmap(this.getMeasuredWidth(), this.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tempBitmap);
canvas.drawPath(path, paintPath);
canvas.drawBitmap(tempBitmap, 0, 0, paintPath);
imageViewackground.setImageBitmap(tempBitmap);
Try using
path.arcTo(rect, (float)startingAngle, sweepAngle);
or
path.arcTo(rect, (float)startingAngle, sweepAngle,true);

Speeding up bitmap drawing on android

I have been battling with trying to draw a bitmap and then highlighting a region on it with a rectangle. Originally, I was drawing a bitmap with alpha black in paint to make image darker and then on top drawing original bitmap in a region creating effect of highlight. I discovered that largest slowdown was because of alpha in Paint. So I have reworked the code and ended up with following in my draw thread:
private synchronized void drawSquare(int xStart, int yStart, int xEnd, int yEnd) {
Canvas c = holder.lockCanvas();
if(c != null) {
// Draw the background picture on top with some changed alpha channel to blend
Paint paint = new Paint();
paint.setAntiAlias(true);
if(bg != null && cWidth > 0 && cHeight > 0) {
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.DIFFERENCE);
c.drawBitmap(bg, gTransform, blackSqr); // Draw derker background
c.clipRect(xStart, yStart, xEnd, yEnd, Region.Op.REPLACE);
c.drawBitmap(bg, gTransform, paint); ///draw original in selection
c.clipRect(0, 0, cWidth, cHeight,Region.Op.REPLACE);
}
Matrix RTcorner = new Matrix();
RTcorner.setRotate(90);
RTcorner.postTranslate(xEnd + 13, yStart - 13);
Matrix RBcorner = new Matrix();
RBcorner.setRotate(180);
RBcorner.postTranslate(xEnd + 13, yEnd + 13);
Matrix LBcorner = new Matrix();
LBcorner.setRotate(270);
LBcorner.postTranslate(xStart - 13, yEnd + 13);
// Draw the fancy bounding box
c.drawRect(xStart, yStart, xEnd, yEnd, linePaintB);
// Draw corners for the fancy box
c.drawBitmap(corner, xStart - 13, yStart - 13, new Paint());
c.drawBitmap(corner, RBcorner, new Paint());
c.drawBitmap(corner, LBcorner, new Paint());
c.drawBitmap(corner, RTcorner, new Paint());
}
holder.unlockCanvasAndPost(c);
}
So this clips out my selection area, I draw with paint that has this code to make it darker.
blackSqr.setColorFilter(new LightingColorFilter(Color.rgb(100,100,100),0));
And in the area inside the clip I draw my original bitmap. It works. But I am not happy with response time. After profiling Bitmap is what takes the longest. I have scaled the bitmap to the size of the screen already so it's drawing 300x800-ish image. The biggest resource hog seems to be the Lighting effect. Because when I turn it off I get decent response time.
So I was wondering if I have missed anything to improve how quickly bitmap is drawn, maybe caching? Or am I just stuck with this because I want darker image and actually should rethink the "highlighting/selection" altogether? Why is is so expensive to draw a bitmap with alpha colour in 2D image?
if I understand what you want, you want a rectangle (with rounded corners) to highlight a part from another image.
if it is that, then I would use an image with the square wit draw9patch and use it as a floating view over the image view
RelativeLaoyut (Image container)
+- ImageView (your actual image)
+- view (it has the square as a background, and you only have to move it to the area you want to highlight)
I'm sorry, I'm not good explaining myself.
For anyone that is interested, perhaps facing similar problem. This solution applies to my particular situation, but I have a separate background bitmap with darkened pixels manually set using:
for(int i = 0; i < cWidth; i++){
for(int j = 0; j < cHeight; j++){
int c = bg2.getPixel(i, j);
float mult = 0.15f;
int r = (int) (Color.red(c) * mult);
int g = (int) (Color.green(c) * mult);
int b = (int) (Color.blue(c) * mult);
bg2.setPixel(i, j, Color.rgb(r, g, b));
}
}
Then use the bg2 to draw main part and the original (not darkened) for the clip rectangle of the selection. There is a bit of overhead for creating and maintaining the second bitmap but the draw speed and response time is quick and smooth in comparison to bitmaps with alpha.

Button class in Android OpenGL with TextureBuffer Pointer

I am creating a button class that will display a blue circle if it is not being pressed and a red circle if it is being pressed. I start by building a 4 vertex quad composed of two triangles. I generate my own bitmap and draw two circles on the bitmap next to each other, left to right. I then create a texture buffer with instead 4 uv points, I create it with 8, one that maps out the blue circle, one that maps out the red circle. I would then like to render the red circle when the button is pressed. Ideally I would like to call the
gl.glTexCoordPointer
method and pass in an offset, but that is not working. Here is my method that I used to generate the bitmap, draw on the bitmap using the canvas and paint objects and then attempt to map the textures. Note that I have to generate a texture that is a power of 2 so there is some math in there that allows me to generate a bigger bitmap than I need based of the width and height variables of the button that were specified in the constructor.
public void InitializeButton(GL10 gl, int upcolor, int downcolor, String symbol)
{
//Our variables for creating a bitmap and texture
Canvas canvas = null;
Bitmap bitmap = null;
Paint paint = null;
//Set up the bitmap type
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
/*
* We now want to calculate the size of the texture. Remember it is best to be a square
* texture or at least a power of 2. The below equation below will do this. For example
* if the width of the button was say 20, we need to find the smallest power of 2 that is
* greater than 20. In this case we know it is 32. But how do we calculate that? First we
* have to find out the exponent of what 2^x = 20. Then we find the ceiling of tha number.
* In order to make that calculation we have to take the log of that, but in order to use
* the log function which is base 10, we have to switch to base 2 so that means
* we have to take the log(width)/log(2) to switch to base 2, then get the ceiling of that
* number because it would be between 4 and 5 in this case. When we take the ceiling we get
* 5 and 2^5 is 32.
*
* Side note, we want to double the size to make sure there is room for the up and the down
* actions.
*/
widthTexture = (int) Math.pow(2,Math.ceil((Math.log(this.width*2)/Math.log(2))));
heightTexture = (int) Math.pow(2,Math.ceil((Math.log(this.height*2)/Math.log(2))));
/*
* Now we will create the bitmap for the creation of the button
*/
bitmap = Bitmap.createBitmap(widthTexture,heightTexture,conf);
//Now create a new canvas from that bitmap
canvas = new Canvas(bitmap);
//Create a new Paint
paint = new Paint();
/*
* Now we want to render the draw the up and down button on the texture. We are just going
* to use two different colors to represent up and down. So we will draw the up circle button
* starting at 0 0 and the down button off to the right.
*/
paint.setColor(upcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(0,0,width,height), paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width/2, height/2, paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width/2+3, height/2+3, paint);
//Draw the down color button
paint.setColor(downcolor);
paint.setAlpha(120);
canvas.drawOval(new RectF(width,0,width*2,height), paint);
paint.setColor(Color.WHITE);
canvas.drawText(symbol, width+(width/2), height/2, paint);
paint.setColor(Color.BLACK);
canvas.drawText(symbol, width+(width/2)+3, height/2+3, paint);
float widthpercent = ((float)width/(float)widthTexture);
float heightpercent = ((float)height/(float)heightTexture);
/*
* Now create two texture maps. One for the up button and one for the down button
* You can change the offset of the draw texture thing to change the animations now
*/
float uvTextures[] = {0f, heightpercent,
widthpercent, heightpercent,
widthpercent, 0f,
0f, 0f,
widthpercent, heightpercent,
widthpercent*2, heightpercent,
widthpercent*2, 0f,
widthpercent, 0f,
};
/*
* Allocate the byte buffer so it is a normal array of floats and not a java array.
* load the uvTexture values inside.
*/
ByteBuffer tbb = ByteBuffer.allocateDirect(uvTextures.length*4);
tbb.order(ByteOrder.nativeOrder());
textureBuffer = tbb.asFloatBuffer();
textureBuffer.put(uvTextures);
textureBuffer.position(0);
int [] textures = new int[1];
gl.glGenTextures(1, textures,0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
textureID = textures[0];
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_S,GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_T,GL10.GL_REPEAT);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,0,bitmap,0);
//Don't forget to deallocate the bitmap
bitmap.recycle();
}
So eventually in the render method, I want to be able to render different coordinates on the texture map to the same vertices. So I call glTexCoordPointer and change the offset to "counter" where counter should have been 6*4 (6 vertices times 4 bytes per float) but that doesn't work, so I tried counter at 0 and incremented it and never found the magical number that will map the red button. On a side note, when I tried that, I would get very weird patterns drawn, sometimes showing 10 to 15 mini blue and red circles.
if(isdown)
gl.glTexCoordPointer(2,GL10.GL_FLOAT,counter,textureBuffer);
else
gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,textureBuffer);
#Harism was correct for my situation. I hope I am still using this correctly. I was thinking the stride variable could be changed, but I had to change the position of the buffer. This worked for animating textures on 3D surfaces. I don't know if this is the "best" way to do it, but until then I'll be using this.

Android Bitmap Transformation Math

I need to combine 2 images into one. Basically all i need to do is to overlay one of them on top of the other in the center of the image. This needs to work on all major android devices.
I have tried a number of things, but here is my code snippet as of right now (and yes I know it's messed up, we need to figure out delx and dely):
/* Rotate our original photo */
// final float scale = getResources().getDisplayMetrics().density;
canvas.drawBitmap(bmp, 0f, 0f, null);
final float overlay_scale_factor = .5f;
final int overlaywidth = (int)(overlay.getWidth() * overlay_scale_factor);
final int overlayheight = (int)(overlay.getHeight() * overlay_scale_factor);
final int delx = overlaywidth;
final int dely = overlayheight;
Matrix mat = new Matrix();
mat.postRotate(270);
mat.postScale(overlay_scale_factor, overlay_scale_factor);
//mat.postTranslate(-delx, -dely);
canvas.drawBitmap(overlay, mat, null);
/* Bottom image 'composite' is now a composite of the two. */
Any help is appreciated. I know this is just math, but I'm not good at this kind of stuff.
The first image, 'bmp' is 100% the size of the canvas.
The second image, 'overlay' is the overlay that needs to be centered after it's rotated 270 degrees.
Totally untested, but I'd expect something like this to work:
// Set the origin (0,0) in the middle of the view
canvas.translate(width/2, height/2);
// Draw the first bitmap so it is centered at (0,0)
canvas.drawBitmap(bmp, -bmp.getWidth()/2, -bmp.getHeight()/2, null);
// Rotate & scale
canvas.save();
canvas.rotate(270);
canvas.scale(.5f);
// Draw the overlay
canvas.drawBitmap(overlay, -overlay.getWidth()/2, -overlay.getHeight()/2, null);
canvas.restore();

Categories

Resources