mirror of
https://github.com/esimov/pigo-wasm-demos.git
synced 2025-09-26 20:31:20 +08:00
wasm: re-implemented face pixelator demo
This commit is contained in:
@@ -3,11 +3,12 @@ package pixelate
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/esimov/pigo-wasm-demos/detector"
|
||||
"github.com/esimov/pigo-wasm-demos/pixels"
|
||||
)
|
||||
|
||||
// Canvas struct holds the Javascript objects needed for the Canvas creation
|
||||
@@ -23,10 +24,14 @@ type Canvas struct {
|
||||
windowSize struct{ width, height int }
|
||||
|
||||
// Canvas properties
|
||||
canvas js.Value
|
||||
ctx js.Value
|
||||
reqID js.Value
|
||||
renderer js.Func
|
||||
canvas js.Value
|
||||
ellipse js.Value
|
||||
offscreen js.Value
|
||||
ctx js.Value
|
||||
ctxMask js.Value
|
||||
ctxOffscr js.Value
|
||||
reqID js.Value
|
||||
renderer js.Func
|
||||
|
||||
// Webcam properties
|
||||
navigator js.Value
|
||||
@@ -35,30 +40,28 @@ type Canvas struct {
|
||||
// Canvas interaction related variables
|
||||
showPupil bool
|
||||
showFrame bool
|
||||
useNoise bool
|
||||
|
||||
// Quantizer related variables
|
||||
numOfColors int
|
||||
cellSize int
|
||||
noiseLevel int
|
||||
|
||||
frame *image.NRGBA
|
||||
}
|
||||
|
||||
// SubImager is a wrapper implementing the SubImage method from the image package.
|
||||
type SubImager interface {
|
||||
SubImage(r image.Rectangle) image.Image
|
||||
}
|
||||
|
||||
const (
|
||||
minColors = 2
|
||||
maxColors = 32
|
||||
minCellSize = 8
|
||||
maxCellSize = 30
|
||||
minColors = 2
|
||||
maxColors = 32
|
||||
minCellSize = 8
|
||||
maxCellSize = 30
|
||||
minNoiseLevel = 0
|
||||
maxNoiseLevel = 20
|
||||
noiseLevel = 0
|
||||
)
|
||||
|
||||
var (
|
||||
det *detector.Detector
|
||||
quant Quant
|
||||
pigo *detector.Detector
|
||||
quant *Quant
|
||||
)
|
||||
|
||||
// NewCanvas creates and initializes the new Canvas element
|
||||
@@ -68,25 +71,36 @@ func NewCanvas() *Canvas {
|
||||
c.doc = c.window.Get("document")
|
||||
c.body = c.doc.Get("body")
|
||||
|
||||
c.windowSize.width = 640
|
||||
c.windowSize.height = 480
|
||||
c.windowSize.width = 768
|
||||
c.windowSize.height = 576
|
||||
|
||||
c.canvas = c.doc.Call("createElement", "canvas")
|
||||
c.ellipse = c.doc.Call("createElement", "canvas")
|
||||
c.offscreen = c.doc.Call("createElement", "canvas")
|
||||
|
||||
c.canvas.Set("width", c.windowSize.width)
|
||||
c.canvas.Set("height", c.windowSize.height)
|
||||
c.canvas.Set("id", "canvas")
|
||||
c.body.Call("appendChild", c.canvas)
|
||||
|
||||
c.ellipse.Set("width", c.windowSize.width)
|
||||
c.ellipse.Set("height", c.windowSize.height)
|
||||
|
||||
c.offscreen.Set("width", c.windowSize.width)
|
||||
c.offscreen.Set("height", c.windowSize.height)
|
||||
|
||||
c.ctx = c.canvas.Call("getContext", "2d")
|
||||
c.ctxMask = c.ellipse.Call("getContext", "2d")
|
||||
c.ctxOffscr = c.offscreen.Call("getContext", "2d")
|
||||
|
||||
c.showPupil = false
|
||||
c.showFrame = false
|
||||
c.useNoise = false
|
||||
|
||||
c.numOfColors = 8
|
||||
c.cellSize = 10
|
||||
|
||||
det = detector.NewDetector()
|
||||
quant = Quant{}
|
||||
pigo = detector.NewDetector()
|
||||
quant = NewQuantizer()
|
||||
|
||||
return &c
|
||||
}
|
||||
@@ -97,7 +111,7 @@ func (c *Canvas) Render() error {
|
||||
var data = make([]byte, width*height*4)
|
||||
c.done = make(chan struct{})
|
||||
|
||||
err := det.UnpackCascades()
|
||||
err := pigo.UnpackCascades()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,14 +128,14 @@ func (c *Canvas) Render() error {
|
||||
// be able to transfer it from Javascript to Go via the js.CopyBytesToGo function.
|
||||
uint8Arr := js.Global().Get("Uint8Array").New(rgba)
|
||||
js.CopyBytesToGo(data, uint8Arr)
|
||||
gray := c.rgbaToGrayscale(data)
|
||||
gray := pixels.RgbaToGrayscale(data, width, height)
|
||||
|
||||
// Reset the data slice to its default values to avoid unnecessary memory allocation.
|
||||
// Otherwise, the GC won't clean up the memory address allocated by this slice
|
||||
// and the memory will keep up increasing by each iteration.
|
||||
data = make([]byte, len(data))
|
||||
|
||||
res := det.DetectFaces(gray, height, width)
|
||||
res := pigo.DetectFaces(gray, height, width)
|
||||
c.drawDetection(data, res)
|
||||
|
||||
c.window.Get("stats").Call("end")
|
||||
@@ -202,82 +216,37 @@ func (c *Canvas) StartWebcam() (*Canvas, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// rgbaToGrayscale converts the rgb pixel values to grayscale
|
||||
func (c *Canvas) rgbaToGrayscale(data []uint8) []uint8 {
|
||||
rows, cols := c.windowSize.width, c.windowSize.height
|
||||
for r := 0; r < rows; r++ {
|
||||
for c := 0; c < cols; c++ {
|
||||
// gray = 0.2*red + 0.7*green + 0.1*blue
|
||||
data[r*cols+c] = uint8(math.Round(
|
||||
0.2126*float64(data[r*4*cols+4*c+0]) +
|
||||
0.7152*float64(data[r*4*cols+4*c+1]) +
|
||||
0.0722*float64(data[r*4*cols+4*c+2])))
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// pixToImage converts an array buffer to an image.
|
||||
func (c *Canvas) pixToImage(pixels []uint8, dim int) image.Image {
|
||||
c.frame = image.NewNRGBA(image.Rect(0, 0, dim, dim))
|
||||
bounds := c.frame.Bounds()
|
||||
dx, dy := bounds.Max.X, bounds.Max.Y
|
||||
col := color.NRGBA{
|
||||
R: uint8(0),
|
||||
G: uint8(0),
|
||||
B: uint8(0),
|
||||
A: uint8(255),
|
||||
}
|
||||
|
||||
for y := bounds.Min.Y; y < dy; y++ {
|
||||
for x := bounds.Min.X; x < dx*4; x += 4 {
|
||||
col.R = uint8(pixels[x+y*dx*4])
|
||||
col.G = uint8(pixels[x+y*dx*4+1])
|
||||
col.B = uint8(pixels[x+y*dx*4+2])
|
||||
col.A = uint8(pixels[x+y*dx*4+3])
|
||||
|
||||
c.frame.SetNRGBA(y, int(x/4), col)
|
||||
}
|
||||
}
|
||||
return c.frame
|
||||
}
|
||||
|
||||
// imgToPix converts an image to an array buffer
|
||||
func (c *Canvas) imgToPix(img image.Image) []uint8 {
|
||||
bounds := img.Bounds()
|
||||
pixels := make([]uint8, 0, bounds.Max.X*bounds.Max.Y*4)
|
||||
|
||||
for i := bounds.Min.X; i < bounds.Max.X; i++ {
|
||||
for j := bounds.Min.Y; j < bounds.Max.Y; j++ {
|
||||
r, g, b, _ := img.At(i, j).RGBA()
|
||||
pixels = append(pixels, uint8(r>>8), uint8(g>>8), uint8(b>>8), 255)
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
}
|
||||
|
||||
// pixelate pixelates the detected face region
|
||||
func (c *Canvas) pixelate(data []uint8, dets []int, useNoise bool) []uint8 {
|
||||
func (c *Canvas) pixelate(data []uint8, size image.Rectangle, noiseLevel int) []uint8 {
|
||||
// Converts the array buffer to an image
|
||||
img := c.pixToImage(data, int(float64(dets[2])*0.8))
|
||||
img := pixels.PixToImage(data, size)
|
||||
|
||||
// Quantize the substracted image in order to reduce the number of colors.
|
||||
// This will results in a pixelated subtype image.
|
||||
cell := quant.Draw(img, c.numOfColors, c.cellSize, useNoise)
|
||||
return c.imgToPix(cell)
|
||||
// This will create a new pixelated subtype image.
|
||||
cell := quant.Draw(img, c.numOfColors, c.cellSize, noiseLevel)
|
||||
|
||||
dst := image.NewNRGBA(cell.Bounds())
|
||||
draw.Draw(dst, cell.Bounds(), cell, image.Point{}, draw.Over)
|
||||
|
||||
return pixels.ImgToPix(dst)
|
||||
}
|
||||
|
||||
// drawDetection draws the detected faces and eyes.
|
||||
func (c *Canvas) drawDetection(data []uint8, dets [][]int) {
|
||||
for i := 0; i < len(dets); i++ {
|
||||
if dets[i][3] > 50 {
|
||||
var scaleX, scaleY, invScaleX, invScaleY float64
|
||||
var grad js.Value
|
||||
|
||||
for _, det := range dets {
|
||||
leftPupil := pigo.DetectLeftPupil(det)
|
||||
rightPupil := pigo.DetectRightPupil(det)
|
||||
|
||||
if det[3] > 50 {
|
||||
c.ctx.Call("beginPath")
|
||||
c.ctx.Set("lineWidth", 2)
|
||||
c.ctx.Set("strokeStyle", "rgba(255, 0, 0, 0.5)")
|
||||
|
||||
row, col, scale := dets[i][1], dets[i][0], dets[i][2]
|
||||
col = col + int(float64(col)*0.1)
|
||||
scale = int(float64(scale) * 0.8)
|
||||
row, col, scale := det[1], det[0], det[2]
|
||||
//scale = int(float64(scale) * 0.9)
|
||||
|
||||
if c.showFrame {
|
||||
c.ctx.Call("rect", row-scale/2, col-scale/2, scale, scale)
|
||||
@@ -288,26 +257,76 @@ func (c *Canvas) drawDetection(data []uint8, dets [][]int) {
|
||||
uint8Arr := js.Global().Get("Uint8Array").New(subimg)
|
||||
js.CopyBytesToGo(imgData, uint8Arr)
|
||||
|
||||
buffer := c.pixelate(imgData, dets[i], c.useNoise)
|
||||
uint8Arr = js.Global().Get("Uint8Array").New(scale * scale * 4)
|
||||
js.CopyBytesToJS(uint8Arr, buffer)
|
||||
// Draw the ellipse mask.
|
||||
{
|
||||
scx, scy := int(float64(scale)*0.8/1.6), int(float64(scale)*0.8/2.0)
|
||||
rx, ry := scx/2, scy/2
|
||||
|
||||
uint8Clamped := js.Global().Get("Uint8ClampedArray").New(uint8Arr)
|
||||
rawData := js.Global().Get("ImageData").New(uint8Clamped, scale)
|
||||
if rx >= ry {
|
||||
scaleX, invScaleX = 1, 1
|
||||
scaleY = float64(rx) / float64(ry)
|
||||
invScaleY = float64(ry) / float64(rx)
|
||||
grad = c.ctxMask.Call("createRadialGradient", scale/2, float64(scale/2)*invScaleY, 0, scale/2, float64(scale/2)*invScaleY, scx)
|
||||
} else {
|
||||
scaleY, invScaleY = 1, 1
|
||||
scaleX = float64(ry) / float64(rx)
|
||||
invScaleX = float64(rx) / float64(ry)
|
||||
grad = c.ctxMask.Call("createRadialGradient", float64(scale/2)*invScaleX, scale/2, 0, float64(scale/2)*invScaleX, scale/2, scy)
|
||||
}
|
||||
|
||||
// Replace the underlying face region byte array with the quantized values.
|
||||
c.ctx.Call("putImageData", rawData, row-scale/2, col-scale/2)
|
||||
c.ctx.Call("stroke")
|
||||
grad.Call("addColorStop", 0.67, "rgba(0, 0, 0, 255)")
|
||||
grad.Call("addColorStop", 0.82, "rgba(255, 255, 255, 0)")
|
||||
|
||||
// Clear the canvas on each frame.
|
||||
c.ctxMask.Call("clearRect", 0, 0, c.windowSize.width, c.windowSize.height)
|
||||
c.ctxMask.Call("setTransform", scaleX, 0, 0, scaleY, 0, 0)
|
||||
|
||||
c.ctxMask.Set("fillStyle", grad)
|
||||
c.ctxMask.Call("fillRect", 0, 0, scale, scale)
|
||||
}
|
||||
|
||||
// Draw the pixelated image into the ellipse gradient using composite operation.
|
||||
{
|
||||
rect := image.Rect(0, 0, scale, scale)
|
||||
buffer := c.pixelate(imgData, rect, c.noiseLevel)
|
||||
|
||||
uint8Arr = js.Global().Get("Uint8Array").New(scale * scale * 4)
|
||||
js.CopyBytesToJS(uint8Arr, buffer)
|
||||
|
||||
uint8Clamped := js.Global().Get("Uint8ClampedArray").New(uint8Arr)
|
||||
rawData := js.Global().Get("ImageData").New(uint8Clamped, scale)
|
||||
|
||||
// Clear out the canvas on each frame.
|
||||
c.ctxOffscr.Call("clearRect", 0, 0, c.windowSize.width, c.windowSize.height)
|
||||
// Replace the underlying face region with the blurred image.
|
||||
c.ctxOffscr.Call("putImageData", rawData, 0, 0)
|
||||
|
||||
// Calculate the lean angle between the pupils.
|
||||
angle := 1 - (math.Atan2(float64(rightPupil.Col-leftPupil.Col), float64(rightPupil.Row-leftPupil.Row)) * 180 / math.Pi / 90)
|
||||
|
||||
c.ctxOffscr.Call("save")
|
||||
c.ctxOffscr.Call("translate", scale/2, scale/2)
|
||||
c.ctxOffscr.Call("rotate", js.ValueOf(angle).Float())
|
||||
c.ctxOffscr.Call("translate", -scale/2, -scale/2)
|
||||
|
||||
// Apply the ellipse mask over the source image by using composite operation.
|
||||
c.ctxOffscr.Set("globalCompositeOperation", "destination-atop")
|
||||
c.ctxOffscr.Call("drawImage", c.ellipse, 0, 0)
|
||||
c.ctxOffscr.Call("restore")
|
||||
|
||||
// Combine all the layers.
|
||||
c.ctx.Call("drawImage", c.offscreen, row-scale/2, col-scale/2)
|
||||
}
|
||||
|
||||
if c.showPupil {
|
||||
leftPupil := det.DetectLeftPupil(dets[i])
|
||||
leftPupil := pigo.DetectLeftPupil(det)
|
||||
if leftPupil != nil {
|
||||
col, row, scale := leftPupil.Col, leftPupil.Row, leftPupil.Scale/8
|
||||
c.ctx.Call("moveTo", col+int(scale), row)
|
||||
c.ctx.Call("arc", col, row, scale, 0, 2*math.Pi, true)
|
||||
}
|
||||
|
||||
rightPupil := det.DetectRightPupil(dets[i])
|
||||
rightPupil := pigo.DetectRightPupil(det)
|
||||
if rightPupil != nil {
|
||||
col, row, scale := rightPupil.Col, rightPupil.Row, rightPupil.Scale/8
|
||||
c.ctx.Call("moveTo", col+int(scale), row)
|
||||
@@ -328,8 +347,14 @@ func (c *Canvas) detectKeyPress() {
|
||||
c.showPupil = !c.showPupil
|
||||
case keyCode.String() == "f":
|
||||
c.showFrame = !c.showFrame
|
||||
case keyCode.String() == "n":
|
||||
c.useNoise = !c.useNoise
|
||||
case keyCode.String() == "'":
|
||||
if c.noiseLevel <= maxNoiseLevel {
|
||||
c.noiseLevel += 2
|
||||
}
|
||||
case keyCode.String() == ";":
|
||||
if c.noiseLevel > minNoiseLevel {
|
||||
c.noiseLevel -= 2
|
||||
}
|
||||
case keyCode.String() == "=":
|
||||
if c.numOfColors <= maxColors {
|
||||
c.numOfColors++
|
||||
|
@@ -3,23 +3,14 @@ package pixelate
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
*gg.Context
|
||||
}
|
||||
|
||||
// Brightness factor
|
||||
var bf = 1.0005
|
||||
|
||||
// Draw creates uniform cells with the quantified cell color of the source image.
|
||||
func (quant *Quant) Draw(img image.Image, numOfColors int, csize int, useNoise bool) image.Image {
|
||||
var cellSize int
|
||||
func (quant *Quant) Draw(src image.Image, numOfColors int, cellSize int, noiseLevel int) image.Image {
|
||||
dx, dy := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
dst := image.NewNRGBA64(src.Bounds())
|
||||
|
||||
dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
// Calculate the image aspect ratio.
|
||||
imgRatio := func(w, h int) float64 {
|
||||
var ratio float64
|
||||
if w > h {
|
||||
@@ -30,18 +21,13 @@ func (quant *Quant) Draw(img image.Image, numOfColors int, csize int, useNoise b
|
||||
return ratio
|
||||
}
|
||||
|
||||
if csize == 0 {
|
||||
cellSize = int(round(imgRatio(dx, dy) * 0.015))
|
||||
if cellSize == 0 {
|
||||
cellSize = int(imgRatio(dx, dy) * 0.015)
|
||||
} else {
|
||||
cellSize = csize
|
||||
cellSize = cellSize
|
||||
}
|
||||
qimg := quant.Quantize(img, numOfColors)
|
||||
|
||||
ctx := &context{gg.NewContext(dx, dy)}
|
||||
ctx.SetRGB(1, 1, 1)
|
||||
ctx.Clear()
|
||||
ctx.SetRGB(0, 0, 0)
|
||||
rgba := ctx.convertToNRGBA64(qimg)
|
||||
qimg := quant.Quantize(src, numOfColors)
|
||||
|
||||
for x := 0; x < dx; x += cellSize {
|
||||
for y := 0; y < dy; y += cellSize {
|
||||
@@ -50,27 +36,25 @@ func (quant *Quant) Draw(img image.Image, numOfColors int, csize int, useNoise b
|
||||
if rect.Empty() {
|
||||
rect = image.ZR
|
||||
}
|
||||
subImg := rgba.SubImage(rect).(*image.NRGBA64)
|
||||
cellColor := ctx.getAvgColor(subImg)
|
||||
ctx.drawCell(float64(x), float64(y), float64(cellSize), cellColor)
|
||||
subImg := qimg.(*image.Paletted).SubImage(rect).(*image.Paletted)
|
||||
cellColor := getAvgColor(subImg)
|
||||
|
||||
// Fill up the cell with the quantified color.
|
||||
for xx := x; xx < x+cellSize; xx++ {
|
||||
for yy := y; yy < y+cellSize; yy++ {
|
||||
dst.Set(xx, yy, cellColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctxImg := ctx.Image()
|
||||
if useNoise {
|
||||
return noise(ctxImg, dx, dy, 10)
|
||||
if noiseLevel > 0 {
|
||||
addNoise(dst, noiseLevel)
|
||||
}
|
||||
return ctxImg
|
||||
}
|
||||
|
||||
// drawCell draws the cell filling up with the quantified color
|
||||
func (ctx *context) drawCell(x, y, cellSize float64, c color.NRGBA64) {
|
||||
ctx.DrawRectangle(x, y, x+cellSize, y+cellSize)
|
||||
ctx.SetRGBA(float64(c.R/255^0xff)*bf, float64(c.G/255^0xff)*bf, float64(c.B/255^0xff)*bf, 1)
|
||||
ctx.Fill()
|
||||
return dst
|
||||
}
|
||||
|
||||
// getAvgColor get the average color of a cell
|
||||
func (ctx *context) getAvgColor(img *image.NRGBA64) color.NRGBA64 {
|
||||
func getAvgColor(img *image.Paletted) color.NRGBA64 {
|
||||
var (
|
||||
bounds = img.Bounds()
|
||||
r, g, b int
|
||||
@@ -78,52 +62,17 @@ func (ctx *context) getAvgColor(img *image.NRGBA64) color.NRGBA64 {
|
||||
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
var c = img.NRGBA64At(x, y)
|
||||
r += int(c.R)
|
||||
g += int(c.G)
|
||||
b += int(c.B)
|
||||
cr, cg, cb, _ := img.At(x, y).RGBA()
|
||||
r += int(cr)
|
||||
g += int(cg)
|
||||
b += int(cb)
|
||||
}
|
||||
}
|
||||
|
||||
return color.NRGBA64{
|
||||
R: maxUint16(0, minUint16(65535, uint16(r/(bounds.Dx()*bounds.Dy())))),
|
||||
G: maxUint16(0, minUint16(65535, uint16(g/(bounds.Dx()*bounds.Dy())))),
|
||||
B: maxUint16(0, minUint16(65535, uint16(b/(bounds.Dx()*bounds.Dy())))),
|
||||
R: max(0, min(65535, uint16(r/(bounds.Dx()*bounds.Dy())))),
|
||||
G: max(0, min(65535, uint16(g/(bounds.Dx()*bounds.Dy())))),
|
||||
B: max(0, min(65535, uint16(b/(bounds.Dx()*bounds.Dy())))),
|
||||
A: 255,
|
||||
}
|
||||
}
|
||||
|
||||
// convertToNRGBA64 converts an image.Image into an image.NRGBA64.
|
||||
func (ctx *context) convertToNRGBA64(img image.Image) *image.NRGBA64 {
|
||||
var (
|
||||
bounds = img.Bounds()
|
||||
nrgba = image.NewNRGBA64(bounds)
|
||||
)
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
nrgba.Set(x, y, img.At(x, y))
|
||||
}
|
||||
}
|
||||
return nrgba
|
||||
}
|
||||
|
||||
// round number down.
|
||||
func round(x float64) float64 {
|
||||
return math.Floor(x)
|
||||
}
|
||||
|
||||
// minUint16 returns the smallest number between two uint16 numbers.
|
||||
func minUint16(x, y uint16) uint16 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// maxUint16 returns the biggest number between two uint16 numbers.
|
||||
func maxUint16(x, y uint16) uint16 {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
27
pixelate/math.go
Normal file
27
pixelate/math.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package pixelate
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
// Min returns the slowest value of the provided parameters.
|
||||
func min[T constraints.Ordered](values ...T) T {
|
||||
var acc T = values[0]
|
||||
|
||||
for _, v := range values {
|
||||
if v < acc {
|
||||
acc = v
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
// max returns the biggest value of the provided parameters.
|
||||
func max[T constraints.Ordered](values ...T) T {
|
||||
var acc T = values[0]
|
||||
|
||||
for _, v := range values {
|
||||
if v > acc {
|
||||
acc = v
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
@@ -9,19 +9,16 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// SubImager is a wrapper implementing the SubImage method from the image package.
|
||||
type SubImager interface {
|
||||
SubImage(r image.Rectangle) image.Image
|
||||
}
|
||||
|
||||
// Interface which implements the Quantize method.
|
||||
type Quantizer interface {
|
||||
Quantize(image.Image, draw.Image, int, bool, bool) image.Image
|
||||
}
|
||||
|
||||
// Image quantization method. Returns a paletted image.
|
||||
// We need to use type assertion to match the interface returning type.
|
||||
func (q Quant) Quantize(img image.Image, nq int) image.Image {
|
||||
qz := newQuantizer(img, nq) // set up a work space
|
||||
qz.cluster() // cluster pixels by color
|
||||
return qz.Paletted().(image.Image) // generate paletted image from clusters
|
||||
}
|
||||
|
||||
// A workspace with members that can be accessed by methods.
|
||||
type Quant struct {
|
||||
img image.Image // original image
|
||||
@@ -37,9 +34,11 @@ type cluster struct {
|
||||
chRange uint32 // value range (vmax-vmin) of widest channel
|
||||
}
|
||||
|
||||
type point struct{ x, y int }
|
||||
type chValues []uint32
|
||||
type queue []*cluster
|
||||
type (
|
||||
point struct{ x, y int }
|
||||
chValues []uint32
|
||||
queue []*cluster
|
||||
)
|
||||
|
||||
const (
|
||||
rx = iota
|
||||
@@ -47,6 +46,19 @@ const (
|
||||
bx
|
||||
)
|
||||
|
||||
// NewQuantizer is a constructor method which initializes a new Quantizer.
|
||||
func NewQuantizer() *Quant {
|
||||
return &Quant{}
|
||||
}
|
||||
|
||||
// Quantize returns a paletted image.
|
||||
// We need to use type assertion to match the interface returning type.
|
||||
func (q Quant) Quantize(img image.Image, nq int) image.Image {
|
||||
qz := newQuantizer(img, nq) // set up a work space
|
||||
qz.cluster() // cluster pixels by color
|
||||
return qz.Paletted().(image.Image) // generate paletted image from clusters
|
||||
}
|
||||
|
||||
func newQuantizer(img image.Image, nq int) *Quant {
|
||||
b := img.Bounds()
|
||||
npx := (b.Max.X - b.Min.X) * (b.Max.Y - b.Min.Y)
|
||||
|
@@ -13,20 +13,21 @@ type prng struct {
|
||||
div float64
|
||||
}
|
||||
|
||||
// noise apply a noise factor to the source image
|
||||
func noise(pxl image.Image, w, h int, amount int) *image.NRGBA64 {
|
||||
noiseImg := image.NewNRGBA64(image.Rect(0, 0, w, h))
|
||||
// addNoise applies a noise factor to the source image.
|
||||
func addNoise(src *image.NRGBA64, amount int) {
|
||||
size := src.Bounds().Size()
|
||||
prng := &prng{
|
||||
a: 16807,
|
||||
m: 0x7fffffff,
|
||||
rand: 1.0,
|
||||
div: 1.0 / 0x7fffffff,
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
for y := 0; y < h; y++ {
|
||||
|
||||
for x := 0; x < size.X; x++ {
|
||||
for y := 0; y < size.Y; y++ {
|
||||
noise := (prng.randomSeed() - 0.1) * float64(amount)
|
||||
r, g, b, a := pxl.At(x, y).RGBA()
|
||||
rf, gf, bf := float64(r>>8), float64(g>>8), float64(b>>8)
|
||||
r, g, b, a := src.At(x, y).RGBA()
|
||||
rf, gf, bf := float64(r), float64(g), float64(b)
|
||||
|
||||
// Check if color do not overflow the maximum limit after noise has been applied
|
||||
if math.Abs(rf+noise) < 255 && math.Abs(gf+noise) < 255 && math.Abs(bf+noise) < 255 {
|
||||
@@ -37,10 +38,10 @@ func noise(pxl image.Image, w, h int, amount int) *image.NRGBA64 {
|
||||
r2 := max(0, min(255, uint8(rf)))
|
||||
g2 := max(0, min(255, uint8(gf)))
|
||||
b2 := max(0, min(255, uint8(bf)))
|
||||
noiseImg.Set(x, y, color.RGBA{R: r2, G: g2, B: b2, A: uint8(a)})
|
||||
|
||||
src.Set(x, y, color.RGBA{R: r2, G: g2, B: b2, A: uint8(a)})
|
||||
}
|
||||
}
|
||||
return noiseImg
|
||||
}
|
||||
|
||||
// nextLongRand generates a new random number based on the provided seed.
|
||||
@@ -66,19 +67,3 @@ func (prng *prng) randomSeed() float64 {
|
||||
prng.rand = prng.nextLongRand(prng.rand)
|
||||
return float64(prng.rand) * prng.div
|
||||
}
|
||||
|
||||
// min returns the smallest number between two numbers.
|
||||
func min(x, y uint8) uint8 {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// max returns the biggest number between two numbers.
|
||||
func max(x, y uint8) uint8 {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
Reference in New Issue
Block a user