#FEATURE by cg
authorClaus Gittinger <cg@exept.de>
Mon, 23 Oct 2017 11:21:17 +0200
changeset 3497 632e25674f39
parent 3496 83bb01ebff3b
child 3498 262ff2ef9e43
#FEATURE by cg class: ImageEditor class definition added: #ditherToNumberOfGrayColors #thresholdToNumberOfGrayColors comment/format in: #makeMonochromeImage changed: #ditherGrayToDepth: class: ImageEditor class comment/format in: #menuEdit changed: #menu #menuColors
ImageEditor.st
--- a/ImageEditor.st	Mon Oct 09 09:16:44 2017 +0200
+++ b/ImageEditor.st	Mon Oct 23 11:21:17 2017 +0200
@@ -19,7 +19,8 @@
 		lastShiftUsedWrap lastGrabbedScreenArea
 		allowedToChangeImageDimensionAndDepth savedImage savedFile'
 	classVariableNames:'DefaultRelativeSizes LastColormapMode LastDirectory
-		LastSizeString LastURL MaskClipboard LastDepth'
+		LastSizeString LastURL MaskClipboard LastDepth
+		LastNumThresholdGrayColors'
 	poolDictionaries:''
 	category:'Interface-UIPainter'
 !
@@ -2851,7 +2852,7 @@
             isVisible: modeMenuVisible
           )
          (MenuItem
-            label: 'Colors'
+            label: 'Image'
             translateLabel: true
             submenuChannel: menuColors
           )
@@ -2876,6 +2877,8 @@
         nil
         nil
       )
+
+    "Modified: / 23-10-2017 / 10:39:43 / cg"
 !
 
 menuColors
@@ -3124,6 +3127,14 @@
                   itemValue: thresholdGrayToDepth
                 )
                (MenuItem
+                  label: 'Make GrayScale with N Gray Colors (Dither)...'
+                  itemValue: ditherToNumberOfGrayColors
+                )
+               (MenuItem
+                  label: 'Make GrayScale with N Gray Colors (Threshold)...'
+                  itemValue: thresholdToNumberOfGrayColors
+                )
+               (MenuItem
                   label: '-'
                 )
                (MenuItem
@@ -3206,26 +3217,212 @@
         nil
       )
 
-    "Modified: / 31-08-2017 / 14:43:51 / cg"
+    "Modified: / 23-10-2017 / 11:18:58 / cg"
 !
 
 menuEdit
-    <resource: #menu>
     "This resource specification was automatically generated
      by the MenuEditor of ST/X."
+
     "Do not manually edit this!! If it is corrupted,
      the MenuEditor may not be able to read the specification."
+
+
     "
      MenuEditor new openOnClass:ImageEditor andSelector:#menuEdit
-     (Menu new fromLiteralArrayEncoding:(ImageEditor menuEdit)) startUp"
-    
-    ^ #( #Menu
-      #((MenuItem activeHelpKey: editUndo enabled: canUndoHolder label: 'Undo' itemValue: doUndo) (MenuItem label: '-') (MenuItem enabled: imageIsLoadedHolder label: 'Copy to Clipboard' itemValue: doCopyImageToClipboard) (MenuItem label: '-') (MenuItem activeHelpKey: editResize enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: 'Resize...' itemValue: doResizeImage) (MenuItem activeHelpKey: editMagnifyImage enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: 'Magnify...' itemValue: doMagnifyImage) (MenuItem activeHelpKey: editMagnifyImage enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: 'Magnify By...' itemValue: doMagnifyImageBy) (MenuItem activeHelpKey: editRotate enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: 'Rotate...' itemValue: doRotateImage) (MenuItem activeHelpKey: edit3DProjection enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: '3D Projection...' itemValue: do3DProjection) (MenuItem enabled: imageIsLoadedAndAllowedToFlipHolder label: 'Flip' submenu: (Menu ((MenuItem activeHelpKey: editFlipVertical enabled: imageIsLoadedAndNotReadonlyHolder label: 'Flip - Vertical' itemValue: doFlipVertical labelImage: (ResourceRetriever ImageEditor flipVerticalIcon 'Flip - Vertical')) (MenuItem activeHelpKey: editFlipHorizontal enabled: imageIsLoadedAndNotReadonlyHolder label: 'Flip - Horizontal' itemValue: doFlipHorizontal labelImage: (ResourceRetriever ImageEditor flipHorizontalIcon 'Flip - Horizontal'))) nil nil)) (MenuItem label: '-') (MenuItem enabled: imageIsLoadedAndAllowedToChangeImageDimension label: 'Crop' submenu: (Menu ((MenuItem activeHelpKey: cropManual label: 'Manual...' itemValue: doCropManual) (MenuItem label: '-' isVisible: false) (MenuItem activeHelpKey: autoCropAll label: 'All' itemValue: autoCropAll) (MenuItem label: '-') (MenuItem activeHelpKey: autoCropLeft label: 'Left' itemValue: autoCropLeft) (MenuItem activeHelpKey: autoCropRight label: 'Right' itemValue: autoCropRight) (MenuItem activeHelpKey: autoCropTop label: 'Top' itemValue: autoCropTop) (MenuItem activeHelpKey: autoCropBottom label: 'Bottom' itemValue: autoCropBottom)) nil nil)) (MenuItem activeHelpKey: uncropManual enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth label: 'Uncrop (Add Border)...' itemValue: doUnCropManual) (MenuItem activeHelpKey: shiftManual enabled: imageIsLoadedAndAllowedToChangeImageDimension label: 'Shift...' itemValue: doShiftManual) (MenuItem label: '-') (MenuItem activeHelpKey: fileEditMask enabled: hasMaskHolder label: 'Edit Mask' itemValue: doEditMask) (MenuItem enabled: imageIsLoadedAndNotReadonlyHolder label: 'Text...' itemValue: doInsertTextFromUser) (MenuItem label: '-') (MenuItem enabled: imageIsLoadedHolder label: 'Animation Sequence' submenu: (Menu ((MenuItem enabled: imageHasNextImageHolder label: 'Next in Sequence' itemValue: nextImageInSequence) (MenuItem enabled: imageHasPreviousImageHolder label: 'Previous in Sequence' itemValue: previousImageInSequence) (MenuItem label: '-') (MenuItem enabled: imageHasImageSequenceHolder label: 'Edit each from Sequence' itemValue: editEachImageFromSequence)) nil nil)))
-      nil
-      nil )
-
-    "Modified: / 12-04-2017 / 09:25:18 / cg"
-    "Modified (comment): / 30-08-2017 / 00:30:38 / cg"
+     (Menu new fromLiteralArrayEncoding:(ImageEditor menuEdit)) startUp
+    "
+
+    <resource: #menu>
+
+    ^ 
+     #(Menu
+        (
+         (MenuItem
+            activeHelpKey: editUndo
+            enabled: canUndoHolder
+            label: 'Undo'
+            itemValue: doUndo
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            enabled: imageIsLoadedHolder
+            label: 'Copy to Clipboard'
+            itemValue: doCopyImageToClipboard
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            activeHelpKey: editResize
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: 'Resize...'
+            itemValue: doResizeImage
+          )
+         (MenuItem
+            activeHelpKey: editMagnifyImage
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: 'Magnify...'
+            itemValue: doMagnifyImage
+          )
+         (MenuItem
+            activeHelpKey: editMagnifyImage
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: 'Magnify By...'
+            itemValue: doMagnifyImageBy
+          )
+         (MenuItem
+            activeHelpKey: editRotate
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: 'Rotate...'
+            itemValue: doRotateImage
+          )
+         (MenuItem
+            activeHelpKey: edit3DProjection
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: '3D Projection...'
+            itemValue: do3DProjection
+          )
+         (MenuItem
+            enabled: imageIsLoadedAndAllowedToFlipHolder
+            label: 'Flip'
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  activeHelpKey: editFlipVertical
+                  enabled: imageIsLoadedAndNotReadonlyHolder
+                  label: 'Flip - Vertical'
+                  itemValue: doFlipVertical
+                  labelImage: (ResourceRetriever ImageEditor flipVerticalIcon 'Flip - Vertical')
+                )
+               (MenuItem
+                  activeHelpKey: editFlipHorizontal
+                  enabled: imageIsLoadedAndNotReadonlyHolder
+                  label: 'Flip - Horizontal'
+                  itemValue: doFlipHorizontal
+                  labelImage: (ResourceRetriever ImageEditor flipHorizontalIcon 'Flip - Horizontal')
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            enabled: imageIsLoadedAndAllowedToChangeImageDimension
+            label: 'Crop'
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  activeHelpKey: cropManual
+                  label: 'Manual...'
+                  itemValue: doCropManual
+                )
+               (MenuItem
+                  label: '-'
+                  isVisible: false
+                )
+               (MenuItem
+                  activeHelpKey: autoCropAll
+                  label: 'All'
+                  itemValue: autoCropAll
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  activeHelpKey: autoCropLeft
+                  label: 'Left'
+                  itemValue: autoCropLeft
+                )
+               (MenuItem
+                  activeHelpKey: autoCropRight
+                  label: 'Right'
+                  itemValue: autoCropRight
+                )
+               (MenuItem
+                  activeHelpKey: autoCropTop
+                  label: 'Top'
+                  itemValue: autoCropTop
+                )
+               (MenuItem
+                  activeHelpKey: autoCropBottom
+                  label: 'Bottom'
+                  itemValue: autoCropBottom
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            activeHelpKey: uncropManual
+            enabled: imageIsLoadedAndAllowedToChangeImageDimensionAndDepth
+            label: 'Uncrop (Add Border)...'
+            itemValue: doUnCropManual
+          )
+         (MenuItem
+            activeHelpKey: shiftManual
+            enabled: imageIsLoadedAndAllowedToChangeImageDimension
+            label: 'Shift...'
+            itemValue: doShiftManual
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            activeHelpKey: fileEditMask
+            enabled: hasMaskHolder
+            label: 'Edit Mask'
+            itemValue: doEditMask
+          )
+         (MenuItem
+            enabled: imageIsLoadedAndNotReadonlyHolder
+            label: 'Text...'
+            itemValue: doInsertTextFromUser
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            enabled: imageIsLoadedHolder
+            label: 'Animation Sequence'
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  enabled: imageHasNextImageHolder
+                  label: 'Next in Sequence'
+                  itemValue: nextImageInSequence
+                )
+               (MenuItem
+                  enabled: imageHasPreviousImageHolder
+                  label: 'Previous in Sequence'
+                  itemValue: previousImageInSequence
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  enabled: imageHasImageSequenceHolder
+                  label: 'Edit each from Sequence'
+                  itemValue: editEachImageFromSequence
+                )
+               )
+              nil
+              nil
+            )
+          )
+         )
+        nil
+        nil
+      )
 !
 
 menuFile
@@ -7795,18 +7992,19 @@
 
 ditherGrayToDepth:depth
     self withExecuteCursorDo:[
-        |newImage|
-
+        |image newImage|
+
+        image := self image.
         depth == 1 ifTrue:[
-            newImage := self image asErrorDitheredMonochromeImage
+            newImage := image asErrorDitheredMonochromeImage
         ] ifFalse:[
-            newImage := self image asGrayImageDepth:depth dither:#floydSteinberg.
+            newImage := image asGrayImageDepth:depth dither:#floydSteinberg.
         ].
         imageEditView newImageWithUndo:newImage.
     ].
 
     "Created: / 24-08-2017 / 17:51:07 / cg"
-    "Modified: / 30-08-2017 / 01:18:43 / cg"
+    "Modified: / 23-10-2017 / 10:58:18 / cg"
 !
 
 ditherToDepth
@@ -7818,6 +8016,70 @@
     "Modified: / 30-08-2017 / 00:34:42 / cg"
 !
 
+ditherToNumberOfGrayColors
+    |oldDepth numGrayColors suggestion grayImage userInput grayColors|
+
+    oldDepth := self image depth.
+    grayImage := self image asGrayImageDepth:8.
+    
+    suggestion := LastNumThresholdGrayColors notNil ifTrue:[
+                    LastNumThresholdGrayColors
+                  ] ifFalse:[
+                    oldDepth > 8 
+                        ifTrue:[256] 
+                        ifFalse:[2 raisedTo:((oldDepth // 2 - 1) nextPowerOf2)]
+                  ].
+
+    Dialog modifyingBoxWith:[:box |
+        |preview slider update thresholdValue|
+
+        thresholdValue := suggestion asValue.
+
+        box enterField 
+            converter:(PrintConverter new initForNumber);
+            model:thresholdValue.
+
+        box verticalPanel extent:1.0 @ 300.
+
+        box verticalPanel add:(slider := HorizontalSlider new start:2 stop:256 step:1).
+        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 := 
+            [
+                |numGrayColors depth s t tImage|
+
+                numGrayColors := thresholdValue value clampBetween:2 and:256.
+                grayColors := Color grayColorVector:numGrayColors.
+                tImage := grayImage asDitheredImageUsing:grayColors depth:(grayImage depth).
+                
+                preview image:(tImage magnifiedPreservingRatioTo:preview extent).
+            ].
+        update value.
+        box enterField acceptOnLostFocus:true.
+        box enterField acceptOnLeave:true.
+        thresholdValue onChangeEvaluate:update.
+
+    ] do:[
+        userInput := Dialog request:'Number of Gray Colors ?' initialAnswer:suggestion asString.
+    ].
+    userInput isEmptyOrNil ifTrue:[^ self].
+
+
+    numGrayColors := Number readFrom:userInput onError:nil.
+    numGrayColors isNil ifTrue:[^ self].
+
+    grayColors := Color grayColorVector:numGrayColors.
+    imageEditView newImageWithUndo:(grayImage asDitheredImageUsing:grayColors depth:(grayImage depth)).
+
+    "Created: / 23-10-2017 / 11:17:44 / cg"
+!
+
 do3DProjection
     "make a naive 3D projection;
      can be used to create those typical marketing images as seen in web pages"
@@ -8327,6 +8589,7 @@
         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].
 
@@ -8336,7 +8599,7 @@
 
     "Created: / 24-08-2017 / 15:26:44 / cg"
     "Modified: / 24-08-2017 / 17:54:21 / cg"
-    "Modified (format): / 01-09-2017 / 10:28:24 / cg"
+    "Modified (format): / 23-10-2017 / 10:42:52 / cg"
 !
 
 makeNegative
@@ -8376,6 +8639,70 @@
     ].
 
     "Created: / 30-08-2017 / 00:31:33 / cg"
+!
+
+thresholdToNumberOfGrayColors
+    |oldDepth numGrayColors suggestion grayImage userInput grayColors|
+
+    oldDepth := self image depth.
+    grayImage := self image asGrayImageDepth:8.
+    
+    suggestion := LastNumThresholdGrayColors notNil ifTrue:[
+                    LastNumThresholdGrayColors
+                  ] ifFalse:[
+                    oldDepth > 8 
+                        ifTrue:[256] 
+                        ifFalse:[2 raisedTo:((oldDepth // 2 - 1) nextPowerOf2)]
+                  ].
+
+    Dialog modifyingBoxWith:[:box |
+        |preview slider update thresholdValue|
+
+        thresholdValue := suggestion asValue.
+
+        box enterField 
+            converter:(PrintConverter new initForNumber);
+            model:thresholdValue.
+
+        box verticalPanel extent:1.0 @ 300.
+
+        box verticalPanel add:(slider := HorizontalSlider new start:2 stop:256 step:1).
+        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 := 
+            [
+                |numGrayColors depth s t tImage|
+
+                numGrayColors := thresholdValue value clampBetween:2 and:256.
+                grayColors := Color grayColorVector:numGrayColors.
+                tImage := grayImage asNearestPaintImageDepth:(grayImage depth) colors:grayColors.
+                
+                preview image:(tImage magnifiedPreservingRatioTo:preview extent).
+            ].
+        update value.
+        box enterField acceptOnLostFocus:true.
+        box enterField acceptOnLeave:true.
+        thresholdValue onChangeEvaluate:update.
+
+    ] do:[
+        userInput := Dialog request:'Number of Gray Colors ?' initialAnswer:suggestion asString.
+    ].
+    userInput isEmptyOrNil ifTrue:[^ self].
+
+
+    numGrayColors := Number readFrom:userInput onError:nil.
+    numGrayColors isNil ifTrue:[^ self].
+
+    grayColors := Color grayColorVector:numGrayColors.
+    imageEditView newImageWithUndo:(grayImage asNearestPaintImageDepth:(grayImage depth) colors:grayColors).
+
+    "Created: / 23-10-2017 / 10:55:15 / cg"
 ! !
 
 !ImageEditor methodsFor:'user actions-editing-colors'!