A cmd/corrupter/main.go => cmd/corrupter/main.go +93 -0
@@ 0,0 1,93 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "image/png"
+ "log"
+ "math/rand"
+ "os"
+ "time"
+
+ "github.com/r00tman/corrupter"
+)
+
+var seededRand = rand.New(rand.NewSource(1))
+
+func check(e error) {
+ if e != nil {
+ log.Fatal(e)
+ }
+}
+
+func main() {
+ // command line parsing
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage: %s [options] [input] [output]\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, " or: %s [options] - (for stdin+stdout processing)\n", os.Args[0])
+ flag.PrintDefaults()
+ }
+
+ var opts corrupter.Options
+
+ flag.Float64Var(&opts.Mag, "mag", corrupter.DefaultOptions.Mag, "dissolve blur strength")
+ flag.IntVar(&opts.Bheight, "bheight", corrupter.DefaultOptions.Bheight, "average distorted block height")
+ flag.Float64Var(&opts.Boffset, "boffset", corrupter.DefaultOptions.Boffset, "distorted block offset strength")
+ flag.Float64Var(&opts.Stride, "stride", corrupter.DefaultOptions.Stride, "distorted block stride strength")
+
+ flag.Float64Var(&opts.Lag, "lag", corrupter.DefaultOptions.Lag, "per-channel scanline lag strength")
+ flag.Float64Var(&opts.Lr, "lr", corrupter.DefaultOptions.Lr, "initial red scanline lag")
+ flag.Float64Var(&opts.Lg, "lg", corrupter.DefaultOptions.Lg, "initial green scanline lag")
+ flag.Float64Var(&opts.Lb, "lb", corrupter.DefaultOptions.Lb, "initial blue scanline lag")
+ flag.Float64Var(&opts.StdOffset, "stdoffset", corrupter.DefaultOptions.StdOffset, "std. dev. of red-blue channel offset (non-destructive)")
+ flag.UintVar(&opts.Add, "add", corrupter.DefaultOptions.Add, "additional brightness control (0-255)")
+
+ flag.Float64Var(&opts.MeanAbber, "meanabber", corrupter.DefaultOptions.MeanAbber, "mean chromatic abberation offset")
+ flag.Float64Var(&opts.StdAbber, "stdabber", corrupter.DefaultOptions.StdAbber, "std. dev. of chromatic abberation offset (lower values induce longer trails)")
+
+ flag.Int64Var(&opts.Seed, "seed", corrupter.DefaultOptions.Seed, "random seed. set to -1 if you want to generate it from time. the old version has used seed=1")
+
+ flag.Parse()
+
+ if opts.Seed == -1 {
+ seededRand = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
+ } else if opts.Seed != 1 {
+ seededRand = rand.New(rand.NewSource(opts.Seed))
+ }
+
+ // flag.Args() contain all non-option arguments, i.e., our input and output files
+ reader := (*os.File)(nil)
+ if len(flag.Args()) == 0 {
+ flag.Usage()
+ os.Exit(2)
+ } else if flag.Args()[0] == "-" {
+ // stdin/stdout processing
+ reader = os.Stdin
+ } else if len(flag.Args()) == 2 {
+ err := error(nil)
+ reader, err = os.Open(flag.Args()[0])
+ check(err)
+ } else {
+ flag.Usage()
+ os.Exit(2)
+ }
+ m, err := png.Decode(reader)
+ check(err)
+ reader.Close()
+
+ new_img1, err := corrupter.Corrupt(m, opts)
+ check(err)
+
+ // write the image
+ writer := (*os.File)(nil)
+ if flag.Args()[0] == "-" {
+ // stdin/stdout processing
+ writer = os.Stdout
+ } else {
+ writer, err = os.Create(flag.Args()[1])
+ check(err)
+ }
+ e := png.Encoder{CompressionLevel: png.NoCompression}
+ e.Encode(writer, new_img1)
+ writer.Close()
+}
M cmd/lock/lock.go => cmd/lock/lock.go +127 -216
@@ 5,19 5,18 @@ import (
"bufio"
"bytes"
"encoding/json"
- "errors"
- "fmt"
- "image"
"image/png"
+ "flag"
+ "fmt"
"io"
"log"
- "math/rand"
"os"
"os/exec"
"sync"
"time"
"golang.org/x/sys/unix"
+ "github.com/r00tman/corrupter"
)
const (
@@ 27,29 26,17 @@ const (
wrongColor = "#AF005F"
)
-const (
- mag = 7.
- bheight = 10
- boffset = 30.
- strideC = .1
- lag = .005
- lrC = -7.
- lgC = 0.
- lbC = 3.
- stdOffset = 10
- add = 10.
- meanAbber = 10.
- stdAbber = 10.
-)
-
func main() {
log.SetOutput(os.Stderr)
log.SetFlags(log.Ltime)
- swaylock := exec.Command("swaylock",
+ runparts := flag.Bool("runparts", true, "run hooks under $XDG_DATA_DIR/lock/{un,}locked.d/")
+ daemon := flag.Bool("daemonize", false, "don't block on the locking process")
+ flag.Parse()
+
+ swaylock := exec.Command("/home/robert/bin/swaylock",
"--color=#000000EE",
- "--daemonize",
- "--font-size=16",
+ "--font-size=18",
"--font=Iosevka Aile",
"--hide-keyboard-layout",
"--ignore-empty-password",
@@ 72,91 59,95 @@ func main() {
var wg sync.WaitGroup
- {
- swqOutput, err := exec.Command("swaymsg", "--type", "get_tree", "--raw").Output()
+ forOutput(&wg, func(output string) {
+ grim := exec.Command("grim", "-o", output, "-")
+ rc, err := grim.StdoutPipe()
if err != nil {
- log.Fatal(err)
+ log.Fatalf("%s: %v", output, err)
}
- var swqJson struct {
- Nodes []struct {
- Name string
- }
+ defer rc.Close()
+
+ // capture screenshot
+ log.Printf("+ %v", grim.Args)
+ if err := grim.Start(); err != nil {
+ log.Fatalf("%s: %v", output, err)
}
- if err := json.Unmarshal(swqOutput, &swqJson); err != nil {
- log.Fatal(err)
+
+ var buf bytes.Buffer
+ defer buf.Reset()
+
+ // process screenshot
+ log.Printf("%s: processing", output)
+ opts := corrupter.DefaultOptions
+ opts.Seed = time.Now().Unix()
+
+ m, err := png.Decode(rc)
+ if err != nil {
+ log.Fatalf("%s: %v", output, err)
+ }
+
+ img, err := corrupter.Corrupt(m, opts)
+ if err != nil {
+ log.Fatalf("%s: %v", output, err)
}
- for _, output := range swqJson.Nodes {
- if output.Name == "__i3" {
- continue
- }
- wg.Add(1)
- go func(output string) {
- defer wg.Done()
-
- grim := exec.Command("grim", "-o", output, "-")
- rc, err := grim.StdoutPipe()
- if err != nil {
- log.Fatalf("%s: %v", output, err)
- }
- defer rc.Close()
-
- // capture screenshot
- log.Printf("+ %v", grim.Args)
- if err := grim.Start(); err != nil {
- log.Fatalf("%s: %v", output, err)
- }
-
- var buf bytes.Buffer
- defer buf.Reset()
-
- // process screenshot
- log.Printf("%s: processing", output)
- if err := corrupter(rc, &buf, time.Now().Unix()); err != nil {
- log.Fatalf("%s: %v", output, err)
- }
-
- log.Printf("%s: processed %d bytes", output, buf.Len())
-
- // memfd to pass image from internal processing
- // to swaylock
- fd, err := unix.MemfdCreate("lock-"+output, 0)
- if err != nil {
- log.Fatalf("%s: %v", output, err)
- }
- if err := unix.Ftruncate(fd, int64(buf.Len())); err != nil {
- log.Fatal(err)
- }
- mem, err := unix.Mmap(fd, 0, buf.Len(), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
- if err != nil {
- log.Fatalf("%s: %v", output, err)
- }
- copy(mem, buf.Bytes())
- if err := unix.Munmap(mem); err != nil {
- log.Fatalf("%s: %v", output, err)
- }
-
- swaylock.ExtraFiles = append(swaylock.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("/proc/self/fd/%d", fd)))
- swaylock.Args = append(swaylock.Args, "--image="+output+":"+fmt.Sprintf("/proc/self/fd/%d", 2+len(swaylock.ExtraFiles)))
- }(output.Name)
+ e := png.Encoder{CompressionLevel: png.NoCompression}
+ if err := e.Encode(&buf, img); err != nil {
+ log.Fatalf("%s: %v", output, err)
}
- }
- {
- wg.Add(1)
- go func() {
- defer wg.Done()
- runparts := exec.Command("run-parts", "-v", os.ExpandEnv("${XDG_DATA_HOME}/lock.d"))
- logCmd(runparts)
- if err := runparts.Run(); err != nil {
- log.Fatal(err)
- }
- }()
- }
+ log.Printf("%s: processed %d bytes", output, buf.Len())
+
+ // memfd to pass image from internal processing
+ // to swaylock
+ memfd, err := memFd(buf.Bytes())
+ if err != nil {
+ log.Fatalf("%s: failed to create mem fd: %v", output, err)
+ }
+
+ swaylock.ExtraFiles = append(swaylock.ExtraFiles, memfd)
+ swaylock.Args = append(swaylock.Args, "--image="+output+":"+fmt.Sprintf("/proc/self/fd/%d", 2+len(swaylock.ExtraFiles)))
+ swaylock.Args = append(swaylock.Args, flag.Args()...)
+ })
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ if !*runparts {
+ return
+ }
+
+ runParts := exec.Command("run-parts", "-v", os.ExpandEnv("${XDG_DATA_HOME}/lock/locked.d"))
+ logCmd(runParts)
+ if err := runParts.Run(); err != nil {
+ log.Print(err)
+ }
+ }()
+
+ defer func() {
+ if !*runparts {
+ return
+ }
+
+ runParts := exec.Command("run-parts", "-v", os.ExpandEnv("${XDG_DATA_HOME}/lock/unlocked.d"))
+ logCmd(runParts)
+ if err := runParts.Run(); err != nil {
+ log.Print(err)
+ }
+ }()
wg.Wait()
logCmd(swaylock)
+
+ if *daemon {
+ if err := swaylock.Start(); err != nil {
+ log.Fatal(err)
+ }
+ os.Exit(0)
+ }
+
if err := swaylock.Run(); err != nil {
log.Fatal(err)
}
@@ 179,139 170,59 @@ func logCmd(c *exec.Cmd) {
go handler(c.StderrPipe())
}
-func corrupter(reader io.Reader, writer io.Writer, seed int64) error {
- seededRand := rand.New(rand.NewSource(seed))
-
- // flag.Args() contain all non-option arguments, i.e., our input and output files
- m, err := png.Decode(reader)
- if err != nil {
- return err
+func forOutput(wg *sync.WaitGroup, fn func(name string)) {
+ if wg == nil {
+ var _wg sync.WaitGroup
+ wg = &_wg
}
- // trying to obtain raw pointers to color data, since .At(), .Set() are very slow
- m_raw_stride, m_raw_pix := 0, []uint8(nil)
-
- switch m.(type) {
- default:
- return errors.New("unknown image type")
- case *image.NRGBA:
- m_raw := m.(*image.NRGBA)
- m_raw_stride = m_raw.Stride
- m_raw_pix = m_raw.Pix
- case *image.RGBA:
- m_raw := m.(*image.RGBA)
- m_raw_stride = m_raw.Stride
- m_raw_pix = m_raw.Pix
+ swqOutput, err := exec.Command("swaymsg", "--type", "get_tree", "--raw").Output()
+ if err != nil {
+ log.Fatal(err)
}
-
- b := m.Bounds()
-
- // first stage is dissolve+block corruption
- new_img := image.NewNRGBA(b)
- line_off := 0
- stride := 0.
- yset := 0
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- // Every BHEIGHT lines in average a new distorted block begins
- if seededRand.Intn(bheight*b.Max.X) == 0 {
- line_off = offset(seededRand, boffset)
- stride = seededRand.NormFloat64() * strideC
- yset = y
- }
- // at the line where the block has begun, we don't want to offset the image
- // so stride_off is 0 on the block's line
- stride_off := int(stride * float64(y-yset))
-
- // offset is composed of the blur, block offset, and skew offset (stride)
- offx := offset(seededRand, mag) + line_off + stride_off
- offy := offset(seededRand, mag)
-
- // copy the corresponding pixel (4 bytes) to the new image
- src_idx := m_raw_stride*wrap(y+offy, b.Max.Y) + 4*wrap(x+offx, b.Max.X)
- dst_idx := new_img.Stride*y + 4*x
-
- copy(new_img.Pix[dst_idx:dst_idx+4], m_raw_pix[src_idx:src_idx+4])
+ var swqJson struct {
+ Nodes []struct {
+ Name string
}
}
-
- // second stage is adding per-channel scan inconsistency and brightening
- new_img1 := image.NewNRGBA(b)
-
- lr, lg, lb := lrC, lgC, lbC
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- lr += seededRand.NormFloat64() * lag
- lg += seededRand.NormFloat64() * lag
- lb += seededRand.NormFloat64() * lag
- offx := offset(seededRand, stdOffset)
-
- // obtain source pixel base offsets. red/blue border is also smoothed by offx
- ra_idx := new_img.Stride*y + 4*wrap(x+int(lr)-offx, b.Max.X)
- g_idx := new_img.Stride*y + 4*wrap(x+int(lg), b.Max.X)
- b_idx := new_img.Stride*y + 4*wrap(x+int(lb)+offx, b.Max.X)
-
- // pixels are stored in (r, g, b, a) order in memory
- r := new_img.Pix[ra_idx]
- a := new_img.Pix[ra_idx+3]
- g := new_img.Pix[g_idx+1]
- b := new_img.Pix[b_idx+2]
-
- r, g, b = brighten(r, uint8(add)), brighten(g, uint8(add)), brighten(b, uint8(add))
-
- // copy the corresponding pixel (4 bytes) to the new image
- dst_idx := new_img1.Stride*y + 4*x
- copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
- }
+ if err := json.Unmarshal(swqOutput, &swqJson); err != nil {
+ log.Fatal(err)
}
- // third stage is to add chromatic abberation+chromatic trails
- // (trails happen because we're changing the same image we process)
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- offx := meanAbber + offset(seededRand, stdAbber) // lower offset arg = longer trails
-
- // obtain source pixel base offsets. only red and blue are distorted
- ra_idx := new_img1.Stride*y + 4*wrap(x+offx, b.Max.X)
- g_idx := new_img1.Stride*y + 4*x
- b_idx := new_img1.Stride*y + 4*wrap(x-offx, b.Max.X)
-
- // pixels are stored in (r, g, b, a) order in memory
- r := new_img1.Pix[ra_idx]
- a := new_img1.Pix[ra_idx+3]
- g := new_img1.Pix[g_idx+1]
- b := new_img1.Pix[b_idx+2]
-
- // copy the corresponding pixel (4 bytes) to the SAME image. this gets us nice colorful trails
- dst_idx := new_img1.Stride*y + 4*x
- copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
+ for _, output := range swqJson.Nodes {
+ if output.Name == "__i3" {
+ continue
}
+ wg.Add(1)
+ go func(name string) {
+ defer wg.Done()
+ fn(name)
+ }(output.Name)
}
- // write the image
- e := png.Encoder{CompressionLevel: png.NoCompression}
- return e.Encode(writer, new_img1)
+ if wg != nil {
+ wg.Wait()
+ }
}
-// force x to stay in [0, b) range. x is assumed to be in [-b,2*b) range
-func wrap(x, b int) int {
- if x < 0 {
- return x + b
+func memFd(data []byte) (*os.File, error) {
+ // memfd to pass image from internal processing
+ // to swaylock
+ fd, err := unix.MemfdCreate("", 0)
+ if err != nil {
+ return nil, err
}
- if x >= b {
- return x - b
+ if err := unix.Ftruncate(fd, int64(len(data))); err != nil {
+ return nil, err
+ }
+ mem, err := unix.Mmap(fd, 0, len(data), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
+ if err != nil {
+ return nil, err
+ }
+ copy(mem, data)
+ if err := unix.Munmap(mem); err != nil {
+ return nil, err
}
- return x
-}
-
-// get normally distributed (rounded to int) value with the specified std. dev.
-func offset(seededRand *rand.Rand, stddev float64) int {
- sample := seededRand.NormFloat64() * stddev
- return int(sample)
-}
-// brighten the color safely, i.e., by simultaneously reducing contrast
-func brighten(r uint8, add uint8) uint8 {
- r32, add32 := uint32(r), uint32(add)
- return uint8(r32 - r32*add32/255 + add32)
+ return os.NewFile(uintptr(fd), fmt.Sprintf("/proc/self/fd/%d", fd)), nil
}
A corrupter.go => corrupter.go +167 -0
@@ 0,0 1,167 @@
+package corrupter
+
+import (
+ "errors"
+ "image"
+ "math/rand"
+)
+
+var DefaultOptions = Options{
+ Mag: 7.,
+ Bheight: 10,
+ Boffset: 30.,
+ Stride: .1,
+ Lag: .005,
+ Lr: -7.,
+ Lg: 0.,
+ Lb: 3.,
+ StdOffset: 10,
+ Add: 10,
+ MeanAbber: 10.,
+ StdAbber: 10.,
+ Seed: -1,
+}
+
+type Options struct {
+ Mag float64
+ Bheight int
+ Boffset float64
+ Stride float64
+ Lag float64
+ Lr float64
+ Lg float64
+ Lb float64
+ StdOffset float64
+ Add uint
+ MeanAbber float64
+ StdAbber float64
+ Seed int64
+}
+
+func Corrupt(m image.Image, opts Options) (*image.NRGBA, error) {
+ seededRand := rand.New(rand.NewSource(opts.Seed))
+
+ // trying to obtain raw pointers to color data, since .At(), .Set() are very slow
+ m_raw_stride, m_raw_pix := 0, []uint8(nil)
+
+ switch m.(type) {
+ default:
+ return nil, errors.New("unknown image type")
+ case *image.NRGBA:
+ m_raw := m.(*image.NRGBA)
+ m_raw_stride = m_raw.Stride
+ m_raw_pix = m_raw.Pix
+ case *image.RGBA:
+ m_raw := m.(*image.RGBA)
+ m_raw_stride = m_raw.Stride
+ m_raw_pix = m_raw.Pix
+ }
+
+ b := m.Bounds()
+
+ // first stage is dissolve+block corruption
+ new_img := image.NewNRGBA(b)
+ line_off := 0
+ stride := 0.
+ yset := 0
+ for y := 0; y < b.Max.Y; y++ {
+ for x := 0; x < b.Max.X; x++ {
+ // Every BHEIGHT lines in average a new distorted block begins
+ if seededRand.Intn(opts.Bheight*b.Max.X) == 0 {
+ line_off = offset(seededRand, opts.Boffset)
+ stride = seededRand.NormFloat64() * opts.Stride
+ yset = y
+ }
+ // at the line where the block has begun, we don't want to offset the image
+ // so stride_off is 0 on the block's line
+ stride_off := int(stride * float64(y-yset))
+
+ // offset is composed of the blur, block offset, and skew offset (stride)
+ offx := offset(seededRand, opts.Mag) + line_off + stride_off
+ offy := offset(seededRand, opts.Mag)
+
+ // copy the corresponding pixel (4 bytes) to the new image
+ src_idx := m_raw_stride*wrap(y+offy, b.Max.Y) + 4*wrap(x+offx, b.Max.X)
+ dst_idx := new_img.Stride*y + 4*x
+
+ copy(new_img.Pix[dst_idx:dst_idx+4], m_raw_pix[src_idx:src_idx+4])
+ }
+ }
+
+ // second stage is adding per-channel scan inconsistency and brightening
+ new_img1 := image.NewNRGBA(b)
+
+ lr, lg, lb := opts.Lr, opts.Lg, opts.Lb
+ for y := 0; y < b.Max.Y; y++ {
+ for x := 0; x < b.Max.X; x++ {
+ lr += seededRand.NormFloat64() * opts.Lag
+ lg += seededRand.NormFloat64() * opts.Lag
+ lb += seededRand.NormFloat64() * opts.Lag
+ offx := offset(seededRand, opts.StdOffset)
+
+ // obtain source pixel base offsets. red/blue border is also smoothed by offx
+ ra_idx := new_img.Stride*y + 4*wrap(x+int(lr)-offx, b.Max.X)
+ g_idx := new_img.Stride*y + 4*wrap(x+int(lg), b.Max.X)
+ b_idx := new_img.Stride*y + 4*wrap(x+int(lb)+offx, b.Max.X)
+
+ // pixels are stored in (r, g, b, a) order in memory
+ r := new_img.Pix[ra_idx]
+ a := new_img.Pix[ra_idx+3]
+ g := new_img.Pix[g_idx+1]
+ b := new_img.Pix[b_idx+2]
+
+ r, g, b = brighten(r, uint8(opts.Add)), brighten(g, uint8(opts.Add)), brighten(b, uint8(opts.Add))
+
+ // copy the corresponding pixel (4 bytes) to the new image
+ dst_idx := new_img1.Stride*y + 4*x
+ copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
+ }
+ }
+
+ // third stage is to add chromatic abberation+chromatic trails
+ // (trails happen because we're changing the same image we process)
+ for y := 0; y < b.Max.Y; y++ {
+ for x := 0; x < b.Max.X; x++ {
+ offx := int(opts.MeanAbber) + offset(seededRand, opts.StdAbber) // lower offset arg = longer trails
+
+ // obtain source pixel base offsets. only red and blue are distorted
+ ra_idx := new_img1.Stride*y + 4*wrap(x+offx, b.Max.X)
+ g_idx := new_img1.Stride*y + 4*x
+ b_idx := new_img1.Stride*y + 4*wrap(x-offx, b.Max.X)
+
+ // pixels are stored in (r, g, b, a) order in memory
+ r := new_img1.Pix[ra_idx]
+ a := new_img1.Pix[ra_idx+3]
+ g := new_img1.Pix[g_idx+1]
+ b := new_img1.Pix[b_idx+2]
+
+ // copy the corresponding pixel (4 bytes) to the SAME image. this gets us nice colorful trails
+ dst_idx := new_img1.Stride*y + 4*x
+ copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
+ }
+ }
+
+ return new_img1, nil
+}
+
+// force x to stay in [0, b) range. x is assumed to be in [-b,2*b) range
+func wrap(x, b int) int {
+ if x < 0 {
+ return x + b
+ }
+ if x >= b {
+ return x - b
+ }
+ return x
+}
+
+// get normally distributed (rounded to int) value with the specified std. dev.
+func offset(seededRand *rand.Rand, stddev float64) int {
+ return int(seededRand.NormFloat64() * stddev)
+}
+
+// brighten the color safely, i.e., by simultaneously reducing contrast
+func brighten(r uint8, add uint8) uint8 {
+ r32, add32 := uint32(r), uint32(add)
+ return uint8(r32 - r32*add32/255 + add32)
+}
D main.go => main.go +0 -218
@@ 1,218 0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "image"
- "image/png"
- "log"
- "math/rand"
- "os"
- "time"
-)
-
-var seededRand = rand.New(rand.NewSource(1))
-
-func check(e error) {
- if e != nil {
- log.Fatal(e)
- }
-}
-
-// force x to stay in [0, b) range. x is assumed to be in [-b,2*b) range
-func wrap(x, b int) int {
- if x < 0 {
- return x + b
- }
- if x >= b {
- return x - b
- }
- return x
-}
-
-// get normally distributed (rounded to int) value with the specified std. dev.
-func offset(stddev float64) int {
- sample := seededRand.NormFloat64() * stddev
- return int(sample)
-}
-
-// brighten the color safely, i.e., by simultaneously reducing contrast
-func brighten(r uint8, add uint8) uint8 {
- r32, add32 := uint32(r), uint32(add)
- return uint8(r32 - r32*add32/255 + add32)
-}
-
-func main() {
- // command line parsing
- flag.Usage = func() {
- fmt.Fprintf(os.Stderr, "Usage: %s [options] [input] [output]\n", os.Args[0])
- fmt.Fprintf(os.Stderr, " or: %s [options] - (for stdin+stdout processing)\n", os.Args[0])
- flag.PrintDefaults()
- }
- magPtr := flag.Float64("mag", 7.0, "dissolve blur strength")
- blockHeightPtr := flag.Int("bheight", 10, "average distorted block height")
- blockOffsetPtr := flag.Float64("boffset", 30., "distorted block offset strength")
- strideMagPtr := flag.Float64("stride", 0.1, "distorted block stride strength")
-
- lagPtr := flag.Float64("lag", 0.005, "per-channel scanline lag strength")
- lrPtr := flag.Float64("lr", -7, "initial red scanline lag")
- lgPtr := flag.Float64("lg", 0, "initial green scanline lag")
- lbPtr := flag.Float64("lb", 3, "initial blue scanline lag")
- stdOffsetPtr := flag.Float64("stdoffset", 10, "std. dev. of red-blue channel offset (non-destructive)")
- addPtr := flag.Int("add", 37, "additional brightness control (0-255)")
-
- meanAbberPtr := flag.Int("meanabber", 10, "mean chromatic abberation offset")
- stdAbberPtr := flag.Float64("stdabber", 10, "std. dev. of chromatic abberation offset (lower values induce longer trails)")
-
- seedPtr := flag.Int64("seed", -1, "random seed. set to -1 if you want to generate it from time. the old version has used seed=1")
-
- flag.Parse()
-
- if *seedPtr == -1 {
- seededRand = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
- } else if *seedPtr != 1 {
- seededRand = rand.New(rand.NewSource(*seedPtr))
- }
-
- // flag.Args() contain all non-option arguments, i.e., our input and output files
- reader := (*os.File)(nil)
- if len(flag.Args()) == 0 {
- flag.Usage()
- os.Exit(2)
- } else if flag.Args()[0] == "-" {
- // stdin/stdout processing
- reader = os.Stdin
- } else if len(flag.Args()) == 2 {
- err := error(nil)
- reader, err = os.Open(flag.Args()[0])
- check(err)
- } else {
- flag.Usage()
- os.Exit(2)
- }
- m, err := png.Decode(reader)
- check(err)
- reader.Close()
-
- // trying to obtain raw pointers to color data, since .At(), .Set() are very slow
- m_raw_stride, m_raw_pix := 0, []uint8(nil)
-
- switch m.(type) {
- default:
- log.Fatal("unknown image type")
- case *image.NRGBA:
- m_raw := m.(*image.NRGBA)
- m_raw_stride = m_raw.Stride
- m_raw_pix = m_raw.Pix
- case *image.RGBA:
- m_raw := m.(*image.RGBA)
- m_raw_stride = m_raw.Stride
- m_raw_pix = m_raw.Pix
- }
-
- b := m.Bounds()
-
- // first stage is dissolve+block corruption
- new_img := image.NewNRGBA(b)
- line_off := 0
- stride := 0.
- yset := 0
- MAG := *magPtr
- BHEIGHT := *blockHeightPtr
- BOFFSET := *blockOffsetPtr
- STRIDE_MAG := *strideMagPtr
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- // Every BHEIGHT lines in average a new distorted block begins
- if seededRand.Intn(BHEIGHT*b.Max.X) == 0 {
- line_off = offset(BOFFSET)
- stride = seededRand.NormFloat64() * STRIDE_MAG
- yset = y
- }
- // at the line where the block has begun, we don't want to offset the image
- // so stride_off is 0 on the block's line
- stride_off := int(stride * float64(y-yset))
-
- // offset is composed of the blur, block offset, and skew offset (stride)
- offx := offset(MAG) + line_off + stride_off
- offy := offset(MAG)
-
- // copy the corresponding pixel (4 bytes) to the new image
- src_idx := m_raw_stride*wrap(y+offy, b.Max.Y) + 4*wrap(x+offx, b.Max.X)
- dst_idx := new_img.Stride*y + 4*x
-
- copy(new_img.Pix[dst_idx:dst_idx+4], m_raw_pix[src_idx:src_idx+4])
- }
- }
-
- // second stage is adding per-channel scan inconsistency and brightening
- new_img1 := image.NewNRGBA(b)
-
- lr, lg, lb := *lrPtr, *lgPtr, *lbPtr
- LAG := *lagPtr
- ADD := uint8(*addPtr)
- STD_OFFSET := *stdOffsetPtr
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- lr += seededRand.NormFloat64() * LAG
- lg += seededRand.NormFloat64() * LAG
- lb += seededRand.NormFloat64() * LAG
- offx := offset(STD_OFFSET)
-
- // obtain source pixel base offsets. red/blue border is also smoothed by offx
- ra_idx := new_img.Stride*y + 4*wrap(x+int(lr)-offx, b.Max.X)
- g_idx := new_img.Stride*y + 4*wrap(x+int(lg), b.Max.X)
- b_idx := new_img.Stride*y + 4*wrap(x+int(lb)+offx, b.Max.X)
-
- // pixels are stored in (r, g, b, a) order in memory
- r := new_img.Pix[ra_idx]
- a := new_img.Pix[ra_idx+3]
- g := new_img.Pix[g_idx+1]
- b := new_img.Pix[b_idx+2]
-
- r, g, b = brighten(r, ADD), brighten(g, ADD), brighten(b, ADD)
-
- // copy the corresponding pixel (4 bytes) to the new image
- dst_idx := new_img1.Stride*y + 4*x
- copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
- }
- }
-
- // third stage is to add chromatic abberation+chromatic trails
- // (trails happen because we're changing the same image we process)
- MEAN_ABBER := *meanAbberPtr
- STD_ABBER := *stdAbberPtr
- for y := 0; y < b.Max.Y; y++ {
- for x := 0; x < b.Max.X; x++ {
- offx := MEAN_ABBER + offset(STD_ABBER) // lower offset arg = longer trails
-
- // obtain source pixel base offsets. only red and blue are distorted
- ra_idx := new_img1.Stride*y + 4*wrap(x+offx, b.Max.X)
- g_idx := new_img1.Stride*y + 4*x
- b_idx := new_img1.Stride*y + 4*wrap(x-offx, b.Max.X)
-
- // pixels are stored in (r, g, b, a) order in memory
- r := new_img1.Pix[ra_idx]
- a := new_img1.Pix[ra_idx+3]
- g := new_img1.Pix[g_idx+1]
- b := new_img1.Pix[b_idx+2]
-
- // copy the corresponding pixel (4 bytes) to the SAME image. this gets us nice colorful trails
- dst_idx := new_img1.Stride*y + 4*x
- copy(new_img1.Pix[dst_idx:dst_idx+4], []uint8{r, g, b, a})
- }
- }
-
- // write the image
- writer := (*os.File)(nil)
- if flag.Args()[0] == "-" {
- // stdin/stdout processing
- writer = os.Stdout
- } else {
- writer, err = os.Create(flag.Args()[1])
- check(err)
- }
- e := png.Encoder{CompressionLevel: png.NoCompression}
- e.Encode(writer, new_img1)
- writer.Close()
-}