PHP Imagemagick convert greyscale to RGB - php

I made a tool where people can upload photos and modify them, including desaturation, resulting in a greyscale image.
I generate the final image using PHP's GD library.
When printing these images the colors appear wrong so using Image Magick I add a color profile.
This works great except for images that have been greyscaled. The color profile gets added, but when I open the image in Photoshop, it says "The embedded ICC profile cannot be used because the ICC profile is invalid. Ignoring the profile".
In Photoshop the image is set to Greyscale rather than RGB, hence the attached RGB profile is wrong. I need it to be RGB.
I'm using the following code to add all the possible information in an attempt to make the image RGB:
<?php
$i = new Imagick();
$i->readimage('image.jpg');
$i->setimagetype(Imagick::IMGTYPE_TRUECOLOR);
$i->setimagecolorspace(Imagick::COLORSPACE_RGB);
$i->profileimage('icc', file_get_contents('AdobeRGB1998.icc'));
$i->writeimage($d);
$i->destroy();
?>
Does anyone know how to successfully set the image to RGB and attach the profile?
I did try the different methods and combinations for 'setImageProfile' and 'profileImage', also for colorspace and imagetype, but the result is always the same.

#a34z says in a comment:
"Somehow I must let PS know it is an RGB image with only grey pixels in it or something like that."
It is a fundamental error to assume that an RGB image could even contain 'gray' pixels as such!
RGB images do have pixels that are always composed of a mix of 3 colors: R ed + G reen + B lue. These are the 3 channels which are available, no more. There is no such thing as a gray channel in RGB.
What makes an RGB image look gray to our eyes is the fact that each of the 3 numerical channel values are equal or less strictly speaking, at least 'similar enough'. Of course, there is also software that can analyze the color values of the 3 channels and tell you which pixels are 'gray'. ImageMagick's histogram output would happily tell you which shades of gray you would say and use different names for those Grays. But don't be fooled by that color name: the pixel will still be composed from 3 colors with the same (or very similar) intensities, and ImageMagick will also report these values.
If you really need a pure grayscale image (that uses only one channel for the level of gray, not three), then you have to convert it to such an image type.
The two images may still look the same (if the conversion was done correctly, and if your monitor is calibrated, and if your not red-green-blind) -- but their internal file structure is different.
RGB images need ICC profiles that deal with RGB (if any), such as sRGB. For grayscale you cannot use sRGB, there you may want to use DeviceGray or something...

This worked for me to have it recognized as a truecolor image. Assuming $img is the Imagick object containing the greyscaled image, I check if it is indeed greyscale and then edit 1 random pixel and modify its red value by adding or substracting 5 values, depending on red being greater than 5 or not.
<?php
if ($img->getImageType() == Imagick::IMGTYPE_GRAYSCALE)
{
// Get the image dimensions
$dim = $img->getimagegeometry();
// Pick a random pixel
$x = rand(0, $dim['width']-1);
$y = rand(0, $dim['height']-1);
// Define our marge
$marge = 5;
//$x = 0;
//$y = 0;
// Debug info
echo "\r\nTransform greyscale to true color\r\n";
echo "Pixel [$x,$y]\n";
// Get the pixel from the image and get its color value
$pixel = $img->getimagepixelcolor($x, $x);
$color = $pixel->getcolor();
array_pop($color); // remove alpha value
// Determine old color for debug
$oldColor = 'rgb(' . implode(',',$color) . ')';
// Set new red value
$color['r'] = $color['r'] >= $marge ? $color['r']-$marge : $color['r'] + $marge;
// Build new color string
$newColor = 'rgb(' . implode(',',$color) . ')';
// Set the pixel's new color value
$pixel->setcolor($newColor);
echo "$oldColor -> $newColor\r\n\r\n";
// Draw the pixel on the image using an ImagickDraw object on the given coordinates
$draw = new ImagickDraw();
$draw->setfillcolor($pixel);
$draw->point($x, $y);
$img->drawimage($draw);
// Done,
unset($draw, $pixel);
}
// Do other stuff with $img here
?>
Hope this helps anyone in the future.

Related

How to create png from array of pixel color data in PHP?

Lets say I have an arrays of pixels. Basically an array of this data {x,y,r,g,b,a} for each pixel.
How would I go about converting all this data into an actual image file (png preferably)?
Could not find a solution. Any help would be very appreciated.
I had some time to code up a little example. You should be able to see and note that:
the red component increases towards the bottom of the image
the green component increases towards the right of the image
the blue component is absent
the alpha channel is random and between 0 (opaque) and 127 (fully transparent)
// Define width and height
$w=800;
$h=600;
// Create truecolour image so we can have infinitely many colours rather than a limited palette
$img=imagecreatetruecolor($w,$h);
imagesavealpha($img,true);
imagealphablending($img,false);
// Iterate over all pixels
for($y=0;$y<$h;$y++){
for($x=0;$x<$w;$x++){
$r = round(255*$y/$h);
$g = round(255*$x/$w);
$b = 0;
$alpha = rand(0,127);
$color = imagecolorallocatealpha($img,$r,$g,$b,$alpha);
imagesetpixel($img,$x,$y,$color);
}
}
// Save result
imagepng($img,"result.png");
I'll admit I haven't actually used this API, but looks like PHP has what you're looking for.
You create an image identifier with imagecreate or one of the related functions, then color in each pixel with imagesetpixel, using a color identifier created with imagecolorallocatealpha. From there you should be able to output as a PNG with imagepng.
It's worth noting that this image library seems to support drawing lines and shapes and other structures higher than the per-pixel level, so I'd also look into whether your code necessarily needs to build a big pixel array, rather than drawing the image some other way.

ImageMagick / Imagick: Convert a PNG with Alpha channel into a 2 color image (Color, Transparent)

I would like to know and find out, how I can colorize/replace any pixel of an image, that is not (fully) transparent with an opaque pixel.
For example, having a multicolored Logo with transparent pixels, I would like to convert it in to a logo with only the color #ff0000, and not change the transparent background.
I want to achieve this with the PHP Imagick Library. I cannot find any good documentation.
I thought that Imagick::thresholdImage would be a helper, but there is no documentation about the threshold parameter.
Best results are achieved with this fragment of code. But still not working perfectly. Some pixels - i guess those with alpha > 0 and < 1 are not replaced.
$image = new \Imagick($source);
$image->setImageFormat('png');
$fill = new \ImagickPixel('#ff0000');
$image->thresholdImage(0);
$image->paintOpaqueImage('#ffffff', $fill, 1);
$image->writeImage($destination);
I would like to know and find out, how I can colorize/replace any pixel of an image, that is not (fully) transparent with an opaque pixel.
You almost certainly don't.
The code below does what you are asking (I think) and the output looks terrible. Perhaps you should give an example input image, and a hoped for example output image, that you have edited in Photoshop, to show what you were hoping for.
Input image:
Output image:
$imagick = new Imagick("fnord.png");
// Get the alpha channel of the original image.
$imagick->separateImageChannel(\Imagick::CHANNEL_ALPHA);
// Make all the colors above this pure white.
$imagick->whiteThresholdImage("rgb(254, 254, 254)");
// Make all the colors below this pure black.
$imagick->blackThresholdImage("rgb(254, 254, 254)");
// We want the mask the other way round
$imagick->negateImage(false);
$imagickCanvas = new \Imagick();
$imagickCanvas->newPseudoImage(
$imagick->getImageWidth(),
$imagick->getImageHeight(),
"xc:rgb(255, 0, 0)"
);
// Copy the mask back as the alpha channel.
$imagickCanvas->compositeImage($imagick, \Imagick::COMPOSITE_COPYOPACITY, 0, 0);
// Write out the image.
$imagickCanvas->setImageFormat('png');
$imagickCanvas->writeImage("./output.png");

text colour in GB image not correct

I have the following code which creates an image based on whats in a mySQL db based on what has been uploaded via a form
if($bg_img){
list($img_width, $img_height, $img_type, $img_attr) = getimagesize('./images/'.$bg_img);
}
// Use image in background
$im = imagecreatefrompng(IS_DIR."/images/".$bg_img);
$fn = rgb2array($font_color);
$font_color = imagecolorallocate($im, $fn[0], $fn[1], $fn[2]);
This creates the image then assigns a font colour, and later in the code, text is added etc. All that works fine.
I have a strange issue though. I have two 8 bit PNG's. One of them is just a plain grey colour, the other one is an actual graphical image with a logo on it. If I use the basic one, the text uses the colour I have defined in $font_color (which comes from DB) without issues. If I use the more graphical one, the colour is incorrect and the imagecollorallocate doesn't seem to return a set of RGB values.
Is there something that needs to be done with certain types of PNG?

How can I trim just the left and right side of an image using Imagemagick in PHP?

I'm trying to trim a variable amount of whitespace in an image only the left and right side using ImageMagick and PHP. Does anyone know how to do this (perhaps using something other than imagemagick?)?
Here's an example.
I have these two images:
Each has a variable amount of text that is dynamically created in a fixed width image.
What I need to do is trim the background off the right and left side so the images come out like this:
If ImageMagick can't do it, I am willing to use something else, but I will need help on how exactly because I am not much of a programmer. Thanks!
Here's my current code that trims all sides of an image:
<?php
/* Create the object and read the image in */
$i = '3';
$im = new Imagick("test".$i.".png");
/* Trim the image. */
$im->trimImage(0);
/* Ouput the image */
//header("Content-Type: image/" . $im->getImageFormat());
//echo $im;
/*** Write the trimmed image to disk ***/
$im->writeImage(dirname(__FILE__) . '/test'.$i.'.png');
/*Display Image*/
echo $img = "<img src=\"test".$i.".png\">";
?>
I think you are on the right track with ImageMagick's -trim operator 1), but the trick would be to get it tell you what it would do without actually doing it, and then modify that to do what you really want...
So, to get the trim-box ImageMagick calculates for your first image, you do this:
convert -fuzz 10% image.jpg -format "%#" info:
60x29+21+31
That is a 60x29 pixel rectangle, offset 21 across and 31 down from the top left corner. Now, we want to get these values into bash variables, so I set the IFS (Input Field Separator) to split fields on spaces, x and also + signs:
#!/bin/bash
IFS=" x+" read a b c d < <(convert -fuzz 10% image.jpg -format "%#" info:)
echo $a $b $c $d
60 29 21 31
Now I can ignore the 29 and the 31 because we are only interested in cropping the width, and crop like this:
convert image.jpg -crop "${a}x+${c}+0" out.jpg
So, for your 2 images, I get these:
and the full procedure is this:
#!/bin/bash
IFS=" x+" read a b c d < <(convert -fuzz 10% image.jpg -format "%#" info:)
convert image.jpg -crop "${a}x+${c}+0" out.jpg
Notes
1) The -format %# is just a shorthand for the -trim operator, which would be this in full
convert image.jpg -trim info:
image.jpg JPEG 72x40 200x100+16+24 8-bit sRGB 0.000u 0:00.000
From what I can see in the ImageMagick docs on cropping and borders, it doesn't seem to be possible.
you can't specify an edge for "intelligent" cropping (known as-trim on the command line), and all the cropping methods that accept a geometry argument need a fixed number for cropping.
The only idea that comes to mind is to get the colour of the shaved area in a separate call, run trimImage, and add the lost areas back using -border.
Edit: The IM manual is suggesting something similar. Check out Trimming Just One Side of an Image. I'm not familiar with IM's PHP extension to translate the code into PHP calls but it should be half-way straightforward.
The GD based library WideImage has something similar. It's called autoCrop, by default it works on all four sides.
However, you could just add another parameter and based on it only crop top/bottom or left/right.
autoCrop code
It's pretty well documented. $img is a WideImage_Image type. There is also an interactive online demo of it.
Related question: Removing black bars off video thumbnail.
Using GD:
function imageautocrop( &$img) {
$emptycol = function ( $img, $x, $min, $max) {
for( $y=$min; $y<$max; $y++) {
$col = imagecolorsforindex( $img, imagecolorat( $img, $x, $y));
if( $col['alpha'] != 127) return false;
}
return true;
}
$trim = Array('top'=>0,'bot'=>0,'lft'=>0,'rgt'=>0);
$size = Array('x'=>imagesx($img)-1,'y'=>imagesy($img)-1);
// code for affecting rows removed due to question asked
while( $emptycol( $img, $trim['lft'], $trim['top'], $size['y']-$trim['bot'])) $trim['lft']++;
while( $emptycol( $img, $size['x']-$trim['rgt'], $trim['top'], $size['y']-$trim['bot'])) $trim['rgt']++;
$newimg = imagecreate( $size['x']-$trim['lft']-$trim['rgt']+1, $size['y']-$trim['top']-$trim['bot']+1);
imagecopy( $newimg, $img, 0, 0, $trim['lft'], $trim['top'], imagesx($newimg)+1, imagesy($newimg)+1);
imagedestroy($img);
$img = $newimg;
}
It's very old code of mine, so probably not optimal, but it does the job.
It is a two step process as text is dynamically generated
Generate the text image, determine width(image)
Overlay text image into background, determine width(background)
Use one tool mentioned above, crop (width(background)-width(image)/2 on either side
The trick is figuring out the width(image). See: How can I auto adjust the width of a GD-generated image to fit the text?
Then again, if you know width(image), you can crop the width(background) first before overlay
Use cropImage() instead. Something like this, perhaps:
$img_x_size = 800; // Set these to relevant values
$img_y_size = 600;
$crop_pixels = 20; // How many pixels to crop
// cropImage(XsizeOfCrop, YsizeOfCrop, CropXPos, CropYPos)
$im->cropImage($img_x_size - $crop_pixels, $img_y_size, 0, $crop_pixels / 2);

Resize panoramic image to fixed size

I want to resize the images to fixed width and height (i.e. 150px). However, theres a problem, if there is lots of difference in height and width of original photo (for example, panoramic photo), the resized thumbnail looks bad. Is there any any smart solution to resize the photos to a fixed width and height? For example, please have a look at this
image:
Here's my code:
<?php
$params = getimagesize($tempFile);
$width = $params[0];
$height = $params[1];
$newwidth=150;
$newheight= 150;
$tmp=imagecreatetruecolor($newwidth,$newheight);
imagecopyresampled($tmp,$src,0,0,0,0,$newwidth,$newheight,$width,$height);
imagejpeg($tmp,$img_name,80);
imagedestroy($src);
imagedestroy($tmp);
?>
Is there any smart way to resize the images in smart way?
Thanks.
There's a smart solution, it's called Seam Carving, and if your server supports ImageMagick, you do it like this:
<?php
$im = new Imagick( 'image.jpg' );
$im->liquidRescaleImage( 600, 100, 3, 25 );
header( 'Content-Type: image/jpg' );
echo $im;
?>
Or alternatively, if it doesn't support, use exec() (carefully) in order to pass image as an argument to executable which can perform seam carving.
BTW it looks like twitpic just crop's the squared image extract.
In one of my previous projects I used following code:
if ($image->width > $image->height){
//crop image in proportions 4/3, then resize to 500x300 (or proportionally lower resolution),
//sharp it a little and decrease quality.
//I used one of the Yii framework extensions.
$image->crop($image->width, $image->width/4*3)->resize(500, 300, Image::WIDTH)->sharpen(15)->quality(75);
}
It looks like twitpic is finding out how long the short axis is, then takes a square centered on the original image with sides equal to the short axis length, then shrinking that down to 150x150.
Not, resmaple, get only center 150x150 pixels.
You will need to calculate the appropriate coordinates for the original area you want to copy:
imagecopyresampled($tmp,$src,0,0,[THIS VALUE],[THIS VALUE],$newwidth,$newheight, [THIS VALUE],[THIS VALUE]);
As of now, you take the area from 0,0 (x,y) to width,height (x,y) of the original area and try to cramp it into 150x150.
you will need to calculate which of width and height that is the "biggest" and crop that and make sure that the ratio is the same as your resulting image (in your case, ratio is 1.0 because of 150x150).
In your example, where width is 1050 and height is 317 pixels so you want a portion of the original image that is 317x317 (ratio 1.0), you need to:
subtract 317 from 1050 = 733; // this is the excessive area for both sides
divide by 2 =~ 366; // to get the excessive area for one side
Now, use first x coordinate 366, to start 366 pixels from the left.
Use second x coordinate 1050 - 366 start 366 pixels from the right.
So your example should be (just guessing here):
imagecopyresampled($tmp,$src,0,0,366,0,$newwidth,$newheight, $width - 366, 0);
You will of course need some logic in order to calculate this correctly for any other size.

Categories