summaryrefslogtreecommitdiff
path: root/autodistortion/autodistortion.go
blob: 0804402900a53a0171d1e24a314e5453bc03bfcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
Copyright (C) 2019 Leo Tenenbaum

This file is part of AutoDistortion.

AutoDistortion is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

AutoDistortion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with AutoDistortion.  If not, see <https://www.gnu.org/licenses/>.
*/
package autodistortion

import (
    "image"
    "image/color"
    "github.com/pommicket/autodistortion/autoutils"
)

func mod(a int, b int) int {
    // Because a % b is the remainder, not modulus!
    return ((a % b) + b) % b
}

type workInfo struct {
    img image.Image
    xfunction autoutils.Function
    yfunction autoutils.Function
}

func DistortRows(info *workInfo, out [][]color.RGBA, yFrom int, yTo int,
                 done chan struct{}) {

    minX := info.img.Bounds().Min.X
    maxX := info.img.Bounds().Max.X
    minY := info.img.Bounds().Min.Y
    maxY := info.img.Bounds().Max.Y
    width := maxX - minX
    height := maxY - minY
    fwidth := float64(width)
    fheight := float64(height)

    xy := make([]float64, 2)
    for y := yFrom; y < yTo; y++ {
        xy[1] = (float64(y) - float64(minY)) / fheight
        for x := 0; x < width; x++ {
            xy[0] = float64(x) / fwidth
            srcX := mod(int(info.xfunction.Evaluate(xy) * fwidth),  width)  + minX
            srcY := mod(int(info.yfunction.Evaluate(xy) * fheight), height) + minY
            r, g, b, a := info.img.At(int(srcX), int(srcY)).RGBA()
            out[y][x] = color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8),
                                   uint8(a >> 8)}
        }
    }
    done <- struct{}{}

}

func Distort(img image.Image, functionLen int, nThreads int) image.Image {
    rgba := image.NewRGBA(img.Bounds())

    var xfunction autoutils.Function
    var yfunction autoutils.Function
    xfunction.Generate(2, functionLen)
    yfunction.Generate(2, functionLen)

    minY := img.Bounds().Min.Y
    maxY := img.Bounds().Max.Y
    minX := img.Bounds().Min.X
    maxX := img.Bounds().Max.X
    width := maxX - minX
    height := maxY - minY

    if width == 0 || height == 0 {
        // I don't even know if this is possible, but just in case
        return rgba
    }

    info := workInfo{img, xfunction, yfunction}

    // Make a slice of pixels (image.RGBA.Set isn't thread safe ): )
    pixels := make([][]color.RGBA, height)
    done := make(chan struct{})
    for y := 0; y < height; y++ {
        pixels[y] = make([]color.RGBA, width)
    }

    if nThreads > height {
        nThreads = height // Don't make more than one thread per row
    }

    for t := 0; t < nThreads; t++ {
        yFrom := t * (height / nThreads) + minY
        var yTo int
        if t == nThreads - 1 { // Deal with final thread
            yTo = maxY // (go to end of image)
        } else {
            yTo = (t+1) * (height / nThreads) + minY
        }
        go DistortRows(&info, pixels, yFrom, yTo, done)
    }

    for y := 0; y < nThreads; y++ {
        <-done // Wait for all goroutines to finish
    }

    for y := 0; y < height; y++ {
        for x := 0; x < width; x++ {
            rgba.Set(x, y, pixels[y][x])
        }
    }

    return rgba
}