How do you use canvas.drawBitmap natively in an extension?

What is the native way to use canvas.drawBitmap to draw directly on Canvas?

I'm using the method below to try to copy the bitmap directly to Canvas, but the result is not good. Help me!

private void copyBitmapToCanvas(Bitmap bitmap) {
        if (flagLog) Log.d(LOG_NAME, "Copying bitmap to canvas");
        if (bitmap == null || bitmap.isRecycled()) {
            if (flagLog) Log.d(LOG_NAME, "Bitmap is null or recycled");
            return;
        }

        try {
            this.canvas.Clear(); // Limpa o Canvas atual
            
            android.graphics.Canvas c = new android.graphics.Canvas(this.bitmap);
            this.view.draw(c);
            
            this.view.invalidate(); // Notifica que a view deve ser redesenhada
            if (flagLog) Log.d(LOG_NAME, "Bitmap copy successful!");
        } catch (Exception e) {
            if (flagLog) Log.e(LOG_NAME, "Error copying bitmap to canvas", e);
        }
    }

Rest of code:

package com.bosonshiggs.extendedcanvas.helpers;

import com.google.appinventor.components.runtime.Canvas;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Paint;

import java.util.LinkedList;
import java.util.Queue;

import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import java.util.Stack;

//import java.awt.Point;

import android.util.Log;

public class FloodFillHandler {
    private android.view.View view;
    private int width, height;
    Bitmap bitmap;
    Canvas canvas;
    
    private Stack<Bitmap> undoStack = new Stack<>();
    private Stack<Bitmap> redoStack = new Stack<>();
    
    private String LOG_NAME = "ExtendedCanvas";
    private boolean flagLog = false;
    
    public void setCanvas(Canvas canvas) {
    	Log.d(LOG_NAME, "Setting canvas");
        this.view = canvas.getView();
        this.width = canvas.Width();
        this.height = canvas.Height();
        this.canvas = canvas;
        
        //recycleBitmap(this.bitmap);
        this.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    }
    
    private void recycleBitmap(Bitmap bitmap) {
        if (this.bitmap != null && !this.bitmap.isRecycled()) {
            this.bitmap.recycle();
        }
    }
	
    public void clearMemory() {
        if (flagLog) Log.d(LOG_NAME, "Clearing memory");
        clearStack(undoStack);
        clearStack(redoStack);
        recycleBitmap(this.bitmap); // Só recicle this.bitmap se você for recriá-lo depois
        this.bitmap = null; // Defina como null após a reciclagem
        this.canvas.Clear();
    }

    private void clearStack(Stack<Bitmap> stack) {
    	if (flagLog) Log.d(LOG_NAME, "Clearing stack");
        while (!stack.isEmpty()) {
            Bitmap bitmap = stack.pop();
            if (bitmap != null) {
                bitmap.recycle(); // Libera os recursos associados ao bitmap
            }
        }
    }
     
    private void copiesCurrentState() {
        try {
            updateDimensions(); // Garante que as dimensões estejam atualizadas

            if (this.bitmap == null || this.bitmap.getWidth() != this.width || this.bitmap.getHeight() != this.height) {
                recycleBitmap(this.bitmap);
                this.bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888);
            }

            // Redesenha a view no bitmap existente
            android.graphics.Canvas androidCanvas = new android.graphics.Canvas(this.bitmap);
            this.view.draw(androidCanvas);

            if (flagLog) Log.d(LOG_NAME, "State copied successfully!");
        } catch (Exception e) {
            if (flagLog) Log.e(LOG_NAME, "Error copying state", e);
        }
    }

    private void updateDimensions() {
        this.width = this.view.getWidth();
        this.height = this.view.getHeight();
    }

    private Bitmap createBitmap(int width, int height) {
        Bitmap currentBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        android.graphics.Canvas c = new android.graphics.Canvas(currentBitmap);
        this.view.layout(0, 0, width, height);
        this.view.draw(c);
        return currentBitmap;
    }
	
    public void saveCurrentState() {
        if (flagLog) Log.d(LOG_NAME, "Saving current state");
        copiesCurrentState();

        Bitmap currentState = Bitmap.createBitmap(this.bitmap);
        undoStack.push(currentState);
        clearStack(redoStack);

        if (flagLog) Log.d(LOG_NAME, "Canvas state saved");
    }
	
    private void copyBitmapToCanvas(Bitmap bitmap) {
        if (flagLog) Log.d(LOG_NAME, "Copying bitmap to canvas");
        if (bitmap == null || bitmap.isRecycled()) {
            if (flagLog) Log.d(LOG_NAME, "Bitmap is null or recycled");
            return;
        }

        try {
            this.canvas.Clear(); // Limpa o Canvas atual
            
            android.graphics.Canvas c = new android.graphics.Canvas(this.bitmap);
            this.view.draw(c);
            
            this.view.invalidate(); // Notifica que a view deve ser redesenhada
            if (flagLog) Log.d(LOG_NAME, "Bitmap copy successful!");
        } catch (Exception e) {
            if (flagLog) Log.e(LOG_NAME, "Error copying bitmap to canvas", e);
        }
    }

	
    public void undo() {
    	if (flagLog) Log.d(LOG_NAME, "Performing undo");
        if (!undoStack.isEmpty()) {
            this.canvas.Clear();
            Bitmap previousState = undoStack.pop();
            if (previousState == null || previousState.isRecycled()) {
                //Log.d(LOG_NAME, "Previous state is null or recycled");
                return;
            }
            // Recicle o bitmap atual após empurrá-lo para redoStack
            redoStack.push(Bitmap.createBitmap(this.bitmap));
            recycleBitmap(this.bitmap); 
            this.bitmap = previousState;
            copyBitmapToCanvas(previousState);
        }
        this.view.invalidate();
    }

    public void redo() {
        Log.d(LOG_NAME, "Performing redo");
        if (!redoStack.isEmpty()) {
            this.canvas.Clear();
            
            Bitmap nextState = redoStack.pop();
            if (nextState == null || nextState.isRecycled()) {
            	if (flagLog) Log.d(LOG_NAME, "Next state is null or recycled");
                return;
            }
            
            // Recicle o bitmap atual após empurrá-lo para undoStack
            undoStack.push(Bitmap.createBitmap(this.bitmap));
            recycleBitmap(this.bitmap);
            this.bitmap = nextState;
            copyBitmapToCanvas(nextState);
        }
        this.view.invalidate();
    }
//The other non-relevant methods have been hidden
}

Note: My idea is to replace the pixel-by-pixel copy of my post with something more efficient.

I noticed that within the Canvas.java file there are already some notes for "bugs" that were reported by the developers themselves. This hasn't been improved for year...

I not sure what exactly you are trying to do, I assume you are trying to draw something on the canvas's component using CanvasView, you can get a canvas view by calling CanvasComponent.getView() and casing it to a CanvasView

I read the code and tried to implement it but it gave an error. The biggest problem is not copying but drawing the modified bitmap on Canvas.

I'm not sure, but

Have you looked into these methods?

Yes, but the idea of ​​my extension is to save the Canvas states with Indo() and Redo(). O código atual está copiando pixel a pixel, mas gostaria de algo mais eficiente como drawBitmap.

In the example below I'm creating a canvas from and arrangement but it should help.

public Canvas c;
public SimpleDrawingView canvas2;

 @SimpleFunction(description="Create Canvas")

  public void CreateCanvas(AndroidViewComponent arrangement) {

    ViewGroup vg = (ViewGroup)arrangement.getView();
    canvas2 = new SimpleDrawingView(context);
    
    vg.addView(canvas2);
  }


 @SimpleFunction(description="Draw Image from file")

  public void DrawImage(int x, int y, String imageFile) {
    float correctedX = x * Density();
    float correctedY = y * Density();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    Bitmap bitmap = BitmapFactory.decodeFile(imageFile, options);
    
    c.drawBitmap(bitmap, correctedX, correctedY, null);
    canvas2.invalidate();
  }

Good example, but you still can't edit the native Canvas component of MIT App Inventor.

Can you tell a little bit more about your use case? A Canvas is composed of three parts, so depending on what you're trying to accomplish you may have to take different approaches.

  1. Background drawable: either a color drawable or a bitmap drawable depending on whether the user has specified a background iamge
  2. Drawing bitmap: the various methods for drawing lines, arcs, etc. draw to this bitmap
  3. Sprites: the individual sprite layers

Access to 1 and a combination of 1 and 2 should be possible. Writing to 1 is straightforward but 1 and 2 would likely be harder since much of API is private. I assume managing the sprites is not something you're interested but please help us understand more about what you're trying to really achieve.

My idea is to copy the Canvas bitmap and save it on the stack to be used later by the Undo and Redo

I'm doing it this way below but Canvas is not updated correctly.

// get Canvas
android.view.View canvasView = canvasComponent.getView();

// Create a Bitmap to store the Canvas image
Bitmap combinedBitmap = Bitmap.createBitmap(canvasView.getWidth(), canvasView.getHeight(), Bitmap.Config.ARGB_8888);
android.graphics.Canvas combinedCanvas = new android.graphics.Canvas(combinedBitmap);
canvasView.draw(combinedCanvas);

This draws the canvas to the bitmap you've created only. Once you have the combinedBitmap, you'd want to set the background image of the canvas to it.

This is the problem, I'm currently copying pixel-by-pixel and saving it to the canvas in the same way. I would like a native method to do this.