Related
I have an ImageView which is displaying a png that has a bigger aspect ratio than that of the device (vertically speaking - meaning its longer). I want to display this while maintaining aspect ratio, matching the width of the parent, and pinning the imageview to the top of the screen.
The problem i have with using CENTER_CROP as the scale type is that it will (understandable) center the scaled image instead of aligning the top edge to the top edge f the image view.
The problem with FIT_START is that the image will fit the screen height and not fill the width.
I have solved this problem by using a custom ImageView and overriding onDraw(Canvas) and handeling this manually using the canvas; the problem with this approach is that 1) I'm worried there may be a simpler solution, 2) I am getting a VM mem exception when calling super(AttributeSet) in the constructor when trying to set a src img of 330kb when the heap has 3 mb free (with a heap size of 6 mb) and cant work out why.
Any ideas / suggestions / solutions are much welcome :)
Thanks
p.s. i thought that a solution may be to use a matrix scale type and do it myself, but that seems to to be the same or more work than my current solution!
Ok, I have a working solution. The prompt from Darko made me look again at the ImageView class (thanks) and have applied the transformation using a Matrix (as i originally suspected but did not have success on my first attempt!). In my custom imageView class I call setScaleType(ScaleType.MATRIX) after super() in the constructor, and have the following method.
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
Matrix matrix = getImageMatrix();
float scaleFactor = getWidth()/(float)getDrawable().getIntrinsicWidth();
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
return super.setFrame(l, t, r, b);
}
I have placed int in the setFrame() method as in ImageView the call to configureBounds() is within this method, which is where all the scaling and matrix stuff takes place, so seems logical to me (say if you disagree)
Below is the super.setFrame() method from the AOSP (Android Open Source Project)
#Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
mHaveFrame = true;
configureBounds();
return changed;
}
Find the full class src here
Here is my code for centering it at the bottom.
BTW in Dori's Code is a little bug: Since the super.frame() is called at the very end, the getWidth() method might return the wrong value.
If you want to center it at the top simply remove the postTranslate line and you're done.
The nice thing is that with this code you can move it anywhere you want. (right, center => no problem ;)
public class CenterBottomImageView extends ImageView {
public CenterBottomImageView(Context context) {
super(context);
setup();
}
public CenterBottomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public CenterBottomImageView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
setup();
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {
if (getDrawable() == null) {
return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
}
float frameWidth = frameRight - frameLeft;
float frameHeight = frameBottom - frameTop;
float originalImageWidth = (float)getDrawable().getIntrinsicWidth();
float originalImageHeight = (float)getDrawable().getIntrinsicHeight();
float usedScaleFactor = 1;
if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight)) {
// If frame is bigger than image
// => Crop it, keep aspect ratio and position it at the bottom and center horizontally
float fitHorizontallyScaleFactor = frameWidth/originalImageWidth;
float fitVerticallyScaleFactor = frameHeight/originalImageHeight;
usedScaleFactor = Math.max(fitHorizontallyScaleFactor, fitVerticallyScaleFactor);
}
float newImageWidth = originalImageWidth * usedScaleFactor;
float newImageHeight = originalImageHeight * usedScaleFactor;
Matrix matrix = getImageMatrix();
matrix.setScale(usedScaleFactor, usedScaleFactor, 0, 0); // Replaces the old matrix completly
//comment matrix.postTranslate if you want crop from TOP
matrix.postTranslate((frameWidth - newImageWidth) /2, frameHeight - newImageHeight);
setImageMatrix(matrix);
return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
}
}
Beginner tip: If it plain doesn't work, you likely have to extends androidx.appcompat.widget.AppCompatImageView rather than ImageView
You don't need to write a Custom Image View for getting the TOP_CROP functionality. You just need to modify the matrix of the ImageView.
Set the scaleType to matrix for the ImageView:
<ImageView
android:id="#+id/imageView"
android:contentDescription="Image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/image"
android:scaleType="matrix"/>
Set a custom matrix for the ImageView:
final ImageView imageView = (ImageView) findViewById(R.id.imageView);
final Matrix matrix = imageView.getImageMatrix();
final float imageWidth = imageView.getDrawable().getIntrinsicWidth();
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
final float scaleRatio = screenWidth / imageWidth;
matrix.postScale(scaleRatio, scaleRatio);
imageView.setImageMatrix(matrix);
Doing this will give you the TOP_CROP functionality.
This example works with images that is loaded after creation of object + some optimization.
I added some comments in code that explain what's going on.
Remember to call:
imageView.setScaleType(ImageView.ScaleType.MATRIX);
or
android:scaleType="matrix"
Java source:
import com.appunite.imageview.OverlayImageView;
public class TopAlignedImageView extends ImageView {
private Matrix mMatrix;
private boolean mHasFrame;
#SuppressWarnings("UnusedDeclaration")
public TopAlignedImageView(Context context) {
this(context, null, 0);
}
#SuppressWarnings("UnusedDeclaration")
public TopAlignedImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
#SuppressWarnings("UnusedDeclaration")
public TopAlignedImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mHasFrame = false;
mMatrix = new Matrix();
// we have to use own matrix because:
// ImageView.setImageMatrix(Matrix matrix) will not call
// configureBounds(); invalidate(); because we will operate on ImageView object
}
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
boolean changed = super.setFrame(l, t, r, b);
if (changed) {
mHasFrame = true;
// we do not want to call this method if nothing changed
setupScaleMatrix(r-l, b-t);
}
return changed;
}
private void setupScaleMatrix(int width, int height) {
if (!mHasFrame) {
// we have to ensure that we already have frame
// called and have width and height
return;
}
final Drawable drawable = getDrawable();
if (drawable == null) {
// we have to check if drawable is null because
// when not initialized at startup drawable we can
// rise NullPointerException
return;
}
Matrix matrix = mMatrix;
final int intrinsicWidth = drawable.getIntrinsicWidth();
final int intrinsicHeight = drawable.getIntrinsicHeight();
float factorWidth = width/(float) intrinsicWidth;
float factorHeight = height/(float) intrinsicHeight;
float factor = Math.max(factorHeight, factorWidth);
// there magic happen and can be adjusted to current
// needs
matrix.setTranslate(-intrinsicWidth/2.0f, 0);
matrix.postScale(factor, factor, 0, 0);
matrix.postTranslate(width/2.0f, 0);
setImageMatrix(matrix);
}
#Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
#Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
// We have to recalculate image after chaning image
setupScaleMatrix(getWidth(), getHeight());
}
// We do not have to overide setImageBitmap because it calls
// setImageDrawable method
}
Based on Dori I'm using a solution which either scales the image based on the width or height of the image to always fill the surrounding container. This allows scaling an image to fill the whole available space using the top left point of the image rather than the center as origin (CENTER_CROP):
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
Matrix matrix = getImageMatrix();
float scaleFactor, scaleFactorWidth, scaleFactorHeight;
scaleFactorWidth = (float)width/(float)getDrawable().getIntrinsicWidth();
scaleFactorHeight = (float)height/(float)getDrawable().getIntrinsicHeight();
if(scaleFactorHeight > scaleFactorWidth) {
scaleFactor = scaleFactorHeight;
} else {
scaleFactor = scaleFactorWidth;
}
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
return super.setFrame(l, t, r, b);
}
I hope this helps - works like a treat in my project.
None of these solutions worked for me, because I wanted a class that supported an arbitrary crop from either the horizontal or vertical direction, and I wanted it to allow me to change the crop dynamically. I also needed Picasso compatibility, and Picasso sets image drawables lazily.
My implementation is adapted directly from ImageView.java in the AOSP. To use it, declare like so in XML:
<com.yourapp.PercentageCropImageView
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix"/>
From source, if you wish to have a top crop, call:
imageView.setCropYCenterOffsetPct(0f);
If you wish to have a bottom crop, call:
imageView.setCropYCenterOffsetPct(1.0f);
If you wish to have a crop 1/3 of the way down, call:
imageView.setCropYCenterOffsetPct(0.33f);
Furthermore, if you elect to use another crop method, like fit_center, you may do so and none of this custom logic will be triggered. (Other implementations ONLY let you use their cropping methods).
Lastly, I added a method, redraw(), so if you elect to change your crop method/scaleType dynamically in code, you can force the view to redraw. For example:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER);
fullsizeImageView.redraw();
To go back to your custom top-center-third crop, call:
fullsizeImageView.setScaleType(ScaleType.MATRIX);
fullsizeImageView.redraw();
Here is the class:
/*
* Adapted from ImageView code at:
* http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/android/widget/ImageView.java
*/
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class PercentageCropImageView extends ImageView{
private Float mCropYCenterOffsetPct;
private Float mCropXCenterOffsetPct;
public PercentageCropImageView(Context context) {
super(context);
}
public PercentageCropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PercentageCropImageView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public float getCropYCenterOffsetPct() {
return mCropYCenterOffsetPct;
}
public void setCropYCenterOffsetPct(float cropYCenterOffsetPct) {
if (cropYCenterOffsetPct > 1.0) {
throw new IllegalArgumentException("Value too large: Must be <= 1.0");
}
this.mCropYCenterOffsetPct = cropYCenterOffsetPct;
}
public float getCropXCenterOffsetPct() {
return mCropXCenterOffsetPct;
}
public void setCropXCenterOffsetPct(float cropXCenterOffsetPct) {
if (cropXCenterOffsetPct > 1.0) {
throw new IllegalArgumentException("Value too large: Must be <= 1.0");
}
this.mCropXCenterOffsetPct = cropXCenterOffsetPct;
}
private void myConfigureBounds() {
if (this.getScaleType() == ScaleType.MATRIX) {
/*
* Taken from Android's ImageView.java implementation:
*
* Excerpt from their source:
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
}
*/
Drawable d = this.getDrawable();
if (d != null) {
int dwidth = d.getIntrinsicWidth();
int dheight = d.getIntrinsicHeight();
Matrix m = new Matrix();
int vwidth = getWidth() - this.getPaddingLeft() - this.getPaddingRight();
int vheight = getHeight() - this.getPaddingTop() - this.getPaddingBottom();
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) {
float cropXCenterOffsetPct = mCropXCenterOffsetPct != null ?
mCropXCenterOffsetPct.floatValue() : 0.5f;
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * cropXCenterOffsetPct;
} else {
float cropYCenterOffsetPct = mCropYCenterOffsetPct != null ?
mCropYCenterOffsetPct.floatValue() : 0f;
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * cropYCenterOffsetPct;
}
m.setScale(scale, scale);
m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
this.setImageMatrix(m);
}
}
}
// These 3 methods call configureBounds in ImageView.java class, which
// adjusts the matrix in a call to center_crop (android's built-in
// scaling and centering crop method). We also want to trigger
// in the same place, but using our own matrix, which is then set
// directly at line 588 of ImageView.java and then copied over
// as the draw matrix at line 942 of ImageVeiw.java
#Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean changed = super.setFrame(l, t, r, b);
this.myConfigureBounds();
return changed;
}
#Override
public void setImageDrawable(Drawable d) {
super.setImageDrawable(d);
this.myConfigureBounds();
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
this.myConfigureBounds();
}
public void redraw() {
Drawable d = this.getDrawable();
if (d != null) {
// Force toggle to recalculate our bounds
this.setImageDrawable(null);
this.setImageDrawable(d);
}
}
}
Maybe go into the source code for the image view on android and see how it draws the center crop etc.. and maybe copy some of that code into your methods. i don't really know for a better solution than doing this. i have experience manually resizing and cropping the bitmap (search for bitmap transformations) which reduces its actual size but it still creates a bit of an overhead in the process.
public class ImageViewTopCrop extends ImageView {
public ImageViewTopCrop(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
public ImageViewTopCrop(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
public ImageViewTopCrop(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int l, int t, int r, int b) {
computMatrix();
return super.setFrame(l, t, r, b);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
computMatrix();
}
private void computMatrix() {
Matrix matrix = getImageMatrix();
float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth();
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
}
If you are using Fresco (SimpleDraweeView) you can easily do it with:
PointF focusPoint = new PointF(0.5f, 0f);
imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
This one would be for a top crop.
More info at Reference Link
There are 2 problems with the solutions here:
They do not render in the Android Studio layout editor (so you can preview on various screen sizes and aspect ratios)
It only scales by width, so depending on the aspect ratios of the device and the image, you can end up with an empty strip on the bottom
This small modification fixes the problem (place code in onDraw, and check width and height scale factors):
#Override
protected void onDraw(Canvas canvas) {
Matrix matrix = getImageMatrix();
float scaleFactorWidth = getWidth() / (float) getDrawable().getIntrinsicWidth();
float scaleFactorHeight = getHeight() / (float) getDrawable().getIntrinsicHeight();
float scaleFactor = (scaleFactorWidth > scaleFactorHeight) ? scaleFactorWidth : scaleFactorHeight;
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
super.onDraw(canvas);
}
Simplest Solution: Clip the image
#Override
public void draw(Canvas canvas) {
if(getWidth() > 0){
int clipHeight = 250;
canvas.clipRect(0,clipHeight,getWidth(),getHeight());
}
super.draw(canvas);
}
I want to Aspect Fill my image. The size of my image is 1242x168. What I want is that if my screen is small my image will be cut and left align but it seems that it keeps on being aspect fit.
Here is my code
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int device_width = displaymetrics.widthPixels;
float density = getResources().getDisplayMetrics().density;
float dpWidth = displaymetrics.widthPixels / density;
if(dpWidth >= 768) {
int aspectRatio = (int) (0.135 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
}
Here is my xml
<pl.droidsonroids.gif.GifImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/displayImage"
android:layout_gravity="left"
android:scaleType="fitStart"
android:background="#color/red"
android:adjustViewBounds="true"
/>
I tried using android:scaleType="centerCrop" but it is crop in center I wanted to cut the right side that is why I wanted to be left align.
Try Scaling your image based on screen width:
public static float getScreenWidth(Activity activity) {
Display display = activity.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float pxWidth = outMetrics.widthPixels;
return pxWidth;
}
float screenWidth=getScreenWidth(act)
float newHeight = screenWidth;
if (bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
newHeight = (screenWidth * bitmap.getHeight()) / bitmap.getWidth();
}
Bitmap scaledBitmap=Bitmap.createScaledBitmap(bitmap, (int) screenWidth, (int) newHeight, true);
android:adjustViewBounds=true.
android:scaleType="centerCrop"
Links: Resizing ImageView to fit to aspect ratio,
https://gist.github.com/JakeWharton/2856179
I tried doing this
//Get width of device
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
float density = getResources().getDisplayMetrics().density;
float dpWidth = displaymetrics.widthPixels / density;
if(dpWidth > 768 ) {
int aspectRatio = (int) (0.135 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
gifImageView.setIsScale(false);
}else{
int aspectRatio = (int) (0.219 * device_width);
gifImageView.getLayoutParams().height = aspectRatio;
gifImageView.getLayoutParams().width = device_width;
gifImageView.setScaleType(ProportionalImageView.ScaleType.MATRIX);
gifImageView.setIsScale(true);
}
And create a cuztomized class that extends to ImageView / GifImageView
public class ProportionalImageView extends GifImageView {
Boolean isScale = false;
public ProportionalImageView(Context context) {
super(context);
if(isScale) {
setup();
}
}
public ProportionalImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
if(isScale) {
setup();
}
}
public ProportionalImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
if(isScale) {
setup();
}
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int l, int t, int r, int b)
{
if(isScale) {
computeMatrix();
}
return super.setFrame(l, t, r, b);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(isScale) {
computeMatrix();
}
}
private void computeMatrix() {
Matrix matrix = getImageMatrix();
float scaleFactor, scaleFactorWidth, scaleFactorHeight;
scaleFactorWidth = (float) getWidth() / (float) getDrawable().getIntrinsicWidth();
scaleFactorHeight = (float) getHeight() / (float) getDrawable().getIntrinsicHeight();
if (scaleFactorHeight > scaleFactorWidth) {
scaleFactor = scaleFactorHeight;
} else {
scaleFactor = scaleFactorWidth;
}
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
public void setIsScale(Boolean isScale){
this.isScale = isScale;
}
public Boolean getIsScale() {
return isScale;
}
}
My question is very simple:
How to get an Android android.hardware.Camera2 with 1:1 ratio and without deformation like Instagram?
I tested with the GoogeSamples project android-Camera2Basic. But when I change the preview with a ratio of 1:1 image is deformed. Does anyone have an idea on this?
Thanks #CommonsWare.
I followed your advice using negative margin (top and bottom) and it works.
To do that, I just update AutoFitTextureView the GoogeSamples project android-Camera2Basic this way:
public class AutoFitTextureView extends TextureView {
//...
private boolean mWithMargin = false;
//...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int margin = (height - width) / 2;
if(!mWithMargin) {
mWithMargin = true;
ViewGroup.MarginLayoutParams margins = ViewGroup.MarginLayoutParams.class.cast(getLayoutParams());
margins.topMargin = -margin;
margins.bottomMargin = -margin;
margins.leftMargin = 0;
margins.rightMargin = 0;
setLayoutParams(margins);
}
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Create custom texture view like this:
public class AutoFitTextureView extends TextureView {
private int mCameraWidth = 0;
private int mCameraHeight = 0;
private boolean mSquarePreview = false;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAspectRatio(int width, int height, boolean squarePreview) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mCameraWidth = width;
mCameraHeight = height;
mSquarePreview = squarePreview;
requestLayout();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mCameraWidth || 0 == mCameraHeight) {
setMeasuredDimension(width, height);
} else {
/**
* Vertical orientation
*/
if (width < height) {
if (mSquarePreview) {
setTransform(squareTransform(width, height));
setMeasuredDimension(width, width);
} else {
setMeasuredDimension(width, width * mCameraHeight / mCameraWidth);
}
}
/**
* Horizontal orientation
*/
else {
if (mSquarePreview) {
setTransform(squareTransform(width, height));
setMeasuredDimension(height, height);
} else {
setMeasuredDimension(height * mCameraWidth / mCameraHeight, height);
}
}
}
}
private Matrix setupTransform(int sw, int sh, int dw, int dh) {
Matrix matrix = new Matrix();
RectF src = new RectF(0, 0, sw, sh);
RectF dst = new RectF(0, 0, dw, dh);
RectF screen = new RectF(0, 0, dw, dh);
matrix.postRotate(-90, screen.centerX(), screen.centerY());
matrix.mapRect(dst);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
matrix.mapRect(src);
matrix.setRectToRect(screen, src, Matrix.ScaleToFit.FILL);
matrix.postRotate(-90, screen.centerX(), screen.centerY());
return matrix;
}
private Matrix squareTransform(int viewWidth, int viewHeight) {
Matrix matrix = new Matrix();
if (viewWidth < viewHeight) {
MyLogger.log(AutoFitTextureView.class, "Horizontal");
matrix.setScale(1, (float) mCameraHeight / (float) mCameraWidth, viewWidth / 2, viewHeight / 2);
} else {
MyLogger.log(AutoFitTextureView.class, "Vertical");
matrix.setScale((float) mCameraHeight / (float) mCameraWidth, 1, viewWidth / 2, viewHeight / 2);
}
return matrix;
}
}
And call setAspectRatio for your texture view in activity/fragment.
if (mVideoSize.width > mVideoSize.height) {
mTextureView.setAspectRatio(mVideoSize.height, mVideoSize.width, true);
} else {
mTextureView.setAspectRatio(mVideoSize.width, mVideoSize.height, true);
}
mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
mCamera.startPreview();
I did it with the Layout, in that way, google code can be keeped as it comes and automatically set a 1:1 preview based on the UI.
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/footer"
android:layout_below="#id/header">
<com.example.android.camera2video.AutoFitTextureView
android:id="#+id/texture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
Just put the AutoFitTextureView inside a ConstraintLayout and then
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, videoSize);
does all the magic
For anybody looking for this, I tried the above answer. Adding a margin to hide part of textureview to make it look square looks good in preview. But when saving the image, you should remove the hidden areas from the output image also.
An Easier solution is to show a full textureview and to overlay some other layouts on it to make it look square.You can easily crop the image from output.
you can find the sample code here
I would like my ImageView to scale in a particular fashion:
Scale so that the height of the image always fits the height of the ImageView
Crop any excess width
A picture speaks louder than a 1000 words, so here is a representation of how I want my ImageView to behave. Suppose it has a fixed height of say 100dp and suppose its width is match_parent.
Note that
on the phone layout, the image height is stretched, but the sides are cropped, akin to CROP_CENTER.
on the tablet layout, the image is also stretched to fit the ImageView height, behaving like FIT_CENTER
I suspect I need scaleType:matrix, but after that I'm lost. How can I make sure an image fits Y, but crops X?
In xml, use:
android:scaleType="centerCrop"
android:adjustViewBounds="true"
from & thanks to: https://stackoverflow.com/a/15600295/2162226
With a little help from my friends Carlos Robles and pskink, came up with the following custom ImageView:
public class FitYCropXImageView extends ImageView {
boolean done = false;
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
#SuppressWarnings("UnusedDeclaration")
public FitYCropXImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setScaleType(ScaleType.MATRIX);
}
private final RectF drawableRect = new RectF(0, 0, 0,0);
private final RectF viewRect = new RectF(0, 0, 0,0);
private final Matrix m = new Matrix();
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (done) {
return;//Already fixed drawable scale
}
final Drawable d = getDrawable();
if (d == null) {
return;//No drawable to correct for
}
int viewHeight = getMeasuredHeight();
int viewWidth = getMeasuredWidth();
int drawableWidth = d.getIntrinsicWidth();
int drawableHeight = d.getIntrinsicHeight();
drawableRect.set(0, 0, drawableWidth, drawableHeight);//Represents the original image
//Compute the left and right bounds for the scaled image
float viewHalfWidth = viewWidth / 2;
float scale = (float) viewHeight / (float) drawableHeight;
float scaledWidth = drawableWidth * scale;
float scaledHalfWidth = scaledWidth / 2;
viewRect.set(viewHalfWidth - scaledHalfWidth, 0, viewHalfWidth + scaledHalfWidth, viewHeight);
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER /* This constant doesn't matter? */);
setImageMatrix(m);
done = true;
requestLayout();
}
}
If you use scaleType:matrix you will need to create your own Matrix and asign it to the view by means of setImageMatrix(Matrix) or manually modify the matrix at hen onMEasure method of a customImageView.
public class MyImageView extends ImageView {
boolean done=false;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (done)
return;
final Drawable d = getDrawable();
final int drawableW = d.getIntrinsicWidth();
final int drawableH = d.getIntrinsicHeight();
float ratio = drawableW / drawableH;
//int width = getMeasuredWidth();
int height = getMeasuredHeight();
float scale=height/drawableH;
Matrix m = getImageMatrix();
float[] f = new float[9];
m.getValues(f);
f[Matrix.MSCALE_X]=scale;
f[Matrix.MSCALE_Y]=scale;
m.setValues(f);
done = true;
requestLayout();
}
}
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
LayoutParams params;
final ImageView iv0 = new ImageView(this);
//iv0.setBackgroundColor(0xffff0000);
params = new LayoutParams(LayoutParams.MATCH_PARENT, 100);
ll.addView(iv0, params);
final ImageView iv1 = new ImageView(this);
//iv1.setBackgroundColor(0xff00ff00);
params = new LayoutParams(60, 100);
ll.addView(iv1, params);
setContentView(ll);
Runnable action = new Runnable() {
#Override
public void run() {
Drawable d = getResources().getDrawable(R.drawable.layer0);
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
RectF src = new RectF(0, 0, dw, dh);
ImageView[] iviews = {iv0, iv1};
for (int i = 0; i < iviews.length; i++) {
ImageView iv = iviews[i];
iv.setImageDrawable(d);
iv.setScaleType(ScaleType.MATRIX);
float h = iv.getHeight();
float w = iv.getWidth();
float cx = w / 2;
float scale = h / dh;
float deltaw = dw * scale / 2;
RectF dst = new RectF(cx - deltaw, 0, cx + deltaw, h);
Matrix m = new Matrix();
m.setRectToRect(src, dst, ScaleToFit.FILL);
iv.setImageMatrix(m);
}
}
};
iv1.post(action);
If you want to display the center of the image, use:
android:scaleType="centerCrop"
android:adjustViewBounds="true"
If you want to show the edge of the image instead of the center, use:
android:scaleType="matrix"
android:adjustViewBounds="true"
i wish to have an android gallery that will host images of varying aspect ratios. what i'd like is something like CENTER_CROP for the images in the gallery. however, when i set the image scale type to this, the images overrun the gallery image border.
of course, FIT_XY results in squished / flattened images. CENTER results in horizontal or vertical black space inside the gallery image border.
any ideas how to accomplish this? every example i can find uses FIT_XY with fixed size images. i suppose i could crop the images myself but i'd rather not.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView iv = (ImageView) convertView;
if (iv == null) {
iv = new ImageView(context);
iv.setScaleType(ImageView.ScaleType.FIT_XY);
iv.setBackgroundResource(galleryItemBackground);
iv.setLayoutParams(new Gallery.LayoutParams(200, 200));
}
InputStream is;
try {
is = getInputStream(position);
} catch (IOException ioe) {
// TODO?
throw new RuntimeException(ioe);
}
Bitmap bm = BitmapFactory.decodeStream(is);
iv.setImageBitmap(bm);
/*
* if (bitmaps[position] != null) { bitmaps[position].recycle();
* bitmaps[position] = null; } bitmaps[position] = bm;
*/
return iv;
}
I had the same problem as you and looking at ScaleType documentation I found it could be done using ScaleType.MATRIX, for example:
int w = 1000;
int h = 1000;
Paint p = new Paint();
p.setShader(new LinearGradient(0, 0, w, h, Color.BLACK, Color.RED, Shader.TileMode.CLAMP));
Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas g = new Canvas(bmp);
g.drawRect(new Rect(0, 0, w, h), p);
ImageView i3 = new ImageView(context);
i3.setImageBitmap(bmp);
i3.setScaleType(ScaleType.MATRIX);
int viewWidth = 300;
int viewHeight = 300;
Matrix matrix = new Matrix();
matrix.postScale(bmp.getWidth() / viewWidth, bmp.getHeight() / viewHeight, bmp.getWidth() / 2, bmp.getHeight() / 2);
i3.setImageMatrix(matrix);
LayoutParams lp2 = new LayoutParams(viewWidth, viewHeight);
lp2.leftMargin = 100;
lp2.topMargin = 400;
lp2.gravity = 0;
this.addView(i3, lp2);
This solution complicates things a little bit too much though. If you want to scroll and zoom the ImageView you need to use matrix scaling as well. So I'd be interested in knowing any possible alternative.
For this kind of tasks I use this simple class. It fits height or width scaling the image properly (it depends on which is the smaller dimension) . After this operation it centers the image in the ImageView bounds.
public class FixedCenterCrop extends ImageView {
public FixedCenterCrop(Context context) {
super(context);
setScaleType(ScaleType.MATRIX);
}
public FixedCenterCrop(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
}
public FixedCenterCrop(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setScaleType(ScaleType.MATRIX);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
recomputeImgMatrix();
}
#Override
protected boolean setFrame(int l, int t, int r, int b) {
recomputeImgMatrix();
return super.setFrame(l, t, r, b);
}
private void recomputeImgMatrix() {
final Matrix matrix = getImageMatrix();
float scale;
final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int drawableWidth = getDrawable().getIntrinsicWidth();
final int drawableHeight = getDrawable().getIntrinsicHeight();
if (drawableWidth * viewHeight > drawableHeight * viewWidth) {
scale = (float) viewHeight / (float) drawableHeight;
} else {
scale = (float) viewWidth / (float) drawableWidth;
}
matrix.setScale(scale, scale);
matrix.postTranslate((viewWidth - drawableWidth * scale) / 2, (viewHeight - drawableHeight*scale)/2);
setImageMatrix(matrix);
}
}
I ended up just trimming the bitmap to a square myself, as,
Bitmap bm = BitmapFactory.decodeStream(is);
// make it square
int w = bm.getWidth();
int h = bm.getHeight();
if (w > h) {
// trim width
bm = Bitmap.createBitmap(bm, (w - h) / 2, 0, h, h);
} else {
bm = Bitmap.createBitmap(bm, 0, (h - w) / 2, w, w);
}