Remove black edges from existing image using the GD library - php

I'm downloading an image that already has black edges. Note: this isn't a result of me resizing an image. How can I use the GD library to detect and remove these black edges?
UPDATE
This is the cropped image using the script

I was able to come up with a time-consuming fix to this. Do the images being stored need to be stored with those black borders? It'd be much better if you could run every image with the black borders through the following script (using php to loop through every image in the directory) and let php override the old, black-bordered image with the new, borderless image.
The approach I took was to create 4 loops:
To look at black borders on the right (loop through x -> loop through y)
To look at black borders on the left (loop through x -> loop through y)
To look at black borders on the bottom (loop through y -> loop through x)
To look at black borders on the top (loop through y -> loop through x)
Now, each of these loops had another loop in them which would loop through the other coordinate (ie., x->y or y->x). If the inner loop found that one of the pixels lying on the outer loop's line wasn't black, it broke the whole look. If it didn't find that, it would increase one to the counter.
At the end, we simply create a new image with the new dimensions and copy from the new to the old one.
<?php
$image_path = "jcMHt.jpg";
$jpg = imagecreatefromjpeg($image_path);
$black = array("red" => 0, "green" => 0, "blue" => 0, "alpha" => 0);
$removeLeft = 0;
for($x = 0; $x < imagesx($jpg); $x++) {
for($y = 0; $y < imagesy($jpg); $y++) {
if(imagecolorsforindex($jpg, imagecolorat($jpg, $x, $y)) != $black){
break 2;
}
}
$removeLeft += 1;
}
$removeRight = 0;
for($x = imagesx($jpg)-1; $x > 0; $x--) {
for($y = 0; $y < imagesy($jpg); $y++) {
if(imagecolorsforindex($jpg, imagecolorat($jpg, $x, $y)) != $black){
break 2;
}
}
$removeRight += 1;
}
$removeTop = 0;
for($y = 0; $y < imagesy($jpg); $y++) {
for($x = 0; $x < imagesx($jpg); $x++) {
if(imagecolorsforindex($jpg, imagecolorat($jpg, $x, $y)) != $black){
break 2;
}
}
$removeTop += 1;
}
$removeBottom = 0;
for($y = imagesy($jpg)-1; $y > 0; $y--) {
for($x = 0; $x < imagesx($jpg); $x++) {
if(imagecolorsforindex($jpg, imagecolorat($jpg, $x, $y)) != $black){
break 2;
}
}
$removeBottom += 1;
}
$cropped = imagecreatetruecolor(imagesx($jpg) - ($removeLeft + $removeRight), imagesy($jpg) - ($removeTop + $removeBottom));
imagecopy($cropped, $jpg, 0, 0, $removeLeft, $removeTop, imagesx($cropped), imagesy($cropped));
header("Content-type: image/jpeg");
imagejpeg($cropped); //change to `imagejpeg($cropped, $image_path);` to save
imagedestroy($cropped);
imagedestroy($jpg);

Related

Storing Image pixel values in a 2D array using PHP and then access them using a loop

I have this code that I tried to store Image pixel values in 2D Array and then try to access them so that I can recreate the same Image from the pixels stored in the array, the following was what I was trying to do but it only access the array in 1 Dimension, any who can help will much appreciate it
$resource = imagecreatefromjpeg("Broadway_tower_edit.jpg");
$width = 3;
$height = 3;
$arrayPixels = array();
//put pixels values in an array
for($x = 0; $x < $width; $x++) {
for($y = 0; $y < $height; $y++) {
// pixel color at (x, y)
$color = imagecolorat($resource, $x, $y);
$arrayPixels1 = array("$color");
//$myArray[$x][$y] = array('item' => "$color");
$arrayPixels[] = $arrayPixels1;
}
}
//access pixel values an try to create a image
$img = imagecreatetruecolor($width, $height);
for ($y = 0; $y < $height; ++$y) {
for ($x = 0; $x < $width; ++$x) {
imagesetpixel($img, $x, $y, $arrayPixels[$y][$x]);
}
}
// Dump the image to the browser
header('Content-Type: image/jpg');
imagejpeg($img);
// Clean up after ourselves
imagedestroy($img);
Your array is as you say, just the rows, you need to either build up each row and then add it to a list of rows
$arrayPixels = array();
//put pixels values in an array
for($x = 0; $x < $width; $x++) {
$row = array();
for($y = 0; $y < $height; $y++) {
// pixel color at (x, y)
$row[] = imagecolorat($resource, $x, $y);
}
$arrayPixels[] = $row;
}
or do the same as you do when you re-create the image and use the x and y co-ords...
//put pixels values in an array
for($x = 0; $x < $width; $x++) {
for($y = 0; $y < $height; $y++) {
// pixel color at (x, y)
$arrayPixels[$y][$x] = imagecolorat($resource, $x, $y);
}
}

Find the 4 coordinates of a rotated transparent rectangle in png image

I didn't find anything about the subject, but I am dreaming vividly or is it possible in PHP to scan a PNG image and find the transparent positions in a picture?
for example, if there is a image of a TV with a transparent hole where the screen is. Can I find the most top-left, most top-right, most bottom-left, most bottom-right coordinates of transparent pixels by scanning the alpha-channel?
Not sure if there's a library doing this, I checked real quick but did not find..
Maybe not the most elegant solution and I'm sure there's a better way of doing it, but this works for a well-formed png image
// Returns the coordinates of a transparent rectangle in a PNG file (top left, top right, lower left, lower right
public function getTransparentRectangleCoordinates($fileUrl)
{
define ('TRANSPARENCY_THRESHOLD', 100); // 127=fully transparent, 0=black
$img = #imagecreatefrompng($fileUrl);
if (!$img) return ('Invalid PNG Image');
$coordLowestX = array(imagesx($img), '');
$coordHighestX = array(0, '');
$coordLowestY = array('', imagesy($img));
$coordHighestY = array('', 0);
$minX = imagesx($img);
$maxX = 0;
$minY = imagesy($img);
$maxY = 0;
// Scanning image pixels to find transparent points
for ($x=0; $x < imagesx($img); ++$x)
{
for ($y=0; $y < imagesy($img); ++$y)
{
$alpha = (imagecolorat($img, $x, $y) >> 24) & 0xFF;
if ($alpha >= TRANSPARENCY_THRESHOLD)
{
if ($x < $coordLowestX[0]) $coordLowestX = array($x, $y);
if ($x > $coordHighestX[0]) $coordHighestX = array($x, $y);
if ($y < $coordLowestY[1]) $coordLowestY = array($x, $y);
if ($y >= $coordHighestY[1]) $coordHighestY = array($x, $y);
if ($x < $minX) $minX = $x;
if ($x > $maxX) $maxX = $x;
if ($y < $minY) $minY = $y;
if ($y > $maxY) $maxY = $y;
}
}
}
// This means it's a non-rotated rectangle
if ( $coordLowestX == array($minX, $minY) )
{
$isRotated = false;
return array( array($minX, $minY), array($maxX, $minY), array($minX, $maxY), array($maxX, $maxY) );
}
// This means it's a rotated rectangle
else
{
$isRotated = true;
// Rotated counter-clockwise
if ($coordLowestX[1] < $coordHighestX[1])
return array($coordLowestX, $coordLowestY, $coordHighestY, $coordHighestX);
else // Rotated clockwise
return array($coordLowestY, $coordHighestY, $coordLowestX, $coordHighestX);
}
}

How can I optimize this image "edge detection" algorithm?

I have a function that, given an image with a transparent background and an unknown object in it, finds the top, left, right and bottom boundaries of the object. The purpose is so that I can simply draw a box around the boundaries of the object. I'm not trying to detect the actual edges of the object - just the top most, bottom most, etc.
My function works well, but is slow because it scans every single pixel in the image.
My question is: Is there a faster, more efficient way to detected the upper-most, left-most, right-most, and bottom-most non-transparent pixel in an image, using stock PHP/GD functionality?
There's a catch that affects the options: the object in the image may have transparent parts. For example, if it's an image of a non-filled shape.
public static function getObjectBoundaries($image)
{
// this code looks for the first non white/transparent pixel
// from the top, left, right and bottom
$imageInfo = array();
$imageInfo['width'] = imagesx($image);
$imageInfo['height'] = imagesy($image);
$imageInfo['topBoundary'] = $imageInfo['height'];
$imageInfo['bottomBoundary'] = 0;
$imageInfo['leftBoundary'] = $imageInfo['width'];
$imageInfo['rightBoundary'] = 0;
for ($x = 0; $x <= $imageInfo['width'] - 1; $x++) {
for ($y = 0; $y <= $imageInfo['height'] - 1; $y++) {
$pixelColor = imagecolorat($image, $x, $y);
if ($pixelColor != 2130706432) { // if not white/transparent
$imageInfo['topBoundary'] = min($y, $imageInfo['topBoundary']);
$imageInfo['bottomBoundary'] = max($y, $imageInfo['bottomBoundary']);
$imageInfo['leftBoundary'] = min($x, $imageInfo['leftBoundary']);
$imageInfo['rightBoundary'] = max($x, $imageInfo['rightBoundary']);
}
}
}
return $imageInfo;
}
Function calls in PHP are expensive. Calling imagecolorat() per pixel will absolutely ruin performance. Efficient coding in PHP means finding a built-in function that can somehow do the job. The following code makes use of the palette GD functions. At a glance it might not be intuitive but the logic is actually pretty simple: the code keeps copying the image a line of pixels at a time until it notices that it requires more than one colors to represent them.
function getObjectBoundaries2($image) {
$width = imagesx($image);
$height = imagesy($image);
// create a one-pixel high image that uses a PALETTE
$line = imagecreate($width, 1);
for($y = 0; $y < $height; $y++) {
// copy a row of pixels into $line
imagecopy($line, $image, 0, 0, 0, $y, $width, 1);
// count the number of colors in $line
// if it's one, then assume it's the transparent color
$count = imagecolorstotal($line);
if($count > 1) {
// okay, $line has employed more than one color so something's there
// look at the first color in the palette to ensure that our initial
// assumption was correct
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$top = $y;
} else {
// it was not--the first color encountered was opaque
$top = 0;
}
break;
}
}
if(!isset($top)) {
// image is completely empty
return array('width' => $width, 'height' => $height);
}
// do the same thing from the bottom
$line = imagecreate($width, 1);
for($y = $height - 1; $y > $top; $y--) {
imagecopy($line, $image, 0, 0, 0, $y, $width, 1);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$bottom = $y;
} else {
$bottom = $height - 1;
}
break;
}
}
$nonTransparentHeight = $bottom - $top + 1;
// scan from the left, ignoring top and bottom parts known to be transparent
$line = imagecreate(1, $nonTransparentHeight);
for($x = 0; $x < $width; $x++) {
imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$left = $x;
} else {
$left = 0;
}
break;
}
}
// scan from the right
$line = imagecreate(1, $nonTransparentHeight);
for($x = $width - 1; $x > $left; $x--) {
imagecopy($line, $image, 0, 0, $x, $top, 1, $nonTransparentHeight);
$count = imagecolorstotal($line);
if($count > 1) {
$firstColor = imagecolorsforindex($line, 0);
if($firstColor['alpha'] == 127) {
$right = $x;
} else {
$right = $width - 1;
}
break;
}
}
return array('width' => $width, 'height' => $height, 'topBoundary' => $top, 'bottomBoundary' => $bottom, 'leftBoundary' => $left, 'rightBoundary' => $right);
}
I think you could test the 4 sides one after an other, stopping as soon as a pixel is found.
For the top boundary (untested code) :
// false so we can test it's value
$bound_top = false;
// The 2 loops have 2 end conditions, if end of row/line, or pixel found
// Loop from top to bottom
for ($y = 0; $y < $img_height && $bound_top === false; $y++) {
// Loop from left to right (right to left would work to)
for ($x = 0; $x < $img_width && $bound_top === false; $x++) {
if (imageColorAt($img, $x, $y) != 2130706432) {
$bound_top = $y;
}
}
}
After the loops, if $bound_top is still false, don't bother checking the other sides, you checked all pixels, the image is empty. If not, just do the same for the other sides.
Not every pixel needs to be examined. The following code checks columns from left to right to get leftBoundary, right to left to get rightBoundary, rows from top to bottom (while excluding pixels we've already checked) to get topBoundary, and similarly for bottomBoundary.
function get_boundary($image)
{
$imageInfo = array();
$imageInfo['width'] = imagesx($image);
$imageInfo['height'] = imagesy($image);
for ($x = 0; $x < $imageInfo['width']; $x++) {
if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
$imageInfo['leftBoundary'] = $x;
break;
}
}
for ($x = $imageInfo['width']-1; $x >= 0; $x--) {
if (!is_box_empty($image, $x, 0, 1, $imageInfo['height'])) {
$imageInfo['rightBoundary'] = $x;
break;
}
}
for ($y = 0; $y < $imageInfo['height']; $y++) {
if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
$imageInfo['topBoundary'] = $y;
break;
}
}
for ($y = $imageInfo['height']-1; $y >= 0; $y--) {
if (!is_box_empty($image, $imageInfo['leftBoundary'], $y, $imageInfo['rightBoundary']-$imageInfo['leftBoundary']+1, 1)) {
$imageInfo['bottomBoundary'] = $y;
break;
}
}
return $imageInfo;
}
function is_box_empty($image, $x, $y, $w, $h)
{
for ($i = $x; $i < $x+$w; $i++) {
for ($j = $y; $j < $y+$h; $j++) {
$pixelColor = imagecolorat($image, $i, $j);
if ($pixelColor != 2130706432) { // if not white/transparent
return false;
}
}
}
return true;
}

GD Image Library merge 2 images with "Collision Detection"

I have two images, large text on white background. The length varies but the text is always aligned to the left, so there is basically free space on the right side of each image. I now want to merge these two images into one and move them as closely together as possible without having the texts "collide".
I thought of somehow checking on a per pixel column base if there's another color than white (starting from the right side), so I know after how many pixels the text starts.
Found the solution, neat function to strip all the whitespace from an image:
function stripWhitespace($img) {
//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));
return $newimg;
}
From here: Crop whitespace from image in PHP

What am I doing wrong in this loop for image pixels?

<?php
$img = imagecreatefrompng("cuack.png");
$imagew = imagesx($img);
$imageh = imagesy($img);
$width = array();
$heigth = array();
$x = 0;
$y = 0;
for ($x = 0; $x <= $imagew; $x++) {
$rgba = imagecolorat($img,$x,1);
$alpha = ($rgba & 0x7F000000) >> 24;
var_dump($alpha);
}
for ($x = 0; $x <= $imageh; $x++) {
}
I'm trying to check every pixel in an image for transparent pixels, but I'm receiving the following error:
Notice: imagecolorat() [function.imagecolorat]: 1920,1 is out of bounds in C:\www\index.php on line 18
The boundaries start at 0 and thus extend to width − 1 and height − 1 in each direction. Therefore, the <= $imagew needs to be < $imagew. Likewise for <= $imageh.
Width and height just tell you how many rows and columns of pixels there are, not the maximum row or column index (which is one lower).
To walk the whole image, just use two nested loops:
for ($y = 0; $y < $imageh; $y++) {
for ($x = 0; $x < $imagew; $x++) {
// do whatever you want with them in here.
}
}

Categories