Background
I'm drawing a custom View, which consists of an arc along which images are drawn.
A bit like this "Wheel of Fortune" screenshot, where only part of a large disc is visible and, as the user drags the view, images become visible/hidden as appropriate and are drawn at the appropriate position and angle along the disc's edge.
This works fine; I use the code below to create a large bounding box (four times the width of the view, to get a more subtle arc), which I use with Path.arcTo() to draw the visible top edge of the disc.
Because the bounding box is square, the arc drawn (if I were to draw 360°) would be circular.
// Disc dimensions (based on this View's width/height/padding)
final int radius = width * 2;
final float halfWidth = width / 2f;
final float top = mTopPadding;
// Create a large, square bounding box to draw the disc in.
// Centre horizontally; top edge of the disc == top edge of this View (+ padding)
final RectF discBounds =
new RectF(-radius + halfWidth, top, radius + halfWidth, radius * 2 + top);
// Create an arc along the circumference of the disc,
// but only where it will intersect with this View
double arcSweep = Math.toDegrees(Math.asin(halfWidth / radius)) * 2;
double startAngle = 180 + ((180 - arcSweep) / 2d);
mDiscPath.reset();
mDiscPath.arcTo(discBounds, (float) startAngle, (float) arcSweep);
// Close the shape so that we fill the rest of this View
// (the area underneath the arc) with the disc bg colour
mDiscPath.lineTo(width, height);
mDiscPath.lineTo(0, height);
I then create another Path and again call arcTo(), using the exact same bounding box so that the same arc radius is maintained.
This time the sweep angle of the arc is longer, since there may be only two or three images shown within the View at one time, but an arbitrary number of images off-screen (in my case, up to about ten).
// Create another arc, along which the images should move,
// based on the number and width of the images.
// We will later use a PathMeasure object created from
// this Path to determine where to draw each image
arcSweep = (mTotalWidth * 180) / (radius * Math.PI);
startAngle = 180 + ((180 - arcSweep) / 2d);
mImagePath.reset();
mImagePath.arcTo(discBounds, (float) startAngle, (float) arcSweep);
Problem
In onDraw(), the mDiscPath is drawn as the background (canvas.drawPath(mDiscPath, fillPaint)), and then the appropriate bitmaps are drawn based on a PathMeasure object created from mImagePath and how far the user has dragged.
However, it's noticeable that the images do not precisely follow the expected path as the disc is "rotated". This causes problems, as the images need to align accurately to the edge of the disc.
For troubleshooting, I started drawing mImagePath using canvas.drawPath(mImagePath, strokePaint)) to see why the image path didn't seem to follow the disc path.
In the screenshot below, to make the problem more obvious, the regular bitmaps are not drawn on top of the disc, and mImagePath was translated downwards by 4dp (i.e. the problem is also visible when not translated).
Here we can see three independent instances of the custom View stacked on top of one another.
But it's clear that the black line (mImagePath) does not match the radius of the top of the coloured disc (mDiscPath) in each case. i.e. The radius of the black arc appears to be large than the disc's radius.
The arcs for both Path objects were created using the same bounding box, so I would expect both arcs to have the same radius.
The line on the bottom disc seems to match up well, but the top two discs are clearly wrong.
The only real difference between the discs is the number of images displayed, and therefore the sweep angle of the image path (89°, 169°, 222° respectively for the three views).
Question
Why, if the exact same square RectF bounding box is being used to create two Path objects, why do arcs drawn from these Paths have different radii?
Am I missing something? Should I be using a different API?
Postscript
I've ensured the bounding box is correctly sized and doesn't change between creating the two paths.
The start and sweep angles look correct in all cases (i.e. the midpoint of each arc is at 270°).
Creating brand new Paths or resetting the existing Paths makes no difference.
Using the same arc sweep for both Paths does work as expected.
I've tested on various devices and orientations, with and without software rendering.
Related
I am playing around with an application that draws and traces the user's finger on the canvas. See this picture, the green rectangle was drawn by tracing the user's finger motion. I took most of the code from the old FingerPaint application from the android SDK samples. The gist of it is to draw a Path on a canvas with:
canvas.drawPath (path, paint)
My first challenge was to redraw the path correctly when the screen changes. For example, when the phone gets rotated from vertical to horizontal, the image gets resized and I have to redraw the path along the correct coordinates. I was able to correctly do that with transform. I used the path.transform(matrix) to translate the coordinates of the path.
My next challenge now is to properly scale the thickness of the line. When I draw the path, the thickness of line is set with setStrokeWidth. For example, I set the thickness to 12:
paint.setStrokeWidth(12);
I don't know how to properly scale the thickness of the line when the screen changes. Would anynone know how to do that ?
A suggestion would be to first save the canvas and the path as a bitmap and then redraw the bitmap on the new screen dimensions. But I don't want to do that because I want to support a undo/redo operations where the user can undo/redo the paths that were traced on the canvas.
you can use sth like this:
float screenWidthPixel = this.getResources().getDisplayMetrics().widthPixels;
float screenHeightPixel = this.getResources().getDisplayMetrics().heightPixels;
float STROKE_WIDTH = screenWidthPixel * 0.0035f;
float Y_POSITION = screenHeightPixel * 0.01f;
paint.setStrokeWidth(STROKE_WIDTH);
I need a basic idea for how can i warp image on touch of a particular area. Image filters apply warp on whole image but i want to warp single point, like if i want to warp eye of a person then i will touch on that point. So I need a basic idea about this work.
I have tried this one but its also applies filters on whole image.
https://github.com/Jtfinlay/PhotoWarp
App:
https://play.google.com/store/apps/details?id=hu.tonuzaba.android&hl=en
A warp is not just at a "single point" but over some area that you deform in a smooth way.
To achieve this, you need a geometric transform of the coordinates that works in some neighborhood of the touched point. One way to do this is by applying a square grid on the image and moving the grid nodes around the touched points with some law of yours (for instance, apply a displacement vector to all nodes, with a decaying factor such that far away nodes don't move).
Then you need a resampling function that computes the new coordinates of every pixel and copies the color of the source pixel.
For good results, you must actually work in reverse: scan the destination image and for every pixel retrieve the source coordinates and source pixels. Apply bilinear or bicubic resampling to avoid aliasing.
For ease of implementation, the gridding idea should be adapted as well: rather than deforming the destination grid, keep it unchanged and apply the inverse deformation to the source grid.
Last thing: in the grid approach, see the displacements of the grid nodes as two scalar functions DX(i, j) and DY(i, j) that you can handle separately. From the knowledge of the displacements at the nodes, you can estimate the displacement of any pixel by interpolation (bicubic would be appropriate here).
you can use canvas to detect that portion and stop action on that portion in ontouchlistener
code sample
Bitmap pricetagBmp = BitmapFactory.decodeResource(getActivity().getResources(), R.drawable.ic_tag_circle_24dp);
// canvas.drawBitmap(pricetagBmp,left + (right - left) / 2, top + (bottom - top) / 2 - (bounds.height() / 2),circlePaint);
float imageStartX = (left + ((right-left)/2)) - (pricetagBmp.getWidth()/2);
float imageStartY = (top + ((bottom - top) / 2)) - (pricetagBmp.getHeight()/2);
canvas.drawBitmap(pricetagBmp, imageStartX, imageStartY,circlePaint);
and in ontouchlistener if that points detected you can perform no action
Note: you can replace drawBitmap with drawRect or something else with invisible color
I want to achieve a tilt effect when a button is clicked, on Android OS.
Tilt Effect: Not the whole button will be seen as pressed. Only the part that touch event occured should seem to be pressed.
Is this easily possible on Android?
A simple way would be to use canvas draws to draw 4 sided shapes.
Consider each 4 corners. The "untouched" rectangle would be full size the touched rectangle would be smaller.
You just need to draw your four sided shape using a point you calculate for each part of the rectangle. You can get the touch position, then figure out how much "weight" to give each point.
to calculate each corner, you need to figure out how much "weight" to give the touched coordinate, and how much "weight" to give the untouched coordinate. If you touch the top left corner, that corner would use 100% of the touched coordinate, and the other three corners would all use the untouched coordinate.
If you touched the top middle, you would get a shape like this:
We can calculate the corners for any touch spot, by calculating how far from the corner your touch is
float untouchedXWeight1 = Math.abs(xt - x1)/width;
//maximum of 1, minimum of 0
float untouchedYWeight1 = Math.abs(yt - y1)/height;
float untouchedWeight1 = (untouchedXWeight1 + untouchedYWeight1)/2;
//also maximum of 1, minimum of 0
float touchedWeight1 = 1 - untouchedWeight1;
so with those weights, you can calculate your x and y positions for that corner:
x1 = xUntouched1 * untouchedWeight + xTouched1 * touchedWeight1;
y1 = yUntouched1 * untouchedWeight + yTouched1 * touchedWeight1;
Then do similarly for the other 3 corners.
I've created a first draft here : https://github.com/flavienlaurent/TiltEffect
In a second step, I will make it usable with Button etc.
Unfortunatly, I didn't use the very good (but too mathematical for me) answer of HalR
I looked at the H&M Android app and trying to figure out how to implement some widget.
Can anyone have an idea how this image frame is implemented?
I can guess that it using openGL.
A transparent png frame? Which could also be nine-patch!
I will venture a guess ;)
First the front image is created. In this case, it is built by inflating a linear layout with an ImageView and a TextView. Then this is cached to a Bitmap (at setup phase, not draw time).
In onDraw, that bitmap is drawn to the screen. Then the canvas is clipped to avoid drawing that area any more. Saves a lot of drawing time to not do a quadruple overdraw of transparent pixels.
Then the backgrounds are drawn like this:
for(int i = NUMBER_OF_LAYERS - 1; i > 0; i--) {
canvas.save();
float rotation = MAX_ANGLE * shiftModifier * ((float) i / (NUMBER_OF_LAYERS - 1));
canvas.rotate(rotation, mImageHalfWidth, mImageHalfHeight);
paint.setAlpha((int) (255f / (2 * i)));
canvas.drawRect(mBitmap.getBounds(), paint);
canvas.restore();
}
NUMBER_OF_LAYERS is the number of backgrounds layers.
MAX_ANGLE is the rotation angle for the most tilted layer.
shiftModifier is used to animate the background layers. It moves from zero (background completely hidden) to one (background angle = MAX_ANGLE).
paint is just a Paint with color set to white.
I have some sprites (Well, custom classes that implement Sprite, but whatever) that I resize. AndEngine resizes the image from the center, which makes an image placed at 0,0 no longer appear at 0,0. To fix this I applied
sprite.setScaleCenterX(0);
sprite.setScaleCenterY(0);
This places the image where I want it. However, now when I rotate the image, the image moves around (If the image were a plain square, rotating it should make no visible change). To fix this I applied
sprite.setRotationCenterX((sprite.getWidth() * sprite.getScaleX()) / 2);
sprite.setRotationCenterY((sprite.getHeight() * sprite.getScaleY()) / 2);
(For some reason, resizing a Sprite doesn't change the dimensions of the sprite, just the visual image, hence multiplying it by the scale). This, however, did not correct the problem, but merely changed where the image moved to when flipped.
Is my math off here? Wouldn't this center the rotation on the image so that the image doesn't move position? Or is there something else I'm missing?
Below is full code:
Sprite sprite = new Sprite(0, 0, singleTrackTR, getVertexBufferObjectManager());
sprite.setScale(scaleX, scaleY);
sprite.setScaleCenterX(0);
sprite.setScaleCenterY(0);
sprite.setRotationCenterX((sprite.getWidth() * sprite.getScaleX()) / 2);
sprite.setRotationCenterY((sprite.getHeight() * sprite.getScaleY()) / 2);
All your code is correct. I tried it myself, both the setProperty(x, y) and the setPropertyX/Y(a) versions.
By any chance, do you have it connected to a Body? Note that the Body also doesn't scale with a Sprite's setScale. It has its own setTransform method, which takes x and y (that you both have to divide by PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT) and a rotation value.