github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/gift.go (about)

     1  /*
     2  
     3  Package gift provides a set of useful image processing filters.
     4  
     5  Basic usage:
     6  
     7  	// 1. Create a new GIFT and add some filters:
     8  
     9  	g := gift.New(
    10  	    gift.Resize(800, 0, gift.LanczosResampling),
    11  	    gift.UnsharpMask(1.0, 1.0, 0.0),
    12  	)
    13  
    14  	// 2. Create a new image of the corresponding size.
    15  	// dst is a new target image, src is the original image
    16  
    17  	dst := image.NewRGBA(g.Bounds(src.Bounds()))
    18  
    19  	// 3. Use Draw func to apply the filters to src and store the result in dst:
    20  
    21  	g.Draw(dst, src)
    22  
    23  */
    24  package gift
    25  
    26  import (
    27  	"image"
    28  	"image/draw"
    29  )
    30  
    31  // Filter is an image processing filter.
    32  type Filter interface {
    33  	// Draw applies the filter to the src image and outputs the result to the dst image.
    34  	Draw(dst draw.Image, src image.Image, options *Options)
    35  	// Bounds calculates the appropriate bounds of an image after applying the filter.
    36  	Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle)
    37  }
    38  
    39  // Options is the parameters passed to image processing filters.
    40  type Options struct {
    41  	Parallelization bool
    42  }
    43  
    44  var defaultOptions = Options{
    45  	Parallelization: true,
    46  }
    47  
    48  // GIFT implements a list of filters that can be applied to an image at once.
    49  type GIFT struct {
    50  	Filters []Filter
    51  	Options Options
    52  }
    53  
    54  // New creates a new instance of the filter toolkit and initializes it with the given list of filters.
    55  func New(filters ...Filter) *GIFT {
    56  	return &GIFT{
    57  		Filters: filters,
    58  		Options: defaultOptions,
    59  	}
    60  }
    61  
    62  // SetParallelization enables or disables faster image processing using parallel goroutines.
    63  // Parallelization is enabled by default.
    64  // To achieve maximum performance, make sure to allow Go to utilize all CPU cores:
    65  //
    66  // 	runtime.GOMAXPROCS(runtime.NumCPU())
    67  //
    68  func (g *GIFT) SetParallelization(isEnabled bool) {
    69  	g.Options.Parallelization = isEnabled
    70  }
    71  
    72  // Parallelization returns the current state of parallelization option.
    73  func (g *GIFT) Parallelization() bool {
    74  	return g.Options.Parallelization
    75  }
    76  
    77  // Add appends the given filters to the list of filters.
    78  func (g *GIFT) Add(filters ...Filter) {
    79  	g.Filters = append(g.Filters, filters...)
    80  }
    81  
    82  // Empty removes all the filters from the list.
    83  func (g *GIFT) Empty() {
    84  	g.Filters = []Filter{}
    85  }
    86  
    87  // Bounds calculates the appropriate bounds for the result image after applying all the added filters.
    88  // Parameter srcBounds is the bounds of the source image.
    89  //
    90  // Example:
    91  //
    92  // 	src := image.NewRGBA(image.Rect(0, 0, 100, 200))
    93  //	g := gift.New(gift.Rotate90())
    94  //
    95  // 	// calculate image bounds after applying rotation and create a new image of that size.
    96  // 	dst := image.NewRGBA(g.Bounds(src.Bounds())) // dst bounds: (0, 0, 200, 100)
    97  //
    98  //
    99  func (g *GIFT) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   100  	b := srcBounds
   101  	for _, f := range g.Filters {
   102  		b = f.Bounds(b)
   103  	}
   104  	dstBounds = b
   105  	return
   106  }
   107  
   108  // Draw applies all the added filters to the src image and outputs the result to the dst image.
   109  func (g *GIFT) Draw(dst draw.Image, src image.Image) {
   110  	if len(g.Filters) == 0 {
   111  		copyimage(dst, src, &g.Options)
   112  		return
   113  	}
   114  
   115  	first, last := 0, len(g.Filters)-1
   116  	var tmpIn image.Image
   117  	var tmpOut draw.Image
   118  
   119  	for i, f := range g.Filters {
   120  		if i == first {
   121  			tmpIn = src
   122  		} else {
   123  			tmpIn = tmpOut
   124  		}
   125  
   126  		if i == last {
   127  			tmpOut = dst
   128  		} else {
   129  			tmpOut = createTempImage(f.Bounds(tmpIn.Bounds()))
   130  		}
   131  
   132  		f.Draw(tmpOut, tmpIn, &g.Options)
   133  	}
   134  }
   135  
   136  // Operator is an image composition operator.
   137  type Operator int
   138  
   139  const (
   140  	CopyOperator Operator = iota
   141  	OverOperator
   142  )
   143  
   144  // DrawAt applies all the added filters to the src image and outputs the result to the dst image
   145  // at the specified position pt using the specified composition operator op.
   146  func (g *GIFT) DrawAt(dst draw.Image, src image.Image, pt image.Point, op Operator) {
   147  	switch op {
   148  	case OverOperator:
   149  		tb := g.Bounds(src.Bounds())
   150  		tb = tb.Sub(tb.Min).Add(pt)
   151  		tmp := createTempImage(tb)
   152  		g.Draw(tmp, src)
   153  		pixGetterDst := newPixelGetter(dst)
   154  		pixGetterTmp := newPixelGetter(tmp)
   155  		pixSetterDst := newPixelSetter(dst)
   156  		ib := tb.Intersect(dst.Bounds())
   157  		parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(pmin, pmax int) {
   158  			for y := pmin; y < pmax; y++ {
   159  				for x := ib.Min.X; x < ib.Max.X; x++ {
   160  					px0 := pixGetterDst.getPixel(x, y)
   161  					px1 := pixGetterTmp.getPixel(x, y)
   162  					c1 := px1.A
   163  					c0 := (1 - c1) * px0.A
   164  					cs := c0 + c1
   165  					c0 /= cs
   166  					c1 /= cs
   167  					r := px0.R*c0 + px1.R*c1
   168  					g := px0.G*c0 + px1.G*c1
   169  					b := px0.B*c0 + px1.B*c1
   170  					a := px0.A + px1.A*(1-px0.A)
   171  					pixSetterDst.setPixel(x, y, pixel{r, g, b, a})
   172  				}
   173  			}
   174  		})
   175  
   176  	default:
   177  		if pt.Eq(dst.Bounds().Min) {
   178  			g.Draw(dst, src)
   179  			return
   180  		}
   181  		if subimg, ok := getSubImage(dst, pt); ok {
   182  			g.Draw(subimg, src)
   183  			return
   184  		}
   185  		tb := g.Bounds(src.Bounds())
   186  		tb = tb.Sub(tb.Min).Add(pt)
   187  		tmp := createTempImage(tb)
   188  		g.Draw(tmp, src)
   189  		pixGetter := newPixelGetter(tmp)
   190  		pixSetter := newPixelSetter(dst)
   191  		ib := tb.Intersect(dst.Bounds())
   192  		parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(pmin, pmax int) {
   193  			for y := pmin; y < pmax; y++ {
   194  				for x := ib.Min.X; x < ib.Max.X; x++ {
   195  					pixSetter.setPixel(x, y, pixGetter.getPixel(x, y))
   196  				}
   197  			}
   198  		})
   199  	}
   200  }
   201  
   202  func getSubImage(img draw.Image, pt image.Point) (draw.Image, bool) {
   203  	if !pt.In(img.Bounds()) {
   204  		return nil, false
   205  	}
   206  	switch img := img.(type) {
   207  	case *image.Gray:
   208  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   209  	case *image.Gray16:
   210  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   211  	case *image.RGBA:
   212  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   213  	case *image.RGBA64:
   214  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   215  	case *image.NRGBA:
   216  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   217  	case *image.NRGBA64:
   218  		return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
   219  	default:
   220  		return nil, false
   221  	}
   222  }