Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cropping image lowers quality and border looks bad

Tags:

java

android

Using some math, i created the following java-function, to input a Bitmap, and have it crop out a centered square in which a circle is cropped out again with a black border around it. The rest of the square should be transparent. Additionatly, there is a transparent distance to the sides to not damage the preview when sending the image via Messengers.

The code of my function is as following:

 public static Bitmap edit_image(Bitmap src,boolean makeborder) {
        int width = src.getWidth();
        int height = src.getHeight();
        int A, R, G, B;
        int pixel;

        int middlex = width/2;
        int middley = height/2;

        int seitenlaenge,startx,starty;
        if(width>height) 
        {
            seitenlaenge=height;
            starty=0;

            startx = middlex - (seitenlaenge/2);
        }
        else 
        {
            seitenlaenge=width; 
            startx=0;

            starty = middley - (seitenlaenge/2);
        }

        int kreisradius = seitenlaenge/2;
        int mittx = startx + kreisradius;
        int mitty = starty + kreisradius;
        int border=2;
        int seitenabstand=55;

        Bitmap bmOut = Bitmap.createBitmap(seitenlaenge+seitenabstand, seitenlaenge+seitenabstand, Bitmap.Config.ARGB_8888);
        bmOut.setHasAlpha(true);

        for(int x = 0; x < width; ++x) {
            for(int y = 0; y < height; ++y) {
                int distzumitte = (int) (Math.pow(mittx-x,2) + Math.pow(mitty-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
                distzumitte = (int) Math.sqrt(distzumitte);

                pixel = src.getPixel(x, y);

                A = Color.alpha(pixel);
                R = (int)Color.red(pixel);
                G = (int)Color.green(pixel);
                B = (int)Color.blue(pixel);
                int color = Color.argb(A, R, G, B);

                int afterx=x-startx+(seitenabstand/2);
                int aftery=y-starty+(seitenabstand/2);

                if(x < startx || y < starty || afterx>=seitenlaenge+seitenabstand || aftery>=seitenlaenge+seitenabstand) //seitenrand
                {
                    continue;
                }
                else if(distzumitte > kreisradius)
                {
                    color=0x00FFFFFF;
                }
                else if(distzumitte > kreisradius-border && makeborder) //border
                {
                    color = Color.argb(A, 0, 0, 0);
                }
                bmOut.setPixel(afterx, aftery, color);
            }
        }

        return bmOut;
    }

This function works fine, but there are some problems occuring that i wasn't able to resolve yet.

  • The quality of the image is decreased significantly
  • The border is not really round, but appears to be flat at the edges of the image (on some devices?!)

I'd appreciate any help regarding that problems. I got to admit that i'm not the best in math and there should probably be a better formula to ceate the border.

like image 583
James Cameron Avatar asked Jul 17 '13 15:07

James Cameron


3 Answers

your source code is hard to read, since it is a mix of German and English in the variable names. Additionally you don't say which image library you use, so we don't exactly know where the classes Bitmap and Color come from.

Anyway, it is very obvious, that you are operating only on a Bitmap. Bitmap means the whole image is stored in the RAM pixel by pixel. There is no lossy compression. I don't see anything in your source code, that can affect the quality of the image.

It is very likely, that the answer is in the Code that you don't show us. Additionally, what you describe (botrh of the problems) sounds like a very typical low quality JPEG compression. I am sure, somewhere after you call you function, you convert/save the image to a JPEG. Try to do that at that position to BMP, TIFF or PNG and see that the error disappears magically. Maybe you can also set the quality level of the JPEG somewhere to avoid that.

To make it easier for others (maybe) also to find a good answer, please allow me to translate your code to English:

    public static Bitmap edit_image(Bitmap src,boolean makeborder) {
        int width = src.getWidth();
        int height = src.getHeight();
        int A, R, G, B;
        int pixel;

        int middlex = width/2;
        int middley = height/2;

        int sideLength,startx,starty;
        if(width>height) 
        {
            sideLength=height;
            starty=0;

            startx = middlex - (sideLength/2);
        }
        else 
        {
            sideLength=width; 
            startx=0;

            starty = middley - (sideLength/2);
        }

        int circleRadius = sideLength/2;
        int middleX = startx + circleRadius;
        int middleY = starty + circleRadius;
        int border=2;
        int sideDistance=55;

        Bitmap bmOut = Bitmap.createBitmap(sideLength+sideDistance, sideLength+sideDistance, Bitmap.Config.ARGB_8888);
        bmOut.setHasAlpha(true);

        for(int x = 0; x < width; ++x) {
            for(int y = 0; y < height; ++y) {
                int distanceToMiddle = (int) (Math.pow(middleX-x,2) + Math.pow(middleY-y,2)); // (Xm-Xp)^2 + (Ym-Yp)^2 = dist^2
                distanceToMiddle = (int) Math.sqrt(distanceToMiddle);

                pixel = src.getPixel(x, y);

                A = Color.alpha(pixel);
                R = (int)Color.red(pixel);
                G = (int)Color.green(pixel);
                B = (int)Color.blue(pixel);
                int color = Color.argb(A, R, G, B);

                int afterx=x-startx+(sideDistance/2);
                int aftery=y-starty+(sideDistance/2);

                if(x < startx || y < starty || afterx>=sideLength+sideDistance || aftery>=sideLength+sideDistance) //margin
                {
                    continue;
                }
                else if(distanceToMiddle > circleRadius)
                {
                    color=0x00FFFFFF;
                }
                else if(distanceToMiddle > circleRadius-border && makeborder) //border
                {
                    color = Color.argb(A, 0, 0, 0);
                }
                bmOut.setPixel(afterx, aftery, color);
            }
        }

        return bmOut;
    }
like image 58
Kenyakorn Ketsombut Avatar answered Oct 20 '22 23:10

Kenyakorn Ketsombut


I think that you need to check PorterDuffXferMode.

You will find some technical informations about compositing images modes HERE.

There is some good example of making bitmap with rounded edges HERE. You just need to tweak a bit source code and you're ready to go...

Hope it will help.

like image 38
Pawel Cala Avatar answered Oct 20 '22 22:10

Pawel Cala


Regarding the quality I can't see anything wrong with your method. Running the code with Java Swing no quality is lost. The only problem is that the image has aliased edges.

The aliasing problem will tend to disappear as the screen resolution increases and would be more noticeable for lower resolutions. This might explain why you see it in some devices only.The same problem applies to your border but in that case it would be more noticable since the color is single black.

Your algorithm defines a square area of the original image. To find the square it starts from the image's center and expand to either the width or the height of the image whichever is smaller. I am referring to this area as the square.

The aliasing is caused by your code that sets the colors (I am using pseudo-code):

if ( outOfSquare() ) {
    continue;  // case 1: this works but you depend upon the new image' s default pixel value i.e. transparent black
} else if ( insideSquare() && ! insideCircle() ) {
    color = 0x00FFFFFF;  // case 2: transparent white. <- Redundant
} else if ( insideBorder() ) {  
    color = Color.argb(A, 0, 0, 0);  // case 3: Black color using the transparency of the original image. 
} else { // inside the inner circle 
    // case 4: leave image color
}

Some notes about the code:

  • Case 1 depends upon the default pixel value of the original image i.e. transparent black. It works but better to set it explicitly
  • Case 2 is redundant. Handle it in the same way you handle case 1. We are only interested in what happens inside the circle.
  • Case 3 (when you draw the border) is not clear what it expects. Using the alpha of the original image has the potential of messing up your new image if it happens that the original alpha varies along the circle's edges. So this is clearly wrong and depending on the image, can potentially be another cause of your problems.
  • Case 4 is ok.

Now at your circle's periphery the following color transitions take place:

  • If border is not used: full transparency -> full image color (case 2 and 4 in the pseudocode)
  • If border is used: full transparency -> full black -> full image color (cases 2, 3 and 4)

To achieve a better quality at the edges you need to introduce some intermediate states that would make the transitions smoother (the new transitions are shown in italics):

  • Border is not used: full transparency -> partial transparency with image color -> full image color
  • Border is used: full transparency -> partial transparency of Black color -> full Black color -> partial transparency of Black color + Image color (i.e. blending) -> Full image color

I hope that helps

like image 1
c.s. Avatar answered Oct 20 '22 22:10

c.s.