Color separation
- 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
-- -- 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
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()