How would I be able to detect that an image is blank (only of a single, arbitrary color or, with a gif, frames of random arbitrary colors) using PHP and/or imagemagick?
I think this is what I'm going to try:
http://www.php.net/manual/en/function.imagecolorat.php#97957
You can check the image inside of PHP using imagecolorat (this may be slow, but it works):
function isPngValidButBlank($filename) {
$img = imagecreatefrompng($filename);
if(!$img)
return false;
$width = imagesx($img);
$height = imagesy($img);
if(!$width || !$height)
return false;
$firstcolor = imagecolorat($img, 0, 0);
for($i = 0; $i < $width; $i++) {
for($j = 0; $j < $height; $j++) {
$color = imagecolorat($img, $i, $j);
if($color != $firstcolor)
return false;
}
}
return true;
}
http://www.php.net/manual/en/function.imagecolorstotal.php gives you the amount of colors in an image. Hmm, in my demo it doesn't seem to work, sorry :( an image i created (fully red, 20x20 pixels) gives 0 colors for PNG and 3 colors for GIF.
Ok: http://www.dynamicdrive.com/forums/showpost.php?p=161187&postcount=2 look at the 2nd piece of code. Tested here: http://www.pendemo.nl/totalcolors.php
Kevin's solution can be sped up using random sampling. If you have some idea of the percentage of pixels that should be different from the background (assuming you aren't dealing with lots of images with only 1 different pixel), you can use the Poisson distribution:
probability of finding a nonblank pixel = 1 - e^(-n*p)
where n is the number of samples to try, and p is the percentage of pixels expected to be nonblank. Solve for n to get the appropriate number of samples to try:
n = -log(1 - x) / p
where x is the desired probability and log is natural log. For example, if you are reasonably sure that 0.1% of the image should be nonblank, and you want to have a 99.99% chance of finding at least one nonblank pixel,
n = -log(1-.9999)/.001 = 9210 samples needed.
Much faster than checking every pixel. To be 100% sure, you can always go back and check all of them if the sampling doesn't find any.
For anyone using Imagick to try and achieve this, the getImageColors() method did the trick.
https://www.php.net/manual/en/imagick.getimagecolors.php
$img = new Imagick();
$img->readImage($image);
$colors = $img->getImageColors();
if(!$colors > 1) {
return false;
}
Get the standard-deviation from the verbose statistics for each tile. If the standard deviation is 0, then the image is one color.
Supposedly, 'number of colors' will also do this; would be 1.
Use the -format option: http://www.imagemagick.org/script/escape.php
Related
In a recent competition I was given the task to extract binary data (another PNG) from a PNG image file's alpha channel. The data was encoded in such a way that if I read the values in the alpha channel for each pixel from the top left (e.g. 80,78,71,13,10,26,10) up to a specific point then the resulting data would form another image.
Initially I tried to complete this task using PHP, but I hit a roadblock that I could not overcome. Consider the code below:
function pad($hex){
return strlen($hex) < 2 ? "0$hex" : $hex;
}
$channel = '';
$image = 'image.png';
$ir = imagecreatefrompng($image);
imagesavealpha($ir, true);
list($width, $height) = getimagesize($image);
for ($y = 0; $y < $height; $y++){
for ($x = 0; $x < $width; $x++){
$pixel = imagecolorat($ir, $x, $y);
$colors = imagecolorsforindex($ir, $pixel);
$a = pad(dechex($colors['alpha']));
$channel.= $a;
}
}
After running this, I noticed that the output did not contain the PNG magic number, and I just didn't know what went wrong. After a bit of digging I found out that $colors['alpha'] only contained values less than or equal to 127. Due to the data having been encoded with a 0-255 range in mind, I could not find a way to extract it, and ended up (successfully) solving the problem with node.js instead.
So, my question is: Is there any way I could have read the PNG file's alpha channel that would have returned the values in a 0 to 255 range as opposed to 0-127, or is this a hard-coded limitation of PHP and/or GD?
For the record, I tried to use ($colors['alpha']/127)*255 in order to try and forge the value in the incorrect range to the correct one, but to no avail.
It is a limitation of GD. According to https://bitbucket.org/libgd/gd-libgd/issues/132/history-behind-7-bit-alpha-component, in GD source it says:
gd has only 7 bits of alpha channel resolution, and 127 is
transparent, 0 opaque. A moment of convenience, a lifetime of
compatibility.
If you install ImageMagick PHP extension, you can get the alpha value between 0-255 for a pixel (let's say with x=300 and y=490) like this:
$image = new Imagick(__DIR__ . DIRECTORY_SEPARATOR . 'image.png');
$x = 300;
$y = 490;
$pixel = $image->getImagePixelColor($x, $y);
$colors = $pixel->getColor(true);
echo 'Alpha value: ' . round($colors['a'] * 255, 0);
ImageMagick: https://www.imagemagick.org
ImageMagick for PHP (called Imagick): http://php.net/manual/en/book.imagick.php
In your code:
list($width, $height) = getimagesize($image);
references a variable '$image' that was not defined in the code.
using getimagesize means $image is a filename.
so this line get's width and height from a filename.
This line broke the code when I tested it.
You already have your answer, but this is for posterity.
Considering the code, I feel:
$width=imagesx ($ir);
$height=imagesy ($ir);
Would be more logical (and actually works)
I've scoured around the internet a fair bit and I can't seem to find any reference to what I am attempting to achieve... I fear that means I'm probably going about doing something the wrong way, but I'll pose this question here anyways in hopes that maybe I am not.
I would like to take an already generated image that has a rectangular selection already drawn on it via a specific color and a dynamic (but always rectangular) path, and crop or cut-out (and use) the inner area of that rectangular path.
Let's use an image generated by google maps as an example for this:
I thought perhaps the imagemagick library would hold a solution for this, but, I don't know if it's because I haven't quite narrowed down the exact key terms for what I am looking to do exactly, or if it's because it cannot (at least not simply) be done, but I haven't turned up any solutions.
Any solutions, advice, or smacks to the head are welcome.
[Please note that (for now) I would like to operate under the assumption that these images already exist, so any information regarding the pixel coordinates of the relative selection area on the image doesn't exist]
Your problem seems to boil down to this: How do I find a red rectangle in an image?
This is quite an open-ended problem, and could actually be quite difficult to solve. However, if the following assumptions can be made, then the task will be a lot easier:
The rectangle is drawn in pure RGB red (#ff0000).
The rectangle is aligned parallel with the image edges.
The image is saved in a lossless format like PNG.
The image contains no other pixels of this exact colour.
We know the width of the rectangle's edges.
The example you provided seems to tick all these boxes. Since it's stored as an 8-bit indexed color image, the first step would be to convert it into a true color image. This makes it easier to check the pixel values.
Then find the outermost edges of the frame, inset the coordinates by the frame width, and crop the image. Here's some code that will do this for you:
<?php
$src_img = 'er7RT.png';
$frame_color = 0xff0000;
$frame_width = 6;
// Load image and copy to true color image resource
$im = imagecreatefrompng($src_img);
$sw = imagesx($im);
$sh = imagesy($im);
$im1 = imagecreatetruecolor($sw, $sh);
imagecopy ($im1, $im, 0, 0, 0, 0, $sw, $sh);
imagedestroy($im);
// Get outer dimensions of frame.
// Assume the frame color appears nowhere else in the image.
$minx = $miny = 999999;
$maxx = $maxy = -$minx;
for ($x=0; $x<$sw; $x++) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $minx = $x; break 2; }
}
for ($x=$sw-1; $x>=0; $x--) for ($y=$sh/20; $y<$sh; $y+=$sh/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxx = $x; break 2; }
}
for ($y=0; $y<$sh; $y++) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $miny = $y; break 2; }
}
for ($y=$sh-1; $y>=0; $y--) for ($x=$sw/20; $x<$sw; $x+=$sw/10) {
if (imagecolorat($im1,$x,$y)==$frame_color) { $maxy = $y; break 2; }
}
if ($minx>=$maxx || $miny>=$maxy) die("Couldn't locate frame");
// Subtract frame width to obtain crop region
$minx += $frame_width;
$maxx -= $frame_width;
$miny += $frame_width;
$maxy -= $frame_width;
// Create new image with cropped dimensions
$im2 = imagecreatetruecolor($maxx-$minx, $maxy-$miny);
imagecopy ($im2, $im1, 0, 0, $minx, $miny, $maxx-$minx, $maxy-$miny);
// Finish up
header("Content-Type: image/png");
imagepng($im2);
imagedestroy($im1);
imagedestroy($im2);
I've made a search by color feature on my photo website. To do this, I looped through all the pixels in one image and recorded the top 10 in my database. The problem I have is that because my photos have people subjects, my searchers are really searching for colors of the subject, rather than the background (sky, paving, grass etc). I know this will be very hard to eliminate completely but I'm thinking I need to change the color slightly to give better results.
Please see this example of one photo:
The example on the left is what I was doing, collecting ALL pixels. Black and grays took up the top 3 colors in that selection, a shade of pink was 4th and navy blue from the background girl's dress was 5th - I only collect the top 5 colors and my main color wasn't really ranked that good. If I could change the way I loop through the pixels, to select only the colors in the green box (right), I think it would make a slight bit of difference in my collection. This is where I'm struggling to see the math.
I was doing this, to loop through all pixels:
$size = #getimagesize($imageFile);
for ($x = 0; $x < $size[0]; $x += $granularity) {
for($y = 0; $y < $size[1]; $y += $granularity) {
// do stuff here
}
But I need to change this to take roughly 5% off the top and bottom and roughly 8-10% of the sides and then loop through the new selection.
I did start this but then stopped because I wasn't sure about my calculations returning odd numbers or number likes 1.030044858.
This is how far I got and hoped somebody can help clean it up and get me over the line:
$granularity = 4;
$granularity = max(1, abs((int)$granularity));
$size = #getimagesize($imageFile);
$sizeX = $size[0];
$sizeY = $size[1];
$xPerCent = 8; // for me to set & adjust
$yPerCent = 5; // for me to set & adjust
$xSelectionWidth = ($sizeX/100) * $xPerCent;
$xSelectionWidth = $sizeX - ($xSelectionWidth * 2);
$xSelectionHeight = ($sizeY/100) * $yPerCent;
$xSelectionHeight = $sizeY - ($ySelectionHeight * 2);
$xSelectionStart = 0;
$xSelectionEnd = 0;
$ySelectionStart = 0;
$ySelectionEnd = 0;
for($x = 0; $x < $size[0]; $x += $granularity) {
for($y = 0; $y < $size[1]; $y += $granularity) {
// do stuff here
}
}
If you want to ignore something in a picture, I suggest you to convert it to B&W and take the biggest white area (you will need to set details of the convertion to be sure you do not get all non already white color as black).
I mean red, and skin color should be set as white and violet as black. In this case, the background and any dark surface would be black. Read http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale its a great introduction but, I am talking about real Black and white so, you may adjust/adapt this one.
This will greatly help you to get focus on the right part of the image.
The more accurate the B&W form recognition, the better you can identify the subject you wat color from. Please read http://en.wikipedia.org/wiki/Pattern_recognition for detailed infos on pattern recognition.
Get the set of coordinates you want to look at in the original image.
Sory I am not a PHP person, I can not be more specific in this way but, I hope this will help you...
This post is an extension of a question asked a couple years ago here:
Crop whitespace from image in PHP
That post does a magnificent job of explaining how to use php to crop pure white space from around an image. The issue here is that it expects "pure white" around the image. Here is what i am trying to do and how it differs:
I am using my cell phone as a "scanner" to take pictures of documents and then upload them to my server (don't worry...there is no "secret agent" stuff going on here...it is actually mostly receipts from lunches and sometimes business cards). In my case I always try to use a dark background to contrast with the light color paper. The images never come out with "pure" black/white colors however. My image is always in JPG format. I am experimenting with the code referenced above (but swapped to test for dark/black border) and wonder if there is a way to generalize the function to apply more broadly? I can install ImageMagick and use trimImage and some other stuff, but would rather stick to GD since that will be more useful to more StackOverflow users. Here is what i thought of so far:
IDEA #1: convert the image to greyscale and crank the contrast very high...do the comparison there (presumable against pure black at that point)...then use the found coordinates on the original (non-greyscale) image. What do you think...good approach? Would it work?
I would use imagefilter($img, IMG_FILTER_GRAYSCALE); and then imagefilter($img, IMG_FILTER_CONTRAST, -90);
IDEA #2: Go generic. This is the idea I need the most help with. It would be really cool if i could automatically detect the approximate range of colors from the border. The idea would be to sample a pixel (or several) from each of the four corners and then "average" them to figure out the background. This would mean that one day if i wanted to take the photo against a brown background, or a red background, or a green background...no problem. I think that the first step in this direction would involve the use of imagecolorclosest() but frankly am in WAY over my head on how to select, compare, average, and then re-compare the colors. Ideally there would be a way to just plug in some of this logic to the original function (from link above) and it would then be able to work generically regardless of what background color is in the image (assuming it is reasonably consistent) and would work with a photo (thus an image where the background color is not absolutely the same color value all the way across).
So...two part question: Is my IDEA #1 the best approach and are there any problems i should be aware of or suggestions on improvement/implementation? And...does anyone want to give a go at IDEA #2 and at least give me enough code to get moving in the right direction?
So i went ahead and played around with IDEA#1 and it is working exceptionally well. I am all set and need no further help at the moment...though still think that IDEA #2 is a better all-around solution, so if anyone wants to provide some pointers for it, i will play around with the ideas and try to come up with additional code examples to post here. For now, here is my working version of IDEA#1 as described above and tested on several cell-phone images:
//MANY THANKS TO: http://stackoverflow.com/questions/1669683/crop-whitespace-from-image-in-php
$OriginalImageFileLocation = 'sampleimage.jpg';
//load the image into a variable (and throw an error if image does not exist)
if( !$img = imagecreatefromjpeg("$OriginalImageFileLocation") )
{ exit("Could not use image $OriginalImageFileLocation"); }
// This function only can detect the background if it is "pure black"...so convert to greyscale then crank the contrast
imagefilter($img, IMG_FILTER_GRAYSCALE);
imagefilter($img, IMG_FILTER_CONTRAST, -100);
//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) != 0x000000) {
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) != 0x000000) {
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) != 0x000000) {
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) != 0x000000) {
break 2; //out of the 'right' loop
}
}
}
//now re-initialize the original image since the greyscaled version was just for gathering cordinates
$img = imagecreatefromjpeg("$OriginalImageFileLocation");
// OPTIONAL: make the final image a bit prettier (and greyscale for long-term consistency)
imagefilter($img, IMG_FILTER_GRAYSCALE);
imagefilter($img, IMG_FILTER_BRIGHTNESS, +15);
imagefilter($img, IMG_FILTER_CONTRAST, -20);
//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);
I want the new height and width to which the image needs to be resized. There are two conditions
Width needs to be around 180px(170-180) but < 180px (uploaded image is always > 180)
Height should be max 180px (uploaded image may or may not be > 180)
If you are writing program for Linux I would recommend using ImageMagick. It is more memory efficient and probably faster than any PHP based method. Almost all servers have it installed. Following code will do the trick.
function resizeTo($source, $dest, $w=180, $h=180) {
system("convert $source -resize {$w}x{$h} $dest");
}
It will mind the aspect ratio.
Edit:
Sorry for the confusion. I think the following should do what you are looking for. It is not tested and might need a little bit debugging, if you run into trouble I can try and post again.
//accepts and returns point object (having ->x and ->y)
function resizeTo($current, $max) {
if($current->x <= $max->x && $current->y <= $max->y) //you will not need this but
return $current; // still its good to have
if( ($current->y / $max->y) > ($current->x / $max->x) ) { //y axis needs more trimming
$r=$current->y / $max->y;
$current->y = $max->y;
$current->x = $current->x / $r;
} else {
$r=$current->x / $max->x;
$current->x = $max->x;
$current->y = $current->y / $r;
}
return $current;
}
You just need a few steps:
1. scale = imageWidth / 180;
2. scale = (imageHeight/scale>180) ? imageHeight/180 : scale;
The first one will set the scale factor you need to make the width 180 (based on your comment that it is ALWAYS larger then 180)
The second one will check if the height will be larger then 180 with that scale. If it is, then the scale will be height/180. If its not, you already have the max height.
Then you also need steps to get the actual width and height:
width = imageWidth/scale;
height = imageHeight/scale;
Considering you want to make the imageWidth between 170 and 180 I guess cropping the image is also a possibility. If that is the case you need an additional check
if (width<170) {
width = 170;
height = imageHeigh / (imageWidth/170);
//resize image to width and height
//crop image to height = 180
}