Page 1 of 1

[SOLVED] How to get pixels index/indices of a quantized image?

Posted: 2018-07-06T08:49:53-07:00
by rix
I'm writing a program to convert PNG color images to a unique YCrCbA bitmap format that ImageMagick cannot directly output.
So I need to manually construct the color index map table and then write each pixel in its color index number.
The index should be 1 byte thus it limits to 256 colors.

Here is what I came so far:

Code: Select all

#include <Magick++.h>
#include <MagickCore/MagickCore.h>

int main(void)
{
	// ...

	// Load the PNG image from a memory buffer
	Magick::Blob pngBlob(pngBuffer.data(), pngBuffer.size());
	Magick::Image image(pngBlob);

	image.quantizeColorSpace(MagickCore::YCbCrColorspace);
	image.quantizeColors(256);
	image.quantizeDither(true);
	image.quantizeDitherMethod(MagickCore::DitherMethod::FloydSteinbergDitherMethod);
	image.quantize();

	// Write the color index map table
	for (unsigned char i = 0; i < image.colorMapSize(); i++) {
		Magick::ColorYCrCbA color(image.colorMap(i));
		out.writeColorIndexEntry(i, color.Y(), color.Cr(), color.Cb(), color.Alpha());
	}

	// Write every pixel in its color index number
	for (int row = 0; row < image.rows(); row++) {
		// Get one row of pixels at a time
		const Magick::Quantum *pixels = image.getConstPixels(0, row, image.columns(), 1);
		for (int col = 0; col < image.columns(); col++) {
			// Get pixel index number (This is not working properly!)
			unsigned char index = (unsigned char)MagickCore::GetPixelIndex(image.constImage(), pixels++);
			out.writePixelColorIndex(index);
		}
	}

	// ...
}
Since Magick++ has no support of reading YCrCbA color, I wrote a custom ColorYCrCbA class:

Code: Select all

namespace Magick {

	//
	// YCrCbA Colorspace color
	//
	// Argument ranges:
	//        Y : 0 through 255
	//        Cr: 0 through 255
	//        Cb: 0 through 255
	//        A : 0 through 255
	class MagickPPExport ColorYCrCbA : public Color
	{
	public:

		// Default constructor
		ColorYCrCbA(void)
			: Color()
		{
		};

		// Copy constructor
		ColorYCrCbA(const Color &color_)
			: Color(color_)
		{
		};

		// PixelInfo constructor
		ColorYCrCbA(const MagickCore::PixelInfo pixel_info_)
			: Color(pixel_info_)
		{
		}

		// Destructor
		~ColorYCrCbA(void)
		{
		};

		// Assignment operator from base class
		ColorYCrCbA& operator=(const Color& color_)
		{
			*static_cast<Color*>(this) = color_;
			return(*this);
		};

		// Color Y (0 through 255)
		const unsigned char Y(void) const
		{
			return MagickCore::ScaleQuantumToChar(quantumRed());
		};

		// Color Cr (0 through 255)
		const unsigned char Cr(void) const
		{
			return MagickCore::ScaleQuantumToChar(quantumGreen());
		};

		// Color Cb (0 through 255)
		const unsigned char Cb(void) const
		{
			return MagickCore::ScaleQuantumToChar(quantumBlue());
		};

		// Color Alpha (0 through 255)
		const unsigned char Alpha(void) const
		{
			return MagickCore::ScaleQuantumToChar(quantumAlpha());
		};

	protected:

		// Constructor to construct with PixelInfo*
		ColorYCrCbA(PixelInfo *rep_, PixelType pixelType_)
			: Color(rep_, pixelType_)
		{
		};

	};
}
The problem is, when I inspect the output file, there are 178 colors, for example, in the color index map table. This means every pixel should take index value from 0x00 to 0xB1. But in the actual pixels data, I found many indices much larger than that range, like 0xE3, 0xF2 and so on.

I'm not sure what went wrong. I'm using ImageMagick 7.0.8-5 statically compiled on Windows x64 with MSVC 2017. Can someone tell me what's the right way of doing this?

Re: How to get pixels index/indices of a quantized image?

Posted: 2018-07-06T16:27:04-07:00
by rix
I've solved it myself.

After reading through the quantization source code, I found out the correct way to iterate through the "Magick::Quantum *pixels" array.
Since IM's Image structure can store each pixel in different colorspaces and presentations at the same time. There may be more than 3 or 4 channels other than RGBA, like Luma, Chroma etc. Each pixel's data is stored in all these used channels. That means if your Image has n channels, each pixel takes n Quantums in memory. Therefore, when iterating through the pixels by Quantum, each time you want to iterate one pixel, you have to increase n Quantums, where n is the total channels in use of the Image.

So here is the correct way of iterating pixels:

Code: Select all

#include <Magick++.h>
#include <MagickCore/MagickCore.h>

int main(void)
{
	// ...

	// Load the PNG image from a memory buffer
	Magick::Blob pngBlob(pngBuffer.data(), pngBuffer.size());
	Magick::Image image(pngBlob);

	image.quantizeColorSpace(MagickCore::YCbCrColorspace);
	image.quantizeColors(256);
	image.quantizeDither(true);
	image.quantizeDitherMethod(MagickCore::DitherMethod::FloydSteinbergDitherMethod);
	image.quantize();

	const MagickCore::Image *constImage = image.constImage();

	// Write the color index map table
	for (unsigned char i = 0; i < constImage.colors; i++) {
		Magick::ColorYCrCbA color(image.colorMap(i));
		out.writeColorIndexEntry(i, color.Y(), color.Cr(), color.Cb(), color.Alpha());
	}

	// Write every pixel in its color index number
	for (int row = 0; row < constImage.rows; row++) {
		// Get one row of pixels at a time
		const Magick::Quantum *pixels = image.getConstPixels(0, row, constImage.columns, 1);
		for (int col = 0; col < constImage.columns; col++) {
			// Get pixel index number
			unsigned char index = (unsigned char)MagickCore::GetPixelIndex(constImage, pixels);
			out.writePixelColorIndex(index);
			// Iterate one pixel at a time
			pixels += MagickCore::GetPixelChannels(constImage);
		}
	}

	// ...
}
The MagickCore::GetPixelChannels method is inline so it performs well. However the Magick++'s methods are not inline, you may want to avoid calling these as much as possible inside a busy loop.

Re: [SOLVED] How to get pixels index/indices of a quantized image?

Posted: 2018-07-07T01:57:38-07:00
by snibgo
GetPixelChannels is also constant for an image (every pixel has the same number of channels) so it could be called once only, instead of for each pixel.