ImageEditor.st
changeset 3195 32e373ae50fa
parent 3191 ae97e9478128
child 3196 830d70ff1d54
--- a/ImageEditor.st	Thu Feb 26 21:54:40 2015 +0100
+++ b/ImageEditor.st	Thu Feb 26 23:04:44 2015 +0100
@@ -1843,196 +1843,198 @@
     <resource: #canvas>
 
     ^ 
-     #(FullSpec
-        name: windowSpec
-        window: 
-       (WindowSpec
-          label: 'Image Editor'
-          name: 'Image Editor'
-          min: (Point 400 320)
-          bounds: (Rectangle 0 0 450 350)
-          menu: menu
-          icon: defaultIcon
-        )
-        component: 
-       (SpecCollection
-          collection: (
-           (MenuPanelSpec
-              name: 'menuToolbarView'
-              layout: (LayoutFrame 0 0.0 0 0 0 1.0 32 0)
-              style: (FontDescription helvetica medium roman 10)
-              menu: menuToolbar
-              showSeparatingLines: true
-            )
-           (VariableHorizontalPanelSpec
-              name: 'mainPanel'
-              layout: (LayoutFrame 0 0.0 34 0.0 0 1.0 -26 1.0)
-              snapMode: both
-              barLevel: 0
-              component: 
-             (SpecCollection
-                collection: (
-                 (ViewSpec
-                    name: 'leftView'
-                    level: 1
-                    component: 
-                   (SpecCollection
-                      collection: (
-                       (VariableVerticalPanelSpec
-                          name: 'verticalPanel'
-                          layout: (LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
-                          level: 0
-                          snapMode: both
-                          component: 
-                         (SpecCollection
-                            collection: (
-                             (ViewSpec
-                                name: 'View1'
-                                component: 
-                               (SpecCollection
-                                  collection: (
-                                   (MenuPanelSpec
-                                      name: 'MouseButtonColorToolBar'
-                                      layout: (LayoutFrame 0 0.0 0 0 0 1.0 24 0)
-                                      level: 0
-                                      menu: menuMouseButtonColors
-                                    )
-                                   (DataSetSpec
-                                      name: 'colorDataSetView'
-                                      layout: (LayoutFrame 0 0.0 26 0.0 0 1.0 0 1.0)
-                                      activeHelpKey: colorMapTable
-                                      style: (FontDescription helvetica medium roman 10)
-                                      model: selectionOfColor
-                                      menu: colorMapMenu
-                                      hasHorizontalScrollBar: true
-                                      hasVerticalScrollBar: true
-                                      miniScrollerHorizontal: true
-                                      miniScrollerVertical: true
-                                      dataList: listOfColors
-                                      has3Dseparators: true
-                                      doubleClickSelector: doubleClickOnColor:
-                                      columnHolder: colorTableColumns
-                                      verticalSpacing: 1
-                                      columnAdaptor: colorColumnAdaptor
-                                    )
+    #(FullSpec
+       name: windowSpec
+       window: 
+      (WindowSpec
+         label: 'Image Editor'
+         name: 'Image Editor'
+         min: (Point 400 320)
+         bounds: (Rectangle 0 0 450 350)
+         menu: menu
+         icon: defaultIcon
+       )
+       component: 
+      (SpecCollection
+         collection: (
+          (MenuPanelSpec
+             name: 'menuToolbarView'
+             layout: (LayoutFrame 0 0.0 0 0 0 1.0 32 0)
+             style: (FontDescription helvetica medium roman 10)
+             menu: menuToolbar
+             showSeparatingLines: true
+           )
+          (VariableHorizontalPanelSpec
+             name: 'mainPanel'
+             layout: (LayoutFrame 0 0.0 34 0.0 0 1.0 -26 1.0)
+             snapMode: both
+             barLevel: 0
+             component: 
+            (SpecCollection
+               collection: (
+                (ViewSpec
+                   name: 'leftView'
+                   level: 1
+                   component: 
+                  (SpecCollection
+                     collection: (
+                      (VariableVerticalPanelSpec
+                         name: 'verticalPanel'
+                         layout: (LayoutFrame 0 0.0 0 0.0 0 1.0 0 1.0)
+                         level: 0
+                         snapMode: both
+                         component: 
+                        (SpecCollection
+                           collection: (
+                            (ViewSpec
+                               name: 'View1'
+                               component: 
+                              (SpecCollection
+                                 collection: (
+                                  (MenuPanelSpec
+                                     name: 'MouseButtonColorToolBar'
+                                     layout: (LayoutFrame 0 0.0 0 0 0 1.0 24 0)
+                                     level: 0
+                                     menu: menuMouseButtonColors
                                    )
-                                 
-                                )
-                              )
-                             (ArbitraryComponentSpec
-                                name: 'imagePreView'
-                                activeHelpKey: previewView
-                                menu: previewMenu
-                                hasHorizontalScrollBar: true
-                                hasVerticalScrollBar: true
-                                miniScrollerHorizontal: false
-                                miniScrollerVertical: false
-                                hasBorder: false
-                                component: ImageView
-                              )
+                                  (DataSetSpec
+                                     name: 'colorDataSetView'
+                                     layout: (LayoutFrame 0 0.0 26 0.0 0 1.0 0 1.0)
+                                     activeHelpKey: colorMapTable
+                                     style: (FontDescription helvetica medium roman 10)
+                                     model: selectedColors
+                                     menu: colorMapMenu
+                                     hasHorizontalScrollBar: true
+                                     hasVerticalScrollBar: true
+                                     miniScrollerHorizontal: true
+                                     miniScrollerVertical: true
+                                     dataList: listOfColors
+                                     has3Dseparators: true
+                                     doubleClickSelector: doubleClickOnColor:
+                                     columnHolder: colorTableColumns
+                                     multipleSelectOk: true
+                                     verticalSpacing: 1
+                                     columnAdaptor: colorColumnAdaptor
+                                   )
+                                  )
+                                
+                               )
                              )
-                           
-                          )
-                          handles: (Any 0.5 1.0)
-                        )
+                            (ArbitraryComponentSpec
+                               name: 'imagePreView'
+                               activeHelpKey: previewView
+                               menu: previewMenu
+                               hasHorizontalScrollBar: true
+                               hasVerticalScrollBar: true
+                               miniScrollerHorizontal: false
+                               miniScrollerVertical: false
+                               hasBorder: false
+                               component: ImageView
+                             )
+                            )
+                          
+                         )
+                         handles: (Any 0.5 1.0)
                        )
-                     
-                    )
-                  )
-                 (ViewSpec
-                    name: 'rightView'
-                    component: 
-                   (SpecCollection
-                      collection: (
-                       (MenuPanelSpec
-                          name: 'ToolBar1'
-                          layout: (LayoutFrame 0 0 0 0.0 28 0 0 1.0)
-                          level: 1
-                          menu: toolsMenuToolbar
-                          verticalLayout: true
-                          centerItems: true
-                          textDefault: true
-                        )
-                       (ViewSpec
-                          name: 'editingView'
-                          layout: (LayoutFrame 28 0.0 0 0.0 0 1.0 0 1.0)
-                          level: 1
-                          component: 
-                         (SpecCollection
-                            collection: (
-                             (ArbitraryComponentSpec
-                                name: 'imageEditView'
-                                layout: (LayoutFrame 2 0.0 2 0.0 -2 1.0 -24 1.0)
-                                hasHorizontalScrollBar: true
-                                hasVerticalScrollBar: true
-                                hasBorder: false
-                                component: ImageEditView
-                              )
-                             (LabelSpec
-                                name: 'coordLabel'
-                                layout: (LayoutFrame 2 0.0 -22 1 -83 1.0 0 1.0)
-                                level: -1
-                                labelChannel: imageInfoHolder
-                                resizeForLabel: false
-                                adjust: left
-                              )
-                             (ArrowButtonSpec
-                                name: 'magnifyDownButton'
-                                layout: (LayoutFrame -80 1 -22 1 -58 1 0 1)
-                                activeHelpKey: magnifyImageDown
-                                model: doMagnifyDown
-                                enableChannel: imageIsLoadedHolder
-                                isTriggerOnDown: true
-                                direction: left
-                              )
-                             (ArrowButtonSpec
-                                name: 'magnifyUpButton'
-                                layout: (LayoutFrame -24 1 -22 1 -2 1 0 1)
-                                activeHelpKey: magnifyImageUp
-                                model: doMagnifyUp
-                                enableChannel: imageIsLoadedHolder
-                                isTriggerOnDown: true
-                                direction: right
-                              )
-                             (InputFieldSpec
-                                name: 'magnificationInputField'
-                                layout: (LayoutFrame -57 1 -22 1 -26 1 0 1)
-                                activeHelpKey: magnificationNumber
-                                enableChannel: imageIsLoadedHolder
-                                model: magnificationHolder
-                                type: numberInRange
-                                acceptOnReturn: true
-                                acceptOnTab: true
-                                numChars: 2
-                                minValue: 1
-                                maxValue: 99
-                                acceptOnPointerLeave: true
-                              )
+                      )
+                    
+                   )
+                 )
+                (ViewSpec
+                   name: 'rightView'
+                   component: 
+                  (SpecCollection
+                     collection: (
+                      (MenuPanelSpec
+                         name: 'ToolBar1'
+                         layout: (LayoutFrame 0 0 0 0.0 28 0 0 1.0)
+                         level: 1
+                         menu: toolsMenuToolbar
+                         verticalLayout: true
+                         centerItems: true
+                         textDefault: true
+                       )
+                      (ViewSpec
+                         name: 'editingView'
+                         layout: (LayoutFrame 28 0.0 0 0.0 0 1.0 0 1.0)
+                         level: 1
+                         component: 
+                        (SpecCollection
+                           collection: (
+                            (ArbitraryComponentSpec
+                               name: 'imageEditView'
+                               layout: (LayoutFrame 2 0.0 2 0.0 -2 1.0 -24 1.0)
+                               hasHorizontalScrollBar: true
+                               hasVerticalScrollBar: true
+                               hasBorder: false
+                               component: ImageEditView
+                             )
+                            (LabelSpec
+                               name: 'coordLabel'
+                               layout: (LayoutFrame 2 0.0 -22 1 -83 1.0 0 1.0)
+                               level: -1
+                               translateLabel: true
+                               labelChannel: imageInfoHolder
+                               resizeForLabel: false
+                               adjust: left
                              )
-                           
-                          )
-                        )
+                            (ArrowButtonSpec
+                               name: 'magnifyDownButton'
+                               layout: (LayoutFrame -80 1 -22 1 -58 1 0 1)
+                               activeHelpKey: magnifyImageDown
+                               translateLabel: true
+                               model: doMagnifyDown
+                               enableChannel: imageIsLoadedHolder
+                               isTriggerOnDown: true
+                               direction: left
+                             )
+                            (ArrowButtonSpec
+                               name: 'magnifyUpButton'
+                               layout: (LayoutFrame -24 1 -22 1 -2 1 0 1)
+                               activeHelpKey: magnifyImageUp
+                               translateLabel: true
+                               model: doMagnifyUp
+                               enableChannel: imageIsLoadedHolder
+                               isTriggerOnDown: true
+                               direction: right
+                             )
+                            (InputFieldSpec
+                               name: 'magnificationInputField'
+                               layout: (LayoutFrame -57 1 -22 1 -26 1 0 1)
+                               activeHelpKey: magnificationNumber
+                               enableChannel: imageIsLoadedHolder
+                               model: magnificationHolder
+                               type: numberInRange
+                               acceptOnReturn: true
+                               acceptOnTab: true
+                               numChars: 2
+                               minValue: 1
+                               maxValue: 99
+                               acceptOnPointerLeave: true
+                             )
+                            )
+                          
+                         )
                        )
-                     
-                    )
-                  )
+                      )
+                    
+                   )
                  )
-               
-              )
-              handles: (Any 0.288889 1.0)
-            )
-           (UISubSpecification
-              name: 'infoBarSubSpec'
-              layout: (LayoutFrame 0 0.0 -24 1 0 1.0 0 1.0)
-              majorKey: ToolApplicationModel
-              minorKey: windowSpecForInfoBar
-            )
+                )
+              
+             )
+             handles: (Any 0.28888900000000006 1.0)
            )
-         
-        )
-      )
-
-    "Modified: / 04-07-2010 / 10:18:33 / cg"
+          (UISubSpecification
+             name: 'infoBarSubSpec'
+             layout: (LayoutFrame 0 0.0 -24 1 0 1.0 0 1.0)
+             majorKey: ToolApplicationModel
+             minorKey: windowSpecForInfoBar
+           )
+          )
+        
+       )
+     )
 ! !
 
 !ImageEditor class methodsFor:'menu specs'!
@@ -2070,21 +2072,21 @@
             label: '-'
           )
          (MenuItem
-            enabled: hasColormapAndColorSelected
+            enabled: hasColormapAndSingleColorSelected
             label: 'Cut Color'
             itemValue: cutColorFromColormap
             translateLabel: true
             isVisible: false
           )
          (MenuItem
-            enabled: hasColorSelectedHolder
+            enabled: hasSingleColorSelectedHolder
             label: 'Copy Color'
             itemValue: copyColorFromColormap
             translateLabel: true
             shortcutKey: Copy
           )
          (MenuItem
-            enabled: hasColormapAndColorSelected
+            enabled: hasColormapAndSingleColorSelected
             label: 'Pick and Paste Color...'
             itemValue: pickAndPasteColor
             translateLabel: true
@@ -2099,7 +2101,7 @@
             label: '-'
           )
          (MenuItem
-            enabled: hasColormapAndColorSelected
+            enabled: hasColormapAndSingleColorSelected
             label: 'Edit Color...'
             itemValue: editSelectedColor
             translateLabel: true
@@ -2123,10 +2125,16 @@
             translateLabel: true
           )
          (MenuItem
+            enabled: hasColormapAndColorSelected
+            label: 'Color Shift'
+            itemValue: makeSelectedColorShifted
+            translateLabel: true
+          )
+         (MenuItem
             label: '-'
           )
          (MenuItem
-            enabled: hasColorSelectedHolder
+            enabled: hasSingleColorSelectedHolder
             label: 'Inspect Color'
             itemValue: inspectColor
             translateLabel: true
@@ -3726,7 +3734,7 @@
 !
 
 hasColorSelectedHolder
-    ^ [ self selectedColorIndexOrNil notNil ]
+    ^ [ self selectedColors value notEmptyOrNil "self selectedColorIndexOrNil notNil" ]
 
     "Created: / 04-07-2010 / 10:12:22 / cg"
 !
@@ -3744,12 +3752,24 @@
     "Modified: / 04-07-2010 / 10:13:13 / cg"
 !
 
+hasColormapAndSingleColorSelected
+    ^ [ self hasColormapHolder value and:[self hasSingleColorSelectedHolder value]]
+
+    "Modified: / 04-07-2010 / 10:13:13 / cg"
+!
+
 hasColormapHolder
     ^ [self hasColormap]
 
     "Created: / 04-07-2010 / 10:13:05 / cg"
 !
 
+hasSingleColorSelectedHolder
+    ^ [ self selectedColors value size == 1 "self selectedColorIndexOrNil notNil" ]
+
+    "Created: / 04-07-2010 / 10:12:22 / cg"
+!
+
 imageHasImageSequence
     |img|
 
@@ -3898,6 +3918,18 @@
     "Created: / 04-07-2010 / 10:19:34 / cg"
 !
 
+selectedColors
+    "returns a valueHolder for the current set of selected colors."
+
+    |holder|
+
+    (holder := builder bindingAt:#selectedColors) isNil ifTrue:[
+        builder aspectAt:#selectedColors put:(holder := nil asValue).
+        holder onChangeSend:#selectedColorsChanged to:self.
+    ].
+    ^ holder
+!
+
 selectionOfColor
     "returns a valueHolder for the current selection of the edit color.
      Here, an AspectAdaptor which accesses selectedColorIndex is returned."
@@ -4006,6 +4038,22 @@
     "Modified: / 18-01-2012 / 13:58:38 / cg"
 !
 
+selectedColorsChanged
+    |colorIndices|
+
+    (colorIndices := self selectedColors value) isEmptyOrNil ifTrue:[
+        self selectionOfColor value:nil
+    ] ifFalse:[
+        colorIndices size == 1 ifTrue:[
+            "/ as single color selected
+            self selectionOfColor value:colorIndices first
+        ] ifFalse:[
+            "/ multipl selected
+            self selectionOfColor value:nil
+        ].
+    ].
+!
+
 update:something with:aParameter from:changedObject
     |clrIndex img imagePreView clr changedColor|
 
@@ -4090,7 +4138,7 @@
             ] ifFalse:[
                 clrIndex := self listOfColors indexOf:aParameter.
             ].
-            self selectionOfColor value:clrIndex.
+            self selectedColors value:{clrIndex}. "/ selectionOfColor value:clrIndex.
             ^ self.
         ].
         ^ self.
@@ -4266,7 +4314,7 @@
             self updateImage.
             self updateImagePreView.
 
-            self selectionOfColor value:oldSel.
+            self selectedColors value:{oldSel}.
             imageEditView selectedColorIndex:oldSel.
             imageEditView selectedColor:(self listOfColors at:oldSel).
         ]
@@ -4569,7 +4617,7 @@
         mouseKeyColorMode := 1 asValue.
         mouseKeyColorMode onChangeEvaluate: [
             imageEditView mouseKeyColorMode:mouseKeyColorMode value. 
-            self selectionOfColor value: (self listOfColors indexOf:imageEditView selectedColor).
+            self selectedColors value:{ self listOfColors indexOf:imageEditView selectedColor }.
         ]
     ].
 
@@ -4614,6 +4662,38 @@
 !
 
 sortBlockForColors
+    ^ self sortBlockForColorsByHLS.
+"/    ^ self sortBlockForColorsByRGB
+!
+
+sortBlockForColorsByHLS
+    ^ [:a :b |
+        |h1 h2 s1 s2 l1 l2|
+
+        h1 := a hue ? 0.
+        h2 := b hue ? 0.
+        (h1 between: h2-30 and:h2+30) ifTrue:[
+            l1 := a light.
+            l2 := b light.
+            l1 = l2 ifTrue:[
+                a saturation < b saturation
+            ] ifFalse:[
+                l1 < l2
+            ].
+"/            s1 := a saturation.
+"/            s2 := b saturation.
+"/            s1 = s2 ifTrue:[
+"/                a light < b light
+"/            ] ifFalse:[
+"/                s1 < s2 
+"/            ]
+        ] ifFalse:[
+            h1 < h2 
+        ]
+      ]
+!
+
+sortBlockForColorsByRGB
     ^ [:a :b |
             a redByte == b redByte ifTrue:[
                 a greenByte == b greenByte ifTrue:[
@@ -4808,8 +4888,7 @@
         ].
         drawingColormap add:newColor.
         self listOfColors contents:drawingColormap.
-        self selectionOfColor value:(drawingColormap size).
-
+        self selectedColors value:{drawingColormap size}.
         "/ self warn:'Image has no colormap.\Change colorMap mode first.' withCRs.
         ^ self
     ].
@@ -4859,7 +4938,7 @@
         listOfColors size > (oldCListSize + 1) ifTrue:[
             listOfColors removeLast
         ].
-        self selectionOfColor value:(listOfColors size).
+        self selectedColors value:{listOfColors size}.
         self updateLabelsAndHistory.
     ]
 
@@ -4985,6 +5064,140 @@
     ].
 !
 
+changeHLSOfColors:colorsToShift
+    "interactive Hue/Light/Saturation editing"
+
+    |bindings hueShift lightValue saturationValue originalColormap firstChange acceptChannel 
+     shiftAction avgColorHolder avgColor shiftedColor shiftProcess readySema
+     originalPixels p previewImage previewImageHolder originalPreviewColormap originalPreviewPixels
+     anyChange |
+
+    "/ compute the averageColor in the background (while asking user)
+    avgColorHolder := nil asValue.
+    previewImageHolder := nil asValue.
+
+    readySema := Semaphore new.
+    [
+        |image red green blue|
+
+        image := imageEditView image.
+        originalColormap := image colorMap copy.
+        originalPixels := image bits.
+        red := (colorsToShift collect:[:clr | clr red]) average.
+        green := (colorsToShift collect:[:clr | clr green]) average.
+        blue := (colorsToShift collect:[:clr | clr blue]) average.
+        avgColor := Color red:red green:green blue:blue.
+        avgColorHolder value:avgColor.
+
+        previewImage := self image magnifiedPreservingRatioTo:100@100.
+        previewImageHolder value: previewImage.
+        originalPreviewColormap := previewImage colorMap copy.
+        originalPreviewPixels := previewImage bits.
+
+        readySema signal.
+    ] forkAt:7.
+
+    acceptChannel := TriggerValue new.
+
+    firstChange := true.
+    anyChange := false.
+
+    shiftedColor := [:clr :hShift :lFactor :sFactor |
+                        Color 
+                                hue:((clr hue) ? 0 + hShift) 
+                                light:((clr light * lFactor / 100) min:100)
+                                saturation:((clr saturation * sFactor / 100) min:100)].
+
+    shiftAction := 
+        [
+            |hShift lFactor sFactor|
+
+            acceptChannel value:true.
+
+            firstChange ifTrue:[
+                imageEditView makeUndo.
+                firstChange := false.
+                anyChange := true.
+            ].
+            readySema notNil ifTrue:[readySema wait. readySema := nil].
+
+            hShift := hueShift value.
+            lFactor := lightValue value.
+            sFactor := saturationValue value.
+
+            avgColorHolder value:(shiftedColor value:avgColor value:hShift value:lFactor value:sFactor).
+
+            previewImage
+                colorMap:originalPreviewColormap copy;
+                bits:originalPreviewPixels copy;
+                release;
+                colorMapProcessing:[:clr | 
+                    (colorsToShift includes:clr) ifTrue:[
+                        shiftedColor value:clr value:hShift value:lFactor value:sFactor.
+                    ] ifFalse:[
+                        clr
+                    ]
+                ].
+            previewImageHolder value:nil; value:previewImage.
+
+            shiftProcess notNil ifTrue:[
+                shiftProcess terminate.
+                shiftProcess waitUntilTerminated.
+                shiftProcess := nil.
+            ].
+
+            shiftProcess := 
+                [
+                    [
+                        imageEditView image 
+                            colorMap:originalColormap copy;
+                            bits:originalPixels copy;
+                            release;
+                            colorMapProcessing:[:clr | 
+                                (colorsToShift includes:clr) ifTrue:[
+                                    shiftedColor value:clr value:hShift value:lFactor value:sFactor.
+                                ] ifFalse:[
+                                    clr
+                                ]
+                            ].
+                        self updateImage.
+                        self updateInfoLabel.
+                        self updateImagePreView.
+                    ] ensure:[ shiftProcess := nil ].    
+                ] forkAt:7.
+        ].
+
+    bindings := IdentityDictionary new.
+    bindings at:#hueShiftAmount put:(hueShift := 0 asValue).
+    hueShift onChangeEvaluate:shiftAction.
+
+    bindings at:#lightAmount put:(lightValue := 100 asValue).
+    lightValue onChangeEvaluate:shiftAction.
+
+    bindings at:#saturationAmount put:(saturationValue := 100 asValue).
+    saturationValue onChangeEvaluate:shiftAction.
+
+    bindings at:#acceptChannel put:acceptChannel.
+    bindings at:#hlsColor put:avgColorHolder.
+    bindings at:#previewImageHolder put:previewImageHolder.
+
+    (self openDialogInterface:#changeHLSDialogSpec withBindings:bindings) 
+    ifFalse:[ 
+        anyChange ifTrue:[
+            imageEditView undo
+        ]
+    ].
+
+    (p := shiftProcess) notNil ifTrue:[
+        p waitUntilTerminated.
+    ].
+
+    anyChange ifTrue:[
+        self updateImage.
+        self updateImagePreView.
+    ].
+!
+
 clearColormapEntry0AndMaskedPixels
     "ensure that there is a colorMap entry with 0/0/0 at position
      0 and then clear all masked pixels (to pixelValue 0).
@@ -5543,15 +5756,27 @@
 !
 
 makeSelectedColorBrighter
-    self processSelectedColorWith:[:clr | clr lightened]
+    self processSelectedColorsWith:[:clr | clr lightened]
 !
 
 makeSelectedColorDarker
-    self processSelectedColorWith:[:clr | clr darkened]
+    self processSelectedColorsWith:[:clr | clr darkened]
 !
 
 makeSelectedColorGray
-    self processSelectedColorWith:[:clr | Color brightness:(clr brightness)]
+    self processSelectedColorsWith:[:clr | Color brightness:(clr brightness)]
+!
+
+makeSelectedColorShifted
+    |cMap colors|
+
+    cMap := self image colorMap.
+    self hasMask ifTrue:[
+        colors := self selectedColors value collect:[:idx | cMap at:idx-1].
+    ] ifFalse:[
+        colors := self selectedColors value collect:[:idx | cMap at:idx].
+    ].
+    self changeHLSOfColors:colors.
 !
 
 makeSlightlyBrighter
@@ -5711,13 +5936,21 @@
 !
 
 processSelectedColorWith:aBlock
-    "undoable color processing: the selected color will be replaced by the
-     value of aBlock"
-
-    |img cMap modifiedColormap oldColor newImage selectedColorIndex oldSelection newColor|
-
-    selectedColorIndex := self selectedColorIndexOrNil.
-    selectedColorIndex isNil ifTrue:[^ self].
+    "undoable color processing: 
+     the selected color will be replaced by the value of aBlock"
+
+    self processSelectedColorsWith:aBlock.
+!
+
+processSelectedColorsWith:aBlock
+    "undoable color processing: 
+     the selected colors will be replaced by the value of aBlock 
+     (which gets a color vector and must return a color vector)"
+
+    |img cMap modifiedColormap oldColors newImage selectedColorIndices newColors|
+
+    selectedColorIndices := self selectedColors value.
+    selectedColorIndices isEmptyOrNil ifTrue:[^ self].
 
     img := self image.
     cMap := img colorMap.
@@ -5725,14 +5958,19 @@
         self warn:(resources stringWithCRs:'Image has no colormap.\Please change the colorMap mode first.').
         ^ self
     ].
-
-    oldColor := cMap at:selectedColorIndex.
+    self hasMask ifTrue:[
+        oldColors := selectedColorIndices collect:[:idx | cMap at:idx-1].
+    ] ifFalse:[
+        oldColors := selectedColorIndices collect:[:idx | cMap at:idx].
+    ].
     imageEditView makeUndo.
 
     modifiedColormap := cMap asNewArray.
 
-    newColor := aBlock value:oldColor.
-    modifiedColormap at:selectedColorIndex put:newColor.
+    newColors := oldColors collect:aBlock.
+    selectedColorIndices with:newColors do:[:idx :newColor | 
+        modifiedColormap at:idx put:newColor
+    ].
 
     newImage := img species new
                     width:img width
@@ -5744,15 +5982,10 @@
     newImage fileName:img fileName.
     newImage mask:(img mask copy).
 
-    oldSelection := self selectionOfColor value.
-
     (imageEditView image:newImage) notNil ifTrue:[
         self fetchImageData.
     ].
-    self selectionOfColor value:oldSelection.
-
-    "Created: / 12.3.1999 / 00:20:28 / cg"
-    "Modified: / 16.3.1999 / 21:57:26 / cg"
+    self selectedColors value:selectedColorIndices.
 !
 
 reduceNumberOfColors
@@ -5809,19 +6042,11 @@
     ].
 
     self withExecuteCursorDo:[
-        image := self image.
-        "/ usedColors := image usedColorsMax:4096.
-        imageEditView makeUndo.
-
-        newImage := image copy.
-        newImage photometric == #palette ifTrue:[
-            newImage colorMap:(OrderedCollection new).
-        ].
-
-        image
-            colorsFromX:0 y:0 toX:(image width-1) y:(image height-1) 
-            do:[:x :y :clr |
-                |r g b nr ng nb newClr|
+        |reduceColor|
+
+        reduceColor :=
+            [:clr |
+                |r g b nr ng nb|
 
                 r := clr redByte.
                 g := clr greenByte.
@@ -5830,15 +6055,23 @@
                 ng := (g roundTo:rndG) min:255.
                 nb := (b roundTo:rndB) min:255. 
 
-                newClr := Color redByte:nr greenByte:ng blueByte:nb.
-                newImage photometric == #palette ifTrue:[
-                    (newImage colorMap includes:newClr) ifFalse:[
-                        newImage colorMap add:newClr
-                    ].
+                Color redByte:nr greenByte:ng blueByte:nb.
+            ].
+
+        image := self image.
+        "/ usedColors := image usedColorsMax:4096.
+        imageEditView makeUndo.
+
+        newImage := image copy.
+        newImage photometric == #palette ifTrue:[
+            newImage colorMap:(image colorMap collect:reduceColor).
+        ] ifFalse:[
+            image
+                colorsFromX:0 y:0 toX:(image width-1) y:(image height-1) 
+                do:[:x :y :clr |
+                    newImage colorAtX:x y:y put:(reduceColor value:clr)
                 ].
-                newImage colorAtX:x y:y put:newClr
-            ].
-
+        ].
         imageEditView image:newImage.
         imageEditView setModified.
         self updateImage.
@@ -5886,7 +6119,7 @@
             ].
         ].
     ].
-    self selectionOfColor value:idx.
+    self selectedColors value:{idx}.
 
     "Modified: / 02-07-2010 / 12:06:07 / cg"
 !
@@ -5932,7 +6165,7 @@
 sortColorMap
     "calculates a new color map for the image, sorting colors"
 
-    self sortColorMapWith:self sortBlockForColors
+    self sortColorMapWith:self sortBlockForColorsByRGB
 !
 
 sortColorMapWith:sortBlock