Search code examples
c++mathlibjpeg

How to get the calculate the RGB values of a pixel from the luminance?


I want to compute the RGB values from the luminance.

The data that I know are :

  • the new luminance (the value that I want to apply)
  • the old luminance
  • the old RGB values.

We can compute the luminance from the RGB values like this :
uint8_t luminance = R * 0.21 + G * 0.71 + B * 0.07;

My code is :

// We create a function to set the luminance of a pixel
void jpegImage::setLuminance(uint8_t newLuminance, unsigned int x, unsigned int y) {

  // If the X or Y value is out of range, we throw an error
  if(x >= width) {
    throw std::runtime_error("Error : in jpegImage::setLuminance : The X value is out of range");
  }
  else if(y >= height) {
    throw std::runtime_error("Error : in jpegImage::setLuminance : The Y value is out of range");
  }

  // If the image is monochrome
  if(pixelSize == 1) {

    // We set the pixel value to the luminance
    pixels[y][x] = newLuminance;
  }

  // Else if the image is colored, we throw an error
  else if(pixelSize == 3) {
    // I don't know how to proceed
    // My image is stored in a std::vector<std::vector<uint8_t>> pixels;

    // This is a list that contain the lines of the image
    // Each line contains the RGB values of the following pixels
    // For example an image with 2 columns and 3 lines
    // [[R, G, B, R, G, B], [R, G, B, R, G, B], [R, G, B, R, G, B]]

    // For example, the R value with x = 23, y = 12 is:
    // pixels[12][23 * pixelSize];
    // For example, the B value with x = 23, y = 12 is:
    // pixels[12][23 * pixelSize + 2];
    // (If the image is colored, the pixelSize will be 3 (R, G and B)
    // (If the image is monochrome the pixelSIze will be 1 (just the luminance value)
  }
}

How can I proceed ? Thanks !


Solution

  • You don't need the old luminance if you have the original RGB.

    Referencing https://www.fourcc.org/fccyvrgb.php for YUV to RGB conversion.

    Compute U and V from original RGB:

    ```
    V =  (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
    U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128
    ```
    

    Y is the new luminance normalized to a value between 0 and 255

    Then just convert back to RGB:

    B = 1.164(Y - 16)                   + 2.018(U - 128)
    G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
    R = 1.164(Y - 16) + 1.596(V - 128)
    

    Make sure you clamp your computed values of each equation to be in range of 0..255. Some of these formulas can convert a YUV or RGB value to something less than 0 or higher than 255.

    There's also multiple formula for converting between YUV and RGB. (Different constants). I noticed the page listed above has a different computation for Y than you cited. They are all relatively close with different precisions and adjustments. For just changing the brightness of a pixel, almost any formula will do.

    Updated

    I originally deleted this answer after the OP suggested it wasn't working for him. I was too busy for the last few days to investigate, but I wrote some sample code to confirm my hypothesis. At the bottom of this answer is a snippet of GDI+ based code increases the luminance of an image by a variable amount. Along with the code is an image that I tested this out on and two conversions. One at 130% brightness. Another at 170% brightness.

    Here's a sample conversion

    Original Image Original Image

    Updated Image (at 130% Y) Updated image

    Updated Image (at 170% Y) enter image description here

    Source:

    #define CLAMP(val) {val = (val > 255) ? 255 : ((val < 0) ? 0 : val);}
    
    void Brighten(Gdiplus::BitmapData& dataIn, Gdiplus::BitmapData& dataOut, const double YMultiplier=1.3)
    {
        if ( ((dataIn.PixelFormat != PixelFormat24bppRGB) && (dataIn.PixelFormat != PixelFormat32bppARGB)) ||
             ((dataOut.PixelFormat != PixelFormat24bppRGB) && (dataOut.PixelFormat != PixelFormat32bppARGB)))
        {
            return;
        }
    
        if ((dataIn.Width != dataOut.Width) || (dataIn.Height != dataOut.Height))
        {
            // images sizes aren't the same
            return;
        }
    
    
        const size_t incrementIn = dataIn.PixelFormat == PixelFormat24bppRGB ? 3 : 4;
        const size_t incrementOut = dataOut.PixelFormat == PixelFormat24bppRGB ? 3 : 4;
        const size_t width = dataIn.Width;
        const size_t height = dataIn.Height;
    
    
        for (size_t y = 0; y < height; y++)
        {
            auto ptrRowIn = (BYTE*)(dataIn.Scan0) + (y * dataIn.Stride);
            auto ptrRowOut = (BYTE*)(dataOut.Scan0) + (y * dataOut.Stride);
    
            for (size_t x = 0; x < width; x++)
            {
                uint8_t B = ptrRowIn[0];
                uint8_t G = ptrRowIn[1];
                uint8_t R = ptrRowIn[2];
                uint8_t A = (incrementIn == 3) ? 0xFF : ptrRowIn[3];
    
                auto Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16;
                auto V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128;
                auto U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128;
    
                Y *= YMultiplier;
    
                auto newB = 1.164*(Y - 16) + 2.018*(U - 128);
                auto newG = 1.164*(Y - 16) - 0.813*(V - 128) - 0.391*(U - 128);
                auto newR = 1.164*(Y - 16) + 1.596*(V - 128);
    
                CLAMP(newR);
                CLAMP(newG);
                CLAMP(newB);
    
                ptrRowOut[0] = newB;
                ptrRowOut[1] = newG;
                ptrRowOut[2] = newR;
                if (incrementOut == 4)
                {
                    ptrRowOut[3] = A; // keep original alpha
                }
    
                ptrRowIn += incrementIn;
                ptrRowOut += incrementOut;
            }
        }
    }