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

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;
}

Related

PHP Remove black bar from image

I have a script that crops and rotates an image.
But i end up with a black bar in my image:
http://prntscr.com/r9l1w5 (screenshot)
I found a script on the internet, that is able to remove black bars on images. But i am unable to make it work together on 1 page.
(view-image.php) My script:
<?php
$filenamegetter = $_GET['imgid'];
$degree = 0;
// File and rotation
$filename = 'img/' . $filenamegetter . '';
$degrees = $degree;
$percent = 0.30;
// Content type
header('Content-type: image/jpeg');
// Get new sizes
list($width, $height) = getimagesize($filename);
if ($width > "1000") {
$newwidth = $width * $percent;
$newheight = $height * $percent;
} else {
$newwidth = $width;
$newheight = $height;
}
// Load
$thumb = imagecreatetruecolor($newwidth, $newheight);
$source = imagecreatefromjpeg($filename);
// Rotate
$rotate = imagerotate($source, $degrees, 0);
imagecopyresized($thumb, $rotate, 0, 0, 0, 0, $newwidth, $newheight, $width, $height);
imagejpeg($thumb);
// Free the memory
imagedestroy($source);
imagedestroy($thumb);
?>
The anti black bar script i wish it works together with:
<?php
$image_path = "image.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);
I'd like to know how i can implent the anti black bar script in my script. I tried multiple ways but i end up with a empty page.
For anyone having problems with imagecopyresampled or imagerotate with black bars on background, I have found a code example here:
There are a lot of factors but for me the problem was the new size after rotating the image.
https://qna.habr.com/q/646622#answer_1417035
// get image sizes (X,Y)
$wx = imagesx($imageW);
$wy = imagesy($imageW);
// create a new image from the sizes on transparent canvas
$new = imagecreatetruecolor($wx, $wy);
$transparent = imagecolorallocatealpha($new, 0, 0, 0, 127);
$rotate = imagerotate($imageW, 280, $transparent);
imagealphablending($rotate, true);
imagesavealpha($rotate, true);
// get the newest image X and Y
$ix = imagesx($rotate);
$iy = imagesy($rotate);
//copy the image to the canvas
imagecopyresampled($destImg, $rotate, 940, 2050, 0, 0, $ix, $iy, $ix, $iy);

Fpdf - set background color for row

I need set lightgrey background color for row. I use multicell view for my PDF.
My code is:
$countRow = 0;
foreach ($arrPeriod as $key=>$val) {
if($countRow % 2 == 0){
$this->setFillColor(230,230,230);
$this->SetTextColor(0,0,0);
}else{
$this->setFillColor(255,255,255);
$this->SetTextColor(0,0,0);
}
$this->Row([
$val['lead_name'],
$val['content'],
$val['date_due']
]
);
$countRow++;
}
I have problem that not full column has lightgrey background:
My Row function is:
function Row($data)
{
//Calculate the height of the row
$nb = 0;
for ($i = 0; $i < count($data); $i++) {
$nb = max($nb,$this->GetMultiCellHeight($this->widths[$i], $data[$i]));
}
$h = 5 * $nb;
//Issue a page break first if needed
$this->CheckPageBreak($h);
//Draw the cells of the row
for ($i = 0; $i < count($data); $i++) {
$w = $this->widths[$i];
$a = isset($this->aligns[$i]) ? $this->aligns[$i] : 'L';
//Save the current position
$x = $this->GetX();
$y = $this->GetY();
//Draw the border
$this->Rect($x, $y, $w, $h);
//Set font
if ($i == 0 || $i == 2) {
$this->SetFont('Arial', 'B', 10);
} else {
$this->SetFont('Arial', '', 10);
}
//Print the text
$this->MultiCell($w, 4.5, $data[$i], 0, $a, true);
//Put the position to the right of the cell
$this->SetXY($x + $w, $y);
}
//Go to the next line
$this->Ln($h);
}
How can I fix it and fill correct my row?
You already calculate the maximum number of cells/height in a column per row ($nb / $h).
So just draw the background in your Rect() call instead of the MulitCell():
$this->Rect($x, $y, $w, $h, true);
In any case you should check the caluclation, too: You calculate the height by 5 * $nb but the cell height in your MultiCell() call is only 4.5. This will shift of when you have more lines.

Size and position of one image in another via PHP

I have two images(small and big). Big one contains a small one. Like if the small one is a photo and a big one is a page from the photo album.
How do I get coordinates of that small image in the big one using PHP? And also I need to know the size of that image in big one...so just a(x,y) coordinate of any angle and sizes of sides of that presentation of the small image...
(x,y, width, height)
I've already asked the question like that and got a brilliant answer (here) but I've forgot to mention over there that the size of a small image could be different from the the size of that image in the big image...
And also if it is possible to deal with a presentation of that small image in the big image can have something covering one of its angles... Like in this example:
Small image:
Big image:
Small image always has just a rectangular shape.
Alright, this answer does not perfectly answer the question, but it should give you a good start! I know I repeat myself in the code, but my goal was simply to get something working so you can build on it, this isn't production code!
Preconditions
Starting with the large picture:
We need to find as best as possible the position of this other picture:
I decided to break the process into many substeps, which you could improve or remove depending on what you want the code to do.
For testing purposes, I did test my algorithm on different input images so you'll see a variable defining what file to load...
We start with:
function microtime_float()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
$time_start = microtime_float();
$largeFilename = "large.jpg";
$small = imagecreatefromjpeg("small.jpg");
$large = imagecreatefromjpeg($largeFilename);
and
imagedestroy($small);
imagedestroy($large);
$time_end = microtime_float();
echo "in " . ($time_end - $time_start) . " seconds\n";
To have a good idea on our performances. Luckily, most of the algorithm was pretty fast so I didn't have to optimize more.
Background Detection
I started by detecting the background color. I assumed that the background color would be the color most present in the picture. To do this, I only counted how many references of each color I could find in the large picture, sort it with decending values and took the first one as the background color (should allow the code to be adaptable if you changed the source pictures)
function FindBackgroundColor($image)
{
// assume that the color that's present the most is the background color
$colorRefcount = array();
$width = imagesx($image);
$height = imagesy($image);
for($x = 0; $x < $width; ++$x)
{
for($y = 0; $y < $height; ++$y)
{
$color = imagecolorat($image, $x, $y);
if(isset($colorRefcount[$color]))
$colorRefcount[$color] = $colorRefcount[$color] + 1;
else
$colorRefcount[$color] = 1;
}
}
arsort($colorRefcount);
reset($colorRefcount);
return key($colorRefcount);
}
$background = FindBackgroundColor($large); // Should be white
Partitionning
My first step was to try to find all the regions where non background pixels were. With a little padding, I was able to group regions into bigger regions (so that a paragraph would be a single region instead of multiple individual letters). I started with a padding of 5 and got good enough results so I stuck with it.
This is broken into multiple function calls, so here we go:
function FindRegions($image, $backgroundColor, $padding)
{
// Find all regions within image where colors are != backgroundColor, including a padding so that adjacent regions are merged together
$width = imagesx($image);
$height = imagesy($image);
$regions = array();
for($x = 0; $x < $width; ++$x)
{
for($y = 0; $y < $height; ++$y)
{
$color = imagecolorat($image, $x, $y);
if($color == $backgroundColor)
{
continue;
}
if(IsInsideRegions($regions, $x, $y))
{
continue;
}
$region = ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding);
array_push($regions, $region);
}
}
return $regions;
}
$regions = FindRegions($large, $background, 5);
Here, we iterate on every pixel of the picture, if its background color, we discard it, otherwise, we check if its position is already present in a region we found, if that's the case, we skip it too. Now, if we didn't skip the pixel, it means that it's a colored pixel that should be part of a region, so we start ExpandRegionFrom this pixel.
The code to check if we're inside a region is pretty simple:
function IsInsideRegions($regions, $x, $y)
{
foreach($regions as $region)
{
if(($region["left"] <= $x && $region["right"] >= $x) &&
($region["bottom"] <= $y && $region["top"] >= $y))
{
return true;
}
}
return false;
}
Now, the expanding code will try to grow the region in each direction and will do so as long as it found new pixels to add to the region:
function ExpandRegionFrom($image, $x, $y, $backgroundColor, $padding)
{
$width = imagesx($image);
$height = imagesy($image);
$left = $x;
$bottom = $y;
$right = $x + 1;
$top = $y + 1;
$expanded = false;
do
{
$expanded = false;
$newLeft = ShouldExpandLeft($image, $backgroundColor, $left, $bottom, $top, $padding);
if($newLeft != $left)
{
$left = $newLeft;
$expanded = true;
}
$newRight = ShouldExpandRight($image, $backgroundColor, $right, $bottom, $top, $width, $padding);
if($newRight != $right)
{
$right = $newRight;
$expanded = true;
}
$newTop = ShouldExpandTop($image, $backgroundColor, $top, $left, $right, $height, $padding);
if($newTop != $top)
{
$top = $newTop;
$expanded = true;
}
$newBottom = ShouldExpandBottom($image, $backgroundColor, $bottom, $left, $right, $padding);
if($newBottom != $bottom)
{
$bottom = $newBottom;
$expanded = true;
}
}
while($expanded == true);
$region = array();
$region["left"] = $left;
$region["bottom"] = $bottom;
$region["right"] = $right;
$region["top"] = $top;
return $region;
}
The ShouldExpand methods could have been written in a cleaner fashion, but I went for something fast to prototype with:
function ShouldExpandLeft($image, $background, $left, $bottom, $top, $padding)
{
// Find the farthest pixel that is not $background starting at $left - $padding closing in to $left
for($x = max(0, $left - $padding); $x < $left; ++$x)
{
for($y = $bottom; $y <= $top; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $x;
}
}
}
return $left;
}
function ShouldExpandRight($image, $background, $right, $bottom, $top, $width, $padding)
{
// Find the farthest pixel that is not $background starting at $right + $padding closing in to $right
$from = min($width - 1, $right + $padding);
$to = $right;
for($x = $from; $x > $to; --$x)
{
for($y = $bottom; $y <= $top; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $x;
}
}
}
return $right;
}
function ShouldExpandTop($image, $background, $top, $left, $right, $height, $padding)
{
// Find the farthest pixel that is not $background starting at $top + $padding closing in to $top
for($x = $left; $x <= $right; ++$x)
{
for($y = min($height - 1, $top + $padding); $y > $top; --$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $y;
}
}
}
return $top;
}
function ShouldExpandBottom($image, $background, $bottom, $left, $right, $padding)
{
// Find the farthest pixel that is not $background starting at $bottom - $padding closing in to $bottom
for($x = $left; $x <= $right; ++$x)
{
for($y = max(0, $bottom - $padding); $y < $bottom; ++$y)
{
$pixelColor = imagecolorat($image, $x, $y);
if($pixelColor != $background)
{
return $y;
}
}
}
return $bottom;
}
Now, to see if the algorithm was succesful, I added some debug code.
Debug Rendering
I created a second image to store debug info and store it on disk so I could later see my progress.
Using the following code:
$large2 = imagecreatefromjpeg($largeFilename);
$red = imagecolorallocate($large2, 255, 0, 0);
$green = imagecolorallocate($large2, 0, 255, 0);
$blue = imagecolorallocate($large2, 0, 0, 255);
function DrawRegions($image, $regions, $color)
{
foreach($regions as $region)
{
imagerectangle($image, $region["left"], $region["bottom"], $region["right"], $region["top"], $color);
}
}
DrawRegions($large2, $regions, $red);
imagejpeg($large2, "regions.jpg");
I could validate that my partitioning code was doing a decent job:
Aspect Ratio
I decided to filter out some regions based on aspect ratio (the ratio between the width and the height). Other filtering could be applied such as average pixel color or something, but the aspect ratio check was very fast so I used it.
I simply defined a "window" where regions would be kept, if their aspect ration was between a minimum and maximum value;
$smallAspectRatio = imagesx($small) / imagesy($small);
function PruneOutWrongAspectRatio($regions, $minAspectRatio, $maxAspectRatio)
{
$result = array();
foreach($regions as $region)
{
$aspectRatio = ($region["right"] - $region["left"]) / ($region["top"] - $region["bottom"]);
if($aspectRatio >= $minAspectRatio && $aspectRatio <= $maxAspectRatio)
{
array_push($result, $region);
}
}
return $result;
}
$filterOnAspectRatio = true;
if($filterOnAspectRatio == true)
{
$regions = PruneOutWrongAspectRatio($regions, $smallAspectRatio - 0.1 * $smallAspectRatio, $smallAspectRatio + 0.1 * $smallAspectRatio);
DrawRegions($large2, $regions, $blue);
}
imagejpeg($large2, "aspectratio.jpg");
By adding the DrawRegions call, I now paint in blue the regions that are still in the list as potential positions:
As you can see, only 4 position remains!
Finding the Corners
We're almost done! Now, what I'm doing is looking at the colors in the four corners from the small picture, and try to find the best matching pixel in the corners of the remaining regions. This code has the most potential to fail so if you have to invest time in improving the solution, this code would be a good candidate.
function FindCorners($large, $small, $regions)
{
$result = array();
$bottomLeftColor = imagecolorat($small, 0, 0);
$blColors = GetColorComponents($bottomLeftColor);
$bottomRightColor = imagecolorat($small, imagesx($small) - 1, 0);
$brColors = GetColorComponents($bottomRightColor);
$topLeftColor = imagecolorat($small, 0, imagesy($small) - 1);
$tlColors = GetColorComponents($topLeftColor);
$topRightColor = imagecolorat($small, imagesx($small) - 1, imagesy($small) - 1);
$trColors = GetColorComponents($topRightColor);
foreach($regions as $region)
{
$bottomLeft = null;
$bottomRight = null;
$topLeft = null;
$topRight = null;
$regionWidth = $region["right"] - $region["left"];
$regionHeight = $region["top"] - $region["bottom"];
$maxRadius = min($regionWidth, $regionHeight);
$topLeft = RadialFindColor($large, $tlColors, $region["left"], $region["top"], 1, -1, $maxRadius);
$topRight = RadialFindColor($large, $trColors, $region["right"], $region["top"], -1, -1, $maxRadius);
$bottomLeft = RadialFindColor($large, $blColors, $region["left"], $region["bottom"], 1, 1, $maxRadius);
$bottomRight = RadialFindColor($large, $brColors, $region["right"], $region["bottom"], -1, 1, $maxRadius);
if($bottomLeft["found"] && $topRight["found"] && $topLeft["found"] && $bottomRight["found"])
{
$left = min($bottomLeft["x"], $topLeft["x"]);
$right = max($bottomRight["x"], $topRight["x"]);
$bottom = min($bottomLeft["y"], $bottomRight["y"]);
$top = max($topLeft["y"], $topRight["y"]);
array_push($result, array("left" => $left, "right" => $right, "bottom" => $bottom, "top" => $top));
}
}
return $result;
}
$closeOnCorners = true;
if($closeOnCorners == true)
{
$regions = FindCorners($large, $small, $regions);
DrawRegions($large2, $regions, $green);
}
I tried to find the matching color by increasing "radially" (its basically squares) from the corners until I find a matching pixel (within a tolerance):
function GetColorComponents($color)
{
return array("red" => $color & 0xFF, "green" => ($color >> 8) & 0xFF, "blue" => ($color >> 16) & 0xFF);
}
function GetDistance($color, $r, $g, $b)
{
$colors = GetColorComponents($color);
return (abs($r - $colors["red"]) + abs($g - $colors["green"]) + abs($b - $colors["blue"]));
}
function RadialFindColor($large, $color, $startx, $starty, $xIncrement, $yIncrement, $maxRadius)
{
$result = array("x" => -1, "y" => -1, "found" => false);
$treshold = 40;
for($r = 1; $r <= $maxRadius; ++$r)
{
$closest = array("x" => -1, "y" => -1, "distance" => 1000);
for($i = 0; $i <= $r; ++$i)
{
$x = $startx + $i * $xIncrement;
$y = $starty + $r * $yIncrement;
$pixelColor = imagecolorat($large, $x, $y);
$distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
if($distance < $treshold && $distance < $closest["distance"])
{
$closest["x"] = $x;
$closest["y"] = $y;
$closest["distance"] = $distance;
break;
}
}
for($i = 0; $i < $r; ++$i)
{
$x = $startx + $r * $xIncrement;
$y = $starty + $i * $yIncrement;
$pixelColor = imagecolorat($large, $x, $y);
$distance = GetDistance($pixelColor, $color["red"], $color["green"], $color["blue"]);
if($distance < $treshold && $distance < $closest["distance"])
{
$closest["x"] = $x;
$closest["y"] = $y;
$closest["distance"] = $distance;
break;
}
}
if($closest["distance"] != 1000)
{
$result["x"] = $closest["x"];
$result["y"] = $closest["y"];
$result["found"] = true;
return $result;
}
}
return $result;
}
As you can see, I'm no PHP expert, I didn't know there was a built in function to get the rgb channels, oops!
Final Call
So now that the algorithm ran, let's see what it found using the following code:
foreach($regions as $region)
{
echo "Potentially between " . $region["left"] . "," . $region["bottom"] . " and " . $region["right"] . "," . $region["top"] . "\n";
}
imagejpeg($large2, "final.jpg");
imagedestroy($large2);
The output (which is pretty close to the real solution):
Potentially between 108,380 and 867,827
in 7.9796848297119 seconds
Giving this picture (the rectangle between 108,380 and 867,827 is drawn in green)
Hope this helps!
My solution work if there is no color (except white and black around the image, but you can modify the script to get it work differently)
$width = imagesx($this->img_src);
$height = imagesy($this->img_src);
// navigate through pixels of image
for ($y = 0; $y < $height; $y++) {
for ($x=0; $x < $width; $x++) {
list($r, $g, $b) = imagergbat($this->img_src, $x, $y);
$black = 0.1;
$white = 0.9;
// calculate if the color is next to white or black, if not register it as a good pixel
$gs = (($r / 3) + ($g / 3) + ($b / 3);
$first_pixel = array();
if ($gs > $white && $gs < $black) {
// get coordinate of first pixel (left top)
if (empty($first_pixel))
$first_pixel = array($x, $y);
// And save last_pixel each time till the last one
$last_pixel = array($x, $y);
}
}
}
And you get the coordinates of your image. You have just to crop it after this.

How to pass filename to function and loop through and crop images?

I'm trying to crop a list of images. The list is stored in a txt file.
I run through the txt file and store the img urls into an array
Array ( [0] => img001.jpg [1] => img002.jpg [2] => img003.jpg [3] => img004.jpg )
The .php, .txt, and .jpg images are all in the same folder and there a IMG2 subfolder.
I'm running this locally.
I was getting an error loading file, but now I just get a blank screen.
Can someone help me make this loop through the files in an array crop them. I realize that in the example below i'm just sending one value from $lines[1], thats because I can't even get that to work.
Once that works then adding a FOR loop should be straight forward.
Thanks
<?PHP
error_reporting(E_ALL);
ini_set('display_errors', '1');
$fd = fopen ("files.txt", "r");
while (!feof ($fd))
{
$buffer = fgets($fd, 4096);
$lines[] = $buffer;
}
fclose ($fd);
print_r($lines);
$imgurl = $lines[1];
processImage($imgurl); //or below line doesn't work
//foreach ($lines as $imgurl) processImage(trim($imgurl));
Function processImage($imgurl) {
//load the image
$img = #imagecreatefromjpeg($imgurl);
if (!$img) { /* See if it failed */
$img = imagecreatetruecolor(150, 30); /* Create a black image */
$bgc = imagecolorallocate($img, 255, 255, 255);
$tc = imagecolorallocate($img, 0, 0, 0);
imagefilledrectangle($img, 0, 0, 150, 30, $bgc);
/* Output an errmsg */
imagestring($img, 1, 5, 5, "Error loading $imgurl", $tc);
}
return $img;
$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);
// Save the image
$newname = "t_".$imgurl;
imagejpeg($newimg, $newname);
// Free up memory
imagedestroy($newimg);
imagedestroy($img);
}
?>
Somewhere at the top of your image processing function, you have the line:
return $img;
So that´s as far as it gets, it always returns from the function from there, the code after that is never reached.
Just removing that line should get you a lot further.

imagettfbbox() returns wrong dimensions when using space characters inside text

I'm creating a graphical menu dynamically from PHP, it results in only 1 image that looks like this:
One Two Three Four
The problem however is, that I have to determine the x-offset and the width of each page title (e.g. the left offset and the width of "One" in pixels) to position the image with css.
All works fine, except for page titles that contain spaces - imagettfbbox() returns the wrong positions. I am using an arial font (TTF)
Any suggestions on how I get around this issue?
Edit: I got it working now, using the following function to determine the text's bounds:
function calculateTextBox($font_size, $font_angle, $font_file, $text) {
$box = imagettfbbox($font_size, $font_angle, $font_file, $text);
$min_x = min(array($box[0], $box[2], $box[4], $box[6]));
$max_x = max(array($box[0], $box[2], $box[4], $box[6]));
$min_y = min(array($box[1], $box[3], $box[5], $box[7]));
$max_y = max(array($box[1], $box[3], $box[5], $box[7]));
return array(
'left' => ($min_x >= -1) ? -abs($min_x + 1) : abs($min_x + 2),
'top' => abs($min_y),
'width' => $max_x - $min_x,
'height' => $max_y - $min_y,
'box' => $box
);
}
Edit 2: Unfortunately I keep getting wrong dimensions when using different font sizes and font files...
One way to do it would be to use the imagecolorat function to work out the colour of a pixel at particular point in the image, its not going to be the quickest or even necessarily the best way to do it but it should work.
Something like this (searching for black pixels) should work, and will return the bounds you're looking for which you can then translate into x/ co-ordinates if you want:
function get_bounds($image)
{
$height = imagesy($image);
$width = imagesx($image);
$to_return = new stdClass();
$to_return->top = null;
$to_return->bottom = null;
$to_return->left = null;
$to_return->right = null;
for ($x = 0; $x < $width; $x++)
{
for ($y = 0; $y < $height; $y++)
{
$color = imagecolorat($image, $x, $y);
$rgb = imagecolorsforindex($image, $color);
// If its black
if (($rgb['red'] == 0) && ($rgb['green'] == 0) && ($rgb['blue'] == 0))
{
if (($y < $to_return->top) || is_null($to_return->top))
{
$to_return->top = $y;
}
if (($y > $to_return->bottom) || is_null($to_return->bottom))
{
$to_return->bottom = $y;
}
if (($x < $to_return->left) || is_null($to_return->left))
{
$to_return->left = $x;
}
if (($x > $to_return->right) || is_null($to_return->right))
{
$to_return->right = $x;
}
}
}
}
return $to_return;
}
Actually, its real bounds can be calculated from imagettftext function's return values.

Categories