privet-kitty / dufy Goto Github PK
View Code? Open in Web Editor NEWColorimetry library for Common Lisp
License: MIT License
Colorimetry library for Common Lisp
License: MIT License
No issue, simple question:
You have changed to code from lists to multiple values. What was the reason for that?
I'm in the process of analyzing your code and comparing dufy results with other tools, especially with CIE Color Calculator from http://www.brucelindbloom.com, which you certainly know.
I noticed that the QRGB to XYZ conversion for wide gamut returns the wrong result. Here comversion for RGB=120, 20, 40 for wide gamut RGB model.
dufy:
0.12765862750546625d0
0.047881765987511024d0
0.017826366573979197d0
bruce:
0.139265
0.052142
0.013330
When I add explicitly illuminant D50 to your definition of wide gamut:
(defparameter +wide-gamut-d50+
(make-rgbspace 0.7347d0 0.2653d0 0.1152d0 0.8264d0 0.1566d0 0.0177d0
:illuminant +illum-d50+
:linearizer (gen-linearizer #.(float 563/256 1d0))
:delinearizer (gen-delinearizer #.(float 563/256 1d0)))
"Wide-gamut RGB with D50 illuminant, 8-bit per channel.")
and convert the same RGB values, I get:
0.13939888615831778d0
0.05226704262351012d0
0.01335707983335173d0
Which is nearly the same as Bruce.
The Bruce's math: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
The Bruce's code: http://www.brucelindbloom.com/javascript/ColorConv.js
I think one should use D50 here because the wide gamut is defined with D50 (https://en.wikipedia.org/wiki/Wide-gamut_RGB_color_space). What do you think about that?
Since the system structure has gotten complicated, we should use package-inferred-system
in the future. If so, it will be better to leave the old package prefixes (e.g. dufy-examples) as nicknames.
An implementation using a list can be as fast as the current implementation using multiple values if appropriate declarations (type
and dynamic-extent
) are put. If so, it is worth considering that future versions (0.3 or 0.4?) handle a list instead of multiple values.
I put the code of this experiment in a new branch: experiment.lisp
Take the converter lchab-to-lrgb
(LCHab to linear RGB) as an example, which is composed of the three converters, lchab-to-lab
, lab-to-xyz
, and xyz-to-lrgb
. Below is the benchmark of the current implementation using multiple values.
(in-package :dufy-core)
;; Current implementation (automatically generated)
(declaim (inline lchab-to-lrgb)
(ftype (function * (values double-float double-float double-float &optional))
lchab-to-lrgb))
(defun lchab-to-lrgb (lstar cstarab hab &key (rgbspace +srgb+)
&aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed 3) (safety 1))
(type real lstar cstarab hab))
(multiple-value-call #'xyz-to-lrgb
(multiple-value-call #'lab-to-xyz
(lchab-to-lab lstar cstarab hab)
:illuminant
illuminant)
:rgbspace
rgbspace))
;; benchmark
(defun bench-mv-version (num &optional (sample 10))
(let ((state (sb-ext:seed-random-state 1)))
(format t "~&~F sec."
(time-median sample ;; Repeats SAMPLE times and returns the median time.
(print (loop repeat num
sum (multiple-value-call
#'(lambda (x y z)
(declare (double-float x y z))
(+ x y z))
(lchab-to-lrgb (random 100d0 state)
(- (random 128d0 state) 256d0)
(- (random 128d0 state) 256d0)))))))))
(bench-mv-version 5000000)
;; 0.969 sec.
And then the benchmark code for a list version will be as follows:
(defun bench-list-version (num &optional (sample 10))
(let ((state (sb-ext:seed-random-state 1)))
(format t "~&~F sec."
(time-median sample
(print (loop repeat num
sum (destructuring-bind (x y z)
(list-lchab-to-lrgb (random 100d0 state)
(- (random 128d0 state) 256d0)
(- (random 128d0 state) 256d0))
(declare (double-float x y z))
(+ x y z))))))))
The problem is how we append the converters list-lchab-to-lab
, list-lab-to-xyz
, and list-xyz-to-lrgb
into list-lchab-to-lrgb
. The simplest way will be apply
and rcurry
:
(declaim (inline list-lchab-to-lrgb)
(ftype (function * (values (%list double-float double-float double-float) &optional))
list-lchab-to-lrgb))
(defun list-lchab-to-lrgb (lstar cstarab hab &key (rgbspace +srgb+)
&aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed 3) (safety 1))
(type real lstar cstarab hab))
(apply (alexandria:rcurry #'list-xyz-to-lrgb :rgbspace rgbspace)
(apply (alexandria:rcurry #'list-lab-to-xyz :illuminant illuminant)
(list-lchab-to-lab lstar cstarab hab))))
(bench-list-version 5000000)
;; 2.4135 sec.
(The type (%list double-float double-float double-float)
here is equivalent to (cons double-float (cons double-float (cons double-float)))
.)
Unfortunately alexandria:rcurry
doesn't seem to be efficient here. (Maybe extra type declarations make it a bit faster.) The second simplest and much faster way is to use destructuring-bind
(or a compiler-macro like rcurry
expanded to destructuring-bind
):
(defun list-lchab-to-lrgb (lstar cstarab hab &key (rgbspace +srgb+)
&aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed 3) (safety 1))
(type real lstar cstarab hab))
(destructuring-bind (lstar astar bstar)
(list-lchab-to-lab lstar cstarab hab)
(declare (type double-float lstar astar bstar))
(destructuring-bind (x y z)
(list-lab-to-xyz lstar astar bstar :illuminant illuminant)
(declare (type double-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))))
(bench-list-version 5000000)
;; 1.219 sec.
It is, however, still slower than the reference.
By the way, the destructuring-bind
of SBCL is expanded as follows:
(destructuring-bind (x y z)
(list-lab-to-xyz lstar astar bstar :illuminant illuminant)
(declare (type double-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
;; macroexpand =>
(let* ((#:g646
(sb-c::check-ds-list
(list-lab-to-xyz lstar astar bstar :illuminant illuminant) 3 3
'(x y z)))
(x (pop #:g646))
(y (pop #:g646))
(z (pop #:g646)))
(declare (type double-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
In this case it will be effective to declare dynamic-extent
and the type of the returned list. A desirable macro-expansion will be as follows:
(let* ((#:g650 (list-lab-to-xyz lstar astar bstar :illuminant illuminant))
(x (car #:g650)))
(declare (dynamic-extent #:g650)
(type (%list double-float double-float double-float) #:g650)
(type double-float x))
(let* ((#:g651 (cdr #:g650))
(y (car #:g651)))
(declare (type (%list double-float double-float) #:g651)
(type double-float y))
(let* ((#:g652 (cdr #:g651))
(z (car #:g652)))
(declare (type (%list double-float) #:g652)
(type double-float z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))))
;; The following expansion is simpler and works on SBCL though it appears to be illegal.
(let* ((#:g653 (list-lab-to-xyz lstar astar bstar :illuminant illuminant))
(x (pop #:g653))
(y (pop #:g653))
(z (pop #:g653)))
(declare (dynamic-extent #:g653)
(type (%list double-float double-float double-float) #:g653)
(type double-float x)
(type double-float y)
(type double-float z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
Such a macro (named typed-destructuring-bind
) makes it faster than the previous one:
(defun list-lchab-to-lrgb
(lstar cstarab hab &key (rgbspace +srgb+) &aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed 3) (safety 1))
(type real lstar cstarab hab))
(typed-destructuring-bind ((lstar double-float) (astar double-float) (bstar double-float))
(list-lchab-to-lab lstar cstarab hab)
(typed-destructuring-bind ((x double-float) (y double-float) (z double-float))
(list-lab-to-xyz lstar astar bstar :illuminant illuminant)
(list-xyz-to-lrgb x y z :rgbspace rgbspace))))
(bench-list-version 5000000)
;; 0.968 sec.
Now we have (probably) achieved the same performance as the multiple-value version.
This issue has the same topic as #3.
I made isochroma maps for two interpolation methods of Munsell renotation data:
xy-plane at Munsell value = 1.24, by method 1 :
xy-plane at Munsell value = 1.24, by method 2:
The latter is better by appearance.
Other interpolation methods I can think of now is as follows:
3. (tri)linear interpolation of xyY.
4. interpolation via LCHuv.
5. interpolation via Munsell-like color spaces by Miyahara-Yoshida.
One way to compare these methods is to evaluate the interpolation errors by interpolating a part of the Munsell renotation data based on the other part of the data, which is, however, insufficient to grasp the accuracy of overall interpolations, since there is a possibility of overfitting; it seems to me that it is essentially no different from comparing by appearance.
For speed maybe I should use multiple values insteads of lists.
As an experiment I rewrote hex-to-xyz
with multiple-values:
(defun hex-to-xyz-expr (hex &optional (rgbspace srgb))
(multiple-value-call #'rgb255-to-xyz-expr
(hex-to-rgb255-expr hex)
rgbspace))
(defun hex-to-rgb255-expr (hex)
(values (logand (ash hex -16) #xff)
(logand (ash hex -8) #xff)
(logand hex #xff)))
(defun rgb255-to-xyz-expr (r g b &optional (rgbspace srgb))
(rgb-to-xyz-expr (* r #.(float 1/255 1d0))
(* g #.(float 1/255 1d0))
(* b #.(float 1/255 1d0))
rgbspace))
(defun rgb-to-xyz-expr (r g b &optional (rgbspace srgb))
(multiple-value-call #'lrgb-to-xyz-expr
(rgb-to-lrgb-expr r g b rgbspace)
rgbspace))
(defun lrgb-to-xyz-expr (lr lg lb &optional (rgbspace srgb))
(multiply-matrix-and-vec-expr (rgbspace-to-xyz-matrix rgbspace)
lr lg lb))
(defun rgb-to-lrgb-expr (r g b &optional (rgbspace srgb))
(let ((lin (rgbspace-linearizer rgbspace)))
(values (funcall lin r)
(funcall lin g)
(funcall lin b))))
(defun multiply-matrix-and-vec-expr (matrix x y z)
(values (+ (* x (aref matrix 0 0))
(* y (aref matrix 0 1))
(* z (aref matrix 0 2)))
(+ (* x (aref matrix 1 0))
(* y (aref matrix 1 1))
(* z (aref matrix 1 2)))
(+ (* x (aref matrix 2 0))
(* y (aref matrix 2 1))
(* z (aref matrix 2 2)))))
CL-USER> (time (dotimes (x 5000000) (dufy:hex-to-xyz (random #xffffff))))
Evaluation took:
5.484 seconds of real time
5.484375 seconds of total run time (5.484375 user, 0.000000 system)
[ Run times consist of 0.236 seconds GC time, and 5.249 seconds non-GC time. ]
100.00% CPU
13,712,283,543 processor cycles
4,059,388,640 bytes consed
CL-USER> (time (dotimes (x 5000000) (dufy::hex-to-xyz-expr (random #xffffff))))
Evaluation took:
4.969 seconds of real time
4.968750 seconds of total run time (4.968750 user, 0.000000 system)
[ Run times consist of 0.189 seconds GC time, and 4.780 seconds non-GC time. ]
100.00% CPU
12,414,436,542 processor cycles
2,859,426,592 bytes consed
Apparently multiple-value version does less consing. It could be better to measure it again with some delarations for optimization.
I recommend defining different structures for the different colors. With that said, I think using lists or vectors is the right thing to do, so I’d use the DEFSTRUCT option :type
to specify the the constructed by the constructor, e.g., :type list
.
By doing this, you’ll get a nice readable API for constructing and projecting colors. The code will look less like list wrangling, and it’ll give you the option in the future to change data representation.
There was another issue about whether to use multiple values. I recommend having “internal” functions called XYZ-TO-RGB-VALUES, from which the other functions can be built.
dufy/munsell
dufy/core
module instead of dufy
lchab-to-mhvc-illum-c
lchab-to-mhvc-illum-c
I install dufy via quicklisp, version 0.1.11 (2018-04-12) and get following error:
debugger invoked on a SIMPLE-TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {23D30061}>:
Cannot set SYMBOL-VALUE of *MAXIMUM-CHROMA* to
"The largest chroma which the converters accepts. It is less than
MOST-POSITIVE-DOUBLE-FLOAT because of efficiency: e.g. in
SBCL (64-bit) it is desirable that a float F
fulfills (typep (round F) '(SIGNED-BYTE 64)",
not of type DOUBLE-FLOAT.
For me I've changed in the file munsell.lisp the line
#-(or sbcl 64-bit) most-positive-double-float
to
#+(or sbcl 64-bit) most-positive-double-float
Could you fix it right?
A propos - really great work with dufy!!!
The cond in linearize-srgb have a negative clause for x < -0.040449936d0 (exactly: (* -0.0031308d0 12.92d0)). You make similar computation in delinearize-srgb. Why? I've never seen such a formula (negative term). Neither in wikipedia, nor Bruce Lindbloom, nor other sources (e.g. https://www.w3.org/Graphics/Color/srgb.pdf).
dufy-munsell
dufy-extra-data
dufy-internal
module for commonly used functions which are not exported in the main package define define-colorspace
, define-primary-converter
, defconverter
define-primary-converter
takes an ordinary lambda list.define-primary-converter
should signal an error if &optional or &rest arguments are given.define-primary-converter
should warn if main arguments are not consistent with that of the colorspace object.defconverter
can handle duplicated &aux bindings properly.defconverter
takes exclude-args
argument to exclude particular argumemts. improve the interface of define-primary-converter
introduce let-converter
as a local version of defconverter
introduce functional
structure for general definition of a functional on a color space.
rgba
, qrgba
color spaces and related convertersint
colorspace to rgbpack
and rgbapack
dufy-extra-data
package
deltae
to deltaeab
deltaeXX
to lab-deltaeXX
I made the branch better-munsell whose default illuminant of Munsell converters is not C but D65. This has an advantage in terms of the consistency of design, as the default illuminant of dufy is D65.
It has, however, a disadvantage in terms of the consistency of data: the luminance Y no longer corresponds to Munsell value strictly, because the new data is generated via Bradford transformation. This inconsistency produces a problem especilly about interpolation, which currently depends on the value-luminance correspondence.
As an immediate conclusion, this change is no good.
In the current code the white point coordinates appears literal at the place where they are needed, e.g. (defparameter +illum-d65+ (make-illuminant 0.31271d0 0.32902d0 ...)
. What do you think about a bunch of constants for white points for all standard illuminants, once for 2°-observer and once 10°-observer, like this:
(defconstant +white-point-A-CIE-1931+ '(0.44757 . 0.40745))
(defconstant +white-point-A-CIE-1964+ '(0.45117 . 0.40594))
(defconstant +white-point-B-CIE-1931+ '(0.34842 . 0.35161))
(defconstant +white-point-B-CIE-1964+ '(0.34980 . 0.35270))
(defconstant +white-point-C-CIE-1931+ '(0.31006 . 0.31616))
(defconstant +white-point-C-CIE-1964+ '(0.31039 . 0.31905))
(defconstant +white-point-D50-CIE-1931+ '(0.34567 . 0.35850))
(defconstant +white-point-D50-CIE-1964+ '(0.34773 . 0.35952))
(defconstant +white-point-D55-CIE-1931+ '(0.33242 . 0.34743))
(defconstant +white-point-D55-CIE-1964+ '(0.33411 . 0.34877))
(defconstant +white-point-D65-CIE-1931+ '(0.31271 . 0.32902))
(defconstant +white-point-D65-CIE-1964+ '(0.31382 . 0.33100))
(defconstant +white-point-D75-CIE-1931+ '(0.29902 . 0.31485))
(defconstant +white-point-D75-CIE-1964+ '(0.29968 . 0.31740))
(defconstant +white-point-E-CIE-1931+ '(1/3 . 1/3))
(defconstant +white-point-E-CIE-1964+ '(1/3 . 1/3))
(defconstant +white-point-F1-CIE-1931+ '(0.31310 . 0.33727))
(defconstant +white-point-F1-CIE-1964+ '(0.31811 . 0.33559))
(defconstant +white-point-F2-CIE-1931+ '(0.37208 . 0.37529))
(defconstant +white-point-F2-CIE-1964+ '(0.37925 . 0.36733))
(defconstant +white-point-F3-CIE-1931+ '(0.40910 . 0.39430))
(defconstant +white-point-F3-CIE-1964+ '(0.41761 . 0.38324))
(defconstant +white-point-F4-CIE-1931+ '(0.44018 . 0.40329))
(defconstant +white-point-F4-CIE-1964+ '(0.44920 . 0.39074))
(defconstant +white-point-F5-CIE-1931+ '(0.31379 . 0.34531))
(defconstant +white-point-F5-CIE-1964+ '(0.31975 . 0.34246))
(defconstant +white-point-F6-CIE-1931+ '(0.37790 . 0.38835))
(defconstant +white-point-F6-CIE-1964+ '(0.38660 . 0.37847))
(defconstant +white-point-F7-CIE-1931+ '(0.31292 . 0.32933))
(defconstant +white-point-F7-CIE-1964+ '(0.31569 . 0.32960))
(defconstant +white-point-F8-CIE-1931+ '(0.34588 . 0.35875))
(defconstant +white-point-F8-CIE-1964+ '(0.34902 . 0.35939))
(defconstant +white-point-F9-CIE-1931+ '(0.37417 . 0.37281))
(defconstant +white-point-F9-CIE-1964+ '(0.37829 . 0.37045))
(defconstant +white-point-F10-CIE-1931+ '(0.34609 . 0.35986))
(defconstant +white-point-F10-CIE-1964+ '(0.35090 . 0.35444))
(defconstant +white-point-F11-CIE-1931+ '(0.38052 . 0.37713))
(defconstant +white-point-F11-CIE-1964+ '(0.38541 . 0.37123))
(defconstant +white-point-F12-CIE-1931+ '(0.43695 . 0.40441))
(defconstant +white-point-F12-CIE-1964+ '(0.44256 . 0.39717))
That would be great convenience for the user of your library who like to play with different illuminants than that few already defined.
Source for above data:
https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants
lchab-to-mhvc
lchab-to-munsell
xyz-to-mhvc
and xyz-to-munsell
-to-hex
and hex-to-
functionsA declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.