I have been working with the camera2 api demo from google and unfortunately the sample application is built to display the textureview preview at approximately 70% of the screen height, after looking around I was able to determine that this was being caused by the AutoFitTextureView overriding the onMeasure() method as shown 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, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
I attempted to correct this by setting the correct heights and widths in setMeasuredDimension(width, height);, this fixed the height issue and gave me a full screen preview from the textureview, however the aspect ratio is completely broken and warped on every device, what is the standard way of fixing this? I see many apps on the play store have found a way to solve this problem but havent been able to track down a fix, any help will go a long way, thanks.
I was able to fix the issue by switching setMeasuredDimension();
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(height * mRatioWidth / mRatioHeight, height);
} else {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
}
}
Below is the code that we used to measure a preview which supports 4:3, 16:9 and 1:1 preview sizes. The height is scaled because the app is block in portrait, it does not rotate to landscape.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "[onMeasure] Before transforming: " + width + "x" + height);
int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
boolean isInHorizontal = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
int newWidth;
int newHeight;
Log.d(TAG, "[onMeasure] Get measured dimensions: " + getMeasuredWidth() + "x" + getMeasuredHeight());
if (isInHorizontal) {
newHeight = getMeasuredHeight();
if (mAspectRatioOneOne) newWidth = getMeasuredHeight();
else newWidth = (int) (newHeight * mAspectRatio);
} else {
newWidth = getMeasuredWidth();
if (mAspectRatioOneOne) newHeight = getMeasuredWidth();
else newHeight = (int) (newWidth * mAspectRatio);
}
setMeasuredDimension(newWidth, newHeight);
Log.d(TAG, "[onMeasure] After transforming: " + getMeasuredWidth() + "x" + getMeasuredHeight());
}
I had similar problem using Camera2 implementation. I used AutoFitTextureView implementation of TextureView as preview from camera, and want to adjust to correct ratio. My problem income from different implementations of methods and propagate to onSurfaceTextureSizeChanged.
[1]com.google.android.cameraview.CameraView#onMeasure(..) -> [2]com.google.android.cameraview.AutoFitTextureView#onMeasure(..) -> [3]onSurfaceTextureSizeChanged(..)
Problem where in different if [1] than in [2].
I replaced in [1]:
if (height < width * ratio.getY() / ratio.getX()) {
to
if (!(height < width * ratio.getY() / ratio.getX())) {
because [2] had if based on width not height
if (width < height * mRatioWidth / mRatioHeight) {
Verify if methods onMeasure(..) are implemented correctly.
Or you could do like Edmund Rojas - above
If you want full-screen, undistorted preview, then you have to select a preview resolution for the camera that matches the aspect ratio of your screen.
However, if your screen size isn't a standard size (16:9 or 4:3, basically), especially once you subtract off the soft buttons and the notification bar (assuming you're not completely full screen), then the only option for having the preview fill the screen is to cut some of it off.
You should be able to modify the TextureView's transform matrix to remove the distortion, by looking at the view's width and height and the selected preview width and height, but this will necessarily cut some of it off.
AutoFitTextureView in layout should use wrap_content
<AutoFitTextureView
android:id="#+id/texture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
/**
* A [TextureView] that can be adjusted to a specified aspect ratio.
*/
class AutoFitTextureView : TextureView {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : super(context, attrs, defStyle)
private var ratioWidth = 0
private var ratioHeight = 0
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* #param width Relative horizontal size
* #param height Relative vertical size
*/
fun setAspectRatio(width: Int, height: Int) {
require(!(width < 0 || height < 0)) { "Size cannot be negative." }
ratioWidth = width
ratioHeight = height
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
if (ratioWidth == 0 || ratioHeight == 0) {
setMeasuredDimension(width, height)
} else {
if (width < height * ratioWidth / ratioHeight) {
setMeasuredDimension(width, width * ratioHeight / ratioWidth)
} else {
setMeasuredDimension(height * ratioWidth / ratioHeight, height)
}
}
}
}
Related
Java and C# (Xamarin.Android) accepted
I have a MediaRecorder that streams the cameraframes onto a TextureView (basic camera2 approach)
the TextureView fills my screen.
I want the video to fill the screen but keep the aspect ratio.
I know I have to eventually cut of either left/right or top/bottom parts of the video, to keep the aspect ratio.
How can I make the video fit the TextureView and "zoom in" until the whole screen is filled? I don't want "black bars" that compromise for the ratio to be kept.
I guess this has to be accomplished through overriding the OnMeasure of the TextureView
C# extended TextureView
//Called by my MediaRecorder Implementation, when I set the video dimensions (mostly width = 1280, height = 720)
public void SetAspectRatio(int width, int height)
{
if (width == 0 || height == 0)
throw new ArgumentException("Size cannot be negative.");
mRatioWidth = width;
mRatioHeight = height;
RequestLayout();
}
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.GetSize(widthMeasureSpec);
int height = MeasureSpec.GetSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight)
{
SetMeasuredDimension(width, height);
}
else
{
if (width > (float)height * mRatioWidth / (float)mRatioHeight)
{
SetMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
}
else
{
SetMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
I have found the answer, thanks to this comment from this post.
C# Xamarin.Android:
I call the following method when my surface texture size changes (OnSurfaceSizeTextureChanged callback in SurfaceTextureListener)
public void ConfigureTransform(int viewWidth, int viewHeight)
{
int rotation = (int)ThisActivity.WindowManager.DefaultDisplay.Rotation;
var matrix = new Matrix();
var viewRect = new RectF(0, 0, viewWidth, viewHeight);
var bufferRect = new RectF(0, 0, previewSize.Height, previewSize.Width);
float centerX = viewRect.CenterX();
float centerY = viewRect.CenterY();
//Set the scale accordingly, to have the preview texture view fill the whole screen with cutting of the least amount of the preview as possible
//(but this way we keep the aspect ratio -> no preview distortion)
bool isLandscape = ((int)SurfaceOrientation.Rotation90 == rotation || (int)SurfaceOrientation.Rotation270 == rotation);
float scale;
if (isLandscape)
{
scale = System.Math.Max(
(float)viewWidth / previewSize.Height,
(float)viewWidth / previewSize.Width);
}
else
{
scale = System.Math.Max(
(float)viewHeight / previewSize.Height,
(float)viewHeight / previewSize.Width);
}
if ((int)SurfaceOrientation.Rotation90 == rotation || (int)SurfaceOrientation.Rotation270 == rotation || (int)SurfaceOrientation.Rotation180 == rotation)
{
bufferRect.Offset((centerX - bufferRect.CenterX()), (centerY - bufferRect.CenterY()));
matrix.SetRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.Fill);
matrix.PostScale(scale, scale, centerX, centerY);
//Small check if orientation is Reverse Portrait -> dont substract 2 from rotation, otherwise the preview is flipped by 180°
if ((int)SurfaceOrientation.Rotation180 == rotation)
matrix.PostRotate(90 * rotation, centerX, centerY);
else
matrix.PostRotate(90 * (rotation - 2), centerX, centerY);
}
TextureView.SetTransform(matrix);
}
I am working with the Google sample project, but I cannot seem to get the preview to work without stretching it.
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0)
{
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
I have tried physically changing the aspect ration on the AutoFitTextureView class, this makes it full screen, but causes it to stretch.
Has anyone figured out a successful implementation of this ?
you need to modify the setUpCameraOutputs method. Modify the following line
previously--->
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
modified--->
largest =getFullScreenPreview(map.getOutputSizes(ImageFormat.JPEG),width,height);
previously--->
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
modified---->
mPreviewSize = getFullScreenPreview(map.getOutputSizes(SurfaceTexture.class),
width, height);
and method for getting fullscreen preview is as follows-
private Size getFullScreenPreview(Size[] outputSizes, int width, int height) {
List<Size> outputSizeList = Arrays.asList(outputSizes);
outputSizeList = sortListInDescendingOrder(outputSizeList); //because in some phones available list is in ascending order
Size fullScreenSize = outputSizeList.get(0);
for (int i = 0; i < outputSizeList.size(); i++) {
int orginalWidth = outputSizeList.get(i).getWidth();
int orginalHeight = outputSizeList.get(i).getHeight();
float orginalRatio = (float) orginalWidth / (float) orginalHeight;
float requiredRatio;
if (width > height) {
requiredRatio = ((float) width / height); //for landscape mode
if ((outputSizeList.get(i).getWidth() > width && outputSizeList.get(i).getHeight() > height)) {
// because if we select preview size hire than device display resolution it may fail to create capture request
continue;
}
} else {
requiredRatio = 1 / ((float) width / height); //for portrait mode
if ((outputSizeList.get(i).getWidth() > height && outputSizeList.get(i).getHeight() > width)) {
// because if we select preview size hire than device display resolution it may fail to create capture request
continue;
}
}
if (orginalRatio == requiredRatio) {
fullScreenSize = outputSizeList.get(i);
break;
}
}
return fullScreenSize;
}
I'm using picasso library for load images. I find two ways for reduct images size. What is difference of reduce image size using resize
Picasso
.with(context)
.load(imageurl)
.resize(200, 100)
.onlyScaleDown() // the image will only be resized if it's bigger than 200x100 pixels.
.into(imageViewResizeScaleDown);
and using transform method like this one
public class BitmapTransform implements Transformation {
int maxWidth;
int maxHeight;
public BitmapTransform(int maxWidth, int maxHeight) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
#Override
public Bitmap transform(Bitmap source) {
int targetWidth, targetHeight;
double aspectRatio;
if (source.getWidth() > source.getHeight()) {
targetWidth = maxWidth;
aspectRatio = (double) source.getHeight() / (double) source.getWidth();
targetHeight = (int) (targetWidth * aspectRatio);
} else {
targetHeight = maxHeight;
aspectRatio = (double) source.getWidth() / (double) source.getHeight();
targetWidth = (int) (targetHeight * aspectRatio);
}
Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
if (result != source) {
source.recycle();
}
return result;
}
#Override
public String key() {
return maxWidth + "x" + maxHeight;
}
};
Picasso.with(context).load(imageurl)
.transform(new BitmapTransform (200, 100))
.into(imageViewResizeScaleDown);
Tanks for your help
The resize() is used literally for resizing the image while a Transformation is an interface for your image manipulation. e.g. Making the image Grayscale etc. Although you can use it also for resizing the image but the purpose isn't intended for that.
I'm using this code to crop an image:
Bitmap raw = ((BitmapDrawable)hWlp).getBitmap();
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager)
context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
wallpaper = Bitmap.createBitmap(raw, width/2, 0, width, height);
My source image (raw) is 800x960 and the target image is 800x480 (screen size). So I don't understand why I get this error:
java.lang.IllegalArgumentException: x + width must be <= bitmap.width()
if in my case x + width (480/2 + 480) is 720 and bitmap.width() is 960
You can use like that :
public static Bitmap cropAndScale (Bitmap source,int scale){
int factor = source.getHeight() <= source.getWidth() ? source.getHeight(): source.getWidth();
int longer = source.getHeight() >= source.getWidth() ? source.getHeight(): source.getWidth();
int x = source.getHeight() >= source.getWidth() ?0:(longer-factor)/2;
int y = source.getHeight() <= source.getWidth() ?0:(longer-factor)/2;
source = Bitmap.createBitmap(source, x, y, factor, factor);
source = Bitmap.createScaledBitmap(source, scale, scale, false);
return source;
}
This is example and you can change variables by displaymetrics
You can get this error only, if your app is in landscape orientation ;)
I think, in this case metrics.widthPixels can return 800px instead 480px.
I'm using the barcode-reader example from Google's Android Vision API.
The preview size doesn't seem to fill up the whole space available (I'm using a Nexus 4 and there is a white unused space to the right of preview, about 1/3 of the width).
I would like to be able to run this example on various devices and always have it fill up the whole space available.
So the bit I've been playing with is:
CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector).setFacing(CameraSource.CAMERA_FACING_BACK).setRequestedPreviewSize(?, ?).setRequestedFps(15.0f);
Any ideas?
Thanks!
just remove or comment below code from CameraSourcePreview class
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}
and use layoutHeight instead of childHeight of "CameraSourcePreview" class in this loop - for (int i = 0; i < getChildCount(); ++i){...}
if (mCameraSource != null)
{
Size size = mCameraSource.getPreviewSize();
if (size != null)
{
width = size.getWidth();
height = size.getHeight();
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode())
{
int tmp = width;
//noinspection SuspiciousNameCombination
width = height;
height = tmp;
}
final int layoutWidth = right - left;
final int layoutHeight = bottom - top;
// Computes height and width for potentially doing fit width.
int childWidth = layoutWidth;
int childHeight = (int) (((float) layoutWidth / (float) width) * height);
for (int i = 0; i < getChildCount(); ++i)
{
getChildAt(i).layout(0, 0, childWidth, layoutHeight);
}
try
{
startIfReady();
}
catch (SecurityException se)
{
Log.e(TAG, "Do not have permission to start the camera", se);
}
catch (IOException e)
{
Log.e(TAG, "Could not start camera source.", e);
}
}
There are two ways to make the camera image fill the entire screen.
Akesh Dubey's answer, which displays the whole image by stretching it to fit the layout's width and height. However, aspect ratio is not preserved.
My answer below, which crops the image to make it fit without sacrificing aspect ratio.
In order to crop-fit the image, all you have to do is change one > into a <. Find the below if-statement and change the condition like so:
if (childHeight < layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}