Giter VIP home page Giter VIP logo

scrtwpns / mixbox Goto Github PK

View Code? Open in Web Editor NEW
1.9K 28.0 113.0 13.49 MB

Mixbox is a library for natural color mixing based on real pigments.

Home Page: https://scrtwpns.com/mixbox

License: Other

C++ 22.98% C 0.42% GLSL 0.68% HLSL 1.95% HTML 2.17% JavaScript 1.69% Python 20.26% Batchfile 0.03% Java 1.95% C# 44.00% Rust 1.08% GDScript 1.68% ShaderLab 0.36% Metal 0.74%
color rgb pigments kubelka-munk color-mixing paints paint-mixing

mixbox's Introduction

Mixbox: Pigment-Based Color Mixing

Mixbox is a new blending method for natural color mixing. It produces saturated gradients with hue shifts and natural secondary colors during blending. Yellow and blue make green. The interface is simple - RGB in, RGB out. Internally, Mixbox treats colors as real-life pigments using the Kubelka & Munk theory to predict realistic color behavior. That way, colors act like actual paints and bring more vibrance and intuition into digital painting.

Mixbox is shipping in Rebelle 5 Pro as the Rebelle Pigments feature and in the Flip Fluids addon for Blender.

Usage

  • C / C++: #include "mixbox.h" and build mixbox.cpp together with your project
  • C#: use Mixbox package from NuGet https://www.nuget.org/packages/Mixbox/2.0.0
  • Java: add implementation 'com.scrtwpns:mixbox:2.0.0' to your Gradle
  • JavaScript: <script src="https://scrtwpns.com/mixbox.js">
  • Node: npm install mixbox
  • Python: pip install pymixbox
  • Rust: add mixbox = "2.0.0" to your Cargo.toml
  • Unity: add package from git url git://github.com/scrtwpns/mixbox.git#upm
  • Godot: copy godot\addons to the root of your project
  • Shaders: load mixbox_lut.png as texture and include mixbox.glsl/.hlsl/.metal code into your shader

Pigment Colors

Pigment RGB Float RGB Linear RGB
Cadmium Yellow 254, 236, 0 0.996, 0.925, 0.0 0.991, 0.839, 0.0
Hansa Yellow 252, 211, 0 0.988, 0.827, 0.0 0.973, 0.651, 0.0
Cadmium Orange 255, 105, 0 1.0, 0.412, 0.0 1.0, 0.141, 0.0
Cadmium Red 255, 39, 2 1.0, 0.153, 0.008 1.0, 0.02, 0.001
Quinacridone Magenta 128, 2, 46 0.502, 0.008, 0.18 0.216, 0.001, 0.027
Cobalt Violet 78, 0, 66 0.306, 0.0, 0.259 0.076, 0.0, 0.054
Ultramarine Blue 25, 0, 89 0.098, 0.0, 0.349 0.01, 0.0, 0.1
Cobalt Blue 0, 33, 133 0.0, 0.129, 0.522 0.0, 0.015, 0.235
Phthalo Blue 13, 27, 68 0.051, 0.106, 0.267 0.004, 0.011, 0.058
Phthalo Green 0, 60, 50 0.0, 0.235, 0.196 0.0, 0.045, 0.032
Permanent Green 7, 109, 22 0.027, 0.427, 0.086 0.002, 0.153, 0.008
Sap Green 107, 148, 4 0.42, 0.58, 0.016 0.147, 0.296, 0.001
Burnt Sienna 123, 72, 0 0.482, 0.282, 0.0 0.198, 0.065, 0.0

C / C++

#include <stdio.h>
#include "mixbox.h"

int main() {
  unsigned char r1 =   0, g1 = 33,  b1 = 133; // blue
  unsigned char r2 = 252, g2 = 211, b2 = 0;   // yellow
  float t = 0.5;
  unsigned char r, g, b;

  mixbox_lerp(r1, g1, b1,  // first color
              r2, g2, b2,  // second color
              t,           // mixing ratio
              &r, &g, &b); // result

  printf("%d %d %d\n", r, g, b);
}

GLSL Shader

#ifdef GL_ES
precision highp float;
#endif

uniform sampler2D mixbox_lut; // bind the "mixbox_lut.png" texture here

#include "mixbox.glsl" // paste the contents of mixbox.glsl here

void main(void) {
  vec3 rgb1 = vec3(0, 0.129, 0.522); // blue
  vec3 rgb2 = vec3(0.988, 0.827, 0); // yellow
  float t = 0.5;                     // mixing ratio

  vec3 rgb = mixbox_lerp(rgb1, rgb2, t);

  gl_FragColor = vec4(rgb, 1.0);
}

Rust

fn main() {
    let rgb1 = [0, 33, 133];  // blue
    let rgb2 = [252, 211, 0]; // yellow
    let t = 0.5;              // mixing ratio

    let [r, g, b] = mixbox::lerp(&rgb1, &rgb2, t);

    println!("{} {} {}", r, g, b);
}

Python

import mixbox

rgb1 = (0, 33, 133)  # blue
rgb2 = (252, 211, 0) # yellow
t = 0.5              # mixing ratio

rgb_mix = mixbox.lerp(rgb1, rgb2, t)

print(rgb_mix)

JavaScript

<html>
  <body>
    <script src="https://scrtwpns.com/mixbox.js"></script>
    <script>
      var rgb1 = "rgb(0, 33, 133)";  // blue
      var rgb2 = "rgb(252, 211, 0)"; // yellow
      var t = 0.5;                   // mixing ratio

      var mixed  = mixbox.lerp(rgb1, rgb2, t);

      document.body.style.background = mixed;
    </script>
  </body>
</html>

Node

import mixbox from 'mixbox';

let rgb1 = "rgb(0, 33, 133)";  // blue
let rgb2 = "rgb(252, 211, 0)"; // yellow
let t = 0.5;                   // mixing ratio

let mixed  = mixbox.lerp(rgb1, rgb2, t);

console.log(mixed);

Java

import java.awt.Color;
import com.scrtwpns.Mixbox;

class HelloMixbox {
  public static void main(String[] args) {
    Color color1 = new Color(0, 33, 133);  // blue
    Color color2 = new Color(252, 211, 0); // yellow
    float t = 0.5f;                        // mixing ratio

    Color colorMix = new Color(Mixbox.lerp(color1.getRGB(), color2.getRGB(), t));

    System.out.print(colorMix);
  }
}

Android

package com.example.hellomixbox;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.graphics.Color;

import com.scrtwpns.Mixbox;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        int color1 = Color.rgb(0, 33, 133);  // blue
        int color2 = Color.rgb(252, 211, 0); // yellow
        float t = 0.5f;                      // mixing ratio

        int colorMix = Mixbox.lerp(color1, color2, t);

        View view = new View(this);
        view.setBackgroundColor(colorMix);
        setContentView(view);
    }
}

C#

using System.Drawing;
using Scrtwpns.Mixbox;

public class HelloMixbox
{
    public static void Main(string[] args)
    {
        Color color1 = Color.FromArgb(0, 33, 133);  // blue
        Color color2 = Color.FromArgb(252, 211, 0); // yellow
        float t = 0.5f;                             // mixing ratio

        Color colorMix = Color.FromArgb(Mixbox.Lerp(color1.ToArgb(), color2.ToArgb(), t));

        System.Console.WriteLine(colorMix);
    }
}

Unity

using UnityEngine;
using Scrtwpns.Mixbox;

public class NewBehaviourScript : MonoBehaviour
{
    void Start()
    {
        Color color1 = new Color(0.0f, 0.129f, 0.522f); // blue
        Color color2 = new Color(0.988f, 0.827f, 0.0f); // yellow
        float t = 0.5f;                                 // mixing ratio

        Color colorMix = Mixbox.Lerp(color1, color2, t);

        Debug.Log(colorMix);
    }
}

Unity Shader

Shader "MixboxHelloShader"
{
    Properties
    {
        _MixboxLUT ("Mixbox LUT", 2D) = "white" {} // assign "Packages/Mixbox/Textures/MixboxLUT.png"

        _Color1 ("Color 1", Color) = (0, 0.129, 0.522, 1) // blue
        _Color2 ("Color 2", Color) = (0.988, 0.827, 0, 1) // yellow
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MixboxLUT;
            #include "Packages/com.scrtwpns.mixbox/ShaderLibrary/Mixbox.cginc"

            fixed4 _Color1;
            fixed4 _Color2;

            struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
            struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 mixedColor = MixboxLerp(_Color1, _Color2, i.uv.x);
                return mixedColor;
            }
            ENDCG
        }
    }
}

Unity Shader Graph

Godot

var Mixbox = preload("res://addons/mixbox/mixbox.gd")

var color1 = Color(0.0, 0.129, 0.522) # blue
var color2 = Color(0.988, 0.827, 0.0) # yellow
var t = 0.5                           # mixing ratio

var color_mix = Mixbox.lerp(color1, color2, t)

print(color_mix)

Godot Shader

shader_type canvas_item;

uniform sampler2D mixbox_lut; // attach "addons/mixbox/mixbox_lut.png" here

uniform vec4 color1 : hint_color = vec4(0.0, 0.129, 0.522, 1.0); // blue
uniform vec4 color2 : hint_color = vec4(0.988, 0.827, 0.0, 1.0); // yellow

#include "addons/mixbox/mixbox.gdshaderinc"

void fragment() {
    COLOR = mixbox_lerp(color1, color2, UV.x);
}

Godot VisualShader

WebGL

<script src="https://scrtwpns.com/mixbox.js"></script>
var shader = `
  precision highp float;

  uniform sampler2D mixbox_lut; // bind mixbox.lutTexture(gl) here

  #include "mixbox.glsl"

  void main(void) {
    vec3 rgb1 = vec3(0, 0.129, 0.522); // blue
    vec3 rgb2 = vec3(0.988, 0.827, 0); // yellow
    float t = 0.5;                     // mixing ratio

    vec3 rgb = mixbox_lerp(rgb1, rgb2, t);

    gl_FragColor = vec4(rgb, 1.0);
  }
`;

shader = shader.replace('#include "mixbox.glsl"', mixbox.glsl());
gl.useProgram(shaderProgram);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, mixbox.lutTexture(gl));
gl.uniform1i(gl.getUniformLocation(shaderProgram, "mixbox_lut"), 0);

Examples

Gradients Mountains Palette Snakes
source code source code source code
Splash Art Paint Mixer Pigment Fluids
source code source code source code

Painter

This painting app runs two color mixing implementations in parallel: one based on Mixbox and the other that performs ordinary RGB mixing. The app allows switching between them on the fly, showing the differences between pigment-based mixing and the normal RGB mixing. To launch the painter in your browser, please click here.

License

Copyright (c) 2022, Secret Weapons. All rights reserved.
Mixbox is provided under the CC BY-NC 4.0 license for non-commercial use only.
If you want to obtain commercial license, please contact: [email protected]

mixbox's People

Contributors

jamriska avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mixbox's Issues

happy to help with an R version

Thanks for the excellent library and associated documentation. I'd like to help create and R interface if you are interested.

Gamma/non-linearity question

Could you comment on the linearity of the sRGB values the functions take?

8bit/component sRGB is usually non-linear (baked-in 2.2 gamma) because 8bit is not enough to store colors without visible banding.

Does the code take non-linearity into account? I.e. when mixing colors in RGB this matters as mixing non-linear colors simply leads to (subtly) wrong results.

What about the float variants then?

I.e. can you update the README to clearly state what gamma the sRGB values should have in both cases? I think it would also help to mention this in the paper.

On that note, it is extremely uncommon (can't think of a single case I came across in the wild) to have 32bit/component sRGB values that are not linear.

I.e. float sRGB is commonly sRGB linear. sRGB primaries & white point but with a linear (1.0) gamma. Aka: no baked-in transfer function.

Invert color in latent space?

Inverting an RGB color is easy - just subtract each component from 1 (0.6 becomes 0.4, 0.1 becomes 0.9 etc.).

How can I do it in Latent space?

I have my colors in Latent, so I've been converting them to RGB to invert them, which leads to inconsistent results (due to Latent->RGB->Latent conversion). So if I could do it all in Latent would be better.

Problems with Python and JavaScript

I am having trouble with Mixbox on Programiz, a Python I used the following code.
`import mixbox

rgb1 = (0, 33, 133) # blue
rgb2 = (252, 211, 0) # yellow
t = 0.5 # mixing ratio

rgb_mix = mixbox.lerp(rgb1,rgb2,t)

print(rgb_mix)`

But this is what I recieved.

`ERROR!
Traceback (most recent call last):
File "<main.py>", line 3, in
ModuleNotFoundError: No module named 'mixbox'

=== Code Exited With Errors ===`

Alternatively, I was tying to make Mixbox mix multiple colors work in a JavaScript Compiler. Programiz does that too, but I also used Tynker, since it has an HTML extension. I was able to make the simple usage work on a HTML. This is my code.
`var rgb1 = "rgb(0, 33, 133)"; // blue
var rgb2 = "rgb(252, 211, 0)"; // yellow
var t = 0.5; // mixing ratio

var mixed = mixbox.lerp(rgb1, rgb2, t);

console.log(mixed);`
It successfully filled the screen with the mix result. But when I tried to add the following code on the main JavaScript

`var z1 = mixbox.rgbToLatent(rgb1);
var z2 = mixbox.rgbToLatent(rgb2);
var z3 = mixbox.rgbToLatent(rgb3);

var zMix = new Array(mixbox.LATENT_SIZE);

for (var i = 0; i < zMix.length; i++) { // mix:
zMix[i] = (0.3z1[i] + // 30% of rgb1
0.6
z2[i] + // 60% of rgb2
0.1*z3[i]); // 10% of rgb3
}

var rgbMix = mixbox.latentToRgb(zMix);`

nothing happens. Same thing with Python. I additionally installed Mixbox in a command line, but it couldn't help with code. If you can help me make both of these work, I will be very thankful

Licensing compatibility

I really love what you guys have achieved! I've always had issues with color mixing in digital paint. However, when I looked into this I see people saying it can't be implemented in open source software because of the license. Is it possible to adjust the licensing to something GNU compatible? I'd hate to see this lost to time because of legal hurdles. I would really love this in Krita/GIMP/Blender3D.

Publish code to calculate `concs_table`

Rationales:

  1. The code can probably be parallelized and, if fast enough, would allow creating the table on a client on first run or even on every run.
  2. The primaries could be adjusted to use inputs from a space that has a larger gamut than sRGB.

polynomial and LUT

In the paper two LUTs are mentioned. In the code, only one is used, and one polynomial.

  • where do the numbers for the weights and other coefficients come from?
  • Do you guys also share the data used for the reflectance, scattering and absorption values of the all the 38 wavelength used?
  • In the float_rgb_to_latent(rgb) function, the _lut seems to be a large 1D vector, as opposed to the PNG file that is a 4096x4096 2D array. Based on the paper, the _lut contains the "unmix" calculations for all RGB colors, which means the c=[c0,c1,c2] is saved in every pixel of the lut png file?

Moving concs_table_png_data to another file

Arguably concs_table_png_data is data rather than code. Beside the inability to preview the code, and thus discuss it, here on Github it makes the main file go from 10MB to 76KB which for some editors might also make it challenging to parse. If it doesn't impact performances particularly I'd suggest moving it to a dedicated file.

Gamut question

I apologize in advance if I am missing something obvious from your paper/talk with below questions.

TLDR; Why did you use sRGB and not ACEScg as the space to assume input/output RGB triplets for your algorithm to lie in?

In your paper you say:

Since the mixtures of ๐’ซ* lie outside the RGB gamut [...]

What RGB gamut are you talking about here? sRGBโ€™s? Any RGB spaceโ€™s gamut?

I am inclined to read this as:

Since (some of) the mixtures of ๐’ซ* lie outside (any) the RGB gamut [...]

Is this the correct way to read this? And if so: why not choose an RGB space with a larger gamut to do all the calculations in?

Modern displays โ€“ especially those used by CG professionals โ€“, have much larger gamuts than that covered by sRGB. Could you elaborate why you choose sRGB at all for this research?

See e.g. Wikipedia for an (incomplete) list of devices that have P3 wide color gamut nowadays.

To be practical: I would think that colors which lie outside of even ACEScg or ACES2065-1 gamuts, e.g. something like ultramarine, would look much closer to their physical pigment counterpart, on a modern screen, if you did the maths in one of those spaces instead of sRGB.

Typescript support

Hi folks!

Just wondering if there was any plan (or any opportunity for contribution) for Typescript support?

I put together an ad hoc index.d.ts and figured at the very least it could be contributed to the DefinitelyTyped package.

declare module 'mixbox' {
    export type Rgba = { r: number; g: number; b: number; a?: number };
    export type RgbArray = [ number, number, number ];
    export type RgbaArray = [ number, number, number, number ];
    export type Latent = [ number, number, number, number, number, number, number ];

    export type Color = string | RgbArray | RgbaArray | Rgba | number;

    export function lerp(
        color1: Color, color2: Color, t: number,
    ): RgbArray | RgbaArray | void;

    export function lerpFloat(
        color1: Color, color2: Color, t: number,
    ): RgbArray | void;

    export function lerpLinearFloat(
        color1: Color, color2: Color, t: number,
    ): RgbArray | void;

    export function rgbToLatent(r: Color, g?: number, b?: number): Latent | void;

    export function latentToRgb(latent: Latent): RgbArray;

    export function floatRgbToLatent(r: Color, g?: number, b?: number): Latent | void;

    export function latentToFloatRgb(latent: Latent): RgbArray;

    export function linearFloatRgbToLatent(r: Color, g?: number, b?: number): Latent | void;

    export function latentToLinearFloatRgb(latent: Latent): RgbArray;

    export function glsl(): string;

    export function lutTexture(gl: WebGLRenderingContext): WebGLTexture;
}

Let me know what y'all think!

Compiling for Arduino

Hi! I was trying to compile mixbox.cpp for Arduino, using Arduino-IDE; I changed the import of cmath to an import of math.h and std::pow to just pow. Now I'm running into issues with lut:

mixbox.cpp:698:41: error: overflow in constant expression
unsigned char lut[646464*3 + 12675];

That's quite a large array for embedded devices. Is there a way to scale this down?

Can it be imported to krita

Hi , this is an amazing tool that can really solve a lot of problems in digital painting but my question is can This be implemented in a digital software like krita that would be nice to know.

Mixing with white

Just want to know if there any issue when mixing with white. It seems that I am getting the resulting color lighter than expected.

Un-mix (or remove) a color?

Although not possible in real life, can I simulate removing one color from another using mixbox?

For example, since blue and yellow make green, removing yellow from green should make blue.

I tried using a negative mixing ratio and that didn't seem to work.
I also tried inverting the color to be removed first (so a color like (0,5,255) becomes (255,250,0)), but I'm not sure if it worked.

the inverse

There is mixbox_latent_to_rgb but is there a mixbox_latent to the Golden Acrylics ? I don't see one.

I want to un-mix RGB to paint. Your paper says that the process is invertible.

License file

While the license in the readme is fine, having a dedicated "LICENSE" file as is common practice would be nice :)

Why does blending order matter? Is there a way to avoid this?

The brown I get by adding Red, Blue and Yellow is different depending on how I add them.

If I add them all at once, I get the color on the left.
If I blend Yellow and Red (1/2), then blend that to blue (1/3), I get the color on the right.
image

All at once:

		var z1 = Mixbox.rgb_to_latent(Color(1,1,0,1))
		var z2 = Mixbox.rgb_to_latent(Color(1,0,0,1))
		var z3 = Mixbox.rgb_to_latent(Color(0,0,1,1))
		var z_mix = []
		z_mix.resize(Mixbox.LATENT_SIZE)
		for i in z_mix.size():
			z_mix[i] = (1.0/3.0*z1[i] +
						1.0/3.0*z2[i] +
						1.0/3.0*z3[i])
		color_mix = Mixbox.latent_to_rgb(z_mix)				

In two blends:

		color_mix2 = Mixbox.lerp(Color(1,1,0,1),Color(1,0,0,1),0.5)
		color_mix2 = Mixbox.lerp(color_mix2,Color(0,0,1,1),1.0/3.0)

Here's another attempt with two blends, this time doing R+B, then that +Y:
(note the brown on the right is different from both, the all-at-once mix on the left, and the previous 2-step blend)
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.