The following block of php code is supposed to take this image as input snd produce this image as output (convert the black to yellow and the light-blue to black):
However, I'm getting this image as output instead.
Can anyone see the problem with my code?
$im = imagecreatefrompng("./input.png");
$width = imagesx($im);
$height = imagesy($im);
$new = imagecreate($width, $height);
imagecopy($new, $im, 0, 0, 0, 0, $width, $height);
imagecolorset($new, imagecolorexact($new, 0, 0, 0), 255, 255, 0);
for($i = 0; $i < $width; $i++) {
for($j = 0; $j < $height; $j++) {
$index = imagecolorat($new, $i, $j);
$rgb = imagecolorsforindex($new, $index);
if($rgb['red'] != 255 && $rgb['green'] != 255 && $rgb['blue'] != 0) {
echo '(' . $i . ', ' . $j . ')' . 'color => (' . (255 - $rgb['blue']) . ', ' . (255 - $rgb['blue']) . ', 0)<br />';
$color = imagecolorallocate($new, 255 - $rgb['blue'], 255 - $rgb['blue'], 0);
imagesetpixel($new, $i, $j, $color);
}
unset($index);
unset($rgb);
}
}
imagepng($new, 'tesst.png');
imagedestroy($im);
imagedestroy($new);
I believe the source of the issue here is that when using a palette based image, such as the one you have created by calling imagecreate(), it is possible to declare the same color at multiple indexes within the palette.
This means that, because you are calling imagecolorallocate() on every iteration, the palette eventually becomes full and imagecolorallocate() starts returning false (this can be seen if you var_dump($color); before the imagesetpixel() call). false evaluates to zero when cast to an integer - so when the palette fills up, all remaining pixels are effectively converted to the background color.
There are two things that you could do about this, the first and probably easiest is just to use a true-color image, which is just a simple case of changing imagecreate($width, $height); to imagecreatetruecolor($width, $height);.
If you want to stick with the palette-based image (for example for reasons of output image data size - with an image containing so few colors, a palette-based image will be considerably smaller), you will need to cache the allocated colors manually so you can re-use them, something like this:
// ...
$colors = array();
for ($x = 0; $x < $width; $x++) { // iterate x axis
for ($y = 0; $y < $height; $y++) { // iterate y axis
// Get the color at this index
$index = imagecolorat($new, $x, $y);
// Only allocate a new color if not already done
if (!isset($colors[$index])) {
$rgb = imagecolorsforindex($new, $index);
if ($rgb['red'] != 255 || $rgb['green'] != 255 || $rgb['blue'] != 0) {
// If it's not the background color allocate a new color
$r = $g = 255 - $rgb['blue'];
$b = 0;
$colors[$index] = imagecolorallocate($new, $r, $g, $b);
} else {
// Otherwise set the index to false, we can ignore it
$colors[$index] = false;
}
}
// If there's something to do, do it
if ($colors[$index] !== false) {
imagesetpixel($new, $x, $y, $colors[$index]);
}
}
}
// ...
You may also wish to track the colors in use in the image so you can "cleanse the palette" afterwards (i.e. deallocate any colors that are no longer in use in the image, which will further help with the data size reduction). Although arguably it would be better, in this case, to start with a clean palette and inspect the old image resource to get the pixel details, instead of copying the original to a new image resource.
yes your
$color = imagecolorallocate($new, 255 - $rgb['blue'], 255 - $rgb['blue'], 0);
is messing everything up..
if you desire the same output... just paste the line outside for loop this will solve your problem if you want the specific image:
$color = imagecolorallocate($new, 35, 35, 0); //got from debugging
and it will get u the desired output.
Dins
Related
I have some images and each of them has a masked (transparent) rectangular region inside of the same size. How can I detect the coordinates of those regions? I searched for libraries but none of them does it. I want to put my QR codes instead of the masked part.
I would try (not tested) to read the image and use a transparent color:
$src = imagecreatefrompng("your-image.png");
$trans = imageColorAllocateAlpha($src, 0, 0, 0, 127);
$transArray = imagecolorsforindex($src, $trans);
The I would read the image dimensions and check every pixel like this:
$width = imagesx($src);
$height = imagesy($src);
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$color = imagecolorat($src, $x, $y);
$colors = imagecolorsforindex($src, $color);
if ($colors["alpha"] == $transArray["alpha"]) {
// you have detected the first pixel of transparency
// here you have to remember smallest $x and smallest $y
// as well as biggest $x and biggest $y
// that should be your rectangle
}
}
}
I'm trying to convert a grayscale image to pure black and white in PHP using the GD library.
The purpose would be to detect the cervical cells within the image.
I'll leave the PHP code and a MatLab one (I wrote this code in MatLab and I'm trying to obtain the same result in PHP). Basically, I'm having trouble accessing each individual pixel's color and modifying it. sample image
PHP:
<?php
$im = imagecreatefromjpeg("celule.jpg");
function imagetograyscale($im)
{
if (imageistruecolor($im)) {
imagetruecolortopalette($im, false, 256);
}
for ($c = 0; $c < imagecolorstotal($im); $c++) {
$col = imagecolorsforindex($im, $c);
$gray = round(0.299 * $col['red'] + 0.587 * $col['green'] + 0.114 * $col['blue']);
imagecolorset($im, $c, $gray, $gray, $gray);
}
}
imagetograyscale($im);
//imagefilter($im, IMG_FILTER_CONTRAST, -255); //i'm not looking for this effect
header('Content-type: image/jpeg');
imagejpeg($im);
$C = imagesx($im); //width
$L = imagesy($im); //height
echo "Dimensiuni imagine: latime $C, inaltime $L <br>";
//scanning through the image
for($x = 0; $x < $L; $x++) { //each line
for($y = 0; $y < $C; $y++) { //each column
// pixel color at (x, y)
$color = imagecolorat($im, $y, $x);
$color = imagecolorsforindex($im, $color); //getting rgb values
$RED[$x][$y] = $color["red"]; //each rgb component
$GREEN[$x][$y] = $color["green"];
$BLUE[$x][$y] = $color["blue"];
}
}
?>
MATLAB:
clear all, clc, close all;
I = imread('celule.jpg');
imshow(I)
title('original');
a=rgb2gray(I);
figure;
imshow(a)
title('grayscale');
s=size(a);
for i=1:s(1)
for j=1:s(2)
if a(i,j)>190
a(i,j)=0;
else a(i,j)=255;
end
end
end
figure;
imshow(a)
title('pure black and white');
Here's a way to do that with gd:
#!/usr/bin/php -f
<?php
// Open image and get dimensions
$im = imagecreatefromjpeg("cellule.jpg");
$w = imagesx($im);
$h = imagesy($im);
// Convert to greyscale
imagefilter($im,IMG_FILTER_GRAYSCALE);
imagepng($im, "grey.png"); // DEBUG only
// Allocate a new palette image to hold the b&w output
$out = imagecreate($w,$h);
// Allocate b&w palette entries
$black = imagecolorallocate($out,0,0,0);
$white = imagecolorallocate($out,255,255,255);
// Iterate over all pixels, thresholding to pure b&w
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
// Get current color
$index = imagecolorat($im, $x, $y);
$grey = imagecolorsforindex($im, $index)['red'];
// Set pixel white if below threshold - don't bother settting black as image is initially black anyway
if ($grey <= 190) {
imagesetpixel($out,$x,$y,$white);
}
}
}
imagepng($out, "result.png");
?>
I would like to create watermark with PHP and GD library.
I would like to do repeating the watermark logo with auto margin(space) between each repeated logo, also zigzag position.
Is it possible to create dash line watermark that connect each watermark logo?
The result would be like this:
I have finished the zigzag by using loop function and odd even clause.
/*
* utils
$widthWatermark = imagesx($logo);
$heightWatermark = imagesy($logo);
$widthPhoto = imagesx($output);
$heightPhoto = imagesy($output);
*/
// $xLogoPosition = 0;
// $yLogoPosition = 0;
$__xRepeat = ceil($widthPhoto / $widthWatermark);
$__yRepeat = ceil($heightPhoto / $heightWatermark);
$margin = (int)self::$option['margin'];
for ($i = 0; $i <= $__xRepeat; $i++) {
if ($i % 2 === 0) {
$pre_ii = 1;
} else {
$pre_ii = 0;
}
for ($ii = 0; $ii <= $__yRepeat; $ii++) {
$ii_zero = $ii - $pre_ii;
if ($ii_zero % 2 === 0) {
$y_xindent = $widthWatermark;
}else{
$y_xindent = 0;
}
$this->imagecopymerge_alpha($output, $logo, ($xLogoPosition + $widthWatermark * $i + $y_xindent), ($yLogoPosition + $widthWatermark * $ii), 0, 0, ImageSX($logo), ImageSY($logo), self::$option['opacity']);
}
}
now I stuck at how to create dashed line that have diagonal position that connect to each other logo.
I have a hint from http://php.net/manual/en/function.imagedashedline.php
but I don't how to use and combine it with my previous code that generate zigzag logo
Edit
It turns out that PHP/GD actually has a function - imagesettile() - specifically to handle this situation.
I've modified my original answer to account for this:
<?php
// create php image of a 'dashed cross'.
$crossW = $crossH = 200;
$cross = imagecreatetruecolor($crossW, $crossH);
imagefill($cross, 0, 0, 0x7fff00ff); // transparent magenta.
imagesetthickness($cross, 1);
imagesetstyle(
$cross,
array_merge(
array_fill(0, 3, 0x7fff00ff), // transparent magenta.
array_fill(0, 8, 0x60ffffff) // partially-transparent white.
)
);
imageline($cross, 0, 0, $crossW, $crossH, IMG_COLOR_STYLED);
imageline($cross, $crossW, 0, 0, $crossH, IMG_COLOR_STYLED);
$imageFile = 'wm2.jpg';
// open the image file to be watermarked and store its height and width.
$image = imagecreatefromjpeg($imageFile);
$imWidth = imagesx($image);
$imHeight = imagesy($image);
// apply the cross pattern as a tile to the image file.
imagesettile($image, $cross);
imagefilledrectangle($image, 0, 0, $imWidth, $imHeight, IMG_COLOR_TILED);
header('Content-type: image/png');
imagepng($image);
imagedestroy($cross);
imagedestroy($image);
exit;
Input:
Result:
Is there any way to get the x,y position of a colour in an image in PHP ?
Eg : In this image
can I get the starting point,ie the x,y positions of the colour RED.
I need to create an option for the user to change the colour of a particular portion in an image.So if the user wants to change the red colour to blue in this image.I use imagefill() function to change the colour,but it need the x,y coordinates to work.Hope this make sense.
Try something like this:
// applied only to a PNG images, You can add the other format image loading for Yourself
function changeTheColor($image, $findColor, $replaceColor) {
$img = imagecreatefrompng($image);
$x = imagesx($img);
$y = imagesy($img);
$newImg = imagecreate($x, $y);
$bgColor = imagecolorallocate($newImg, 0, 0, 0);
for($i = 0; $i < $x; $i++) {
for($j = 0; $j < $y; $j++) {
$ima = imagecolorat($img, $i, $j);
$oldColor = imagecolorsforindex($img, $ima);
if($oldColor['red'] == $findColor['red'] && $oldColor['green'] == $findColor['green'] && $oldColor['blue'] == $findColor['blue'] && $oldColor['alpha'] == $findColor['alpha'])
$ima = imagecolorallocatealpha($newImage, $replaceColor['red'], $replaceColor['green'], $replaceColor['blue'], $replaceColor['alpha']);
}
imagesetpixel($newImg, $i, $j, $ima);
}
}
return imagepng($newImg);
}
We are expecting here that $findColor and $replaceColor are arrays with this structure:
$color = array(
'red' => 0,
'green' => 0,
'blue' => 0,
'alpha' => 0,
);
Didn't try the code but it at least should point You the right way. It loops through every pixel, check the color at that pixel and if it is the one we are looking for, replaces it with the $replaceColor. If not, the very same color is then placed into the new image at the very same position.
As it uses two for loops it may be very time and memory consumpting on large images.
How do I check if a PNG image has transparent pixels using PHP's GD extension?
I know this is old, but I just found this on the comments of the PHP docs. (link)
Here is the function which determines whether the PNG image contains alpha or not:
<?php
function is_alpha_png($fn){
return (ord(#file_get_contents($fn, NULL, NULL, 25, 1)) == 6);
}
?>
The color type of PNG image is stored at byte offset 25. Possible values of that 25'th byte is:
0 - greyscale
2 - RGB
3 - RGB with palette
4 - greyscale + alpha
6 - RGB + alpha
Only works for PNG images though.
It doesn't look like you can detect transparency at a glance.
The comments on the imagecolorat manual page suggest that the resulting integer when working with a true-color image can actually be shifted four times total, with the fourth being the alpha channel (the other three being red, green and blue). Therefore, given any pixel location at $x and $y, you can detect alpha using:
$rgba = imagecolorat($im,$x,$y);
$alpha = ($rgba & 0x7F000000) >> 24;
$red = ($rgba & 0xFF0000) >> 16;
$green = ($rgba & 0x00FF00) >> 8;
$blue = ($rgba & 0x0000FF);
An $alpha of 127 is apparently completely transparent, while zero is completely opaque.
Unfortunately you might need to process every single pixel in the image just to find one that is transparent, and then this only works with true-color images. Otherwise imagecolorat returns a color index, which you must then look up using imagecolorsforindex, which actually returns an array with an alpha value.
I know this is an old thread, but in my opinion it needs improvement since walking through a huge png by checking all pixels only to find out it is not transparent is a waste of time. So after some googleing I found Jon Fox's Blog and I improved his code with the help of the W3C PNG Specification further to be reliable, fast and have a minimum on memory imprint:
function IsTransparentPng($File){
//32-bit pngs
//4 checks for greyscale + alpha and RGB + alpha
if ((ord(file_get_contents($File, false, null, 25, 1)) & 4)>0){
return true;
}
//8 bit pngs
$fd=fopen($File, 'r');
$continue=true;
$plte=false;
$trns=false;
$idat=false;
while($continue===true){
$continue=false;
$line=fread($fd, 1024);
if ($plte===false){
$plte=(stripos($line, 'PLTE')!==false);
}
if ($trns===false){
$trns=(stripos($line, 'tRNS')!==false);
}
if ($idat===false){
$idat=(stripos($line, 'IDAT')!==false);
}
if ($idat===false and !($plte===true and $trns===true)){
$continue=true;
}
}
fclose($fd);
return ($plte===true and $trns===true);
}
It can be done!
I've combined all answers and comments into one function which should be fast & reliable:
function hasAlpha($imgdata) {
$w = imagesx($imgdata);
$h = imagesy($imgdata);
if($w>50 || $h>50){ //resize the image to save processing if larger than 50px:
$thumb = imagecreatetruecolor(10, 10);
imagealphablending($thumb, FALSE);
imagecopyresized( $thumb, $imgdata, 0, 0, 0, 0, 10, 10, $w, $h );
$imgdata = $thumb;
$w = imagesx($imgdata);
$h = imagesy($imgdata);
}
//run through pixels until transparent pixel is found:
for($i = 0; $i<$w; $i++) {
for($j = 0; $j < $h; $j++) {
$rgba = imagecolorat($imgdata, $i, $j);
if(($rgba & 0x7F000000) >> 24) return true;
}
}
return false;
}
//SAMPLE USE:
hasAlpha( imagecreatefrompng("myfile.png") ); //returns true if img has transparency
Pretty strait forward function, it will check if there is any transparent pixel in the image, if it is, it will return true.
$im = imagecreatefrompng('./transparent.png');
if(check_transparent($im)) {
echo 'DA';
}
else {
echo 'NU';
}
function check_transparent($im) {
$width = imagesx($im); // Get the width of the image
$height = imagesy($im); // Get the height of the image
// We run the image pixel by pixel and as soon as we find a transparent pixel we stop and return true.
for($i = 0; $i < $width; $i++) {
for($j = 0; $j < $height; $j++) {
$rgba = imagecolorat($im, $i, $j);
if(($rgba & 0x7F000000) >> 24) {
return true;
}
}
}
// If we dont find any pixel the function will return false.
return false;
}
This is how I detect 8-32 bit transparency. It only work with PNG's.
function detect_transparency($file){
if(!#getimagesize($file)) return false;
if(ord(file_get_contents($file, false, null, 25, 1)) & 4) return true;
$content = file_get_contents($file);
if(stripos($content,'PLTE') !== false && stripos($content, 'tRNS') !== false) return true;
return false;
}
cronoklee's function is very good, but when I was using it I found a bug. It does not work for images with 8 bit pallet. Here is the fixed variant:
public function hasAlpha($imgdata)
{
$w = imagesx($imgdata);
$h = imagesy($imgdata);
if($w>100 || $h>100){ //resize the image to save processing
$thumb = imagecreatetruecolor(100, 100);
imagealphablending($thumb, FALSE);
imagecopyresized( $thumb, $imgdata, 0, 0, 0, 0, 100, 100, $w, $h );
$imgdata = $thumb;
$w = imagesx($imgdata);
$h = imagesy($imgdata);
}
//run through pixels until transparent pixel is found:
for($i = 0; $i<$w; $i++) {
for($j = 0; $j < $h; $j++) {
$ci = imagecolorat($imgdata, $i, $j);
$rgba = imagecolorsforindex($imgdata, $ci);
if($rgba['alpha']) { return true; }
}
}
return false;
}
Improved cronoklee's function. Removed unnecessary bit shifting for each pixel, reduced false negatives count, added explanation in function description.
/**
* Estimates, if image has pixels with transparency. It shrinks image to 64 times smaller
* size, if necessary, and searches for the first pixel with non-zero alpha byte.
* If image has 1% opacity, it will be detected. If any block of 8x8 pixels has at least
* one semi-opaque pixel, the block will trigger positive result. There are still cases,
* where image with hardly noticeable transparency will be reported as non-transparent,
* but it's almost always safe to fill such image with monotonic background.
*
* Icons with size <= 64x64 (or having square <= 4096 pixels) are fully scanned with
* absolutely reliable result.
*
* #param resource $image
* #return bool
*/
function hasTransparency ($image): bool {
if (!is_resource($image)) {
throw new \InvalidArgumentException("Image resource expected. Got: " . gettype($image));
}
$shrinkFactor = 64.0;
$minSquareToShrink = 64.0 * 64.0;
$width = imagesx($image);
$height = imagesy($image);
$square = $width * $height;
if ($square <= $minSquareToShrink) {
[$thumb, $thumbWidth, $thumbHeight] = [$image, $width, $height];
} else {
$thumbSquare = $square / $shrinkFactor;
$thumbWidth = (int) round($width / sqrt($shrinkFactor));
$thumbWidth < 1 and $thumbWidth = 1;
$thumbHeight = (int) round($thumbSquare / $thumbWidth);
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
imagealphablending($thumb, false);
imagecopyresized($thumb, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $width, $height);
}
for ($i = 0; $i < $thumbWidth; $i++) {
for ($j = 0; $j < $thumbHeight; $j++) {
if (imagecolorat($thumb, $i, $j) & 0x7F000000) {
return true;
}
}
}
return false;
}
Usage:
hasTransparency( imagecreatefrompng("myfile.png") ); //returns true if img has transparency