Image.st
changeset 7911 58ff1521d533
parent 7907 b736986b5efc
child 7913 a4a886f5c480
--- a/Image.st	Fri Feb 17 12:51:00 2017 +0100
+++ b/Image.st	Fri Feb 17 16:04:32 2017 +0100
@@ -9966,109 +9966,51 @@
 
 floodFillAt: aPoint withColor: aColor
     "fill a area with aColor like a flood up to surrounded pixels having different colors.
-     By using #atImageAndMask:put: it also works on images with mono masks."
-
-    self floodFillAt:aPoint withValue:(self valueFromColor:aColor)
-
-"/    |surroundingPixelsOfDo detectedPixel processPixelToFill
-"/     allDetectedPixelCoordinates enumerateDetectedPixelsAndDo
-"/     toDo idx pixel w h|
-"/
-"/    w := self width.
-"/    h := self height.
-"/
-"/    surroundingPixelsOfDo :=
-"/        [:pX :pY :fn |
-"/            |nX nY|
-"/
-"/            nX := pX + 1.
-"/            nY := pY + 1.
-"/            (nY < h) ifTrue: [fn value:pX value:nY].
-"/            (pY > 0) ifTrue: [fn value:pX value:(pY - 1)].
-"/            (nX < w) ifTrue: [fn value:nX value:pY].
-"/            (pX > 0) ifTrue: [fn value:(pX - 1) value:pY].
-"/        ].
-"/
-"/    enumerateDetectedPixelsAndDo :=
-"/        [:detectedPixels :action |
-"/            |idx|
-"/
-"/            idx := 1.
-"/            0 to:h-1 do:[:y |
-"/                0 to:w-1 do:[:x |
-"/                    (detectedPixels at:idx) ifTrue:[
-"/                        action value:x value:y
-"/                    ].
-"/                    idx := idx + 1.
-"/                ].
-"/            ].
-"/        ].
-"/
-"/    processPixelToFill := [:spX :spY |
-"/            |sp idx|
-"/
-"/            ((self pixelAtX:spX y:spY) == detectedPixel and: [mask isNil or:[(mask pixelAtX:spX y:spY) == 1]])
-"/            ifTrue: [
-"/                idx := 1 + spX + (spY * w).
-"/                (allDetectedPixelCoordinates at:idx) ifFalse:[
-"/                    allDetectedPixelCoordinates at:idx put:true.
-"/                    toDo add:spX @ spY.
-"/                ].
-"/            ]
-"/        ].
-"/
-"/    (mask notNil and: [(mask pixelAt:aPoint) == 0]) ifTrue:[
-"/        allDetectedPixelCoordinates := mask floodFillAt: aPoint withColor: Color white.
-"/        enumerateDetectedPixelsAndDo
-"/                value:allDetectedPixelCoordinates
-"/                value:[:x :y | self atImageAndMask:(x@y) put: aColor].
-"/        ^ allDetectedPixelCoordinates
-"/    ].
-"/
-"/    detectedPixel := self pixelAt: aPoint.
-"/
-"/    allDetectedPixelCoordinates := BooleanArray new:(w * h).
-"/    toDo := OrderedCollection new:1000.
-"/    allDetectedPixelCoordinates at:(1 + aPoint x + (aPoint y * w)) put:true.
-"/    toDo add:aPoint.
-"/
-"/    [toDo notEmpty] whileTrue:[
-"/        |p|
-"/
-"/        p := toDo removeFirst.
-"/        surroundingPixelsOfDo value:p x value:p y value:processPixelToFill.
-"/    ].
-"/
-"/    idx := 1.
-"/    aColor redByte notNil ifTrue:[
-"/        pixel := self valueFromColor:aColor.
-"/    ].
-"/
-"/    pixel isNil ifTrue:[
-"/        enumerateDetectedPixelsAndDo
-"/                value:allDetectedPixelCoordinates
-"/                value:[:x :y | mask pixelAtX:x y:y put:0].
-"/    ] ifFalse:[
-"/        enumerateDetectedPixelsAndDo
-"/                value:allDetectedPixelCoordinates
-"/                value:[:x :y | self pixelAtX:x y:y put:pixel].
-"/    ].
-"/    ^ allDetectedPixelCoordinates
-
-    "Modified: / 29.7.1998 / 03:09:16 / cg"
+     By using #atImageAndMask:put: it also works on images with mono masks.
+     Currently returns a collection of all pixels which have been modified (needed by imageEditor?),
+     but that should be changed to avoid huge massdata creation (instead, call a block for each)"
+
+    ^ self floodFillAt:aPoint withValue:(self valueFromColor:aColor)
+
+    "Modified: / 17-02-2017 / 15:05:29 / cg"
 !
 
 floodFillAt:aPoint withValue:aPixelValueOrNil
     "fill a area with aColor like a flood up to surrounded pixels having different colors.
-     By using #atImageAndMask:put: it also works on images with mono masks."
+     By using #atImageAndMask:put: it also works on images with mono masks.
+     Currently returns a collection of all pixels which have been modified (needed by imageEditor?),
+     but that should be changed to avoid huge massdata creation (instead, call a block for each)"
+
+    ^ self floodFillAt:aPoint withValue:aPixelValueOrNil maxDeviationInLight:0 maxDeviationInHue:0
+
+    "Modified (format): / 30-01-2017 / 20:57:36 / stefan"
+    "Modified (comment): / 17-02-2017 / 15:05:19 / cg"
+!
+
+floodFillAt:aPoint withValue:aPixelValueOrNil maxDeviationInLight:maxLightError maxDeviationInHue:maxHueError
+    "fill an area with aColor like a flood up to surrounded pixels having different colors.
+     If maxLightError/maxLueError are zero, the flood fills only pixels with the same value as the pixel at aPoint;
+     otherwise, a slight error (fraction) in light/hue is allowd, and pixels with a nearby color are also filled.
+     By using #atImageAndMask:put: it also works on images with mono masks.
+     Currently returns a collection of all pixels which have been modified (needed by imageEditor?),
+     but that should be changed to avoid huge massdata creation (instead, call a block for each)"
 
     |surroundingPixelsOfDo detectedPixel detectedMask processPixelToFill
      allDetectedPixelCoordinates enumerateDetectedPixelsAndDo
-     toDo w h drawAction|
+     toDo w h drawAction 
+     detectedColor detectedHue detectedLight minHueOK maxHueOK minLightOK maxLightOK
+     almostSamePixel|
 
     w := self width.
     h := self height.
 
+    detectedPixel := self pixelAt: aPoint.
+    mask isNil ifTrue:[
+        detectedMask := 1
+    ] ifFalse:[
+        detectedMask := mask pixelAt: aPoint.
+    ].
+
     surroundingPixelsOfDo :=
         [:pX :pY :fn |
             |nX nY|
@@ -10096,29 +10038,91 @@
             ].
         ].
 
-    processPixelToFill :=
-        [:spX :spY |
-            |samePixel idx|
-
-            mask isNil ifTrue:[
-                samePixel := (self pixelAtX:spX y:spY) == detectedPixel
-            ] ifFalse:[
-                detectedMask == 0 ifTrue:[
-                    samePixel := (mask pixelAtX:spX y:spY) == 0
+    (maxLightError = 0 and:[maxHueError = 0]) ifTrue:[ 
+        "/ compare pixels exactly - a faster algorithm
+        processPixelToFill :=
+            [:spX :spY |
+                |samePixel idx|
+
+                mask isNil ifTrue:[
+                    samePixel := (self pixelAtX:spX y:spY) == detectedPixel
                 ] ifFalse:[
-                    samePixel := ((self pixelAtX:spX y:spY) == detectedPixel)
-                                 and:[ (mask pixelAtX:spX y:spY) == detectedMask ]
+                    detectedMask == 0 ifTrue:[
+                        samePixel := (mask pixelAtX:spX y:spY) == 0
+                    ] ifFalse:[
+                        samePixel := ((self pixelAtX:spX y:spY) == detectedPixel)
+                                     and:[ (mask pixelAtX:spX y:spY) == detectedMask ]
+                    ].
                 ].
-            ].
-            samePixel ifTrue: [
-                idx := 1 + spX + (spY * w).
-                (allDetectedPixelCoordinates at:idx) ifFalse:[
-                    allDetectedPixelCoordinates at:idx put:true.
-                    toDo add:spX @ spY.
+                samePixel ifTrue: [
+                    idx := 1 + spX + (spY * w).
+                    (allDetectedPixelCoordinates at:idx) ifFalse:[
+                        allDetectedPixelCoordinates at:idx put:true.
+                        toDo add:spX @ spY.
+                    ].
+                ]
+            ].
+    ] ifFalse:[
+        "/ compare pixels with some error-tolerance - a slow algorithm because we have to compute
+        "/ hue and light for every pixel...
+        detectedColor := self colorFromValue:detectedPixel.
+        detectedHue := detectedColor hue.
+        detectedLight := detectedColor light.
+        detectedHue notNil ifTrue:[
+            minHueOK := detectedHue * (1.0 - maxHueError).
+            maxHueOK := detectedHue * (1.0 + maxHueError).
+        ].    
+        minLightOK := detectedLight * (1.0 - maxLightError).   
+        maxLightOK := detectedLight * (1.0 + maxLightError).
+        
+        almostSamePixel := 
+            [:pixelValue |
+                |same pixelColor pixelHue pixelLight errHue errLight|
+
+                (same := (pixelColor == detectedPixel)) ifFalse:[
+                    pixelColor := self colorFromValue:pixelValue.
+                    pixelHue := pixelColor hue.
+                    pixelLight := pixelColor light.
+                    detectedHue isNil ifTrue:[
+                        "/ detect gray - what should we do?
+                        same := "(pixelHue isNil) and:["pixelLight between:minLightOK and:maxLightOK"]". 
+                    ] ifFalse:[    
+                        pixelHue isNil ifTrue:[
+                            "/ pixel is gray - what should we do?
+                            "/ same := false.
+                            same := pixelLight between:minLightOK and:maxLightOK.
+                        ] ifFalse:[    
+                            same := (pixelHue between:minHueOK and:maxHueOK) and:[pixelLight between:minLightOK and:maxLightOK]. 
+                        ].
+                    ].
                 ].
-            ]
-        ].
-
+                same
+            ].
+            
+        processPixelToFill :=
+            [:spX :spY |
+                |samePixel idx|
+
+                mask isNil ifTrue:[
+                    samePixel := almostSamePixel value:(self pixelAtX:spX y:spY)
+                ] ifFalse:[
+                    detectedMask == 0 ifTrue:[
+                        samePixel := (mask pixelAtX:spX y:spY) == 0
+                    ] ifFalse:[
+                        samePixel := (almostSamePixel value:(self pixelAtX:spX y:spY))
+                                     and:[ (mask pixelAtX:spX y:spY) == detectedMask ]
+                    ].
+                ].
+                samePixel ifTrue: [
+                    idx := 1 + spX + (spY * w).
+                    (allDetectedPixelCoordinates at:idx) ifFalse:[
+                        allDetectedPixelCoordinates at:idx put:true.
+                        toDo add:spX @ spY.
+                    ].
+                ]
+            ].
+    ].
+    
 "/    (mask notNil and: [(mask pixelAt:aPoint) == 0]) ifTrue:[
 "/        allDetectedPixelCoordinates := mask floodFillAt: aPoint withColor: Color white.
 "/        enumerateDetectedPixelsAndDo
@@ -10127,13 +10131,6 @@
 "/        ^ allDetectedPixelCoordinates
 "/    ].
 
-    detectedPixel := self pixelAt: aPoint.
-    mask isNil ifTrue:[
-        detectedMask := 1
-    ] ifFalse:[
-        detectedMask := mask pixelAt: aPoint.
-    ].
-
     allDetectedPixelCoordinates := BooleanArray new:(w * h).
     toDo := OrderedCollection new:1000.
     allDetectedPixelCoordinates at:((aPoint y * w) + aPoint x + 1) put:true.
@@ -10166,8 +10163,7 @@
     self release. "/ device-image is no longer valid
     ^ allDetectedPixelCoordinates
 
-    "Modified: / 29-07-1998 / 03:09:16 / cg"
-    "Modified (format): / 30-01-2017 / 20:57:36 / stefan"
+    "Created: / 17-02-2017 / 15:03:33 / cg"
 !
 
 rectangle: aRectangle withColor:aColor