-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
2,156 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
language: go | ||
go: | ||
- 1.2 | ||
- release | ||
- tip | ||
|
||
script: | ||
- go test -v ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright (c) <2014> <John K. Luebs> | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,50 @@ | ||
go-chromath | ||
=========== | ||
# go-chromath | ||
|
||
Useful color math functions for golang | ||
go-chromath is a library for color space math including transformations, chromatic adaptation and color difference (ΔE) calculation. It provides implentations of common RGB spaces to CIEXYZ, and CIEXYZ to useful CIE spaces such as L\*a\*b\*, LCH, Luv. Special care has been taken in the implementation of CIEDE2000, a calculation that is commonly defectively implemented. | ||
|
||
## Use | ||
|
||
Let's convert two colors from sRGB to L\*a\*b with a reference white of D50 and perform a CIEDE2000 color difference calculation. This requires creating a coverter from RGB to XYZ, and from XYZ to Lab. One converter instance can be used on all points. | ||
```go | ||
c1 := RGB{194, 0, 120} // xkcd magenta | ||
c2 := RGB{203, 65, 107} // xkcd hot pink | ||
|
||
targetIlluminant := &IlluminantRefD50 | ||
``` | ||
|
||
Create an sRGB Transformer to XYZ with a target illuminant of D50. Since sRGB is defined to have a native illuminant of D65, use the Bradford response transform to adapt. Use an 8-bit value (0-255) scaler with clamping to scale input values, and set the output scale (for XYZ) to unity. Finally, do not override the native linearization. | ||
```go | ||
rgb2xyz := NewRGBTransformer(&SpaceSRGB, &AdaptationBradford, targetIlluminant, &Scaler8bClamping, 1.0, nil) | ||
``` | ||
|
||
Create an XYZ ⇔ Lab transformer with an assumed illuminant of D50. Note that this illuminant needs to match the target illuminant for all XYZ transformed points. This will not perform adaptation as the XYZ point objects themselves do not carry their whitepoint information. | ||
```go | ||
lab2xyz := NewLabTransformer(targetIlluminant, 1.0) | ||
|
||
// Now converting color points is simple | ||
c1xyz := rgb2xyz.Convert(c1) | ||
c2xyz := rgb2xyz.Convert(c2) | ||
``` | ||
|
||
Now convert to L\*a\*b. The naming convention is that conversion is "towards" XYZ, and so XYZ ⇒ Any is "inversion" | ||
```go | ||
c1lab := lab2xyz.Invert(c1xyz) | ||
c2lab := lab2xyz.Invert(c2xyz) | ||
``` | ||
|
||
Perform the ΔE computation, which is provided by a stateless function. | ||
```go | ||
Δe2000 := DeltaECIE2000(c1lab, c2lab, &KLChDefault) | ||
``` | ||
|
||
Since 8-bit sRGB is such a common model, and the sRGB gamma function is quite expensive in floating point, a faster sRGB scaler compander implementation is provided: `SRGBFastCompander`. | ||
This uses a lookup table for linearization, but is still quite slow for companding, although this could be improved. The compander includes scaling for 8-bit RGB, so should not need a scaler. Use it like this: | ||
```go | ||
// no scaler, and override compander with SRGBFastCompander | ||
rgb2xyz := NewRGBTransformer(&SpaceSRGB, &AdaptationBradford, targetIlluminant, nil, 1.0, &SRGBFastCompander) | ||
``` | ||
|
||
## TODO | ||
* While there are some basic tests, coverage in transformations needs improvements. The CIEDE2000 is better tested. | ||
* An implementation of CIECAM02 is also forthcoming. | ||
* More documentation is required. Unfortunately all this presupposes a previous familiarlity with the color space and matching fundamentals. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package chromath | ||
|
||
const ( | ||
CIEKappa = 24389.0/27.0 | ||
CIEEps = 216/24389.0 | ||
) | ||
|
||
type Point [3]float64 | ||
type XYZ Point | ||
|
||
type YZMatrix Matrix | ||
|
||
func (p XYZ) X() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p XYZ) Y() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p XYZ) Z() float64 { | ||
return p[2] | ||
} | ||
|
||
// Transform applies an arbitrary transformation matrix to an XYZ point | ||
// This is most useful with adaptation matrices generated from (Adaptation) Transform | ||
func (p XYZ) Transform(tm *Matrix) XYZ { | ||
return XYZ(tm.Mul3x1(Point(p))) | ||
} | ||
|
||
type Lab Point | ||
|
||
func (p Lab) L() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p Lab) A() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p Lab) B() float64 { | ||
return p[2] | ||
} | ||
|
||
type LCh Point | ||
|
||
func (p LCh) L() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p LCh) C() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p LCh) H() float64 { | ||
return p[2] | ||
} | ||
|
||
type Luv Point | ||
|
||
func (p Luv) L() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p Luv) U() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p Luv) V() float64 { | ||
return p[2] | ||
} | ||
|
||
type LChuv Point | ||
|
||
func (p LChuv) L() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p LChuv) C() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p LChuv) H() float64 { | ||
return p[2] | ||
} | ||
|
||
type RGB Point | ||
|
||
func (p RGB) R() float64 { | ||
return p[0] | ||
} | ||
|
||
func (p RGB) G() float64 { | ||
return p[1] | ||
} | ||
|
||
func (p RGB) B() float64 { | ||
return p[2] | ||
} | ||
|
||
// XyYPrimary specifies the three additive primaries in CIE XyY | ||
// The first index is for the primary (ie 0 is typically R in RGB) and the | ||
// second index is an xyY element | ||
type XyYPrimary struct { | ||
Xr, Yr, Xg, Yg, Xb, Yb float64 | ||
} | ||
|
||
type Gamma float64 | ||
|
||
type Compander interface { | ||
Init(*RGBSpace) Compander | ||
Compand(Point) Point | ||
Linearize(Point) Point | ||
} | ||
|
||
type Scaler interface { | ||
Init(*RGBSpace) Scaler | ||
Scale(Point) Point | ||
ScaleInv(Point) Point | ||
} | ||
|
||
// XYZTransform calculates a transform matrix for the primaries to XYZ | ||
func (p XyYPrimary) XYZTransform(illuminantRef XYZ) Matrix { | ||
m := Matrix{ | ||
p.Xr/p.Yr, p.Xg/p.Yg, p.Xb/p.Yb, | ||
1.0, 1.0, 1.0, | ||
(1.0-p.Xr-p.Yr)/p.Yr, (1.0-p.Xg-p.Yg)/p.Yg, (1.0-p.Xb-p.Yb)/p.Yb, | ||
} | ||
s := m.Inv().Transpose().Mul3x1(Point(illuminantRef)) | ||
|
||
return Matrix{ | ||
s[0]*m[0], s[1]*m[1], s[2]*m[2], | ||
s[0]*m[3], s[1]*m[4], s[2]*m[5], | ||
s[0]*m[6], s[1]*m[7], s[2]*m[8], | ||
}.Transpose() | ||
} | ||
|
||
type Adaptation Matrix | ||
|
||
func (a Adaptation) Transform(sourceIlluminant XYZ, targetIlluminant XYZ) Matrix { | ||
ma := Matrix(a) | ||
mai := ma.Inv() | ||
sc := ma.Mul3x1(Point(sourceIlluminant)) | ||
dc := ma.Mul3x1(Point(targetIlluminant)) | ||
|
||
sm := Matrix{dc[0]/sc[0], 0, 0, 0, dc[1]/sc[1], 0, 0, 0, dc[2]/sc[2]} | ||
return mai.Mul3(sm).Mul3(ma) | ||
} | ||
|
||
func sqr(v float64) float64 { | ||
return v * v | ||
} |
Oops, something went wrong.