github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/ebiten_no.go (about)

     1  //go:build gtxt
     2  
     3  package etxt
     4  
     5  import "image"
     6  import "image/draw"
     7  import "image/color"
     8  import "golang.org/x/image/math/fixed"
     9  
    10  type TargetImage = draw.Image
    11  type GlyphMask = *image.Alpha
    12  
    13  type MixMode uint8
    14  
    15  const (
    16  	MixOver       MixMode = 0 // glyphs drawn over target (default mode)
    17  	MixReplace    MixMode = 1 // glyph mask only (transparent pixels included!)
    18  	MixAdd        MixMode = 2 // add colors (black adds nothing, white stays white)
    19  	MixSub        MixMode = 3 // subtract colors (black removes nothing) (alpha = target)
    20  	MixMultiply   MixMode = 4 // multiply % of glyph and target colors and MixOver
    21  	MixCut        MixMode = 5 // cut glyph shape hole based on alpha (cutout text)
    22  	MixFiftyFifty MixMode = 6 // mix glyph and target hues 50%-50% and MixOver
    23  
    24  	// TODO: many of the modes above will have some trouble with
    25  	//       semi-transparency, I should look more into it.
    26  )
    27  const defaultMixMode = MixOver
    28  
    29  // this doesn't do anything in gtxt, only ebiten needs it
    30  func convertAlphaImageToGlyphMask(i *image.Alpha) GlyphMask { return i }
    31  
    32  // TODO: just remove glyph index from default draw func if unused everywhere.
    33  //       Maybe replace with ColorM? like... that would seem much more
    34  //       reasonable for traverse. we are doing a lot of color conversion each
    35  //       time. hmmmm... at least for ebitengine.
    36  
    37  // The default glyph drawing function used in renderers. Do not confuse with
    38  // the main [Renderer.Draw]() function. DefaultDrawFunc is a low level function,
    39  // rarely necessary except when paired with [Renderer.Traverse]*() operations.
    40  func (self *Renderer) DefaultDrawFunc(dot fixed.Point26_6, mask GlyphMask, _ GlyphIndex) {
    41  	if mask == nil {
    42  		return
    43  	} // spaces and empty glyphs will be nil
    44  
    45  	// compute src and target rects within bounds
    46  	targetBounds := self.target.Bounds()
    47  	srcRect := mask.Rect
    48  	shift := image.Pt(dot.X.Floor(), dot.Y.Floor())
    49  	targetRect := targetBounds.Intersect(srcRect.Add(shift))
    50  	if targetRect.Empty() {
    51  		return
    52  	}
    53  	shift.X, shift.Y = -shift.X, -shift.Y
    54  	srcRect = targetRect.Add(shift)
    55  
    56  	switch self.mixMode {
    57  	case MixReplace: // ---- source only ----
    58  		self.mixImageInto(mask, self.target, srcRect, targetRect,
    59  			func(new, _ color.Color) color.Color { return new })
    60  	case MixOver: // ---- default mixing ----
    61  		self.mixImageInto(mask, self.target, srcRect, targetRect, mixOverFunc)
    62  	case MixCut: // ---- remove alpha mode ----
    63  		self.mixImageInto(mask, self.target, srcRect, targetRect,
    64  			func(new, curr color.Color) color.Color {
    65  				_, _, _, na := new.RGBA()
    66  				if na == 0 {
    67  					return curr
    68  				}
    69  				cr, cg, cb, ca := curr.RGBA()
    70  
    71  				alpha := ca - na
    72  				if alpha < 0 {
    73  					alpha = 0
    74  				}
    75  				return color.RGBA64{
    76  					R: min32As16(cr, alpha),
    77  					G: min32As16(cg, alpha),
    78  					B: min32As16(cb, alpha),
    79  					A: uint16(alpha),
    80  				}
    81  			})
    82  	case MixMultiply: // ---- multiplicative mixing ----
    83  		self.mixImageInto(mask, self.target, srcRect, targetRect,
    84  			func(new, curr color.Color) color.Color {
    85  				nr, ng, nb, na := new.RGBA()
    86  				cr, cg, cb, ca := curr.RGBA()
    87  				pureMult := color.RGBA64{
    88  					R: uint16(nr * cr / 0xFFFF),
    89  					G: uint16(ng * cg / 0xFFFF),
    90  					B: uint16(nb * cb / 0xFFFF),
    91  					A: uint16(na * ca / 0xFFFF),
    92  				}
    93  				return mixOverFunc(pureMult, curr)
    94  			})
    95  	case MixAdd: // --- additive mixing ----
    96  		self.mixImageInto(mask, self.target, srcRect, targetRect,
    97  			func(new, curr color.Color) color.Color {
    98  				nr, ng, nb, na := new.RGBA()
    99  				if na == 0 {
   100  					return curr
   101  				}
   102  				cr, cg, cb, ca := curr.RGBA()
   103  				return color.RGBA64{
   104  					R: uint16N(nr + cr),
   105  					G: uint16N(ng + cg),
   106  					B: uint16N(nb + cb),
   107  					A: uint16N(na + ca),
   108  				}
   109  			})
   110  	case MixSub: // --- subtractive mixing (only color) ----
   111  		self.mixImageInto(mask, self.target, srcRect, targetRect,
   112  			func(new, curr color.Color) color.Color {
   113  				nr, ng, nb, na := new.RGBA()
   114  				if na == 0 {
   115  					return curr
   116  				}
   117  				cr, cg, cb, ca := curr.RGBA()
   118  				return color.RGBA64{
   119  					R: uint32subFloor16(cr, nr),
   120  					G: uint32subFloor16(cg, ng),
   121  					B: uint32subFloor16(cb, nb),
   122  					A: uint16(ca),
   123  				}
   124  			})
   125  	case MixFiftyFifty: // ---- 50%-50% hue mixing ----
   126  		self.mixImageInto(mask, self.target, srcRect, targetRect,
   127  			func(new, curr color.Color) color.Color {
   128  				var nr, ng, nb, na uint32
   129  				nrgba, isNrgba := new.(color.NRGBA64)
   130  				if isNrgba {
   131  					nr, ng, nb, na = uint32(nrgba.R), uint32(nrgba.G), uint32(nrgba.B), uint32(nrgba.A)
   132  				} else {
   133  					nr, ng, nb, na = new.RGBA()
   134  				}
   135  				if na == 0 {
   136  					return curr
   137  				}
   138  				if !isNrgba && na != 65535 {
   139  					panic("broken assumptions")
   140  				}
   141  				cr, cg, cb, ca := curr.RGBA()
   142  				alphaSum := na + ca
   143  				if alphaSum > 65535 {
   144  					alphaSum = 65535
   145  				}
   146  				partial := color.NRGBA64{
   147  					R: uint16((nr + cr) / 2),
   148  					G: uint16((ng + cg) / 2),
   149  					B: uint16((nb + cb) / 2),
   150  					A: uint16(na),
   151  				}
   152  				return mixOverFunc(partial, curr)
   153  			})
   154  	//case MixCMYK:
   155  	// pigment color mixing? mixbox?
   156  	//   black = 1 - max(R, G, B)/255
   157  	//   cyan  = (1 - (R/255 - K))/(1 - K)
   158  	//   magenta ... like cyan, but with G
   159  	//   yellow ... like cyan, but with B
   160  	// TODO: what about shifting gradients with hue?
   161  	default:
   162  		panic("unexpected mix mode")
   163  	}
   164  }
   165  
   166  // All this code is extremely slow due to using a very straightforward
   167  // implementation. Making this faster, though, is not so trivial.
   168  func (self *Renderer) mixImageInto(src GlyphMask, target draw.Image, srcRect, tarRect image.Rectangle, mixFunc func(color.Color, color.Color) color.Color) {
   169  	width := srcRect.Dx()
   170  	height := srcRect.Dy()
   171  	srcOffX := srcRect.Min.X
   172  	srcOffY := srcRect.Min.Y
   173  	tarOffX := tarRect.Min.X
   174  	tarOffY := tarRect.Min.Y
   175  
   176  	r, g, b, a := self.mainColor.RGBA()
   177  	transColor := color.RGBA64{0, 0, 0, 0}
   178  	directColor := color.RGBA64{
   179  		R: uint16(r),
   180  		G: uint16(g),
   181  		B: uint16(b),
   182  		A: uint16(a),
   183  	}
   184  	nrgba := color.NRGBA64{
   185  		R: uint16(r),
   186  		G: uint16(g),
   187  		B: uint16(b),
   188  		A: 0,
   189  	}
   190  
   191  	for y := 0; y < height; y++ {
   192  		for x := 0; x < width; x++ {
   193  			// get mask alpha applied to our main drawing color
   194  			level := src.AlphaAt(srcOffX+x, srcOffY+y).A
   195  			var newColor color.Color
   196  			if level == 0 {
   197  				newColor = transColor
   198  			} else if level == 255 {
   199  				newColor = directColor
   200  			} else {
   201  				nrgba.A = uint16((a * uint32(level)) / 255)
   202  				newColor = nrgba
   203  			}
   204  
   205  			// get target current color and mix
   206  			currColor := target.At(tarOffX+x, tarOffY+y)
   207  			mixColor := mixFunc(newColor, currColor)
   208  			target.Set(tarOffX+x, tarOffY+y, mixColor)
   209  		}
   210  	}
   211  }
   212  
   213  func uint16N(value uint32) uint16 {
   214  	if value > 65535 {
   215  		return 65535
   216  	}
   217  	return uint16(value)
   218  }
   219  
   220  func uint32subFloor16(a, b uint32) uint16 {
   221  	if b >= a {
   222  		return 0
   223  	}
   224  	return uint16(a - b)
   225  }
   226  
   227  func min32As16(a, b uint32) uint16 {
   228  	if a <= b {
   229  		return uint16(a)
   230  	}
   231  	return uint16(b)
   232  }
   233  
   234  // ---- color mixing functions ----
   235  func mixOverFunc(new, curr color.Color) color.Color {
   236  	nr, ng, nb, na := new.RGBA()
   237  	if na == 0xFFFF {
   238  		return new
   239  	}
   240  	if na == 0 {
   241  		return curr
   242  	}
   243  	cr, cg, cb, ca := curr.RGBA()
   244  	if ca == 0 {
   245  		return new
   246  	}
   247  
   248  	return color.RGBA64{
   249  		R: uint16N((nr*0xFFFF + cr*(0xFFFF-na)) / 0xFFFF),
   250  		G: uint16N((ng*0xFFFF + cg*(0xFFFF-na)) / 0xFFFF),
   251  		B: uint16N((nb*0xFFFF + cb*(0xFFFF-na)) / 0xFFFF),
   252  		A: uint16N((na*0xFFFF + ca*(0xFFFF-na)) / 0xFFFF),
   253  	}
   254  }