3

I m using GD library to create images on the fly.
But when i rotate image using imagerotate() function
it works fine but it gives very much irritating rough edges of image
which is rotated.
as it is shown in this picture.
So how to make these sides/edges of rotated image smooth ?
enter image description here

Hemen Ashodia
  • 172
  • 3
  • 20
  • 1
    Have you seen http://stackoverflow.com/questions/4801900/php-image-quality-issue-on-rotating-and-merging? – j08691 Nov 30 '12 at 21:46
  • yes and even i have tried to run that code but it didn't work for me @j08691 – Hemen Ashodia Nov 30 '12 at 21:55
  • Do you have the code you're currently using and an example image you're attempting to rotate? You should also look at http://stackoverflow.com/questions/13540231/rotate-a-png-then-resave-with-image-transparency/13540666 – MatsLindh Nov 30 '12 at 22:13
  • Maybe it can be done in two steps: 1. Create big image and rotate it. 2. Scale it with imagecopyresampled. – Sergiy T. Nov 30 '12 at 22:15
  • i thought about the same idea but it creates performance issue @SergiyT. – Hemen Ashodia Dec 01 '12 at 07:29

3 Answers3

6

One way to avoid from getting the Jaggies effect when rotating images is by using another way to sample the pixels than just taking the adjusted pixels, for example to use Nearest-neighbor interpolation to make the edges smoother. You can see matlab code example:

im1 = imread('lena.jpg');imshow(im1);  
[m,n,p]=size(im1);
thet = rand(1);
mm = m*sqrt(2);
nn = n*sqrt(2);
for t=1:mm
   for s=1:nn
      i = uint16((t-mm/2)*cos(thet)+(s-nn/2)*sin(thet)+m/2);
      j = uint16(-(t-mm/2)*sin(thet)+(s-nn/2)*cos(thet)+n/2);
      if i>0 && j>0 && i<=m && j<=n           
         im2(t,s,:)=im1(i,j,:);
      end
   end
end
figure;
imshow(im2);

taken from (here). Basically it means that when sampling the pixels in the original picture, we sample near pixels and interpolate them to get the target pixel value. This way you can achive waht you want withourt installing any additiional packages.

EDIT

I've found some old code I once wrote in Java, which contains implementations of a couple of sampling algorithems. Here is the code:

Nearest Neighbor sampler:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by nearest neighbor sampling
 */
private Pixel sampleNearestNeighbor(double x, double y) {
    int X = (int) Math.round(x);
    int Y = (int) Math.round(y);
    if (X >= 0 && Y >= 0 && X < this.pixels.length
            && Y < this.pixels[0].length)
        // (X,Y) is within this.pixels' borders
        return new Pixel(pixels[X][Y].getRGB());
    else
        return new Pixel(255, 255, 255);
    // sample color will be default white
}

Bilinear sampler:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by bilinear interpolation
 */
private Pixel sampleBilinear(double x, double y) {
    int x1, y1, x2, y2;
    x1 = (int) Math.floor(x);
    y1 = (int) Math.floor(y);
    double weightX = x - x1;
    double weightY = y - y1;
    if (x1 >= 0 && y1 >= 0 && x1 + 1 < this.pixels.length
            && y1 + 1 < this.pixels[0].length) {
        x2 = x1 + 1;
        y2 = y1 + 1;

        double redAX = (weightX * this.pixels[x2][y1].getRed())
                + (1 - weightX) * this.pixels[x1][y1].getRed();
        double greenAX = (weightX * this.pixels[x2][y1].getGreen())
                + (1 - weightX) * this.pixels[x1][y1].getGreen();
        double blueAX = (weightX * this.pixels[x2][y1].getBlue())
                + (1 - weightX) * this.pixels[x1][y1].getBlue();
        // bilinear interpolation of A point

        double redBX = (weightX * this.pixels[x2][y2].getRed())
                + (1 - weightX) * this.pixels[x1][y2].getRed();
        double greenBX = (weightX * this.pixels[x2][y2].getGreen())
                + (1 - weightX) * this.pixels[x1][y2].getGreen();
        double blueBX = (weightX * this.pixels[x2][y2].getBlue())
                + (1 - weightX) * this.pixels[x1][y2].getBlue();
        // bilinear interpolation of B point

        int red = (int) (weightY * redBX + (1 - weightY) * redAX);
        int green = (int) (weightY * greenBX + (1 - weightY) * greenAX);
        int blue = (int) (weightY * blueBX + (1 - weightY) * blueAX);
        // bilinear interpolation of A and B
        return new Pixel(red, green, blue);

    } else if (x1 >= 0
            && y1 >= 0 // last row or column
            && (x1 == this.pixels.length - 1 || y1 == this.pixels[0].length - 1)) {
        return new Pixel(this.pixels[x1][y1].getRed(), this.pixels[x1][y1]
                .getGreen(), this.pixels[x1][y1].getBlue());
    } else
        return new Pixel(255, 255, 255);
    // sample color will be default white
}

Gaussian sampler:

/**
 * @pre (this!=null) && (this.pixels!=null)
 * @post returns the sampled pixel of (x,y) by gaussian function
 */
private Pixel sampleGaussian(double u, double v) {
    double w = 3; // sampling distance
    double sqrSigma = Math.pow(w / 3.0, 2); // sigma^2
    double normal = 0;
    double red = 0, green = 0, blue = 0;
    double minIX = Math.round(u - w);
    double maxIX = Math.round(u + w);
    double minIY = Math.round(v - w);
    double maxIY = Math.round(v + w);

    for (int ix = (int) minIX; ix <= maxIX; ix++) {
        for (int iy = (int) minIY; iy <= maxIY; iy++) {
            double sqrD = Math.pow(ix - u, 2) + Math.pow(iy - v, 2);
            // squared distance between (ix,iy) and (u,v)
            if (sqrD < Math.pow(w, 2) && ix >= 0 && iy >= 0
                    && ix < pixels.length && iy < pixels[0].length) {
                // gaussian function
                double gaussianWeight = Math.pow(2, -1 * (sqrD / sqrSigma));
                normal += gaussianWeight;
                red += gaussianWeight * pixels[ix][iy].getRed();
                green += gaussianWeight * pixels[ix][iy].getGreen();
                blue += gaussianWeight * pixels[ix][iy].getBlue();
            }
        }
    }
    red /= normal;
    green /= normal;
    blue /= normal;
    return new Pixel(red, green, blue);
}

Actual rotate:

     /**
 * @pre (this!=null) && (this.pixels!=null) && (1 <= samplingMethod <= 3)
 * @post creates a new rotated-by-degrees Image and returns it
 */
public myImage rotate(double degrees, int samplingMethod) {
    myImage outputImg = null;

    int t = 0;
    for (; degrees < 0 || degrees >= 180; degrees += (degrees < 0) ? 180
            : -180)
        t++;

    int w = this.pixels.length;
    int h = this.pixels[0].length;
    double cosinus = Math.cos(Math.toRadians(degrees));
    double sinus = Math.sin(Math.toRadians(degrees));

    int width = Math.round((float) (w * Math.abs(cosinus) + h * sinus));
    int height = Math.round((float) (h * Math.abs(cosinus) + w * sinus));

    w--;
    h--; // move from (1,..,k) to (0,..,1-k)

    Pixel[][] pixelsArray = new Pixel[width][height];
    double x = 0; // x coordinate in the source image
    double y = 0; // y coordinate in the source image

    if (degrees >= 90) { // // 270 or 90 degrees turn
        double temp = cosinus;
        cosinus = sinus;
        sinus = -temp;
    }

    for (int i = 0; i < width; i++) {
        for (int j = 0; j < height; j++) {

            double x0 = i;
            double y0 = j;
            if (degrees >= 90) {
                if ((t % 2 == 1)) { // 270 degrees turn
                    x0 = j;
                    y0 = width - i - 1;
                } else { // 90 degrees turn
                    x0 = height - j - 1;
                    y0 = i;
                }
            } else if (t % 2 == 1) { // 180 degrees turn
                x0 = width - x0 - 1;
                y0 = height - y0 - 1;
            }

            // calculate new x/y coordinates and
            // adjust their locations to the middle of the picture
            x = x0 * cosinus - (y0 - sinus * w) * sinus;
            y = x0 * sinus + (y0 - sinus * w) * cosinus;

            if (x < -0.5 || x > w + 0.5 || y < -0.5 || y > h + 0.5)
                // the pixels that does not have a source will be painted in
                // default white
                pixelsArray[i][j] = new Pixel(255, 255, 255);
            else {
                if (samplingMethod == 1)
                    pixelsArray[i][j] = sampleNearestNeighbor(x, y);
                else if (samplingMethod == 2)
                    pixelsArray[i][j] = sampleBilinear(x, y);
                else if (samplingMethod == 3)
                    pixelsArray[i][j] = sampleGaussian(x, y);
            }
        }
        outputImg = new myImage(pixelsArray);
    }

    return outputImg;
}
Community
  • 1
  • 1
Kuf
  • 15,678
  • 4
  • 61
  • 85
2

It may sound rather hack-ish, but it is the simplest way to do it, and even big enterprise solutions use this.

The trick is to first create the image 2X the size of what you need, then do all the drawing calls and then resize it to required original size.

Not only it is really easy to do, but also it is as fast as it gets and it produces very nice results. I use this trick for all cases when I need to apply blur to edges.

Another advantate to this is that it does not include blur on the rest of the image and it remains crisp clear - only borders of rotated image gets smoothed.

NewProger
  • 2,625
  • 7
  • 33
  • 54
0

One thing you could try is to use imageantialias() to smooth the edges.

If that doesn't suit your needs, GD itself will probably not suffice.

GD uses very fast methods for all it's abilites without any actual smoothing or anything like that involved. If you want some proper image editing, you could either look into ImageMagick (which requires extra-software on the server) or write your own functions based on GD.

Keep in mind though, that php is really slow with huge amounts of data, so writing your own functions might be disappointing. (From my experience, PHP is roughly 40 times slower than compiled code.)

I recommend using ImageMagick for any image work where result quality matters.

Cobra_Fast
  • 14,395
  • 8
  • 51
  • 97