| 1 | get("#121") -- compareTables
 | 
| 2 | get("#348") -- bright and rgb
 | 
| 3 | |
| 4 | maxSize = {width=100, height=50}
 | 
| 5 | minSize = {width=4, height=4}
 | 
| 6 | threshold = 0.5 | 
| 7 | wandSize = 2 -- magic wand size | 
| 8 | |
| 9 | local rectangles, img | 
| 10 | |
| 11 | function cloneRectangle(r) | 
| 12 | return newRectangle(r.x, r.y, r.width, r.height) | 
| 13 | end | 
| 14 | |
| 15 | function newRectangle(x, y, w, h) | 
| 16 |   return {x=x, y=y, width=w, height=h}
 | 
| 17 | end | 
| 18 | |
| 19 | function contains(r, x, y) | 
| 20 | return x >= r.x and y >= r.y and x < r.x+r.width and y < r.y+r.width | 
| 21 | end | 
| 22 | |
| 23 | function seen(x, y) | 
| 24 | for r, _ in pairs(rectangles) do | 
| 25 | if contains(r, x, y) then | 
| 26 | return true | 
| 27 | end | 
| 28 | end | 
| 29 | return false | 
| 30 | end | 
| 31 | |
| 32 | -- returns rectangle | 
| 33 | function magicWand(image, x, y) | 
| 34 | if image ~= img then | 
| 35 |     img, rectangles = image, {}
 | 
| 36 | end | 
| 37 | if seen(x, y) then return nil end | 
| 38 | local r = newRectangle(x, y, 1, 1) | 
| 39 | |
| 40 | while true do | 
| 41 | local last = cloneRectangle(r) | 
| 42 | expandRight(image, r) | 
| 43 | if tooLarge(r) then return nil end | 
| 44 | expandBottom(image, r) | 
| 45 | if tooLarge(r) then return nil end | 
| 46 | if compareTables(last, r) then | 
| 47 | if r.width >= minSize.width and r.height >= minSize.width then | 
| 48 | rectangles[r] = true | 
| 49 | return r | 
| 50 | else | 
| 51 | return nil | 
| 52 | end | 
| 53 | end | 
| 54 | end | 
| 55 | end | 
| 56 | |
| 57 | function tooLarge(r) | 
| 58 | return maxSize ~= nil and (r.width > maxSize.width or r.height > maxSize.height) | 
| 59 | end | 
| 60 | |
| 61 | function expandLeft(image, r) | 
| 62 | local newX = math.max(r.x - wandSize, 0) | 
| 63 | if newX == r.x then return end | 
| 64 | newX = searchFromLeft(image, newRectangle(newX, r.y, r.x-newX, r.height)) | 
| 65 | r.width = r.width+r.x-newX | 
| 66 | r.x = newX | 
| 67 | end | 
| 68 | |
| 69 | function searchFromLeft(image, r) | 
| 70 | for x = r.x, r.x+r.width-1 do | 
| 71 | if regionNotEmpty(image, newRectangle(x, r.y, 1, r.height)) then | 
| 72 | return x | 
| 73 | end | 
| 74 | end | 
| 75 | return r.x+r.width | 
| 76 | end | 
| 77 | |
| 78 | function expandRight(image, r) | 
| 79 | local newX = math.min(r.x + r.width + wandSize, image.width) | 
| 80 | if newX == r.x+r.width then return end | 
| 81 | newX = searchFromRight(image, newRectangle(r.x+r.width, r.y, newX-(r.x+r.width), r.height)) | 
| 82 | r.width = newX-r.x | 
| 83 | end | 
| 84 | |
| 85 | function searchFromRight(image, r) | 
| 86 | for x = r.x+r.width-1, r.x, -1 do | 
| 87 | if regionNotEmpty(image, newRectangle(x, r.y, 1, r.height)) then | 
| 88 | return x+1 | 
| 89 | end | 
| 90 | end | 
| 91 | return r.x | 
| 92 | end | 
| 93 | |
| 94 | function expandTop(image, r) | 
| 95 | local newY = math.max(r.y - wandSize, 0) | 
| 96 | if newY == r.y then return end | 
| 97 | newY = searchFromTop(image, newRectangle(r.x, newY, r.width, r.y-newY)) | 
| 98 | r.height = r.height+r.y-newY | 
| 99 | r.y = newY | 
| 100 | end | 
| 101 | |
| 102 | function searchFromTop(image, r) | 
| 103 | for y = r.y, r.y+r.height-1 do | 
| 104 | if regionNotEmpty(image, newRectangle(r.x, y, r.width, 1)) then | 
| 105 | return y | 
| 106 | end | 
| 107 | end | 
| 108 | return r.y+r.height | 
| 109 | end | 
| 110 | |
| 111 | function expandBottom(image, r) | 
| 112 | local newY = math.min(r.y + r.height + wandSize, image.height) | 
| 113 | if newY == r.y+r.height then return end | 
| 114 | newY = searchFromBottom(image, newRectangle(r.x, r.y + r.height, r.width, newY-(r.y+r.height))) | 
| 115 | r.height = newY-r.y | 
| 116 | end | 
| 117 | |
| 118 | function searchFromBottom(image, r) | 
| 119 | for y = r.y+r.height-1, r.y, -1 do | 
| 120 | if regionNotEmpty(image, newRectangle(r.x, y, r.width, 1)) then | 
| 121 | return y+1 | 
| 122 | end | 
| 123 | end | 
| 124 | return r.y | 
| 125 | end | 
| 126 | |
| 127 | -- we're looking for dark pixels this time | 
| 128 | function regionNotEmpty(image, r) | 
| 129 | --return image.clip(rectangle).anyPixelBrighterThan(threshold) | 
| 130 | for y=r.y, r.y+r.height-1 do | 
| 131 | for x=r.x, r.x+r.width-1 do | 
| 132 | if bright(rgb(image.getInt(x, y))) < threshold then | 
| 133 | return true | 
| 134 | end | 
| 135 | end | 
| 136 | end | 
| 137 | return false | 
| 138 | end | 
| 139 | |
| 140 | |
| 141 | function recttostring(r) | 
| 142 | return r.x..", "..r.y..", "..r.x+r.width..", "..r.y+r.height | 
| 143 | end | 
| 144 | |
| 145 | function magicWandAll(image) | 
| 146 |   local allrects = {}
 | 
| 147 | |
| 148 | for y = 0, image.height-1, 2 do | 
| 149 | for x = 0, image.width-1, 2 do | 
| 150 | local r = magicWand(image, x, y) | 
| 151 | |
| 152 | if r then | 
| 153 | allrects[recttostring(r)] = true | 
| 154 | end | 
| 155 | end | 
| 156 | end | 
| 157 | |
| 158 | return allrects | 
| 159 | end | 
Began life as a copy of #369
test run test run with input download show line numbers
Travelled to 12 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
| Snippet ID: | #398 | 
| Snippet name: | Magic Wand Algorithm (optimizing) | 
| Eternal ID of this version: | #398/1 | 
| Text MD5: | 118d7f99c02fdc09a4f53a82dda704cb | 
| Author: | stefan | 
| Category: | image recognition | 
| Type: | Lua code | 
| Public (visible to everyone): | Yes | 
| Archived (hidden from active list): | No | 
| Created/modified: | 2015-02-05 00:16:59 | 
| Source code size: | 3870 bytes / 159 lines | 
| Pitched / IR pitched: | No / Yes | 
| Views / Downloads: | 1000 / 234 | 
| Referenced in: | [show references] |