Patch title: Release 87 bulk changes
Abstract:
File: /pliant/graphic/color/editor.page
Key:
    Removed line
    Added line
module "/pliant/language/unsafe.pli"
module "/pliant/graphic/misc/float.pli"
module "/pliant/admin/file.pli"
module "/pliant/protocol/http/uvar.pli"
module "database.pli"
module "spectrum.pli"
module "color.pli"
module "adjust.pli"
module "gradation.pli"
module "ink.pli"
module "gamut.pli"
module "spectro.pli"

module "/pliant/math/curve.pli"
module "/pliant/graphic/image/prototype.pli"
module "/pliant/graphic/image/pixmap.pli"
module "/pliant/graphic/filter/io.pli"
module "/pliant/util/encoding/http.pli"

module "/pliant/protocol/http/style/draw.pli"

requires "color_administrator"

gvar CBool initialized
constant { initialized := false }


method page display_spectrum filter1 filter2 filter3 scale_x scale_y
  arg_rw HtmlPage page ; arg ColorSpectrum32 filter1 filter2 filter3 ; arg Int scale_x scale_y
  var Link:ImagePixmap pixmap :> new ImagePixmap
  pixmap setup (image_prototype 700 100 400 0 300*scale_x+1 100*scale_y+1 color_gamut:"rgb") ""
  for (var Int w) 400 700
    (var ColorSpectrum sp) set_step 10
    for (var Int i) 400 700 step 10
      sp set_measure i 0.1
    sp set_measure w-w%10 0.1+0.9*(1-w%10/10)
    sp set_measure w-w%10+10 0.1+0.9*(w%10/10)
    var ColorRGB888 rgb := cast filter_XYZ:(cast sp ColorSpectrum32) ColorRGB888
    for (var Int iy) 0 100*scale_y
      for (var Int i) 0 scale_x-1
        if (700-w)*scale_x+i<pixmap:size_x
          memory_copy addressof:rgb (pixmap pixel (700-w)*scale_x+i iy) ColorRGB888:size
  for (var Int d) 0 2
    var ColorSpectrum32 filter := shunt d=0 filter1 d=1 filter2 filter3
    if filter<>undefined
      var ColorSpectrum s := cast filter ColorSpectrum
      for (var Int w) 400 700
        var Float z := s w
        if z>=0 and z<=1
          (pixmap pixel (700-w)*scale_x (cast 100*scale_y*(1-z) Int)) map uInt8 d := 255
  var Str f1 := string (cast filter1 ColorSpectrum)
  var Str f2 := string (cast filter2 ColorSpectrum)
  var Str f3 := string (cast filter3 ColorSpectrum)
  page draw pixmap position
  draw pixmap position
    f1 parse (var ColorSpectrum s1a)
    f2 parse (var ColorSpectrum s2a)
    f3 parse (var ColorSpectrum s3a)
    display_spectrum (cast s1a ColorSpectrum32) (cast s2a ColorSpectrum32) (cast s3a ColorSpectrum32) 2 5

method page display_spectrum filter1 filter2 filter3
  arg_rw HtmlPage page ; arg ColorSpectrum32 filter1 filter2 filter3
  display_spectrum filter1 filter2 filter3 1 1

if (options option "device") and (options option "channel")
  var Link:ImagePixmap img :> new ImagePixmap
  img setup (image_prototype 0 0 501 501 501 501 color_gamut:"rgb") ""
  var ColorRGB888 black ; black r := 0 ; black g := 0 ; black b := 0
  var ColorRGB888 dark ; dark r := 40h ; dark g := 40h ; dark b := 40h
  var ColorRGB888 middle ; middle r := 80h ; middle g := 80h ; middle b := 80h
  var ColorRGB888 light ; light r := 0C0h ; light g := 0C0h ; light b := 0C0h
  for (var Int i) 0 500
    img fill 0 i 501 addressof:light
  for (var Int i) 0 500 step 5
    for (var Int j) 0 500 step 5
      (img pixel i j) map ColorRGB888 := shunt i%50=0 or j%50=0 black i%25=0 or j%25=0 dark middle
  for (var Int i) 0 2
    for (var Int j) 0 2
      for (var Int k) -2 2
        var Int xx := i*250+k
        var Int yy := j*250
        if xx>=0 and xx<=500 and yy>=0 and yy<=500
          (img pixel xx yy) map ColorRGB888 := black
        var Int xx := i*250
        var Int yy := j*250+k
        if xx>=0 and xx<=500 and yy>=0 and yy<=500
          (img pixel xx yy) map ColorRGB888 := black
  var Data:ColorChannel ch :> color_database:data:device:(options option "device" Str):channel:(options option "channel" Str)
  var ColorSpectrum32 z_s0 := ch s0
  var ColorSpectrum32 z_s100 := ch s100
  if z_s0=undefined and z_s100=undefined
    return
  var Curve curve1 := var Curve empty_curve
  var Curve curve2 := var Curve empty_curve
  var Curve curve3 := var Curve empty_curve
  var Float range_x := 1
  var Float range_y := 1
  each sample ch:sample filter (keyof:sample parse (var Int i)) sort (right keyof:sample 9 " ")
    keyof:sample parse (var Float x)
    var Float y := ink_density color_spectrum32:sample z_s0 z_s100
    curve1 through x y
    var Float y := ink_surface color_spectrum32:sample z_s0 z_s100
    curve2 through x y
    var Float y := ink_thickness color_spectrum32:sample z_s0 z_s100
    curve3 through x y
    range_x := max range_x x
    range_y := max range_y y
  curve1 compute y_from_x
  curve2 compute y_from_x
  curve3 compute y_from_x
  if curve3=success
    var ColorRGB888 blue ; blue r := 0 ; blue g := 0 ; blue b := 255
    for (var Int i) 0 500
      var Float y := (curve3 y i/500*range_x 1e-6)/range_y
      if y=defined
        var Int j := cast y*500 Int
        if j>=0 and j<=500
          (img pixel i 500-j) map ColorRGB888 := blue
  if curve2=success
    var ColorRGB888 green ; green r := 0 ; green g := 255 ; green b := 0
    for (var Int i) 0 500
      var Float y := (curve2 y i/500*range_x 1e-6)/range_y
      if y=defined
        var Int j := cast y*500 Int
        if j>=0 and j<=500
          (img pixel i 500-j) map ColorRGB888 := green
  if curve1=success
    var ColorRGB888 red ; red r := 255 ; red g := 0 ; red b := 0
    for (var Int i) 0 500
      var Float y := (curve1 y i/500*range_x 1e-6)/range_y
      if y=defined
        var Int j := cast y*500 Int
        if j>=0 and j<=500
          (img pixel i 500-j) map ColorRGB888 := red
  reset_http_answer
  http_request answer_mime_type := "image/png"
  http_request send_header
  img save http_stream "filter [dq].png[dq]"
  http_request send_header "mime [dq]image/png[dq]"
  img save http_request:answer_stream "filter [dq].png[dq]"
  http_request send_footer
  return
  
title "Color database editor"

para
  [Please notice that in Pliant terminology, a 'color gamut', a 'color device' and a 'color profile' are basically all the same thing.]

page button "load new parameters"
  color_ink_cache_reset
  color_gamut_cache_reset
  file_tree_delete "data:/pliant/graphic/cache/"
  color_version += 1
  reload_page

uvar Str src_gamut_name densities dest_gamut_name

page note "sample color conversion tool"
  title "Sample color conversion"
  input "Source gamut: " src_gamut_name length 80
  input "Source densities (in %): " densities length 40
  input "Destination gamut: " dest_gamut_name length 80
  button "Compute"
    reload_page
  para
    var Link:ColorGamut src_gamut :> color_gamut src_gamut_name
    var Link:ColorGamut dest_gamut :> color_gamut dest_gamut_name
    if src_gamut=success and dest_gamut=success
      var ColorBuffer pixel
      var Str all := densities ; var Int i := 0
      while i<gamut_maximum_dimension and i<src_gamut:dimension and (all parse (var Float d) any:(var Str remain))
        pixel:bytes i := cast d/100*255 Int
        all := remain ; i += 1
      var ColorXYZ pixel_color := src_gamut simulate addressof:pixel
      var ColorXYZ wished_color := pixel_color
      color_adjust wished_color (dest_gamut query "options")
      dest_gamut formulate wished_color addressof:(var ColorBuffer result)
      var ColorXYZ result_color := dest_gamut simulate addressof:result
      table columns 3 border 0
        cell
          table columns 2
            for (var Int i) 0 src_gamut:dimension-1
              cell
                text (src_gamut query "component_name "+string:i)
              cell
                text (string pixel:bytes:i/255*100 "fixed 1")+" %"
        cell
          void
        cell
          table columns 2
            for (var Int i) 0 dest_gamut:dimension-1
              cell
                text (dest_gamut query "component_name "+string:i)
              cell
                text (string result:bytes:i/255*100 "fixed 1")+" %"
        cell
          var ColorXYZn colorn := cast pixel_color ColorXYZn
          text "XYZn "+(string colorn:X "fixed 3")+" "+(string colorn:Y "fixed 3")+" "+(string colorn:Z "fixed 3") ; eol
          var ColorLCh lch := cast pixel_color ColorLCh
          text "LCh "+(string lch:L "fixed 1")+" "+(string lch:C "fixed 1")+" "+(string lch:h "fixed 0")
        cell
          var ColorXYZn colorn := cast wished_color ColorXYZn
          text "XYZn "+(string colorn:X "fixed 3")+" "+(string colorn:Y "fixed 3")+" "+(string colorn:Z "fixed 3") ; eol
          var ColorLCh lch := cast wished_color ColorLCh
          text "LCh "+(string lch:L "fixed 1")+" "+(string lch:C "fixed 1")+" "+(string lch:h "fixed 0")
        cell
          var ColorXYZn colorn := cast result_color ColorXYZn
          text "XYZn "+(string colorn:X "fixed 3")+" "+(string colorn:Y "fixed 3")+" "+(string colorn:Z "fixed 3") ; eol
          var ColorLCh lch := cast result_color ColorLCh
          text "LCh "+(string lch:L "fixed 1")+" "+(string lch:C "fixed 1")+" "+(string lch:h "fixed 0")
        cell color (cast pixel_color ColorRGB888)
          fixed [ ]
        cell void
        cell color (cast result_color ColorRGB888)
          fixed [ ]
  para
    display_spectrum (src_gamut simulate2 addressof:pixel) (dest_gamut simulate2 addressof:result) (var ColorSpectrum32 no_spectrum)

table columns 3
  cell header [Device]
  cell void
  cell header [Description]
  each d color_database:data:device
    cell
      text keyof:d
    cell
      text d:label
    cell
      page button "edit"
        title "Color device '"+keyof:d+"'"
        page note "printer settings used to build this profile"
          title "Printer settings for '"+keyof:d+"' color profile"
          var Data:ColorPrinter p :> d printer
          table columns 3 border 0
            cell [Driver:]
            cell
              input "" p:driver length 10
            cell
              [The filter is the Pliant driver.] ; eol
              [Most common filter are] ; fixed [ escp2 ] ; [for Epson stylus line,]
              fixed [ pcl ] ; [for HP Designjet (does not work on HP Desktop inkjet printers), ]
              [and ] ; fixed [ gimprint ] ; [if the printer will be driven using Gimp-print free software.]
            cell [Model:]
            cell
              input "" p:model length 20
            cell
              [Must be one of the models the driver is awared of.]
            cell [Extra options:]
            cell
              input "" p:options length 40
            cell
              [As an example, the] ; fixed [ escp2 ] ; [driver recognizes] ; fixed [ unidirectional ] ; [option.]
            cell [Paper name:]
            cell
              input "" p:paper length 40 noeol
            cell
              [This is informative only.]
            cell [Paper size:]
            cell
              input "" p:size_x length 4 noeol
              input " x " p:size_y length 4 noeol
              [ mm]
            cell
              void
            cell [Margins:]
            cell
              input "L" p:margin_left length 3 noeol
              input "T" p:margin_top length 3 noeol
              input "R" p:margin_right length 3 noeol
              input "B" p:margin_bottom length 3 noeol
              [ mm]
            cell
              void
            cell [R鳯lution:]
            cell
              input "" p:resolution_x length 4 noeol
              input " x " p:resolution_y length 4 noeol
              [ dpi]
            cell
              void
            cell [Antialiasing:]
            cell
              input "" p:antialiasing_x length 1 noeol
              input " x " p:antialiasing_y length 1
            cell
              [Use no antialiasing if your computer is slow, 2 if your computer is faster than the printer.] 
            cell [Sharpening:]
            cell
              input "" p:sharpening length 5
            cell
              [Leave it blank unless you want to experiment.]
            cell [Maximum density:]
            cell
              input "" p:limit length 5
            cell
              [Leave it blank unless you know what you do: this is not the right way to correct a poor calibration.]
            cell [Middle adjustment:]
            cell
              input "" p:middle length 5
            cell
              [Leave it blank unless you know what you do: this is not the right way to correct a poor calibration.]
            cell [Gamut:]
            cell
              input "" p:gamut length 40
            cell
              void
            cell [Calibration steps:]
            cell
              input "" p:grid_steps length 2
            cell
              [How many steps do we compute on the calibration grid in each dimension. ]
              [33 is perfect but will require several hours of calibration on a fast computer, 17 is high quality, and 9 is correct.]
          button "Update"
            goto_backward
        table columns 2 border 0
          cell [Label:]
          cell
            input "" d:label length 60
          cell [Description:]
          cell
            text_input "" d:comment columns 60 rows 10
          cell [Gamut extra options:]
          cell
            input "" d:options length 60 noeol
            button "Update"
              color_version += 1
              goto_backward
            link "what do these options mean" "/pliant/graphic/color/adjust"
          
        table columns 5
          cell header [Ink]
          cell header [Sample]
          cell header [Opacity]
          cell void
          cell header [Options]
          each ch d:channel
            cell
              text keyof:ch
            var ColorSpectrum32 a_s0 := ch s0
            var ColorSpectrum32 a_s100 := ch s100
            if a_s0=defined and a_s100=defined
              cell color (cast (filter_XYZ a_s100/a_s0) ColorRGB888)
                fixed [ ]
            else
              cell void
            cell
              if (ch:options option "opacity" Float)=defined
                text string:(ch:options option "opacity" Float)
            cell
              page button "edit"
                title "Ink '"+keyof:ch+"' for device '"+keyof:d+"'"
                var ColorSpectrum32 s0 := ch s0
                var ColorSpectrum32 s100 := ch s100
                var CBool composed := (keyof:ch search "+" -1)<>(-1)
                var Float specular := ch:options+" "+d:options option "specular" Float 0
                if s0=defined and s100=defined
                  check ColorSpectrum32:size=32*Float32:size
                  table columns 4 border 0
                    cell
                      var Float mi := 1
                      var Float ma := 0
                      for (var Int w) 0 30
                        mi := min mi ((addressof:s100 translate Float32 w) map Float32)/((addressof:s0 translate Float32 w) map Float32)
                        ma := max ma ((addressof:s100 translate Float32 w) map Float32)/((addressof:s0 translate Float32 w) map Float32)
                      text "Reflexion range: "
                      fixed
                        text (string (cast mi*1000 Int)/1000)+" "+(string (cast ma*1000 Int)/1000)
                      eol
                      var ColorXYZ xyz := filter_XYZ s100/s0
                      text "XYZ: "
                      fixed
                        text (string xyz:X "fixed 3")+" "+(string xyz:Y "fixed 3")+" "+(string xyz:Z "fixed 3")
                      eol
                      var ColorYxy yxy := cast xyz ColorYxy
                      text "Yxy: "
                      fixed
                        text (string yxy:Y "fixed 3")+" "+(string yxy:x "fixed 3")+" "+(string yxy:y "fixed 3")
                      eol
                      var ColorLab lab := cast xyz ColorLab
                      text "L*a*b*: "
                      fixed
                        text (string lab:L "fixed 1")+" "+(string lab:a "fixed 1")+" "+(string lab:b "fixed 1")
                      eol
                      text "Specular: "+(string specular "fixed 3")
                    var ColorXYZ paper := filter_XYZ s0
                    cell
                      [Paper:] ; eol
                      var ColorLCh lch := cast paper ColorLCh
                      [L*C*h] ; small [ ab] ; eol
                      fixed
                        text (string lch:L "fixed 1") ; fixed [ ] ; text (string lch:C "fixed 1") ; fixed [ ] ; text (string lch:h "fixed 0") ; eol
                      [Sample and spectrum ->]
                    cell
                      var ColorRGB888 rgb := cast paper ColorRGB888
                      image "sample/r"+string:(cast rgb:r Int)+"g"+string:(cast rgb:g Int)+"b"+string:(cast rgb:b Int)
                    cell
                      display_spectrum s0 (var ColorSpectrum32 no_spectrum) (cast no_spectrum ColorSpectrum32)
                input "Options: " ch:options length 60 noeol
                var Float display := ch:options option "density_exposure_display" Float 0
                page button "Update"
                  color_version += 1
                  goto_backward
                table columns (shunt composed 9 7)
                  cell header [Color]
                  cell header
                    [Linear[lf]Thickness[lf]Surface]
                  cell header [Density]
                  cell header [Opacity]
                  cell header [Sample]
                  cell header
                    [L*C*h] ; small [ ab]
                  if composed
                    cell header [computation[lf]color]
                    cell header [computation[lf]sample]
                  cell header
                    [Spectrum] ; eol
                    small [red is measured, green is computed]
                  each sample ch:sample sort (right keyof:sample 3 " ")
                    cell
                      text keyof:sample
                    cell
                      if s0=defined
                        var ColorXYZ wished := filter_XYZ color_spectrum32:sample/s0
                      if s0=defined and s100=defined
                        var Float density := ink_linear color_spectrum32:sample s0 s100
                        text (string density*100 "fixed 1")+"%"
                        var ColorXYZ got := filter_XYZ (ink_linear_simulation density s0 s100)/s0
                        var Float err := lab_distance wished got
                        fixed [ ] ; small { html "&Delta;E " ; text (string err "fixed 1") } 
                        var Float err := cmc_distance wished got
                        fixed [ ] ; small (text "CMC "+(string err "fixed 1"))
                        eol
                        var Float density := ink_thickness color_spectrum32:sample s0 s100
                        text (string density*100 "fixed 1")+"%"
                        var ColorXYZ got := filter_XYZ (ink_thickness_simulation density s0 s100)/s0
                        var Float err := lab_distance wished got
                        fixed [ ] ; small { html "&Delta;E " ; text (string err "fixed 1") } 
                        var Float err := cmc_distance wished got
                        fixed [ ] ; small (text "CMC "+(string err "fixed 1"))
                        eol
                        var Float density := ink_surface color_spectrum32:sample s0 s100
                        text (string (cast density*1000 Int)/10)+"%"
                        var ColorXYZ got := filter_XYZ (ink_surface_simulation density s0 s100)/s0
                        var Float err := lab_distance wished got
                        fixed [ ] ; small { html "&Delta;E " ; text (string err "fixed 1") } 
                        var Float err := cmc_distance wished got
                        fixed [ ] ; small (text "CMC "+(string err "fixed 1"))
                    cell
                      if s0=defined and s100=defined
                        var Float density := ink_density color_spectrum32:sample s0 s100
                        bold (text (string (exposure density display)*100 "fixed 1")+"%")
                    cell
                      text string:(sample option "opacity" Float)
                    cell
                      if s0=defined
                        var ColorRGB888 rgb := cast (filter_XYZ color_spectrum32:sample/s0) ColorRGB888
                        image "sample/r"+string:(cast rgb:r Int)+"g"+string:(cast rgb:g Int)+"b"+string:(cast rgb:b Int)
                    cell
                      if s0=defined
                        var ColorLCh lch := cast wished ColorLCh
                        text (string lch:L "fixed 1") ; fixed [ ] ; text (string lch:C "fixed 1") ; fixed [ ] ; text (string lch:h "fixed 0")
                    if composed
                      cell
                        part compute_mixture
                          var ColorSpectrum32 s1 := cast 1 ColorSpectrum32
                          var Str all := keyof ch
                          while all<>""
                            if not (all parse any:(var Str first) "+" any:(var Str remain))
                              first := all ; remain := ""
                            var ColorSpectrum32 n1 := color_spectrum32 d:channel:first:sample:(keyof sample)
                            var ColorSpectrum32 d1 := color_spectrum32 d:channel:first:sample:"0"
                            if n1=undefined or d1=undefined
                              s1 := cast (cast undefined Float) ColorSpectrum32 ; leave compute_mixture
                            n1 := max n1-(cast specular ColorSpectrum32) (cast 0 ColorSpectrum32)
                            d1 := max d1-(cast specular ColorSpectrum32) (cast 0 ColorSpectrum32)
                            s1 *= n1/d1
                            all := remain
                          s1 := (1-specular)*s1+(cast specular ColorSpectrum32)
                          var ColorXYZ wished1 := filter_XYZ s1
                          var ColorLCh lch1 := cast wished1 ColorLCh
                          text (string lch1:L "fixed 1") ; fixed [ ] ; text (string lch1:C "fixed 1") ; fixed [ ] ; text (string lch1:h "fixed 0")
                      cell
                        if s1=defined
                          var ColorRGB888 rgb := cast filter_XYZ:s1 ColorRGB888
                          image "sample/r"+string:(cast rgb:r Int)+"g"+string:(cast rgb:g Int)+"b"+string:(cast rgb:b Int)
                      cell
                        if s0=defined and s1=defined
                          display_spectrum color_spectrum32:sample/s0 s1 (cast specular ColorSpectrum32)
                    else
                      cell
                        if s0=defined
                          display_spectrum color_spectrum32:sample/s0 (var ColorSpectrum32 no_spectrum) (cast specular ColorSpectrum32)
                input "Color level: " (var Str l) noeol
                page button "Measure" noeol
                  var Str s
                  if (l parse any:(var Str l1) "=" any:(var Str l2) "/" any:(var Str l3) "/" any:(var Str l4))
                    l := l1
                    s := color_database:data:device:l2:channel:l3:sample:l4
                  else
                    if not initialized
                      console "initializing spectrocolorimeter interface" eol
                      spectro_init
                      initialized := true
                    s := string spectro_spectrum
                  ch:sample create l
                  ch:sample l := s
                  reload_page
                button "Delete" noeol
                  ch:sample delete l
                  reload_page
                if s0=defined
                  para
                    input "Gamut: " (var Str gamut_name) length 40 noeol
                    page button "Formulate" noeol
                      title "Color formultation to '"+gamut_name+"'"
                      var ColorSpectrum32 s0a := ch s0
                      var ColorSpectrum32 s100a := ch s100
                      var Link:ColorGamut gamut :> color_gamut gamut_name
                      table columns 7
                        cell header [Color]
                        cell header
                          [Thickness[lf]Surface[lf]]
                          [L*C*h] ; small [ ab]
                        cell header [Density]
                        cell header [Sample]
                        cell header [Formulation]
                        cell header
                          [L*C*h] ; small [ ab]
                        cell header [Proof color]
                        each sample ch:sample sort (right keyof:sample 3 " ")
                          cell
                            text keyof:sample
                          cell
                            if s100a=defined
                              var Float density := ink_thickness color_spectrum32:sample s0a s100a
                              text (string density*100 "fixed 1")+"%" ; eol
                              var Float density := ink_surface color_spectrum32:sample s0a s100a
                              text (string density*100 "fixed 1")+"%" ; eol
                            var ColorLCh lch2 := cast (filter_XYZ color_spectrum32:sample/s0a) ColorLCh
                            text (string lch2:L "fixed 1") ; fixed [ ] ; text (string lch2:C "fixed 1") ; fixed [ ] ; text (string lch2:h "fixed 0")
                          cell
                            if s100a=defined
                              var Float density := ink_density color_spectrum32:sample s0a s100a
                              text (string density*100 "fixed 1")+"%"
                          cell
                            var ColorRGB888 rgb2 := cast (filter_XYZ color_spectrum32:sample/s0a) ColorRGB888
                            image "sample/r"+string:(cast rgb2:r Int)+"g"+string:(cast rgb2:g Int)+"b"+string:(cast rgb2:b Int)
                          gamut formulate (filter_XYZ color_spectrum32:sample/s0a) addressof:(var ColorBuffer pixel)
                          gamut decode addressof:pixel (var (Array Float32 gamut_maximum_dimension) components)
                          var ColorXYZ final := gamut simulate addressof:pixel
                          cell
                            fixed
                              for (var Int dim) 0 gamut:dimension-1
                                text (shunt dim=0 "" " ")+(right string:(cast pixel:bytes:dim Int) 3 " ")+" "
                            eol
                            for (var Int dim) 0 gamut:dimension-1
                              text (shunt dim=0 "" " ")+(string components:dim*100 "fixed 1")+"%"
                          cell
                            var Float err := lab_distance (filter_XYZ color_spectrum32:sample/s0a) final
                            small { html "&Delta;E " ; text (string err "fixed 1") } ; eol
                            var Float err := cmc_distance (filter_XYZ color_spectrum32:sample/s0a) final
                            small (text "CMC "+(string err "fixed 1")) ; eol
                            var ColorLCh lch2 := cast final ColorLCh
                            text (string lch2:L "fixed 1") ; fixed [ ] ; text (string lch2:C "fixed 1") ; fixed [ ] ; text (string lch2:h "fixed 0")
                          cell
                            var ColorRGB888 rgb2 := cast final ColorRGB888
                            image "sample/r"+string:(cast rgb2:r Int)+"g"+string:(cast rgb2:g Int)+"b"+string:(cast rgb2:b Int)
                if s0=defined and s100=defined
                  para
                    image "editor.html?"+(http_encode "device "+(string keyof:d)+" channel "+(string keyof:ch))
                    eol ; small [red is density, green is surface,  blue is thickness.]
            cell
              text ch:options
        input "Channel ID: " (var Str cid) noeol
        button "Create new ink" noeol
          d:channel create cid
          reload_page
        button "Delete the ink"
          d:channel delete cid
          reload_page
        table columns 4
          cell header [Color]
          cell header [... is an alias of]
          cell header [Sample]
          cell header [Opacity]
          each a d:alias
            cell
              text keyof:a
            cell
              input "" a
            var Data:ColorChannel ch :> d:channel a
            var ColorSpectrum32 a_s0 := ch s0
            var ColorSpectrum32 a_s100 := ch s100
            if a_s0=defined and a_s100=defined
              cell color (cast (filter_XYZ a_s100/a_s0) ColorRGB888)
                fixed [ ]
            else
              cell void
            cell
              if (ch:options option "opacity" Float)=defined
                text string:(ch:options option "opacity" Float)
        input "Alias ID: " (var Str aid) noeol
        button "Create new alias" noeol
          d:alias create aid
          reload_page
        button "Delete the alias"
          d:alias delete aid
          reload_page
        table columns 2
          cell header [Grid]
          cell header [Steps]
          each g d:grid
            cell
              text keyof:g
            cell
              input "" g length 2
        input "Grid: " (var Str gid) length 40 noeol
        button "Create new grid" noeol
          d:grid create gid
          d:grid gid := undefined
          reload_page
        button "Delete the grid"
          d:grid delete gid
          reload_page
        [A grid value could be 'process_cyan+process_magenta+process_yellow+process_black' specifying that when converting from a gamut using several of these colors, the conversion will use a multidimension grid instead of per channel conversion.]
     

input "Color device ID: " (var Str did) noeol
button "Create new color device" noeol
  color_database:data:device create did
  reload_page
button "Delete the color device"
  color_database:data:device delete did
  reload_page