github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/rank.go (about) 1 package gift 2 3 import ( 4 "image" 5 "image/draw" 6 ) 7 8 type rankMode int 9 10 const ( 11 rankMedian rankMode = iota 12 rankMin 13 rankMax 14 ) 15 16 type rankFilter struct { 17 ksize int 18 disk bool 19 mode rankMode 20 } 21 22 func (p *rankFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 23 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 24 return 25 } 26 27 func (p *rankFilter) Draw(dst draw.Image, src image.Image, options *Options) { 28 if options == nil { 29 options = &defaultOptions 30 } 31 32 srcb := src.Bounds() 33 dstb := dst.Bounds() 34 35 if srcb.Dx() <= 0 || srcb.Dy() <= 0 { 36 return 37 } 38 39 ksize := p.ksize 40 if ksize%2 == 0 { 41 ksize-- 42 } 43 44 if ksize <= 1 { 45 copyimage(dst, src, options) 46 return 47 } 48 kradius := ksize / 2 49 50 opaque := isOpaque(src) 51 52 var disk []float32 53 if p.disk { 54 disk = genDisk(ksize) 55 } 56 57 pixGetter := newPixelGetter(src) 58 pixSetter := newPixelSetter(dst) 59 60 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 61 pxbuf := []pixel{} 62 63 var rbuf, gbuf, bbuf, abuf []float32 64 if p.mode == rankMedian { 65 rbuf = make([]float32, 0, ksize*ksize) 66 gbuf = make([]float32, 0, ksize*ksize) 67 bbuf = make([]float32, 0, ksize*ksize) 68 if !opaque { 69 abuf = make([]float32, 0, ksize*ksize) 70 } 71 } 72 73 for y := pmin; y < pmax; y++ { 74 // init buffer 75 pxbuf = pxbuf[0:0] 76 for i := srcb.Min.X - kradius; i <= srcb.Min.X+kradius; i++ { 77 for j := y - kradius; j <= y+kradius; j++ { 78 kx, ky := i, j 79 if kx < srcb.Min.X { 80 kx = srcb.Min.X 81 } else if kx > srcb.Max.X-1 { 82 kx = srcb.Max.X - 1 83 } 84 if ky < srcb.Min.Y { 85 ky = srcb.Min.Y 86 } else if ky > srcb.Max.Y-1 { 87 ky = srcb.Max.Y - 1 88 } 89 pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky)) 90 } 91 } 92 93 for x := srcb.Min.X; x < srcb.Max.X; x++ { 94 var r, g, b, a float32 95 if p.mode == rankMedian { 96 rbuf = rbuf[0:0] 97 gbuf = gbuf[0:0] 98 bbuf = bbuf[0:0] 99 if !opaque { 100 abuf = abuf[0:0] 101 } 102 } else if p.mode == rankMin { 103 r, g, b, a = 1.0, 1.0, 1.0, 1.0 104 } else if p.mode == rankMax { 105 r, g, b, a = 0.0, 0.0, 0.0, 0.0 106 } 107 108 sz := 0 109 for i := 0; i < ksize; i++ { 110 for j := 0; j < ksize; j++ { 111 112 if p.disk { 113 if disk[i*ksize+j] == 0.0 { 114 continue 115 } 116 } 117 118 px := pxbuf[i*ksize+j] 119 if p.mode == rankMedian { 120 rbuf = append(rbuf, px.R) 121 gbuf = append(gbuf, px.G) 122 bbuf = append(bbuf, px.B) 123 if !opaque { 124 abuf = append(abuf, px.A) 125 } 126 } else if p.mode == rankMin { 127 r = minf32(r, px.R) 128 g = minf32(g, px.G) 129 b = minf32(b, px.B) 130 if !opaque { 131 a = minf32(a, px.A) 132 } 133 } else if p.mode == rankMax { 134 r = maxf32(r, px.R) 135 g = maxf32(g, px.G) 136 b = maxf32(b, px.B) 137 if !opaque { 138 a = maxf32(a, px.A) 139 } 140 } 141 sz++ 142 } 143 } 144 145 if p.mode == rankMedian { 146 qsortf32(rbuf) 147 qsortf32(gbuf) 148 qsortf32(bbuf) 149 if !opaque { 150 qsortf32(abuf) 151 } 152 153 idx := sz / 2 154 r, g, b = rbuf[idx], gbuf[idx], bbuf[idx] 155 if !opaque { 156 a = abuf[idx] 157 } 158 } 159 160 if opaque { 161 a = 1.0 162 } 163 164 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a}) 165 166 // rotate buffer columns 167 if x < srcb.Max.X-1 { 168 copy(pxbuf[0:], pxbuf[ksize:]) 169 pxbuf = pxbuf[0 : ksize*(ksize-1)] 170 kx := x + 1 + kradius 171 if kx > srcb.Max.X-1 { 172 kx = srcb.Max.X - 1 173 } 174 for j := y - kradius; j <= y+kradius; j++ { 175 ky := j 176 if ky < srcb.Min.Y { 177 ky = srcb.Min.Y 178 } else if ky > srcb.Max.Y-1 { 179 ky = srcb.Max.Y - 1 180 } 181 pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky)) 182 } 183 } 184 } 185 } 186 }) 187 } 188 189 // Median creates a median image filter. 190 // Picks a median value per channel in neighborhood for each pixel. 191 // The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7). 192 // If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood. 193 func Median(ksize int, disk bool) Filter { 194 return &rankFilter{ 195 ksize: ksize, 196 disk: disk, 197 mode: rankMedian, 198 } 199 } 200 201 // Minimum creates a local minimum image filter. 202 // Picks a minimum value per channel in neighborhood for each pixel. 203 // The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7). 204 // If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood. 205 func Minimum(ksize int, disk bool) Filter { 206 return &rankFilter{ 207 ksize: ksize, 208 disk: disk, 209 mode: rankMin, 210 } 211 } 212 213 // Maximum creates a local maximum image filter. 214 // Picks a maximum value per channel in neighborhood for each pixel. 215 // The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7). 216 // If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood. 217 func Maximum(ksize int, disk bool) Filter { 218 return &rankFilter{ 219 ksize: ksize, 220 disk: disk, 221 mode: rankMax, 222 } 223 }