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 }