# HG changeset patch # User Claus Gittinger # Date 1508750477 -7200 # Node ID 632e25674f39e883a6b92a5ee4165203c884007c # Parent 83bb01ebff3b6c90702f23c772db0f6db9eca8a8 #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 diff -r 83bb01ebff3b -r 632e25674f39 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 - "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 + " + + + + ^ + #(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'!