在Android中使用矩阵缩放和旋转位图

38

我试图在创建最终位图之前通过单个操作进行缩放和旋转,但是preRotate、postConcat似乎不起作用。

Bitmap bmp = ... original image ...

Matrix m = new Matrix()
m.setScale(x, y);
m.preRotate(degrees, (float) width / 2, (float) height / 2);

Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);

它只应用了缩放而没有旋转。


不确定这是否有帮助..但你尝试过postRotate()吗?或者,你可以在setScale()之前尝试preRotate()吗? - Gopal Nair
@geeknizer 我不认为你已经让它工作了。我想要做到这一点,但是不像下面所述创建两个单独的位图。 - StuStirling
8个回答

45
答案已经给出,但为了让任何阅读此文的人更加清楚:
1)如果您希望在位图中执行一个转换,可以使用SET(setRotate,setScale等)。但是请注意,任何“set”方法的调用都会覆盖其他转换。 就像一个新矩阵一样。 这就是为什么OP的旋转不起作用的原因。 这些调用不是逐行执行的。 当绘制新位图时,它们好像预定在运行时由GPU执行。 像这样,GPU解析矩阵并旋转它,然后创建一个缩放的新矩阵,忽略了先前的矩阵。
2)如果您希望执行多个转换,则必须使用“pre”或“post”方法。
那么,postRotate和preRotate之间有什么区别呢? 好吧,这个矩阵数学不是我的强项,但我知道的是图形卡使用矩阵乘法进行这些变换。 看起来效率要高得多。 而且,据我记得从学校开始,当乘法矩阵的顺序很重要。 A X B!= B X A。 因此,缩放矩阵然后旋转它与旋转矩阵然后缩放它不同。
但是,由于屏幕上的最终结果相同,我们高级程序员通常不需要了解这些区别。 GPU需要。
好吧,在那些执行非常复杂的矩阵操作的罕见情况下,结果并不是您预期的或性能很差,并且您需要深入了解这些方法以修复代码时,android文档无论如何都无法提供太多帮助。 相反,一本好的线性代数书将成为您的最佳伙伴。 ;)

3
"A X B != B X A。因此,缩放矩阵再旋转,与旋转后再缩放矩阵是不同的。[...] 不过最终在屏幕上的结果是相同的。" - 其中,关于单点的旋转和缩放是可交换的(AB = BA);并且只有当它们可交换时才看起来相同。高级程序员需要了解这一点。例如:在三维空间中的旋转是不可交换的 - 尝试将一本书翻转。 - not all wrong
你说得对!我考虑的是参考点总是相同的,但如果缩放和旋转的参考点不同,那当然会有很大的差异! - PFROLIM

40

这是代码

public class Bitmaptest extends Activity {
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        LinearLayout linLayout = new LinearLayout(this);

        // load the origial BitMap (500 x 500 px)
        Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),
               R.drawable.android);

        int width = bitmapOrg.getWidth();
        int height = bitmapOrg.getHeight();
        int newWidth = 200;
        int newHeight = 200;

        // calculate the scale - in this case = 0.4f
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // createa matrix for the manipulation
        Matrix matrix = new Matrix();
        // resize the bit map
        matrix.postScale(scaleWidth, scaleHeight);
        // rotate the Bitmap
        matrix.postRotate(45);

        // recreate the new Bitmap
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,
                          newWidth, newHeight, matrix, true);

        // make a Drawable from Bitmap to allow to set the BitMap
        // to the ImageView, ImageButton or what ever
        BitmapDrawable bmd = new BitmapDrawable(resizedBitmap);

        ImageView imageView = new ImageView(this);

        // set the Drawable on the ImageView
        imageView.setImageDrawable(bmd);

        // center the Image
        imageView.setScaleType(ScaleType.CENTER);

        // add ImageView to the Layout
        linLayout.addView(imageView,
                new LinearLayout.LayoutParams(
                      LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT
                )
        );

        // set LinearLayout as ContentView
        setContentView(linLayout);
    }
}

18
这个答案是错误的。在创建位图时,请使用原始宽度和高度。将“Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg,0,0, newWidth,newHeight,matrix,true);”更改为“Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg,0,0, width,height,matrix,true); ”否则它将简单地裁剪图像的某些部分并忽略矩阵postScale.setScale。 - F.O.O
1
我意识到这可能是一篇旧帖子,但我在那个链接上收到了病毒警告,所以观众要小心。 - AndrewIsOffline
1
@AndrewIsOffline 感谢您的标记。您是正确的,这是旧端口,似乎该网站不再安全。我已经删除了链接。 - Arslan Anwar

1

Canvas保存了一个矩阵堆栈,您可以使用以下方法:

Canvas.save()

Doc: /** * Saves the current matrix and clip onto a private stack. *

* Subsequent calls to translate,scale,rotate,skew,concat or clipRect, * clipPath will all operate as usual, but when the balancing call to * restore() is made, those calls will be forgotten, and the settings that * existed before the save() will be reinstated. * * @return The value to pass to restoreToCount() to balance this save() */

Canvas.restore()

Doc: /** * This call balances a previous call to save(), and is used to remove all * modifications to the matrix/clip state since the last save call. It is * an error to call restore() more times than save() was called. */

example: A custom View(Android) which looks like a rotary knob(e.g. potentiometer)

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    viewX = getWidth();     //views width
    viewY = getHeight();    //views height
    setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); //a must call for every custom view
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    double tempAngel = 3.6 * barValue;
    int deltaX = bitmap.getWidth() / 2;
    int deltaY = bitmap.getHeight() / 2;
    ...
    canvas.save();
    canvas.translate(viewX / 2, viewY / 2);             //translate drawing point to center
    canvas.rotate((float) tempAngel);                   //rotate matrix
    canvas.save();                                      //save matrix. your drawing point is still at (viewX / 2, viewY / 2)
    canvas.translate(deltaX * -1, deltaY * -1);         //translate drawing  point a bit up and left to draw the bitmap in the middle
    canvas.drawBitmap(bitmap, 0,0, bitmapPaint);   // draw bitmap to the tranlated point at 0,0
    canvas.restore();     //must calls...
    canvas.restore();
}


1
所有之前的答案都假设这个位图的更改是在视图中进行的。然而,在我的情况下,我正在进行的更改是为了保存出来。我想回答这个问题,以帮助那些处于类似情况的人们。
有两种方法可以进行翻译。下面的 dx 是 X 轴上的平移量,dy 是 Y 轴上的平移量。其他变量应该是不言自明的。

1 - 图像内的平移(不旋转)

val newBitmap = Bitmap.createBitmap(originalBitmap, dx, dy, newWidth, newHeight, matrix, false)

2 - 复杂矩阵

 matrix.postTranslate(dx, dy)

 val newBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888)

 val canvas = Canvas(newBitmap)
 canvas.drawBitmap(originalBitmap, matrix, null)

1
如果您遇到了OutOfMemory的问题,可以尝试以下方法:
Bitmap MyFinalBitmap = Bitmap.createBitmap(CurrentBitmap, 0, 0,CurrentBitmap.getWidth()/2, CurrentBitmap.getHeight()/2,matrix, true);

1
Matrix rotateMatrix = new Matrix();
rotateMatrix.postRotate(rotation);
rotatedBitmap = Bitmap.createBitmap(loadedImage, 0, 0,loadedImage.getWidth(), loadedImage.getHeight(),rotateMatrix, false);

0
使用矩阵将原始位图的面积缩小到50%并压缩位图,直到其大小小于200k。 {{link1:在Android中将位图压缩到特定字节大小}}

质量很重要。如果你将一张照片缩小到这个程度,那么你最好不要管理这张照片,因为它会变成一个复古的像素土豆。 - Kibotu

0
参考以下代码,看起来是可以工作的。在你的代码中,你将矩阵定义为m,但引用时却使用了matrix
public class FourthActivity extends Activity {
private static final int WIDTH = 50;
private static final int HEIGHT = 50;
private static final int STRIDE = 64;  

private static int[] createColors() {
    int[] colors = new int[STRIDE * HEIGHT];
    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            int r = x * 255 / (WIDTH - 1);
            int g = y * 255 / (HEIGHT - 1);
            int b = 255 - Math.min(r, g);
            int a = Math.max(r, g);
            colors[y * STRIDE + x] = (a << 24) | (r << 16) | (g << 8) | b;
        }
    }
    return colors;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main2);
    final ImageView view1 = (ImageView) findViewById(R.id.imageView1);

    int[] colors = createColors();
    final Bitmap bmp1 = Bitmap.createBitmap(colors, 0, STRIDE, WIDTH, HEIGHT,
    Bitmap.Config.ARGB_8888);
    view1.setImageBitmap(bmp1);
    Button button = (Button) findViewById(R.id.button1);
    button.setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View v) {   
            Matrix matrix = new Matrix();
            matrix.setScale(2, 2);
            matrix.preRotate(45, (float) WIDTH / 2, (float) HEIGHT / 2);

            Bitmap bmp2 = Bitmap.createBitmap(bmp1, 0, 0, 
      bmp1.getWidth(), bmp1.getHeight(), matrix, true);
            ImageView view2 = (ImageView) findViewById(R.id.imageView2);
            view2.setImageBitmap(bmp2);
        }
    });
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接