How much of the canvas is filled - java

OK,
I'm trying to make a app that has a Scratch off feature (like scratch off tickets from the loto).
I've been digging around and can not find the a way to tell how much of my canvas is covered by my parent/color. See what i want is that they have to uncover x% of the background image before they can "click" to claim.
I did find the answer to the original question. but a new one has come up dealing with the same thing.
when calculating out the percent "int percent = (trans_count / max_count) * 100;" it always returns 0 and in the my app the max_count is always 90000 (because of the picture size and my androids screen).
Here is my current code.
class TouchView extends ImageButton {
public Boolean Clickable = false;
private Bitmap bgr = null;
private Bitmap overlayDefault;
private Bitmap overlay;
private Paint pTouch;
private int X = -100;
private int Y = -100;
private Canvas c2;
public TouchView(Context context) { super(context); build();}
public TouchView(Context context, AttributeSet attrs) { super(context, attrs); build(attrs); }
public TouchView(Context context, AttributeSet attrs, Integer params) { super(context, attrs, params); build(attrs);}
private void build()
{
bgr = BitmapFactory.decodeResource(getResources(),R.drawable.loser);
overlayDefault = BitmapFactory.decodeResource(getResources(),R.drawable.over);
overlay = BitmapFactory.decodeResource(getResources(), R.drawable.over).copy(Config.ARGB_8888, true);
pTouch = new Paint(Paint.ANTI_ALIAS_FLAG);
pTouch.setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
pTouch.setColor(Color.TRANSPARENT);
pTouch.setMaskFilter(new BlurMaskFilter(2, Blur.NORMAL));
pTouch.setStrokeWidth(20);
}
private void build(AttributeSet attrs)
{
bgr = BitmapFactory.decodeResource(getResources(),R.drawable.loser);
overlayDefault = BitmapFactory.decodeResource(getResources(),R.drawable.over);
overlay = BitmapFactory.decodeResource(getResources(), R.drawable.over).copy(Config.ARGB_8888, true);
pTouch = new Paint(Paint.ANTI_ALIAS_FLAG);
pTouch.setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
pTouch.setColor(Color.TRANSPARENT);
pTouch.setMaskFilter(new BlurMaskFilter(2, Blur.NORMAL));
pTouch.setStrokeWidth(20);
}
public void setAsWinner() {
bgr = BitmapFactory.decodeResource(getResources(), R.drawable.winner);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
int sX = X;
int sY = Y;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
X = (int) ev.getX();
Y = (int) ev.getY();
invalidate();
c2.drawCircle(X, Y, 5, pTouch);
break;
}
case MotionEvent.ACTION_MOVE: {
X = (int) ev.getX();
Y = (int) ev.getY();
invalidate();
c2.drawLine(sX, sY, X, Y, pTouch);
break;
}
case MotionEvent.ACTION_UP:
X = (int) ev.getX();
Y = (int) ev.getY();
invalidate();
c2.drawLine(sX, sY, X, Y, pTouch);
break;
}
try
{
int pixels[] = new int[(overlay.getWidth() * overlay.getHeight())];
overlay.getPixels(pixels, 0, overlay.getWidth(), 0, 0, overlay.getWidth(), overlay.getHeight());
int max_count = pixels.length;
int trans_count = 0;
for(int i = 0 ; i < max_count; i++)
{
if (pixels[i] == 0)
trans_count++;
}
int percent = (trans_count / max_count) * 100;
if (percent > 60)
{
Clickable = true;
}
}
catch (IllegalArgumentException e)
{
Log.i ("info", e.getMessage());
}
catch (ArrayIndexOutOfBoundsException e)
{
Log.i ("info", e.getMessage());
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw background
if (bgr != null)
canvas.drawBitmap(bgr, 0, 0, null);
if (c2 == null)
{
int width = overlay.getWidth();
int height = overlay.getHeight();
RectF bounds = new RectF(canvas.getClipBounds());
float scaleWidth = bounds.width() / width;
float scaleHeight = bounds.height() / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
overlay = Bitmap.createBitmap(overlay,0, 0, width, height, matrix, true);
c2 = new Canvas(overlay);
}
canvas.drawBitmap(overlay, 0, 0, null);
}
}

trans_count and max_count are integers so java performs integer division when you do trans_count/max_count and the result is 0 because trans_count is smaller than max_count. What you want is floating point division, you can force it like this:
double percentage = 100.0 * trans_count / max_count;
Basically what will happen is that 100.0 * trans_count will get executed first giving a floating point value (because one of the values is floating point), then the result will be divided by max_count again giving a floating point value.

Related

Forward mouse input to jxbrowser running in background

I am trying to embed jxbrowser into a opengl game as a gui.(Actually it's Minecraft, but not relevant in this case)
The drawing part is complete. I can draw the browser UI at anywhere as a quad.
BUT, since the browser is running in background, I need to forward the user's mouse position to the browser, or the browser's view.
I've tried the code from JxBrowser's documentation about forwarding mouse events, but looks like it only works with heavyweight widget.
I've tried using heavyweight, but it doesn't provide a way to bake into a BufferedImage.
Here's what I got so far:
public class BrowserScreen extends GuiScreen {
private BrowserView view;
private Browser browser;
public BrowserScreen(BrowserView view, Browser browser) {
this.view = view;
this.browser = browser;
}
int browserWidth = 1260;
int browserHeight = (int) (browserWidth * (float) height / (float) width);
#Override
public void initGui() {
browserHeight = (int) (browserWidth * (float) height / (float) width);
browser.setSize(browserWidth, browserHeight);
view.setSize(browserWidth, browserHeight);
view.setVisible(true);
}
#Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
if (browser == null || view == null) return;
LightWeightWidget component = (LightWeightWidget) view.getComponent(0);
BufferedImage image = (BufferedImage) component.getImage();
if (image == null) return;
//Bind captured image to opengl
int width = image.getWidth();
int height = image.getHeight();
int[] pixels = new int[width * height];
image.getRGB(0, 0, width, height, pixels, 0, width);
ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 3); //4 for RGBA, 3 for RGB
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = pixels[y * width + x];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component
buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component
buffer.put((byte) (pixel & 0xFF)); // Blue component
//buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. Only for RGBA
}
}
buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS
// You now have a ByteBuffer filled with the color data of each pixel.
// Now just create a texture ID and bind it. Then you can load it using
// whatever OpenGL method you want, for example:
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, width, height, 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
//Draw the quad
float pictureRatio = (float) width / (float) height;
float screenRatio = (float) this.width / (float) this.height;
int x = 0;
int y = 0;
if (pictureRatio > screenRatio) {
height = (int) (this.width * ((float) height / (float) width));
y = this.height / 2 - height / 2;
} else {
width = (int) (this.height * ((float) width / (float) height));
x = this.width / 2 - width / 2;
}
Gui.drawScaledCustomSizeModalRect(x, y, 0, 0, width, height, this.width, this.height, width, height);
}
#Override
public void handleMouseInput() throws IOException {
if (browser == null) return;
if (Mouse.isButtonDown(-1)) {
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_PRESSED)
.setButtonType(BrowserMouseEvent.MouseButtonType.PRIMARY)
.setClickCount(1)
.setModifiers(new BrowserKeyEvent.KeyModifiersBuilder().mouseButton().build());
browser.forwardMouseEvent(builder.build());
System.out.println("Left clicked");
}
int i = Mouse.getEventDWheel();
if (i != 0) {
if (i > 1) {
i = 6;
}
if (i < -1) {
i = -6;
}
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_WHEEL)
.setScrollBarPixelsPerLine(25)
.setScrollType(WHEEL_BLOCK_SCROLL)
.setUnitsToScroll(i);
browser.forwardMouseEvent(builder.build());
System.out.println("Scrolled: " + i);
}
super.handleMouseInput();
}
#Override
protected void mouseReleased(int mouseX, int mouseY, int state) {
if (browser == null) return;
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_RELEASED)
.setButtonType(BrowserMouseEvent.MouseButtonType.PRIMARY)
.setClickCount(1)
.setModifiers(BrowserKeyEvent.KeyModifiers.NO_MODIFIERS);
browser.forwardMouseEvent(builder.build());
System.out.println("Released");
}
#Override
public void onGuiClosed() {
dispose();
}
private boolean dispose() {
if (browser != null) {
if (!browser.isDisposed()) browser.dispose();
browser = null;
}
if (view != null) {
if (view.isEnabled()) {
view.setEnabled(false);
view = null;
return true;
}
view = null;
}
return false;
}
public static BrowserScreen open(String url) {
Locale.setDefault(Locale.CHINA);
Browser browser = new Browser(BrowserType.LIGHTWEIGHT);
BrowserPreferences preferences = browser.getPreferences();
preferences.setTransparentBackground(true);
browser.setPreferences(preferences);
BrowserView view = new BrowserView(browser);
final BrowserScreen screen = new BrowserScreen(view, browser);
browser.loadURL(url);
return screen;
}
}
Mouse events forwarding works in the lightweight mode as well. If you run the sample application from the mentioned article replacing the browser constructor with Browser browser = new Browser(BrowserType.LIGHTWEIGHT);, you should see that it works as expected.
I noticed that you don't define mouse event coordinates when composing the BrowserMouseEvent instance.
Please try filling all fields of the BrowserMouseEvent instance(similar to the way described in the article).
I fixed the problem myself. It turns out that:
For every mouse event, I only need to fill in the setX and setY method. The setGlobal method has to be left empty.
The X and Y values forwarded are calculated incorrectly.
For the mouse click event, both the MOUSE_PRESSED and MOUSE_RELEASED events have to be called.
And, here's how I pass the MOUSE_MOVED, since it is not in the according documentation:
BrowserMouseEvent.BrowserMouseEventBuilder builder = new BrowserMouseEvent.BrowserMouseEventBuilder();
builder.setEventType(MOUSE_MOVED)
.setX(browserX)
.setY(browserY)
.setModifiers(new BrowserKeyEvent.KeyModifiersBuilder().mouseButton().build());
browser.forwardMouseEvent(builder.build());

Java OpenCV Layer small image onto larger image with transparency

I am trying to write a function that overlays an image at a rectangle with transparency over top of another image, However it doesn't layer the images it just erases the section that I overlay and the transparency cuts through the entire image. Here is my code.
public static void overlayImage(String imagePath, String overlayPath, int x, int y, int width, int height) {
Mat overlay = Imgcodecs.imread(overlayPath, Imgcodecs.IMREAD_UNCHANGED);
Mat image = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_UNCHANGED);
Rectangle rect = new Rectangle(x, y, width, height);
Imgproc.resize(overlay, overlay, rect.size());
Mat submat = image.submat(new Rect(rect.x, rect.y, overlay.cols(), overlay.rows()));
overlay.copyTo(submat);
Imgcodecs.imwrite(imagePath, image);
}
EDIT: Here are some example pictures:
Before:
After:
Found this function that does exactly what I needed.
public static void overlayImage(Mat background,Mat foreground,Mat output, Point location){
background.copyTo(output);
for(int y = (int) Math.max(location.y , 0); y < background.rows(); ++y){
int fY = (int) (y - location.y);
if(fY >= foreground.rows())
break;
for(int x = (int) Math.max(location.x, 0); x < background.cols(); ++x){
int fX = (int) (x - location.x);
if(fX >= foreground.cols()){
break;
}
double opacity;
double[] finalPixelValue = new double[4];
opacity = foreground.get(fY , fX)[3];
finalPixelValue[0] = background.get(y, x)[0];
finalPixelValue[1] = background.get(y, x)[1];
finalPixelValue[2] = background.get(y, x)[2];
finalPixelValue[3] = background.get(y, x)[3];
for(int c = 0; c < output.channels(); ++c){
if(opacity > 0){
double foregroundPx = foreground.get(fY, fX)[c];
double backgroundPx = background.get(y, x)[c];
float fOpacity = (float) (opacity / 255);
finalPixelValue[c] = ((backgroundPx * ( 1.0 - fOpacity)) + (foregroundPx * fOpacity));
if(c==3){
finalPixelValue[c] = foreground.get(fY,fX)[3];
}
}
}
output.put(y, x,finalPixelValue);
}
}
}

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

Cannot draw canvas with desired Bitmap after OnTouchEvent

I am making a tictactoe android application . After making on customview called TicTacToeGFX I tried to make an onTouchEvent which (after taking into account) the coordinates of the touch , it would fill the corresponding rectangle with the bitmap circle or cross. The Problem is that in the onTouchEvent I cannot redraw the canvas with the desired Bitmap. So my question is How to I re-draw a canvas after an onTouchEvent?
public class NewGame extends Activity {
TicTacToeGFX Game;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Game = new TicTacToeGFX(this);
setContentView(Game);
}
}
public class TicTacToeGFX extends View {
int x, y;
int rectanglescounter = 0;
public Rect[] rectangles = new Rect[9];
// BitMap
Bitmap cross = BitmapFactory.decodeResource(getResources(),
R.drawable.cross1);
Bitmap circle = BitmapFactory.decodeResource(getResources(),
R.drawable.circle1);
Bitmap empty = BitmapFactory.decodeResource(getResources(),
R.drawable.empty1);
Paint paint = new Paint();
Canvas canvas = new Canvas();
public TicTacToeGFX(Context context) {
super(context);
setFocusable(true);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// -------------------------------------------calculate
// screen------------------------------------------------------
x = (int) ((getWidth()) / 3);
y = (int) ((getHeight()) / 3);
// //------------------------------------------- calculate rectangle
// positions on
// screen------------------------------------------------------
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
rectangles[rectanglescounter] = new Rect((x * j), (y * i),
((x * j) + x), ((y * i) + y));
if (rectanglescounter < 8)
rectanglescounter++;
else
break;
}
}
// ------------------------------------------- draw the canvas with
// empty Bitmap
// ------------------------------------------------------
int counter = 0;
canvas.drawRGB(0, 0, 0);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
canvas.drawBitmap(empty, null, rectangles[counter], null);
counter++;
}
}
// ------------------------------------------- draw
// LinesS------------------------------------------------------
paint.setColor(Color.WHITE);
canvas.drawLine(x, 0, x, 3 * y, paint);// 1h
canvas.drawLine(2 * x, 0, 2 * x, 3 * y, paint);// 2h
canvas.drawLine(0, y, 3 * x, y, paint);// 3h
canvas.drawLine(0, 2 * y, 3 * x, 2 * y, paint);// 4h
}
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
x = (int) event.getX();
y = (int) event.getY();
switch (eventaction) {
case MotionEvent.ACTION_DOWN:
Toast.makeText(getContext(), "Hello", 1).show();
canvas.drawRGB(0, 0, 0);
invalidate();
break;
case MotionEvent.ACTION_MOVE: // touch drag with the ball
// move the balls the same as the finger
break;
}
return true;
}
}

Categories