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  }