Is it possible to achieve this degree of color manipulation using pure imagemagick commands?
I suppose it might be done with level-color and a special transparent png for shapes.
Also is it required to change original colors to b/w before any manipulations with color levels?
This is one way to do it in Imagemagick. Yes, you need mask images. You may or may not want to convert your input to grayscale. But in this case, I suspect you want to start with grayscale.
Here is my input:
Here I create 4 simple non-overlapping rectangular region binary masks. But in your example it looks like part of the yellow overlaps with the green to make the orange color
convert -size 100x299 xc:white -size 300x299 xc:black +append mask1.png
convert -size 100x299 xc:black -size 100x299 xc:white -size 200x299 xc:black +append mask2.png
convert -size 200x299 xc:black -size 100x299 xc:white -size 100x299 xc:black +append mask3.png
convert -size 300x299 xc:black -size 100x299 xc:white +append mask4.png
Then I create 4 different color images the same size as the input and composite them successively with the one each of the masks saving over the in-memory image (mpr:img) that I created from the grayscale image.
compose method: blend (50%-50%)
(Note other blend ratios can be applied if desired using -define compose:args=50,50 by changing the two numbers, but keep the total = 100)
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose blend -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose blend -composite \
result1.jpg
compose method: multiply
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose multiply -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose multiply -composite \
result2.jpg
compose method: overlay
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose overlay -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose overlay -composite \
result3.jpg
compose method: colorize
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -colorize 100 \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -colorize 100 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -colorize 100 \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -colorize 100 \) mask4.png -compose colorize -composite \
result4.jpg
There are many other compose methods that you could try. See https://imagemagick.org/Usage/compose/
You can also do the same using +level-colors with either black or white as the second color.
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,red \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,green1 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,blue \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,yellow \) mask4.png -compose colorize -composite \
result5.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors red,white \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors green1,white \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors blue,white \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors yellow,white \) mask4.png -compose colorize -composite \
result6.jpg
You can do it also with -tint.
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -tint 100 \) mask1.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -tint 100 \) mask2.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -tint 100 \) mask3.png -compose colorize -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -tint 100 \) mask4.png -compose colorize -composite \
result7.jpg
The results differ with different compose methods. Here are that last 3 with compose over rather than compose colorize:
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,red \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,green1 \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,blue \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors black,yellow \) mask4.png -compose over -composite \
result5b.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img +level-colors red,white \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors green1,white \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors blue,white \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img +level-colors yellow,white \) mask4.png -compose over -composite \
result6b.jpg
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img -fill red -tint 100 \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill green1 -tint 100 \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill blue -tint 100 \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img -fill yellow -tint 100 \) mask4.png -compose over -composite \
result7b.jpg
See also https://imagemagick.org/Usage/color_mods/#duotone for a method using a custom colored look-up table image with the -clut function. Here is that method:
convert barn.jpg -colorspace gray -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:red xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask1.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:green1 xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask2.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:blue xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask3.png -compose over -composite -write mpr:img +delete \
mpr:img \( mpr:img \( -size 1x1 xc:black xc:yellow xc:white +append -size 1x256 gradient: -rotate 90 +swap -interpolate Bicubic -clut \) -interpolate Bicubic -clut \) mask4.png -compose over -composite \
result8.jpg
This appears to me to be the closest colorization result to that which was used in your original example.
Related
Hey all I have these 3 imageMagick scripts (command line arguments) that I am trying to combine into Imagemagick.NET code.
First (merging 2 images together):
convert ^
( testingl.jpg -resize 610x440^^ -gravity West -extent 1080x440 ) ^
( testingr.jpg -resize 610x440^^ -gravity East -extent 1080x440 ) ^
blend_mask.png -blur 0x7 ^
-composite bothImagesMerged.jpg
Second (Create 2 round objects with photo inside):
convert lisa.jpg -resize 100x100! ^
null: ( -size 100x100 xc:black -fill white -draw "circle 50,50 50,88" ) ^
-alpha off -compose copy_opacity -layers composite ^
null: ( -size 100x100 xc:"graya(100%,0)" -fill black -draw "circle 50,50 50,90" -blur 0x5 ) ^
-compose dstover -layers composite ^
-background none -gravity center +smush -25+0 ^
roundImageLisa.png
convert homer.jpg -resize 100x100! ^
null: ( -size 100x100 xc:black -fill white -draw "circle 50,50 50,88" ) ^
-alpha off -compose copy_opacity -layers composite ^
null: ( -size 100x100 xc:"graya(100%,0)" -fill black -draw "circle 50,50 50,90" -blur 0x5 ) ^
-compose dstover -layers composite ^
-background none -gravity center +smush -25+0 ^
roundImageHomer.png
Third (Write text on top of photo):
convert -size 1080x440 xc:none -gravity center ^
-font arial -pointsize 40 ^
-stroke black -strokewidth 2 -annotate +-330+-150 "Lisa Simpson" ^
-stroke black -strokewidth 2 -annotate +330+-150 "Homer Simpson" ^
-background none -shadow 520x3+0+0 +repage ^
-stroke none -fill white -annotate +-330+-150 "Lisa Simpson" ^
-stroke none -fill white -annotate +330+-150 "Homer Simpson" ^
bothImagesMerged.jpg +swap -gravity center -geometry +0-3 ^
-composite textOverImg.jpg
If I was able to combine all 3 of those then the output would look something like this:
I've tried to put all of them into a one-liner but can not seem to find the correct way (order mainly) into doing so.
I do have some code that produces the round images in C#:
Bitmap bitmap = new Bitmap("lisa.jpg");
MagickImageCollection images = new MagickImageCollection();
IMagickImage roundImg = null;
IMagickImage mask = new MagickImage("xc:black", 100, 100);
mask.Settings.FillColor = MagickColors.White;
mask.Draw(new DrawableCircle(50, 50, 50, 90));
mask.HasAlpha = false;
roundImg = new MagickImage(bitmap);
roundImg.Resize(100, 100);
roundImg.Composite(mask, CompositeOperator.CopyAlpha);
roundImg.Draw(new DrawableStrokeColor(MagickColors.Black),
new DrawableStrokeWidth(1),
new DrawableFillColor(MagickColors.None),
new DrawableCircle(50, 50, 50, 90));
IMagickImage shadow = new MagickImage("xc:none", 100, 100);
shadow.Settings.FillColor = MagickColors.Black;
shadow.Draw(new DrawableCircle(50, 50, 50, 90));
shadow.Blur(0, 5);
roundImg.Composite(shadow, CompositeOperator.DstOver);
images.Add(roundImg);
images.First().BackgroundColor = MagickColors.None;
IMagickImage result = new MagickImage();
result = images.SmushHorizontal(-35);
result.Write("lisa_round.png");
mask.Dispose();
shadow.Dispose();
result.Dispose();
images.Dispose();
Assistance would be great! #fmw42
How can I make this output a GIF instead of a PNG:
convert GIF.gif ( +clone -alpha extract -virtual-pixel black -spread 10 -blur 0x3 -threshold 50% -spread 1 -blur 0x.7 ) -alpha off -compose Copy_Opacity -composite torn_paper.png
Given an input image, I was thinking about how the image could be re-colored to a single new color keeping the luminance of the image similar to what it was earlier.
So I wrote a naive code:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <bits/stdc++.h>
using namespace cv;
using namespace std;
int main() {
Mat img = imread("test2.png", 1);
Mat hsv; cvtColor(img, hsv, CV_BGR2HSV);
vector<Mat > channels;split(hsv, channels);
Mat luminance; channels[2].copyTo(luminance);
Mat res; img.copyTo(res);
channels.clear(); split(res, channels);
for (int i = 0; i<res.rows; i++) {
for (int j = 0; j<res.cols; j++) {
channels[0].at<uchar>(i, j) = 0;
channels[1].at<uchar>(i, j) = 0;
channels[2].at<uchar>(i, j) = 255;
}
}
merge(channels, res);
cvtColor(res, hsv, CV_BGR2HSV);
channels.clear(); split(hsv, channels);
luminance.copyTo(channels[2]);
merge(channels, res);
cvtColor(res, res, CV_HSV2BGR);
imwrite("result.png", res);
return 0;
}
What I actually did is just extracted the luminance map of the original image, then created an image with the color I want it to be in, and replaced the luminance map of this output image with the luminance map of input image.
But resultant image seems to be darker in shade. Is there any better way to do this?
Input image:
Resulting image:
I think you are looking for "tinting". I don't have any references for how you to do it with OpenCV but there is a description in Anthony Thyssen's excellent ImageMagick notes here - search for the word "somehow". Maybe you can adapt it to OpenCV, if the effect is what you seek.
At the command-line, with ImageMagick, I did this:
convert drop.png -fill red -tint 50% result.jpg
Here is another way in Imagemagick.
convert \( input.png -colorspace gray \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result1.png
convert \( input.png -colorspace lab -channel red -separate \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result2.png
convert \( input.png -colorspace hsi -channel blue -separate \) \( -clone 0 -fill red -colorize 100 \) \( -clone 0 \) -compose colorize -composite result3.png
Choose what colorspace represents the intensity/luminance you want to use. See my script, color2gray at http://www.fmwconcepts.com/imagemagick/color2gray/index.php to see what different colorspace intensity/luminance show as gray.
I'm firstly drawing a wide transparent image with columns, which works fine.
convert -size 5568x1920 xc:none iphone6plus.png
convert iphone6plus.png -strokewidth 0 -fill lightgray -draw "rectangle 1080,0 1121,1920 " iphone6plus.png
convert iphone6plus.png -strokewidth 0 -fill lightgray -draw "rectangle 2202,0 2243,1920 " iphone6plus.png
convert iphone6plus.png -strokewidth 0 -fill lightgray -draw "rectangle 3324,0 3365,1920 " iphone6plus.png
convert iphone6plus.png -strokewidth 0 -fill lightgray -draw "rectangle 4446,0 4487,1920 " iphone6plus.png
Then I'm rotating another image, again works fine.
convert /en-US/iPhone6Plus-main_start_multi_child.png \
-rotate -2 \
iphone6plus-new.png
However, I'm trying to insert the second (rotated image) into the first image at a specific position / size. I'm having problems, I've tried several things, the closest I've got seems to overwrite the source image.
convert iphone6plus.png \
-geometry 40x40+5+10 \
-composite \
iphone6plus-new.png
What should I be using?
Also how should I improve the speed of this operation.
EDIT: Issue shown below...
convert -size 5568x1920 xc:none -strokewidth 0 -fill lightgray \
-draw "rectangle 1080,0 1121,1920 " \
-draw "rectangle 2202,0 2243,1920 " \
-draw "rectangle 3324,0 3365,1920 " \
-draw "rectangle 4446,0 4487,1920 " \
\( iPhone6Plus-main_start_multi_child.png -background transparent -rotate -2 -gravity center -resize 1473x755 \) \
-geometry -190-50 \
\( iPhone6Plus-test_multi_child.png -background transparent -gravity center -resize 1473x755 \) \
-geometry +2000-50 \
-composite iphone6plus-new.png
convert -size 5568x1920 xc:none -strokewidth 0 -fill lightgray \
-draw "rectangle 1080,0 1121,1920 " \
-draw "rectangle 2202,0 2243,1920 " \
-draw "rectangle 3324,0 3365,1920 " \
-draw "rectangle 4446,0 4487,1920 " \
\( iPhone6Plus-test_multi_child.png -background transparent -gravity center -resize 1473x755 \) \
-geometry +2000-50 \
-composite iphone6plus-new.png
Firstly, settings such as strokewidth and fill persist until changed, so don't keep repeating them.
Secondly, just create your background once and keep adding to it rather than saving and closing and then re-opening on the next line.
convert -size 5568x1920 xc:none -strokewidth 0 -fill lightgray \
-draw "rectangle 1080,0 1121,1920 " \
-draw "rectangle 2202,0 2243,1920 " \
-draw "rectangle 3324,0 3365,1920 " \
-draw "rectangle 4446,0 4487,1920 " basic.png
Now, load in your new image that needs rotating and apply rotation to it inside parentheses so that the rest is not affected, then composite that onto the background:
convert -size 5568x1920 xc:none -strokewidth 0 -fill lightgray \
-draw "rectangle 1080,0 1121,1920 " \
-draw "rectangle 2202,0 2243,1920 " \
-draw "rectangle 3324,0 3365,1920 " \
-draw "rectangle 4446,0 4487,1920 " \
\( SomeOtherImage.png -rotate -2 -resize AxB \) \
-geometry +X+Y -composite result.png
You may need +repage after the -resize AxB.
Updated Answer
Does this get closer to what you need maybe?
#!/bin/bash
convert -size 5568x1920 xc:none -strokewidth 0 -fill lightgray -background transparent -gravity center \
-draw "rectangle 1080,0 1121,1920" \
-draw "rectangle 2202,0 2243,1920" \
-draw "rectangle 3324,0 3365,1920" \
-draw "rectangle 4446,0 4487,1920" \
\( xc:blue[1024x768] -rotate -2 -resize 1473x755 \) -geometry -190-50 -composite \
\( xc:red[1024x768] -resize 1473x755 \) -geometry +2000-50 -composite result.png
I got this code to check if an image file contains blue pixels with Imagemagick and counting them - then saving the result.
It works well, but it seems like many processes of Imagemagick hang forever on the server and are making it very slow.
Is there a way to improve this code and avoid this trouble?
module.exports = function (File) {
File.observe('after save', function countPixels(ctx, next) {
if (ctx.instance && !ctx.instance.blue_pixels) {
var exec = require('child_process').exec;
// Convert file to retrieve only blue pixels:
exec('convert ' + ctx.instance.path + ' -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" -format "%[fx:mean*w*h]" info:',
function (error, stdout, stderr) {
if (error !== null) {
return next(error);
} else {
ctx.instance.blue_pixels = stdout;
File.upsert(ctx.instance);
}
});
}
next();
});
};
The -fx operator that you are using is notoriously slow - especially for large images. I had a try at casting the same formula using faster methods which may help you. So, I made a sample image:
convert xc:red xc:lime -append \( xc:blue xc:cyan -append \) +append -resize 256x256! input.png
And then rewrote your expression like this:
convert input.png \
\( -clone 0 -separate -delete 0 -evaluate-sequence subtract -threshold 20% -write BG.png \) \
\( -clone 0 -separate -delete 1 -evaluate-sequence subtract -threshold 20% -write BR.png \) \
\( -clone 0 -colorspace hsl -separate -delete 0,2 -threshold 60% -write S.png \) \
-delete 0 \
-evaluate-sequence min result.png
Note that the -write XYZ.png are just debug statements that can be removed.
Basically, I am building a mask of all pixels that meet your criteria and making them white, and making all the ones that don't match your criteria black and at the end, I run -evaluate-sequence min to find the minimum of each pixel so that all three of your conditions must effectively be met:
that blue exceeds green by 20%
that blue exceeds red by 20%
that the saturation exceeds 60%
The -separate -delete N splits your image into RGB channels and then deletes one of the resulting channels, so if I -delete 1 (that is the Green channel) I am left with Red and Blue. Here are the intermediate, debug images. The first one is the condition Blue exceeds Red by 20%:
Then that Blue exceeds Green by 20%:
And finally that the Saturation exceeds 60%:
And then the result:
You'll need to put your -format "%[fx:mean*w*h]" info: back on the end in place of the output image name to get the count of saturated blue pixels.
If I run your command:
convert input.png -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" result.png
My brain is not quite right today, so please run some checks - I may have something back-to-front somewhere!
As a benchmark, on a 10,000x10,000 pixel PNG, my code runs in 30 seconds, whereas the -fx equivalent takes nearly 7 minutes.
I don't know imagelagick part. But for node part I see that you call next non regarding to imagemgick opertion.
module.exports = function (File) {
File.observe('after save', function countPixels(ctx, next) {
if (ctx.instance && !ctx.instance.blue_pixels) {
var exec = require('child_process').exec;
// Convert file to retrieve only blue pixels:
exec('convert ' + ctx.instance.path + ' -fx "u.b>(u.g+0.2)&&u.b>(u.r+0.2)&&saturation>0.6" -format "%[fx:mean*w*h]" info:',
function (error, stdout, stderr) {
if (error !== null) {
return next(error);
} else {
ctx.instance.blue_pixels = stdout;
File.upsert(ctx.instance);
next();
}
});
}
else{next();}
//next(); //run next hook ASAP (before imagemagick returns back the result)
});
};