sammycage / plutovg

Tiny 2D vector graphics library in C
MIT License
335 stars 34 forks source link

adapting plutovg to read/write RGB565 bitmaps #41

Open codewitch-honey-crisis opened 2 weeks ago

codewitch-honey-crisis commented 2 weeks ago

Hi there. I'm working on a fork of your neat little library for use on embedded systems.

One optimization I'm attempting to make is the ability to do direct writes to particular types of bitmaps, or otherwise it uses callbacks to write to arbitrary sources it can't "directly" bind to. I got the callbacks working, and the "direct binds" work for RGBA8888 bitmaps, but not RGB565. Some of my colors are off.

Firstly, I have a question. BYTE_MUL() in plutovg-blend.c: it appears to multiply each channel by 1/255 of the alpha value passed in.

So when you use it, you're basically doing half the blend, up front, and then completing the blend with the destination pixel for each pixel in the loop.

Is that an accurate description of what you're doing there?

Also I'm attempting to do it with RGB565 and it's infuriating me, in part because RGB565 is big endian in order to work with 90% of the actual true color display hardware out there on embedded platforms.

I've got composition_solid_source somewhat working but I don't understand the intent of composition_solid_source_over.

Is it one of these? https://en.wikipedia.org/wiki/Blend_modes

And if so which one? I'm a little lost here, so anything you can tell me about this functionality - particularly with the thought in mind that I don't have an alpha channel to work with - I'd be so grateful.

Thanks in advance.

sammycage commented 2 weeks ago

Hi there!

Yes, your understanding of BYTE_MUL() is correct. It effectively multiplies each channel of the pixel by a factor derived from the alpha value. This operation allows for an initial blending step, applying the alpha value before the final blending occurs with the destination pixel.

Regarding composition_source_over, it implements the "source-over" blend mode. This means that the source pixel is drawn over the destination pixel, taking the alpha channel into account for transparency. The logic ensures that the source color is blended based on its alpha value while also considering the destination pixel's current color.

For your implementation with RGB565, since you don’t have an alpha channel, you'll need to adjust the blending approach. You can treat the source pixels as fully opaque (using const_alpha set to 255) when composing the colors. The key is to ensure you are handling the conversion between ARGB32 (premultiplied) and RGB565 correctly, especially considering the endianness.

When converting, remember to extract the RGB components from ARGB and then shift them into the appropriate positions for RGB565. If you need more help with specific parts of your implementation, feel free to ask!

Best of luck with your optimizations!

codewitch-honey-crisis commented 2 weeks ago

Thank you! That's very helpful. I do have some code I may need help with in this regard, but I'm going to try to bang on it in light of your explanation first and see how far I get. Thanks again for your help. Leaving this open for the moment, because I'll probably be back with questions, but if you close it, I won't complain. :)

codewitch-honey-crisis commented 2 weeks ago

Oh there is one thing I would like, if you're up for it. I don't understand the implementation of BYTE_MUL() enough to make a derivative function, but it would be nice to have a version of BYTE_MUL that works with RGBA instead of ARGB (i think in your code it's actually RGBA and BGRA from my perspective because my pixels are all big endian due to display hardware). Basically right now I'm converting to ARGB/BGRA from RGBA and back in order to use BYTE_MUL but if I had a BYTE_MUL() that could handle RGBA/ARGB directly that would be fantastic

This code should demonstrate the order of my pixels:

static uint32_t vector_to_rgba32(uint32_t vcol) {
    uint32_t alpha = plutovg_alpha(vcol);
    uint32_t red = plutovg_red(vcol);
    uint32_t green = plutovg_green(vcol);
    uint32_t blue = plutovg_blue(vcol);
    return (alpha<<24)|(red<<0)|(green<<8)|(blue<<16);
}

If you're not up for it, that's okay. If you are, wonderful!

codewitch-honey-crisis commented 2 weeks ago

i added you to my private repo plutovg-blend.cpp That's my current playground for blend. It's still sloppy because I'm in the middle of ripping it up, but hopefully it helps

sammycage commented 2 weeks ago

I see that I’ve been added to your private repo. I’m looking forward to checking out your work in plutovg-blend.cpp—it looks like a great playground for blending magic!

If you ever want to discuss things more privately, feel free to reach out to me via email at sammycageagle@gmail.com.

codewitch-honey-crisis commented 2 weeks ago

Sure thing. It gets a little weird because I've sort of shoehorned some of my graphics lib into your code. There's some of my pixels template structs in that code. Here's more info on them

Basically my lib allows you to define pixels of an arbitrary binary format. I also have helpers for common formats, so you can do like rgba_pixel<32> which is shorthand for a pixel with 4 channels, named R,G,B, and A each 8 bits. So if you see that stuff, that's what it is. I'm using your conversion functions rather than my pixels, because although my lib can convert (often transparently) between pixel formats (and does so in the callback code) it is necessarily a bit slower than your hard coded mechanism.

megaadam commented 2 weeks ago

Greetings from The Lounge

your 565=>888 conversion appears a bit optimistic to me... https://github.com/codewitch-honey-crisis/plutovg_tvg/blob/a18a738662ae8bed7600dd00c474a8a79e459728/src/main.c#L144-L148

565 white 0xffff would become 888 dark green 0x1f3f1f wouldn't it? I would expect 0xffffff or is there a realigning multiplication in in rgb16_to_vector() ?

codewitch-honey-crisis commented 2 weeks ago

oh i think i copied that from the Zig reference implementation. could be wrong. i honestly don't remember but i usually & 15, & 31, & 15 when I write, so that's why I think I lifted that code. Thanks for pointing that out. I'll sort it out.

megaadam commented 2 weeks ago

I would at least shift 'em 565 bits to the most significant range of the 888 => 0xf8fcf8

and vice versa when going back: if you accidentally use the lsb range, tiny nuances will have huge effect which could explain the extream saturation we saw in your camera pic!

So you did understand blending itself perfectly.

codewitch-honey-crisis commented 2 weeks ago

I finally got all my blends working optimized, except one half of my argb32/rgb16 conversion process uses my graphics lib's somewhat slower generalized conversion function because when I tried hardcoding it for some reason I can't get it to work. If you're interested in it:

constexpr static const uint16_t rgb16_r_left = 0;
constexpr static const uint16_t rgb16_g_left = 5;
constexpr static const uint16_t rgb16_b_left = 11;

static uint32_t rgb16_to_argb32p(uint16_t pixel) {
    // ::gfx::rgb_pixel<16> px(bits::from_be(pixel),true);
    // ::gfx::vector_pixel px2;
    // ::gfx::convert(px,&px2);
    // return px2.native_value;
    pixel=bits::from_be(pixel);
    uint32_t b = (pixel >> rgb16_r_left) & 0x1F;
    uint32_t g = (pixel >> rgb16_g_left) & 0x3F;
    uint32_t r = (pixel >> rgb16_b_left) & 0x1F;
    r = (r * 8.225806451612903f);
    g = (g * 4);
    b = (b * 8.225806451612903f);
    // Return the ARGB Premultiplied pixel value
    return (((uint32_t)255) << 24) | (r << 16) | (g << 8) | b;
}

static uint16_t argb32p_to_rgb16(uint32_t pixel) {
    ::gfx::vector_pixel px(pixel,true);
    ::gfx::rgb_pixel<16> px2;
    ::gfx::convert(px,&px2);
    return bits::from_be(px2.native_value);
    // uint16_t a = (pixel >> 24) & 0xFF;
    // uint16_t r = (pixel >> 16) & 0xFF;
    // uint16_t g = (pixel >> 8) & 0xFF;
    // uint16_t b = (pixel >> 0) & 0xFF;
    // if (a != 255) {
    //     r = (r * a) / 255;
    //     g = (g * a) / 255;
    //     b = (b * a) / 255;
    // }
    // r=((uint8_t)r)>>3;
    // g=((uint8_t)g)>>2;
    // b=((uint8_t)b)>>3;
    // // Return the RGBA Plain pixel value
    // return bits::from_be((r<<rgb16_r_left)| (g << rgb16_g_left)|(b << rgb16_b_left));
}

The commented code represents an alternative. The first section commented code will work. The second section of commented code does not work, and I don't know why. The part above it that replaces it does, using my library's convert<>() template function.

Edit: I noticed a bug. I fixed it, but it still doesn't work. Edited the comment

Edit 2: Okay what I thought was a bug is not a bug seeing as how it currently works. But it is effectively doubling each channel's effective range. Weird. I'm not doing anything like that in reverse using the gfx generalized conversion function, so I don't even know why this works.

Edit 3: No I'm not. That is the range of RGB565. I was totally thinking it was half that for each channel. WTF

megaadam commented 1 week ago

I see the logic of multiplying with 8.225806451612903 I would prolly r * 255 * 15 integer mul might be faster on small CPUs, and at least... it looks nicer. Why didn't you add any fraction to the green multiplication ? 63 * 4 will not reach 255...

codewitch-honey-crisis commented 1 week ago

i was coding quickly. not really thinking, just trying to get it to work. that was a result of quick calc divide. I did plan on cleaning it up, but i forgot to in the last iteration. i'll do it once you're done.

megaadam commented 1 week ago

once I am done? I think you typed that, too, too quickly :)

codewitch-honey-crisis commented 1 week ago

Sorry, i posted in the wrong place. that was meant for sam not you.

sammycage commented 1 week ago

@codewitch-honey-crisis This library

Oh there is one thing I would like, if you're up for it. I don't understand the implementation of BYTE_MUL() enough to make a derivative function, but it would be nice to have a version of BYTE_MUL that works with RGBA instead of ARGB (i think in your code it's actually RGBA and BGRA from my perspective because my pixels are all big endian due to display hardware). Basically right now I'm converting to ARGB/BGRA from RGBA and back in order to use BYTE_MUL but if I had a BYTE_MUL() that could handle RGBA/ARGB directly that would be fantastic

This code should demonstrate the order of my pixels:

static uint32_t vector_to_rgba32(uint32_t vcol) {
    uint32_t alpha = plutovg_alpha(vcol);
    uint32_t red = plutovg_red(vcol);
    uint32_t green = plutovg_green(vcol);
    uint32_t blue = plutovg_blue(vcol);
    return (alpha<<24)|(red<<0)|(green<<8)|(blue<<16);
}

If you're not up for it, that's okay. If you are, wonderful!

Hey @codewitch-honey-crisis! This library tackles each channel independently, so you can say goodbye to the endless ORs and ANDs. Check it out: agg_pixfmt_rgba.h. Happy coding!