Android: how to Zoom using camera2 API - java

I am new to android development and i am creating app that deals with camera but i am having problem with the zoom in function. I am using camera2 code sample from google, i modified bit using solutions from here but still it is not working.
public float finger_spacing = 0;
public int zoom_level = 1;
.
.
.
public boolean onTouch(View v, MotionEvent event) {
try {
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
float maxzoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM))*10;
Rect m = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
int action = event.getAction();
float current_finger_spacing;
if (event.getPointerCount() > 1) {
// Multi touch logic
current_finger_spacing = getFingerSpacing(event);
if(finger_spacing != 0){
if(current_finger_spacing > finger_spacing && maxzoom > zoom_level){
zoom_level++;
}
else if (current_finger_spacing < finger_spacing && zoom_level > 1){
zoom_level--;
}
int minW = (int) (m.width() / maxzoom);
int minH = (int) (m.height() / maxzoom);
int difW = m.width() - minW;
int difH = m.height() - minH;
int cropW = difW /100 *(int)zoom_level;
int cropH = difH /100 *(int)zoom_level;
cropW -= cropW & 3;
cropH -= cropH & 3;
Rect zoom = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
finger_spacing = current_finger_spacing;
}
else{
if (action == MotionEvent.ACTION_UP) {
//single touch logic
}
}
try {
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,
null);
}
catch (CameraAccessException e) {
e.printStackTrace();
}
catch (NullPointerException ex)
{
ex.printStackTrace();
}
}
catch (CameraAccessException e)
{
throw new RuntimeException("can not access camera.", e);
}
return true;
}
//Determine the space between the first two fingers
#SuppressWarnings("deprecation")
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
I have added this in the end of Camera2BasicFragment class but it changes nothing.

Related

Images are not resizing after dynamically adding Imageviews in relative layout

I am trying to create photo collage app. Here I am dynamically adding imageviews in relative layout by passing array of positions for creating grid. Grids are successfully created. But when I add images, the images aren't scaled properly or not fitting to entire imageview's area. Also when I add touch listener on images, images are moviing outside the area of imageview.Please help me in this.Thanks in advance Here is my code
public void drawGrids() {
Resources res = getResources();
int Rid = c.id;
TypedArray ta = res.obtainTypedArray(Rid);
int n = ta.length();
String[][] array = new String[n][];
for (int i = 0; i < n; ++i) {
int id = ta.getResourceId(i, 0);
if (id > 0) {
array[i] = res.getStringArray(id);
Log.e(" array", "" + i + " " + Arrays.toString(array[i]));
String[] values = Arrays.toString(array[i]).replaceAll("[\\[\\]\\s]", "").split(","); // extracting each element from array
final int position = i;
limit = position+1;
float x = Float.parseFloat(values[0]);
float y = Float.parseFloat(values[1]);
float w = Float.parseFloat(values[2]);
float h = Float.parseFloat(values[3]);
Log.e(" x:", "" + x);
Log.e(" y:", "" + y);
Log.e(" w:", "" + w);
Log.e(" h:", "" + h);
img1 = new ImageView(getActivity());
img1.setImageResource(R.drawable.button_background);
params = new RelativeLayout.LayoutParams((int) ((Screen_width * w) - padding), (int) ((Screen_height * h) - padding));
// x= x* Screen_width
// y= y* Screen_height
params.leftMargin = (int) ((Screen_width * x) + padding);
params.topMargin = (int) ((Screen_height * y) + padding);
params.rightMargin = padding;
params.bottomMargin = padding;
Log.e(" px(x):", "" + (int) (Screen_width * x));
Log.e(" px(y):", "" + (int) (Screen_height * y));
Log.e(" px(w):", "" + (int) (Screen_width * w));
Log.e("px(h)", "" + (int) (Screen_height * h));
if(!mSelectedImages.isEmpty()) {
onPickedSuccessfully(mSelectedImages);
}
else {
img1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.e("clicked", "" + position);
onClickPickImageMultipleWithLimit(img1);
}
});
}
IMGS.add(img1); // arraylist of imageview
root1.addView(img1, params);
createPreview();
} else {
// something wrong with the XML
}
}
ta.recycle();
}
public void onPickedSuccessfully(ArrayList<ImageEntry> images) { // selected images path are to be fetched here
mSelectedImages = images;
handler.post(new Runnable() {
#Override
public void run() {
for (j = 0; j <IMGS.size(); j++) {
final ImageView child=IMGS.get(j);
child.onTouchListener(new MultiTouchListener);// multitouch listener for zooming and scrolling picked image
Log.e("w n h", "" + child.getWidth() + " " + child.getHeight());
int areaheight = child.getWidth();
int areawidth = child.getHeight();
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
oldBitmap = BitmapFactory.decodeFile(String.valueOf(mSelectedImages.get(j)), bmOptions);//decodeFile(String.valueOf(mSelectedImages.get(j)));
int height = oldBitmap.getHeight(), width = oldBitmap.getWidth();
Log.e(" b width and height ", "" + oldBitmap.getWidth() + " " + oldBitmap.getHeight());
Log.e(" area width and height ", "" + areawidth + " " + areaheight);
Bitmap scaledBitmap;
if (areaheight > areawidth) {
// portrait
float ratio = (float) height / areaheight;
height = areaheight;
width = (int) (width / ratio);
scaledBitmap = Bitmap.createScaledBitmap(oldBitmap, width, height, true);
Log.e("porait scaled w ht ", "" + scaledBitmap.getWidth() + " " + scaledBitmap.getHeight());
} else if (areawidth > areaheight) {
//landscape
float ratio = (float) width / areawidth;
width = areawidth;
height = (int) (height / ratio);
scaledBitmap = Bitmap.createScaledBitmap(oldBitmap, width, height, true);
Log.e("landscape scaled w ht ", "" + scaledBitmap.getWidth() + " " + scaledBitmap.getHeight());
} else {
// square
height = areaheight;
width = areawidth;
scaledBitmap = Bitmap.createScaledBitmap(oldBitmap, width, height, true);
Log.e("square scaled w ht ", "" + scaledBitmap.getWidth() + " " + scaledBitmap.getHeight());
}
child.setImageBitmap(scaledBitmap);
}
}
});
In your case you need to add line:
img1.setScaleType(ImageView.ScaleType.CENTER_CROP); // or CENTER_INSIDE, or FIT_CENTER, or for example FIT_XY
after:
img1.setImageResource(R.drawable.button_background);
Thus, you will provide scaling for all images that will be set into your ImageView. You can read more about scaleType here and here
Rather than use ImageView and setOnTouchListener, you may try this custom view.
public class ScaleImageView extends ImageView {
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.0f;
private float x = 0;
private float y = 0;
private float tx = 0;
private float ty = 0;
private float dx = 0;
private float dy = 0;
private int scrollLimitX = 0;
private int scrollLimitY = 0;
private boolean justScaled = false;
public ScaleImageView(Context context) {
super(context);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
public ScaleImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent ev) {
if(ev.getPointerCount() == 1){
switch(ev.getAction()){
case MotionEvent.ACTION_DOWN:
tx = ev.getX();
ty = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if(!justScaled){
dx = tx - ev.getX();
dy = ty - ev.getY();
tx -= dx;
ty -= dy;
int scrollX = (int)(this.getScrollX()/mScaleFactor);
int scrollY = (int)(this.getScrollY()/mScaleFactor);
if(Math.abs(scrollX+dx) > scrollLimitX) dx = 0;
if(Math.abs(scrollY+dy) > scrollLimitY) dy = 0;
this.scrollBy((int)(dx*mScaleFactor), (int)(dy*mScaleFactor));
}
break;
case MotionEvent.ACTION_UP:
justScaled = false;
break;
}
}else if(ev.getPointerCount() == 2){
justScaled = true;
mScaleDetector.onTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
#Override
public void onDraw(Canvas canvas) {
x = this.getWidth()/2;
y = this.getHeight()/2;
canvas.scale(mScaleFactor, mScaleFactor, x ,y);
int scrollX = (int)(this.getScrollX()/mScaleFactor);
int scrollY = (int)(this.getScrollY()/mScaleFactor);
if(Math.abs(scrollX) > scrollLimitX) dx = scrollLimitX - scrollX; else dx = 0;
if(Math.abs(scrollY) > scrollLimitY) dy = scrollLimitY - scrollY; else dy = 0;
this.scrollBy((int)(dx*mScaleFactor), (int)(dy*mScaleFactor));
super.onDraw(canvas);
}
public void setScaleFactor(float mfactor){
this.mScaleFactor = mfactor;
}
public float getScaleFactor(){
return this.mScaleFactor;
}
public void setScrollLimit(int x, int y){
this.scrollLimitX = x/2;
this.scrollLimitY = y/2;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 5.0f));
ScaleImageView.this.invalidate();
return true;
}
}
}
To limit the image that at least one corner is inside the view:
// zoomImg is the Bitmap.
// mZoomImage is the ScaleImageView.
float scaleFactor = Math.min((float)(mZoomImage.getWidth())/zoomImg.getWidth(), (float)(mZoomImage.getHeight())/zoomImg.getHeight());
mZoomImage.setScrollLimit((int)(zoomImg.getWidth()*scaleFactor), (int)(zoomImg.getHeight()*scaleFactor));
Hope this is helpful!

Touch Input Problems on Immersive Mode on Android

I have a sample game and enabled immersive mode but the right Buttons on the end doesn't work well. When I enable immersive mode the Touch Input works as it would be a screen with the software buttons though they don't be visible at all.
Every Touch Input on the right corner where the software buttons would be if i turn the immersive mode off will not work.
I don't know what to do to fix this. I attach the source code here:
Thats my Main game Class:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Immersive Mode
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
int frameBufferWidth = isPortrait ? 480: 800;
int frameBufferHeight = isPortrait ? 800: 480;
Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth,
frameBufferHeight, Config.RGB_565);
float scaleX = (float) frameBufferWidth
/ getWindowManager().getDefaultDisplay().getWidth();
float scaleY = (float) frameBufferHeight
/ getWindowManager().getDefaultDisplay().getHeight();
renderView = new AndroidFastRenderView(this, frameBuffer);
graphics = new AndroidGraphics(getAssets(), frameBuffer);
fileIO = new AndroidFileIO(this);
audio = new AndroidAudio(this);
input = new AndroidInput(this, renderView, scaleX, scaleY); <- INPUT
screen = getInitScreen();
setContentView(renderView);
The Android Input class will initialize this Class
package com.kilobolt.framework.implementation;
import java.util.ArrayList;
import java.util.List;
import android.view.MotionEvent;
import android.view.View;
import com.kilobolt.framework.Pool;
import com.kilobolt.framework.Input.TouchEvent;
import com.kilobolt.framework.Pool.PoolObjectFactory;
public class MultiTouchHandler implements TouchHandler {
private static final int MAX_TOUCHPOINTS = 10;
boolean[] isTouched = new boolean[MAX_TOUCHPOINTS];
int[] touchX = new int[MAX_TOUCHPOINTS];
int[] touchY = new int[MAX_TOUCHPOINTS];
int[] id = new int[MAX_TOUCHPOINTS];
Pool<TouchEvent> touchEventPool;
List<TouchEvent> touchEvents = new ArrayList<TouchEvent>();
List<TouchEvent> touchEventsBuffer = new ArrayList<TouchEvent>();
float scaleX;
float scaleY;
public MultiTouchHandler(View view, float scaleX, float scaleY) {
PoolObjectFactory<TouchEvent> factory = new PoolObjectFactory<TouchEvent>() {
#Override
public TouchEvent createObject() {
return new TouchEvent();
}
};
touchEventPool = new Pool<TouchEvent>(factory, 100);
view.setOnTouchListener(this);
this.scaleX = scaleX;
this.scaleY = scaleY;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
synchronized (this) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
int pointerCount = event.getPointerCount();
TouchEvent touchEvent;
for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
if (i >= pointerCount) {
isTouched[i] = false;
id[i] = -1;
continue;
}
int pointerId = event.getPointerId(i);
if (event.getAction() != MotionEvent.ACTION_MOVE && i != pointerIndex) {
// if it's an up/down/cancel/out event, mask the id to see if we should process it for this touch
// point
continue;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_DOWN;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = true;
id[i] = pointerId;
touchEventsBuffer.add(touchEvent);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_UP;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = false;
id[i] = -1;
touchEventsBuffer.add(touchEvent);
break;
case MotionEvent.ACTION_MOVE:
touchEvent = touchEventPool.newObject();
touchEvent.type = TouchEvent.TOUCH_DRAGGED;
touchEvent.pointer = pointerId;
touchEvent.x = touchX[i] = (int) (event.getX(i) * scaleX);
touchEvent.y = touchY[i] = (int) (event.getY(i) * scaleY);
isTouched[i] = true;
id[i] = pointerId;
touchEventsBuffer.add(touchEvent);
break;
}
}
return true;
}
}
#Override
public boolean isTouchDown(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return false;
else
return isTouched[index];
}
}
#Override
public int getTouchX(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return 0;
else
return touchX[index];
}
}
#Override
public int getTouchY(int pointer) {
synchronized (this) {
int index = getIndex(pointer);
if (index < 0 || index >= MAX_TOUCHPOINTS)
return 0;
else
return touchY[index];
}
}
#Override
public List<TouchEvent> getTouchEvents() {
synchronized (this) {
int len = touchEvents.size();
for (int i = 0; i < len; i++)
touchEventPool.free(touchEvents.get(i));
touchEvents.clear();
touchEvents.addAll(touchEventsBuffer);
touchEventsBuffer.clear();
return touchEvents;
}
}
// returns the index for a given pointerId or -1 if no index.
private int getIndex(int pointerId) {
for (int i = 0; i < MAX_TOUCHPOINTS; i++) {
if (id[i] == pointerId) {
return i;
}
}
return -1;
}
}

Zoom Camera2 Preview using TextureView

i have a Problem with my Preview Zoom for the Camera2 API. I am using a TextureView.
I want to zoom only the preview Stream that was showed in the TextureView.
I want to zoom the Area where i use the Zoom Gesture.
I use the SimpleOnScaleGestureListener!
I added following Code. The zoomingFactor and the x and y Position are right.
private void updateTextureViewSize(float xPosi,float yPosi, float scale){
float scaleX = 1.0f;
float scaleY = 1.0f;
float mVideoWidth = mCamcontrol.getmPreviewSize().getWidth();
float mVideoHeight = mCamcontrol.getmPreviewSize().getHeight();
int rotation = getWindowManager().getDefaultDisplay().getRotation();
RectF viewRect = new RectF(0, 0, 1440, 2560);
RectF bufferRect = new RectF(0, 0, mVideoHeight, mVideoWidth);
bufferRect.offset(xPosi - bufferRect.centerX(), yPosi - bufferRect.centerY());
//16:9 faktor
scaleX = ((mScale * scale) / 9f) * 16f;
scaleY = ((mScale * scale) / 16f) * 9f;
Matrix matrix = new Matrix();
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.FILL);
scalefactorView.setText(String.valueOf(xPosi) + " " + String.valueOf(yPosi));
matrix.setScale(scaleY, scaleX, xPosi, yPosi);
matrix.postRotate(90 * (rotation - 2), xPosi, yPosi);
mTextureView.setTransform(matrix);
}
Zooming is Right, but not the Position where i Zoom. For Example! When i zoom on the position right/middle i see only the left/top rectangle of the Stream.
I added the following pictures to unterstand the problem.
Android Camera2 api : Pinch Zoom In/Out
Use this sample code for Camera2Basic from google developers. https://github.com/googlesamples/android-Camera2Basic
Now declare two class variables –
public float finger_spacing = 0;
public int zoom_level = 1;
and update the given onTouch() method.
public boolean onTouch(View v, MotionEvent event) {
try {
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
float maxzoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM))*10;
Rect m = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
int action = event.getAction();
float current_finger_spacing;
if (event.getPointerCount() > 1) {
// Multi touch logic
current_finger_spacing = getFingerSpacing(event);
if(finger_spacing != 0){
if(current_finger_spacing > finger_spacing && maxzoom > zoom_level){
zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1){
zoom_level--;
}
int minW = (int) (m.width() / maxzoom);
int minH = (int) (m.height() / maxzoom);
int difW = m.width() - minW;
int difH = m.height() - minH;
int cropW = difW /100 *(int)zoom_level;
int cropH = difH /100 *(int)zoom_level;
cropW -= cropW & 3;
cropH -= cropH & 3;
Rect zoom = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
finger_spacing = current_finger_spacing;
} else{
if (action == MotionEvent.ACTION_UP) {
//single touch logic
}
}
try {
mCaptureSession
.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException ex) {
ex.printStackTrace();
}
} catch (CameraAccessException e) {
throw new RuntimeException("can not access camera.", e);
}
return true;
}
//Determine the space between the first two fingers
#SuppressWarnings("deprecation")
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Thanks to #arin 's answer, I made an improved version.
His code is basically working, but there are 2 problems:
1) Readability - actually I don't know what is going on calculating the Rect zoom
2) In my Android 7.1.1 device, the preview will freeze if the zoom is big to a certain extent. Since I solved this problem with the code below, I am pretty sure it is because the original code allowed over-zooming beyond camera's maximum zoom ratio.
(In fact, I don't know why he needs to apply *10 on the ratio returned by CameraCharacteristics)
Below are my codes: (I do this all inside my custom TextureView, which also stores my Camera2 objects and logics):
Related Member variables:
protected CameraCharacteristics cameraCharacteristics;
protected CameraCaptureSession captureSession;
protected CaptureRequest.Builder previewRequestBuilder;
//Zooming
protected float fingerSpacing = 0;
protected float zoomLevel = 1f;
protected float maximumZoomLevel;
protected Rect zoom;
Right after you get CameraCharacteristics from CameraManager, probably in some initial setup:
maximumZoomLevel = cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
override onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
try {
Rect rect = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (rect == null) return false;
float currentFingerSpacing;
if (event.getPointerCount() == 2) { //Multi touch.
currentFingerSpacing = getFingerSpacing(event);
float delta = 0.05f; //Control this value to control the zooming sensibility
if (fingerSpacing != 0) {
if (currentFingerSpacing > fingerSpacing) { //Don't over zoom-in
if ((maximumZoomLevel - zoomLevel) <= delta) {
delta = maximumZoomLevel - zoomLevel;
}
zoomLevel = zoomLevel + delta;
} else if (currentFingerSpacing < fingerSpacing){ //Don't over zoom-out
if ((zoomLevel - delta) < 1f) {
delta = zoomLevel - 1f;
}
zoomLevel = zoomLevel - delta;
}
float ratio = (float) 1 / zoomLevel; //This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect
//croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped
int croppedWidth = rect.width() - Math.round((float)rect.width() * ratio);
int croppedHeight = rect.height() - Math.round((float)rect.height() * ratio);
//Finally, zoom represents the zoomed visible area
zoom = new Rect(croppedWidth/2, croppedHeight/2,
rect.width() - croppedWidth/2, rect.height() - croppedHeight/2);
previewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
fingerSpacing = currentFingerSpacing;
} else { //Single touch point, needs to return true in order to detect one more touch point
return true;
}
captureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallback, null);
return true;
} catch (final Exception e) {
//Error handling up to you
return true;
}
}
And the getFingerSpacing method:
private float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
Finally don't forget to set the crop region when you actually take the photo. My code is base on this Camera2Basic, I do this inside the captureStillPicture() method:
//Zoom
if (zoom != null) {
captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
}
#arin Answer is working thank #arin just one thing zoom sensitivity too high.
To control this i make some changes in might be useful to you.
Change zoom_level data type to double
public int zoom_level = 1; to public double zoom_level = 1;
Then increase or decrease zoom_level with low value i use 0.4
if (current_finger_spacing > finger_spacing && maxzoom > zoom_level) {
zoom_level = zoom_level + .4;
//zoom_level++;
} else if (current_finger_spacing < finger_spacing && zoom_level > 1) {
zoom_level = zoom_level - .4;
//zoom_level--;
}
Here is a Pan and Zoom object from Camera2 that I made to work using the OnScaleGestureListener and SimpleOnGestureListener-onScroll outputs. This will only work as expected if you have a camera with support level > LEGACY, as LEGACY only supports crop to center.
Two caveats:
One is that this is currently NOT set up to output to JPEG output, as rectangles for JPEG outputs must have dimensions which are multiple of 16 (See why here). The second is that I've locked my screen to landscape mode, and my camera is locked to landscape as well, but it should be possible to deal with screen rotations after a few tweaks.
You'll need to pass in the screen dimensions
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) mView.getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
the Maximum Camera Digital Zoom
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
float maxZoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
} catch (CameraAccessException e) {
e.printStackTrace();
}
the Camera Sensor's Active Array Size
try {
CameraManager manager = (CameraManager) mView.getContext().getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraID);
Rect rectInit = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
} catch (CameraAccessException e) {
e.printStackTrace();
}
Here is my object initialization
mScaler = new CamScaler(maxZoom, rectInit.width(), rectInit.height(), displayMetrics.heightPixels, displayMetrics.widthPixels);
the CamScaler class
public class CamScaler {
private final float ZOOM_MIN = 1.0f;
private final int X_MIN = 0;
private final int Y_MIN = 0;
private int displayWidth;
private int displayHeight;
private Rect current_rect;
private int xCenter;
private int yCenter;
private int xWidth;
private int yHeight;
private int xMax;
private int yMax;
private float zoomMax;
private float zoomCurrent;
public CamScaler(float zoomMax, int xMax, int yMax, int displayHeight, int displayWidth) {
this.xMax = xMax;
this.yMax = yMax;
this.zoomMax = zoomMax;
current_rect = new Rect(X_MIN,Y_MIN, xMax, yMax); //(0,0,xMax,yMax) as the starting rectangle
zoomCurrent = ZOOM_MIN;
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX();
yCenter = current_rect.centerY();
this.displayHeight = displayHeight;
this.displayWidth = displayWidth;
}
public void pan(float distanceX, float distanceY){
//calculate the shift in the we want to take on the camera sensor with respect to the distance moved on the screen
int xShift = Math.round((distanceX/displayWidth)*xWidth); //scales down to a percentage of the current view width->converts to a pixel shift
int yShift = Math.round((distanceY/displayHeight)*yHeight); //scales down to a percentage of the current view height->converts to a pixel shift
//check if the shift will push us pass our maximums, this should account for both negative and positive values of xShift and yShift correctly
if ( !((xCenter + Math.round(xWidth/2.0) + xShift < xMax) && (xCenter - Math.round(xWidth/2.0) + xShift > 0))) { //if not within xBounds, set xShift to 0
xShift = 0;
}
if ( !((yCenter + Math.round(yHeight/2) + yShift < yMax) && (yCenter - Math.round(yHeight/2.0) + yShift > 0))) { //if not within yBounds, set yShift to 0
yShift = 0;
}
Log.d("Scaler", "pan: xShift" + xShift + " yShift " + yShift);
current_rect.offset(xShift,yShift);
Log.d("Scaler", "pan: current_rect" + current_rect.toString());
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
}
public void zoom(float scale_change){
if ( (zoomCurrent*scale_change < zoomMax) && (zoomCurrent*scale_change > ZOOM_MIN) ){ //if we are within zoom bounds
zoomCurrent *= scale_change; //update the zoom factor
int newWidthHalf = (int)Math.floor(xMax/zoomCurrent/2.0);
int newHeightHalf = (int)Math.floor(yMax/zoomCurrent/2.0);
int xTempCenter = xCenter;
int yTempCenter = yCenter;
//if at edge we need to shift and scale
if (xCenter + newWidthHalf > xMax) { //if at right edge
xTempCenter = xMax - newWidthHalf; //shift center to the left
} else if (xCenter - newWidthHalf < 0) { //if at left edge
xTempCenter = newWidthHalf; //shift center to the right
}
if (yCenter + newHeightHalf > yMax) { //if at bottom
yTempCenter = yMax - newHeightHalf; //shift center up
} else if (yCenter - newHeightHalf < 0) { //if at top
yTempCenter = newHeightHalf; //shift center down
}
Log.d("Scaler", "zoom: " + zoomCurrent);
Log.d(TAG, "current center(x,y) " + xTempCenter + " " + yTempCenter + "current halfwidths(x,y) " + newWidthHalf + " " + newHeightHalf);
current_rect.set(xTempCenter - newWidthHalf, yTempCenter - newHeightHalf,xTempCenter + newWidthHalf, yTempCenter + newHeightHalf);
Log.d("Scaler", "zoom: current_rect" + current_rect.toString());
xWidth = current_rect.width();
yHeight = current_rect.height();
xCenter = current_rect.centerX(); //update center
yCenter = current_rect.centerY(); //update center
} //if not in digital zoom bounds, do nothing
}
public Rect getCurrentView() {
return current_rect;
}
}
And how to use it
public void pan(float distanceX, float distanceY){
if (mScaler != null) {
synchronized (mScaler) {
mScaler.pan(distanceX, distanceY);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
public void zoom(float scale_factor) {
if (mScaler!= null) {
synchronized (mScaler) {
mScaler.zoom(scale_factor);
try {
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mScaler.getCurrentView());
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
}
}
The inputs to these functions are directly passed through from the gesture listeners
I hope this helps someone!
In addition to arin answer,Need to add captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
To captureStillPicture() method to let zoom take effect on capture

Why is my byte[] array returning zero instead of the position?

I made some foolish mistakes with my last post, but I'm back and I feel I have a better formatted question.
I am calling a function called getData() which is a byte[] return type and I am sending it through packets to another program. On the program side I receive "98 0 0 101" which tells me that something is wrong with my bDrive.getX() and bDrive.getY() functions.
Those functions are derived from a Joystick class that I made which displays a View and draws a joystick.
I have tried keeping everything in my Mybot Activity besides the socket work.
public class Mybot extends Activity {
Thread arduino;
ArduinoConnection socketThread;
Joystick bDrive;
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(R.layout.activity_Mybot);
socketThread = new ArduinoConnection(Mybot.this);
arduino = new Thread( socketThread);
arduino.start();
}
public byte[] getData() {
bDrive = (Joystick)findViewById(R.id.bDrive); // I think this is the culprit
byte[] arrayOfData = new byte[4];
arrayOfData[0] = 98;
arrayOfData[1] = (byte) bDrive.getX(); // Returns 0
arrayOfData[2] = (byte) bDrive.getY(); // Returns 0
arrayOfData[3] = 101;
return arrayOfData;
}
}
I feel the error is within the findViewById() portion of my getData() function. However I handled the onTouch within my Joystick class so I don't really understand why this would be an issue.
public class ArduinoConnection implements Runnable {
boolean sending = true;
public static DatagramSocket socket;
Mybot host;
public ArduinoConnection(Mybot mini) {
this.host = mini;
}
public void run() {
try {
socket = new DatagramSocket();
InetAddress localInetAddress = InetAddress.getByName("192.168.10.104");
socket.connect(localInetAddress, 61557);
running = true;
while (sending) {
byte[] arrayOfByte = this.host.getData(); // Returning 98 0 0 101
DatagramPacket localDatagramPacket = new DatagramPacket(arrayOfByte, arrayOfByte.length);
try {
socket.send(localDatagramPacket);
} catch (IOException localIOException) {
}
sending = true;
}
socket.close();
} catch (UnknownHostException localUnknownHostException) {
sending = false;
} catch (SocketException e) {
e.printStackTrace();
sending = false;
}
}
}
The Arduino code is straight forward.
The joystick class:
public class Joystick extends View
{
private Paint background;
private String label = null;
private Paint labelPaint;
private boolean liftReset = true;
private Paint line;
private boolean locked = false;
private float normX;
private float normY;
private int posX;
private int posY;
private Paint thumb;
private int thumbRadius;
public Joystick(Context paramContext)
{
super(paramContext);
}
public Joystick(Context paramContext, AttributeSet paramAttributeSet)
{
super(paramContext, paramAttributeSet);
setupJoystick(paramContext, paramAttributeSet);
}
public Joystick(Context paramContext, AttributeSet paramAttributeSet, int paramInt)
{
super(paramContext, paramAttributeSet, paramInt);
setupJoystick(paramContext, paramAttributeSet);
}
private int min(int paramInt1, int paramInt2)
{
if (paramInt1 <= paramInt2) {
return paramInt1;
}
return paramInt2;
}
private void updateNormPos()
{
this.normX = (-(this.posX - getWidth() / 2) / (getWidth() / 2.0F - this.thumbRadius));
this.normY = (-(this.posY - getHeight() / 2) / (getHeight() / 2.0F - this.thumbRadius));
}
public float getX()
{
return this.normX;
}
public float getY()
{
return this.normY;
}
protected Path octagon(int paramInt1, int paramInt2, int paramInt3)
{
Path localPath = new Path();
localPath.moveTo(paramInt2, paramInt3 + paramInt1);
localPath.lineTo(paramInt2 + 0.866F * paramInt1, paramInt3 + paramInt1 / 2);
localPath.lineTo(paramInt2 + 0.866F * paramInt1, paramInt3 - paramInt1 / 2);
localPath.lineTo(paramInt2, paramInt3 - paramInt1);
localPath.lineTo(paramInt2 - 0.866F * paramInt1, paramInt3 - paramInt1 / 2);
localPath.lineTo(paramInt2 - 0.866F * paramInt1, paramInt3 + paramInt1 / 2);
return localPath;
}
protected void onDraw(Canvas paramCanvas)
{
if ((this.posX == -1) && (this.posY == -1))
{
this.posX = (getWidth() / 2);
this.posY = (getHeight() / 2);
}
paramCanvas.drawCircle(getWidth() /2 , getHeight() /2 , 125, this.background);
if (this.label != null) {
paramCanvas.drawText(this.label, getWidth() / 2, 40.0F, this.labelPaint);
}
paramCanvas.drawLine(getWidth() / 2, getHeight() / 2, this.posX, this.posY, this.line);
paramCanvas.drawPath(octagon(this.thumbRadius, this.posX, this.posY), this.thumb);
}
public boolean onTouchEvent(MotionEvent paramMotionEvent) {
if (this.locked == true) {
return false;
}
if (paramMotionEvent.getAction() == 1 && this.liftReset) {
this.posX = (getWidth() / 2);
this.posY = (getHeight() / 2);
}else {
for (this.posY = (getHeight() / 2); ; this.posY = min(this.posY, getHeight() - this.thumbRadius)) {
invalidate();
updateNormPos();
double dx = ((int) paramMotionEvent.getX() - getWidth() / 2);
double dy = ((int) paramMotionEvent.getY() - getHeight() / 2);
double len = Math.hypot(dx, dy);
if (len > 100) {
dx = dx * 100 / len;
dy = dy * 100 / len;
}
this.posX = (int) dx + getWidth() / 2;
this.posY = (int) dy + getHeight() / 2;
return true;
}
}
return true;
}
protected void setupJoystick(Context paramContext, AttributeSet paramAttributeSet)
{
int i = 2;
int j = -16776961;
int k = -16711936;
int m = -65536;
this.shadowRadius = 2;
this.thumbRadius = 23;
TypedArray localTypedArray = paramContext.obtainStyledAttributes(paramAttributeSet, R.styleable.Joystick, 0, 0);
try
{
this.thumbRadius = localTypedArray.getDimensionPixelSize(0, 23);
this.shadowRadius = localTypedArray.getDimensionPixelSize(1, 2);
i = localTypedArray.getDimensionPixelSize(2, 2);
m = localTypedArray.getColor(3, -16776961);
k = localTypedArray.getColor(4, -16777216);
j = localTypedArray.getColor(5, -7829368);
this.label = localTypedArray.getString(6);
this.liftReset = localTypedArray.getBoolean(8, true);
}
catch (Exception localException)
{
for (;;)
{
Log.e("MYAPP", "exception: " + localException.getMessage());
Log.e("MYAPP", "exception: " + localException.toString());
localTypedArray.recycle();
}
}
finally
{
localTypedArray.recycle();
}
// Color in everything
this.background = new Paint();
this.background.setColor(k);
this.background.setStyle(Paint.Style.FILL_AND_STROKE);
this.thumb = new Paint();
this.thumb.setAntiAlias(true);
this.thumb.setColor(m);
this.thumb.setStyle(Paint.Style.FILL);
this.line = new Paint();
this.line.setAntiAlias(true);
this.line.setColor(j);
this.line.setStrokeWidth(i);
this.line.setStyle(Paint.Style.STROKE);
this.labelPaint = new Paint();
this.labelPaint.setAntiAlias(true);
this.labelPaint.setColor(-1);
this.labelPaint.setStyle(Paint.Style.FILL);
this.labelPaint.setTextAlign(Paint.Align.CENTER);
this.labelPaint.setTextSize(30.0F);
this.labelPaint.setAlpha(128);
this.posX = -1;
this.posY = -1;
}
}
Again, this isn't a Null Pointer Exception, it doesn't throw any errors. I probably made a tiny mistake somewhere, but I just don't understand why I am getting 98 0 0 101 instead of 98 (X coordinate of joystick) (Y coordinate of joystick) 101.
Any help would be appreciated!

Android Canvas Check if Point is on Line

I have a Custom View which draws lines an saves them to an ArrayList. What I want to be able to do is check if a specified point is on a line inside the Arraylist and return the line. I have been able to do this when the line is straight by using if (l.startX == l.stopX && l.startY < l.stopY but this doesn't work if the line is on an angle. I would also like to do this with drawText and drawCircle. Is there some simple way of doing this that I have missed?
DrawView.java
class Line {
float startX, startY, stopX, stopY;
public Line(float startX, float startY, float stopX, float stopY) {
this.startX = startX;
this.startY = startY;
this.stopX = stopX;
this.stopY = stopY;
}
public Line(float startX, float startY) { // for convenience
this(startX, startY, startX, startY);
}
}
public class DrawView extends View {
Paint paint = new Paint();
ArrayList<Line> lines = new ArrayList<Line>();
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(6f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
}
#Override
protected void onDraw(Canvas canvas) {
for (Line l : lines) {
canvas.drawLine(l.startX, l.startY, l.stopX, l.stopY, paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
lines.add(new Line(event.getX(), event.getY()));
return true;
}
else if ((event.getAction() == MotionEvent.ACTION_MOVE ||
event.getAction() == MotionEvent.ACTION_UP) &&
lines.size() > 0) {
Line current = lines.get(lines.size() - 1);
current.stopX = event.getX();
current.stopY = event.getY();
Invalidate();
return true;
}
else {
return false;
}
}
}
I don't know if there is a canvas library for this but manually you can:
go through your list of lines
form equations for each line in let's say slope-intercept form y = mx + b
plugin in the event.X() and event.Y() into the above equation as x and y for each line
if the 2 sides are equal then your touch event is on that line
After many hours of trying I managed to come up with a an algorithm to check if a point is on a given line using a mixture of slope-intersect formula and if (x = startX && y > startY && y < stopY) Below is the code, it returns true if point is on line otherwise false, hopefully this can save someone time!
public boolean checkPosition(float x, float y, float sx, float sy, float ex, float ey) {
float mX = ex - sx;
float mY = ey - sy;
float smX = sx - ex;
float smY = sy - ey;
float pmX = mX;
float pmY = mY;
float psmX = smX;
float psmY = smY;
float yY = ey - y;
float xX = ex - x;
float sX = sx - x;
float sY = sy - y;
float m = mY / mX;
float b = sy - (m * sx);
if (mX < 0) {
pmX = mX * - 1;
}
if (mY < 0) {
pmY = mY * - 1;
}
if (smX < 0) {
psmX = smX * - 1;
}
if (smY < 0) {
psmY = smY * - 1;
}
if (yY < 0) {
yY = yY * - 1;
}
if (xX < 0) {
xX = xX * - 1;
}
if (sX < 0) {
sX = sX * - 1;
}
if (sY < 0) {
sY = sY * - 1;
}
if (sy == ey && y == sy) {
if (sx >= ex) {
if (x <= sx && x >= ex) return true;
else return false;
}
else if (ex >= sx) {
if (x <= ex && x >= sx) return true;
else return false;
}
else return false;
}
else if (sx == ex && x == sx) {
if (sy >= ey) {
if (y <= sy && y >= ey) return true;
else return false;
}
else if (ey >= sy) {
if (y <= ey && y >= sy) return true;
else return false;
}
else return false;
}
else if (xX <= pmX && sX <= psmX && yY <= pmY && sY <= psmY) {
if (y == (m * x) + b) return true;
else return false;
}
else return false;
}

Categories