#UI_ENHANCEMENT by cg draft
authorClaus Gittinger <cg@exept.de>
Fri, 01 Sep 2017 10:17:35 +0200
changeset 3476 2854769b212a
parent 3475 7eafbd0cad40
child 3477 52202af265c2
#UI_ENHANCEMENT by cg class: ImageEditor added: #addColorToColormap:undoable: #doInvertedBitsImage #makeInvertedBits #makeNegative #nonUndoableClearColormapEntry0AndMaskedPixels #nonUndoableClearMaskedPixels #nonUndoableCompressColorMap #nonUndoableSortColorMapWith: #updateImageAfterDoing: comment/format in: #addColorToColormap: #do3DProjection #sortColorMapWith: changed:19 methods class: ImageEditor class changed: #menuColors
ImageEditor.st
--- a/ImageEditor.st	Thu Aug 31 21:39:41 2017 +0200
+++ b/ImageEditor.st	Fri Sep 01 10:17:35 2017 +0200
@@ -2850,6 +2850,7 @@
     "Do not manually edit this!! If it is corrupted,
      the MenuEditor may not be able to read the specification."
 
+
     "
      MenuEditor new openOnClass:ImageEditor andSelector:#menuColors
      (Menu new fromLiteralArrayEncoding:(ImageEditor menuColors)) startUp
@@ -2861,160 +2862,145 @@
      #(Menu
         (
          (MenuItem
-            "/ enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
             enabled: imageIsLoaded
             label: 'Depth'
-            translateLabel: true
             submenu: 
            (Menu
               (
                (MenuItem
                   activeHelpKey: colorMap1
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '1-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth1
                   choice: colorMapMode
                   choiceValue: depth1
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap1M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '1-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked1
                   choice: colorMapMode
                   choiceValue: masked1
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap2
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '2-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth2
                   choice: colorMapMode
                   choiceValue: depth2
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap2M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '2-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked2
                   choice: colorMapMode
                   choiceValue: masked2
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap4
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '4-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth4
                   choice: colorMapMode
                   choiceValue: depth4
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap4M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '4-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked4
                   choice: colorMapMode
                   choiceValue: masked4
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap8
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '8-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth8
                   choice: colorMapMode
                   choiceValue: depth8
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap8M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '8-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked8
                   choice: colorMapMode
                   choiceValue: masked8
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap16
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '16-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth16
                   choice: colorMapMode
                   choiceValue: depth16
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap16M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '16-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked16
                   choice: colorMapMode
                   choiceValue: masked16
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap24
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '24-Plane'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth24
                   choice: colorMapMode
                   choiceValue: depth24
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   activeHelpKey: colorMap24M
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '24-Plane + Mask'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: masked24
                   choice: colorMapMode
                   choiceValue: masked24
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                (MenuItem
                   label: '-'
                 )
                (MenuItem
                   activeHelpKey: colorMap32
+                  enabled: imageIsLoadedAndNotReadonlyHolder
                   label: '32-Plane (rgba)'
                   itemValue: colorMapMode:
-                  translateLabel: true
                   argument: depth32
                   choice: colorMapMode
                   choiceValue: depth32
-            enabled: imageIsLoadedAndNotReadonlyHolder
                 )
                )
               nil
@@ -3024,7 +3010,6 @@
          (MenuItem
             enabled: imageIsLoadedAndNotReadonlyHolder
             label: 'ColorMap'
-            translateLabel: true
             submenu: 
            (Menu
               (
@@ -3033,44 +3018,19 @@
                   enabled: hasColormapHolder
                   label: 'Compress Colormap'
                   itemValue: #'menu_compressColorMap'
-                  translateLabel: true
                 )
                (MenuItem
                   enabled: hasColormapHolder
                   label: 'Sort Colormap'
                   itemValue: #'menu_sortColorMap'
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Reduce Number of Colors by Rounding...'
                   itemValue: reduceNumberOfColors2
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Reduce Number of Colors by Masking Bits...'
                   itemValue: reduceNumberOfColors
-                  translateLabel: true
-                )
-               (MenuItem
-                  label: '-'
-                )
-               (MenuItem
-                  enabled: imageIsLoadedHolder
-                  label: 'Brighten'
-                  itemValue: doBrightenImage
-                  translateLabel: true
-                )
-               (MenuItem
-                  enabled: imageIsLoadedHolder
-                  label: 'Darken'
-                  itemValue: doDarkenImage
-                  translateLabel: true
-                )
-               (MenuItem
-                  enabled: imageIsLoadedHolder
-                  label: 'Invert'
-                  itemValue: doNegativeImage
-                  translateLabel: true
                 )
                )
               nil
@@ -3080,24 +3040,17 @@
          (MenuItem
             enabled: imageIsLoadedAndNotReadonlyHolder
             label: 'Process'
-            translateLabel: true
             submenu: 
            (Menu
               (
                (MenuItem
-                  label: 'Make GrayScale'
-                  itemValue: makeGrayScaleImage
-                  translateLabel: true
+                  label: 'Negative'
+                  itemValue: makeNegative
                 )
                (MenuItem
-                  label: 'Make Monochrome...'
-                  itemValue: makeMonochromeImage
-                  translateLabel: true
-                )
-               (MenuItem
-                  label: 'Make Inverse'
-                  itemValue: makeInverse
-                  translateLabel: true
+                  label: 'Invert Pixel Bits'
+                  itemValue: makeInvertedBits
+                  isVisible: hasColormap
                 )
                (MenuItem
                   label: '-'
@@ -3106,28 +3059,34 @@
                   enabled: allowedToChangeImageDimensionAndDepth
                   label: 'Make dithered 8Bit Palette'
                   itemValue: makeDitheredPaletteImage
-                  translateLabel: true
                   isVisible: false
                 )
                (MenuItem
                   label: 'Dither to Depth...'
                   itemValue: ditherToDepth
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Threshold to Depth...'
                   itemValue: thresholdToDepth
-                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Make Monochrome...'
+                  itemValue: makeMonochromeImage
+                )
+               (MenuItem
+                  label: 'Make GrayScale (same Depth)'
+                  itemValue: makeGrayScaleImage
                 )
                (MenuItem
                   label: 'Make GrayScale with Depth (Dither)...'
                   itemValue: ditherGrayToDepth
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Make GrayScale with Depth (Threshold)...'
                   itemValue: thresholdGrayToDepth
-                  translateLabel: true
                 )
                (MenuItem
                   label: '-'
@@ -3135,25 +3094,18 @@
                (MenuItem
                   label: 'Make Slightly Brighter'
                   itemValue: makeSlightlyBrighter
-                  translateLabel: true
+                )
+               (MenuItem
+                  label: 'Make Brighter'
+                  itemValue: makeBrighter
                 )
                (MenuItem
                   label: 'Make Slightly Darker'
                   itemValue: makeSlightlyDarker
-                  translateLabel: true
-                )
-               (MenuItem
-                  label: '-'
-                )
-               (MenuItem
-                  label: 'Make Brighter'
-                  itemValue: makeBrighter
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Make Darker'
                   itemValue: makeDarker
-                  translateLabel: true
                 )
                (MenuItem
                   label: '-'
@@ -3161,12 +3113,20 @@
                (MenuItem
                   label: 'Change HLS...'
                   itemValue: changeHLS
-                  translateLabel: true
                 )
                (MenuItem
                   label: 'Colorize...'
                   itemValue: colorize
-                  translateLabel: true
+                )
+               (MenuItem
+                  enabled: imageIsLoadedHolder
+                  label: 'Brighten'
+                  itemValue: doBrightenImage
+                )
+               (MenuItem
+                  enabled: imageIsLoadedHolder
+                  label: 'Darken'
+                  itemValue: doDarkenImage
                 )
                )
               nil
@@ -3176,7 +3136,6 @@
          (MenuItem
             enabled: imageIsLoadedAndNotReadonlyHolder
             label: 'Mask'
-            translateLabel: true
             submenu: 
            (Menu
               (
@@ -3185,26 +3144,22 @@
                   enabled: hasMask
                   label: 'Copy Mask'
                   itemValue: #'menu_copyMask'
-                  translateLabel: true
                 )
                (MenuItem
                   activeHelpKey: pasteMask
                   enabled: hasMask
                   label: 'Paste Mask'
                   itemValue: #'menu_pasteMask'
-                  translateLabel: true
                 )
                (MenuItem
                   enabled: hasMask
                   label: 'Clear Masked Pixels'
                   itemValue: #'menu_clearMaskedPixels'
-                  translateLabel: true
                 )
                (MenuItem
                   enabled: hasMask
                   label: 'Clear Colormap Entry for Masked Pixels'
                   itemValue: #'menu_clearColormapEntry0AndMaskedPixels'
-                  translateLabel: true
                 )
                )
               nil
@@ -3216,7 +3171,7 @@
         nil
       )
 
-    "Modified: / 30-08-2017 / 00:35:31 / cg"
+    "Modified: / 31-08-2017 / 14:43:51 / cg"
 !
 
 menuEdit
@@ -4588,9 +4543,9 @@
 !
 
 hasColormapAndColorSelected
-    ^ [ self hasColormapHolder value and:[self hasColorSelectedHolder value]]
-
-    "Modified: / 04-07-2010 / 10:13:13 / cg"
+    ^ [ self hasColormap and:[self hasColorSelectedHolder value]]
+
+    "Modified: / 31-08-2017 / 14:08:20 / cg"
 !
 
 hasColormapAndSingleColorSelected
@@ -5129,7 +5084,7 @@
 updateLabelsAndHistory
     "updates labels and history, if something has changed"
 
-    |image|
+    |image rsrcClass rsrcSelector imgFile|
 
     image := self image.
 
@@ -5138,17 +5093,16 @@
 
     self updateInfoLabel.
 
-    imageEditView resourceClass notNil ifTrue:[
-        imageEditView resourceSelector notNil ifTrue:[
-            self addHistoryEntryForClass:imageEditView resourceClass selector:imageEditView resourceSelector.
-        ]
-    ].
-
-    image fileName notNil ifTrue: [
-        self addHistoryEntryForFile:image fileName.
-    ].
-
-    "Modified: / 04-07-2010 / 10:16:02 / cg"
+    ((rsrcClass := imageEditView resourceClass) notNil 
+    and:[ (rsrcSelector := imageEditView resourceSelector) notNil ]) ifTrue:[
+        self addHistoryEntryForClass:rsrcClass selector:rsrcSelector.
+    ] ifFalse:[
+        (imgFile := image fileName) notNil ifTrue: [
+            self addHistoryEntryForFile:imgFile.
+        ].
+    ].
+
+    "Modified: / 01-09-2017 / 10:10:12 / cg"
 !
 
 updateListOfColorsAndColormapMode
@@ -5984,13 +5938,18 @@
 !ImageEditor methodsFor:'user actions-colormap'!
 
 addColorToColormap
-    self addColorToColormap:(Color black)
+    "undoable: add black (a new color) to the map"
+    
+    self addColorToColormap:(Color black) undoable:true
+
+    "Modified: / 31-08-2017 / 14:30:32 / cg"
 !
 
 addColorToColormap:newColor
-    "when editing a palette image, the new color is added to the images colorMap
+    "undoable
+     when editing a palette image, the new color is added to the image's colorMap
      (unless it is full).
-     when editing a true-color image, it is added to my own list-of-colors,
+     When editing a true-color image, it is added to my own list-of-colors,
      which only holds drawing colors, but is not the colormap's image"
      
     |depth img cMap newColorMap newImage oldCListSize newMode listOfColors|
@@ -6063,6 +6022,84 @@
 
     "Created: / 12-03-1999 / 00:20:28 / cg"
     "Modified: / 16-02-2017 / 10:17:25 / cg"
+    "Modified (comment): / 31-08-2017 / 14:28:11 / cg"
+!
+
+addColorToColormap:newColor undoable:undoable
+    "when editing a palette image, the new color is added to the image's colorMap
+     (unless it is full).
+     When editing a true-color image, it is added to my own list-of-colors,
+     which only holds drawing colors, but is not the colormap's image"
+     
+    |depth img cMap newColorMap newImage oldCListSize newMode listOfColors|
+
+    img := self image.
+    img isNil ifTrue:[
+        self warn:'No Image.'.
+        ^ self
+    ].
+
+    depth := img depth.
+    cMap := img colorMap.
+    (cMap isNil or:[cMap isMappedPalette or:[cMap isFixedPalette]]) ifTrue:[
+        drawingColormap isNil ifTrue:[
+            self information:(resources stringWithCRs:'Image has no colormap.\The shown colorMap is for drawing only.').
+            drawingColormap := OrderedCollection new.
+        ].
+        drawingColormap add:newColor.
+        self listOfColors contents:drawingColormap.
+        self selectedColors value:{drawingColormap size}.
+        "/ self warn:'Image has no colormap.\Change colorMap mode first.' withCRs.
+        ^ self
+    ].
+
+    (cMap size == (1 bitShift:depth)) ifTrue:[
+        depth >= 8 ifTrue:[
+            self warn:'No space for more colors in colormap.'.
+            ^ self
+        ].
+        (self confirm:(resources stringWithCRs:'No space for more colors in colormap.\Change depth ?'))
+        ifFalse:[
+            ^ self
+        ].
+
+        undoable ifTrue:[ imageEditView makeUndo ].
+        img mask notNil ifTrue:[
+            newMode := 'masked' , (depth*2) printString.
+        ] ifFalse:[
+            newMode := 'depth' , (depth*2) printString.
+        ].
+        self colorMapMode:newMode.
+    ] ifFalse:[
+        undoable ifTrue:[ imageEditView makeUndo ].
+    ].
+
+    cMap := cMap asArray.
+    listOfColors := self listOfColors.
+    oldCListSize := listOfColors size.
+
+    newColorMap := cMap copyWith:newColor.
+
+    newImage := img species new
+                    width:(img width) height:(img height) depth:depth
+                    fromArray:img bits.
+
+    newImage colorMap:newColorMap.  
+    newImage fileName:img fileName.
+    newImage mask:(img mask copy).
+
+    (imageEditView image:newImage) notNil ifTrue:[
+        listOfColors contents: newImage colorMap.
+        self findColorMapMode.
+        "/ mhmh - somehow, we get two colors added ... (sigh findColorMapMode adds another one ...)
+        listOfColors size > (oldCListSize + 1) ifTrue:[
+            listOfColors removeLast
+        ].
+        self selectedColors value:{listOfColors size}.
+        self updateLabelsAndHistory.
+    ]
+
+    "Created: / 31-08-2017 / 14:30:01 / cg"
 !
 
 changeHLS
@@ -6334,18 +6371,21 @@
 
     |index colorMap| 
 
-    self compressColorMap.
+    self nonUndoableCompressColorMap.
+    
     colorMap := self image colorMap.
     (colorMap includes:(Color black)) ifFalse:[
-        self addColorToColormap:(Color black).
+        self addColorToColormap:(Color black) undoable:false.
         colorMap := self image colorMap.
     ].
     index := colorMap indexOf:(Color black).
     index == 1 ifFalse:[
-        self sortColorMap.
+        self nonUndoableSortColorMapWith:self sortBlockForColorsByRGB.
         colorMap := self image colorMap.
     ].
-    self clearMaskedPixels
+    self nonUndoableClearMaskedPixels
+
+    "Modified: / 31-08-2017 / 14:30:28 / cg"
 !
 
 clearMaskedPixels
@@ -6471,7 +6511,9 @@
                             ] ifFalse:[
                                 colorMapMode value:prevMode.
                                 self findColorMapMode.    
-                                self warn:('Too many used colors in image (', oldImage usedColors size printString , ').').
+                                self warn:(resources 
+                                            stringWithCRs:'Too many used colors (%1) in image.\\You should choose one of:\\- convert the image to grey\- reduce the number of colors\- dither to depth\- choose another depth'
+                                            with: oldImage realUsedColors size ).
                                 ^ self
     "/                            (self confirm:('Too many used colors in image (', oldImage usedColors size printString , ').\\Dither ?' withCRs))
     "/                            ifFalse:[.
@@ -6636,7 +6678,7 @@
         ]
     ]
 
-    "Modified: / 24-08-2017 / 18:16:02 / cg"
+    "Modified: / 31-08-2017 / 14:41:06 / cg"
 !
 
 colorize
@@ -6808,55 +6850,21 @@
 !
 
 makeBrighter
-    | anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeBrighter.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
+    self updateImageAfterDoing:#makeBrighter.
+
+    "Modified: / 31-08-2017 / 12:16:30 / cg"
 !
 
 makeDarker
-    | anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeDarker.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
+    self updateImageAfterDoing:#makeDarker.
+
+    "Modified: / 31-08-2017 / 12:16:24 / cg"
 !
 
 makeGrayScaleImage
-    |anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeGrayScaleImage.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
-!
-
-makeInverse
-    | anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeInverse.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
+    self updateImageAfterDoing:#makeGrayScaleImage.
+
+    "Modified: / 31-08-2017 / 12:16:15 / cg"
 !
 
 makeSelectedColorBrighter
@@ -6872,45 +6880,33 @@
 !
 
 makeSelectedColorShifted
-    |cMap colors|
+    "shift the selected color (in the colormap)
+     using the hls/rgb shifting slider dialog"
+     
+    |cMap colors cmapOffset|
 
     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].
-    ].
+    
+    "/ if there is a mask, it is at position 1 in the table
+    cmapOffset := self hasMask ifTrue:[1] ifFalse:[0].
+    colors := self selectedColors value collect:[:idx | cMap at:idx-cmapOffset].
     self changeHLSOfColors:colors.
+
+    "Modified: / 31-08-2017 / 14:15:03 / cg"
 !
 
 makeSlightlyBrighter
-    | anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeSlightlyBrighter.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
+    self updateImageAfterDoing:#makeSlightlyBrighter.
 
     "Created: / 24-11-2010 / 11:06:11 / cg"
+    "Modified: / 31-08-2017 / 12:16:01 / cg"
 !
 
 makeSlightlyDarker
-    | anyChange|
-
-    self withExecuteCursorDo:[
-        anyChange := imageEditView makeSlightlyDarker.
-        anyChange ifFalse:[
-            Dialog warn:'Image unchanged'.
-        ] ifTrue:[
-            self updateImage.
-        ]
-    ].
+    self updateImageAfterDoing:#makeSlightlyDarker.
 
     "Created: / 24-11-2010 / 11:06:23 / cg"
+    "Modified: / 31-08-2017 / 12:15:55 / cg"
 !
 
 menu_clearColormapEntry0AndMaskedPixels
@@ -6919,18 +6915,21 @@
 
     imageEditView makeUndo.
     self withExecuteCursorDo:[
-        self clearColormapEntry0AndMaskedPixels
+        self nonUndoableClearColormapEntry0AndMaskedPixels
     ]
+
+    "Modified: / 31-08-2017 / 14:31:21 / cg"
 !
 
 menu_clearMaskedPixels
     "clear all masked pixels (to pixelValue 0)"
 
     imageEditView makeUndo.
-
     self withExecuteCursorDo:[
-        self clearMaskedPixels
+        self nonUndoableClearMaskedPixels
     ]
+
+    "Modified: / 31-08-2017 / 14:23:20 / cg"
 !
 
 menu_compressColorMap
@@ -6953,10 +6952,11 @@
     ].
 
     imageEditView makeUndo.
-
     self withExecuteCursorDo:[
-        self compressColorMap
+        self nonUndoableCompressColorMap
     ]
+
+    "Modified: / 31-08-2017 / 14:22:19 / cg"
 !
 
 menu_copyMask
@@ -7001,13 +7001,12 @@
     ].
 
     imageEditView makeUndo.
-
     self withExecuteCursorDo:[
-        self sortColorMapWith:sortBlock
+        self nonUndoableSortColorMapWith:sortBlock
     ]
 
-    "Modified: / 15.9.1998 / 17:53:32 / cg"
-    "Created: / 30.9.1998 / 23:51:23 / cg"
+    "Created: / 30-09-1998 / 23:51:23 / cg"
+    "Modified: / 31-08-2017 / 14:20:14 / cg"
 !
 
 pasteColorIntoColormap
@@ -7039,7 +7038,9 @@
 !
 
 pickAndAddColorToColormap
-    self addColorToColormap:(Color fromUser)
+    self addColorToColormap:(Color fromUser) undoable:true
+
+    "Modified: / 31-08-2017 / 14:30:22 / cg"
 !
 
 pickAndPasteColor
@@ -7301,11 +7302,161 @@
 sortColorMap
     "calculates a new color map for the image, sorting colors"
 
-    self sortColorMapWith:self sortBlockForColorsByRGB
+    self nonUndoableSortColorMapWith:self sortBlockForColorsByRGB
+
+    "Modified: / 31-08-2017 / 14:20:07 / cg"
 !
 
 sortColorMapWith:sortBlock
-    "calculates a new color map for the image, sorting colors"
+    "warning: not undoable
+     calculates a new color map for the image, sorting colors"
+
+    |depth newColorMap newImage oldImage usedColors oldToNew oldBits newBits tmpBits
+     expectedSize w h| 
+
+    oldImage := self image.
+    depth := oldImage depth.
+    w := oldImage width.
+    h := oldImage height.
+
+    usedColors := oldImage realColorMap.
+
+    "/ translation table
+    oldToNew := ByteArray new:(1 bitShift:depth).
+    newColorMap := usedColors asArray.
+    newColorMap sort:sortBlock.
+
+    oldImage colorMap asArray keysAndValuesDo:[:oldIdx :clr |
+        |newPixel|
+
+        (usedColors includes:clr) ifTrue:[
+            newPixel := newColorMap indexOf:clr.
+            oldToNew at:oldIdx put:newPixel-1.
+        ]
+    ].
+
+    oldBits := oldImage bits.
+    "/ sanity check...
+    expectedSize := ((w * h * depth + 7) // 8).
+    (oldBits size < expectedSize) ifTrue:[
+        self halt:'incorrect pixeldata size'.
+        oldBits := (ByteArray new:expectedSize) replaceFrom:1 with:oldBits; yourself.
+    ].
+    newBits := ByteArray new:(oldBits size).
+    depth ~~ 8 ifTrue:[
+
+        "/ expand/compress can only handle 8bits
+        tmpBits := ByteArray uninitializedNew:(w*h).
+        oldBits
+            expandPixels:depth
+            width:w height:h 
+            into:tmpBits
+            mapping:oldToNew.
+        tmpBits
+            compressPixels:depth 
+            width:w height:h 
+            into:newBits 
+            mapping:nil
+    ] ifFalse:[
+        oldBits
+            expandPixels:depth
+            width:w height:h 
+            into:newBits
+            mapping:oldToNew.
+    ].
+
+    newImage := oldImage species new
+                    width:w height:h depth:depth
+                    fromArray:newBits.
+
+    newImage colorMap:newColorMap.  
+    newImage fileName:oldImage fileName.
+    newImage mask:(oldImage mask copy).
+
+    (imageEditView image:newImage) notNil ifTrue:[
+        self fetchImageData.
+    ]
+
+    "Modified: / 15-09-1998 / 17:53:32 / cg"
+    "Created: / 30-09-1998 / 23:51:23 / cg"
+    "Modified (comment): / 31-08-2017 / 14:19:21 / cg"
+!
+
+updateImageAfterDoing:aBlockOrSelector
+    self withExecuteCursorDo:[
+        aBlockOrSelector value:imageEditView.
+        self updateImage.
+    ].
+
+    "Created: / 31-08-2017 / 12:14:39 / cg"
+! !
+
+!ImageEditor methodsFor:'user actions-colormap-basic'!
+
+nonUndoableClearColormapEntry0AndMaskedPixels
+    "ensure that there is a colorMap entry with 0/0/0 at position
+     0 and then clear all masked pixels (to pixelValue 0).
+     This is required for windows icons to be really transparent"
+
+    |index colorMap| 
+
+    self nonUndoableCompressColorMap.
+    
+    colorMap := self image colorMap.
+    (colorMap includes:(Color black)) ifFalse:[
+        self addColorToColormap:(Color black) undoable:false.
+        colorMap := self image colorMap.
+    ].
+    index := colorMap indexOf:(Color black).
+    index == 1 ifFalse:[
+        self nonUndoableSortColorMapWith:self sortBlockForColorsByRGB.
+        colorMap := self image colorMap.
+    ].
+    self nonUndoableClearMaskedPixels
+
+    "Created: / 31-08-2017 / 14:31:09 / cg"
+!
+
+nonUndoableClearMaskedPixels
+    "clear all masked pixels (to pixelValue 0)"
+
+    |newImage| 
+
+    newImage := self image clearMaskedPixels.
+    0 to:newImage height - 1 do:[:y |
+        0 to:newImage width - 1 do:[:x |
+            (newImage maskAtX:x y:y) == 0 ifTrue:[
+                newImage pixelAtX:x y:y put:0
+            ]
+        ]
+    ].
+
+    (imageEditView image:newImage) notNil ifTrue:[
+        self fetchImageData.
+    ]
+
+    "Created: / 31-08-2017 / 14:22:58 / cg"
+!
+
+nonUndoableCompressColorMap
+    "not undoable
+     calculates a new color map for the image, using only used colors"
+
+    |newImage| 
+
+    newImage := self image.
+    newImage compressColorMap.
+
+    (imageEditView image:newImage) notNil ifTrue:[
+        self fetchImageData.
+    ]
+
+    "Created: / 31-08-2017 / 14:21:51 / cg"
+!
+
+nonUndoableSortColorMapWith:sortBlock
+    "not undoable
+     calculates a new color map for the image, sorting colors"
 
     |depth newColorMap newImage oldImage usedColors oldToNew oldBits newBits tmpBits
      expectedSize w h| 
@@ -7373,8 +7524,7 @@
         self fetchImageData.
     ]
 
-    "Modified: / 15.9.1998 / 17:53:32 / cg"
-    "Created: / 30.9.1998 / 23:51:23 / cg"
+    "Created: / 31-08-2017 / 14:19:42 / cg"
 ! !
 
 !ImageEditor methodsFor:'user actions-editing'!
@@ -7424,6 +7574,623 @@
     "Created: / 20-02-2017 / 18:06:03 / cg"
 !
 
+ditherGrayToDepth
+    self askForDepthThenDo:[:depth |
+        self ditherGrayToDepth:depth
+    ].
+
+    "Created: / 24-08-2017 / 17:49:42 / cg"
+!
+
+ditherGrayToDepth:depth
+    self withExecuteCursorDo:[
+        |newImage|
+
+        depth == 1 ifTrue:[
+            newImage := self image asErrorDitheredMonochromeImage
+        ] ifFalse:[
+            newImage := self image asGrayImageDepth:depth dither:#floydSteinberg.
+        ].
+        imageEditView newImageWithUndo:newImage.
+    ].
+
+    "Created: / 24-08-2017 / 17:51:07 / cg"
+    "Modified: / 30-08-2017 / 01:18:43 / cg"
+!
+
+ditherToDepth
+    self askForDepthThenDo:[:depth |
+        self convertToDepth:depth dither:true
+    ].
+
+    "Created: / 07-07-2006 / 13:22:10 / cg"
+    "Modified: / 30-08-2017 / 00:34:42 / cg"
+!
+
+do3DProjection
+    "make a naive 3D projection;
+     can be used to create those typical marketing images as seen in web pages"
+     
+    |box dx1 dx2 image|
+
+    image := imageEditView image.
+
+    box := EnterBox new.
+    box title:(resources string:'dX1 (0 < dx < 0.5):').
+    box okText:(resources string:'OK').
+    box abortText:(resources string:'Cancel').
+    box initialText:'0.1'.
+    box showAtPointer.
+
+    (box accepted 
+    and: [(dx1 := Number readFrom:(box contents) onError:nil) notNil])
+    ifTrue:[
+        box title:(resources string:'dX2 (0 < dx < 0.5):').
+        box initialText:(dx1 printString).
+        box showAtPointer.
+        (box accepted 
+        and: [(dx2 := Number readFrom:(box contents) onError:nil) notNil])
+        ifTrue:[
+            imageEditView threeDProjection:dx1 and:dx2.
+        ]
+    ].
+
+    self updateInfoLabel
+
+    "Modified (comment): / 31-08-2017 / 13:58:03 / cg"
+!
+
+doBrightenImage
+    imageEditView brightenImage.
+    self listOfColors removeAll.
+    self findColorMapMode.     
+    "/ imageEditView removelastUndo
+!
+
+doBrowseClass
+    "opens a System Browser on the resourceClass and the resourceSelector"
+
+    |cls|
+
+    cls := imageEditView resourceClass.
+    cls isNil ifTrue:[^ self warn:'No Class specified'].
+
+    cls browserClass
+        openInClass:cls class 
+        selector:(imageEditView resourceSelector)
+
+    "Modified: / 31.7.1998 / 02:01:15 / cg"
+!
+
+doCopyImageToClipboard
+    imageEditView copyImageToClipboard.
+!
+
+doCropManual
+    "let user specify borders and cut them off"
+
+    |bindings left top right bottom img firstChange cropAction acceptChannel|
+
+    acceptChannel := TriggerValue new.
+
+    firstChange := true.
+
+    cropAction := 
+        [:lV :rV :tV :bV | |l r t b|
+            acceptChannel value:true.
+
+            l := lV value.
+            r := rV value.
+            t := tV value.
+            b := bV value.
+            (l + r + t + b) == 0 ifTrue:[
+                UserPreferences current beepInEditor ifTrue:[                
+                    self window beep
+                ]
+            ] ifFalse:[
+                img := imageEditView image.
+                firstChange ifTrue:[
+                    imageEditView makeUndo.
+                    firstChange := false.
+                ].
+                imageEditView
+                    makeSubImageX:l y:t 
+                    width:(img width - l - r)
+                    height:(img height - t - b).
+
+                self updateImagePreView.
+                self updateInfoLabel
+            ].
+        ].
+
+    bindings := IdentityDictionary new.
+    bindings at:#cropLeftAmount put:(left := 1 asValue).
+    bindings at:#cropRightAmount put:(right := 1 asValue).
+    bindings at:#cropTopAmount put:(top := 1 asValue).
+    bindings at:#cropBottomAmount put:(bottom := 1 asValue).
+    bindings at:#acceptChannel put:acceptChannel.
+
+    bindings at:#cropLeftNow   put:[ cropAction value:left value:0 value:0 value:0 ].
+    bindings at:#cropRightNow  put:[ cropAction value:0 value:right value:0 value:0 ].
+    bindings at:#cropTopNow    put:[ cropAction value:0 value:0 value:top value:0 ].
+    bindings at:#cropBottomNow put:[ cropAction value:0 value:0 value:0 value:bottom ].
+
+    bindings at:#applyCropAction   put:[ cropAction value:left value:right value:top value:bottom ].
+    bindings at:#cropBoxIsDialog   put:true.
+    
+    (self openDialogInterface:#cropSpec withBindings:bindings) 
+    ifFalse:[ 
+        firstChange ~~ true ifTrue:[
+            imageEditView undo.
+            self updateImagePreView.
+        ]
+    ].
+
+    "Created: / 07-09-1998 / 18:16:07 / cg"
+    "Modified: / 19-02-2017 / 15:43:50 / cg"
+!
+
+doDarkenImage
+    imageEditView darkenImage.
+    self listOfColors removeAll.
+    self findColorMapMode.     
+    "/ imageEditView removelastUndo
+!
+
+doEditMask
+    |mask|
+
+    (mask := self image mask) notNil ifTrue:[
+        mask edit
+    ].
+
+    "Modified: / 18-02-2017 / 00:38:51 / cg"
+!
+
+doFlipHorizontal
+    "flips horizontally current image"
+
+    imageEditView flipHorizontal
+!
+
+doFlipVertical
+    "flips vertically current image"
+
+    imageEditView flipVertical
+!
+
+doInsertTextFromUser
+    |text tempForm tempImage maskImage font w h paintColor|
+
+    text := Dialog request:'Text to be inserted (placed as bitmap into clipboard for paste):'.
+    text isEmptyOrNil ifTrue:[^ self ].
+
+    font := Font family:'arial' size:20.
+    font := font onDevice:Screen current.
+    w := font widthOf:text.
+    h := font heightOf:text.
+
+    tempForm := Form extent:(w@h) depth:1 onDevice:(Screen current).
+    tempForm clear.
+    tempForm font:font.
+    tempForm paint:(Color colorId:1).
+    tempForm displayString:text at:(0@font ascent).
+
+    tempImage := tempForm asImage.
+    maskImage := tempForm asImage.
+
+    paintColor := imageEditView selectedColor.
+    paintColor  colorId == 0 ifTrue:[
+        paintColor := Color black
+    ].
+    tempImage   
+        photometric:#palette;
+        colorMap:(Array 
+                    with:Color white 
+                    with:paintColor);
+        mask:maskImage.
+
+    ImageEditView copyImageToClipboard:tempImage.
+    self editMode value:#paste.
+
+    "Modified: / 11-11-2007 / 12:32:55 / cg"
+!
+
+doInspectImage
+    "opens a System Browser on the resourceClass and the resourceSelector"
+
+    self image inspect
+!
+
+doInvertedBitsImage
+    "inverts the pixels"
+
+    self withExecuteCursorDo:[
+        imageEditView makeInvertedBits.
+        self updateImage.
+    ].
+"/
+"/    imageEditView negativeImage.
+"/    self listOfColors removeAll.
+"/    self findColorMapMode.     
+"/    "/ imageEditView removelastUndo
+"/
+"/
+
+    "Created: / 31-08-2017 / 12:49:13 / cg"
+!
+
+doMagnifyDown
+    "magnifies the current image one step down"
+
+    |magHolder mag|
+
+    magHolder := self magnificationHolder.
+    (mag := magHolder value) > 1 ifTrue: [
+        magHolder value: mag - 1
+    ]
+
+    "Modified: / 26.7.1998 / 20:24:08 / cg"
+!
+
+doMagnifyImage
+    "magnifies the current image to a new size"
+
+    |box newSize image antiAliased|
+
+    antiAliased := false asValue.
+    image := imageEditView image.
+
+    box := EnterBox new.
+    box title:(resources string:'Images new size:').
+    box okText:(resources string:'OK').
+    box abortText:(resources string:'Cancel').
+    box initialText:image extent printString.
+    box verticalPanel add:(CheckBox label:(resources string:'Antialias/Smooth') model:antiAliased).
+    box showAtPointer.
+    
+    (box accepted 
+    and: [(newSize := self pointFromString:(box contents)) notNil])
+    ifTrue:[
+        newSize isPoint ifFalse:[
+            self warn:'Please enter the new size as ''x @ y''.'.
+            ^ self.    
+        ].
+        antiAliased value ifTrue:[
+            ((newSize x < image width) or:[(newSize y < image height)]) ifTrue:[
+                imageEditView magnifySmoothingTo:newSize.    
+            ] ifFalse:[    
+                imageEditView magnifyAntiAliasedImageTo:newSize.
+            ].
+        ] ifFalse:[
+            imageEditView magnifyImageTo:newSize.
+        ].
+    ].
+
+    self updateInfoLabel
+
+    "Modified: / 30-08-2017 / 15:46:02 / cg"
+!
+
+doMagnifyImageBy
+    "magnifies the current image (by a scale)"
+
+    |oldSize newSize scaleString scale image antiAliased smoothing|
+
+    image := imageEditView image.
+    oldSize := image extent.
+
+    antiAliased := false asValue.
+    smoothing := false asValue.
+    
+    Dialog modifyingBoxWith:[:box |
+        box verticalPanel add:(CheckBox label:(resources string:'Antialias/Smooth') model:antiAliased).
+        "/ box verticalPanel add:(CheckBox label:(resources string:'Smoothing') model:smoothing).
+    ] do:[
+        scaleString := Dialog 
+                   request:(resources string:'Scale factor (<1 to shrink; >1 to magnify):') 
+                   initialAnswer:'1'
+                   list:#('0.1' '0.25' '0.3' '0.5' '1.5' '2' '3' '4').     
+    ].
+    scaleString isNil ifTrue:[^ self].
+
+    scale := Object readFromString:scaleString onError:nil.
+
+    scale notNil ifTrue:[
+        scale isNumber ifFalse:[
+            self warn:'please enter a scale factor (<1 to shrink; >1 to magnify).'.
+            ^ self.    
+        ].
+        newSize := oldSize * scale.
+        antiAliased value ifTrue:[
+            scale < 1 ifTrue:[
+                imageEditView magnifySmoothingBy:scale.
+            ] ifFalse:[    
+                imageEditView magnifyAntiAliasedImageTo:newSize.
+            ].
+        ] ifFalse:[
+            imageEditView magnifyImageTo:newSize.
+        ].
+    ].
+
+    self updateInfoLabel
+
+    "Modified: / 30-08-2017 / 15:34:56 / cg"
+!
+
+doMagnifyUp
+    "magnifies the current image one step up"
+
+    |magHolder mag|
+
+    magHolder := self magnificationHolder.
+    (mag := magHolder value) < 63 ifTrue: [
+        magHolder value: mag + 1
+    ]
+
+    "Modified: / 26.7.1998 / 20:23:52 / cg"
+!
+
+doNegativeImage
+    "negates current image by negating the color map"
+
+    self withExecuteCursorDo:[
+        imageEditView negativeImage.
+        self updateImage.
+    ].
+"/
+"/    imageEditView negativeImage.
+"/    self listOfColors removeAll.
+"/    self findColorMapMode.     
+"/    "/ imageEditView removelastUndo
+"/
+"/
+
+    "Modified: / 31-08-2017 / 12:44:25 / cg"
+!
+
+doResizeImage
+    "resizes the current image"
+
+    |box newSize image|
+
+    image := imageEditView image.
+
+    box := EnterBox new.
+    box title:(resources string:'Images new size:').
+    box okText:(resources string:'OK').
+    box abortText:(resources string:'Cancel').
+    box initialText:image extent printString.
+    box showAtPointer.
+    (box accepted 
+    and: [(newSize := self pointFromString:(box contents)) notNil])
+    ifTrue:[
+        imageEditView resizeImageTo:newSize.
+    ].
+!
+
+doRotateImage
+    "rotates current image"
+
+    |rotationString box rotation|
+
+    rotationString := Dialog 
+                        request:(resources string:'Rotate by (degrees, clockwise):')
+                        list:#( '-90' '90' '180' '45' '-45'  '135' '-135' ) 
+                        initialAnswer:90.
+    rotationString isEmptyOrNil ifTrue:[^ self].    "/ canceled
+    rotation := Number readFrom:rotationString onError:[nil].
+    rotation isNil ifTrue:[^ self].   
+
+"/    box := EnterBox new.
+"/    box title:(resources string:'Rotate by (degrees, clockwise):').
+"/    box okText:(resources string:'OK').
+"/    box abortText:(resources string:'Cancel').
+"/    box initialText: '0'.
+"/    box showAtPointer.
+"/    (box accepted and: [(rotation := Number readFromString: box contents onError:nil) notNil])
+"/    ifFalse:[ ^ self ].
+
+    imageEditView rotateImageBy:rotation.
+    self updateInfoLabel.
+
+    "Modified: / 18-03-2012 / 14:41:14 / cg"
+    "Modified (comment): / 24-08-2017 / 15:02:57 / cg"
+!
+
+doShiftManual
+    "let user specify amount and shift"
+
+    |bindings amount img firstChange shiftAction acceptChannel wrapHolder|
+
+    acceptChannel := TriggerValue new.
+    wrapHolder := (lastShiftUsedWrap ? true) asValue.
+
+    firstChange := true.
+
+    shiftAction := 
+        [:shiftH :shiftV | 
+            acceptChannel value:true.
+
+            img := imageEditView image.
+            firstChange ifTrue:[
+                imageEditView makeUndo.
+                firstChange := false.
+            ].
+            imageEditView shiftImageHorizontal:(shiftH value) vertical:(shiftV value) wrap:(wrapHolder value).
+            self updateInfoLabel
+        ].
+
+    bindings := IdentityDictionary new.
+    bindings at:#shiftAmount put:(amount := 1 asValue).
+    bindings at:#wrap put:wrapHolder.
+    bindings at:#acceptChannel put:acceptChannel.
+
+    bindings at:#shiftLeftNow   put:[ shiftAction value:(-1*amount value) value:0 ].
+    bindings at:#shiftRightNow  put:[ shiftAction value:amount value value:0 ].
+    bindings at:#shiftUpNow     put:[ shiftAction value:0 value:(-1*amount value) ].
+    bindings at:#shiftDownNow   put:[ shiftAction value:0 value:amount value ].
+
+    (self openDialogInterface:#shiftDialogSpec withBindings:bindings) 
+    ifFalse:[ 
+        firstChange ~~ true ifTrue:[
+          imageEditView undo
+        ]
+    ].
+    lastShiftUsedWrap := wrapHolder value.
+
+    "Created: / 7.9.1998 / 18:16:07 / cg"
+    "Modified: / 7.9.1998 / 18:20:42 / cg"
+!
+
+doUnCropManual
+    "let user specify borders and add them"
+
+    |bindings leftAmount topAmount rightAmount bottomAmount img|
+
+    bindings := IdentityDictionary new.
+    bindings at:#cropLeftAmount put:(leftAmount := 1 asValue).
+    bindings at:#cropRightAmount put:(rightAmount := 1 asValue).
+    bindings at:#cropTopAmount put:(topAmount := 1 asValue).
+    bindings at:#cropBottomAmount  put:(bottomAmount := 1 asValue).
+    bindings at:#cropBoxIsDialog   put:true.
+
+    (self openDialogInterface:#uncropSpec withBindings:bindings)
+    ifTrue:[
+        leftAmount := leftAmount value.
+        rightAmount := rightAmount value.
+        topAmount := topAmount value.
+        bottomAmount := bottomAmount value.
+        img := imageEditView image.
+
+        imageEditView
+            makeBorderedImageX:leftAmount y:topAmount 
+            width:(img width + leftAmount + rightAmount)
+            height:(img height + topAmount + bottomAmount).
+        self updateInfoLabel
+    ].
+
+    "Created: / 07-09-1998 / 18:16:07 / cg"
+    "Modified: / 19-02-2017 / 15:31:00 / cg"
+!
+
+doUndo
+    "reverses last edit action"
+
+    imageEditView undo.
+    self updateImagePreView
+!
+
+makeInverse
+    "inverts the pixels - for palettes, this leads to funny results"
+
+    self updateImageAfterDoing:#makeInverse.
+
+    "Modified: / 31-08-2017 / 12:16:07 / cg"
+    "Modified (comment): / 31-08-2017 / 13:51:28 / cg"
+!
+
+makeMonochromeImage
+    "let user choose a threshold, then convert to monochrome"
+    
+    |image userInput thresholdBrighness|
+
+    image := imageEditView image.
+    
+    Dialog modifyingBoxWith:[:box |
+        |preview slider update thresholdValue|
+
+        thresholdValue := 0.5 asValue.
+
+        box enterField 
+            converter:(PrintConverter new initForNumber);
+            model:thresholdValue.
+            
+        box verticalPanel extent:1.0 @ 300.
+        
+        box verticalPanel add:(slider := HorizontalSlider new start:0 stop:1 step:0.05).
+        slider model:thresholdValue.
+        slider width:1.0; leftInset:4; rightInset:4.
+
+        box verticalPanel add:(preview := ImageView new).
+        preview extent:300 @300.
+        preview level:-1.
+        box verticalPanel horizontalLayout:#fitSpace.
+        
+        update := 
+            [
+                |s t|
+
+                t := thresholdValue value clampBetween:0 and:1.   
+                preview image:((image asThresholdMonochromeImage:t)
+                            magnifiedPreservingRatioTo:preview extent).
+            ].
+        update value.
+        box enterField acceptOnLostFocus:true.
+        box enterField acceptOnLeave:true.
+        thresholdValue onChangeEvaluate:update.
+
+    ] do:[
+        userInput := Dialog request:'Threshold (0=black; 1=white) ?' initialAnswer:0.5.
+    ].
+    userInput isEmptyOrNil ifTrue:[^ self].
+    thresholdBrighness := Number readFrom:userInput onError:nil.
+    thresholdBrighness isNil ifTrue:[^ self].
+
+    thresholdBrighness := thresholdBrighness clampBetween:0 and:1.
+    imageEditView newImageWithUndo:(image asThresholdMonochromeImage:thresholdBrighness)
+
+    "Created: / 24-08-2017 / 15:26:44 / cg"
+    "Modified: / 24-08-2017 / 17:54:21 / cg"
+!
+
+makeNegative
+    "negates current image by negating the color map"
+
+    self withExecuteCursorDo:[
+        imageEditView negativeImage.
+        self updateImage.
+    ].
+"/
+"/    imageEditView negativeImage.
+"/    self listOfColors removeAll.
+"/    self findColorMapMode.     
+"/    "/ imageEditView removelastUndo
+"/
+"/
+
+    "Created: / 31-08-2017 / 13:49:47 / cg"
+!
+
+thresholdGrayToDepth
+    self askForDepthThenDo:[:depth |
+        self thresholdGrayToDepth:depth
+    ].
+
+    "Created: / 24-08-2017 / 17:49:23 / cg"
+!
+
+thresholdGrayToDepth:depth
+    self withExecuteCursorDo:[
+        |newImage|
+
+        newImage := self image asThresholdGrayImageDepth:depth.
+        imageEditView newImageWithUndo:newImage.
+    ].
+
+    "Created: / 24-08-2017 / 17:49:30 / cg"
+!
+
+thresholdToDepth
+    self askForDepthThenDo:[:depth |
+        self convertToDepth:depth dither:false
+    ].
+
+    "Created: / 30-08-2017 / 00:31:33 / cg"
+! !
+
+!ImageEditor methodsFor:'user actions-editing-colors'!
+
 convertToDepth:depth dither:doDither
     |answer labels values 
      ditherColors fixColors
@@ -7588,564 +8355,13 @@
     "Modified: / 30-08-2017 / 02:13:03 / cg"
 !
 
-ditherGrayToDepth
-    self askForDepthThenDo:[:depth |
-        self ditherGrayToDepth:depth
-    ].
-
-    "Created: / 24-08-2017 / 17:49:42 / cg"
-!
-
-ditherGrayToDepth:depth
-    self withExecuteCursorDo:[
-        |newImage|
-
-        depth == 1 ifTrue:[
-            newImage := self image asErrorDitheredMonochromeImage
-        ] ifFalse:[
-            newImage := self image asGrayImageDepth:depth dither:#floydSteinberg.
-        ].
-        imageEditView newImageWithUndo:newImage.
-    ].
-
-    "Created: / 24-08-2017 / 17:51:07 / cg"
-    "Modified: / 30-08-2017 / 01:18:43 / cg"
-!
-
-ditherToDepth
-    self askForDepthThenDo:[:depth |
-        self convertToDepth:depth dither:true
-    ].
-
-    "Created: / 07-07-2006 / 13:22:10 / cg"
-    "Modified: / 30-08-2017 / 00:34:42 / cg"
-!
-
-do3DProjection
-    |box dx1 dx2 image|
-
-    image := imageEditView image.
-
-    box := EnterBox new.
-    box title:(resources string:'dX1 (0 < dx < 0.5):').
-    box okText:(resources string:'OK').
-    box abortText:(resources string:'Cancel').
-    box initialText:'0.1'.
-    box showAtPointer.
-
-    (box accepted 
-    and: [(dx1 := Number readFrom:(box contents) onError:nil) notNil])
-    ifTrue:[
-        box title:(resources string:'dX2 (0 < dx < 0.5):').
-        box initialText:(dx1 printString).
-        box showAtPointer.
-        (box accepted 
-        and: [(dx2 := Number readFrom:(box contents) onError:nil) notNil])
-        ifTrue:[
-            imageEditView threeDProjection:dx1 and:dx2.
-        ]
-    ].
-
-    self updateInfoLabel
-!
-
-doBrightenImage
-    imageEditView brightenImage.
-    self listOfColors removeAll.
-    self findColorMapMode.     
-    "/ imageEditView removelastUndo
-!
-
-doBrowseClass
-    "opens a System Browser on the resourceClass and the resourceSelector"
-
-    |cls|
-
-    cls := imageEditView resourceClass.
-    cls isNil ifTrue:[^ self warn:'No Class specified'].
-
-    cls browserClass
-        openInClass:cls class 
-        selector:(imageEditView resourceSelector)
-
-    "Modified: / 31.7.1998 / 02:01:15 / cg"
-!
-
-doCopyImageToClipboard
-    imageEditView copyImageToClipboard.
-!
-
-doCropManual
-    "let user specify borders and cut them off"
-
-    |bindings left top right bottom img firstChange cropAction acceptChannel|
-
-    acceptChannel := TriggerValue new.
-
-    firstChange := true.
-
-    cropAction := 
-        [:lV :rV :tV :bV | |l r t b|
-            acceptChannel value:true.
-
-            l := lV value.
-            r := rV value.
-            t := tV value.
-            b := bV value.
-            (l + r + t + b) == 0 ifTrue:[
-                UserPreferences current beepInEditor ifTrue:[                
-                    self window beep
-                ]
-            ] ifFalse:[
-                img := imageEditView image.
-                firstChange ifTrue:[
-                    imageEditView makeUndo.
-                    firstChange := false.
-                ].
-                imageEditView
-                    makeSubImageX:l y:t 
-                    width:(img width - l - r)
-                    height:(img height - t - b).
-
-                self updateImagePreView.
-                self updateInfoLabel
-            ].
-        ].
-
-    bindings := IdentityDictionary new.
-    bindings at:#cropLeftAmount put:(left := 1 asValue).
-    bindings at:#cropRightAmount put:(right := 1 asValue).
-    bindings at:#cropTopAmount put:(top := 1 asValue).
-    bindings at:#cropBottomAmount put:(bottom := 1 asValue).
-    bindings at:#acceptChannel put:acceptChannel.
-
-    bindings at:#cropLeftNow   put:[ cropAction value:left value:0 value:0 value:0 ].
-    bindings at:#cropRightNow  put:[ cropAction value:0 value:right value:0 value:0 ].
-    bindings at:#cropTopNow    put:[ cropAction value:0 value:0 value:top value:0 ].
-    bindings at:#cropBottomNow put:[ cropAction value:0 value:0 value:0 value:bottom ].
-
-    bindings at:#applyCropAction   put:[ cropAction value:left value:right value:top value:bottom ].
-    bindings at:#cropBoxIsDialog   put:true.
-    
-    (self openDialogInterface:#cropSpec withBindings:bindings) 
-    ifFalse:[ 
-        firstChange ~~ true ifTrue:[
-            imageEditView undo.
-            self updateImagePreView.
-        ]
-    ].
-
-    "Created: / 07-09-1998 / 18:16:07 / cg"
-    "Modified: / 19-02-2017 / 15:43:50 / cg"
-!
-
-doDarkenImage
-    imageEditView darkenImage.
-    self listOfColors removeAll.
-    self findColorMapMode.     
-    "/ imageEditView removelastUndo
-!
-
-doEditMask
-    |mask|
-
-    (mask := self image mask) notNil ifTrue:[
-        mask edit
-    ].
-
-    "Modified: / 18-02-2017 / 00:38:51 / cg"
-!
-
-doFlipHorizontal
-    "flips horizontally current image"
-
-    imageEditView flipHorizontal
-!
-
-doFlipVertical
-    "flips vertically current image"
-
-    imageEditView flipVertical
-!
-
-doInsertTextFromUser
-    |text tempForm tempImage maskImage font w h paintColor|
-
-    text := Dialog request:'Text to be inserted (placed as bitmap into clipboard for paste):'.
-    text isEmptyOrNil ifTrue:[^ self ].
-
-    font := Font family:'arial' size:20.
-    font := font onDevice:Screen current.
-    w := font widthOf:text.
-    h := font heightOf:text.
-
-    tempForm := Form extent:(w@h) depth:1 onDevice:(Screen current).
-    tempForm clear.
-    tempForm font:font.
-    tempForm paint:(Color colorId:1).
-    tempForm displayString:text at:(0@font ascent).
-
-    tempImage := tempForm asImage.
-    maskImage := tempForm asImage.
-
-    paintColor := imageEditView selectedColor.
-    paintColor  colorId == 0 ifTrue:[
-        paintColor := Color black
-    ].
-    tempImage   
-        photometric:#palette;
-        colorMap:(Array 
-                    with:Color white 
-                    with:paintColor);
-        mask:maskImage.
-
-    ImageEditView copyImageToClipboard:tempImage.
-    self editMode value:#paste.
-
-    "Modified: / 11-11-2007 / 12:32:55 / cg"
-!
-
-doInspectImage
-    "opens a System Browser on the resourceClass and the resourceSelector"
-
-    self image inspect
-!
-
-doMagnifyDown
-    "magnifies the current image one step down"
-
-    |magHolder mag|
-
-    magHolder := self magnificationHolder.
-    (mag := magHolder value) > 1 ifTrue: [
-        magHolder value: mag - 1
-    ]
-
-    "Modified: / 26.7.1998 / 20:24:08 / cg"
-!
-
-doMagnifyImage
-    "magnifies the current image to a new size"
-
-    |box newSize image antiAliased|
-
-    antiAliased := false asValue.
-    image := imageEditView image.
-
-    box := EnterBox new.
-    box title:(resources string:'Images new size:').
-    box okText:(resources string:'OK').
-    box abortText:(resources string:'Cancel').
-    box initialText:image extent printString.
-    box verticalPanel add:(CheckBox label:(resources string:'Antialias/Smooth') model:antiAliased).
-    box showAtPointer.
-    
-    (box accepted 
-    and: [(newSize := self pointFromString:(box contents)) notNil])
-    ifTrue:[
-        newSize isPoint ifFalse:[
-            self warn:'Please enter the new size as ''x @ y''.'.
-            ^ self.    
-        ].
-        antiAliased value ifTrue:[
-            ((newSize x < image width) or:[(newSize y < image height)]) ifTrue:[
-                imageEditView magnifySmoothingTo:newSize.    
-            ] ifFalse:[    
-                imageEditView magnifyAntiAliasedImageTo:newSize.
-            ].
-        ] ifFalse:[
-            imageEditView magnifyImageTo:newSize.
-        ].
-    ].
-
-    self updateInfoLabel
-
-    "Modified: / 30-08-2017 / 15:46:02 / cg"
-!
-
-doMagnifyImageBy
-    "magnifies the current image (by a scale)"
-
-    |oldSize newSize scaleString scale image antiAliased smoothing|
-
-    image := imageEditView image.
-    oldSize := image extent.
-
-    antiAliased := false asValue.
-    smoothing := false asValue.
-    
-    Dialog modifyingBoxWith:[:box |
-        box verticalPanel add:(CheckBox label:(resources string:'Antialias/Smooth') model:antiAliased).
-        "/ box verticalPanel add:(CheckBox label:(resources string:'Smoothing') model:smoothing).
-    ] do:[
-        scaleString := Dialog 
-                   request:(resources string:'Scale factor (<1 to shrink; >1 to magnify):') 
-                   initialAnswer:'1'
-                   list:#('0.1' '0.25' '0.3' '0.5' '1.5' '2' '3' '4').     
-    ].
-    scaleString isNil ifTrue:[^ self].
-
-    scale := Object readFromString:scaleString onError:nil.
-
-    scale notNil ifTrue:[
-        scale isNumber ifFalse:[
-            self warn:'please enter a scale factor (<1 to shrink; >1 to magnify).'.
-            ^ self.    
-        ].
-        newSize := oldSize * scale.
-        antiAliased value ifTrue:[
-            scale < 1 ifTrue:[
-                imageEditView magnifySmoothingBy:scale.
-            ] ifFalse:[    
-                imageEditView magnifyAntiAliasedImageTo:newSize.
-            ].
-        ] ifFalse:[
-            imageEditView magnifyImageTo:newSize.
-        ].
-    ].
-
-    self updateInfoLabel
-
-    "Modified: / 30-08-2017 / 15:34:56 / cg"
-!
-
-doMagnifyUp
-    "magnifies the current image one step up"
-
-    |magHolder mag|
-
-    magHolder := self magnificationHolder.
-    (mag := magHolder value) < 63 ifTrue: [
-        magHolder value: mag + 1
-    ]
-
-    "Modified: / 26.7.1998 / 20:23:52 / cg"
-!
-
-doNegativeImage
-    "negates current image by negating the color map"
-
-    self image depth ~~ 1 ifTrue:[
-        Dialog warn:'Only useful for depth 1 images'.
-        ^ self
-    ].
-    imageEditView negativeImage.
-    self listOfColors removeAll.
-    self findColorMapMode.     
-    "/ imageEditView removelastUndo
-!
-
-doResizeImage
-    "resizes the current image"
-
-    |box newSize image|
-
-    image := imageEditView image.
-
-    box := EnterBox new.
-    box title:(resources string:'Images new size:').
-    box okText:(resources string:'OK').
-    box abortText:(resources string:'Cancel').
-    box initialText:image extent printString.
-    box showAtPointer.
-    (box accepted 
-    and: [(newSize := self pointFromString:(box contents)) notNil])
-    ifTrue:[
-        imageEditView resizeImageTo:newSize.
-    ].
-!
-
-doRotateImage
-    "rotates current image"
-
-    |rotationString box rotation|
-
-    rotationString := Dialog 
-                        request:(resources string:'Rotate by (degrees, clockwise):')
-                        list:#( '-90' '90' '180' '45' '-45'  '135' '-135' ) 
-                        initialAnswer:90.
-    rotationString isEmptyOrNil ifTrue:[^ self].    "/ canceled
-    rotation := Number readFrom:rotationString onError:[nil].
-    rotation isNil ifTrue:[^ self].   
-
-"/    box := EnterBox new.
-"/    box title:(resources string:'Rotate by (degrees, clockwise):').
-"/    box okText:(resources string:'OK').
-"/    box abortText:(resources string:'Cancel').
-"/    box initialText: '0'.
-"/    box showAtPointer.
-"/    (box accepted and: [(rotation := Number readFromString: box contents onError:nil) notNil])
-"/    ifFalse:[ ^ self ].
-
-    imageEditView rotateImageBy:rotation.
-    self updateInfoLabel.
-
-    "Modified: / 18-03-2012 / 14:41:14 / cg"
-    "Modified (comment): / 24-08-2017 / 15:02:57 / cg"
-!
-
-doShiftManual
-    "let user specify amount and shift"
-
-    |bindings amount img firstChange shiftAction acceptChannel wrapHolder|
-
-    acceptChannel := TriggerValue new.
-    wrapHolder := (lastShiftUsedWrap ? true) asValue.
-
-    firstChange := true.
-
-    shiftAction := 
-        [:shiftH :shiftV | 
-            acceptChannel value:true.
-
-            img := imageEditView image.
-            firstChange ifTrue:[
-                imageEditView makeUndo.
-                firstChange := false.
-            ].
-            imageEditView shiftImageHorizontal:(shiftH value) vertical:(shiftV value) wrap:(wrapHolder value).
-            self updateInfoLabel
-        ].
-
-    bindings := IdentityDictionary new.
-    bindings at:#shiftAmount put:(amount := 1 asValue).
-    bindings at:#wrap put:wrapHolder.
-    bindings at:#acceptChannel put:acceptChannel.
-
-    bindings at:#shiftLeftNow   put:[ shiftAction value:(-1*amount value) value:0 ].
-    bindings at:#shiftRightNow  put:[ shiftAction value:amount value value:0 ].
-    bindings at:#shiftUpNow     put:[ shiftAction value:0 value:(-1*amount value) ].
-    bindings at:#shiftDownNow   put:[ shiftAction value:0 value:amount value ].
-
-    (self openDialogInterface:#shiftDialogSpec withBindings:bindings) 
-    ifFalse:[ 
-        firstChange ~~ true ifTrue:[
-          imageEditView undo
-        ]
-    ].
-    lastShiftUsedWrap := wrapHolder value.
-
-    "Created: / 7.9.1998 / 18:16:07 / cg"
-    "Modified: / 7.9.1998 / 18:20:42 / cg"
-!
-
-doUnCropManual
-    "let user specify borders and add them"
-
-    |bindings leftAmount topAmount rightAmount bottomAmount img|
-
-    bindings := IdentityDictionary new.
-    bindings at:#cropLeftAmount put:(leftAmount := 1 asValue).
-    bindings at:#cropRightAmount put:(rightAmount := 1 asValue).
-    bindings at:#cropTopAmount put:(topAmount := 1 asValue).
-    bindings at:#cropBottomAmount  put:(bottomAmount := 1 asValue).
-    bindings at:#cropBoxIsDialog   put:true.
-
-    (self openDialogInterface:#uncropSpec withBindings:bindings)
-    ifTrue:[
-        leftAmount := leftAmount value.
-        rightAmount := rightAmount value.
-        topAmount := topAmount value.
-        bottomAmount := bottomAmount value.
-        img := imageEditView image.
-
-        imageEditView
-            makeBorderedImageX:leftAmount y:topAmount 
-            width:(img width + leftAmount + rightAmount)
-            height:(img height + topAmount + bottomAmount).
-        self updateInfoLabel
-    ].
-
-    "Created: / 07-09-1998 / 18:16:07 / cg"
-    "Modified: / 19-02-2017 / 15:31:00 / cg"
-!
-
-doUndo
-    "reverses last edit action"
-
-    imageEditView undo.
-    self updateImagePreView
-!
-
-makeMonochromeImage
-    "let user choose a threshold, then convert to monochrome"
-    
-    |image userInput thresholdBrighness|
-
-    image := imageEditView image.
-    
-    Dialog modifyingBoxWith:[:box |
-        |preview slider update thresholdValue|
-
-        thresholdValue := 0.5 asValue.
-
-        box enterField 
-            converter:(PrintConverter new initForNumber);
-            model:thresholdValue.
-            
-        box verticalPanel extent:1.0 @ 300.
-        
-        box verticalPanel add:(slider := HorizontalSlider new start:0 stop:1 step:0.05).
-        slider model:thresholdValue.
-        slider width:1.0; leftInset:4; rightInset:4.
-
-        box verticalPanel add:(preview := ImageView new).
-        preview extent:300 @300.
-        preview level:-1.
-        box verticalPanel horizontalLayout:#fitSpace.
-        
-        update := 
-            [
-                |s t|
-
-                t := thresholdValue value clampBetween:0 and:1.   
-                preview image:((image asThresholdMonochromeImage:t)
-                            magnifiedPreservingRatioTo:preview extent).
-            ].
-        update value.
-        box enterField acceptOnLostFocus:true.
-        box enterField acceptOnLeave:true.
-        thresholdValue onChangeEvaluate:update.
-
-    ] do:[
-        userInput := Dialog request:'Threshold (0=black; 1=white) ?' initialAnswer:0.5.
-    ].
-    userInput isEmptyOrNil ifTrue:[^ self].
-    thresholdBrighness := Number readFrom:userInput onError:nil.
-    thresholdBrighness isNil ifTrue:[^ self].
-
-    thresholdBrighness := thresholdBrighness clampBetween:0 and:1.
-    imageEditView newImageWithUndo:(image asThresholdMonochromeImage:thresholdBrighness)
-
-    "Created: / 24-08-2017 / 15:26:44 / cg"
-    "Modified: / 24-08-2017 / 17:54:21 / cg"
-!
-
-thresholdGrayToDepth
-    self askForDepthThenDo:[:depth |
-        self thresholdGrayToDepth:depth
-    ].
-
-    "Created: / 24-08-2017 / 17:49:23 / cg"
-!
-
-thresholdGrayToDepth:depth
-    self withExecuteCursorDo:[
-        |newImage|
-
-        newImage := self image asThresholdGrayImageDepth:depth.
-        imageEditView newImageWithUndo:newImage.
-    ].
-
-    "Created: / 24-08-2017 / 17:49:30 / cg"
-!
-
-thresholdToDepth
-    self askForDepthThenDo:[:depth |
-        self convertToDepth:depth dither:false
-    ].
-
-    "Created: / 30-08-2017 / 00:31:33 / cg"
+makeInvertedBits
+    "inverts the pixels - for palettes, this leads to funny results.
+     For others, this is the same as negating"
+
+    self updateImageAfterDoing:#makeInvertedBits.
+
+    "Created: / 31-08-2017 / 13:51:10 / cg"
 ! !
 
 !ImageEditor methodsFor:'user actions-image sequences'!