Something like drawing to a frame buffer? [SOLVED]

I don’t understand why you’re ignoring all the advice people are giving you and are trying to crunch the RGB values manually.

It’s madness to do manual bit-crunching like that when you could just use the PixelRGB/PixelARGB classes to do the operation correctly with shorter, more readable code!

And colour operations on integer RGB values are not for the faint-hearted… getting them right is a total nightmare. Your code doesn’t account for overflows, pre-multiplication, endianness or packing, so it probably isn’t even going to work!

2 Likes

Hi Jules,

I’d need to sit down fresh (tomorrow) and carefully reread all the comments in both threads again to give you a proper answer as to why I did that, but perhaps I’m not so much intentionally ignoring good advice, just that I’m a little bewildered by options and so I pick the one that seems best (at the cost of “ignoring” the others).

Take for example PixelRGB, I’m not at the level yet where I can work out on my own what I need to do just by reading the documentation, but I try anyway, so I look at the PixelRGB documentation (without understanding its role in the bigger picture), and I’m looking around for words that might make sense to me. I see a set() method and think this is what I’ll need to use, but it takes a Pixel object as an argument. And searching the documentation for a Pixel class yields nothing. So how do I even begin to interface with that function? I can’t even imagine how that function should be applied to my Image::BitmapData - so unfortunately, I have to drop that route and go with another strategy because I’ll just not be able to make this work on my own.

…And since my best success in JUCE so far has come from editing the audio buffer directly, and since that’s all I know, the only assumption I make otherwise is that to get the best performance (and that’s why I come to c++ from something like Proccessing) I need to edit the ‘graphics’ buffer.

And that’s kind of where I am.

What I need though is to take a step back and get a more conceptual understanding of everything, but I just don’t know where to begin.

A couple of years ago, someone on the libcinder forum recommended I take this course: https://eu.udacity.com/course/interactive-3d-graphics--cs291 , I never did, and I probably should :wink:

I hope this post was of any value to you.

For those specifically, they’re marked as template<class Pixel> meaning that Pixel is simply referring to the template type you use when calling the method.

If I’m not mistaken, those template methods exist so you can use the different kinds of pixel classes available… PixelRGB, PixelARGB, PixelAlpha, etc. all can be used with that method. The template prevents them from having to write one unique method per “pixel” class

Ah okay, great, But still not sure how I’m supposed to use that in conjuction with Image::BitmapData

True, I don’t see an immediate way in the library to get PixelARGB from BitmapData… aside from using reinterpret_cast which isn’t recommended since you’re foregoing type-checking, but works in the case below:

PixelARGB *data = reinterpret_cast<PixelARGB*>(someARGBBitmapData.data);

But it seems that you could just be using the other methods of BitmapData, such as:

const Colour c = bitmapData.getPixelColour(x, y);
const PixelARGB pixel = c.getPixelARGB();

Or grabbing the argb values as you’re doing, and construct a PixelARGB from them

Yes, I tried that, but the problem with the Colour class is that it is comparably slow. - why I opted to work on the individual bytes directly

Then you can use reinterpret_cast<PixelARGB*>, since the uint8 *data components are already in the same format that the struct is in

I just wouldn’t use reinterpret casting outside of situations like this

But will this make @jules happy? :sweat_smile:

1 Like

This is really confusing me, so if I want to i.e. convolve one image with another, I would reinterpret both bitmap buffers as a pixelRGB class/enum/thingy. (I’m using RGB for now)

PixelRGB* data = reinterpret_cast<PixelRGB*>(someARGBBitmapData.data);

Then this will give me access to functions such as data.blend(const PixelRGB src)

So theoretically I could convolve one entire image with another one. But this still doesn’t let me apply mathematical operations on only the selected pixels I care for, or a set of operations that I care to do.


EDIT: the below has been solved by changing jlimit<uint8> to jlimit<int>


To the best of my current understanding assuming buffer3 is all zeros, and noiseBuffer contains noise:

Image::BitmapData buffer3Data(buffer3, Image::BitmapData::writeOnly);
Image::BitmapData noiseBufferData(noiseBuffer, Image::BitmapData::readOnly);

    for (int y = 0; y < rows; ++y)
        for (int x = 0; x < cols; ++x)
        {
            auto* buf3 = buffer3Data.getPixelPointer(x,y);
            auto* noise = noiseBufferData.getPixelPointer(x,y);
            uint8 something = jlimit<uint8>(0, 255, buf3[0] - noise[0]);
            buf3[0] = something;
            buf3[1] = something;
            buf3[2] = something;
        }

I would expect this (0 - noise) to display just pure black. Yet it doesn’t. :thinking:

No, it does not work exactly this way.

  1. You have to create an image first:
    Image image (Image::PixelFormat::ARGB, width, height, false)
  2. Then you create a BitmapData object:
    Image::BitmapData bd (image, Image::BitmapData::readWrite)

BitmapData object (or rather Image itself) has an internal buffer where the pixels are placed one by one (like RGBARGBARGBA - or in a different order dependent on the platform etc.)
BitmapData::data is a pointer to this buffer. If you need to operate directly on the pixels you can do the trick with reinterpret_cast:

for (int j = 0; j < image.getHeight(); ++j)
{
    PixelARGB* p = reinterpret_cast<PixelARGB*> (bd.getLinePointer (j));
    for (int i = 0; i < image.getWidth(); ++i)
        p[i].multiplyAlpha (0.5f);
}

or even shorter if you need to go through all pixels:

PixelARGB* p = reinterpret_cast<PixelARGB*> (bd.data);
for (int i = 0; i < image.getWidth() * image.getHeight(); ++i)
    p[i].multiplyAlpha (0.5f);

I really recommend reading the online docs. You seem to do the low level things just guessing how they work :wink:

Which docs in particular are you talking about, because I’m trying to read the docs.juce.com, but for example, it’s not explicit that I’d need to reinterpret_cast, and there are little things, that I think would only be obvious to C++ wizards. I’m often asking for book/video recommendations, if you have any that you think will be useful to me, I’d love to know! But agreed. I’m doing way to much guesswork!

So I tried your method.

I have no problem creating and displaying an Image. In fact I already feed Simplex noise into the buffer of that image. Then I call this function (before displaying the image):

void beLikeMBO()
{
    Image::BitmapData noiseBufferData(noiseBuffer, Image::BitmapData::readWrite);

    for (int y = 0; y < rows; ++y)
    {
        PixelRGB* p = reinterpret_cast<PixelRGB*> (noiseBufferData.getLinePointer(y));
        for (int x = 0; x < cols; ++x)
        {
            p[x].multiplyAlpha(0.25f);
        }
    }
}

Yet it makes no noticeable difference to the noise I display without calling that function.

So then I’m wondering maybe it doesn’t work because PixelRGB doesn’t actually have an alpha channel (not sure why it would contain that function if not though!) so I try .blend()…

void beLessLikeMBO()
{
    Image::BitmapData buffer3Data(buffer3, Image::BitmapData::readWrite);
    Image::BitmapData noiseBufferData(noiseBuffer, Image::BitmapData::readWrite);

    for (int y = 0; y < rows; ++y)
    {
        PixelRGB* p = reinterpret_cast<PixelRGB*> (noiseBufferData.getLinePointer(y));
        PixelRGB* p2 = reinterpret_cast<PixelRGB*> (buffer3Data.getLinePointer(y));
        for (int x = 0; x < cols; ++x)
        {
            p2[x].blend(p);
        }
    }
}

And that unfortunately produces an error beyond my capacity to fix:

$    Compiling Main.cpp
    In file included from /usr/share/juce/modules/juce_graphics/juce_graphics.h:112,
                     from /usr/share/juce/modules/juce_audio_devices/juce_audio_devices.h:58,
                     from ../../Source/../JuceLibraryCode/JuceHeader.h:18,
                     from ../../Source/Main.cpp:11:
    /usr/share/juce/modules/juce_graphics/colour/juce_PixelFormats.h: In instantiation of ‘void juce::PixelRGB::blend(const Pixel&) [with Pixel = juce::PixelRGB*]’:
    ../../Source/MainComponent.h:170:30:   required from here
    /usr/share/juce/modules/juce_graphics/colour/juce_PixelFormats.h:472:52: error: request for member ‘getAlpha’ in ‘src’, which is of pointer type ‘juce::PixelRGB* const’ (maybe you meant to use ‘->’ ?)
             const uint32 alpha = (uint32) (0x100 - src.getAlpha());
                                                    ~~~~^~~~~~~~
    /usr/share/juce/modules/juce_graphics/colour/juce_PixelFormats.h:475:47: error: request for member ‘getEvenBytes’ in ‘src’, which is of pointer type ‘juce::PixelRGB* const’ (maybe you meant to use ‘->’ ?)
             uint32 rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha));
                                               ~~~~^~~~~~~~~~~~
    /usr/share/juce/modules/juce_graphics/colour/juce_PixelFormats.h:477:47: error: request for member ‘getOddBytes’ in ‘src’, which is of pointer type ‘juce::PixelRGB* const’ (maybe you meant to use ‘->’ ?)
             uint32 ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8));
                                               ~~~~^~~~~~~~~~~

AFAIR pixels are stored internally in the RGBA format anyway, so you shouldn’t cast the data pointer onto PixelRGB.

But I declared my Image as type RGB…

Image noiseBuffer {Image::RGB, cols, rows, true};

Read the docs: :wink:

format - the preferred pixel format. Note that this is only a *hint* which is passed to the ImageType class - different ImageTypes may not support all formats, so may substitute e.g. ARGB for RGB.

1 Like

Okay you got me! :raised_hands:

Although to be fair, (and due to my incompetence most likely) after reading that, I still don’t really know what that means.

But Okay, I’m gonna take a break, and try again with everything as ARGB.

Thanks for your help.

Woah!

Just to update anyone who is still following this (and apologies, but I think this will be the last post in this thread)

But I fixed my problem of uint8 underflow (and yes, Jules was right!). The code that I expected to work (from a previous post):

Image::BitmapData buffer3Data(buffer3, Image::BitmapData::writeOnly);
Image::BitmapData noiseBufferData(noiseBuffer, Image::BitmapData::readOnly);

    for (int y = 0; y < rows; ++y)
        for (int x = 0; x < cols; ++x)
        {
            auto* buf3 = buffer3Data.getPixelPointer(x,y);
            auto* noise = noiseBufferData.getPixelPointer(x,y);
            uint8 something = jlimit<uint8>(0, 255, buf3[0] - noise[0]);
            buf3[0] = something;
            buf3[1] = something;
            buf3[2] = something;
        }

Was indeed suffering underflow/arithmatic problems. I tried a jlimit to force the numbers to be ‘correct’, but you can’t fix problems with the same thing that created it… jlimit needed to operate on them from a ‘higher dimension’… In the <int> domain (in order to have my calculation produce a negative number for jlimit to clip!) from there we implicitly convert into uint8 something after jlimit was given a chance to work it’s magic!

I should note that on the OSX / CoreGraphics backend, it will actually give you back an ARGB image!

Best is to at least check the BitmapData::pixelStride value, so you don’t read out of bounds. So the worst that can happen is, that your images look gibberish…

Here is my ColourCurve class to do colour adjustments on image, which works easily for video in realtime:

3 Likes

Which reminds me that I’ve got another lecture to add to the playlist! :wink: Thanks Daniel!