Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw lines on an ImageView using canvas which are unaffected by zooming or scaling?

I am drawing lines on an ImageView by doing something like this:

    Bitmap imageBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
    Bitmap duplicateBitmap = Bitmap.createBitmap(imageBitmap.getWidth(),imageBitmap.getHeight(),Bitmap.Config.RGB_565);

    Canvas targetCanvas = new Canvas(duplicateBitmap);
    targetCanvas.drawBitmap(imageBitmap,0,0,null);
    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStrokeWidth(3f);
    targetCanvas.drawLine(0f,100f, imageBitmap.getWidth(),100f,paint);
    imageView.setImageDrawable(new BitmapDrawable(getResources(),duplicateBitmap));

Which looks okay when not zoomed in.

But when I zoom in the line becomes thick. (Which is expected behavior)

Now, how to draw lines on an image view so that when the user zooms in the thickness of the line is unaffected?

P.S: I tried "zooming in" the image first and then draw lines on the image. (upon button click or something). But that didn't work, the line was still very thick.

I also found that when the image is of lower resolution the drawn lines are thicker. So I assume that drawing on an image is somehow related to image resolution too.

I was thinking of masking the ImageView with a high res canvas but that would be memory inefficient. Also, I am clueless about how to actually implement this.

I somehow believe that the answers to this question can solve my problem.

like image 488
Shreemaan Abhishek Avatar asked Sep 23 '20 08:09

Shreemaan Abhishek


2 Answers

The correct approach is to render the lines dynamically using a View canvas, and not into the bitmap itself.

The reason why your implementation fails, is because if you render the grid directly into the bitmap, then you are effectively changing the bitmap pixels, so logically if you zoom the bitmap then the grid pixels will also zoom, as everything bitmap + grid have become the same single image.

A solution can be to use any ready-made zoom image view widget. You'll find lots in GitHub such as for example:

ZoomImageView

Then you have 2 options, to extend the widget to create your own subclass, or to perform the implementation directly into the widget code. Is up to you.

What you need to do in any of both cases, is to override its onDraw callback, and perform the grid drawing right after the super.onDraw(canvas) call. No need to create new bitmaps or new canvas instances. Use the method's canvas to perform the drawing.

@Override
protected void onDraw(final Canvas canvas) {
    
    super.onDraw(canvas);

    // Render the grid

    final int slotWidth  = this.getWidth() / 3;
    final int slotHeight = this.getHeight() / 3;

    this.paint.setColor(this.gridColor);

    // Horizontal lines
    canvas.drawLine(0, slotHeight, this.getWidth(), slotHeight, this.paint);
    canvas.drawLine(0, (slotHeight * 2), this.getWidth(), (slotHeight * 2), this.paint);

    // Vertical lines
    canvas.drawLine(slotWidth, 0, slotWidth, this.getHeight(), this.paint);
    canvas.drawLine((slotWidth * 2), 0, (slotWidth * 2), this.getHeight(), this.paint);
}

As a side note, avoid creating new objects inside the onDraw method, such as Paint instances, etc. as the onDraw can be called several times in a short period. Lint should warn you anyway about this. Never create new object instances in methods such as onDraw, onMeasure, etc., for such scenarios always precache/reuse them.

A simpler alternative, is to extend a View and create a new dedicated grid view widget, unrelated to the actual Zoom Image View. Perform the same, so, also override the onDraw method as the above example, and render the grid as shown. Then place this new View into your layout design, exactly on top of your Zoom Image View control, ensuring both controls have the same metrics.

like image 99
PerracoLabs Avatar answered Oct 18 '22 22:10

PerracoLabs


Use this TouchImageView library for zooming and detecting the zoom via OnTouchImageViewListener and isZoomed(). If zoomed, then redraw the canvas using the zoomed area. This is the code:

touchImageView.setOnTouchImageViewListener(() -> {
    if (touchImageView.isZoomed()){
        //gets the zoomed area
        Bitmap result = Bitmap.createBitmap(touchImageView.getWidth(), touchImageView.getHeight(), Bitmap.Config.RGB_565);
        Canvas c = new Canvas(result);
        touchImageView.draw(c);
    }
});
like image 38
Sina Avatar answered Oct 19 '22 00:10

Sina