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