Only drawing changes with Android View.onDraw() - java

I was surprised to find that View.onDraw() wipes the canvas before drawing. This isn't what I want. What's a good way to retain the previous drawings so that I only need to draw the changes on top of the previous drawings during each call?

There are couple of APIs to define dirty rect for view to invalidate:
public void invalidate(Rect dirty)
public void invalidate(int l, int t, int r, int b)
However more likely the View is redrawn all the way, when for example you swipe it, or another View above it in Z order get's invalidated.
You can try to use setDrawingCacheEnabled and if you are doing the drawing yourself, be sure to cache Bitmaps you are drawing.

I implemented a SurfaceView. Then in I created my own bitmap and canvas that I draw into, to draw to the screen I draw the bitmap to the screen.
For example:
private Canvas myCanvas = null;
private Bitmap myCanvasBitmap = null;
private Matrix identityMatrix;
public mySurfaceCreated(canvasWidth, canvasHeight){
myCanvasBitmap = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888);
myCanvas = new Canvas();
myCanvas.setBitmap(myCanvasBitmap);
identityMatrix = new Matrix();
}
public void myDraw(Canvas canvas) {
// Draw stuff onto myCanvas, NOT the canvas given by the android OS.
drawStuff(myCanvas);
// Draw onto the canvas given by the android OS.
canvas.drawBitmap(myCanvasBitmap, identityMatrix, null);
}
This way I do not need to draw the entire canvas each time, but instead only the needed changes. Note that the canvas created by the OS is still completely redrawn each time.

Related

Full screen background regardless of screen size and keep image aspect ratio

I have a 540x720 background image.
I need to show it in full screen regardless of screen size and resolution (My game will run on desktop and on Android) and regardless of portrait/landscape mode.
I want to keep the aspect ratio of the image, if necessary by cropping the image.
I have already tried ScreenViewport and ExtendViewport but when resizing the window, the background is no longer full screen (for example if the window becomes horizontal) and letter-boxing occurs (showing white side bars).
public class MainMenuScreen implements Screen, InputProcessor {
final MyGame game;
TextureRegionDrawable textureRegionDrawableBackground = new TextureRegionDrawable(new TextureRegion(new Texture(game.backgroundFileName)));
Stage stageBackground = new Stage(new ScreenViewport());
// Stage stageBackground = new Stage(new ExtendViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight())));
Image image = new Image(textureRegionDrawableBackground);
image.setPosition(0, 0);
image.setHeight(Gdx.graphics.getHeight());
// makes the background as tall as the screen while maintaining its aspect ratio
image.setWidth(Gdx.graphics.getHeight() * textureRegionDrawableBackground.getRegion().getRegionWidth() / textureRegionDrawableBackground.getRegion().getRegionHeight());
stageBackground.addActor(image);
}
public void render(float delta) {
stageBackground.getViewport().apply();
stageBackground.draw();
}
public void resize(int width, int height) {
stageBackground.getViewport().update(width, height, true);
}
How to achieve that?
Thanks
Use FillViewport which will maintain aspect ratio and be full screen by cropping part of the image.
See here
https://gamefromscratch.com/libgdx-tutorial-part-17-viewports/

Using Android canvas in different class, debug crash errors

Just starting out with Android, canvas, the works. I have existing Java code that can draw shapes on a graphics object. I am trying to use that code in an Android app with Canvas. Basically, I'm trying to avoid refactoring all of my Java code to use Canvas explicitly. So I'm in the process of making my own "Graphics" object. All it should do is call the appropriate Canvas methods to draw the specified shape.
I've read multiple posts here about not being able to use the canvas object outside of the onDraw() method. From what I understand, you can't pass a canvas object to a different class and expect it to work correctly. But I also do not have a complete understanding of how all this works.
The app crashes in the Graphics class in the drawOval method. I've read up a log about all of this, but I haven't found a good answer as to why this specifically doesn't work.
I haven't been able to find a way to get crash logs in a java friendly way (aka a stacktrace). It just throws Fatal signal 11 (SIGSEGV), code 1, fault addr 0x130 in tid 1520.
Thanks! Let me know if more details are needed. Here's my code:
MainActivity
public class MainActivity extends AppCompatActivity {
MyCanvas canvas;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
canvas = new MyCanvas(this);
setContentView(canvas);
}
}
MyCanvas View
public class MyCanvas extends View {
Graphics graphics;
List<Shape> shapes;
public MyCanvas(Context context) {
super(context);
graphics = new Graphics();
shapes = new ArrayList<>();
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
graphics.setCanvas(canvas); //Sets the canvas object in the graphics class
for (Shape shape : shapes) {
try { //this is in a try/catch block for custom exception handling
//This just calls the specific shapes render method,
//in this case, a circle from the makeShape Method
//The graphics object then calls the specific shape to
//render on the canvas
shape.render(graphics, 0, 0);
} catch (ShapeException e) {
e.printStackTrace();
}
}
invalidate();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
try { //this is in a try/catch block for custom exception handling
makeShape(x, y);
} catch (ShapeException e) {
e.printStackTrace();
}
invalidate();
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
break;
}
}
invalidate();
return true;
}
public void makeShape(int x, int y) throws ShapeException{
Point p = new Point(x, y);
Shape shape = new Circle(p, 50, 50);
shapes.add(shape);
}
}
Graphics
public class Graphics {
private Canvas canvas = null;
public Graphics() {
}
public void setCanvas(Canvas canvas) {
if (this.canvas == null) {
this.canvas = canvas;
}
}
public void drawOval(int x, int y, int width, int height) {
Paint paint1 = new Paint();
paint.setColor(Color.BLACK);
canvas.drawCircle(500, 500, 50, paint1); //App crashes here
}
}
You have a slight misunderstanding about being able to pass a Canvas to different classes. There is absolutely nothing wrong with that; passing a canvas to a class method is effectively the same as passing it to a regular function. Even storing the Canvas reference in a member variable isn't going to hurt anything.
HOWEVER, the above is only true with the understanding that the Canvas cannot be used outside the bounds of the draw()/onDraw() method. That is, any method that uses the Canvas must be called from within onDraw(), or a function called by onDraw(), etc.
This is because the Canvas is initialized (by the framework) immediately before onDraw(), in order to prepare for the current drawing operation. You might wish to think of it as initializing the Canvas with a Bitmap that will serve as the drawing output surface for this particular screen frame (which is not far from the truth). Once onDraw() has returned, the framework assumes your code will no longer be using it, and it can submit the output surface/Bitmap/etc. to the screen compositor for rendering, without fear of further alterations.
And, your approach is a good one, as a technique for adapting existing code to use a novel graphics object.
So, to address the crash: SIGSEGVs should never happen in normal Android development when you're not dealing (directly) with native/JNI routines. However, it occurs to me that you're not updating the Canvas object in the event it changes. This could be causing the crash (in native code, which is why you don't get a Java stack trace). You used an old Canvas after a newer one was given to you. Remove the condition if (this.canvas == null) and you should be fine.

Android camera preview with color filter [duplicate]

This question already has answers here:
How can I manipulate the camera preview?
(3 answers)
Closed 7 years ago.
I have a CameraPreview class. It extends SurfaceView and implements SurfaceHolder.Callback and PreviewCallback. I would like to change image, which camera sends to SurfaceView. It should be done in the following part of the code:
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
Log.d(TAG, "onPreviewFrame");
Canvas canvas = mHolder.lockCanvas();
synchronized (mHolder) {
Paint p = new Paint();
p.setColor(Color.RED);
canvas.drawRect(0, 0, 100, 200, p);
mHolder.unlockCanvasAndPost(canvas);
}
}
But I get following exception:
Exception locking surface
java.lang.IllegalArgumentException
at android.view.Surface.nativeLockCanvas(Native Method)
at android.view.Surface.lockCanvas(Surface.java:243)
Google says that the error occures because of the unlocked canvas, but i always leave it locked.
How can I draw over the image on SurfaceView?
I create a function which apply color filter with exist image, i hope this code is correct.
#Override
protected void overDraw(Canvas canvas) {
Paint p = new Paint();
ColorMatrixColorFilter fl = new ColorMatrixColorFilter(new ColorMatrix(filter));
p.setColorFilter(fl);
canvas.drawPaint(p);
Log.d(TAG, "overDraw");
}
float[] filter = new float[]
{
//r, g, b, a, k
0.33,0.33,0.33,0,0, //r
0.33,0.33,0.33,0,0, //g
0.33,0.33,0.33,0,0, //b
0,0,0,1,0, //a
};
No you cannot change the SurfaceView camera preview. The onPreviewFrame() callback happens independently from drawing the camera preview on the surface. You can overlay the surface with another view, which could mask it. If your manipulation is simple, you can use a TextureView instead and apply some OpenGL shader on the texture received from camera. But usually we simply hide the live preview and push the data received in onPreviewFrame() after manipulation.

Transparent background in NoiseInk (Java)

A friend is making an art piece using NoiseInk https://github.com/trentbrooks/Noise-Ink and would like the background to be transparent instead of black. Ideally he would have this Java app running over a video (hence the transparency). So how could he play a video as the background, or at the very least make the background transparent? I have tried everything I could think of!
The catch is your friend is not clearing the screen, but adding a transparent rectangle to get a trails effect in the easyFade() function:
void easyFade()
{
fill(bgColor,overlayAlpha);
noStroke();
rect(0,0,width,height);//fade background
}
The same can be done with images, but slightly different: simply use the tint() function to add transparency to your background video. Just for testing purposes, say you have an image to test this with (test.png), you can try something like this:
PImage bg;
void setup(){
size(240,240);noStroke();
bg = loadImage("test.png");
}
void draw(){
easyFade();
fill(255);
ellipse(mouseX,mouseY,10,10);
}
void easyFade()
{
tint(255,10);
image(bg,0,0);
}
In your case the video will either be a loaded Movie or a live Capture but the above would still work, since both extend PImage (and you render them to screen with the image() call).
UPDATE
One issue with this approach though is the fade will affect the background video as well.
To keep things separated, you can use another 'layer' using PGraphics:
import processing.video.*;
PGraphics gfx;
Capture video;
int w = 640;
int h = 480;
void setup() {
size(w, h);
video = new Capture(this, w, h,30);
video.start();
gfx = createGraphics(w, h);
gfx.beginDraw();
gfx.background(0);
gfx.endDraw();
// println(Capture.list());
}
void draw(){
image(video,0,0);
pushStyle();
tint(255,127);//tint just the rendered graphics as transparent to see the video beneath
image(gfx,0,0);
popStyle();
//draw your awesome optical flow graphics here
gfx.beginDraw();
gfx.fill(0,10);//same as easyFade() draw transparent bg
gfx.rect(0,0,width,height);
gfx.fill(255);
gfx.ellipse(mouseX,mouseY,10,10);
gfx.endDraw();
}
void captureEvent(Capture c) {
c.read();
}
Again, this would display the graphics faded.
A variation of this would be blending the graphics layer, using the blend() function.
Given that normally the graphics background is dark and you want to overlay the bright graphics, you can use a blend mode like ADD or SCREEN which would make 'background'/dark areas transparent:
import processing.video.*;
PGraphics gfx;
Capture video;
int w = 640;
int h = 480;
void setup() {
size(w, h);
video = new Capture(this, w, h,30);
video.start();
gfx = createGraphics(w, h);
gfx.beginDraw();
gfx.background(0);
gfx.endDraw();
// println(Capture.list());
}
void draw(){
image(video,0,0);
blend(gfx,0,0,w,h,0,0,w,h,SCREEN);//blend the rendered graphics on top of the video using SCREEN mode
//draw your awesome optical flow graphics here
gfx.beginDraw();
gfx.fill(0,10);//same as easyFade() draw transparent bg
gfx.rect(0,0,width,height);
gfx.fill(255);
gfx.ellipse(mouseX,mouseY,10,10);
gfx.endDraw();
}
void captureEvent(Capture c) {
c.read();
}
Have fun playing with these! :)

Creating transparent circle cutout in a View in Android

I'm trying to create a translucent help overlay to be displayed over my activity's main screen when the user first opens the app. I would like to highlight a button contained in the main layout (and inflated using setContentView), by "cutting out" a section of the overlay which corresponds to the position of the button, and make the cutout transparent.
The overlay is a programmatically-created view (which extends RelativeLayout) which is added to my activity's main FrameLayout, like this:
private void addHelpOverlay(){
HelpOverlay help = new HelpOverlay(this);
help.setBackgroundColor(Color.parseColor("#BB222222"));
mainLayer.addView(help);
}
public class HelpOverlay extends RelativeLayout{
public HelpOverlay(Context context){
super(context);
}
#Override
public void dispatchDraw(Canvas canvas){
canvas.drawColor(Color.parseColor("#BB222222"));
Paint mPaint = new Paint();
mPaint.setColor(0xFFFFFF);
mPaint.setAlpha(0);
mPaint.setAntiAlias(true);
canvas.drawCircle(buttonX, buttonY, 100, mPaint);
super.dispatchDraw(canvas);
}
}
The above code doesn't actually show anything, just the translucent layout with no circle cutout. I assume this is because it's just drawing a transparent circle over top of the translucent layout. I'm really struggling to accomplish this, any suggestions would be appreciated!
Try adding PorterDuff to your paint object . which will make the specific area to transparent
Paint mPaint = new Paint();
mPaint.setColor(0xFFFFFF);
mPaint.setAlpha(0);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
canvas.drawCircle(buttonX, buttonY, 100, mPaint);
If you get a picth black in the circle area, that must be due to graphic rendering problem , you can enable it using below code right before you declare paint object .
if (android.os.Build.VERSION.SDK_INT >= 11) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
I guess this should fix your issue

Categories

Resources