fhucho
fhucho

Reputation: 34530

Draw smoothly scaled bitmaps on Canvas

This is how I draw Bitmap on Canvas in my Android app:

canvas.save();
canvas.scale(scale, scale, x, y);
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();

However the Bitmap is not scaled smoothly, no anti-aliasing is performed. How can I enable anti-aliasing?

Upvotes: 33

Views: 44842

Answers (5)

fkybrd
fkybrd

Reputation: 21

Row and column skipping is available in API 33. Shared source codes didn't work correct. Smooth rescaling is not a practial algorithm for dynamic game graphics. This algorithm is useful with pre-defined graphics. If there is a finder, share it please.


Edit: Mosaic rescale algorithm is here for Bitmap objects:

UnpackedColor.java

import java.util.Random;
import android.graphics.Color;
import java.lang.Math;
public class UnpackedColor {
 public float A, R, G, B;
 public UnpackedColor(
  float A,
  float R,
  float G,
  float B
  ) {
  this.A = A;
  this.R = R;
  this.G = G;
  this.B = B;
  }
 public UnpackedColor(int packedColor) {
  A = Color.alpha(packedColor) / 255f;
  R = Color.red(packedColor) / 255f;
  G = Color.green(packedColor) / 255f;
  B = Color.blue(packedColor) / 255f;
  }
 public void sum(UnpackedColor ref) {
  if (ref == null) return;
  A += ref.A;
  R += ref.R;
  G += ref.G;
  B += ref.B;
  }
 public void div(float divider) {
  A /= divider;
  R /= divider;
  G /= divider;
  B /= divider;
  }
 public void pow(float value) {
  A *= value;
  R *= value;
  G *= value;
  B *= value;
  }
 public int pack() {
  int Ar = Math.round(255 * A);
  int Rr = Math.round(255 * R);
  int Gr = Math.round(255 * G);
  int Br = Math.round(255 * B);
  if (Ar > 255) Ar = 255;
  else if (Ar < 0) Ar = 0;
  if (Rr > 255) Rr = 255;
  else if (Rr < 0) Rr = 0;
  if (Gr > 255) Gr = 255;
  else if (Gr < 0) Gr = 0;
  if (Br > 255) Br = 255;
  else if (Br < 0) Br = 0;
  return Color.argb(Ar, Rr, Gr, Br);
  }
 }

Rescale.java

import android.graphics.Bitmap;
import java.lang.Math;
public class Rescale {
 public static Bitmap filterBitmap(Bitmap refBitmap, float ratio) {
  int newWidth = Math.round(refBitmap.getWidth() * ratio);
  int newHeight = Math.round(refBitmap.getHeight() * ratio);
  if (newWidth <= 0) {
   newWidth = 1;
   }
  if (newHeight <= 0) {
   newHeight = 1;
   }
  return Rescale.filterBitmap(refBitmap, newWidth, newHeight);
  }
 public static Bitmap filterBitmap(Bitmap refBitmap, float widthRatio, float heightRatio) {
  int newWidth = Math.round(refBitmap.getWidth() * widthRatio);
  int newHeight = Math.round(refBitmap.getHeight() * heightRatio);
  if (newWidth <= 0) {
   newWidth = 1;
   }
  if (newHeight <= 0) {
   newHeight = 1;
   }
  return Rescale.filterBitmap(refBitmap, newWidth, newHeight);
  }
 public static Bitmap filterBitmap(Bitmap refBitmap, int newWidth, int newHeight) {
  int width = refBitmap.getWidth();
  int height = refBitmap.getHeight();
  Bitmap ret = Bitmap.createBitmap(
   newWidth,
   newHeight,
   Bitmap.Config.ARGB_8888
   );
  int gridXPointer = 0;
  float gridXPointerF = 0.0f;
  float gridXPointerF2 = 0.0f;
  float gridYPointerF = 0.0f;
  float gridYPointerF2 = 0.0f;
  UnpackedColor loopTmpColor = null;
  UnpackedColor unpackedRetPixel = null;
  float retWeight = 0.0f;
  float tmpYWeight = 0.0f;
  float tmpWeight = 0.0f;
  float refXF = 0.0f;
  float refYF = 0.0f;
  float loopF = 0.0f;
  float loop2F = 0.0f;
  float loopFMod = 0.0f;
  float loop2FMod = 0.0f;
  int refXFInt = 0;
  int refYFInt = 0;
  float refXFMod = 0.0f;
  float refYFMod = 0.0f;
  float refXFFirstMod = 0.0f;
  float loopFInt = 0.0f;
  float loop2FInt = 0.0f;
  for (int gridYPointer = 0; gridYPointer < newHeight; gridYPointer++) {
   gridYPointerF = (gridYPointer * (float)height) / (float)newHeight;
   gridYPointerF2 = ((gridYPointer + 1) * (float)height) / (float)newHeight;
   gridXPointer = 0;
   for (; gridXPointer < newWidth; gridXPointer++) {
    unpackedRetPixel = new UnpackedColor(
     0.0f,
     0.0f,
     0.0f,
     0.0f
     );
    gridXPointerF = (gridXPointer * (float)width) / (float)newWidth;
    gridXPointerF2 = ((gridXPointer + 1) * (float)width) / (float)newWidth;
    retWeight = 0.0f;
    refYF = gridYPointerF;
    refYFMod = refYF % 1.0f;
    loopF = gridYPointerF2;
    loop2F = gridXPointerF2;
    loopFMod = loopF % 1.0f;
    loop2FMod = loop2F % 1.0f;
    loopFInt = (int)loopF;
    loop2FInt = (int)loop2F;
    retWeight = 0;
    for (; refYF < loopF; refYF += 1.0f) {
     refYFInt = (int)refYF;
     if (refYFInt == loopFInt) {
      tmpYWeight = loopFMod - refYFMod;
      }
     else if (refYFMod > 0.0f) {
      tmpYWeight = 1.0f - refYFMod;
      }
     else {
      tmpYWeight = 1.0f;
      }
     refXF = gridXPointerF;
     refXFMod = refXFFirstMod;
     for (; refXF < loop2F; refXF += 1.0f) {
      refXFInt = (int)refXF;
      loopTmpColor = new UnpackedColor(
       refBitmap.getPixel(
        refXFInt,
        refYFInt
        )
       );
      tmpWeight = tmpYWeight;
      if (refXFInt == loop2FInt) {
       tmpWeight *= loop2FMod - refXFMod;
       }
      else if (refXFMod > 0.0f) {
       tmpWeight *= 1.0f - refXFMod;
       }
      else {
       tmpWeight *= 1.0f;
       }
      loopTmpColor.pow(tmpWeight);
      unpackedRetPixel.sum(loopTmpColor);
      retWeight += tmpWeight;
      if (refXFMod > 0.0f) {
       refXF = refXFInt;
       refXFMod = 0.0f;
       }
      }
     if (refYFMod > 0.0f) {
      refYF = refYFInt;
      refYFMod = 0.0f;
      }
     }
    unpackedRetPixel.div(retWeight);
    ret.setPixel(
     gridXPointer,
     gridYPointer,
     unpackedRetPixel.pack()
     );
    }
   }
  return ret;
  }
 }

Using the filter:

Bitmap b = ((BitmapDrawable)getResources().getDrawable(R.mipmap.icon)).getBitmap();
b = Rescale.filterBitmap(b, 0.75f);
//Or
b = Rescale.filterBitmap(b, 0.75f, 1.25f);
//Or
b = Rescale.filterBitmap(b, 250, 500);

Upvotes: 0

Ingo
Ingo

Reputation: 5381

You need only one Line of Code:

canvas.drawBitmap(bitmap, x, y, new Paint(Paint.ANTI_ALIAS_FLAG)); 

Not 5 Lines

Upvotes: -1

Sileria
Sileria

Reputation: 15632

Both Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); or paint.setFilterBitmap(true); worked for me but be very careful, on my game it cut down the FPS from 30FPS to 17FPS only. So if it is a mission critical drawing like in a game you better scale the image at loading time. Which I did in the following manner:

public Bitmap getImage (int id, int width, int height) {
    Bitmap bmp = BitmapFactory.decodeResource( getResources(), id );
    Bitmap img = Bitmap.createScaledBitmap( bmp, width, height, true );
    bmp.recycle();
    return img;
}

Upvotes: 19

Vit Khudenko
Vit Khudenko

Reputation: 28418

Try this:

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
paint.setDither(true);

canvas.drawBitmap(bitmap, x, y, paint);

Upvotes: 81

mreichelt
mreichelt

Reputation: 12455

Have you tried creating a Paint object, calling setAntiAlias(true) on it and passing it to the drawBitmap method as the 4th parameter? If this does not work I guess you should scale down the drawBitmap call instead of scaling the Canvas, e.g. by using drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint).

Upvotes: 2

Related Questions