github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/imgutils/helper.go (about) 1 package imgutils 2 3 import ( 4 "container/heap" 5 "image" 6 "image/color" 7 "image/color/palette" 8 "image/draw" 9 "sort" 10 ) 11 12 const ( 13 numDimensions = 3 14 ) 15 16 func min(x, y int) int { 17 if x < y { 18 return x 19 } 20 return y 21 } 22 23 func max(x, y int) int { 24 if x > y { 25 return x 26 } 27 return y 28 } 29 30 type point [numDimensions]int 31 32 type block struct { 33 minCorner, maxCorner point 34 points []point 35 // The index is needed by update and is maintained by the heap.Interface methods. 36 index int // The index of the item in the heap. 37 } 38 39 func newBlock(p []point) *block { 40 return &block{ 41 minCorner: point{0x00, 0x00, 0x00}, 42 maxCorner: point{0xFF, 0xFF, 0xFF}, 43 points: p, 44 } 45 } 46 47 func (b *block) longestSideIndex() int { 48 m := b.maxCorner[0] - b.minCorner[0] 49 maxIndex := 0 50 for i := 1; i < numDimensions; i++ { 51 diff := b.maxCorner[i] - b.minCorner[i] 52 if diff > m { 53 m = diff 54 maxIndex = i 55 } 56 } 57 return maxIndex 58 } 59 60 func (b *block) longestSideLength() int { 61 i := b.longestSideIndex() 62 return b.maxCorner[i] - b.minCorner[i] 63 } 64 65 func (b *block) shrink() { 66 for j := 0; j < numDimensions; j++ { 67 b.minCorner[j] = b.points[0][j] 68 b.maxCorner[j] = b.points[0][j] 69 } 70 for i := 1; i < len(b.points); i++ { 71 for j := 0; j < numDimensions; j++ { 72 b.minCorner[j] = min(b.minCorner[j], b.points[i][j]) 73 b.maxCorner[j] = max(b.maxCorner[j], b.points[i][j]) 74 } 75 } 76 } 77 78 type pointSorter struct { 79 points []point 80 by func(p1, p2 *point) bool 81 } 82 83 func (p *pointSorter) Len() int { 84 return len(p.points) 85 } 86 87 func (p *pointSorter) Swap(i, j int) { 88 p.points[i], p.points[j] = p.points[j], p.points[i] 89 } 90 91 func (p *pointSorter) Less(i, j int) bool { 92 return p.by(&p.points[i], &p.points[j]) 93 } 94 95 // A priorityQueue implements heap.Interface and holds blocks. 96 type priorityQueue []*block 97 98 func (pq priorityQueue) Len() int { return len(pq) } 99 100 func (pq priorityQueue) Less(i, j int) bool { 101 return pq[i].longestSideLength() > pq[j].longestSideLength() 102 } 103 104 func (pq priorityQueue) Swap(i, j int) { 105 pq[i], pq[j] = pq[j], pq[i] 106 pq[i].index = i 107 pq[j].index = j 108 } 109 110 func (pq *priorityQueue) Push(x interface{}) { 111 n := len(*pq) 112 item := x.(*block) 113 item.index = n 114 *pq = append(*pq, item) 115 } 116 117 func (pq *priorityQueue) Pop() interface{} { 118 old := *pq 119 n := len(old) 120 item := old[n-1] 121 item.index = -1 // for safety 122 *pq = old[:n-1] 123 return item 124 } 125 126 func (pq *priorityQueue) top() interface{} { 127 n := len(*pq) 128 if n == 0 { 129 return nil 130 } 131 return (*pq)[n-1] 132 } 133 134 // clip clips r against each image's bounds (after translating into 135 // the destination image's co-ordinate space) and shifts the point 136 // sp by the same amount as the change in r.Min. 137 func clip(dst draw.Image, r *image.Rectangle, src image.Image, sp *image.Point) { 138 orig := r.Min 139 *r = r.Intersect(dst.Bounds()) 140 *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp))) 141 dx := r.Min.X - orig.X 142 dy := r.Min.Y - orig.Y 143 if dx == 0 && dy == 0 { 144 return 145 } 146 (*sp).X += dx 147 (*sp).Y += dy 148 } 149 150 // MedianCutQuantizer constructs a palette with a maximum of 151 // NumColor colors by iteratively splitting clusters of color 152 // points mapped on a three-dimensional (RGB) Euclidian space. 153 // Once the number of clusters is within the specified bounds, 154 // the resulting color is computed by averaging those within 155 // each grouping. 156 type MedianCutQuantizer struct { 157 NumColor int 158 } 159 160 func (q *MedianCutQuantizer) medianCut(points []point) color.Palette { 161 if q.NumColor == 0 { 162 return color.Palette{} 163 } 164 165 initialBlock := newBlock(points) 166 initialBlock.shrink() 167 pq := &priorityQueue{} 168 heap.Init(pq) 169 heap.Push(pq, initialBlock) 170 171 for pq.Len() < q.NumColor && len(pq.top().(*block).points) > 1 { 172 longestBlock := heap.Pop(pq).(*block) 173 points := longestBlock.points 174 li := longestBlock.longestSideIndex() 175 // TODO: Instead of sorting the entire slice, finding the median using an 176 // algorithm like introselect would give much better performance. 177 sort.Sort(&pointSorter{ 178 points: points, 179 by: func(p1, p2 *point) bool { return p1[li] < p2[li] }, 180 }) 181 median := len(points) / 2 182 block1 := newBlock(points[:median]) 183 block2 := newBlock(points[median:]) 184 block1.shrink() 185 block2.shrink() 186 heap.Push(pq, block1) 187 heap.Push(pq, block2) 188 } 189 190 palette := make(color.Palette, q.NumColor) 191 var n int 192 for n = 0; pq.Len() > 0; n++ { 193 block := heap.Pop(pq).(*block) 194 var sum [numDimensions]int 195 for i := 0; i < len(block.points); i++ { 196 for j := 0; j < numDimensions; j++ { 197 sum[j] += block.points[i][j] 198 } 199 } 200 palette[n] = color.RGBA64{ 201 R: uint16(sum[0] / len(block.points)), 202 G: uint16(sum[1] / len(block.points)), 203 B: uint16(sum[2] / len(block.points)), 204 A: 0xFFFF, 205 } 206 } 207 // Trim to only the colors present in the image, which 208 // could be less than NumColor. 209 return palette[:n] 210 } 211 212 func (q *MedianCutQuantizer) Quantize(dst *image.Paletted, r image.Rectangle, src image.Image, sp image.Point) { 213 clip(dst, &r, src, &sp) 214 if r.Empty() { 215 return 216 } 217 218 points := make([]point, r.Dx()*r.Dy()) 219 colorSet := make(map[uint32]color.Color, q.NumColor) 220 i := 0 221 for y := r.Min.Y; y < r.Max.Y; y++ { 222 for x := r.Min.X; x < r.Max.X; x++ { 223 c := src.At(x, y) 224 r, g, b, _ := c.RGBA() 225 colorSet[(r>>8)<<16|(g>>8)<<8|b>>8] = c 226 points[i][0] = int(r) 227 points[i][1] = int(g) 228 points[i][2] = int(b) 229 i++ 230 } 231 } 232 if len(colorSet) <= q.NumColor { 233 // No need to quantize since the total number of colors 234 // fits within the palette. 235 dst.Palette = make(color.Palette, len(colorSet)) 236 i := 0 237 for _, c := range colorSet { 238 dst.Palette[i] = c 239 i++ 240 } 241 } else { 242 dst.Palette = q.medianCut(points) 243 } 244 245 for y := 0; y < r.Dy(); y++ { 246 for x := 0; x < r.Dx(); x++ { 247 // TODO: this should be done more efficiently. 248 dst.Set(sp.X+x, sp.Y+y, src.At(r.Min.X+x, r.Min.Y+y)) 249 } 250 } 251 } 252 253 func inPalette(p color.Palette, c color.Color) int { 254 ret := -1 255 for i, v := range p { 256 if v == c { 257 return i 258 } 259 } 260 return ret 261 } 262 263 func getSubPalette(m image.Image) color.Palette { 264 p := color.Palette{color.RGBA{0x00, 0x00, 0x00, 0x00}} 265 p9 := color.Palette(palette.Plan9) 266 b := m.Bounds() 267 black := false 268 for y := b.Min.Y; y < b.Max.Y; y++ { 269 for x := b.Min.X; x < b.Max.X; x++ { 270 c := m.At(x, y) 271 cc := p9.Convert(c) 272 if cc == p9[0] { 273 black = true 274 } 275 if inPalette(p, cc) == -1 { 276 p = append(p, cc) 277 } 278 } 279 } 280 if len(p) < 256 && black == true { 281 p[0] = color.RGBA{0x00, 0x00, 0x00, 0x00} // transparent 282 p = append(p, p9[0]) 283 } 284 return p 285 }