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();
}
}
Related
my capture picture output not same with my camera preview in landscape mode
before cpture
after capture
whats wrong ? and whats have i do. thanks
This is the AutoFitTextView class which I pulled from Google sample. You can take a look at here. It aims to show camera view and config the ratio base on the physical size of device.
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
// Some codes here...
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
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 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
There are 2 points in this class:
You can't ensure the ratio works properly in every device. However, we are able to choose optimized size which is already defined in this class.
This condition is wrong: if (width < height * mRatioWidth / mRatioHeight). It should be > because when width is bigger than height, we calculate and set measure dimension base on width (not height).
UPDATED
If you just want every device will work properly in a particular ratio, then set hard ratio for it (for instance: 4/3)
You can achieve that by replacing those lines of code:
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
-> previewSize = Size(4, 3)
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'm developing a camera2 app. Everything is working fine on Nexus 5 and Nexus 4, but on LG G2 preview for video is stretched (only for back camera, for front camera everything is fine). I'm setting SurfaceTexture the next way:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
float ratioSurface = width > height ? (float) width / height : (float) height / width;
float ratioPreview = (float) mRatioWidth / mRatioHeight;
int scaledHeight = height;
int scaledWidth = width;
if (ratioPreview > ratioSurface) {
scaledHeight = (int) (((float) mRatioWidth / mRatioHeight) * width);
} else if (ratioPreview < ratioSurface) {
scaledWidth = (int) (height / ((float) mRatioWidth / mRatioHeight));
}
setMeasuredDimension(scaledWidth, scaledHeight);
}
After setAspectRatio() I'm called setSurfaceTexture():
private void setSurfaceTexture(Size size) {
final Matrix matrix = new Matrix();
int width = resource.cameraTexture.getWidth();
int height = resource.cameraTexture.getHeight();
int rotation = controller.getRotation();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
matrix.postRotate(90 * (rotation - 2), width / 2, height / 2);
}
resource.mForegroundHandler.post(new Runnable() {
#Override
public void run() {
resource.cameraTexture.setTransform(matrix);
}
});
}
I was trying to use SurfaceView, but result is the same: when switches to video mode, preview is stretched. I tried many ways, but result is always the same.
Please, help me...
EDIT: Video is still stretched on LG G2 with CyanogenMod, but in phones with official Lollipop the issue was next: LEGACY devices not always support preview video 16:9, so I've changed video size to 4:3.
I am working on a video conferencing project. My video display is using surface view. Now during a video call there is a chance of aspect ratio change for the incoming frames. So i have tried the following code for it
public void surfaceResize() {
// WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point size = new Point();
int screenWidth = 0;
//Get the SurfaceView layout parameters
float aspectRatio = (float) recv_frame_width / recv_frame_height;
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
{
//Get the width of the screen
getWindowManager().getDefaultDisplay().getSize(size);
screenWidth = size.x;
//Set the width of the SurfaceView to the width of the screen
surflp.width = screenWidth;
//Set the height of the SurfaceView to match the aspect ratio of the video
//be sure to cast these as floats otherwise the calculation will likely be 0
surflp.height = (int) ((1 / aspectRatio) * (float)screenWidth);
//Commit the layout parameters
} else {
size.x = size.y = 0;
//Get the width of the screen
getWindowManager().getDefaultDisplay().getSize(size);
int screenHeight = size.y;
//Set the width of the SurfaceView to the width of the screen
surflp.height = screenHeight;
//Set the width of the SurfaceView to match the aspect ratio of the video
//be sure to cast these as floats otherwise the calculation will likely be 0
surflp.width = (int) ( aspectRatio * (float)screenHeight);
//Commit the layout parameters
// code to do for Portrait Mode
}
surflp.addRule(RelativeLayout.CENTER_HORIZONTAL);
surflp.addRule(RelativeLayout.CENTER_VERTICAL);
if(myVideoSurfaceView != null)
myVideoSurfaceView.setLayoutParams(surflp);
System.out.println("Surface resized*****************************************");
}
Everything is fine if I call this function at beginning of call.
My problem is when I call this function in between a call for changing aspect ratio it takes too much time to display the next frame. Sometimes the video gets stuck even.
I tried to destroy and recreate the surface with
myVideoSurface.setVisibility(VIEW.GONE);
But the surface is not getting created.
I am using Mediacodec for video decode I will get notified when there is a resolution change.
Is there something more I should do for resizing a surfaceView when already video is being played.
Thanks for help.........................
Hello try with below code:
private void setVideoSize() {
// // Get the dimensions of the video
int videoWidth = mediaPlayer.getVideoWidth();
int videoHeight = mediaPlayer.getVideoHeight();
float videoProportion = (float) videoWidth / (float) videoHeight;
// Get the width of the screen
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
float screenProportion = (float) screenWidth / (float) screenHeight;
// Get the SurfaceView layout parameters
android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
if (videoProportion > screenProportion) {
lp.width = screenWidth;
lp.height = (int) ((float) screenWidth / videoProportion);
} else {
lp.width = (int) (videoProportion * (float) screenHeight);
lp.height = screenHeight;
}
// Commit the layout parameters
surfaceView.setLayoutParams(lp);
}
This problem can be solved like this.