Changing all pixels not a certain color - php

Just for the sake of my question, lets say this function:
int imagecolorexact ( resource $image , int $red , int $green , int $blue )
We can call it up pretty easy like this:
$index = imagecolorexact($image, 0, 0, 0);
Obviously because we are talking the example in hand is about RGB colors, therefore the $red, $green and $blue values are between 0 and 255. This function returns an index of the pixel with that rgb compound.
What I need is to get the index of all pixels that their compound is NOT (0, 0, 0), so I'd thought to do it like this:
for($i = 0; $i <= 255; $i++) {
for($j = 0; $i <= 255; $i++) {
for($k = 0; $i <= 255; $i++) {
$index[] = imagecolorexact($image, $i, $j, $k);
}
}
}
But this will run 255*255*255 times which is alot I quess.
My question is:
Is there any way to simplify this?
Why am I asking this?
I am trying to change all the black pixels from an image to yellow, thus that would be:
$image = imagecreatefrompng("./test.png");
imagecolorset($image, imagecolorexact($image, 0, 0, 0), 255, 255, 0);
And I need to change all the NON black pixels from the same image into black, thus that will be (I quess?):
for($i = 1; $i <= 255; $i++) {
for($j = 1; $i <= 255; $i++) {
for($k = 1; $i <= 255; $i++) {
$index = imagecolorexact($image, $i, $j, $k);
imagecolorset($new, $value, 0, 0, 0);
}
}
}
Thus I am asking if there is a better, optimized way?

Related

Calculate image variance in Imagick

How can I calculate the variation of an image in Imagick? I already converted the image in greyscale, but through native functions I can only get Mean and Standard Deviation.
My approach is generating two noise images and calculate the Euclidian difference between both RGB pixel color values.
This will certainly look better with real images for example of a webcam.
$getRandomNoiseImage = function ($x, $y, $steps): Imagick {
$draw = new ImagickDraw();
$draw->setResolution($x, $y);
$draw->setStrokeWidth(1);
$pixel = new ImagickPixel();
for ($i = 0; $i < $x; $i += $steps) {
for ($j = 0; $j < $y; $j += $steps) {
$color = join(',', [rand(0, 255), rand(0, 255), rand(0, 255)]);
$pixel->setColor("rgb($color)");
$draw->setFillColor($pixel);
$draw->setStrokeColor($pixel);
$draw->rectangle($i, $j, $i + $steps, $j + $steps);
}
}
$im = new Imagick();
$im->newImage($x, $y, new ImagickPixel('black'));
$im->drawImage($draw);
return $im;
};
$width = 64;
$height = 64;
$im1 = $getRandomNoiseImage($width, $height, 16);
$im2 = $getRandomNoiseImage($width, $height, 16);
$draw = new ImagickDraw();
$draw->setResolution($width, $height);
$draw->setStrokeWidth(1);
$pixel = new ImagickPixel();
for ($i = 0; $i < $width; $i++) {
for ($j = 0; $j < $height; $j++) {
$c1 = $im1->getImagePixelColor($i, $j)->getColor(0);
$c2 = $im2->getImagePixelColor($i, $j)->getColor(0);
$color = [
sqrt(abs($c2['r']**2 - $c1['r']**2)),
sqrt(abs($c2['g']**2 - $c1['g']**2)),
sqrt(abs($c2['b']**2 - $c1['b']**2)),
];
$pixel->setColor(sprintf('rgb(%d,%d,%d)', ...$color));
$draw->setFillColor($pixel);
$draw->setStrokeColor($pixel);
$draw->rectangle($i, $j, $i + 1, $j + 1);
}
}
$im3 = new Imagick();
$im3->newImage($width, $height, new ImagickPixel('black'));
$im3->drawImage($draw);
$im1->writeImage('image1.png');
$im2->writeImage('image2.png');
$im3->writeImage('image3.png');

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.

How do I nest this for loop?

I am trying to create an image with a pattern of a repeated circle. I am doing this in PHP with GD. So far I have been able to tile the circle in horizontal manner (x-axis) but am unable to tile it in the vertical(y-axis). Here is an example image.
Below is the code that created the above image :
$width = 1000;
$height = 500;
$image_p = imagecreatetruecolor($width, $height);
$color = imagecolorallocate($image_p, 0, 255, 0);
for ($i = 0; $i <= 10; $i++){
if ($i % 2 !== 0){ //only if odd numbers
imagefilledellipse ($image_p, 50 * $i, 50, 100, 100, $color);
}
}
imagejpeg($image_p, uniqid() .'.jpg');
My guess is that in order to tile each circle in a vertical manner it just needs another nested for loop and it would be similar to one already there except the change in y-axis like so :
imagefilledellipse ($image_p, 50, 50 * $i, 100, 100, $color);
I have tried a lot of nesting variation but could not get it to work. Please help.
The function imagefilledellipse has the following signature (I suppose):
imagefilledellipse(image, x, y, width, height, color)
Which means that you are drawing for every i in 0 < i < 10 a circle with a different x position.
Swap it with the y parameter to draw vertical circles:
$width = 1000;
$height = 500;
$image_p = imagecreatetruecolor($width, $height);
$color = imagecolorallocate($image_p, 0, 255, 0);
for ($i = 0; $i <= 10; $i++){
if ($i % 2 !== 0){ //only if odd numbers
imagefilledellipse ($image_p, 50, 50 * $i, 100, 100, $color);
}
}
imagejpeg($image_p, uniqid() .'.jpg');
In order to draw both horizontal and vertical circles you will need indeed, as you said, a nested for-loop:
$width = 1000;
$height = 500;
$image_p = imagecreatetruecolor($width, $height);
$color = imagecolorallocate($image_p, 0, 255, 0);
for ($i = 0; $i <= 10; $i++){
for ($j = 0; $j <= 10; $j++) {
if ($i % 2 !== 0 && $j % 2 !== 0) { //only if odd numbers
imagefilledellipse ($image_p, 50 * $i, 50 * $j, 100, 100, $color);
}
}
}
imagejpeg($image_p, uniqid() .'.jpg');
Also, you do not need to check for odd numbers if you would change the scale from i * 50 to 50 + i * 100, like this:
imagefilledellipse ($image_p, 50 + 100 * $i, 50 + 100 * $j, 100, 100, $color);
If you know how many columns you want it should be quite easy.
$colCounter=0;
$yAxis = 50;
for ($i = 0; $i <= 10; $i++){
if ($i % 2 !== 0){ //only if odd numbers
if ($colCounter % 5 === 0){ // Do something every 5 cols
$yAxis = $yAxis + 50 // add 50 onto each row
}
$colCounter++;//increment counter
imagefilledellipse ($image_p, 50 * $i, $yAxis, 100, 100, $color);
}
}
Note this is un-tested code

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

php gd pixelate too sharp

i have this script for pixelize my images the script is working but i want more smooth edges:
$imgfile = 'batman.jpg';
$image = ImageCreateFromJPEG($imgfile);
$imagex = imagesx($image);
$imagey = imagesy($image);
$pixelate_amount = 10;
$tmpImage = ImageCreateTrueColor($imagex, $imagey);
imagecopyresized($tmpImage, $image, 0, 0, 0, 0, round($imagex / $pixelate_amount), round($imagey / $pixelate_amount), $imagex, $imagey);
$pixelated = ImageCreateTrueColor($imagex, $imagey);
imagecopyresized($pixelated, $tmpImage, 0, 0, 0, 0, $imagex, $imagey, round($imagex / $pixelate_amount), round($imagey / $pixelate_amount));
header("Content-Type: image/jpeg");
imageJPEG($pixelated, "", 100);
I have:
this produce:
is there anything i miss?
Here's what you need (script I currently use). This script is based from the script at http://www.talkphp.com/19670-post1.html:
function convertToPixel($im, $size) {
$size = (int)$size;
$sizeX = imagesx($im);
$sizeY = imagesy($im);
if($sizeX < 3 && $sizeX < 3) { // or you can choose any size you want
return;
}
for($i = 0;$i < $sizeX; $i += $size) {
for($j = 0;$j < $sizeY; $j += $size) {
$colors = Array('alpha' => 0, 'red' => 0, 'green' => 0, 'blue' => 0, 'total' => 0);
for($k = 0; $k < $size; ++$k) {
for($l = 0; $l < $size; ++$l) {
if($i + $k >= $sizeX || $j + $l >= $sizeY) {
continue;
}
$color = imagecolorat($im, $i + $k, $j + $l);
imagecolordeallocate($im, $color);
$colors['alpha'] += ($color >> 24) & 0xFF;
$colors['red'] += ($color >> 16) & 0xFF;
$colors['green'] += ($color >> 8) & 0xFF;
$colors['blue'] += $color & 0xFF;
++$colors['total'];
}
}
$color = imagecolorallocatealpha($im, $colors['red'] / $colors['total'], $colors['green'] / $colors['total'], $colors['blue'] / $colors['total'], $colors['alpha'] / $colors['total']);
imagefilledrectangle($im, $i, $j, ($i + $size - 1), ($j + $size - 1), $color);
}
}
}
header('Content-type: image/jpg');
$im = imagecreatefromjpeg($imgfile);
convertToPixel($im, 15);
imagejpeg($im, '', 100);
This will produce:
You can also change the value passed in convertToPixel to modify the pixel size.
)
Use imagecopyresampled() instead of imagecopyresized().
http://php.net/manual/en/function.imagecopyresampled.php

Categories