I first created a custom Surface View to draw things on; to visually describe, a green background on which I draw some lines and circles. Then, I decided to remove the green background and have a live camera preview as the background instead. This is what I tried, but could not get it working (below). What happens is, both the camera view and my custom surface view are being created, but only the camera view is being shown. I'm sure my custom Surface View is also created because I've a TouchListener in this custom view that prints x,y values when touched (and this happens). But my custom drawing is not being shown over the camera preview, as I want it to be. Please help!!
gameview.xml file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
</FrameLayout>
Camera View:
public class CameraView extends SurfaceView implements SurfaceHolder.Callback{
Camera camera;
public CameraView(Context context, Camera c) {
super(context);
camera = c;
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// TODO Auto-generated constructor stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
try {
camera.setPreviewDisplay(getHolder());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
camera.startPreview();
}
}
Custom Surface View named VirtuaParkView: This is where I draw stuff using onDraw() method being called continuously from a thread. Also implements SurfaceHolder.Callback. Code not written here for brevity.
How it's all put together:
CameraView cView = new CameraView(getApplicationContext(),Camera.open()); //get Camera View
VirtuaParkView vParkView = new VirtuaParkView(getApplicationContext()); // get custom surface view
setContentView(R.layout.gameview); //set the Frame layout
FrameLayout fl = (FrameLayout)findViewById(R.id.mainlayout); // get the layout root
fl.addView(cView); //add camera view
fl.addView(vParkView,new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT)); //add custom drawing
I don't know what your VirtualParkView looks like but try using this custom View. It should draw a small green square where you touch it.
public class CameraOverlay extends View {
private static final Integer rectSize = 30;
Paint paint;
private Rect touchRect;
public CameraOverlay(Context context) {
super(context);
paint = new Paint();
touchRect = new Rect(0,1,0,1);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
int height = canvas.getHeight();
int width = canvas.getWidth();
canvas.drawLine(width/2,0,width/2,height,paint);
canvas.drawLine(0,height/2,width,height/2,paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
canvas.drawRect(touchRect, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "CameraOverlay - onTouchEvent()");
invalidate();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
float x = event.getX();
float y = event.getY();
touchRect.left = (int) (x - rectSize);
touchRect.top =(int) (y - rectSize);
touchRect.right = (int) (x + rectSize);
touchRect.bottom = (int) (y + rectSize);
}
return false;
}
}
Then, instead of your VirtualParkView, add this to your frame -
if (mCameraOverlay == null)
mCameraOverlay = new CameraOverlay(this);
f1.addView(mCameraOverlay);
Related
I am using a tflite model and I am trying to draw a RectF on the image where the object is detected.
Here is my CameraActivity.java class where I am detecting the object.
predictBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
img = Bitmap.createScaledBitmap(img, 128, 128, true);
try {
DrawView drawView = new DrawView(getApplicationContext());
Android model = Android.newInstance(getApplicationContext());
// Creates inputs for reference.
TensorImage image = TensorImage.fromBitmap(img);
// Runs model inference and gets result.
Android.Outputs outputs = model.process(image);
Android.DetectionResult detectionResult = outputs.getDetectionResultList().get(0);
// Gets result from DetectionResult.
float score = detectionResult.getScoreAsFloat();
RectF location = detectionResult.getLocationAsRectF();
String category = detectionResult.getCategoryAsString();
// Releases model resources if no longer used.
mCanvas.drawBoundingBox();
model.close();
// here we will print out the results of the object to text views based on the image that is inputted by the user
// we print out object type and its accuracy score
mCanvasView.drawRect();
objecttv.setText(category);
scoretv.setText(Float.toString(score));
} catch (IOException e) {
// TODO Handle the exception
}
}
}
);
Here is my DrawView.java class
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class DrawView extends View {
Paint boxPaint;
public static Canvas mCanvas;
public DrawView(Context context) {
super(context);
boxPaint = new Paint();
}
public void drawBoundingBox() {
// Refresh the view by calling onDraw function
invalidate();
}
public void onDraw(Canvas canvas, AttributeSet attrs) {
// Draw what you want
boxPaint.setColor(Color.RED);
boxPaint.setAlpha(200);
boxPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(location, boxPaint);
}
}
In my activity_camera.xml , I added the drawview to match the image I am uploading.
<com.example.aPROJECT.DrawView
android:id="#+id/canvasView"
android:layout_width="281dp"
android:layout_height="324dp"
android:layout_marginTop="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<ImageView
android:id="#+id/imageView"
android:layout_width="281dp"
android:layout_height="324dp"
android:layout_marginTop="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#android:drawable/ic_menu_gallery"
tools:ignore="ImageContrastCheck" />
In the CameraActivity.java , on line mCanvas.drawBoundingBox(); it cannot access the mCanvas from the DrawView.java class.
On line mCanvasView.drawRect(); it cannot resolve the symbol .
What am I missing here?
There are several problems:
if you want to access a static field outside of its class in Java you need to either specify the class name like this DrawView.mCanvas or to use a static import (add a line
import static /*class.package.name.here.*/DrawView.mCanvas;
above the class declaration and then you can use mCanvas directly)
drawBoundingBox() is a method in DrawView, not canvas so the correct way to call would be drawView.drawBoundingBox()
even if the class would compile you'd get a NullPointerException when drawRoundingBox() was called because mCanvas has not been initialized in DrawView
the canvas in a View is only a temporary variable as parameter to onDraw used to create the user interface at that moment in time (each frame) and is not meant to be used outside of that method since it points/refers to a video memory buffer that is continuously changed
What you should do is create a Canvas for the Bitmap you're trying to draw on and use it (you don't need a view to alter a bitmap):
//if img is the bitmap you want to draw on
Canvas c = new Canvas(img); //before createScaledBitmap()
drawBoundingBox(c);// move onDraw code to drawBoundingBox()
// and the latter method to CameraActivity
//Should you need to get the bitmap screenshot of a view simply draw it:
Bitmap vscr = Bitmap.createBitmap(yourView.getWidth(), yourView.getHeight(), Bitmap.Config.ARGB_8888);
//ensure the view was added to the layout (measured) so that its width and height are greater than zero when you create the bitmap
yourView.draw(vscr);
Example
public class CameraActivity extends ActivityCompat {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
predictBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
img = Bitmap.createScaledBitmap(img, 128, 128, true);
try {
//DrawView drawView = new DrawView(getApplicationContext());
Android model = Android.newInstance(getApplicationContext());
// Creates inputs for reference.
TensorImage image = TensorImage.fromBitmap(img);
// Runs model inference and gets result.
Android.Outputs outputs = model.process(image);
Android.DetectionResult detectionResult = outputs.getDetectionResultList().get(0);
// Gets result from DetectionResult.
float score = detectionResult.getScoreAsFloat();
RectF location = detectionResult.getLocationAsRectF();
String category = detectionResult.getCategoryAsString();
// Releases model resources if no longer used.
Canvas canvas = new Canvas(img);
drawBoundingBox(canvas, location);
model.close();
// here we will print out the results of the object to text views based on the image that is inputted by the user
// we print out object type and its accuracy score
mCanvasView.drawRect();
objecttv.setText(category);
scoretv.setText(Float.toString(score));
} catch (IOException e) {
// TODO Handle the exception
}
}
});
} //onCreate
void drawBoundingBox(Canvas canvas, RectF location) {
// Draw what you want
Paint boxPaint = new Paint();
boxPaint.setColor(Color.RED);
boxPaint.setAlpha(200);
boxPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(location, boxPaint);
}
}
I'm developing an Android app that have a overlay view that the user can access from anywhere.
I have taken the code from the following question:
Overlay
Put it simple, a class "HUD" that call a subclass ViewGroup"HUDView", containing all the necessary elements.
Now I want to add in this ViewGroup also a SeekBar that, on progress change, draw a Canvas every time: for example seekBar progress =1, draw a circle with 10° angle, progress=2, redraw the circle with 20° angle, and so on...
The circle and the seekbar must overlay the actual screen from everywhere.
I've managed to put the Canvas (created in the onDraw method of the ViewGroup) and the seekBar (created in the onCreate method of the HUD class, also with his listener).
In the method "onProgressChanged" I would like to redraw the circle, but this doesnt seems to work.
Here the listener code:
// SeekBar Change Listener
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progresValue, boolean fromUser) {
mView = new HUDView(getApplicationContext());
progress = progresValue;
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.CENTER | Gravity.TOP;
params.setTitle("Load Average");
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.addView(mView, params);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
//Toast.makeText(getApplicationContext(), "Started tracking seekbar", Toast.LENGTH_SHORT).show();
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
//textView.setText("Covered: " + progress + "/" + seekBar.getMax());
//Toast.makeText(getApplicationContext(), "Stopped tracking seekbar", Toast.LENGTH_SHORT).show();
}
});
Here the onDraw method of the HUDView:
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final RectF oval = new RectF();
oval.set(30, 30, 450, 450);
Bitmap bg = Bitmap.createBitmap(480, 480, Bitmap.Config.ARGB_8888);
canvas.drawArc(oval, 90, progress*10 , true, paint);
}
I am using a glow effect which works using setMaskFilter to blur the painted area:
public static Paint createGlowPaint(Context context, #ColorRes int baseColorKey) {
float glowWidth = context.getResources().getDimension(R.dimen.analog_blur_width);
Paint paint = new Paint();
paint.setColor((Workarounds.getColor(context, baseColorKey) & 0xFFFFFF) | 0x40000000);
paint.setMaskFilter(new BlurMaskFilter(glowWidth, BlurMaskFilter.Blur.NORMAL));
paint.setStrokeWidth(glowWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
return paint;
}
This is used along with custom vector graphics to do the drawing of the blur and then the drawing of the hands of my watch face. This is all packaged up into a Drawable so that I can use it in more than one place. Or so I thought! It turns out that even though this works fine when painted directly onto the top-level canvas, when I try to use ImageView#setImageDrawable to set the exact same Drawable onto an ImageView, the filter no longer gets applied.
You can see how this sort of blur looks:
Using it with an ImageView, now you get a hard edge instead:
What is going on here?
Edit for additional info:
Code that does the drawing, i.e. is using the glow paint:
public abstract class Hands extends Drawable {
// ... lots of other cruft
#Override
public void draw(Canvas canvas) {
//todo could probably precompute more in onBoundsChange
Rect bounds = getBounds();
int centerX = bounds.centerX();
int centerY = bounds.centerY();
if (watchMode.isInteractive()) {
handGlowPath.reset();
handGlowPath.addPath(hourHand.getPath());
handGlowPath.addPath(minuteHand.getPath());
handGlowPath.addCircle(centerX, centerY, centreRadius, Path.Direction.CCW);
canvas.drawPath(handGlowPath, handGlowPaint);
}
hourHand.draw(canvas);
minuteHand.draw(canvas);
if (watchMode.isInteractive()) {
secondHand.draw(canvas);
if (hasThirds()) {
thirdHand.draw(canvas);
}
}
canvas.drawCircle(centerX, centerY, centreRadius, centrePaint.getPaint());
}
Code that puts the drawable into the ImageView:
class PreviewListViewAdapter extends BaseAdapter implements ListAdapter {
// ... other methods for the list adapter
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView instanceof ViewHolder) {
holder = (ViewHolder) convertView;
} else {
holder = new ViewHolder(getContext(), inflater.inflate(R.layout.config_list_item, parent, false));
}
// Deliberately making this a square.
LinearLayout.LayoutParams holderLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
LinearLayout.LayoutParams viewLayoutParams =
new LinearLayout.LayoutParams(parent.getWidth(), parent.getWidth());
if (position == 0) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
} else if (position == getCount() - 1) {
holderLayoutParams.height += 20 * getResources().getDisplayMetrics().density;
viewLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
} else {
viewLayoutParams.gravity = Gravity.CENTER;
}
holder.setLayoutParams(holderLayoutParams);
holder.view.setLayoutParams(viewLayoutParams);
holder.image.setImageDrawable(items[position].drawable);
holder.text.setText(items[position].labelText);
return holder;
}
}
Starting from API 14, BlurMaskFilters are not supported when hardware acceleration is enabled.
To work around this, set your ImageView's layer type to software:
holder.image.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I am extend a SupportMapFragment which contain google map here, I lay my google map on a frameLayout according to How to handle onTouch event for map in Google Map API v2?. Ondraw()method is called , but the problem is rectangle is never been showed, even when I use setWillNotDraw(false); here.
I already got the right position of the rectangle, the only problem is it is not showed. Following is my most part of code:
Add-on:When I remove the google map it works as I expect, actually the rectangle drawed on the background frameLayout, so it will be covered by the map, now the way I solve the question is add a image view as a rectangle and dynamic change its size when touch moving. I have no idea how to draw directly on the top of this viewgroup(framelayout) at the moment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
//stash reference to google map
mGoogleMap = getMap();
//show the location
mGoogleMap.setMyLocationEnabled(true);
mTouchView = new TouchableWrapper(getActivity());
mTouchView.addView(view);
return mTouchView;
}
private class TouchableWrapper extends FrameLayout {
//box is used to illustrate the drawing box, the painter is used to set the color of the box.
private Box mCurrentBox = null;
private Paint mBoxPaint;
public TouchableWrapper(Context context) {
super(context);
mBoxPaint = new Paint();
mBoxPaint.setColor(0xffff0000);
// mBoxPaint.setStrokeMiter(20);
mBoxPaint.setStrokeWidth(8);
this.setWillNotDraw(false);
setWillNotDraw(false);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// decide when the child should hold the motion event.
return true; //always let the relative layout to hold the motion event.
}
#Override
public boolean onTouchEvent(MotionEvent event) {
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMapIsTouched = true;
mCurrentBox = new Box(curr);//original code for draw Rectangle.
Log.d(TAG, "action.down box is"+mCurrentBox);
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "action_move");
if (mCurrentBox != null) {
mCurrentBox.setmCurrent(curr);
Log.d(TAG, "action.move box is"+mCurrentBox);
this.invalidate(); //TODO
}
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "action_up");
mMapIsTouched = false;
mCurrentBox = null;
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
float left = 0;
float right = 0;
float top = 0;
float bottom = 0;
if (mCurrentBox!=null) {
left = Math.min(mCurrentBox.getmOrigin().x, mCurrentBox.getCurrent().x) ;
right = Math.max(mCurrentBox.getmOrigin().x, mCurrentBox.getCurrent().x) ;
top = Math.min(mCurrentBox.getmOrigin().y, mCurrentBox.getCurrent().y) ;
bottom = Math.max(mCurrentBox.getmOrigin().y, mCurrentBox.getCurrent().y) ;
canvas.drawRect(left, top, right, bottom, mBoxPaint);
}
Log.i(TAG, "value is"+left+right+top+bottom);
}
You could wrap the Map in a FrameLayout containing also a Drawable (your rectangle). In that way, once you find where the rectangle shall be, you could hide and show it without having to redraw it.
You can see here for an example.
I have seen lots of tutorial and information but i could not find any single place how to use the default settings of the existing camera application into any other customized camera application. I have seen the sharpness of the image and its focus is very fine in the built-in camera application. Now i am creating my own application with my customized features but i am still unable to make it sharp and non-blurry... I dont want to use Intent technique of the camera because i have to do some image processing afterward.
I have used zooming but strangely zoom is not properly working ...like it works in built-in camera application
here is my surface change code
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
Log.e(TAG, "surfaceChanged");
// XXX stopPreview() will crash if preview is not running
if (mPreviewRunning) {
mCamera.stopPreview();
}
Camera.Parameters params = mCamera.getParameters();
List<Camera.Size> sizes = params.getSupportedPreviewSizes();
mFrameWidth = w;
mFrameHeight = h;
// selecting optimal camera preview size
{
double minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes)
{
if (Math.abs(size.height - h) < minDiff)
{
mFrameWidth = size.width;
mFrameHeight = size.height;
minDiff = Math.abs(size.height - h);
}
}
}
try
{
//params.set("rotation", 180);
//params.set("orientation", "landscape");
//params.set("auto", "WHITE_BALANCE_AUTO");//WHITE_BALANCE_AUTO
Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if(display.getRotation() == Surface.ROTATION_0)
{
params.setPreviewSize(mFrameHeight, mFrameWidth);
mCamera.setDisplayOrientation(90);
}
if(display.getRotation() == Surface.ROTATION_90)
{
params.setPreviewSize(mFrameWidth, mFrameHeight);
}
if(display.getRotation() == Surface.ROTATION_180)
{
params.setPreviewSize(mFrameHeight, mFrameWidth);
}
if(display.getRotation() == Surface.ROTATION_270)
{
params.setPreviewSize(mFrameWidth, mFrameHeight);
mCamera.setDisplayOrientation(180);
}
if(params.isZoomSupported())
{
Log.e(TAG, params.getZoom()+"surfaceChanged camer zoom"+params.getMinExposureCompensation());
params.setZoom(params.getMaxZoom());
params.setExposureCompensation(1);
// params.setColorEffect("none");
params.setWhiteBalance(params.WHITE_BALANCE_AUTO);
params.setFocusMode(params.FOCUS_MODE_AUTO);
params.setSceneMode(params.SCENE_MODE_ACTION);
}
params.set("auto", "FOCUS_MODE_AUTO");
params.setPreviewSize(mFrameWidth,mFrameHeight);
mCamera.setParameters(params);
mCamera.setPreviewDisplay(holder);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
mCamera.startPreview();
mPreviewRunning = true;
}
Kindly let me know how to make the camera preview exactly same as the built in application one.
You mean a fullscreen camera preview?
I use this code:
this.requestWindowFeature(Window.FEATURE_NO_TITLE); //no title
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //no status bar, etc
and this:
setContentView(R.layout.main);
addContentView(overlay, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
((FrameLayout) findViewById(R.id.preview)).addView(preview);
the first snippet sets the app to fullscreen and hide title and status bar.
the second snipppet adds my overlay (extended View) to the main layout.
Here my xml and java code:
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
</LinearLayout>
Overlay.java:
class Overlay extends View {
String text = "";
String textBearing = "Bearing: ";
public Overlay(Context context) {
super(context);
}
#Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setTextSize(16);
canvas.drawText(text, 20, 20, paint);
canvas.drawText(textBearing, 20, 50, paint);
super.onDraw(canvas);
}
}
And my activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE); //no title
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); //fullscreen
overlay = new Overlay(this);
setContentView(R.layout.main);
addContentView(overlay, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
camera = getCameraInstance(); //camera.open();
preview = new Preview(this, camera);
((FrameLayout) findViewById(R.id.preview)).addView(preview);
}
Hope it helps
I encountered the same problem with you. After reading the source code of the builtin camera app, and comparing the focus processing of builtin camera and my own camera, I realized the problem is on autofocus.
So try this:
mCamera.autoFocus(new Camera.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success, Camera camera) {
mCamera.takePicture(null, null, mPicture);
}
});
which makes the result image as sharp as builtin camera.
The documents is here.