Crop whitespace from image in PHP

34,827

Solution 1

To trim all whitespace, as you call it, surrounding the interesting part of the image, first we find out where the "whitespace" stops, and then we copy everything inside of those borders.

//load the image
$img = imagecreatefromjpeg("http://ecx.images-amazon.com/images/I/413XvF0yukL._SL500_AA280_.jpg");

//find the size of the borders
$b_top = 0;
$b_btm = 0;
$b_lft = 0;
$b_rt = 0;

//top
for(; $b_top < imagesy($img); ++$b_top) {
  for($x = 0; $x < imagesx($img); ++$x) {
    if(imagecolorat($img, $x, $b_top) != 0xFFFFFF) {
       break 2; //out of the 'top' loop
    }
  }
}

//bottom
for(; $b_btm < imagesy($img); ++$b_btm) {
  for($x = 0; $x < imagesx($img); ++$x) {
    if(imagecolorat($img, $x, imagesy($img) - $b_btm-1) != 0xFFFFFF) {
       break 2; //out of the 'bottom' loop
    }
  }
}

//left
for(; $b_lft < imagesx($img); ++$b_lft) {
  for($y = 0; $y < imagesy($img); ++$y) {
    if(imagecolorat($img, $b_lft, $y) != 0xFFFFFF) {
       break 2; //out of the 'left' loop
    }
  }
}

//right
for(; $b_rt < imagesx($img); ++$b_rt) {
  for($y = 0; $y < imagesy($img); ++$y) {
    if(imagecolorat($img, imagesx($img) - $b_rt-1, $y) != 0xFFFFFF) {
       break 2; //out of the 'right' loop
    }
  }
}

//copy the contents, excluding the border
$newimg = imagecreatetruecolor(
    imagesx($img)-($b_lft+$b_rt), imagesy($img)-($b_top+$b_btm));

imagecopy($newimg, $img, 0, 0, $b_lft, $b_top, imagesx($newimg), imagesy($newimg));

//finally, output the image
header("Content-Type: image/jpeg");
imagejpeg($newimg);

My old example, that assumes an identical "border" on all sides of the image, just to clarify the comments :)

//load the image
$img = imagecreatefromjpeg("img.jpg");

//find the size of the border.
$border = 0;
while(imagecolorat($img, $border, $border) == 0xFFFFFF) {
  $border++;
}

//copy the contents, excluding the border
//This code assumes that the border is the same size on all sides of the image.
$newimg = imagecreatetruecolor(imagesx($img)-($border*2), imagesy($img)-($border*2));
imagecopy($newimg, $img, 0, 0, $border, $border, imagesx($newimg), imagesy($newimg));

//finally, if you want, overwrite the original image
imagejpeg($newimg, "img.jpg");

Solution 2

Gnud's script redundantly calls imagesx and imagesy. It also iterates every pixel on every side, even when the corners overlap. This improved version eliminates redundant function calls and checks every pixel only once, granting a significant increase in speed. The function returns a status ($result['#']) equal to 2 if every pixel is the trimmed.

example();
function example(){
    $img = imagecreatefromjpeg("http://ecx.images-amazon.com/images/I/413XvF0yukL._SL500_AA280_.jpg");

    // find the trimmed image border
    $box = imageTrimBox($img);

    // copy cropped portion
    $img2 = imagecreate($box['w'], $box['h']);
    imagecopy($img2, $img, 0, 0, $box['l'], $box['t'], $box['w'], $box['h']);

    // output cropped image to the browser
    header('Content-Type: image/png');
    imagepng($img2);

    imagedestroy($img);
    imagedestroy($img2);
}



function imageTrimBox($img, $hex=null){
if (!ctype_xdigit($hex)) $hex = imagecolorat($img, 0,0);
$b_top = $b_lft = 0;
$b_rt = $w1 = $w2 = imagesx($img);
$b_btm = $h1 = $h2 = imagesy($img);

do {
    //top
    for(; $b_top < $h1; ++$b_top) {
        for($x = 0; $x < $w1; ++$x) {
            if(imagecolorat($img, $x, $b_top) != $hex) {
                break 2;
            }
        }
    }

    // stop if all pixels are trimmed
    if ($b_top == $b_btm) {
        $b_top = 0;
        $code = 2;
        break 1;
    }

    // bottom
    for(; $b_btm >= 0; --$b_btm) {
        for($x = 0; $x < $w1; ++$x) {
            if(imagecolorat($img, $x, $b_btm-1) != $hex) {
                break 2;
            }
        }
    }

    // left
    for(; $b_lft < $w1; ++$b_lft) {
        for($y = $b_top; $y <= $b_btm; ++$y) {
            if(imagecolorat($img, $b_lft, $y) != $hex) {
                break 2;
            }
        }
    }

    // right
    for(; $b_rt >= 0; --$b_rt) {
        for($y = $b_top; $y <= $b_btm; ++$y) {
            if(imagecolorat($img, $b_rt-1, $y) != $hex) {
                break 2;
            }
        }

    }

    $w2 = $b_rt - $b_lft;
    $h2 = $b_btm - $b_top;
    $code = ($w2 < $w1 || $h2 < $h1) ? 1 : 0;
} while (0);

// result codes:
// 0 = Trim Zero Pixels
// 1 = Trim Some Pixels
// 2 = Trim All Pixels
return array(
    '#'     => $code,   // result code
    'l'     => $b_lft,  // left
    't'     => $b_top,  // top
    'r'     => $b_rt,   // right
    'b'     => $b_btm,  // bottom
    'w'     => $w2,     // new width
    'h'     => $h2,     // new height
    'w1'    => $w1,     // original width
    'h1'    => $h1,     // original height
);
}

Solution 3

I know this is pretty old but if you have ImageMagick enabled you can use this method

Trim Image

Solution 4

PHP's gd library has the imagecropauto function (PHP version 5.5+):

<?php 
$img=imagecreatefrompng("tux.png"); // Load and instantiate the image
if($img) {
  $cropped=imagecropauto($img,IMG_CROP_DEFAULT); // Auto-crop the image

  imagedestroy($img); // Clean up as $img is no longer needed

  header("Content-type: image/png"); // Set the appropriate header so the browser
                                     // knows how to present it
  imagepng($cropped); // Return the newly cropped image
}

By default imagecropauto will try to crop using transparency, and then fall back on using the 4 corners of the image to attempt to detect the background to crop; I have also had success with the following constants in place of IMG_CROP_AUTO in the example above:

  • IMG_CROP_BLACK - Useful for images with a black background
  • IMG_CROP_WHITE - Useful for images with a white background
  • IMG_CROP_THRESHOLD - Allows you to set a colour and threshold to use when cropping

Solution 5

I realize this is quite old but I have a slightly different take on trimming an image via GD. Instead of doing just one side at a time - do all four. It is faster and less expensive cpu-wise in some ways. However, if you stop the FOR loops the moment you find the top-bottom-left-right sides - that is faster than this.

So first there is:

#
#   Do all four sides at once
#
        echo "Finding the top-left-bottom-right edges of the image...please wait.\n";
        $top = 99999;
        $bot = -99999;
        $left = 99999;
        $right = -99999;
        for( $x=$offset; $x<($w-$offset); $x++ ){
            for( $y=$offset; $y<($h-$offset); $y++ ){
                $rgb = imagecolorat( $gd, $x, $y );
                if( $color != $rgb ){
                    $left = ($x < $left) ? $x : $left;
                    $right = ($x > $right) ? $x : $right;
                    $top = ($y < $top) ? $y : $top;
                    $bot = ($y > $bot) ? $y : $bot;
                    }
                }
            }

and then there is:

#
#   Top
#
            echo "Finding the top of the image\n";
            $top = null;
            for( $y=$offset; $y<($h-$offset); $y++ ){
                for( $x=$offset; $x<($w-$offset); $x++ ){
                    $rgb = imagecolorat( $gd, $x, $y );
                    if( $color != $rgb ){ $top = $y; break; }
                    }

                if( !is_null($top) ){ break; }
                }
#
#   Bottom
#
            echo "Finding the bottom of the image\n";
            $bot = null;
            for( $y=($h-$offset); $y>$offset; $y-- ){
                for( $x=$offset; $x<($w-$offset); $x++ ){
                    $rgb = imagecolorat( $gd, $x, $y );
                    if( $color != $rgb ){ $bot = $y; break; }
                    }

                if( !is_null($bot) ){ break; }
                }
#
#   Left
#
            echo "Finding the left of the image\n";
            $left = null;
            for( $x=$offset; $x<($w-$offset); $x++ ){
                for( $y=$offset; $y<($h-$offset); $y++ ){
                    $rgb = imagecolorat( $gd, $x, $y );
                    if( $color != $rgb ){ $left = $x; break; }
                    }

                if( !is_null($left) ){ break; }
                }
#
#   right
#
            echo "Finding the right of the image\n";
            $right = null;
            for( $x=($w-$offset); $x>$offset; $x-- ){
                for( $y=$offset; $y<($h-$offset); $y++ ){
                    $rgb = imagecolorat( $gd, $x, $y );
                    if( $color != $rgb ){ $right = $x; break; }
                    }

                if( !is_null($right) ){ break; }
                }

In both cases, the $color variable contains the first color dot in the image:

$color = imagecolorat( $gd, 0, 0 );

This is because in GIF images - the first dot is 99% of the time the transparent (or background) color. Also, the $offset is (for me) a way to say I know the image is only going to be so wide and so high. So if I draw something that is only a maximum of 256 by 256 but I put it on a 1024 x 1024 background I can whack off some of that background and make an offset of 255 thus making the FOR loops only go from 255 to (1024-255) or 769.

Ok - before someone asks - WHY I would do such a thing - Because some fonts (like Bastarda) don't have the correct font information in them and a 256pt output of the letter "z" produces an image where the bottom of the "z" goes past 256 (down to something like 512) so in order to get the entire image you have to start (or end) farther down than what you'd think the font would go. So I split the difference and whack off 255 pixels from either end. This was after actually seeing that Bastarda does this.

Some additional notes:

1. PNG images you CAN set up to be like GIF images but normally you will have to specify what the background color is going to be.
2. JPEG images do NOT uncompress the exact same way each time. So even comparing the same image you loaded twice might not work the same and may give different sizes.
3. These routines work best on simple black and white (or two color) images. Multiple colors can throw these routines off. Especially if you decide to use tolerances.
4. To use tolerances to determine if you have found the edge of an image, all you have to do is to pre-compute both the high and low tolerance (ie: if you have a tolerance of five(5) on the red component, then you can compute the tolerance as EITHER X-5-to-x+5 OR x-2.5-to-x+2.5 depending upon if you want the tolerance to be the WHOLE range or just the +/- range). You can have a tolerance for the RED, GREEN, BLUE, and ALPHA parts of the color or the entire color itself. So there are several different tolerances you can compute if you want and all of them are the correct way to do it depending upon your needs.

Share:
34,827
usertest
Author by

usertest

Updated on January 18, 2022

Comments

  • usertest
    usertest over 2 years

    Is it possible to remove the whitespace surrounding an image in PHP?

    NOTE: to clarify I mean something like photoshops trim feature.

    Thanks.