imagick icon indicating copy to clipboard operation
imagick copied to clipboard

Random pixels change color after applying filters that should not be affecting them

Open Wes0617 opened this issue 3 years ago • 6 comments

Original:

Original Image

<?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:

Pixel artifacts

Magnified artifacts:

magnified

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:

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.

Wes0617 avatar Nov 10 '21 19:11 Wes0617

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.

Danack avatar Nov 12 '21 17:11 Danack

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.

Danack avatar Nov 14 '21 13:11 Danack

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 og_crop_in_two_pass_via_exr

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 og_crop_in_one_pass_and_threshold

Adding a threshold command, that turns all the nearly white colours in 255 white prevents this phenomenon from affecting the output image..

Danack avatar Nov 14 '21 14:11 Danack

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");

og_crop_in_one_pass_colorspace_log

Danack avatar Nov 14 '21 14:11 Danack

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");

Danack avatar Nov 14 '21 19:11 Danack

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)

Danack avatar Nov 14 '21 19:11 Danack