Color separation

From Rosetta Code
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

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

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.

FreeBASIC

Based on Wren entry.

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

Windowtitle "Color separation, RGB model"
Dim As String imagen = "lena50.bmp"
Dim As Long ancho, alto, f = Freefile()
' open BMP file
Open imagen For Binary Access Read As #f
' retrieve BMP dimensions
Get #f, 19, ancho : Get #f, 23, alto
Close #f
' create image with BMP dimensions
Dim As Any Ptr img = Imagecreate(ancho, alto)
Screenres 40+(ancho*4), alto+10, 32
' load BMP file into image buffer
Bload imagen

Dim As Integer y, x
Dim As Ulong col, r, g, b

'' Create one pixel image for cada color.
Dim imgR As Any Ptr = Imagecreate(ancho, alto)
Dim imgG As Any Ptr = Imagecreate(ancho, alto)
Dim imgB As Any Ptr = Imagecreate(ancho, alto)
For y = 0 To alto-1
    For x = 0 To ancho-1
        col = Point(x,y)
        r = col Shr 16 And 255
        g = col Shr 8  And 255
        b = col        And 255
        Pset imgR, (x, y), Rgb(r, 0, 0)
        Pset imgG, (x, y), Rgb(0, g, 0)
        Pset imgB, (x, y), Rgb(0, 0, b)
    Next x
Next y

'' Draw the image onto the screen using various blitting methods.
Put (  ancho+10, 0), imgR, Pset
Put (2*ancho+20, 0), imgG, Pset
Put (3*ancho+30, 0), imgB, Pset

'' Destroy the image.
Imagedestroy imgR : Imagedestroy imgG : Imagedestroy imgB

Sleep

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

WindowTitle "Color separation, CMY model improved 1.0"
Dim As String imagen =  "lena.bmp"
Dim As Long ancho, alto, f = FreeFile()
' open BMP file
Open imagen For Binary Access Read As #f
' retrieve BMP dimensions
Get #f, 19, ancho : Get #f, 23, alto
Close #f
' create image with BMP dimensions
Dim As Any Ptr img = ImageCreate(ancho, alto)
ScreenRes 40+(ancho*4), alto+10, 32

'draw original image
BLoad imagen

Dim As Integer y, x
Dim As ULong col, r, g, b

'' Create one pixel image for cada color.
Dim imgC As Any Ptr = ImageCreate(ancho, alto)
Dim imgM As Any Ptr = ImageCreate(ancho, alto)
Dim imgY As Any Ptr = ImageCreate(ancho, alto)
For y = 0 To alto-1
    For x = 0 To ancho-1
        col = Point(x,y)
        r = col Shr 16 And 255
        g = col Shr 8  And 255
        b = col        And 255
        PSet imgC, (x, y), RGB(r, 255, 255)
        PSet imgM, (x, y), RGB(255, g, 255)
        PSet imgY, (x, y), RGB(255, 255, b)
    Next x
Next y

'' Draw the image onto the screen using various blitting methods.
Put (  ancho+10, 0), imgC, PSet
Put (2*ancho+20, 0), imgM, PSet
Put (3*ancho+30, 0), imgY, PSet

'' Destroy the image.
ImageDestroy imgC : ImageDestroy imgM : ImageDestroy imgY

Sleep

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

#define min(a, b) iif((a) < (b), (a), (b))

WindowTitle "Color separation, CMYK model improved 1.0"
Dim As String imagen = "lady.bmp"
Dim As Long ancho, alto, f = FreeFile()
' open BMP file
Open imagen For Binary Access Read As #f
' retrieve BMP dimensions
Get #f, 19, ancho : Get #f, 23, alto
Close #f
' create image with BMP dimensions

Dim As Any Ptr img = ImageCreate(ancho, alto)
ScreenRes 50 + (ancho * 4), 20 + (alto * 2), 32
'draw original image
BLoad imagen

Dim As Integer i, j
Dim As ULong col, c, m, y, k, kc, rc, bc, gc

'' Create one pixel image for cada color.
Dim imgC As Any Ptr = ImageCreate(ancho, alto)
Dim imgM As Any Ptr = ImageCreate(ancho, alto)
Dim imgY As Any Ptr = ImageCreate(ancho, alto)
Dim imgk As Any Ptr = ImageCreate(ancho, alto)

For i = 0 To alto-1
    For j = 0 To ancho-1

        col = Point(j,i,img)
        rc = 255 - col Shr 16 And 255
        gc = 255 - col Shr 8  And 255
        bc = 255 - col        And 255
        k = min(min(rc, gc), bc)
        kc = 255 - k

        If k <> 255 Then
            c = (rc - k) * 255 \ kc
            m = (gc - k) * 255 \ kc
            y = (bc - k) * 255 \ kc
            PSet imgC, (j, i), RGB(255 - c, 255, 255)
            PSet imgM, (j, i), RGB(255, 255 - m, 255)
            PSet imgY, (j, i), RGB(255, 255, 255 - y)
        Else
            PSet imgC, (j, i), RGB(255, 255, 255)
            PSet imgM, (j, i), RGB(255, 255, 255)
            PSet imgY, (j, i), RGB(255, 255, 255)
        End If
        PSet imgK, (j, i), RGB(kc, kc, kc)

    Next j
Next i

'' Draw the image onto the screen using various blitting methods.
Put (            10, alto + 10), imgC, PSet
Put (    ancho + 20, alto + 10), imgM, PSet
Put (2 * ancho + 30, alto + 10), imgY, PSet
Put (3 * ancho + 40, alto + 10), imgK, PSet

'' Destroy the image.
Imagedestroy imgC : Imagedestroy imgM : Imagedestroy imgY : Imagedestroy imgk

Sleep

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

Phix

Translation of: Wren
--
-- demo\rosetta\Colour_separation.exw
-- ==================================
--
without js -- imImage, im_width, im_height, im_pixel, IupImageRGB, allocate,
            -- imFileImageLoadBitmap, peekNS, wait_key, IupImageFromImImage
include pGUI.e

enum RGB_R, RGB_G, RGB_B,
     CMY_C, CMY_M, CMY_Y,
     CMYKC, CMYKM, CMYKY, CMYKK

integer width = 0, height
sequence original

function separate(imImage img, integer cdx)
    if width=0 then
        width = im_width(img)
        height = im_height(img)
        original = repeat(repeat(0,width),height)
        for y=height-1 to 0 by -1 do
            for x=0 to width-1 do
                original[height-y,x+1] = im_pixel(img, x, y)
            end for
        end for
    end if
    sequence new_image = repeat(repeat(0,width),height)
    for y=1 to height do
        for x=1 to width do
            sequence pyx = original[y,x]
            integer {r,g,b} = pyx, kc, adx = cdx
            if cdx>=CMYKC then
                {r,g,b} = sq_sub(255,pyx)
                integer k = min({r,g,b})
                kc = 255 - k
                if cdx<CMYKK then
                    {r,g,b} = iff(kc=0?{0,0,0}:sq_floor(sq_mul(sq_div(sq_sub({r,g,b},k),kc),255)))
                    adx -= 3
                end if
            end if
            switch adx do
                case RGB_R: pyx = {r,0,0}
                case RGB_G: pyx = {0,g,0}
                case RGB_B: pyx = {0,0,b}
                case CMY_C: pyx = {r,#FF,#FF}
                case CMY_M: pyx = {#FF,g,#FF}
                case CMY_Y: pyx = {#FF,#FF,b}
                case CMYKK: pyx = {kc,kc,kc}
            end switch
            new_image[y,x] = pyx
        end for
    end for
    new_image = flatten(new_image) -- (as needed by IupImageRGB)
    Ihandle new_img = IupImageRGB(width, height, new_image) 
    return new_img
end function

IupOpen()

constant w = machine_word(),
         TITLE = "Colour separation",
         pError = allocate(w)
--imImage im1 = imFileImageLoadBitmap("Lenna50.jpg",0,pError)
imImage im1 = imFileImageLoadBitmap("lady.png",0,pError)
if im1=NULL then
    ?{"error opening image",peekNS(pError,w,1)}
    {} = wait_key()
    abort(0)
end if
Ihandle dlg,
        flt = IupList("DROPDOWN=YES, VALUE=1")

Ihandln image1 = IupImageFromImImage(im1),
        image2 = separate(im1,1),
        label1 = IupLabel(),
        label2 = IupLabel()
IupSetAttributeHandle(label1, "IMAGE", image1)
IupSetAttributeHandle(label2, "IMAGE", image2)

function valuechanged_cb(Ihandle /*flt*/)
    IupSetAttribute(dlg,"TITLE","Working...")
    image2 = IupDestroy(image2)
    image2 = separate(im1,IupGetInt(flt,"VALUE"))
    IupSetAttributeHandle(label2, "IMAGE", image2)
    IupSetAttribute(dlg,"TITLE",TITLE)
    IupRefresh(dlg)
    return IUP_DEFAULT
end function
IupSetCallback(flt,"VALUECHANGED_CB",Icallback("valuechanged_cb"))

IupSetAttributes(flt,`1=RGB_R, 2=RGB_G, 3=RGB_B, 4=CMY_Y, 5=CMY_M, 6=CMY_Y, 7=CMYK_C, 8=CMYK_M, 9=CMYK_Y, 10=CMYK_K`)
IupSetAttributes(flt,"VISIBLEITEMS=11") -- (still dunno why this trick works)
dlg = IupDialog(IupVbox({flt,
                         IupFill(),
                         IupHbox({IupFill(),label1, label2,IupFill()}),
                         IupFill()}))
IupSetAttribute(dlg, "TITLE", TITLE)
IupShow(dlg)
IupMainLoop()

Output similar, but one at a time, including washed out CMYKC on lenna.jpg, and things being better on the lady.jpg.

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.load(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.load(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.load(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(255, 255, 255))
                    imgM.pset(i, j, Color.rgb(255, 255, 255))
                    imgY.pset(i, j, Color.rgb(255, 255, 255))
                }
                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()