How to use xy coordinates to get data from SQLite db? - java

I have an SQLite database that contains x y coordinates of specific points in an image (Aya_No) among other data, I would like to get information from the db regrading these specific points (Aya_No) on LongPress
Here is part of the db:
Here is a sample image:
I have managed to get screen width and height using this code:
public static int getScreenX (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxWidth = displayMetrics.widthPixels;
return maxWidth;
}
public static int getScreenY (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxHight = displayMetrics.heightPixels;
return maxHight;
}
and I have managed to get x y OnTouchListener and pass it to OnLongClickListener using this code:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
x = event.getX();
y = event.getY();
return false;
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
double Talorance = 25;
double screenX = Utils.getScreenX(context);
double screenY = Utils.getScreenY(context);
// Adjusted x
double xPer = x/Utils.getScreenX(context); // .315
double adjXMin = (x - Talorance) * xPer;
double adjXMax = (x + Talorance) * xPer;
// Adjusted y
double yPer = y/Utils.getScreenY(context); //.33
double adjYMin = (y - Talorance) * yPer;
double adjYMax = (y + Talorance) * yPer;
String ScreenXY = String.format("ScreenXY: (%.2f, %.2f)",screenX, screenY);
String ClickedXY = String.format("ClickedXY: (%.2f, %.2f)",x,y);
String AdjustedMin = String.format("AdjustedMinXY: (%.2f, %.2f)",adjXMin,adjYMin);
String AdjustedMax = String.format("AdjustedMaxXY: (%.2f, %.2f)",adjXMax,adjYMax);
Log.w("ScreenXY", ScreenXY);
Log.w("ClickedXY ", ClickedXY);
Log.w("MinXY", AdjustedMin);
Log.w("MaxXY",AdjustedMax);
int page = lastpage - QuranActivity.mCurrentPage;
return true;
}
});
the results of the last code after longPress of Ayah 1 (circled in red in the 1st image) is:
W/ScreenXY: ScreenXY: (1440.00, 2712.00)
W/ClickedXY: ClickedXY: (574.98, 1064.89)
W/MinXY: AdjustedMinXY: (219.60, 408.32)
W/MaxXY: AdjustedMaxXY: (239.57, 427.96)
while the x y of this particular point in the db is 181, 301
after the long details above and bearing in mind that what I am asking for is actually implemented in many apps using the same image and db ..
How may I adjust x y (of different screen sizes and densities) obtained OnTouchListener to match or be close to the ones in the db (with some tolerance) in order to get relevant information from the db?
I have tried using % of total width and height to adjust x y as you saw in my code, but the results varied from screen size to another and were not close to the ones in the db.
PLEASE HELP!

The solution required some analysis:
1. Get screen size
public static int getScreenX (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxWidth = displayMetrics.widthPixels;
return maxWidth;
}
public static int getScreenY (Context context){
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int maxHight = displayMetrics.heightPixels;
return maxHight;
}
2. get image xy
image_IV.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
x = event.getX();
y = event.getY();
view.onTouchEvent(event);
return true;
}
});
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
return false;
}
});
3. calculate xy relative to screen size
double ScreenX = Utils.getScreenX(context);
double ScreenY = Utils.getScreenY(context);
double xPer = x / ScreenX;
double yPer = y / ScreenY;
4. do some excel work to estimate screen size basis used in db
5. add screen size results to project
double DBScreenX = 0;
double DBScreenY = 0;
if (LandScape) {
DBScreenX = 465;
DBScreenY = 280;
} else {
DBScreenX = 480;
DBScreenY = 822;
}
6. add some tolerance around 15dp
public static int dpToPx(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
double Talorance = Utils.dpToPx(15);
7. calculate adjust xy and determine range using tolerance
// X axis
double xPer = x / ScreenX;
double Adjustedx = xPer * DBScreenX;
double MinX = Adjustedx - Talorance;
double MaxX = Adjustedx + Talorance;
// Y axis
double yPer = y / ScreenY;
double Adjustedy = yPer * DBScreenY;
double MinY = Adjustedy - Talorance;
double MaxY = Adjustedy + Talorance;
8. get info from db based on adjuted xy range
public int getSorahNo (int page, double minX, double maxX, double minY, double maxY) {
int sorahNo = 1000;
open();
Cursor c = mDatabase.rawQuery("SELECT " + Contract.QuranTable.COLUMN_SORAH + " FROM " + Contract.QuranTable.TABLE_NAME +
" WHERE " + Contract.QuranTable.COLUMN_PAGE + " = " + page + " AND " +
Contract.QuranTable.COLUMN_X + " BETWEEN " + minX + " AND " + maxX + " AND " +
Contract.QuranTable.COLUMN_Y + " BETWEEN " + minY + " AND " + maxY, null);
if (c.moveToFirst()) {
do {
sorahNo = c.getInt(c.getColumnIndex(Contract.QuranTable.COLUMN_SORAH));
} while (c.moveToNext());
}
c.close();
if( sorahNo == 1000){
Log.w("getSorahNo ...", "Not successful ... Could not find Sorah based on x y" );
} else {
Log.w("getSorahNo ...", "is successful ... Sorah No = " + sorahNo);
}
close();
return sorahNo;
}

Related

NullPointerException on setText() in a fragment

When I run the following code it crash and throws an NPE error.
I try to access an object, in this case a textview in a fragment activity. How should I go about it ?
Below is the code I have pasted from the fragment activity.
Another issue is how to use the findViewById in the fragment activity.
public class Report extends Fragment implements SensorEventListener {
private static final double NOISE = 2.0 ;
//define
private SensorManager sensorManager;
private Sensor accelerometer;
private boolean mInitialized;
private static final String TAG = "Report";
private TextView Counter;
double X,Y,Z;
double lastX, lastY, lastZ;
double deltaX, deltaY, deltaZ;
int stepsCount;
private int threshold; // point at which we want to trigger a step.
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.report, container, false);
Counter = getActivity().findViewById(R.id.counter);
threshold = 10;
//initialize the variables
mInitialized = false;
//get the sensor manager
sensorManager = (SensorManager)getActivity().getSystemService(getActivity().SENSOR_SERVICE);
//get accelerometer sensor.
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
startSensor();
return view;
}
private void startSensor() {
//register a listener
sensorManager.registerListener(Report.this, accelerometer ,SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
// Log.d (TAG, "onSensorChanged: X:" + sensorEvent.values[0] + "Y:" + sensorEvent.values[1] + "Z:" + sensorEvent.values[2]);
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter's time-constant
// and dT, the event delivery rate
final double alpha = 0.8; // constant for our filter below
double[] gravity = {0, 0, 0};
// Isolate the force of gravity with the low-pass filter.
gravity[0] = alpha * gravity[0] + (1 - alpha) * sensorEvent.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * sensorEvent.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * sensorEvent.values[2];
// Log.d(TAG,"GRAVITY: X:" + gravity[0] + "Y:" + gravity[1] + "Z:" + gravity[2] );
// Remove the gravity contribution with the high-pass filter.
X = sensorEvent.values[0] - gravity[0];
Y = sensorEvent.values[1] - gravity[1];
Z = sensorEvent.values[2] - gravity[2];
//Log.d(TAG,"X:" + X + "Y:" + Y + "Z:" + Z);
lastX = 0;
lastY = 0;
lastZ = 0;
if (!mInitialized) {
//sensor is used for the first time, initialize the last read values
lastX = X;
lastY = Y;
lastZ = Z;
mInitialized = true;
} else {
// sensor is already initialized, and we have previously read values.
// take difference of past and current values and decide which
// axis acceleration was detected by comparing values
deltaX = Math.abs(lastX - X);
deltaY = Math.abs(lastY - Y);
deltaZ = Math.abs(lastZ - Z);
// Log.d(TAG,"X:"+ deltaX + "Y:" + deltaY + "Z:" + deltaZ);
if (deltaX < NOISE)
deltaX = (float) 0.0;
if (deltaY < NOISE)
deltaY = (float) 0.0;
if (deltaZ < NOISE)
deltaZ = (float) 0.0;
lastX = X;
lastY = Y;
lastZ = Z;
if (deltaX > deltaY) {
// Horizontal shake
// do something here if you like
} else if (deltaY > deltaX) {
// Vertical shake
// do something here if you like
} else if (deltaZ>threshold){
stepsCount++;
Log.d(TAG,"STEPS:" + stepsCount);
}
Counter.setText(String.valueOf(stepsCount));//this is the line that throws the error
}
}
#Override
public void onResume(){
super.onResume();
stepsCount=0;
// put your code here...
}
#Override
public void onPause(){
super.onPause();
sensorManager.unregisterListener(this);
// put your code here...
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}
This line in onCreateView():
Counter = getActivity().findViewById(R.id.counter);
needs to be:
Counter = view.findViewById(R.id.counter);
As you're obtaining your TextView from the layout you inflated the line before, not the Activity. As a side note, you shouldn't be naming variables with a capital letter at the front. Only classes should begin with capital letters.

ImageView onTouch not moving up and down but only around circle

I am developing a joystick button. Everything is ok when moving it around circle, but when I want to move it up or down, it goes only around circle again from the right side. So, I want to have both options (to move up or down in circle like real joystick). Here is my code example. Thanks for any help
public class JoystickView extends FrameLayout implements View.OnTouchListener {
private Context context;
private ImageView backgroundImageView;
private ImageView buttonImageView;
private RelativeLayout relativeLayout;
private View rootView;
float xx = 0;
float yy = 0;
public JoystickView(Context context) {
super(context);
this.context = context;
setLayout(this);
buttonImageView.setOnTouchListener(this);
this.setClipChildren(false);
}
public void setLayout(ViewGroup view) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
View v = inflateControlerLayout();
view.addView(v, layoutParams);
}
protected View inflateControlerLayout() {
initSlider();
rootView = relativeLayout;
return rootView;
}
public void initSlider() {
if (relativeLayout == null) {
relativeLayout = new RelativeLayout(context);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
relativeLayout.setLayoutParams(layoutParams);
relativeLayout.setClipChildren(false);
}
if (backgroundImageView == null) {
backgroundImageView = new ImageView(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
backgroundImageView.setLayoutParams(layoutParams);
// load image
try {
// get input stream
InputStream ims = getContext().getAssets().open("joystick_background.png");
// load image as Drawable
Drawable d = Drawable.createFromStream(ims, null);
// set image to ImageView
backgroundImageView.setImageDrawable(d);
} catch (IOException ex) {
return;
}
}
if (buttonImageView == null) {
buttonImageView = new ImageView(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER);
buttonImageView.setLayoutParams(layoutParams);
// load image
try {
// get input stream
InputStream ims2 = getContext().getAssets().open("jostick_button.png");
// load image as Drawable
Drawable d2 = Drawable.createFromStream(ims2, null);
// set image to ImageView
buttonImageView.setImageDrawable(d2);
buttonImageView.bringToFront();
} catch (IOException ex) {
return;
}
}
if (relativeLayout != null) {
relativeLayout.addView(backgroundImageView);
relativeLayout.addView(buttonImageView);
}
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
view.setX(xx);
view.setY(yy);
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
float cx = view.getWidth() / 2.f;
float cy = view.getHeight() / 2.f;
float x = motionEvent.getRawX();
float y = motionEvent.getRawY();
float w = buttonImageView.getWidth();
float h = buttonImageView.getHeight();
double r = Math.min(cx, cy) / 2.;
double dx = x - cx;
double dy = y - cy;
double hypot = Math.hypot(dx, dy);
double cos = dx / hypot;
double sin = dy / hypot;
double rdx = hypot < 1. ? 0. : r * cos;
double rdy = hypot < 1. ? 0. : r * sin;
buttonImageView.setTranslationX((float) (cx + rdx - w / 2.));
buttonImageView.setTranslationY((float) (cy + rdy - h / 2.));
break;
}
return true;
}
}
These lines constrain the coordinates to a circle (oval) path or center:
double rdx = hypot < 1. ? 0. : r * cos;
double rdy = hypot < 1. ? 0. : r * sin;
For hypot to be less than 1 (that's 1 pixel) you'd have to be dead center.
I changed it to only constrain the coordinates within the circle:
double rcos = r * cos;
double rsin = r * sin;
double rdx = Math.abs(dx) < Math.abs(rcos) ? dx : rcos;
double rdy = Math.abs(dy) < Math.abs(rsin) ? dy : rsin;
I think this might be what you want.
I have found a working solution if someone is looking for it
private Point calculate (float x, float y) {
float cx = buttonImageView.getWidth() / 2.f;
float cy = buttonImageView.getHeight() / 2.f;
double r = cx / 2.; // vrednost radius
double dx = x;
double dy = y;
double hypot = Math.hypot(dx, dy); // izracun hipotenuze
double cos = dx / hypot; // cos
double sin = dy / hypot; // sin
double rcos = r * cos;
double rsin = r * sin;
double rdx = Math.abs(dx) < Math.abs(rcos) ? dx : rcos; // if,else
double rdy = Math.abs(dy) < Math.abs(rsin) ? dy : rsin;
return new Point((int)rdx, (int)rdy);
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
final float x = motionEvent.getRawX(); // x točko
final float y = motionEvent.getRawY(); // y točka
//Log.d("VALUES", "RAW X:" + motionEvent.getRawX() + ", RAW Y:" + motionEvent.getRawY() + ", X:" + motionEvent.getX() + ", CX:" + cx + ", CY:" + cy + ", dx:" + dx + ", dy:" + dy + ", Hypo:" + hypot + ", cos:" + cos + ", sin" + sin);
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
xDelta = view.getX() - x;
yDelta = view.getY() - y;
break;
case MotionEvent.ACTION_UP:
doBounceAnimation(buttonImageView);
doVibration();
view.setX(xx);
view.setY(yy);
break;
case MotionEvent.ACTION_POINTER_DOWN:
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_MOVE:
final float transX = (float) x;
final float transY = (float) y;
thread = new Thread() {
#Override
public void run() {
Point newPoint = calculate(transX+ xDelta,transY + yDelta);
buttonImageView.setX(newPoint.x);
buttonImageView.setY(newPoint.y);
}
};
thread.start();
Log.d(TRANSLATIONX,"X:" + transX + ", Y:" + transY);
break;
}
return true;
}
Enjoy coding!

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!

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

Collision detection between circles

I got this working class that creates a DarkHole object in the screen. Its a filled circle with random color, position and radius. When screen is touched, it is supposed to draw a ball avoiding the other ones that have already been drawn.
I used this method:
Colision by Radius
public class DarkHole extends View {
public static final int maxDiameter = 250;
public static final int minDiameter = 240;
/**
* This class log tag.
*/
private static final String LOG_TAG = DarkHole.class.getSimpleName();
private static List<ShapeDrawable> mHoles;
private int mWindowWidth;
private int mWindowHeight;
private Random random;
/**
* The constructor;
*
* #param context application context.
*/
public DarkHole(Context context) {
super(context);
mHoles = new ArrayList<ShapeDrawable>();
random = new Random();
//Get Screen Size
Point point = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
display.getSize(point);
// Get screen max x and y.
mWindowWidth = point.x;
mWindowHeight = point.y;
}
/**
* Draw random hole.
*/
private void generateRandomHole() {
while(true) {
ShapeDrawable hole = new ShapeDrawable(new OvalShape());
// Generate random color.
int r = random.nextInt(255);
int g = random.nextInt(255);
int b = random.nextInt(255);
int randomColor = Color.rgb(r, g, b);
// Generate random position.
int diameter = random.nextInt(maxDiameter - minDiameter + 1) + minDiameter;
int x = random.nextInt((mWindowWidth - diameter) + 1);
int y = random.nextInt((mWindowHeight - diameter) + 1);
hole.getPaint().setColor(randomColor);
hole.setBounds(x, y, x + diameter, y + diameter);
if (checkDrawContains(hole)) {
Log.d(LOG_TAG, "[generateRandomHole] contains!");
hole = null;
random = null;
} else {
Log.d(LOG_TAG, "[generateRandomHole] not contains!");
mHoles.add(hole);
break;
}
}
}
/**
* Draw informative text.
*
* #param canvas canvas object.
*/
private void generateText(Canvas canvas) {
Paint color = new Paint();
color.setColor(Color.BLACK);
color.setTextSize(70);
color.setAntiAlias(true);
color.setDither(true);
canvas.drawText("Bolas: " + mHoles.size(), 10, 50, color);
}
private boolean checkDrawContains(ShapeDrawable newHole) {
long newCenterX = newHole.getBounds().left + (newHole.getBounds().width()/2);
long newCenterY = newHole.getBounds().top + (newHole.getBounds().height()/2);
Log.d(LOG_TAG, "[checkDrawContains] newCenterX: " + newCenterX);
for(ShapeDrawable hole: mHoles) {
long centerX = hole.getBounds().left + (hole.getBounds().width()/2);
long centerY = hole.getBounds().top + (hole.getBounds().height()/2);
long x = centerX - newCenterX;
long y = centerY - newCenterY;
long aux = (long) ((Math.pow(Math.abs(x),2)) + (Math.pow(Math.abs(y),2)));
long distance = (long) Math.sqrt(aux);
Log.d(LOG_TAG, "[checkDrawContains] x: " + x);
Log.d(LOG_TAG, "[checkDrawContains] y: " + y);
Log.d(LOG_TAG, "[checkDrawContains] distance: " + distance);
Log.d(LOG_TAG, "[checkDrawContains] sum radius: " + (newCenterX + centerX));
long sRads = (newHole.getBounds().width()/2) + (hole.getBounds().width()/2);
if(distance <= sRads ) {
return true;
}
}
return false;
}
/** {#inheritDoc} */
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
generateText(canvas);
for (ShapeDrawable hole : mHoles) {
hole.draw(canvas);
}
invalidate();
}
/** {#inheritDoc} */
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
generateRandomHole();
return true;
}
return super.onTouchEvent(event);
}
}
It should work, but It crashes when the recursion starts to recalculate all the random parameters. Is it a stack overflow? Why is that happening? How it should be solved?

Categories