How to draw a bitmap to another, but into a given quadrilateral (not necessary a rectangle)?
Asked Answered
W

3

1

Suppose I have 2 bitmaps. One is smallBitmap, and one is largeBitmap.

I want to draw the entire smallBitmap into largeBitmap, but only to a part of largeBitmap, and not in a straight regtangle, but into a quadrilateral instead.

I think a sketch will best describe what I mean:

enter image description here

An example of this scenario is a tilted smartphone image (like this or this), that you need to put a screenshot into its screen.

The input is: smallBitmap, largeBitmap, "quadrilateral" coordinates of the largeBitmap (where to put the smallBitmap).

The "quadrilateral" of the largeBitmap just has 4 coordinates and it's not necessary a rectangle. It could be a parallelogram or trapezoid, for example.

I need to scale the smallBitmap into the quadrilateral within the largeBitmap, and also support center-crop scaling, so that it won't get distorted

I also need to know how to treat texts the same way, but I guess it's about the same solution.


Here's something that I've tried, but it doesn't even scale:

    //mBigBitmap: size is 720x1280
    //mSmallBitmap: size is 720x720
    mLeftTop = new Point(370, 358);
    mRightTop = new Point(650, 384);
    mLeftBot = new Point(375, 972);
    mRightBot = new Point(660, 942);
    Canvas canvas = new Canvas(mBigBitmap);
    final Matrix matrix = new Matrix();
    matrix.setPolyToPoly(new float[]{0, 0,
                    mBigBitmap.getWidth() - 1, 0,
                    0, mBigBitmap.getHeight() - 1,
                    mBigBitmap.getWidth() - 1, mBigBitmap.getHeight() - 1},
            0,
            new float[]{mLeftTop.x, mLeftTop.y,
                    mRightTop.x, mRightTop.y,
                    mLeftBot.x, mLeftBot.y,
                    mRightBot.x, mRightBot.y
            }
            , 0, 4);
    canvas.drawBitmap(mSmallBitmap, matrix, new Paint());
Woodchopper answered 24/8, 2015 at 10:53 Comment(1)
see Matrix.setPolyToPolyArndt
W
1

Found an answer based on this post.

It seems that Matrix cannot be used as it can't create Trapezoid shapes which can occur in a 3d world.

So what is suggested there is to use the "Camera" class, as such:

    Canvas canvas = new Canvas(bigBitmap);
    Matrix matrix = new Matrix();
    Camera camera = new Camera();
    camera.save();
    camera.translate(...,...,0);
    camera.rotateX(...);
    camera.rotateY(...);
    camera.rotateZ(...);
    camera.getMatrix(matrix);
    int centerX = bigBitmap.getWidth() / 2;
    int centerY = bigBitmap.getHeight() / 2;
    matrix.preTranslate(-centerX, -centerY); //This is the key to getting the correct viewing perspective
    matrix.postTranslate(centerX, centerY);
    canvas.concat(matrix);
    camera.restore();
    canvas.drawBitmap(mSmallBitmap, matrix, new Paint());

Sadly, as you see, the coordinates aren't being used, so you need to either play with the numbers till you get it right, or find a formula to convert between the coordinates and the needed values.

I won't mark this answer as the correct one, because it doesn't fully fit the requirements of the original question (no coordinates are being used).

Plus I can't find how to deal with text while using this solution.

However, it does work, so it might be useful for others.


EDIT: It appears that the reason for setPolyToPoly to not scale the image at all, is that the first input array was incorrect: It was set as the size of the large bitmap, instead of the small one.

So, this is the correct code:

mLeftTop = new Point(370, 358);
mRightTop = new Point(650, 384);
mLeftBot = new Point(375, 972);
mRightBot = new Point(660, 942);
Canvas canvas = new Canvas(mBigBitmap);
final Matrix matrix = new Matrix();
matrix.setPolyToPoly(new float[]{0, 0,
                mSmallBitmap.getWidth() - 1, 0,
                0, mSmallBitmap.getHeight() - 1,
                mSmallBitmap.getWidth() - 1, mSmallBitmap.getHeight() - 1},
        0,
        new float[]{mLeftTop.x, mLeftTop.y,
                mRightTop.x, mRightTop.y,
                mLeftBot.x, mLeftBot.y,
                mRightBot.x, mRightBot.y
        }
        , 0, 4);
canvas.concat(matrix);
final Paint paint = new Paint();
paint.setAntiAlias(true);
canvas.drawBitmap(mSmallBitmap, 0, 0, paint);

However, for center-cropping, it still has this issue, but if you know the correct size of the rectangle before it got tilted, you can do the cropping before, and set it as the input.

As for the text, this is possible as usual, as the canvas stays with the matrix that was created.

Woodchopper answered 25/8, 2015 at 9:24 Comment(30)
"Sadly, as you see, the coordinates aren't being used, so you need to either play with the numbers till you get it right, or find a formula to convert between the coordinates and the needed values." this is because you are using wrong API (Camera), the correct API is Matrixand its setPolyToPoly method which uses two arrays of exact coordinates: four [x, y] points in 2D space, as you can see Camera API returns the Matrix, the same Matrix which is computed by setPolyToPoly method call (i wouldn't be surprised if it used setPolyToPoly code internally)Arndt
@Arndt If you know how to fix the code, please write an alternative in a new answer. That's what I've found.Woodchopper
@Arndt Sadly, this is one of the things I've already tried. You can even see my comment there. This solutions doesn't scale the image, and also doesn't center-crop it.Woodchopper
well, so it seems to have to use Camera API (i'm afraid you will not find anywhere the answer to your exact question, unless you play a bit with it, for example by calling Canvas.drawPoints and seeing what are the coordinates of dst float array)Arndt
@Arndt Sorry. English isn't my main language. I don't understand what you mean. Do you think I can still use setPolyToPoly ? Or is it true that I need to use the Camera API ? And what did Romain Guy mean that it can suffice, yet not even this method is correct?Woodchopper
of course you can use it, but you have to play with the API, do some tests, experiments, i don't know what Romain Guy is saying...Arndt
@Arndt I mean, do you think it can do what I need ? About experiments, I've worked on this for a few hours, but nothing worked, so I've found the Camera solution which might be what I will use.Woodchopper
just use 0, 0, 10, 0, 10, 10, 0, 10 as src and 0, 0, 10, 0, 15, 10, 5, 10 as dst and see what happens, then multiply each item in dst by 2 and see what happens, study my code, study the code from above link, whats so hard in that?Arndt
As I've already written, I've done the research using this function, and it didn't scale to the dimentions I've wanted it to. You probably use the same aspect ratio of the bitmap as the target, so you don't have issues. I need center crop scaling.Woodchopper
I'm not at work right now, but as I wrote, I've tried this approach and failed. I have tight schedule and not always I can spend days on something. Usually it's a matter of few hours.Woodchopper
but again, if you can find a way to use this function using the sample sizes and coordinates I've written, please do write it down. It might be better than what I've done.Woodchopper
post an exact image of what you want, with a detailed 4 coordinates of your quadrilateralArndt
@Arndt I can't put the images, as it's property of the company, but I've already written about the requirements, here: stackoverflow.com/questions/32180534/… . I think the sizes have changed, but the idea is still the same : draw from one bitmap into another, in a specific quadrilateral, and have the ability to use center-crop scaling in addition to the normal one. I also need to do it for texts. As I've found it makes the text have bad quality, sadly.Woodchopper
what you have here: i.sstatic.net/OcZC8.png is a sinple polyToPoly call result, no extra steps required, see: dropbox.com/s/9kjn66p1h0jyst8/poly-debug.apk?dl=0Arndt
@Arndt Sorry, this still doesn't work. Please look at the code I've written in the question. I've added even more information. It doesn't stretch for me, let alone center-crop. On your example I see it stretches, but doesn't center crop.Woodchopper
what center crop? where do you see center crop here: i.sstatic.net/OcZC8.png? how would it look like?Arndt
The image is just a sketch. An illustration of what I need. Just take any image on the web, and try to stretch it this way. I've even given you the sizes of the images that I test. Is the code I've written ok ? What is wrong with it exactly ?Woodchopper
i have no idea what is your meaning of "center crop" i gave you an apk which draws exactly what you wanted in your picture, if i dont't know what you want how can i say whats wrong?Arndt
This is not exactly as I've wanted at all. The code I've written doesn't work - it doesn't stretch the image. And your APK doesn't seem to center crop. The image I've shown is an illustration. I'm not a designer. I can't make it perfect. center crop is a feature available on ImageView, so that if the bitmap's aspect ration is different than the imageView, it will crop around the bitmap in order to fit into the imageView's and keep the aspect ratio. This way, there is not distortion.Woodchopper
Try to think about it this way: you have an image of a smartphone in an angle, and you wish to put any kind of image into the screen of it. I need to fill the screen with content, but without distortion. I've also added links.Woodchopper
sorry, but without seeing what you really want any other activity in this thread is a waste of time imhoArndt
@Arndt I didn't know you don't know what center-crop is. Here's a nice set of samples showing what it means: akira-watson.com/android/imageview_scaletype.html . I need to have support for this on quadrilateral part of a large bitmap. The small bitmap will fit itself into the quadrilateral inside the large bitmap, and I will be able to make it scale in a center-crop way. I also need to have the ability to put text into the quadrilateral, so that it will be tilted&scaled correctly just like the small bitmap.Woodchopper
i know what center-crop is when it comes to ImageView, but center-crop makes no sense to Matrix distorted BitmapArndt
@Arndt Why doesn't it make sense? we live in a 3d world, so if I take my device and have an imageview in it with center-crop of a bitmap, and I tilt my device, I will see the imageView tilted (in my perspective) too. It will be exactly the same as here.Woodchopper
see i.sstatic.net/OcZC8.png is the right image distorted or not? now: "I need to scale the smallBitmap into the quadrilateral within the largeBitmap, and also support center-crop scaling, so that it won't get distorted", so how it cant be distorted when it is distorted by a "Matrix perspective" ?Arndt
@Arndt It's scaled. Not distorted. The image I've shown is an illustration. Not the exact 1-to-1 thing I want. I am not a designer. It's just a sketch.Woodchopper
@Arndt This is the question I've asked all along. If you know how to solve it or you know what's wrong with the code I've written, please write down about it.Woodchopper
with "large bitmap" (tilted smartphone image) still using only setPolyToPoly (with no center crop as i dont know what it means in your definition): dropbox.com/s/7ua5ohdaoc33gmu/poly-debug%20%281%29.apk?dl=0Arndt
@Arndt I don't understand what you are showing. What is the original smallBitmap look like? Why is there animation? Where is the code for this? Where is the part that you show that it stretches in center-crop way?Woodchopper
nothing is stretched in center-crop way, because i have no idea what it means, i just draw what i like on the screen (in this case a yellow text and a moving red rectangle: i added that little animation as a proof that image shown is not a fake image made by a photoshop or something...) and if you insist on "smallBitmap" to be drawn, here you have one: a blue layer with a hole inside is a "smallBitmap": dropbox.com/s/9kjn66p1h0jyst8/poly-debug.apk?dl=0Arndt
G
0

to skew a bitmap,probably Matrix can be handy.

   /*use values accordingly*/
   Matrix matrix = new Matrix();
   matrix.postScale(curScale, curScale);  
   matrix.postRotate(curRotate);
   matrix.postSkew(curSkewX, curSkewY);

   Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);
   myImageView.setImageBitmap(resizedBitmap);
Gorgonzola answered 24/8, 2015 at 11:4 Comment(4)
I don't know the skew and rotation values. Only the target coordinates of the rectangle of the other bitmap. updated question to make it clearer.Woodchopper
you can use rotation value as zero and scale values as (1,1) since you do not need to scale or either rotate. try matrix.postSkew (float kx, float ky, float px, float py) where px and py are pivot coordinates.Gorgonzola
I don't need to create another bitmap. I write to the largeBitmap. Also, px and py are just 2 floats, and I have 4 coordinates. What should I do with them?Woodchopper
In addition, skewing causes a shape of parallelogram, but I can't make it have the shape of trapezoid.Woodchopper
G
0

For my answer I am drawing a smaller Bitmap on to a larger Bitmap then drawing that on to a SurfaceView.

  1. Use the bounding quadrilateral to create a bounding rectangle.
  2. Use the bounding rectangle to create a transformation Matrix
  3. Use Matrix.ScaleToFit.CENTER to fill the bounding rectangle to the maximum size possible for the smaller Bitmap.

After these steps are taken just draw to the canvas the bigger Bitmap is using. The bounding quadrilateral is drawn red, the bounding rectangle blue and the big Bitmap is drawn green. Replace your smaller Bitmap with the blue Bitmap (bounding rectangle).

public class MainActivity extends Activity {
final String TAG = this.getClass().getName();

SurfaceView surfaceView;
Bitmap bitmap;
Bitmap bigBitmap;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
    surfaceView.getHolder().addCallback(new SurfaceHolder.Callback2() {
        @Override
        public void surfaceRedrawNeeded(SurfaceHolder holder) {

        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            Canvas surfaceCanvas = holder.lockCanvas();

            surfaceCanvas.drawBitmap(bigBitmap, 0, 0, new Paint());

            holder.unlockCanvasAndPost(surfaceCanvas);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    });

    bitmap = Bitmap.createBitmap(64, 192, Bitmap.Config.ARGB_8888);
    {
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        canvas.drawRect(0, 0, 64, 192, paint);
    }

    bigBitmap = Bitmap.createBitmap(768,768, Bitmap.Config.ARGB_8888);
    {
        Canvas canvas = new Canvas(bigBitmap);

        // Fill background - For visual reference
        Paint paint = new Paint();
        paint.setColor(Color.GREEN);
        canvas.drawRect(0, 0, bigBitmap.getWidth(), bigBitmap.getHeight(), paint);

        // Setup transformation
        Matrix matrixPoly = new Matrix();
        Log.i(TAG, "matrixPoly: " + matrixPoly);

        // Draw Quadrilateral - For visual reference
        boolean canScale;
        canScale = matrixPoly.setPolyToPoly(new float[]{0,0, 64,0, 0,192, 64,192},
                0,
                new float[]{32,32, 96,16, 16,300, 128,256},
                0,
                4);

        Log.i(TAG, "matrixPoly: " + matrixPoly);
        Log.i(TAG, "matrixPoly canScale: " + canScale);

        canvas.drawBitmap(bitmap, matrixPoly, new Paint());

        // Points of Quadrilateral
        // {32,32, 96,16, 16,300, 128,256}
        float rectInQLeft = Math.max(32, 16);
        float rectInQTop = Math.min(32, 16);
        float rectInQRight = Math.min(96, 128);
        float rectInQBottom = Math.max(300, 256);
        ;
        Matrix matrixRect = new Matrix();
        Log.i(TAG, "matrixRect: " + matrixRect);
        canScale = matrixRect.setRectToRect(new RectF(0, 0, 64, 192),
                new RectF(rectInQLeft, rectInQTop, rectInQRight, rectInQBottom),
                Matrix.ScaleToFit.CENTER);

        Log.i(TAG, "matrixRect: " + matrixRect);
        Log.i(TAG, "matrixRect canScale: " + canScale);

        // Draw scaled bitmap
        Canvas smallBitmapCanvas = new Canvas(bitmap);
        Paint smallBitmapPaint = new Paint();
        smallBitmapPaint.setColor(Color.BLUE);
        smallBitmapCanvas.drawRect(0, 0, 64, 192, smallBitmapPaint);

        canvas.drawBitmap(bitmap, matrixRect, new Paint());
    }
}
Gora answered 25/8, 2015 at 21:53 Comment(10)
Seems like something I've already tried. Where's the part that make it stretch and do a center-crop stretching?Woodchopper
Is the quadrilateral really just for bounding? That would make the transformation of the small bitmap completely different! I'll take a look at that.Gora
I have reworked my answer, the quadrilateral is now used as a bounding area to create a bounding rectangle.Gora
I didn't mention putting a frame around. Look at the image I've shown on the question. I want it to scale, with the ability of center-crop scaling. I also need to do it for text. As an example, look a this image of a smartphone : theinquirer.net/IMG/252/312252/… . Now, choose any image that you wish from the internet, and try to set it as the content of the screen of the smartphone.Woodchopper
Here's another example of an smartphone image that I should be able to draw into: cdn2.vox-cdn.com/thumbor/mPWCe0qOA1GRjJpFe8RojqCBPp4=/… .Woodchopper
If you could take a look at this: w3schools.com/cssref/tryit.asp?filename=trycss3_js_perspective and replace the transform line with: transform: rotateX(15deg) rotateY(15deg) rotateZ(0deg); Is this what you mean, to move one image into the perspective of the other, while maintaining the aspect ratio (in the perspective)?Gora
Yes, this is what I need. I've run it on Chrome. Both before and after the change is what I need, but not only for text. I need it for bitmaps too, and also have the ability to keep the aspect ration in exchange for cropping (what's called "center-crop" on Android, as shown here: akira-watson.com/android/imageview_scaletype.html ). You don't have to write so much code. Just show the minimal one based on the requirements. No need for UI related stuff (SurfaceView etc...). For all I care, it can be shown on an ImageView as it's a bitmap.Woodchopper
You can call this effect "star wars effect", as other people have written on other posts. the content is tilted in a way that shows as if it's in 3d.Woodchopper
Transformation must be done in 3d space. OpenGL is available.Gora
There are multiple ways to transform images in the 3d space without really using SurfaceView. You can even have animations on views that will transform them using many 3d functions.Woodchopper

© 2022 - 2024 — McMap. All rights reserved.