Suppose I put some points as (x1,y1) = (133,123), (x2,y2) = (149,136), (x3,y3) = (182,136) and so on which makes a shape something like this:
Now I want to change the position of these points as per the screen resolution such that the shape gets resized and gets centered and also such that the shape doesn't get damaged. Please help me.
You can grab the scale factor from the DisplayMetrics as shown in the Android - Supporting Multiple Screens documentation:
final float scale = getResources().getDisplayMetrics().density;
Multiply all you x and y coordinates with scale and your points are screen density independent.
To fit the image on the screen (or View probably), you can grab the width and height of your View. Check the width and height of your image and calculate the maximum scale factor.
Combine (multiply) both scale factors and your image should fit your View.
you can use onMeasure method to get measure then you can start painting with that position. I dont know below code is working truly, maybe it need to be optimized.
protected void onDraw(Canvas canvas) {
int height = getMeasuredHeight();
int width = getMeasuredWidth();
// Find the center
px = width / 2;
py = height / 2;
canvas.drawColor(BACKGROUND);
canvas.drawBitmap(mBitmap, 0, 0, null);
canvas.drawPath(mPath, mPaint);
// TODO remove if you dont want points to be drawn
for (Point point : mPoints) {
canvas.drawPoint(point.x + px, point.y + py, mPaint);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec);
setMeasuredDimension(measuredHeight, measuredWidth);
}
private int measureHeight(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
// Default size if no limits are specified.
int result = 500;
if (specMode == MeasureSpec.AT_MOST) {
// Calculate the ideal size of your
// control within this maximum size.
// If your control fills the available
// space return the outer bound.
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
// If your control can fit within these bounds return that value.
result = specSize;
}
return result;
}
private int measureWidth(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
// Default size if no limits are specified.
int result = 500;
if (specMode == MeasureSpec.AT_MOST) {
// Calculate the ideal size of your control
// within this maximum size.
// If your control fills the available space
// return the outer bound.
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
// If your control can fit within these bounds return that value.
result = specSize;
}
return result;
}
Related
I have a question about how the screen orientation in Android is handled when we use the Camera2 API in combination with SurfaceView.
I was playing with the official HdrViewfinder google sample code at https://github.com/googlesamples/android-HdrViewfinder a little bit. In that project, they use a class called FixedAspectSurfaceView which is an extension of SurfaceView.
But that project displays the camera preview correctly only when the screenOrientation of the activity (AndroidManifest) is in landscape mode, not in portrait mode. Setting the attribute to portrait swaps the preview in a weird way.
How could I modify that code to be also able to see the camera preview correctly in portrait mode ?
So, the FixedAspectSurfaceView.java class looks like this:
public class FixedAspectSurfaceView extends SurfaceView {
/**
* Desired width/height ratio
*/
private float mAspectRatio;
private GestureDetector mGestureDetector;
public FixedAspectSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// Get initial aspect ratio from custom attributes
TypedArray a =
context.getTheme().obtainStyledAttributes(attrs,
R.styleable.FixedAspectSurfaceView, 0, 0);
setAspectRatio(a.getFloat(
R.styleable.FixedAspectSurfaceView_aspectRatio, 1.f));
a.recycle();
}
/**
* Set the desired aspect ratio for this view.
*
* #param aspect the desired width/height ratio in the current UI orientation. Must be a
* positive value.
*/
public void setAspectRatio(float aspect) {
if (aspect <= 0) {
throw new IllegalArgumentException("Aspect ratio must be positive");
}
mAspectRatio = aspect;
requestLayout();
}
/**
* Set a gesture listener to listen for touch events
*/
public void setGestureListener(Context context, GestureDetector.OnGestureListener listener) {
if (listener == null) {
mGestureDetector = null;
} else {
mGestureDetector = new GestureDetector(context, listener);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
// General goal: Adjust dimensions to maintain the requested aspect ratio as much
// as possible. Depending on the measure specs handed down, this may not be possible
// Only set one of these to true
boolean scaleWidth = false;
boolean scaleHeight = false;
// Sort out which dimension to scale, if either can be. There are 9 combinations of
// possible measure specs; a few cases below handle multiple combinations
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
// Can't adjust sizes at all, do nothing
} else if (widthMode == MeasureSpec.EXACTLY) {
// Width is fixed, heightMode either AT_MOST or UNSPECIFIED, so adjust height
scaleHeight = true;
} else if (heightMode == MeasureSpec.EXACTLY) {
// Height is fixed, widthMode either AT_MOST or UNSPECIFIED, so adjust width
scaleWidth = true;
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
// Need to fit into box <= [width, height] in size.
// Maximize the View's area while maintaining aspect ratio
// This means keeping one dimension as large as possible and shrinking the other
float boxAspectRatio = width / (float) height;
if (boxAspectRatio > mAspectRatio) {
// Box is wider than requested aspect; pillarbox
scaleWidth = true;
} else {
// Box is narrower than requested aspect; letterbox
scaleHeight = true;
}
} else if (widthMode == MeasureSpec.AT_MOST) {
// Maximize width, heightSpec is UNSPECIFIED
scaleHeight = true;
} else if (heightMode == MeasureSpec.AT_MOST) {
// Maximize height, widthSpec is UNSPECIFIED
scaleWidth = true;
} else {
// Both MeasureSpecs are UNSPECIFIED. This is probably a pathological layout,
// with width == height == 0
// but arbitrarily scale height anyway
scaleHeight = true;
}
// Do the scaling
if (scaleWidth) {
width = (int) (height * mAspectRatio);
} else if (scaleHeight) {
height = (int) (width / mAspectRatio);
}
// Override width/height if needed for EXACTLY and AT_MOST specs
width = View.resolveSizeAndState(width, widthMeasureSpec, 0);
height = View.resolveSizeAndState(height, heightMeasureSpec, 0);
// Finally set the calculated dimensions
setMeasuredDimension(width, height);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector != null) {
return mGestureDetector.onTouchEvent(event);
}
return false;
}
}
I changed the screenOrientation attribute in the AndroidManifest file to portrait.
I changed also the activity_main.xml layout file:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<com.celik.abdullah.project.utils.FixedAspectSurfaceView
android:id="#+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:aspectRatio="0.75"/>
<Button
android:id="#+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:text="next"/>
</FrameLayout>
When I leave the screenOrientation attribute in the manifest file in landscape, the camera preview is fine but the application opens of course "in landscape" mode. When I set the screenOrientation to portrait, then the camera preview "swaps the view to left". I did not know how to describe it but it is definitely weird. Why is the preview when I switch to portrait ?
And how could I modify the project so that it also can be used in portrait mode?
For FixedAspectSurfaceView, you should just be able to set its aspect ratio to the inverse of the aspect ratio you use in landscape. So if in landscape you set it to 4/3, set it to 3/4 for portrait layout.
The camera-to-SurfaceView path should handle all the rotations for you, you just need to keep the shape of the SurfaceView correct.
I know it's bit tricky and still thinking whether is it possible or not, but i want to make my image to adjust without decreasing in image quality on any android device when i used vector Drawable it was pretty convenient but sometimes size of vectors are not memory efficient so i don't want to use them.Though i want to know if there is any way to adjust simple PNG or JPEG files irrespective of resolution and screen size in Android?
If anyone can give me way,it would be great help !!
Use Resize Image View (Custom Image View)
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ResizableImageView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable d = getDrawable();
// get drawable from imageview
if (d == null) {
super.setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
return;
}
int imageHeight = d.getIntrinsicHeight();
int imageWidth = d.getIntrinsicWidth();
// get height and width of the drawable
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// get width and height extracts the size from the supplied measure specification.
float imageRatio = 0.0F;
if (imageHeight > 0) {
imageRatio = imageWidth / imageHeight;
}
float sizeRatio = 0.0F;
if (heightSize > 0) {
sizeRatio = widthSize / heightSize;
}
int width;
int height;
if (imageRatio >= sizeRatio) {
// set width to maximum allowed
width = widthSize;
// scale height
height = width * imageHeight / imageWidth;
} else {
// set height to maximum allowed
height = heightSize;
// scale width
width = height * imageWidth / imageHeight;
}
setMeasuredDimension(width, height);
// This method must be called to store the measured width and measured height. Failing to do so will trigger an exception at measurement time
}
}
I'm implementing Camera 2 API in my project. I'm using TextureView and these line of codes to set the camera fullscreen preview size:
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];
This seems to be the largest preview size that device support. I'm not sure if this size works with all devices and fit its device's aspect ratio without being stretched. Does anyone know?
If your Camera resolutions , texture view and your device's display dimensions are not same then you have to adjust the dimensions. For that you have to put your TextureView inside of FrameLayout. Below Code is applicable to all the devices with various Display resolutions.
Take your Display Dimetions if you are previewing in full screen.Take int DSI_height, int DSI_width global variable.
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
DSI_height = displayMetrics.heightPixels;
DSI_width = displayMetrics.widthPixels;
select your required resolutions from Camera2 API and assign to Size imageDimension, Take private Size imageDimension globally and use
setAspectRatioTextureView(imageDimension.getHeight(),imageDimension.getWidth());
and use below logic
private void setAspectRatioTextureView(int ResolutionWidth , int ResolutionHeight )
{
if(ResolutionWidth > ResolutionHeight){
int newWidth = DSI_width;
int newHeight = ((DSI_width * ResolutionWidth)/ResolutionHeight);
updateTextureViewSize(newWidth,newHeight);
}else {
int newWidth = DSI_width;
int newHeight = ((DSI_width * ResolutionHeight)/ResolutionWidth);
updateTextureViewSize(newWidth,newHeight);
}
}
private void updateTextureViewSize(int viewWidth, int viewHeight) {
Log.d(TAG, "TextureView Width : " + viewWidth + " TextureView Height : " + viewHeight);
textureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
}
There might be edge cases where that approach would fail, but I don't have a perfect answer to your question why.
In contrast, I have a proper approach on how to implement a version that will most certainly work:
Looking at the Google API demos for the Camera 2, I found some sample code that should be helpful to you to make sure it will fit all screen sized correctly:
/**
* Given {#code choices} of {#code Size}s supported by a camera, choose the smallest one that
* is at least as large as the respective texture view size, and that is at most as large as the
* respective max size, and whose aspect ratio matches with the specified value. If such size
* doesn't exist, choose the largest one that is at most as large as the respective max size,
* and whose aspect ratio matches with the specified value.
*
* #param choices The list of sizes that the camera supports for the intended output
* class
* #param textureViewWidth The width of the texture view relative to sensor coordinate
* #param textureViewHeight The height of the texture view relative to sensor coordinate
* #param maxWidth The maximum width that can be chosen
* #param maxHeight The maximum height that can be chosen
* #param aspectRatio The aspect ratio
* #return The optimal {#code Size}, or an arbitrary one if none were big enough
*/
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
Source
Also you should take a look at the whole Camera2BasicFragment.java and AutoFitTextureView.java classes for proper implementation.
I solved this problem via a different approach. I get the screen width and height and calculate how much wider or higher the preview would have to be to fill the whole screen and keep aspect ratio. It works pretty well for me without any distortions.
Add a class member variable:
public DisplayMetrics mMetrics = new DisplayMetrics();
Use the following as onMeasure:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(mMetrics);
double ratio = (double)mRatioWidth / (double)mRatioHeight;
double invertedRatio = (double)mRatioHeight / (double)mRatioWidth;
double portraitHeight = width * invertedRatio;
double portraitWidth = width * (mMetrics.heightPixels / portraitHeight);
double landscapeWidth = height * ratio;
double landscapeHeight = (mMetrics.widthPixels / landscapeWidth) * height;
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension((int)portraitWidth, mMetrics.heightPixels);
} else {
setMeasuredDimension(mMetrics.widthPixels, (int)landscapeHeight);
}
}
}
Any feedback is greatly appreciated ;)
Best M
Change the AutoFitTextureView.java file and set value like below:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, height);
Log.d("rlijeolid1",String.valueOf(width)+"\t"+String.valueOf(height));
} else {
setMeasuredDimension(width , height);
Log.d("rlijeolid2",String.valueOf(height * mRatioWidth / mRatioHeight)+"\t"+String.valueOf(height));
}
}
}
I am creating an application that takes an image and centers it onscreen for alteration. I am using a SurfaceView and Canvas. I am trying to center the SurfaceView in the parent RelativeLayout. I had it working, but it would become distorted on rotate. So I am currently using the surfaceChanged method to reset the size of the bitmap and canvas when the orientation changes:
#Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
bitmap_width = height * imageRatio;
bitmap_height = height;
} else {
bitmap_width = width;
bitmap_height = width / imageRatio;
}
canvas_width = (int) bitmap_width;
canvas_height = (int) bitmap_height;
try {
canvas_bitmap = Bitmap.createScaledBitmap(initial_bitmap, (int) bitmap_width, (int) bitmap_height, true);
surface_canvas = new Canvas();
surface_canvas.setBitmap(canvas_bitmap);
} catch (Exception exception) {
exception.printStackTrace();
}
}
The scaling now works, but now it is not centered. I also noticed that the rest of the background is black, not gray as it used to be. This would lead me to believe that the canvas is stretched beyond the view of the bitmap. Upon further inspection I noticed that the surfaceChanged method gets called twice when started. So I started Logging everything, and this is what I found:
07-27 16:39:21.363 2129-2129/com... E/SURFACE_CHANGEDīš imageRatio: 1.3333334 width: 1080 height: 1701 bitmap_width: 1080.0 bitmap_height: 810.0 canvas_width: 1080 canvas_height: 810 surface_width: -1 surface_height: -2
07-27 16:39:21.388 2129-2129/com... E/SURFACE_CHANGEDīš imageRatio: 0.0 width: 1080 height: 1701 bitmap_width: 1080.0 bitmap_height: Infinity canvas_width: 1080 canvas_height: 2147483647 surface_width: -1 surface_height: -2
Most importantly the imageRatio, which is only set once in my code, changes to 0.0. Thus setting one either the height or the width of the bitmap to Infinity. If I rotate the device, a similar set of two logs appear, with the original imageRatio set to 1.33... and then changing to 0.0. I cannot for the life of me figure out why this would be the case.
Anyway, this is easily avoided. I wrapped my code it an if statement specifying (imageRatio > 0.0). Yet the issue persists, nothing changes!
Okay, I figured it out. The width and the height of the actual SurfaceView was not changing to match the size of the bitmap, so I had to change the size of the SurfaceView before surfaceChanged got called. So I overrode onConfigurationChanged to change the size of the SurfaceView itself with surfaceHolder.setFixedSize(), which ends up calling surfaceChanged anyway:
#Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
int screenWidth = screenSize.x;
int screenHeight = screenSize.y;
float width;
float height;
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenHeight = screenSize.x;
width = screenHeight * imageRatio;
height = screenHeight;
} else if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
screenWidth = screenSize.x;
width = screenWidth;
height = screenWidth / imageRatio;
} else {
Log.e("UNSUPPORTED_ORIENTATION", Integer.toString(config.orientation));
width = screenWidth;
height = screenHeight;
}
surfaceHolder.setFixedSize((int) width, (int) height);
}
Then I used surfaceChanged to set the width and height of the canvas and bitmap to match the surfaceView:
#Override
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) {
bitmapWidth = width;
bitmapHeight = height;
canvasWidth = width;
canvasHeight = height;
try {
canvasBitmap = Bitmap.createScaledBitmap(initialBitmap, (int) bitmapWidth, (int) bitmapHeight, true);
surfaceCanvas = new Canvas();
surfaceCanvas.setBitmap(canvasBitmap);
} catch (Exception exception) {
exception.printStackTrace();
}
}
The way I solved this problem is by creating a custom view for the child views, and then overriding onMeasure() for the custom view. The new onMeasure() sets the width and height to be as large as possible.
The problem is when you show the soft keyboard and rotate the phone. With the orientation change and the keyboard showing, onMeasure() sets the "largest" available height to be something ridiculously small, so when I hide the keyboard, the child views have the wrong size.
Is there a way to tell the views to recompute the layout when the keyboard goes away? Or am I doing onMeasure() wrong? Here's the code:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
setLayoutParams(new LinearLayout.LayoutParams(
measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec))
);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
Display display = ( (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int screenWidth = display.getWidth();
if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the view
result = screenWidth;
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}
Log.d(TAG, "Width: "+String.valueOf(result));
return result;
}
measureHeight() is done the same way.
Your super.onMeasure(widthMeasureSpec, heightMeasureSpec) uses the passed in values I would think you would want these to be the actual measured width and height:
super.onMeasure(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));