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

Magick++ is an object-oriented C++ interface to ImageMagick. Use this forum to discuss, make suggestions about, or report bugs concerning Magick++.
Post Reply
rix
Posts: 2
Joined: 2018-07-06T08:17:12-07:00
Authentication code: 1152

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

Post by rix » 2018-07-06T08:49:53-07:00

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?
Last edited by rix on 2018-07-06T16:27:20-07:00, edited 1 time in total.

rix
Posts: 2
Joined: 2018-07-06T08:17:12-07:00
Authentication code: 1152

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

Post by rix » 2018-07-06T16:27:04-07:00

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.

snibgo
Posts: 10829
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

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

Post by snibgo » 2018-07-07T01:57:38-07:00

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.
snibgo's IM pages: im.snibgo.com

Post Reply