Best way to do this: tint(C, M, Y, K)?


#1

Hello,
I would like to be able to manipulate the CMYK channels of an image in Processing. (I realize that on a monitor the colors of an image are generated by RGB light, but I want to mimic the effects of the printing processing and display those effects on a monitor.) I’m wondering if there’s a way to manipulate the CMYK channels of an image the same way that tint(R, G, B) does for RGB?

For example:

tint(0, 0, 255); // because the parameters of tint are (R, G, B) this tints 
                 // the image blue, but I would like to do this:
tint(0, 255, 0, 0); // if the parameters of tint could be (C, M, Y, K) this 
                    // would tint the image magenta

Thanks for any tips you might have!


#2

Did you try colormode?
https://processing.org/reference/colorMode_.html


#3

I’ve looked at color(), but not colorMode(). In looking at it just now, it doesn’t appear to have CMYK parameters. It only has RGB and Hue, Saturation, Brightness.


#4

You might want to write you own conversion method. I don’t know the exact conversion from rgb to cmyk, but it should be available online and then just use functions like map() or similar, or just do the math per hand.
Here’s a link to a page that gives you the conversion. You could try to figure out the conversion math behind it and just apply it in your method. Actually, i just played a bit with it, and it seems to be a pretty basic math…

https://www.rapidtables.com/convert/color/rgb-to-cmyk.html

Yeah… I always forget the link :sweat_smile:

And theres actually the conversion rates if you scroll down, so its even easier to do. Probably just 4-10 lines of code in total :wink:


#5

Lexyth , did you forget the link?


#6

Thanks guys! I’ll give your idea a whirl.


#7

I have an sketch that “simulates” a live RGB to CMYK conversion from a webcam image.

A webcam image or digital image is always captured in RGB. When the image is displayed on a screen, it is also always in RGB, because screens display all colors mixing the primary additive colors red R, green G and blue B. When you have an CMYK image file, the computer “simulates” how the CMYK would look like when printed on paper, and displays it on the RGB screen. If you have any further questions about that, ask me! :grin:

CMYK images are not supported to be loaded with loadImage(). You may need to convert your CMYK image to RGB with another software before loading it, if that is the case.

I think that is not exactly what you are looking for, but i think it may help:

(to run the code you may need to configure/test your webcam, or rewrite the code to use a loaded image instead. btw, sorry my code may be a bit messy!)

Preview:

Code:

import processing.video.*; 

Capture cam;

PImage cyan;
PImage magenta;
PImage yellow;
PImage black;

int w = 640; // Webcam image width
int h = 480; // Webcam image height

void setup() {
  size(1920, 960);
  background(0);

  // Webcam setup
  String[] cameras = Capture.list();
  printArray(cameras);
  cam = new Capture(this, cameras[0]);
  cam.start();

  // Create Images
  cyan = createImage(w, h, RGB);
  magenta = createImage(w, h, RGB);
  yellow = createImage(w, h, RGB);  
  black = createImage(w, h, RGB);
}

void draw() {
  if (cam.available() == true) {
    cam.read();
    
    // LoadPixels of the images
    cam.loadPixels();
    cyan.loadPixels();
    magenta.loadPixels();
    yellow.loadPixels();
    black.loadPixels();

    // RGB to CMYK convertion simulation
    for (int i = 0; i < cam.pixels.length; i++) {
      float c_ = red(cam.pixels[i]);
      float m_ = green(cam.pixels[i]);
      float y_ = blue(cam.pixels[i]);
      float k_ = brightness(cam.pixels[i]);

      cyan.pixels[i] = color(c_, 255, 255);
      magenta.pixels[i] = color(255, m_, 255);
      yellow.pixels[i] = color(255, 255, y_);
      black.pixels[i] = color(k_);
    }
    
    // UpdatePixels of the images
    cyan.updatePixels();
    magenta.updatePixels();
    yellow.updatePixels();
    black.updatePixels();

    // Display the images
    image(cam, 0, 0);
    image(cyan, w + 0*w, 0);
    image(magenta, w + 1*w, 0);
    image(yellow, w + 0*w, h);
    image(black, w + 1*w, h);
  }
}

#8

By the way, the important part of the code is this one:

RGB is addictive, CMYK is subtractive. So I need to invert the values somehow. My code actually converts the original RGB image to RGB images that simulates the CMYK channels.

Cyan is non-red;
Magenta is non-green.
Yellow is non-blue.

Black is tricky, so i used just the brigthness.

// RGB to CMYK convertion simulation
for (int i = 0; i < cam.pixels.length; i++) {
  float c_ = red(cam.pixels[i]);
  float m_ = green(cam.pixels[i]);
  float y_ = blue(cam.pixels[i]);
  float k_ = brightness(cam.pixels[i]);

  cyan.pixels[i] = color(c_, 255, 255);
  magenta.pixels[i] = color(255, m_, 255);
  yellow.pixels[i] = color(255, 255, y_);
  black.pixels[i] = color(k_);
}

I recomend this video for more info: How Technicolor changed movies


#9

Thanks for this in-depth response brunoruchiga! You’re right–the trick from here is to figure out how to made it look like I’ve added, for example, the C, M, and K together, but not the Y. Which is to say, instead of taking out three colors, I need to figure out how to take out only one. This particular method doesn’t seem to work because in RGB to make cyan you need (0, 255, 255), and to make magenta you need (255, 0, 255), and you can’t really add those two together to make it look like there’s no yellow. I tried using a 50% transparency but the result is identical to doing: (127, 127, 255).


#10

As mentioned before, CMYK is subtractive. If you want your sketch to work in the same way as printing, you need to set your background to white and blend mode to blendMode(SUBTRACT). Individual channels are then represented by their complements: cyan by red, magenta by green, yellow by blue, and black by white.

printing cyan = subtracting red
printing magenta = subtracting green
printing yellow = subtracting blue
printing black = subtracting white

So the process would be like this: convert RGB colors to CMYK by using a formula, then store them in a PImage as color(C, M, Y) and black in another PImage as color(K). If you start with white background and blendMode(SUBTRACT), drawing the CMY image with e.g. tint(0, 0, 255) would draw yellow, same for the other components. For the K image use noTint() to subtract from all channels equally.

To convert from RGB colors to CMYK colors, use the formula alreadu posted here:

You can also store C, M and Y in separate PImages, each will have only red, green or blue, respectively. Then you don’t need to use tint. Either way, you will have a full control over which channels of CMYK you want to draw. Just keep the SUBTRACT blend mode :wink:


#11

I think @Jakub is right.

The reference for all the blend modes: blendMode() or maybe blend(). You can use SUBSTRACT or MULTIPLY and try another ones. They are similar to Photoshop blending modes.

Mathematically, RGB converts directly to CMY, with no need of Black (K). Theoretically, the mix of C+M+Y should result in black. We only use Black on printing because inks are not perfect, so black ink gets better results for contrast and other details. So, when you add black ink, you need to do the math to decrease the CMY values. For more information, look for Grey component replacement.

Here is my code from before, now using blendMode(MULTIPLY), and mixing the color with alpha. The position of the images now can be controled with the mouse. I also hide the Black channel because I am not calculating the “Grey component replacement” (so it would only make the image darker without representing the reality).

import processing.video.*; 

Capture cam;

PImage cyan;
PImage magenta;
PImage yellow;
PImage black;

int w = 640; // Webcam image width
int h = 480; // Webcam image height

void setup() {
  size(1920, 960);
  blendMode(MULTIPLY);

  // Webcam setup
  String[] cameras = Capture.list();
  printArray(cameras);
  cam = new Capture(this, cameras[0]);
  cam.start();
  
  // Create Images  
  cyan = createImage(w, h, ARGB);
  magenta = createImage(w, h, ARGB);
  yellow = createImage(w, h, ARGB);  
  black = createImage(w, h, ARGB);
}

void draw() {
  if (cam.available() == true) {
    background(255);

    cam.read();

    // LoadPixels of the images
    cam.loadPixels();
    cyan.loadPixels();
    magenta.loadPixels();
    yellow.loadPixels();
    black.loadPixels();

    // RGB to CMYK conversion simulation
    for (int i = 0; i < cam.pixels.length; i++) {
      float c_ = (255-red(cam.pixels[i]));
      float m_ = (255-green(cam.pixels[i]));
      float y_ = (255-blue(cam.pixels[i]));
      float k_ = (255-brightness(cam.pixels[i]));

      cyan.pixels[i] = color(0, 255, 255, c_);
      magenta.pixels[i] = color(255, 0, 255, m_);
      yellow.pixels[i] = color(255, 255, 0, y_);
      black.pixels[i] = color(0, k_);
    }
    
    // UpdatePixels of the images    
    cyan.updatePixels();
    magenta.updatePixels();
    yellow.updatePixels();
    black.updatePixels();

    // Display the images with mouse position
    image(cam, 0, 0);
    image(cyan, w + 0*w, 0);
    image(magenta, mouseX-w, 0);
    image(yellow, w + 0*w, mouseY-h);
    //image(black, mouseX-w, mouseY-h);
  }
}