Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411430 Posts in 69363 Topics- by 58416 Members - Latest Member: JamesAGreen

April 20, 2024, 01:15:29 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)RBY colour space
Pages: [1]
Print
Author Topic: RBY colour space  (Read 1795 times)
Juju
Level 0
**


Grumpy Pug


View Profile WWW
« on: December 26, 2014, 06:21:28 PM »

I've been intrigued by using RYB as a natural way to create colour palettes. Fortunately, there's enough help out there (and research papers) to quite easily find a solution for RYB to RGB conversion using either a "dumb" direct method or the mathematically complex cubic interpolation method. The former is basically no more nuanced than simply using RGB to begin with and defeats the point of having another colour space to begin with. The cubic interpolation model (nicely summed up here: http://bahamas10.github.io/ryb/about.html) is surprisingly simple in terms of the arithmetic but present a problem:
Once in RGB how do you get back to RYB?
 
This page gives a tantalising taste of the solution: http://stackoverflow.com/questions/4945457/conversion-between-rgb-and-ryb-color-spaces What's desperately frustrating is that the suggested answer, a Jacobian matrix and Newton-Raphson to find some roots, is provided without code. My knowledge of maths is not where it needs to be to tackle the problem in this manner.
 
I think a blind iterative method may be more appropriate, making an educated guess based on pre-computed values and then naively nudging along the three axes. Opinions?
Logged

Cheesegrater
Level 1
*



View Profile
« Reply #1 on: December 26, 2014, 07:03:55 PM »

It won't converge as quickly as Newton's method, but rather than nudging randomly you should be able to use a Hill climbing algorithm to find an answer.
Logged
jgrams
Level 3
***



View Profile
« Reply #2 on: December 27, 2014, 04:36:08 PM »

Can I ask what your level of math knowledge is? Maybe we could walk you through the process of figuring the Jacobian and doing the Newton-Raphson refinement? It's not too difficult; it's more just tedious (unless you use a computer algebra system of some kind). And that understanding might (possibly?) stand you in good stead for other problems later...?

Edit: Aaaand...because I can never resist trying to explain math to people... http://www.arestlessmind.org/2014/12/28/ryb-to-rgb.jpg (sorry it's all in pencil: for some reason I don't have a convenient way to typeset math on this computer). You can gloss over the "curly-backwards-d" partial derivative symbol, you don't need to fully understand what it means. So I think this reduces it to just needing basic algebra, but if anything isn't clear (or if someone notices that I screwed something up), let me know.
« Last Edit: December 28, 2014, 05:28:21 AM by jgrams » Logged
Gtoknu
Level 0
***


View Profile
« Reply #3 on: December 27, 2014, 06:41:39 PM »

Why are you wanting to work with RYB color space? I'm not criticizing, just curious.

The reason I'm asking this is because I develop a pixel art editor in my free time. If this has some real benefical effects, I may implement it along with the color spaces it already supports (RGB, CMYK, HSB and HUSL.)
Logged

wut
Fallsburg
Level 10
*****


Fear the CircleCat


View Profile
« Reply #4 on: December 29, 2014, 09:32:10 AM »

(sorry it's all in pencil: for some reason I don't have a convenient way to typeset math on this computer)

www.writelatex.com ?  Or any of the other online latex compilers?
Logged
jgrams
Level 3
***



View Profile
« Reply #5 on: December 29, 2014, 01:28:31 PM »

D'oh! Guess I've got to pull myself out of the '90s... Facepalm Wink
Logged
Juju
Level 0
**


Grumpy Pug


View Profile WWW
« Reply #6 on: January 27, 2016, 06:23:52 AM »

So you come back after a year to find the problem halfway solved... amazing, thanks jgrams. I'll restart work on this.

To answer Gtoknu's question, I wanted to create the feeling of painting minifigs. Using the basic rules of RBY painting seemed like quite a natural way to get people to think in that direction. It's intuitive and accessible... though nowadays, I do get the impression more and more people are used to working with HSV/HSL and RGB.
Logged

BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #7 on: January 27, 2016, 02:08:34 PM »

Here's a solution
Code:
import numpy as np

class Vec3:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __add__(self, other):
        return Vec3(self.x+other.x, self.y+other.y, self.z+other.z)
    def __sub__(self, other):
        return Vec3(self.x-other.x, self.y-other.y, self.z-other.z)
    def __mul__(self, other):
        return Vec3(self.x * other, self.y*other, self.z*other)
    def __rmul__(self, other):
        return self * other
    def __iter__(self):
        yield self.x
        yield self.y
        yield self.z
    def __str__(self):
        return "{},{},{}".format(*self)

class AutoDiff:
    def __init__(self, v, dv=0):
        self.v = v
        self.dv = dv

    def __add__(self, other):
        if isinstance(other, AutoDiff):
            return AutoDiff(self.v + other.v, self.dv + other.dv)
        else:
            return AutoDiff(self.v + other, self.dv)

    def __radd__(self, other):
        return self + other

    def __sub__(self, other):
        if isinstance(other, AutoDiff):
            return AutoDiff(self.v - other.v, self.dv - other.dv)
        else:
            return AutoDiff(self.v - other, self.dv)

    def __rsub__(self, other):
        if isinstance(other, AutoDiff):
            return AutoDiff(other.v - self.v, other.dv - self.dv)
        else:
            return AutoDiff(other - self.v, - self.dv)

    def __mul__(self, other):
        if isinstance(other, AutoDiff):
            return AutoDiff(self.v * other.v, self.v * other.dv + self.dv * other.v)
        else:
            return AutoDiff(self.v * other, self.dv * other)

    def __rmul__(self, other):
        return self * other


def cubic_int(t, a, b):
    weight = t * t * (3 - 2 * t)
    return (1 - weight) * a + weight * b

MAGIC = [
    Vec3(1,     1,     1),
    Vec3(1,     1,     0),
    Vec3(1,     0,     0),
    Vec3(1,     0.5,   0),
    Vec3(0.163, 0.373, 0.6),
    Vec3(0.0,   0.66,  0.2),
    Vec3(0.5,   0.0,   0.5),
    Vec3(0.2,   0.094, 0.0)
]

def ryb_to_rgb(iR, iY, iB):
    x0 = cubic_int(iB, MAGIC[0], MAGIC[4])
    x1 = cubic_int(iB, MAGIC[1], MAGIC[5])
    x2 = cubic_int(iB, MAGIC[2], MAGIC[6])
    x3 = cubic_int(iB, MAGIC[3], MAGIC[7])
    y0 = cubic_int(iY, x0, x1)
    y1 = cubic_int(iY, x2, x3)
    return cubic_int(iR, y0, y1)


def rgb_to_ryb(iR, iG, iB, tol=0.0001):
    target = Vec3(iR, iG, iB)
    current_guess = Vec3(0.5, 0.5, 0.5)
    while True:
        x_dir = ryb_to_rgb(*current_guess + Vec3(AutoDiff(0, 1), AutoDiff(0, 0), AutoDiff(0, 0)))
        y_dir = ryb_to_rgb(*current_guess + Vec3(AutoDiff(0, 0), AutoDiff(0, 1), AutoDiff(0, 0)))
        z_dir = ryb_to_rgb(*current_guess + Vec3(AutoDiff(0, 0), AutoDiff(0, 0), AutoDiff(0, 1)))
        guess_result = x_dir.v
        distance = target - guess_result
        if distance.x*distance.x+distance.y*distance.y+distance.z*distance.z < tol * tol:
            return current_guess
        # Newton-Raphson
        J = np.array([list(x_dir.dv), list(y_dir.dv), list(z_dir.dv)]).T
        current_guess += Vec3(*np.linalg.solve(J, np.array(list(distance))))



if __name__ == "__main__":
    center = (1/8) * sum(MAGIC, Vec3(0,0,0))
    print(center)
    print(ryb_to_rgb(0.5, 0.5, 0.5))
    print(rgb_to_ryb(*center))
    print(rgb_to_ryb(*ryb_to_rgb(0.1, 0.2, 0.3)))

Prints


0.607875,0.45337500000000003,0.2875
0.607875,0.45337500000000003,0.2875
0.5,0.5,0.5
0.10000485086080818,0.19999999093416254,0.30000000021028284



Rather than do a huge amount of maths to compute J, I used automatic differentiation, but otherwise it's the same as the proposed solution.

I should add though, from reading the paper, the RBY formula is pulled out of the authors ass, and has no solid colour theory basis.
Logged
Cheezmeister
Level 3
***



View Profile
« Reply #8 on: January 27, 2016, 09:52:06 PM »

(sorry it's all in pencil: for some reason I don't have a convenient way to typeset math on this computer)

www.writelatex.com ?  Or any of the other online latex compilers?

This circulated recently and it's pretty rad: http://webdemo.myscript.com/#/demo/equation
Logged

෴Me෴ @chzmstr | www.luchenlabs.com ቒMadeቓ RA | Nextris | Chromathud   ᙍMakingᙌCheezus II (Devlog)
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #9 on: January 28, 2016, 01:18:02 PM »

Och, it was bugging me, so here's a version without automatic differentiation. It's not as hairy as I was expecting, as trilinear interpolation is, well, linear.
Code:
import numpy as np

class Vec3:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __add__(self, other):
        return Vec3(self.x+other.x, self.y+other.y, self.z+other.z)
    def __sub__(self, other):
        return Vec3(self.x-other.x, self.y-other.y, self.z-other.z)
    def __mul__(self, other):
        return Vec3(self.x * other, self.y*other, self.z*other)
    def __rmul__(self, other):
        return self * other
    def __iter__(self):
        yield self.x
        yield self.y
        yield self.z
    def __str__(self):
        return "{},{},{}".format(*self)

def cubic_int(t, a, b, diff=False):
    if diff:
        weight = 6 * t * (1 - t)
        return weight * (b - a)
    else:
        weight = t * t * (3 - 2 * t)
        return (1 - weight) * a + weight * b


MAGIC = [
    Vec3(1,     1,     1),
    Vec3(1,     1,     0),
    Vec3(1,     0,     0),
    Vec3(1,     0.5,   0),
    Vec3(0.163, 0.373, 0.6),
    Vec3(0.0,   0.66,  0.2),
    Vec3(0.5,   0.0,   0.5),
    Vec3(0.2,   0.094, 0.0)
]

def ryb_to_rgb(iR, iY, iB, dr=False, dy=False, db=False):
    x0 = cubic_int(iB, MAGIC[0], MAGIC[4], db)
    x1 = cubic_int(iB, MAGIC[1], MAGIC[5], db)
    x2 = cubic_int(iB, MAGIC[2], MAGIC[6], db)
    x3 = cubic_int(iB, MAGIC[3], MAGIC[7], db)
    y0 = cubic_int(iY, x0, x1, dy)
    y1 = cubic_int(iY, x2, x3, dy)
    return cubic_int(iR, y0, y1, dr)


def rgb_to_ryb(iR, iG, iB, tol=0.0001):
    target = Vec3(iR, iG, iB)
    current_guess = Vec3(0.5, 0.5, 0.5)
    while True:
        guess_result = ryb_to_rgb(*current_guess)
        x_dir = ryb_to_rgb(*current_guess, dr=True)
        y_dir = ryb_to_rgb(*current_guess, dy=True)
        z_dir = ryb_to_rgb(*current_guess, db=True)
        distance = target - guess_result
        if distance.x*distance.x+distance.y*distance.y+distance.z*distance.z < tol * tol:
            return current_guess
        # Newton-Raphson
        J = np.array([list(x_dir), list(y_dir), list(z_dir)]).T
        current_guess += Vec3(*np.linalg.solve(J, np.array(list(distance))))




if __name__ == "__main__":
    center = (1/8) * sum(MAGIC, Vec3(0,0,0))
    print(center)
    print(ryb_to_rgb(0.5, 0.5, 0.5))
    print(rgb_to_ryb(*center))
    print(rgb_to_ryb(*ryb_to_rgb(0.1, 0.2, 0.3)))
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic