package goeffects

// Package copied from https://github.com/markdaws/go-effects

import "image"
import "runtime"


// NewCartoon returns an effect that renders images as if they are drawn like a cartoon.
// It works by rendering the input image using the OilPainting effect, then drawing lines
// ontop of the image based on the Sobel edge detection method. You will probably have to
// play with the opts values to get a good result. Some starting values are:
// BlurKernelSize: 21
// EdgeThreshold: 40
// OilFilterSize: 15
// OilLevels: 15
func NewCartoon(opts CTOpts) Effect {
	return &cartoon{
		opts: opts,
	}
}

// CTOpts options to pass to the Cartoon effect
type CTOpts struct {
	// BlurKernelSize is the gaussian blur kernel size. You might need to blur
	// the original input image to reduce the amount of noise you get in the edge
	// detection phase. Set to 0 to skip blur, otherwise the number must be an
	// odd number, the bigger the number the more blur
	BlurKernelSize int

	// EdgeThreshold is a number between 0 and 255 that specifies a cutoff point to
	// determine if an intensity change is an edge. Make smaller to include more details
	// as edges
	EdgeThreshold int

	// OilFilterSize specifies how bold the simulated strokes will be when turning the
	// style towards a painting, something around 5,10,15 should work well
	OilFilterSize int

	// OilLevels is the number of levels that the oil painting style will bucket colors in
	// to. Larger number to get more detail.
	OilLevels int

	// DebugPath is not empty is assumed to be a path where intermediate debug files can
	// be written to, such as the gaussian blured image and the sobel edge detection. This
	// can be useful for tweaking parameters
	DebugPath string
}

type cartoon struct {
	opts CTOpts
}

// Apply runs the image through the cartoon filter
func (c *cartoon) Apply(img *Image, numRoutines int) (*Image, error) {
	if numRoutines == 0 {
		numRoutines = runtime.GOMAXPROCS(0)
	}

	pipeline := Pipeline{}
	if c.opts.BlurKernelSize > 0 {
		pipeline.Add(NewGaussian(c.opts.BlurKernelSize, 1), nil)
	}
	pipeline.Add(NewGrayscale(GSLUMINOSITY), nil)
	pipeline.Add(NewSobel(c.opts.EdgeThreshold, false), nil)
	edgeImg, err := pipeline.Run(img, numRoutines)
	if err != nil {
		return nil, err
	}
	edgePix := edgeImg.img.Pix
	pf := func(ri, x, y, offset, inStride int, inPix, outPix []uint8) {

		r := inPix[offset]
		g := inPix[offset+1]
		b := inPix[offset+2]

		rEdge := edgePix[offset]
		if rEdge == 255 {
			r = 0
			b = 0
			g = 0
		}

		outPix[offset] = r
		outPix[offset+1] = g
		outPix[offset+2] = b
		outPix[offset+3] = 255
	}

	oil := NewOilPainting(c.opts.OilFilterSize, c.opts.OilLevels)
	oilImg, err := oil.Apply(img, numRoutines)
	if err != nil {
		return nil, err
	}

	out := &Image{
		img: image.NewRGBA(image.Rectangle{
			Min: image.Point{X: 0, Y: 0},
			Max: image.Point{X: img.Width, Y: img.Height},
		}),
		Width:  img.Width,
		Height: img.Height,

		// Have to take in to account pixels are lost in some of the effects around the edges,
		// so so only have the area where the two rections intersect from the edge detection and
		// the oil painting effect
		Bounds: oilImg.Bounds.Intersect(edgeImg.Bounds),
	}
	runParallel(numRoutines, oilImg, out.Bounds, out, pf, 0)
	return out, nil
}


