%ffp Title :"ColorSpace" Filename :"colorspace.8bf" Description:"Color convertion from/to different color spaces." Copyright :"© 2007 GPL" Author :"Alois Zingl" Version :"1.0" URL :"http://free.pages.at/easyfilter/filtermeister.html" SupportedModes: RGBMode, CMYKMode, HSLMode, HSBMode, LabMode, RGB48Mode, Lab48Mode, CMYK64Mode ctl(0): RadioButton(Group), pos=(270,20), Action=Preview, "&RGB", val=1 ctl(1): RadioButton, pos=(270,30), Action=Preview, "&CMY" ctl(2): RadioButton, pos=(270,40), Action=Preview, "CMY&K" ctl(3): RadioButton, pos=(270,50), Action=Preview, "&HSL" ctl(4): RadioButton, pos=(270,60), Action=Preview, "HS&B" ctl(5): RadioButton, pos=(270,70), Action=Preview, "&L*a*b*" ctl(6): RadioButton, pos=(270,80), Action=Preview, "LCH" ctl(9): GroupBox, pos=(260,6), size=(90,90), "&Input" ctl(10): RadioButton(Group), pos=(370,20), Action=Preview, "RGB", val=1 ctl(11): RadioButton, pos=(370,30), Action=Preview, "CMY" ctl(12): RadioButton, pos=(370,40), Action=Preview, "CMYK" ctl(13): RadioButton, pos=(370,50), Action=Preview, "HSL" ctl(14): RadioButton, pos=(370,60), Action=Preview, "HSB" ctl(15): RadioButton, pos=(370,70), Action=Preview, "L*a*b*" ctl(16): RadioButton, pos=(370,80), Action=Preview, "LCH" ctl(19): GroupBox, pos=(360,6), size=(90,90), "Output" ctl(22): StaticText, "Working space:", pos=(270,122), invisible ctl(23): combobox(Group), "AdobeRGB(1998) D65\nAppleRGB D65\nBestRGB D50\n" // 0-2 "BetaRGB D50\nBruceRGB D65\nCIEE\nColorMatch D50\nDonRGB4 D50\n" // 3-7 "ECI D50\nEktaSpacePS5 D50\nNTSCC\nPAL/SECAM D65\nProPhoto D50\n" // 8-12 "SMPTE-C D65\nsRGB D65\nWideGamut D50\nOptiRGB", // 13-16 pos=(330,120), invisible, size=(90,200), val=14, Action=Preview ctl(24): "&Gamma",pos=(300,140), range=(10,100), gamma=200, Divisor=10, val=24, invisible, Action=Preview ctl(29): GroupBox,"&Options", pos=(260,106),size=(190,50) OnFilterStart: { enableCtl(2,planesWithoutAlpha>3?-1:1); // disable CMYK if less than 4 planes enableCtl(12,planesWithoutAlpha>3?-1:1); if ((ctl(5)||ctl(6)||ctl(15)||ctl(16)) && x0==0.0) // calc matrixes { triggerEvent(20,FME_CUSTOMEVENT,0); // set rgb->xyz->rgb matrix + Gamma(z0) setCtlVal(24,(int)(z0*10.0)); } isTileable = true; return false; } OnCtl(n): { /* FME_CUSTOMEVENTs 20: calculate transformation matrix from/to working space input: ctl(23) working space output: x0..x8 rgb to xyz matrix y0..y8 xyz to rgb matrix z0 Gamma */ if (n==20 && e==FME_CUSTOMEVENT) { /* To convert to the device independant color space L*a*b* we need to know the working space in xyz, defined by xr,yr, xg,yg, xb,yb, the adaption by gamma and the white point, defined by the color temperature in kelvin. */ double xr,yr, xg,yg, xb,yb; // primary xyz working space double xw,yw; // xyz white point double det, u, v, w, dGamma; int colorTemp; // in kelvin switch(ctl(23)) // RGB working space { // http://www.brucelindbloom.com/ case 0: // Adobe RGB (1998) dGamma = 2.2; colorTemp = 6500; xr = 0.64; yr = 0.33; // red xg = 0.21; yg = 0.71; // green xb = 0.15; yb = 0.06; // blue break; case 1: // Apple RGB dGamma = 1.8; colorTemp = 6500; xr = 0.625; yr = 0.34; // red xg = 0.28; yg = 0.595; // green xb = 0.155; yb = 0.07; // blue break; case 2: // Best RGB dGamma = 2.2; colorTemp = 5000; xr = 0.7347; yr = 0.2653; // red xg = 0.215; yg = 0.775; // green xb = 0.13; yb = 0.035; // blue break; case 3: // Beta RGB dGamma = 2.2; colorTemp = 5000; xr = 0.6888; yr = 0.3112; // red xg = 0.1986; yg = 0.7551; // green xb = 0.1265; yb = 0.0352; // blue break; case 4: // Bruce RGB dGamma = 2.2; colorTemp = 6500; xr = 0.64; yr = 0.33; // red xg = 0.28; yg = 0.65; // green xb = 0.15; yb = 0.06; // blue break; default: case 5: // CIE RGB dGamma = 2.2; colorTemp = 5440; xr = 0.735; yr = 0.265; // red xg = 0.274; yg = 0.717; // green xb = 0.167; yb = 0.009; // blue break; case 6: // ColorMatch RGB dGamma = 1.8; colorTemp = 5000; xr = 0.63; yr = 0.34; // red xg = 0.295; yg = 0.605; // green xb = 0.15; yb = 0.075; // blue break; case 7: // Don RGB 4 dGamma = 2.2; colorTemp = 5000; xr = 0.696; yr = 0.3; // red xg = 0.215; yg = 0.765; // green xb = 0.13; yb = 0.035; // blue break; case 8: // ECI RGB dGamma = 1.8; colorTemp = 5000; xr = 0.67; yr = 0.33; // red xg = 0.21; yg = 0.71; // green xb = 0.14; yb = 0.08; // blue break; case 9: // Ekta Space PS5 dGamma = 2.2; colorTemp = 5000; xr = 0.695; yr = 0.305; // red xg = 0.26; yg = 0.7; // green xb = 0.11; yb = 0.005; // blue break; case 10: // NTSC RGB dGamma = 2.2; colorTemp = 6774; xr = 0.67; yr = 0.33; // red xg = 0.21; yg = 0.71; // green xb = 0.14; yb = 0.08; // blue break; case 11: // PAL/SECAM RGB dGamma = 2.2; colorTemp = 6500; xr = 0.64; yr = 0.33; // red xg = 0.29; yg = 0.6; // green xb = 0.15; yb = 0.06; // blue break; case 12: // ProPhoto RGB dGamma = 1.8; colorTemp = 5000; xr = 0.7347; yr = 0.2653; // red xg = 0.1596; yg = 0.8404; // green xb = 0.0366; yb = 0.0001; // blue break; case 13: // SMPTE-C RGB dGamma = 2.2; colorTemp = 6500; xr = 0.63; yr = 0.34; // red xg = 0.31; yg = 0.595; // green xb = 0.155; yb = 0.07; // blue break; case 14: // sRGB dGamma = 2.4; colorTemp = 6500; xr = 0.64; yr = 0.33; // red xg = 0.3; yg = 0.6; // green xb = 0.15; yb = 0.06; // blue break; case 15: // Wide Gamut RGB dGamma = 2.2; colorTemp = 5000; xr = 0.735; yr = 0.265; // red xg = 0.115; yg = 0.826; // green xb = 0.157; yb = 0.018; // blue break; case 16: // OptiRGB dGamma = 2.2; colorTemp = 6500; xr = 0.6658; yr = 0.334; // red xg = 0.1929; yg = 0.7816; // green xb = 0.1355; yb = 0.0399; // blue break; } switch(colorTemp) // set white point in xyz { // http://en.wikipedia.org/wiki/White_point case 5000: // D50 xw = 0.34567; yw = 0.3585; break; case 6500: // D65 xw = 0.31271; yw = 0.32902; break; case 6774: // C NTSC xw = 0.31006; yw = 0.31616; break; case 5440: // E equal energy xw = yw = 1.0/3.0; break; default: // black body curve, aprox. with error |e|<0.0018 in 2000..25000K xw = 0.2348+(344.36+(1405300.0-1865000000/colorTemp)/colorTemp)/colorTemp; yw = (2.633-2.63*xw)*xw-0.2441; break; } /* Calculate transformation matrix of rgb working space zi = 1.0 - xi - yi ( u ) ( xr xg xb )-1 ( xw ) | v | = | yr yg yb | x | yw | ( w ) ( zr zg zb ) ( zw ) rgb to xyz matrix ( x0 x1 x2 ) ( xr*u/xw xg*v/xw xb*w/xw ) | x3 x4 x5 | = | yr*u/yw yg*v/yw yb*w/yw | ( x6 x7 x8 ) ( zr*u/zw zg*v/zw zb*w/zw ) xyz to rgb matrix: (y) = (x)^(-1) */ // invert Matrix u = xb*(yg - yw) + xg*(yw - yb) + xw*(yb - yg); v = xb*(yw - yr) + xr*(yb - yw) + xw*(yr - yb); w = xg*(yr - yw) + xr*(yw - yg) + xw*(yg - yr); det = u+v+w; // rgb to xyz matrix x0 = xr*u/(det*xw); x1 = xg*v/(det*xw); x2 = xb*w/(det*xw); x3 = yr*u/(det*yw); x4 = yg*v/(det*yw); x5 = yb*w/(det*yw); x6 = (1.0-xr-yr)*u/(det*(1.0-xw-yw)); x7 = (1.0-xg-yg)*v/(det*(1.0-xw-yw)); x8 = (1.0-xb-yb)*w/(det*(1.0-xw-yw)); // xyz to rgb matrix, invert (x) y0 = (xb*yg-xg*yb+yb-yg)*xw/u; y1 = (xb*yg-xg*yb-xb+xg)*yw/u; y2 = (xb*yg-xg*yb)*(1.0-xw-yw)/u; y3 = (xr*yb-xb*yr+yr-yb)*xw/v; y4 = (xr*yb-xb*yr-xr+xb)*yw/v; y5 = (xr*yb-xb*yr)*(1.0-xw-yw)/v; y6 = (xg*yr-xr*yg+yg-yr)*xw/w; y7 = (xg*yr-xr*yg-xg+xr)*yw/w; y8 = (xg*yr-xr*yg)*(1.0-xw-yw)/w; z0 = dGamma; } if (e==FME_CLICKED) { if (n<10) i0=n; // set input color space else if (n<20) i1=n-10; // set output color space if (n<20) // enable/disable working space view { i = (i0>4)^(i1>4) ? -1 : 0; enableCtl(22,i); enableCtl(23,i); enableCtl(24,i); } if (n==23 || n==5 || n==6 || n==15 || n==16) // L*a*b*, LCH { triggerEvent(20,FME_CUSTOMEVENT,n); // set rgb->xyz->rgb matrix + Gamma(z0) setCtlVal(24,(int)(z0*10.0)); } } return false; } ForEveryTile: { int C=imageMode RGB R=C-r; G=C-g; B=C-b; break; case 2: // CMYK -> RGB R=(C-r)*(C-k)/C; G=(C-g)*(C-k)/C; B=(C-b)*(C-k)/C; break; case 3: // HSL -> RGB switch(r*3/(C+1)) // Hue { case 0: // 0..120° R = 2*C-r*6; G = r*6; B = 0; break; case 1: // 120°..240° R = 0; G = 4*C-r*6; B = r*6-2*C; break; case 2: default: // 240°..360° R = r*6-4*C; G = 0; B = 6*(C-r); break; } R = g*min(C,R)/C*2+(C-g); // S G = g*min(C,G)/C*2+(C-g); B = g*min(C,B)/C*2+(C-g); if (b+b RGB if (g==0) R=G=B=b; else { i=b*(C-g)/(C+1); r*=6; j=b*(C-g*(r%(C+1))/(C+1))/(C+1); g=b*(C-g*(C-r%(C+1))/(C+1))/(C+1); switch(r/(C+1)) { case 0: R=b; G=g; B=i; break; case 1: R=j; G=b; B=i; break; case 2: R=i; G=b; B=g; break; case 3: R=i; G=j; B=b; break; case 4: R=g; G=i; B=b; break; default: case 5: R=b; G=i; B=j; break; } } break; case 5: // L*a*b* -> RGB case 6: // LCH -> RGB /* lab2rgb http://www.brucelindbloom.com/ range L 0..C -> 0..100, a/b 0..C/2..C -> -128..0..+128 (It does not make much sense to further optimize contants since the improvements compared to pow() are negligible.) */ if (i0==5) // Lab { gVal = (g-C/2)*255.0/C; // a bVal = (b-C/2)*255.0/C; // b } else // LCH { gVal = g*fcos(b*pi2/(C+1))*128.0*sqrt(2.0)/C; // a bVal = g*fsin(b*pi2/(C+1))*128.0*sqrt(2.0)/C; // b } rVal = r*100.0/C; // L yVal = (rVal+16.0)/116.0; // fy, 16=100*e/(3/2-e) xVal = gVal/500.0+yVal; // fx zVal = yVal-bVal/200.0; // fz // do gamma=3 for large or linear funtion for or small values xVal = xVal > e ? xVal*xVal*xVal : e*e*(3.0*xVal-2.0*e); yVal = yVal > e ? yVal*yVal*yVal : e*e*(3.0*yVal-2.0*e); zVal = zVal > e ? zVal*zVal*zVal : e*e*(3.0*zVal-2.0*e); // convertion matrix xyz to rgb rVal = xVal * y0 + yVal * y1 + zVal * y2; gVal = xVal * y3 + yVal * y4 + zVal * y5; bVal = xVal * y6 + yVal * y7 + zVal * y8; // invoke linear part for small sRGB values otherwise gamma function rVal = rVal*(gama-1.0)*fC) g=C+C-g; //S g = b==0||b==i ? 0 : (b-i)*C/g; b = (b+i)>>1; // L break; case 4: // RGB to HSB/HSV b=max(R,G,B); // V i=min(R,G,B); if (R==b) r=G-B; if (G==b) r=B-R+2*(b-i); if (B==b) r=R-G+4*(b-i); r = b==i ? 0 : r/6*C/(b-i); // H if (r<0) r+=C+1; g = b ? C-i*C/b : 0; // S break; case 5: // RGB to L*a*b* case 6: // RGB to LCH // rgb2lab http://www.brucelindbloom.com/ rVal = R/(double)C; gVal = G/(double)C; bVal = B/(double)C; // invoke linear part for small sRGB values otherwise gamma function rVal = rVal*(gama-1.0) xyz xVal = x0 * rVal + x1 * gVal + x2 * bVal; yVal = x3 * rVal + x4 * gVal + x5 * bVal; zVal = x6 * rVal + x7 * gVal + x8 * bVal; // do gamma=3 for large or linear funtion for or small values xVal = xVal > e*e*e ? pow(xVal,1.0/3.0) : (xVal/(e*e)+2.0*e)/3.0; yVal = yVal > e*e*e ? pow(yVal,1.0/3.0) : (yVal/(e*e)+2.0*e)/3.0; zVal = zVal > e*e*e ? pow(zVal,1.0/3.0) : (zVal/(e*e)+2.0*e)/3.0; // convert to L*a*b* xVal = 500.0 * (xVal - yVal); // a*-value zVal = 200.0 * (yVal - zVal); // b*-value yVal = 116.0 * yVal - 16.0; // L*-value, 16=100*e/(3/2-e) // convert real values to int // range L 0..100 -> 0..C, a/b -128..0..+128 -> 0..C/2..C r = yVal*C/100.0; if (i1==5) // Lab { g = xVal*C/255.0+C/2; // a b = zVal*C/255.0+C/2; // b } else // LCH { g = sqrt((xVal*xVal+zVal*zVal)*0.5)*C/128.0; // C b = atan2(zVal,xVal)*(C+1)/pi2; // H if (b<0) b+=C+1; if (b>C) b-=C+1; } break; } if (imageMode==CMYKMode||imageMode==CMYK64Mode) // set negated values for PS { r=C-r; g=C-g; b=C-b; k=C-k; pset(x,y,3,k); } pset(x,y,0,r); pset(x,y,1,g); pset(x,y,2,b); } } return true; } OnFilterEnd: { updateProgress(0,1); return false; }