Coder Social home page Coder Social logo

dufy's People

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

Watchers

 avatar  avatar  avatar  avatar  avatar

dufy's Issues

Build fails on windows 7 64 bit and SBCL 32 bit

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!!!

Suggestion: define white points for all standard illuminations

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

To do for version 0.2.0

To-do list

  • LMS
    • LMS to XYZ
    • XYZ to LMS
  • spectrum
    • spectrum to XYZ
    • XYZ to spectrum
    • temperature to spectrum
  • better support of standard illuminant
    • include spectrum in the illuminant structure
    • get the SPDs of the old standard illuminants, A and C
  • observer
    • define observer as a structure of color matching functions
    • include observer in the illuminant structure
  • better support of Munsell
    • extrapolate a color out of the Munsell renotation data properly
    • better implementation of lchab-to-mhvc
    • add lchab-to-munsell
    • add xyz-to-mhvc and xyz-to-munsell
  • better support of RGB working space
    • modify linearizer and delinearizer so that they do no clamping
    • include bit-per-channel in the RGB space structure
    • include quantizer and dequantizer in the RGB space structure
    • use the term QRGB, instead of RGB255
    • modify -to-hex and hex-to- functions
    • modify HSV/HSL converters in accordance with the changes
  • more detailed README.md

To-do for version 0.5.0

  • Split the test file for each module
  • Better dufy/munsell
    • Rewrite fetch-mrd.lisp with dufy/core module instead of dufy
    • Add the test for checking `hue reversal' doesn't occurs.
    • Better lchab-to-mhvc-illum-c
      • Output the inversed Munsell Renotation Data
      • Implement the inter- and extrapolation of this data
      • Use it to get a better initial value in lchab-to-mhvc-illum-c
  • Make an extra module containing `all' converters.

List vs Multiple Values

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.

Tasks for version 0.3.0

To-do list

  • reorganize modules and packages
    • make the Munsell module independent: dufy-munsell
    • make a module for extra data sets: dufy-extra-data
    • make dufy-internal module for commonly used functions which are not exported in the main package
    • add the description about modules to README.md
  • make an extensible system of color spaces and converters (won't be exported yet)
    • 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. (postponed to the next version)
      • 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.

  • make alpha channel available
    • define rgba, qrgba color spaces and related converters
    • rename int colorspace to rgbpack and rgbapack
    • update the graph in README.md
  • add miscellaneous illuminants data to the dufy-extra-data package
    • F1-12
    • F3.x
    • illuminant B
    • several kinds of gas discharge lamps
  • modify delta-E functions for the future expansion. (In the future, a delta-E function on an arbitrary color space will be automatically generated)
    • rename deltae to deltaeab
    • rename deltaeXX to lab-deltaeXX
    • add CMC l:c delta-E

Comparison of two Munsell-to-xyY converters

I made isochroma maps for two interpolation methods of Munsell renotation data:

  1. interpolation of xyY with converting (x, y) to polar coordinates (r, θ).
  2. interpolation via LCHab.

xy-plane at Munsell value = 1.24, by method 1 :

xy-plane-value-1 24-with-direct-interpolation

xy-plane at Munsell value = 1.24, by method 2:

xy-plane-value-1 24-with-lch-interpolation

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.

Illuminant of Munsell converter

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.

Wrong illuminant in wide-gamut RGB space

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?

To do for version 0.1.0

To do list

  • HSL
  • CIELUV and LCH(uv)
  • better support of Munsell color system
    • implement an inversion system from 24-bit RGB to Munsell
    • build inversion data from sRGB (D65) to Munsell
    • more appropriate conversion of dark colors whose values are within [0, 0.2]
    • string specification of Munsell color
  • three kinds of delta E
    • CIE76
    • CIE94
    • CIEDE2000
  • more detailed README.md

Move to package-inferred-system

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.

List vs multiple values 2

Summary

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

Body

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.

Use DEFSTRUCT for color types

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.

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.