I am adding 1 layout dynamically. In that adding custom view. now I want to draw image on canvas exactly of same size as the size of that layout.
Below is the code of my custom view which i have tried.
public BlockView(Context context, Bitmap bitmap, int layoutHeight,
int layoutWidth) {
super(context);
this.bitmap = bitmap;
this.width = bitmap.getWidth();
this.height = bitmap.getHeight();
this.layoutHeight = layoutHeight;
this.layoutWidth = layoutWidth;
if (drawableWidth <= viewWidth && drawableHeight <= viewHeight) {
scale = 1.0f;
} else {
scale = Math.min((float) viewWidth / (float) drawableWidth,
(float) viewHeight / (float) drawableHeight);
}
mDrawable = new BitmapDrawable(getResources(), bitmap);
}
private static float getDegreesFromRadians(float angle) {
return (float) (angle * 180.0 / Math.PI);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
drawableWidth = mDrawable.getIntrinsicWidth();
drawableHeight = mDrawable.getIntrinsicHeight();
viewWidth = layoutWidth;
viewHeight = layoutHeight;
setMeasuredDimension(viewWidth, viewHeight);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saveCount = canvas.save();
matrix.setScale(scale, scale);
matrix.postRotate(getDegreesFromRadians(angle));
matrix.postTranslate(position.getX(), position.getY());
canvas.concat(matrix);
mDrawable.setBounds(0, 0, drawableWidth, drawableHeight);
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
With above code,I am successfully able to draw image and perform drag,zoom and rotate. But facing problem of not able to draw image of same size as the size of layout. and also need some way to proceed on how to check that image should not be scaled too small than the layout size.
Any help will be really appreciated.
Related
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 am trying to draw a circle which fills the entire changed view (for any device).
Circle will fill as much screen space as possible (without deforming its circular shape).
Rotating the screen orientation will adjust the clock view.
lass Circles extends View
{
private int x = 0;
private int y = 0;
private final int rsec=240;
private final int rmin=200;
private final int rhrs=150;
private Date date;
int viewWidth = 0;
int viewHeight = 0;
int Radius = 0;
public Circles(Context context)
{
super(context);
}
public void onDraw(final Canvas canvas)
{
super.onDraw(canvas);
String msg = "width: " + viewWidth + "height: " + viewHeight;
System.out.println(msg);
x = viewWidth / 2 ;
y = viewHeight / 2 ;
//2 Circels
Paint p1 = new Paint();
p1.setColor(Color.BLUE);
p1.setStyle(Style.STROKE);
Paint p2 = new Paint();
p2.setColor(Color.RED);
p2.setStyle(Style.FILL);
canvas.drawCircle(x, y , Radius, p1);
canvas.drawCircle(x , y , 20, p2);
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld){
super.onSizeChanged(xNew, yNew, xOld, yOld);
viewWidth = xNew;
viewHeight = yNew;
/*
these viewWidth and viewHeight variables
are the global int variables
that were declared above
*/
this.setMeasuredDimension( viewWidth, viewHeight);
Radius=Math.min(viewWidth, viewHeight);
}
the 1st circle , is not display at all when the phone is vertical , but when rotate him horizontal , the edges of the circke can be seen .
is nayone can tell me whats wrong with the onSizeChanged i used. ?
move this line
this.setMeasuredDimension( viewWidth, viewHeight);
after this line
Radius=Math.min(viewWidth, viewHeight);
and see if this works.
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"
We are experiencing the following issue for about a week already:
We are drawing an image we want to be able to rescale and "move" on the user's screen.
In order to achieve that, we used an ImageView and a Matrix: it works well.
However, now we're looking for drawing some rectangles over the background image.
These rectangles have to be rescaled and translated along with the background image... The issue we have is that when we create our shape, and paint it using the following code, this is drawn within the top left part of the image, quite like if the whole user's screen was just interpreted as a part of its real dimensions... ?
My custom ImageView
public class EnvironmentView extends ImageView {
Matrix matrix;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
public static boolean EDIT_RISKYZONE = false;
static boolean SAVE = false;
ScaleGestureDetector mScaleDetector;
Context context;
#Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(3);
paint.setAlpha(80);
float left = 0;
float top = 0;
float right = getWidth();
float down = getHeight();
RectF origRect = new RectF(left, top, right, down);
matrix.mapRect(origRect);
canvas.drawRect(origRect, paint);
}
public EnvironmentView(Context context) {
super(context);
sharedConstructing(context);
}
public EnvironmentView(Context context, AttributeSet attrs) {
super(context, attrs);
sharedConstructing(context);
}
private void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
matrix = new Matrix();
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
}
public void setMaxZoom(float x) {
maxScale = x;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
//Fit to screen.
float scale;
Drawable drawable = getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
setImageMatrix(matrix);
}
}
}
The screen output
Below is a screenshot of what is shown on the user's screen after he has drawn rectangles in the center of his screen, they are supposed to be about 3/4 of the screen's width and height.
Thank you for your help !
The image needs to start at the origin (top left -- 0,0). The rectangle should start half its width from the center of the screen and half its height.
int x = (widthOfScreen - widthOfRect)/2;
int y = (heightOfScreen - heightOfRect)/2;
So I think you need another matrix. First translate the second matrix so it would place the rectangle in the center of the screen, then apply the same operations that you do to the other one. Try doing just this before any other matrix operations are applied (ie comment them all out) and see that it places the rectangle in the right place. Then if is right, it should scale OK.
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);
}