imagick
imagick copied to clipboard
Random pixels change color after applying filters that should not be affecting them
Original:
<?php
$imagick = new Imagick(__DIR__ . "/og.png");
// https://i.stack.imgur.com/t0eJO.png
// first filter
$imagick->modulateImage(188., 100., 100.);
// The pixels are now fully white, so I don't see why increasing the saturation
// should change them (and not even all of them... just some... ?)
// second filter
$imagick->modulateImage(100., 145., 100.);
// save
$imagick->writeImage(__DIR__ . "/result1.png");
Resulting Pixel artifacts:
Magnified artifacts:
But if I save the image, and then load it back into Imagick, I get the correct result:
<?php
$imagick = new Imagick(__DIR__ . "/og.png");
// https://i.stack.imgur.com/t0eJO.png
// first filter
$imagick->modulateImage(188., 100., 100.);
$imagick->setImageFormat("png");
$data = $imagick->getImageBlob();
// second filter
$imagick = new Imagick();
$imagick->readImageBlob($data);
$imagick->modulateImage(100., 145., 100.);
// save
$imagick->writeImage(__DIR__ . "/result2.png");
Expected Result:
Is this caused by bit depth? Color profile? Or is it just a bug? I am using ImageMagick 7.0.7-11
Thank you in advance.
Can you try doing the same on the command line with ImageMagick convert program?
If you can get the same very different result, then it can be reported upstream straight away, and they will probably look at it more quickly.
The pixels are now fully white,
btw, that's wrong.....with hdri they are close to white, but are unlikely to all be 255.
Saving to PNG will convert them to full 255 white, as that is not a HDR image format.....I'm about to try with OpenEXR as my guess is that will not 'flatten' the images.
I don't think it's a bug. Have some code:
$scale = 8;
// Resize just the source image.
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->interpolativeResizeImage(
$scale * $imagick->getImageWidth(),
$scale * $imagick->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick->writeImage(__DIR__ . "/og_crop_before.png");
// Do in one pass
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->modulateImage(188., 100., 100.);
$imagick->modulateImage(100.0, 145.0, 100.);
$imagick->interpolativeResizeImage(
8 * $imagick->getImageWidth(),
8 * $imagick->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick->writeImage(__DIR__ . "/og_crop_in_one_pass.png");
// Do in two pass
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->modulateImage(188., 100., 100.);
$data = $imagick->getImageBlob();
$imagick2 = new Imagick();
$imagick2->readImageBlob($data);
$imagick2->modulateImage(100.0, 145.0, 100.);
$imagick2->interpolativeResizeImage(
8 * $imagick2->getImageWidth(),
8 * $imagick2->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick2->writeImage(__DIR__ . "/og_crop_in_two_passes_via_png.png");
// Do in two passes via EXR
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->modulateImage(188., 100., 100.);
$imagick->setOption('png:bit-depth', '16');
$imagick->setOption('png:color-type', "6");
$imagick->writeImage(__DIR__ . "/og_crop_after_first.png");
$imagick->writeImage(__DIR__ . "/og_crop_after_first.exr");
$imagick2 = new Imagick(__DIR__ . "/og_crop_after_first.exr");
$imagick2->modulateImage(100.0, 145.0, 100.);
$imagick2->interpolativeResizeImage(
8 * $imagick2->getImageWidth(),
8 * $imagick2->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick2->writeImage(__DIR__ . "/og_crop_in_two_pass_via_exr.png");
// Do in one pass and threshold
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->modulateImage(188., 100., 100.);
$imagick->whiteThresholdImage("rgb(254, 254, 254)");
$imagick->modulateImage(100.0, 145.0, 100.);
$imagick->interpolativeResizeImage(
8 * $imagick->getImageWidth(),
8 * $imagick->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick->writeImage(__DIR__ . "/og_crop_in_one_pass_and_threshold.png");
og_crop_in_two_pass_via_exr.png
This shows that the issue is that the pixels are not pure white, and so when you increase the saturation, it's doing the correct thing according to the maths.
og_crop_in_one_pass_and_threshold.png
Adding a threshold command, that turns all the nearly white colours in 255 white prevents this phenomenon from affecting the output image..
Or another option:
// Do in one pass with better colour space
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->transformImageColorspace(Imagick::COLORSPACE_LOG);
$imagick->modulateImage(188., 100., 100.);
$imagick->modulateImage(100.0, 145.0, 100.);
$imagick->interpolativeResizeImage(
8 * $imagick->getImageWidth(),
8 * $imagick->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick->transformImageColorspace(Imagick::COLORSPACE_SRGB);
$imagick->writeImage(__DIR__ . "/og_crop_in_one_pass_colorspace_log.png");
Or with clamp...
// Do in one pass and clamp
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->modulateImage(188., 100., 100.);
$imagick->clampImage();
$imagick->modulateImage(100.0, 145.0, 100.);
$imagick->interpolativeResizeImage(
8 * $imagick->getImageWidth(),
8 * $imagick->getImageHeight(),
Imagick::INTERPOLATE_NEAREST_PIXEL
);
$imagick->writeImage(__DIR__ . "/og_crop_in_one_pass_and_clamp.png");
So,
<?php
declare(strict_types=1);
$imagick = new Imagick(__DIR__ . "/og_crop.png");
$imagick->quantizeImage(1024, \Imagick::COLORSPACE_YIQ, 0, false, false);
$imagick->uniqueImageColors();
$imagick->cropImage(32, 1, $imagick->getImageWidth() - 32, 0);
$imagick->scaleimage(
$imagick->getImageWidth(),
$imagick->getImageHeight() * 20
);
echo "wut\n";
$imagick->writeImage(__DIR__ . "/unique_colours.png");
$export_type = Imagick::PIXEL_QUANTUM;
$digits = 14;
$export_type = Imagick::PIXEL_FLOAT;
$digits = 4;
$imagick2 = new Imagick(__DIR__ . "/unique_colours.png");
$step_0_values = $imagick2->exportImagePixels(
0,
1,
32,
1,
"RGB",
$export_type
);
$imagick2->modulateImage(188., 100., 100.);
$step_1_values = $imagick2->exportImagePixels(
0,
1,
32,
1,
"RGB",
$export_type
);
$imagick2->writeImage(__DIR__ . "/unique_colours_pass_1.png");
$imagick2->modulateImage(100.0, 145.0, 100.);
$imagick2->writeImage(__DIR__ . "/unique_colours_pass_2.png");
$step_2_values = $imagick2->exportImagePixels(
0,
1,
32,
1,
"RGB",
$export_type
);
$imagick3 = new Imagick(__DIR__ . "/unique_colours_pass_2.png");
$step_3_values = $imagick3->exportImagePixels(
0,
1,
32,
1,
"RGB",
$export_type
);
for ($i = 0; $i<32; $i += 1) {
$r_0 = $step_0_values[(3 * $i) ];
$g_0 = $step_0_values[(3 * $i) + 1];
$b_0 = $step_0_values[(3 * $i) + 2];
$r_1 = $step_1_values[(3 * $i) ];
$g_1 = $step_1_values[(3 * $i) + 1];
$b_1 = $step_1_values[(3 * $i) + 2];
$r_2 = $step_2_values[(3 * $i) ];
$g_2 = $step_2_values[(3 * $i) + 1];
$b_2 = $step_2_values[(3 * $i) + 2];
$r_3 = $step_3_values[(3 * $i) ];
$g_3 = $step_3_values[(3 * $i) + 1];
$b_3 = $step_3_values[(3 * $i) + 2];
$format = "rgb(%$digits.2f, %$digits.2f, %$digits.2f)";
$format_string = "%2d $format, $format, $format, $format\n";
printf(
$format_string,
$i,
$r_0, $g_0, $b_0,
$r_1, $g_1, $b_1,
$r_2, $g_2, $b_2,
$r_3, $g_3, $b_3,
);
}
HDRI images are not bound 0-1.
0 rgb(0.19, 0.04, 0.01), rgb(0.35, 0.08, 0.01), rgb(0.43, 0.03, -0.06), rgb(0.43, 0.04, 0.00)
1 rgb(0.20, 0.05, 0.02), rgb(0.37, 0.10, 0.04), rgb(0.44, 0.06, -0.04), rgb(0.44, 0.06, 0.00)
2 rgb(0.22, 0.07, 0.04), rgb(0.42, 0.13, 0.07), rgb(0.50, 0.08, -0.00), rgb(0.50, 0.08, 0.00)
3 rgb(0.23, 0.08, 0.05), rgb(0.43, 0.15, 0.09), rgb(0.51, 0.10, 0.01), rgb(0.51, 0.09, 0.01)
4 rgb(0.22, 0.03, 0.01), rgb(0.41, 0.06, 0.01), rgb(0.50, -0.01, -0.07), rgb(0.50, 0.00, 0.00)
5 rgb(0.24, 0.05, 0.03), rgb(0.45, 0.10, 0.05), rgb(0.54, 0.03, -0.04), rgb(0.54, 0.03, 0.00)
6 rgb(0.18, 0.11, 0.08), rgb(0.34, 0.21, 0.15), rgb(0.38, 0.20, 0.10), rgb(0.38, 0.20, 0.11)
7 rgb(0.19, 0.11, 0.07), rgb(0.35, 0.21, 0.13), rgb(0.41, 0.19, 0.07), rgb(0.40, 0.19, 0.07)
8 rgb(0.21, 0.13, 0.09), rgb(0.40, 0.24, 0.18), rgb(0.45, 0.21, 0.13), rgb(0.45, 0.21, 0.13)
9 rgb(0.21, 0.15, 0.11), rgb(0.40, 0.28, 0.20), rgb(0.44, 0.27, 0.15), rgb(0.44, 0.27, 0.15)
10 rgb(0.22, 0.16, 0.13), rgb(0.42, 0.31, 0.24), rgb(0.46, 0.30, 0.19), rgb(0.46, 0.30, 0.20)
11 rgb(0.26, 0.19, 0.16), rgb(0.49, 0.35, 0.30), rgb(0.53, 0.34, 0.26), rgb(0.53, 0.34, 0.26)
12 rgb(0.19, 0.11, 0.06), rgb(0.36, 0.20, 0.11), rgb(0.42, 0.18, 0.05), rgb(0.42, 0.18, 0.05)
13 rgb(0.21, 0.12, 0.08), rgb(0.39, 0.23, 0.15), rgb(0.45, 0.21, 0.09), rgb(0.45, 0.21, 0.09)
14 rgb(0.24, 0.13, 0.06), rgb(0.44, 0.24, 0.12), rgb(0.52, 0.22, 0.04), rgb(0.51, 0.22, 0.04)
15 rgb(0.22, 0.09, 0.07), rgb(0.42, 0.18, 0.13), rgb(0.48, 0.13, 0.07), rgb(0.49, 0.13, 0.07)
16 rgb(0.24, 0.13, 0.07), rgb(0.45, 0.25, 0.13), rgb(0.52, 0.23, 0.06), rgb(0.52, 0.23, 0.06)
17 rgb(0.26, 0.16, 0.09), rgb(0.49, 0.29, 0.17), rgb(0.57, 0.28, 0.10), rgb(0.57, 0.28, 0.10)
18 rgb(0.22, 0.10, 0.04), rgb(0.42, 0.18, 0.07), rgb(0.50, 0.16, -0.01), rgb(0.50, 0.16, 0.00)
19 rgb(0.24, 0.12, 0.05), rgb(0.46, 0.22, 0.10), rgb(0.54, 0.19, 0.02), rgb(0.54, 0.20, 0.02)
20 rgb(0.29, 0.18, 0.11), rgb(0.54, 0.33, 0.21), rgb(0.61, 0.31, 0.13), rgb(0.61, 0.31, 0.13)
21 rgb(0.38, 0.06, 0.04), rgb(0.72, 0.11, 0.08), rgb(0.86, -0.02, -0.06), rgb(0.86, 0.00, 0.00)
22 rgb(0.45, 0.13, 0.12), rgb(0.81, 0.29, 0.27), rgb(0.93, 0.18, 0.14), rgb(0.93, 0.18, 0.15)
23 rgb(0.32, 0.28, 0.27), rgb(0.60, 0.54, 0.53), rgb(0.61, 0.53, 0.51), rgb(0.61, 0.53, 0.51)
24 rgb(0.49, 0.45, 0.44), rgb(0.88, 0.87, 0.87), rgb(0.89, 0.87, 0.87), rgb(0.89, 0.87, 0.87)
25 rgb(0.52, 0.20, 0.18), rgb(0.82, 0.51, 0.49), rgb(0.90, 0.44, 0.42), rgb(0.90, 0.44, 0.42)
26 rgb(0.66, 0.62, 0.62), rgb(1.19, 1.21, 1.21), rgb(1.19, 1.22, 1.22), rgb(1.00, 1.00, 1.00)
27 rgb(0.84, 0.52, 0.51), rgb(1.13, 1.39, 1.41), rgb(1.07, 1.45, 1.47), rgb(1.00, 1.00, 1.00)
28 rgb(0.84, 0.80, 0.79), rgb(1.46, 1.57, 1.59), rgb(1.43, 1.59, 1.62), rgb(1.00, 1.00, 1.00)
29 rgb(0.98, 0.97, 0.98), rgb(1.70, 1.98, 1.84), rgb(1.63, 2.04, 1.84), rgb(1.00, 1.00, 1.00)
30 rgb(1.00, 0.99, 0.99), rgb(1.58, 2.16, 2.16), rgb(1.45, 2.29, 2.29), rgb(1.00, 1.00, 1.00)
31 rgb(1.00, 0.97, 0.97), rgb(1.00, 2.70, 2.70), rgb(0.62, 3.08, 3.08), rgb(0.62, 1.00, 1.00)