Skip to main content
Richard Rosenman
Inspiring
November 3, 2024
Answered

Cairo 16 bit & 32 bit Rendering (SDK)

  • November 3, 2024
  • 1 reply
  • 681 views

Hi gang!

 

I know there are a few developers on here using Cairo with the AE SDK and I have recently jumped into this as well. It is indeed quite powerful and makes life significantly easier.

 

One thing I can't figure out is how to render in other depths like 16 bit and 32 bit. This is the familiar code for rendering in 8-bit from Cairo:

 

// Write pixel 8-bit
PF_Pixel* sampleIntegral8(PF_EffectWorld& def, int x, int y) {
    return (PF_Pixel*)((char*)def.data +
        (y * def.rowbytes) +
        (x * sizeof(PF_Pixel)));
}

PF_Err cairoCopy8(void* refcon, A_long threadInd, A_long itemNum,
    A_long iterTotal)
{
    cairoRefcon* data = (cairoRefcon*)refcon;
    int i = itemNum;
    PF_Err err = PF_Err_NONE;
    uint32_t* rowP;
    PF_Pixel8* worldPtr = sampleIntegral8(data->output, 0, i);
    rowP = (uint32_t*)(data->data + i * data->stride);
    for (int x = 0; x < data->output.width; x++) {
        worldPtr->alpha = rowP[x] >> 24;
        if (worldPtr->alpha)
        {
            float rf = A_u_char(rowP[x] >> 16) / (float)worldPtr->alpha;
            float gf = A_u_char(rowP[x] >> 😎 / (float)worldPtr->alpha;
            float bf = A_u_char(rowP[x] >> 0) / (float)worldPtr->alpha;
            worldPtr->red = A_u_char(rf * PF_MAX_CHAN8);
            worldPtr->green = A_u_char(gf * PF_MAX_CHAN8);
            worldPtr->blue = A_u_char(bf * PF_MAX_CHAN8);
        }
        worldPtr++;
    }

    return err;
}

 

This is what I've done in my *attempt* to adapt it to 16-bit rendering:

 

PF_Pixel16* sampleIntegral16(PF_EffectWorld& def, int x, int y) {
    return (PF_Pixel16*)((char*)def.data +
        (y * def.rowbytes) +
        (x * sizeof(PF_Pixel16)));
}

PF_Err cairoCopy16(void* refcon, A_long threadInd, A_long itemNum,
    A_long iterTotal)
{
    cairoRefcon* data = (cairoRefcon*)refcon;
    int i = itemNum;
    PF_Err err = PF_Err_NONE;
    uint32_t* rowP;
    PF_Pixel16* worldPtr = sampleIntegral16(data->output, 0, i);
    rowP = (uint32_t*)(data->data + i * data->stride);
    for (int x = 0; x < data->output.width; x++) {
        worldPtr->alpha = rowP[x] >> 24;
        if (worldPtr->alpha)
        {
            float rf = A_u_char(rowP[x] >> 16) / (float)worldPtr->alpha;
            float gf = A_u_char(rowP[x] >> 😎 / (float)worldPtr->alpha;
            float bf = A_u_char(rowP[x] >> 0) / (float)worldPtr->alpha;
            worldPtr->red = A_u_char(rf * PF_MAX_CHAN16);
            worldPtr->green = A_u_char(gf * PF_MAX_CHAN16);
            worldPtr->blue = A_u_char(bf * PF_MAX_CHAN16);
        }
        worldPtr++;
    }

    return err;
}

 

However, this renders incorrectly. It seems it still exports in the range of 0-255. Perhaps it has something to do with the bit-shifting which may need to be different for 16 / 32 bit and which, admittedly, I don't really understand.

 

Does anyone have any ideas on how to correctly adapt this to 16-bit and 32-bit?

 

Thank you as always in advance to everyone. Without this forum, I'd get nowhere.

 

Regards,

-Richard

This topic has been closed for replies.
Correct answer James Whiffin

Here's the 16bpc version I use, credit to the author of the ink effect plugin that was on the old AESDK forums:

PF_Err cairoCopy16(void *refcon, A_long threadInd, A_long iterNum, A_long iterTotal)
{
	cairoRefcon *data = (cairoRefcon*)refcon;

	int i = iterNum;

	PF_Err err = PF_Err_NONE;

	uint32_t *rowP;

	PF_Pixel8 world8;

	PF_Pixel16 *worldPtr = sampleIntegral16(*data->output, 0, i);

	rowP = (uint32_t *)(data->data + i * data->stride);

	for (int x = 0; x < data->output->width; x++) // column
	{
		world8.alpha = rowP[x] >> 24;

		if (world8.alpha > 0)
		{
			world8.red = rowP[x] >> 16;
			world8.green = rowP[x] >> 8;
			world8.blue = rowP[x];

			worldPtr->red = A_u_short(double((PF_MAX_CHAN8 * world8.red) / world8.alpha) * 128.50196);
			worldPtr->green = A_u_short(double((PF_MAX_CHAN8 * world8.green) / world8.alpha) * 128.50196);
			worldPtr->blue = A_u_short(double((PF_MAX_CHAN8 * world8.blue) / world8.alpha) * 128.50196);
			worldPtr->alpha = A_u_short(double(world8.alpha * 128.50196));
		}

		worldPtr++;
	}

	return err;
}

If you make the plugin 32bpc (even though the cairo format may be 8bpc) you don't need to deal with 16bpc, AE will just give you a 32bpc buffer and do the 32 -> 16 conversion for you. Really handy!

 

The newer versions of cairo also have 32bpc support but was so difficult to compile I outsourced it as I couldn't figure it out. 

 

 

1 reply

Community Expert
November 3, 2024

i never user cairo, but this is what pops into mind at a glance:

in the folowing row i see a couple of problems.

float rf = A_u_char(rowP[x] >> 16) / (float)worldPtr->alpha;

1. casting to A_u_char limits the value to 0-255 range. a PF_Pixel16 uses A_u_short for it's channels.

2. bitshifting the channels by multiples of 8 is correct for 8 bits. for 16 bits you should bitshift by multiples of 16.

Richard Rosenman
Inspiring
November 5, 2024

Hi Shachar;

 

Thanks for your always helpful suggestions.

 

Thanks for pointing out the A_u_char - I should have known that is limited to 255, and that was my careless mistake.

 

The 16 bit shift, I would not have understood. I tried those suggestions but unfortunately, the output is still wrong. It outputs all channels but they are wrong - the alpha is aliased and the RGB channels display incorrect colors. This is the adapted code I used as per your suggestions:

 

 

 

PF_Err cairoCopy16(void* refcon, A_long threadInd, A_long itemNum,
	A_long iterTotal)
{
	cairoRefcon* data = (cairoRefcon*)refcon;
	int i = itemNum;
	PF_Err err = PF_Err_NONE;
	uint32_t* rowP;
	PF_Pixel16* worldPtr = sampleIntegral16(data->output, 0, i);
	rowP = (uint32_t*)(data->data + i * data->stride);
	for (int x = 0; x < data->output.width; x++) {
		worldPtr->alpha = rowP[x] >> 48;
		if (worldPtr->alpha)
		{
			float rf = A_u_short(rowP[x] >> 32) / (float)worldPtr->alpha;
			float gf = A_u_short(rowP[x] >> 16) / (float)worldPtr->alpha;
			float bf = A_u_short(rowP[x] >> 0) / (float)worldPtr->alpha;
			float af = A_u_short(rowP[x] >> 48) / (float)PF_MAX_CHAN16;
			worldPtr->red = A_u_short(rf * PF_MAX_CHAN16);
			worldPtr->green = A_u_short(gf * PF_MAX_CHAN16);
			worldPtr->blue = A_u_short(bf * PF_MAX_CHAN16);
			worldPtr->alpha = A_u_short(af * PF_MAX_CHAN16);
		}
		worldPtr++;
	}

	return err;
}

 

 

 

Anyway, I understand if you are unfamiliar with Cairo, you might not have much else to suggest but perhaps someone who uses it might know?

 

I assume Cairo supports 16 bit and 32 bit - I would be very, very surprised if it didn't. The only other thing I can think might be a culprit is the image format one specifies at the beginning:

 

 

 

cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, output->width, output->height);

 

 

 

Looking in the docs, this is what they say about the image formats: https://cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t

 

Actually, the above suggests that the CAIRO_FORMAT_ARGB32 is a 8-bits per channel format? And I don't see any other formats that suggest higher bit depths... Am I nuts? There is the "CAIRO_FORMAT_RGB16_565" but that seems to carry less information than the ARGB32 so I don't see how that could be 16 bits per channel. However, I don't see how Cairo could only offer 8 bit color depth so perhaps I'm missing something here...

 

*EDIT*

 

I do see that it offers the option to use "CAIRO_FORMAT_RGBA128F" which is 32 bit. Perhaps this is one option to go with. It wasn't in my version of Cairo but a newer one.

 

-Richard

James Whiffin
James WhiffinCorrect answer
Legend
November 9, 2024

Here's the 16bpc version I use, credit to the author of the ink effect plugin that was on the old AESDK forums:

PF_Err cairoCopy16(void *refcon, A_long threadInd, A_long iterNum, A_long iterTotal)
{
	cairoRefcon *data = (cairoRefcon*)refcon;

	int i = iterNum;

	PF_Err err = PF_Err_NONE;

	uint32_t *rowP;

	PF_Pixel8 world8;

	PF_Pixel16 *worldPtr = sampleIntegral16(*data->output, 0, i);

	rowP = (uint32_t *)(data->data + i * data->stride);

	for (int x = 0; x < data->output->width; x++) // column
	{
		world8.alpha = rowP[x] >> 24;

		if (world8.alpha > 0)
		{
			world8.red = rowP[x] >> 16;
			world8.green = rowP[x] >> 8;
			world8.blue = rowP[x];

			worldPtr->red = A_u_short(double((PF_MAX_CHAN8 * world8.red) / world8.alpha) * 128.50196);
			worldPtr->green = A_u_short(double((PF_MAX_CHAN8 * world8.green) / world8.alpha) * 128.50196);
			worldPtr->blue = A_u_short(double((PF_MAX_CHAN8 * world8.blue) / world8.alpha) * 128.50196);
			worldPtr->alpha = A_u_short(double(world8.alpha * 128.50196));
		}

		worldPtr++;
	}

	return err;
}

If you make the plugin 32bpc (even though the cairo format may be 8bpc) you don't need to deal with 16bpc, AE will just give you a 32bpc buffer and do the 32 -> 16 conversion for you. Really handy!

 

The newer versions of cairo also have 32bpc support but was so difficult to compile I outsourced it as I couldn't figure it out.