Color separation

Revision as of 13:13, 20 July 2023 by PureFox (talk | contribs) (→‎{{header|Wren}}: Now uses DOME's built-in Math module for the CMYK script.)

Color separation is the act of decomposing a color graphic or photo into single-color layers.

Color separation is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Introduction
Separating in the RGB color model

The RGB color model is used when creating color on surfaces that are naturally black (without color), such as screens of TVs, computer monitors or cellphones. It is called RGB from the names of additive colors (red, green, blue). This model is called additive because the colors are "added" to black (no color).

Each pixel on the screen is built by driving three small and very close but still separated RGB light sources. At common viewing distance, the separate sources are indistinguishable, which tricks the eye to see a given solid color. All the pixels together arranged in the rectangular screen surface conforms the color image.

Each layer shows the intensity of the respective primary color on the original image. Black dots means that these light sources are completely off there.

Separating in the CMY color model

The CMY color model is used when creating color on surfaces that are naturally white (containing all colors), such as paper. It is called CMY from the names of subtractive colors (cyan, magenta, yellow). This model is called subtractive because the colors are "subtracted" to white (all colors).

The process usually involves "mixing" cyan, magenta, and yellow ink onto the paper. In some printing procedures, such as inkjet printers, the inks are not actually mixed, they are usually placed as small dots using different sizes and patterns, i.e. halftoning or dithering.

Each layer shows the intensity of ink application of the respective color on the original image. No ink is applied to the white areas.

Separating in the CMYK color model

The CMYK model adds a black layer to the CMY model. There are several reasons for this, including:

  • In practice, mixing 100% cyan, magenta, and yellow ink on paper (intended to get black) does not fully absorb all the colors, whereas "pure" black ink does.
  • For various types of paper —i.e. standard office paper—, printing darker areas require more cyan, magenta, and yellow ink, making the paper damp.
  • In darker or blacker areas, it is less expensive to use a single ink than three, or to use less of the other inks.
Warning

Simplistic separation into CMY and CMYK layers is often discouraged for professional printing, because the printed result depends on the material properties of the inks, the "mixing" process, and the material properties of the media. For this, the graphic industry has created the so-called ICC profiles. They are specifications for correction and adjustment for a certain combination of printing device and media, among other factors.

If you want to go to a print shop to reproduce an existing image or printed material, do not separate the CMYK layers yourself, instead give them your file or printed material, they will do the color separation and printing process using ICC profiles according to the scanner, printer and paper they will use.

Task

For educational and entertainment purposes, write programs, scripts, functions, procedures, etc. to do the following:

  • Given an image, create three images, one for each color separated in the RGB model.
  • Given an image, create three images, one for each color separated in the CMY model.
  • Given an image, create four images, one for each color separated in the CMYK model. It is not required to implement ICC profiles. Note: there is not a unique algorithm to convert from RGB to CMYK, several algorithms have been developed according to special optimizations, you can use whatever you like. The simplest one is called "rough" or "naive", see this, page 23.

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website.

In this page you can see and run the program(s) related to this task and their results. You can also change either the programs or the parameters they are called with, for experimentation, but remember that these programs were created with the main purpose of showing a clear solution of the task, and they generally lack any kind of validation.

Solution

Color separation in RGB model

 

 

 

Color separation in CMY model

 

 

 

Color separation in CMYK model

 

 

 

Wren

Translation of: Fōrmulæ
Library: DOME

First the script for the RGB model with the original image shown first.

import "dome" for Window
import "graphics" for Canvas, Color, ImageData

class ColorSeparationRGB {
    construct new() {
        Window.title = "Color separation, RGB model"
        var fileName = "Lenna50.jpg"
        var image = ImageData.loadFromFile(fileName)
        var w = image.width
        var h = image.height
        Window.resize(w * 4 + 40, h)
        Canvas.resize(w * 4 + 40, h)

        //draw original image
        image.draw(0, 0)

        var imgR = ImageData.create("lennaR", w, h)
        var imgG = ImageData.create("lennaG", w, h)
        var imgB = ImageData.create("lennaB", w, h)
        for (y in 0...h) {
            for (x in 0...w) {
                var col = image.pget(x, y)
                imgR.pset(x, y, Color.rgb(col.r, 0, 0))
                imgG.pset(x, y, Color.rgb(0, col.g, 0))
                imgB.pset(x, y, Color.rgb(0, 0, col.b))
            }
        }

        // draw color separated images
        imgR.draw(w + 10, 0)
        imgG.draw(2 * (w + 10), 0)
        imgB.draw(3 * (w + 10), 0)
    }

    init() {}
    update() {}
    draw(alpha) {}
}

var Game = ColorSeparationRGB.new()
 

Next the script for the CMY model with the original image shown first.

import "dome" for Window
import "graphics" for Canvas, Color, ImageData

class ColorSeparationCMY {
    construct new() {
        Window.title = "Color separation, CMY model"
        var fileName = "Lenna50.jpg"
        var image = ImageData.loadFromFile(fileName)
        var w = image.width
        var h = image.height
        Window.resize(w * 4 + 40, h)
        Canvas.resize(w * 4 + 40, h)

        //draw original image
        image.draw(0, 0)

        var imgC = ImageData.create("lennaC", w, h)
        var imgM = ImageData.create("lennaM", w, h)
        var imgY = ImageData.create("lennaY", w, h)
        for (y in 0...h) {
            for (x in 0...w) {
                var col = image.pget(x, y)
                imgC.pset(x, y, Color.rgb(col.r, 255, 255))
                imgM.pset(x, y, Color.rgb(255, col.g, 255))
                imgY.pset(x, y, Color.rgb(255, 255, col.b))
            }
        }

        // draw color separated images
        imgC.draw(w + 10, 0)
        imgM.draw(2 * (w + 10), 0)
        imgY.draw(3 * (w + 10), 0)
    }

    init() {}
    update() {}
    draw(alpha) {}
}

var Game = ColorSeparationCMY.new()
 

Finally, the script for the CMYK model with the original image shown first, in the top row.

I've had to borrow the Fōrmulæ image for this one as the red component of the pixels in the previous image is such that the 'C' image seems to come out completely white.

import "dome" for Window
import "graphics" for Canvas, Color, ImageData
import "math" for Math

class ColorSeparationCMYK {
    construct new() {
        Window.title = "Color separation, CMYK model"
        var fileName = "lady.png"
        var image = ImageData.loadFromFile(fileName)
        var w = image.width
        var h = image.height
        Window.resize(w * 4 + 40, h * 2 + 20)
        Canvas.resize(w * 4 + 40, h * 2 + 20)

        // draw original image on top row
        image.draw(0, 0)

        var imgC = ImageData.create("ladyC", w, h)
        var imgM = ImageData.create("ladyM", w, h)
        var imgY = ImageData.create("ladyY", w, h)
        var imgK = ImageData.create("ladyK", w, h)
        for (j in 0...h) {
            for (i in 0...w) {
                var col = image.pget(i, j)
                var rc = 255 - col.r
                var gc = 255 - col.g
                var bc = 255 - col.b
                var k = Math.min(Math.min(rc, gc), bc)
                var kc = 255 - k
                if (kc != 0) {
                    var c = (((rc - k) / kc) * 255).floor
                    var m = (((gc - k) / kc) * 255).floor
                    var y = (((bc - k) / kc) * 255).floor    
                    imgC.pset(i, j, Color.rgb(255-c, 255, 255))
                    imgM.pset(i, j, Color.rgb(255, 255-m, 255))
                    imgY.pset(i, j, Color.rgb(255, 255, 255-y))
                } else {
                    imgC.pset(i, j, Color.rgb(0, 0, 0))
                    imgM.pset(i, j, Color.rgb(0, 0, 0))
                    imgY.pset(i, j, Color.rgb(0, 0, 0))
                }
                imgK.pset(i, j, Color.rgb(kc, kc, kc))
            }
        }

        // draw color separated images on bottom row
        imgC.draw(0, h + 10)
        imgM.draw(w + 10, h + 10)
        imgY.draw(2 * (w + 10), h + 10)
        imgK.draw(3 * (w + 10), h + 10)
    }

    init() {}
    update() {}
    draw(alpha) {}
}

var Game = ColorSeparationCMYK.new()