I have asked questions related to this before, but I think I was framing my objective incorrectly.
What I have: a custom ImageView that displays a graphic and defines multiple touchable areas as rectangles within the image. My problem is scaling. I want to define the touchable area of the image based on it's actual resolution in the bitmap file, but translate those coordinates so the the rectangle covers the same area on the scaled image.
This is what I've got so far:
When the view is created, calculate the ratio of the actual to scaled sizes
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Drawable pic=this.getDrawable();
int realHeight= pic.getIntrinsicHeight();
int realWidth=pic.getIntrinsicWidth();
int scaledHeight=this.getHeight();
int scaleWidth=this.getWidth();
heightRatio=(float)realHeight/scaledHeight;
widthRatio=(float)realWidth/scaleWidth;
}
Now I want to take the coordinates that define rectangle(s) on the original (un-scaled) image
and draw that rectangle(s) to the same area of the image -- but accounting for scale:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p=new Paint();
p.setStrokeWidth(1);
p.setColor(Color.BLUE);
for (HotSpot h: spots)
{
//Each hotspot has a rectangle defined in reference to the actual size of the image
Rect old=h.getRect();
float offsetLeft=old.left+(old.left*widthRatio);
float offsetTop=old.top+(old.top*heightRatio);
float offsetRight=old.right+(old.right*heightRatio);
float offsetBottom=old.bottom+(old.bottom*widthRatio);
RectF nRect=new RectF(offsetLeft,offsetTop,offsetRight,offsetBottom);
canvas.drawRect(nRect,p);
}
The results are "in the ball park" but not quite accurate. Any help is appreciated.
You can try this solution:
Get the screen density
Get the image height (or width)
Divide the height (or width) by the density, so you get the length in inches
Divide the coordinate by the length in inches, so you get a relationship between the coordinate and the image which is independent by the image effective size
When you have to draw the same point on a differently scaled image, multiply the last result for the length in inches of the second image (which you obtain using the same operations listed above)
Example:
On the first image:
float density = getResources().getDisplayMetrics().density;
int width = getWidth();
float inchesLength = width/density;
float scaledXCenter = xCenter / inchesLength;
On the same image with a different scale:
float density = getResources().getDisplayMetrics().density;
int width = getWidth();
float inchesLength = width/density;
float restoredXCenter = scaledXCenter * inchesLength;
Related
I have an ImageView with MathParent height and width
In my activity it loads a pic from resource to ImageView. How can i get width and height of the picture inside the ImageView AFTER it has been scaled.
I have not set the android:scaleType in XML
these dimensions i mean!
You can do a lot with the matrix the view uses to display the image.
Here I calculate the scale the image is drawn at:
private float scaleOfImageView(ImageView image) {
float[] coords = new float[]{0, 0, 1, 1};
Matrix matrix = image.getImageMatrix();
matrix.mapPoints(coords);
return coords[2] - coords[0]; //xscale, method assumes maintaining aspect ratio
}
Applying the scale to the image dimensions gives the displayed image size:
private void logImageDisplaySize(ImageView image) {
Drawable drawable = image.getDrawable();
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
float scale = scaleOfImageView(image);
float displayedWidth = scale * width;
float displayedHeight = scale * height;
Log.d(TAG, String.format("Image drawn at scale: %.2f => %.2f x %.2f",
scale, displayedWidth, displayedHeight));
}
I suspect you don't really care about the image size, I suspect you want to map touch points back to a coordinate on the image, this answer shows how to do this (also using the image matrix): https://stackoverflow.com/a/9945896/360211
I have two images that don't always completely fit onto the screen. Therefore I want to crop them (like CENTER_CROP does) but in a way, so that the bottom area of the image is always visible. Both images fill the entire screen width and each 1/2 of the screen height.
I found a similar solution here:
ImageView scaling TOP_CROP
The accepted solution works perfectly, but I can't figure out how to change the matrix to make it BOTTOM_CROP instead.
Working Code for TOP_CROP:
setScaleType(ScaleType.MATRIX);
Matrix matrix = getImageMatrix();
float scaleFactor = (getWidth() - Data.getImgPadding(c)) / (float) getDrawable().getIntrinsicWidth();
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
Note: A padding to the right is set programmatically, therefore it needs to be included in the calculation (the value is read by Data.getImgPadding(c)).
For BOTTOM_CROP I thought I'd use:
matrix.setScale(scaleFactor, scaleFactor, 0, getHeight());
But it's not really working, for the first image the bottom part is displayed more or less correctly, but there is some sort of margin below it - the bottom image border should be exactly at the bottom ImageView border. I assume I have to use the padding somehow but I don't know how to caculate the "new" image height after the horizontal padding value is set.
The bigger problem however is that (for whatever reason) when I set the value py in setScale() the second ImageView is suddenly empty... both views use the same layout xml, but that can't be the reason as the original code works perfectly on both.
I know this is a long time gone, but I've modified the answer in the post to suit your need.
#Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean hasChanged = super.setFrame(l, t, r, b);
Matrix matrix = getImageMatrix();
float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth();
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
// The Important Bit
Drawable drawable = getDrawable();
float heightD = drawable.getIntrinsicHeight();
float height = getHeight();
matrix.setTranslate(0, height - heightD);
setImageMatrix(matrix);
return hasChanged;
}
Hope it helps!
I newbie to android and graphic development.
I implement a custom-view and want to draw to canvas.
I set the customview size to be (width x (width/2)) so the rectangle height is half of the width.
Now when start drawing, its easy for me to draw on a square area like 1X1.
like this:
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int ChosenSize = Math.min(widthSize, heightSize);
int width = ChosenSize;
int height = (int)(ChosenSize/2);
setMeasuredDimension(width,height);
}
#Override protected void onDraw(Canvas canvas){
float width = (float)getWidth();
float height = (float)getHeight();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(width ,height );
// start drawing in a 1X1 square coordinates
canvas.restore();
}
But after restore() the result is stretched a little bit.
What am i doing wrong ??
Note: I have notice that when not using scale and working with the rectangle width and height the
result looks Ok
Thx
I figure it out.
The reason my drawing stretch, is because I scale from rectangle to square which is mistake.
Usually when canvas is square it easy to draw on 1x1 square, so I scale to canvas.scale(width,height),
But when canvas is rectangle like (width,width/2), I should scale to (width,height/width) to get rect of 1x2.
so after I change scaling to canvas.scale(width,height/width)
I want to create a live wallpaper, and I want to have the bottom background slide together with swiping homescreen pages, while another layer stays always on top of the background and under the app icons.
Is this possible and how can this be done?
You'll have to override public void onOffsetsChanged (float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset)
Using the value of xOffset, you can define a source rectangle that extracts part of your bitmap and draws that part on the screen.
This image should give you an understanding of how xOffset works:
Assuming that there are 5 homescreen pages,
If your picture is of size 960 x 800 (width x height) and if you want to draw a portion of size 480 x 800 every time, then you can define a source rectangle whose co-ordinates would be:
x1 = xOffset * (960 - 480); y1 = 0; x2 = x1 + 480, y2 = 800;
Then your destination rectangle would be the portion of the screen you want to draw onto.
You can then use public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint) method to draw the bitmap onto the screen.
I had used this technique a long time ago. I didn't check this in code before posting, and there may be alternatives (like using canvas.translate()). But hopefully this should help you get started. :)
I have a Bitmap that is larger than the ImageView that I'm putting it in. I have the ScaleType set to center_inside. How do I get the dimensions of the scaled down image?
Ok. I probably should have been clearer. I needed the height and width of the scaled Bitmap before it's ever drawn to the screen so that I could draw some overlays in the correct position. I knew the position of the overlays in the original Bitmap but not the scaled. I figured out some simple formulas to calculate where they should go on the scaled Bitmap.
I'll explain what I did in case someone else may one day need this.
I got the original width and height of the Bitmap. The ImageView's height and width are hard-coded in the xml file at 335.
int bitmap_width = bmp.getWidth();
int bitmap_height = bmp.getHeight();
I determined which one was larger so that I could correctly figure out which one to base the calculations off of. For my current example, width is larger. Since the width was scaled down to the the width of the ImageView, I have to find the scaled down height. I just multiplied the ratio of the ImageView's width to the Bitmap's width times the Bitmap's height. Division is done last because Integer division first would have resulted in an answer of 0.
int scaled_height = image_view_width * bitmap_height / bitmap_width;
With the scaled height I can determine the amount of blank space on either side of the scaled Bitmap by using:
int blank_space_buffer = (image_view_height - scaled_height) / 2;
To determine the x and y coordinates of where the overlay should go on the scaled Bitmap I have to use the original coordinates and these calculated numbers. The x coordinate in this example is easy. Since the scaled width is the width of the ImageView, I just have to multiply the ratio of the ImageView's width to the Bitmap's width with the original x coordinate.
int new_x_coord = image_view_width * start_x / bitmap_width;
The y coordinate is a bit trickier. Take the ratio of the Bitmap's scaled height to the Bitmap's original height. Multiply that value with the original y coordinate. Then add the blank area buffer.
int new_y_coord = scaled_height * start_y / bitmap_height + blank_space_buffer;
This works for what I need. If the height is greater than the width, just reverse the width and height variables.
Here's how I do it:
ImageView iv = (ImageView)findViewById(R.id.imageview);
Rect bounds = iv.getDrawable().getBounds();
int scaledHeight = bounds.height();
int scaledWidth = bounds.width();
You can use Drawable's getIntrinsicWidth() or height method if you want to get the original size.
EDIT: Okay, if you're trying to access these bounds at the time onCreate runs, you'll have to wait and retrieve them till after the layout pass. While I don't know that this is the best way, this is how I've been able to accomplish it. Basically, add a listener to the ImageView, and get your dimensions just before it's drawn to the screen. If you need to make any layout changes from the dimensions you retrieve, you should do it within onPreDraw().
ImageView iv = (ImageView)findViewById(R.id.imageview);
int scaledHeight, scaledWidth;
iv.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
Rect rect = iv.getDrawable().getBounds();
scaledHeight = rect.height();
scaledWidth = rect.width();
iv.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
Try this code:
final ImageView iv = (ImageView) findViewById(R.id.myImageView);
ViewTreeObserver vto = iv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Toast.makeText(MyActivity.this, iv.getWidth() + " x " + iv.getHeight(), Toast.LENGTH_LONG).show();
iv.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
Otherwise you can use onSizeChange() by making this view a custom one.