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

     1  package gift
     2  
     3  import (
     4  	"image"
     5  	"image/draw"
     6  	"math"
     7  )
     8  
     9  // Resampling is an interpolation algorithm used for image resizing.
    10  type Resampling interface {
    11  	Support() float32
    12  	Kernel(float32) float32
    13  }
    14  
    15  func bcspline(x, b, c float32) float32 {
    16  	if x < 0.0 {
    17  		x = -x
    18  	}
    19  	if x < 1.0 {
    20  		return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
    21  	}
    22  	if x < 2.0 {
    23  		return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
    24  	}
    25  	return 0
    26  }
    27  
    28  func sinc(x float32) float32 {
    29  	if x == 0 {
    30  		return 1.0
    31  	}
    32  	return float32(math.Sin(math.Pi*float64(x)) / (math.Pi * float64(x)))
    33  }
    34  
    35  type resamp struct {
    36  	name    string
    37  	support float32
    38  	kernel  func(float32) float32
    39  }
    40  
    41  func (r resamp) String() string {
    42  	return r.name
    43  }
    44  
    45  func (r resamp) Support() float32 {
    46  	return r.support
    47  }
    48  
    49  func (r resamp) Kernel(x float32) float32 {
    50  	return r.kernel(x)
    51  }
    52  
    53  // Nearest neighbor resampling filter.
    54  var NearestNeighborResampling Resampling
    55  
    56  // Box resampling filter.
    57  var BoxResampling Resampling
    58  
    59  // Linear resampling filter.
    60  var LinearResampling Resampling
    61  
    62  // Cubic resampling filter (Catmull-Rom).
    63  var CubicResampling Resampling
    64  
    65  // Lanczos resampling filter (3 lobes).
    66  var LanczosResampling Resampling
    67  
    68  func precomputeResamplingWeights(dstSize, srcSize int, resampling Resampling) [][]uweight {
    69  	du := float32(srcSize) / float32(dstSize)
    70  	scale := du
    71  	if scale < 1.0 {
    72  		scale = 1.0
    73  	}
    74  	ru := float32(math.Ceil(float64(scale * resampling.Support())))
    75  
    76  	tmp := make([]float32, int(ru+2)*2)
    77  	result := make([][]uweight, dstSize)
    78  
    79  	for v := 0; v < dstSize; v++ {
    80  		fU := (float32(v)+0.5)*du - 0.5
    81  
    82  		startu := int(math.Ceil(float64(fU - ru)))
    83  		if startu < 0 {
    84  			startu = 0
    85  		}
    86  		endu := int(math.Floor(float64(fU + ru)))
    87  		if endu > srcSize-1 {
    88  			endu = srcSize - 1
    89  		}
    90  
    91  		sumf := float32(0.0)
    92  		for u := startu; u <= endu; u++ {
    93  			w := resampling.Kernel((float32(u) - fU) / scale)
    94  			sumf += w
    95  			tmp[u-startu] = w
    96  		}
    97  		for u := startu; u <= endu; u++ {
    98  			w := float32(tmp[u-startu] / sumf)
    99  			result[v] = append(result[v], uweight{u, w})
   100  		}
   101  	}
   102  
   103  	return result
   104  }
   105  
   106  func resizeLine(dstBuf []pixel, srcBuf []pixel, weights [][]uweight) {
   107  	for dstu := 0; dstu < len(weights); dstu++ {
   108  		var r, g, b, a float32
   109  		for _, iw := range weights[dstu] {
   110  			c := srcBuf[iw.u]
   111  			wa := c.A * iw.weight
   112  			r += c.R * wa
   113  			g += c.G * wa
   114  			b += c.B * wa
   115  			a += wa
   116  		}
   117  		if a != 0 {
   118  			r /= a
   119  			g /= a
   120  			b /= a
   121  		}
   122  		dstBuf[dstu] = pixel{r, g, b, a}
   123  	}
   124  }
   125  
   126  func resizeHorizontal(dst draw.Image, src image.Image, w int, resampling Resampling, options *Options) {
   127  	srcb := src.Bounds()
   128  	dstb := dst.Bounds()
   129  
   130  	weights := precomputeResamplingWeights(w, srcb.Dx(), resampling)
   131  
   132  	pixGetter := newPixelGetter(src)
   133  	pixSetter := newPixelSetter(dst)
   134  
   135  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
   136  		srcBuf := make([]pixel, srcb.Dx())
   137  		dstBuf := make([]pixel, w)
   138  		for srcy := pmin; srcy < pmax; srcy++ {
   139  			pixGetter.getPixelRow(srcy, &srcBuf)
   140  			resizeLine(dstBuf, srcBuf, weights)
   141  			pixSetter.setPixelRow(dstb.Min.Y+srcy-srcb.Min.Y, dstBuf)
   142  		}
   143  	})
   144  }
   145  
   146  func resizeVertical(dst draw.Image, src image.Image, h int, resampling Resampling, options *Options) {
   147  	srcb := src.Bounds()
   148  	dstb := dst.Bounds()
   149  
   150  	weights := precomputeResamplingWeights(h, srcb.Dy(), resampling)
   151  
   152  	pixGetter := newPixelGetter(src)
   153  	pixSetter := newPixelSetter(dst)
   154  
   155  	parallelize(options.Parallelization, srcb.Min.X, srcb.Max.X, func(pmin, pmax int) {
   156  		srcBuf := make([]pixel, srcb.Dy())
   157  		dstBuf := make([]pixel, h)
   158  		for srcx := pmin; srcx < pmax; srcx++ {
   159  			pixGetter.getPixelColumn(srcx, &srcBuf)
   160  			resizeLine(dstBuf, srcBuf, weights)
   161  			pixSetter.setPixelColumn(dstb.Min.X+srcx-srcb.Min.X, dstBuf)
   162  		}
   163  	})
   164  }
   165  
   166  func resizeNearest(dst draw.Image, src image.Image, w, h int, options *Options) {
   167  	srcb := src.Bounds()
   168  	dstb := dst.Bounds()
   169  	dx := float64(srcb.Dx()) / float64(w)
   170  	dy := float64(srcb.Dy()) / float64(h)
   171  
   172  	pixGetter := newPixelGetter(src)
   173  	pixSetter := newPixelSetter(dst)
   174  
   175  	parallelize(options.Parallelization, dstb.Min.Y, dstb.Min.Y+h, func(pmin, pmax int) {
   176  		for dsty := pmin; dsty < pmax; dsty++ {
   177  			for dstx := dstb.Min.X; dstx < dstb.Min.X+w; dstx++ {
   178  				fx := math.Floor((float64(dstx-dstb.Min.X) + 0.5) * dx)
   179  				fy := math.Floor((float64(dsty-dstb.Min.Y) + 0.5) * dy)
   180  				srcx := srcb.Min.X + int(fx)
   181  				srcy := srcb.Min.Y + int(fy)
   182  				px := pixGetter.getPixel(srcx, srcy)
   183  				pixSetter.setPixel(dstx, dsty, px)
   184  			}
   185  		}
   186  	})
   187  }
   188  
   189  type resizeFilter struct {
   190  	width      int
   191  	height     int
   192  	resampling Resampling
   193  }
   194  
   195  func (p *resizeFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   196  	w, h := p.width, p.height
   197  	srcw, srch := srcBounds.Dx(), srcBounds.Dy()
   198  
   199  	if (w == 0 && h == 0) || w < 0 || h < 0 || srcw <= 0 || srch <= 0 {
   200  		dstBounds = image.Rect(0, 0, 0, 0)
   201  	} else if w == 0 {
   202  		fw := float64(h) * float64(srcw) / float64(srch)
   203  		dstw := int(math.Max(1.0, math.Floor(fw+0.5)))
   204  		dstBounds = image.Rect(0, 0, dstw, h)
   205  	} else if h == 0 {
   206  		fh := float64(w) * float64(srch) / float64(srcw)
   207  		dsth := int(math.Max(1.0, math.Floor(fh+0.5)))
   208  		dstBounds = image.Rect(0, 0, w, dsth)
   209  	} else {
   210  		dstBounds = image.Rect(0, 0, w, h)
   211  	}
   212  
   213  	return
   214  }
   215  
   216  func (p *resizeFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   217  	if options == nil {
   218  		options = &defaultOptions
   219  	}
   220  
   221  	b := p.Bounds(src.Bounds())
   222  	w, h := b.Dx(), b.Dy()
   223  
   224  	if w <= 0 || h <= 0 {
   225  		return
   226  	}
   227  
   228  	if src.Bounds().Dx() == w && src.Bounds().Dy() == h {
   229  		copyimage(dst, src, options)
   230  		return
   231  	}
   232  
   233  	if p.resampling.Support() <= 0 {
   234  		resizeNearest(dst, src, w, h, options)
   235  		return
   236  	}
   237  
   238  	if src.Bounds().Dx() == w {
   239  		resizeVertical(dst, src, h, p.resampling, options)
   240  		return
   241  	}
   242  
   243  	if src.Bounds().Dy() == h {
   244  		resizeHorizontal(dst, src, w, p.resampling, options)
   245  		return
   246  	}
   247  
   248  	tmp := createTempImage(image.Rect(0, 0, w, src.Bounds().Dy()))
   249  	resizeHorizontal(tmp, src, w, p.resampling, options)
   250  	resizeVertical(dst, tmp, h, p.resampling, options)
   251  	return
   252  }
   253  
   254  // Resize creates a filter that resizes an image to the specified width and height using the specified resampling.
   255  // If one of width or height is 0, the image aspect ratio is preserved.
   256  // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling.
   257  //
   258  // Example:
   259  //
   260  //	// Resize the src image to width=300 preserving the aspect ratio.
   261  //	g := gift.New(
   262  //		gift.Resize(300, 0, gift.LanczosResampling),
   263  //	)
   264  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   265  //	g.Draw(dst, src)
   266  //
   267  func Resize(width, height int, resampling Resampling) Filter {
   268  	return &resizeFilter{
   269  		width:      width,
   270  		height:     height,
   271  		resampling: resampling,
   272  	}
   273  }
   274  
   275  type resizeToFitFilter struct {
   276  	width      int
   277  	height     int
   278  	resampling Resampling
   279  }
   280  
   281  func (p *resizeToFitFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
   282  	w, h := p.width, p.height
   283  	srcw, srch := srcBounds.Dx(), srcBounds.Dy()
   284  
   285  	if w <= 0 || h <= 0 || srcw <= 0 || srch <= 0 {
   286  		return image.Rect(0, 0, 0, 0)
   287  	}
   288  
   289  	if srcw <= w && srch <= h {
   290  		return image.Rect(0, 0, srcw, srch)
   291  	}
   292  
   293  	wratio := float64(srcw) / float64(w)
   294  	hratio := float64(srch) / float64(h)
   295  
   296  	var dstw, dsth int
   297  	if wratio > hratio {
   298  		dstw = w
   299  		dsth = minint(int(float64(srch)/wratio+0.5), h)
   300  	} else {
   301  		dsth = h
   302  		dstw = minint(int(float64(srcw)/hratio+0.5), w)
   303  	}
   304  
   305  	return image.Rect(0, 0, dstw, dsth)
   306  }
   307  
   308  func (p *resizeToFitFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   309  	b := p.Bounds(src.Bounds())
   310  	Resize(b.Dx(), b.Dy(), p.resampling).Draw(dst, src, options)
   311  	return
   312  }
   313  
   314  // ResizeToFit creates a filter that resizes an image to fit within the specified dimensions while preserving the aspect ratio.
   315  // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling.
   316  func ResizeToFit(width, height int, resampling Resampling) Filter {
   317  	return &resizeToFitFilter{
   318  		width:      width,
   319  		height:     height,
   320  		resampling: resampling,
   321  	}
   322  }
   323  
   324  type resizeToFillFilter struct {
   325  	width      int
   326  	height     int
   327  	anchor     Anchor
   328  	resampling Resampling
   329  }
   330  
   331  func (p *resizeToFillFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
   332  	w, h := p.width, p.height
   333  	srcw, srch := srcBounds.Dx(), srcBounds.Dy()
   334  
   335  	if w <= 0 || h <= 0 || srcw <= 0 || srch <= 0 {
   336  		return image.Rect(0, 0, 0, 0)
   337  	}
   338  
   339  	return image.Rect(0, 0, w, h)
   340  }
   341  
   342  func (p *resizeToFillFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   343  	b := p.Bounds(src.Bounds())
   344  	w, h := b.Dx(), b.Dy()
   345  
   346  	if w <= 0 || h <= 0 {
   347  		return
   348  	}
   349  
   350  	srcw, srch := src.Bounds().Dx(), src.Bounds().Dy()
   351  
   352  	wratio := float64(srcw) / float64(w)
   353  	hratio := float64(srch) / float64(h)
   354  
   355  	var tmpw, tmph int
   356  	if wratio < hratio {
   357  		tmpw = w
   358  		tmph = maxint(int(float64(srch)/wratio+0.5), h)
   359  	} else {
   360  		tmph = h
   361  		tmpw = maxint(int(float64(srcw)/hratio+0.5), w)
   362  	}
   363  
   364  	tmp := createTempImage(image.Rect(0, 0, tmpw, tmph))
   365  	Resize(tmpw, tmph, p.resampling).Draw(tmp, src, options)
   366  	CropToSize(w, h, p.anchor).Draw(dst, tmp, options)
   367  
   368  	return
   369  }
   370  
   371  // ResizeToFill creates a filter that resizes an image to the smallest possible size that will cover the specified dimensions,
   372  // then crops the resized image to the specified dimensions using the specified anchor point.
   373  // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling.
   374  func ResizeToFill(width, height int, resampling Resampling, anchor Anchor) Filter {
   375  	return &resizeToFillFilter{
   376  		width:      width,
   377  		height:     height,
   378  		anchor:     anchor,
   379  		resampling: resampling,
   380  	}
   381  }
   382  
   383  func init() {
   384  	// Nearest neighbor resampling filter.
   385  	NearestNeighborResampling = resamp{
   386  		name:    "NearestNeighborResampling",
   387  		support: 0.0,
   388  		kernel: func(x float32) float32 {
   389  			return 0
   390  		},
   391  	}
   392  
   393  	// Box resampling filter.
   394  	BoxResampling = resamp{
   395  		name:    "BoxResampling",
   396  		support: 0.5,
   397  		kernel: func(x float32) float32 {
   398  			if x < 0.0 {
   399  				x = -x
   400  			}
   401  			if x <= 0.5 {
   402  				return 1.0
   403  			}
   404  			return 0
   405  		},
   406  	}
   407  
   408  	// Linear resampling filter.
   409  	LinearResampling = resamp{
   410  		name:    "LinearResampling",
   411  		support: 1.0,
   412  		kernel: func(x float32) float32 {
   413  			if x < 0.0 {
   414  				x = -x
   415  			}
   416  			if x < 1.0 {
   417  				return 1.0 - x
   418  			}
   419  			return 0
   420  		},
   421  	}
   422  
   423  	// Cubic resampling filter (Catmull-Rom).
   424  	CubicResampling = resamp{
   425  		name:    "CubicResampling",
   426  		support: 2.0,
   427  		kernel: func(x float32) float32 {
   428  			if x < 0.0 {
   429  				x = -x
   430  			}
   431  			if x < 2.0 {
   432  				return bcspline(x, 0.0, 0.5)
   433  			}
   434  			return 0
   435  		},
   436  	}
   437  
   438  	// Lanczos resampling filter (3 lobes).
   439  	LanczosResampling = resamp{
   440  		name:    "LanczosResampling",
   441  		support: 3.0,
   442  		kernel: func(x float32) float32 {
   443  			if x < 0.0 {
   444  				x = -x
   445  			}
   446  			if x < 3.0 {
   447  				return sinc(x) * sinc(x/3.0)
   448  			}
   449  			return 0
   450  		},
   451  	}
   452  }