Color to Grayscale Exploration

This is a color to grayscale exploration.

Grayscale conversion is a complex process that involves the range of human visual perception, established video standards, gamma color encoding, and considerations of computational efficiency.

Rec. 709 weights combined with gamma-corrected conversion yield precise color fidelity and accurate luminance representation but require more computation due to gamma processing.

Rec. 601 weights produce grayscale images that are visually acceptable to most observers and are simpler and faster to compute.

Typical grayscale methods used by common software include:

  • Processing: Rec. 601 weights Reference
  • Adobe Photoshop: Rec. 709 weights
  • OpenCV: Rec. 601 weights Reference
  • GIMP: Rec. 709 weights
  • p5.js: Rec. 709 weights Reference

Review code to see keyboard and mouse interaction.

// Color to Grayscale Exploration
// Author: GLV
// Date:   2024-11-09

// Inspiration:
// https://discourse.processing.org/t/color-to-grayscale-algorithm/45171

// A mousePressed will toggle the weights
// Moving mouse over far right image will sweep through different gamma values

// Image used:
// https://links.uwaterloo.ca/Repository.html

// References:
// sRGB - Wikipedia
// https://en.wikipedia.org/wiki/SRGB

// Grayscale - Wikipedia
// https://en.wikipedia.org/wiki/Grayscale

// Relative luminance - Wikipedia
// https://en.wikipedia.org/wiki/Relative_luminance

PImage img0, imgCopy;

float [] w0 = {0.2126, 0.7152, 0.0722}; // luma Rec. 709 weights
float [] w1 = {0.2990, 0.5870, 0.1140}; // luma Rec. 601 weights

float [] weight;

void settings()
  {
  // Downloads image and checks if it exists
  if(sketchFile("frymire.tif").isFile())
    {
    img0 = loadImage("frymire.tif");
    println("image loaded!");
    println(img0.width, img0.height);
    }
  else
    {
    //img0 = loadImage("https://links.uwaterloo.ca/Repository/TIF/frymire.tif");
    img0 = loadImage("https://icosahedron.website/system/media_attachments/files/112/915/452/093/567/707/original/ef6b0e1c3e422c2d.png");

    img0.save("frymire.tif");
    println("Downloaded and saved!");
    }  
  
  size(3*img0.width/2, img0.height/2);
  
  //noLoop();
  }
  
void setup()
  {    
  imgCopy = img0.get();
  imgCopy.resize(img0.width/2, img0.height/2);
    
  mouseX = width - (width - 2*width/3)/2;
  weight = w0;
  }  
  
void draw()
  {
  image(imgCopy, 0, 0);  
  
  float gamma = map(mouseX, 2*width/3, width, 0.2, 4.2);
  gamma = constrain(gamma, 0.2, 4.2);
    
  PImage img2 = gsf0(imgCopy, weight);
  image(img2, 1*img2.width, 0);
  
  PImage img1 = gsf1(imgCopy, weight, gamma);
  image(img1, 2*imgCopy.width, 0);
  
  textSize(48);
  textAlign(LEFT, CENTER);
  fill(255, 0, 255);
  gamma = round(gamma*10)/10.0;
  text (nf(gamma, 1, 1), width-imgCopy.width+50 , imgCopy.height-50);
  }

//------------------------------------------------------------    
  
// Grayscale with Processing functions 
  
PImage gsf0(PImage pi, float [] w)
  {  
  PImage pic = pi.copy();
    
  pic.loadPixels();   
  for(int i=0; i<pic.pixels.length; i++)
    {
    int idx = pic.pixels[i];  
    
  // extract
    float r = red(idx);
    float g = green(idx);
    float b = blue(idx); 
           
  // process
    float gs = w[0]*r + w[1]*g + w[2]*b;     
        
  // set
    pic.pixels[i] = color(gs);
    } 
  pic.updatePixels();  
 
  return pic;  
  }
 
boolean toggle = false; 
 
void mousePressed()
  {
  toggle = !toggle;
  if (toggle)
    weight = w0;
  else
    weight = w1;
  }

// https://www.scantips.com/lights/gamma3.html
// https://en.wikipedia.org/wiki/Luma_(video)

// Grayscale with Processing functions (floats)  
  
PImage gsf1(PImage pi, float [] w, float ga)
  {  
  PImage pic = pi.copy();

  pic.loadPixels();   
  for(int i=0; i<pic.pixels.length; i++)
    {
    int idx = pic.pixels[i];  
    
    // extract
    float r = red(idx);
    float g = green(idx);
    float b = blue(idx); 
    
    // process
    float gam = ga;
      
    // Precision gamma to linear 
    float rl = 0;
    float rn = r/255; //normalized to 0 to 1
    if (rn <= 0.04045)
      rl = rn/12.92;
    else
      rl = pow( (rn + 0.055)/1.055, gam );
      
    float gl = 0;
    float gn = g/255; //normalized to 0 to 1
    //if (gn <= 0.04045)
    //  gl = gn/12.92;
    //else
    //  gl = pow( (gn + 0.055)/1.055, gam );
    
    // Cleaner than above:
    gl = (gn <= 0.04045) ? gn/12.92 : pow( (gn+0.055)/1.055, gam );      
            
    float bl = 0;
    float bn = b/255; //normalized to 0 to 1
    if (bn <= 0.04045)
      bl = bn/12.92;
    else
      bl = pow( (bn + 0.055)/1.055, gam );
      
    // grayscale weights  
    float gs = w[0]*rl + w[1]*gl + w[2]*bl;       
        
    // linear to gamma   
    //if (gs <= 0.0031308)
    //  gs = 12.92*gs*255;
    //else
    //  gs = (1.055*pow(gs, 1/gam) - 0.055)*255;
          // Cleaner than above:
    gs = (gs <= 0.0031308) ? 12.92*gs : (1.055*pow(gs, 1/gam) - 0.055);
    gs = gs*255;
           
    // set
    pic.pixels[i] = color(gs);
    } 
  pic.updatePixels();  
  return pic;  
  }  

int count = 0;

void keyPressed()
  {
  int w = img0.width/2; 
  int h = img0.height/2;    
    
  if (count == 0)
    {
    imgCopy = img0.get();
    imgCopy.resize(img0.width/2, img0.height/2);
    }
    
  else if (count == 1)
    imgCopy = img0.get(0, 0, w, h);
  else if (count == 2)
    imgCopy = img0.get(w, 0, w, h);
  else if (count == 3)
    imgCopy = img0.get(w, h, w, h);
  else if (count == 4)
    imgCopy = img0.get(0, h, w, h);
    
  count++;  
  count = count%5; 
  }

:)

3 Likes