(Crop Image) Map crop rectangle from zoomed image to original image - android

I am trying to implement Image cropping in Android following the source code from AOSP gallery app (code actually taken from this library). I have set the ImageView's scale type to be MATRIX and scaled and translated the image using posttranslate and postscale method on the image matrix.
A crop rectangle of fixed 260x260 pixel is drawn on the image, centered within the ImageView. This crop rectangle is fixed in size and can't be moved. Instead the image is allowed to be moved and scaled (zoom in/ zoom out) so that user can put the desired area of the image within the crop rectangle.
while the user moves the image by dx, dy amount, posttranslate(dx, dy) is called on the image's matrix. Then the crop rectangle is mapped to the current matrix by calling matrix.map(rect). And then the new position(on original image) of the crop rectangle is calculated by converting the dx, dy to image space and shifting the crop rectangle accordingly.
here's some source excerpt for moving the image:
// This matrix is recomputed when we go from the thumbnail image to
// the full size image.
protected Matrix baseMatrix = new Matrix();
// This is the supplementary transformation which reflects what
// the user has done in terms of zooming and panning.
//
// This matrix remains the same when we go from the thumbnail image
// to the full size image.
protected Matrix suppMatrix = new Matrix();
// This is the final matrix which is computed as the concatentation
// of the base matrix and the supplementary matrix.
private final Matrix displayMatrix = new Matrix();
//move image by dx dy
protected void panBy(float dx, float dy) {
postTranslate(dx, dy);
setImageMatrix(getImageViewMatrix());
}
protected void postTranslate(float dx, float dy) {
suppMatrix.postTranslate(dx, dy);
}
// Combine the base matrix and the supp matrix to make the final matrix
protected Matrix getImageViewMatrix() {
// The final matrix is computed as the concatentation of the base matrix
// and the supplementary matrix
displayMatrix.set(baseMatrix);
displayMatrix.postConcat(suppMatrix);
return displayMatrix;
}
And this is how the image movement affects the crop rectangle view
public void handlePan(float dx, float dy)
{
RectF r = computeLayout();
float xDelta = (dx * (cropRect.width() / r.width()));
float yDelta = -(dy * (cropRect.height() / r.height()));
cropRect.offset(xDelta, yDelta);
// Put the cropping rectangle inside image rectangle
cropRect.offset(
Math.max(0, imageRect.left - cropRect.left),
Math.max(0, imageRect.top - cropRect.top));
cropRect.offset(
Math.min(0, imageRect.right - cropRect.right),
Math.min(0, imageRect.bottom - cropRect.bottom));
}
private RectF computeLayout() {
RectF r = new RectF(cropRect.left, cropRect.top,
cropRect.right, cropRect.bottom);
matrix.mapRect(r);
return r;
}
The crop view gets the image's current matrix from the following getunrotatedmatrix function
public Matrix getUnrotatedMatrix(){
Matrix unrotated = new Matrix();
getProperBaseMatrix(bitmapDisplayed, unrotated, false);
unrotated.postConcat(suppMatrix);
return unrotated;
}
// Setup the base matrix so that the image is centered and scaled properly.
private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {
float viewWidth = getWidth();
float viewHeight = getHeight();
float w = bitmap.getWidth();
float h = bitmap.getHeight();
matrix.reset();
// We limit up-scaling to 3x otherwise the result may look bad if it's a small icon
float widthScale = Math.min(viewWidth / w, 3.0f);
float heightScale = Math.min(viewHeight / h, 3.0f);
float scale = Math.min(widthScale, heightScale);
if (includeRotation) {
matrix.postConcat(bitmap.getRotateMatrix());
}
matrix.postScale(scale, scale);
matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
}
This works perfectly. But what I am struggling with is, to handle the zooming of the image. I have tried mapping a rectangle to the matrix and then mapping this rectangle to the original image, scaling down the crop rect by the same amount the image is scaled up and vice versa. But none of them worked. Any idea how to achieve this(get the position of the crop rectangle on original image(on sdcard), after zooming the image in imageview)? The zooming is done as below:
protected void zoomTo(float scaleFactor, float centerX, float centerY) {
float oldScale = getScale();
float scale = oldScale*scaleFactor;
if (scale > maxZoom) {
scale = maxZoom;
scaleFactor=maxZoom/oldScale;
}
else if(scale<0.5)
{
scale=0.5f;
scaleFactor=0.5f/oldScale;
}
suppMatrix.postScale(scaleFactor, scaleFactor, centerX, centerY);
setImageMatrix(getImageViewMatrix());
}
I understand that the crop rectangle has to be made smaller(to point to a smaller region of original image) after zoom-in and vice versa. But how to exactly do that scaling and position the crop rectangle appropriately considering the center of zoom (which can be anywhere on the image even outside the crop rectangle).

Related

How to Add Picture Frames to Image?

I am trying to achieve frames functionality , such that if i provide an image After Capturing/retrieving from Gallery ,i have done this part , now where i am stuck is How can i merge two images with respect to frame image accordingly!!
Now solution for combining two images is clearly given Here and Here
But they are not explaining the behaviour of adjusting one image with another such that in my case , Here are some examples:
I am already using Libraries like picasso and EasyImage so if they can help?
Edit:
Test Frame example
I made example. Please refer this repository.
https://github.com/nshmura/TestFrame/
Frame class merges picture's bitmap and frame's bitmap.
public class Frame {
//filename of frame
private String mFrameName;
//Rect of picture area in frame
private final Rect mPictureRect;
//degree of rotation to fit picture and frame.
private final float mRorate;
public Frame(String frameName,int left, int top, int right, int bottom, float rorate) {
mFrameName = frameName;
mPictureRect = new Rect(left, top, right, bottom);
mRorate = rorate;
}
public Bitmap mergeWith(Context context, Bitmap pictureBitmap) {
Bitmap frameBitmap = AssetsUtil.getBitmapFromAsset(context, mFrameName);
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
Bitmap bitmap = Bitmap.createBitmap(frameBitmap.getWidth(), frameBitmap.getHeight(), conf);
Canvas canvas = new Canvas(bitmap);
Matrix matrix = getMatrix(pictureBitmap);
canvas.drawBitmap(pictureBitmap, matrix, null);
canvas.drawBitmap(frameBitmap, 0, 0, null);
return bitmap;
}
Matrix getMatrix(Bitmap pictureBitmap) {
float widthRatio = mPictureRect.width() / (float) pictureBitmap.getWidth();
float heightRatio = mPictureRect.height() / (float) pictureBitmap.getHeight();
float ratio;
if (widthRatio > heightRatio) {
ratio = widthRatio;
} else {
ratio = heightRatio;
}
float width = pictureBitmap.getWidth() * ratio;
float height = pictureBitmap.getHeight() * ratio;
float left = mPictureRect.left - (width - mPictureRect.width()) / 2f;
float top = mPictureRect.top - (height - mPictureRect.height()) / 2f;
Matrix matrix = new Matrix();
matrix.postRotate(mRorate);
matrix.postScale(ratio, ratio);
matrix.postTranslate(left, top);
return matrix;
}
}
Use like this:
//This is sample picture.
//Please take picture form gallery or camera.
Bitmap pictureBitmap = AssetsUtil.getBitmapFromAsset(this, "picture.jpg");
//This is sample frame.
// the number of left, top, right, bottom is the area to show picture.
// last argument is degree of rotation to fit picture and frame.
Frame frameA = new Frame("frame_a.png", 113, 93, 430, 409, 4);
Bitmap mergedBitmap = frameA. mergeWith(this, pictureBitmap);
//showing result bitmap
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(mergedBitmap);
Result is below:

How to center imageView with matrix scaletype?

I'm using Android Studio to display an imageView. I'm using pinch zoom to interact with my ImageView.
Code:
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener{
#Override
public boolean onScale(ScaleGestureDetector detector) {
scale = scale * detector.getScaleFactor();
scale = Math.max(0.1f, Math.min(scale, 5f));
matrix.setScale(scale, scale);
imageView.setImageMatrix(matrix);
return true;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
return true;
}
Zoom is fine, but problem is the position of imageview, it's stuck in left upper corner. Tried layout changes but nothing to do. After some reseaches, I've seen these links in the forum ImageView Center in position with ScaleType Matrix and Center the image in ImageView after zoom-pinch, but these solutions aren't working, I've also checked the links given inside.
Help would be appreciated,
Thanks !
EDIT: Added the piece of code on how I get my ImageView
Picasso.with(this).load(url).resize(350, 330).centerInside().into(imageView);
onCreate Code
ScaleListner Class and Gesture Code
Picasso.with(this).load(url).into(imageView, new Callback.EmptyCallback() {
#Override
public void onSuccess() {
Drawable d = imageView.getDrawable();
// TODO: check that d isn't null
RectF imageRectF = new RectF(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
RectF viewRectF = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
matrix.setRectToRect(imageRectF, viewRectF, ScaleToFit.CENTER);
imageView.setImageMatrix(matrix);
}
});
Edit 2:
How to center the image like CENTER_INSIDE but using matrix:
First we need a rectangle with the image dimensions:
Drawable d = imageView.getDrawable();
// TODO: check that d isn't null
RectF imageRectF = new RectF(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
Next you need a rectangle with the view dimensions:
RectF viewRectF = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
Now we run a method on matrix that will center the image in the view:
matrix.setRectToRect(imageRectF, viewRectF, ScaleToFit.CENTER);
What this does is set up the matrix so that the first rectangle will transform to the second rectangle. ScaleToFit.CENTER will preserve the aspect ratio and have the same effect as scale type CENTER_INSIDE.
So if you call
imageView.setImageMatrix(matrix);
at this point, you will have a centered image.
Edit: I think you are almost there.
Your matrix will undo the image centering that Picasso did, so you need to put in a translation to center the image before you scale it.
#Override
public boolean onScale(ScaleGestureDetector detector) {
Drawable d = imageView.getDrawable();
// if d is null, then Picasso hasn't loaded the image yet
float offsetX = (imageView.getWidth() - d.getIntrinsicWidth()) / 2F;
float offsetY = (imageView.getHeight() - d.getIntrinsicHeight()) / 2F;
float centerX = imageView.getWidth() / 2F;
float centerY = imageView.getHeight() / 2F;
// note that these offset and center values don't change with the scaling,
// so you can calculate them somewhere else and then use them here.
scale *= detector.getScaleFactor();
scale = Math.max(0.1f, Math.min(scale, 5f));
matrix.setScale(scale, scale, centerX, centerY);
matrix.preTranslate(offsetX, offsetY);
imageView.setImageMatrix(matrix);
}
You are using setScale(float sx, float sy). There is another version, setScale(float sx, float sy, float px, float py) where px,py is a pivot point.
So if you want to center your image, determine the center of your view and use the x,y value as the pivot point.
You will also want to center the image inside the view, so you will need to move the image first before you scale.
float offsetX = (imageView.getWidth() - bitmap.getIntrinsicWidth()) / 2F;
float offsetY = (imageView.getHeight() - bitmap.getIntrinsicHeight()) / 2F;
float centerX = imageView.getWidth() / 2F;
float centerY = imageView.getHeight() / 2F;
matrix.setScale(scale, scale, centerX, centerY);
matrix.preTranslate(offsetX, offsetY);

Scale bitmap to specific size, keeping aspect ratio and filling the rest with 0 alpha pixels

I've been searching for a solution to this problem for quite some time and haven't found anything to match my needs yet.
I want to scale a bitmap to a specific size while maintaining aspect ratio.
Think of it as scaling a bitmap using fitCenter in an ImageView, only in a new bitmap.
The source bitmap has to fit inside the destination bitmap which has a specific size, and the rest of the pixels have to be transparent.
I have tried using Glide like so:
Glide.with(context).load(url)
.asBitmap()
.override(1280, 720)
.fitCenter()
.into(1280, 720)
.get();
But this method returns a bitmap that fits only width (or hight) and wraps the size.
I've heard that using Canvas is a possible solution but haven't found any way of achieving my goal using it.
Any help or insight would be greatly appreciated. I will post any needed clarifications if requested.
I managed to solve it using this function:
Bitmap resizeBitmap(Bitmap image, int destWidth, int destHeight) {
Bitmap background = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.ARGB_8888);
float originalWidth = image.getWidth();
float originalHeight = image.getHeight();
Canvas canvas = new Canvas(background);
float scaleX = (float) 1280 / originalWidth;
float scaleY = (float) 720 / originalHeight;
float xTranslation = 0.0f;
float yTranslation = 0.0f;
float scale = 1;
if (scaleX < scaleY) { // Scale on X, translate on Y
scale = scaleX;
yTranslation = (destHeight - originalHeight * scale) / 2.0f;
} else { // Scale on Y, translate on X
scale = scaleY;
xTranslation = (destWidth - originalWidth * scale) / 2.0f;
}
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
Paint paint = new Paint();
paint.setFilterBitmap(true);
canvas.drawBitmap(image, transformation, paint);
return background;
}

Making Bitmap aspect ratio to centerFit Android

I have searched alot for this but got no proper solution!
i have a drawing area in my android app and i want to take image from camera/gallery and set it as a background of canvas.
I get Bitmap after taking photo from camera/gallery and i pass my bitmap to this method to make it aspect fit to my drawView keeping its aspect ratio.
Here newHeight/newWidth are my drawView's height and width.
public Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) {
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
// Compute the scaling factors to fit the new height and width, respectively.
// To cover the final image, the final scaling will be the bigger
// of these two.
float xScale = (float) newWidth / sourceWidth;
float yScale = (float) newHeight / sourceHeight;
float scale = Math.min(xScale, yScale);
// Now get the size of the source bitmap when scaled
float scaledWidth = scale * sourceWidth;
float scaledHeight = scale * sourceHeight;
// Let's find out the upper left coordinates if the scaled bitmap
// should be centered in the new size give by the parameters
float left = (newWidth - scaledWidth) / 2;
float top = (newHeight - scaledHeight) / 2;
// The target rectangle for the new, scaled version of the source bitmap will now
// be
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
// Finally, we create a new bitmap of the specified size and draw our new,
// scaled bitmap onto it.
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
Canvas canvas = new Canvas(dest);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(source, null, targetRect, null);
return dest;
}
It works good on mt Htc m7 but on my galaxy tab4 it makes image strech so sleek as you can see in image below:
do anyone know any method through which i can set bitmap to my drawView like this
Bitmap backgroundBitMap= c.scaleCenterCrop(bmp, drawView.getHeight(), drawView.getWidth());
String tempPath = getPath(selectedImageUri, MainActivity.this);
backgroundBitMap= c.rotateImage(backgroundBitMap,tempPath);
drawView.setBackgroundDrawable(new BitmapDrawable(backgroundBitMap));
but keeping its aspect ration same. Actually i want CenterFit functionality for a bitmap.
Any help would be appreciated! Thanks

Get image dimensions after it draws in screen

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

Categories

Resources