├── .github
├── FUNDING.yml
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── cmd
├── atkinson.go
├── burkes.go
├── floydsteinberg.go
├── main.go
├── sierra2.go
├── sierra3.go
├── sierra_lite.go
└── stucki.go
├── dither_color.go
├── dither_mono.go
├── ditherer.go
├── go.mod
├── input
├── Lenna.png
├── david.jpg
├── flower.jpg
├── gopher.jpg
└── portal.jpg
└── output
├── color
├── Atkinson.png
├── Burkes.png
├── FloydSteinberg.png
├── Sierra-2.png
├── Sierra-3.png
├── Sierra-Lite.png
└── Stucki.png
└── mono
├── Atkinson.png
├── Burkes.png
├── FloydSteinberg.png
├── Sierra-2.png
├── Sierra-3.png
├── Sierra-Lite.png
└── Stucki.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: esimov
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | strategy:
13 | matrix:
14 | go-version: [~1.14, ~1.13, ~1.12, ~1.11]
15 | runs-on: ubuntu-latest
16 | env:
17 | GO111MODULE: "on"
18 | steps:
19 | - name: Install Go
20 | uses: actions/setup-go@v2
21 | with:
22 | go-version: ${{ matrix.go-version }}
23 |
24 | - name: Checkout code
25 | uses: actions/checkout@v2
26 |
27 | - name: Download Go modules
28 | run: go mod download
29 |
30 | - name: Build
31 | run: go build -o /dev/null -v ./cmd/main.go
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | output
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Endre Simó
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dithergo
2 | [](https://github.com/esimov/dithergo/actions)
3 | [](https://pkg.go.dev/github.com/esimov/dithergo)
4 | [](./LICENSE)
5 |
6 | Dithergo is a simple Go library implementing various [dithering](https://en.wikipedia.org/wiki/Dither) algorithm to produce halftone images. It supports color and monochrome image outputs.
7 |
8 | The library implements the following dithering methods: ***Floyd Steinberg, Atkinson, Burkes, Stucki, Sierra-2, Sierra-3, Sierra-Lite***. All of these algorithms have something in common: they diffuse the error in two dimensions, but they always push the error forward, never backward.
9 |
10 | We can represent this with the following diagram:
11 |
12 | X 7 5
13 | 3 5 7 5 3
14 | 1 3 5 3 1
15 |
16 | (1/48)
17 |
18 | where `X` represent the current pixel processed. The fraction at the bottom represents the divisor for the error. Above is the the `Floyd-Steinberg` dithering algorithm which can be transposed into the following Go code:
19 |
20 | ```go
21 | ditherers = []dither.Dither{
22 | dither.Dither{
23 | "FloydSteinberg",
24 | dither.Settings{
25 | [][]float32{
26 | []float32{ 0.0, 0.0, 0.0, 7.0 / 48.0, 5.0 / 48.0 },
27 | []float32{ 3.0 / 48.0, 5.0 / 48.0, 7.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0 },
28 | []float32{ 1.0 / 48.0, 3.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 1.0 / 48.0 },
29 | },
30 | float32(multiplier),
31 | },
32 | },
33 | }
34 | ```
35 |
36 | You can plug in any dithering algorithm, so the library can be further extended.
37 |
38 | ### Installation
39 |
40 | `$ go get -u -v github.com/esimov/dithergo`
41 |
42 | ### Running
43 |
44 | Type `go run cmd/main.go --help` to check all the supported commands. The library supports the following commands:
45 |
46 | ```
47 | Usage of commands:
48 | -e string
49 | Generates & exports the color and greyscale mode halftone images.
50 | Options: 'all', 'color', 'mono' (default "all")
51 | -em float
52 | Error multiplier (default 1.18)
53 | -o string
54 | Output folder
55 | -t Option to export the tresholded image (default true)
56 |
57 | ```
58 | You can run all of the supported dithering algorithms at once, or you can run a specific one from the `cmd` directory.
59 |
60 | ### Results:
61 | | Input |
62 | |:--:|
63 | |
|
64 |
65 | The below images are generated with the default options using Michelangelo's David statue as sample image.
66 |
67 | | Color | Monochrome |
68 | |:--:|:--:|
69 | |
|
|
70 | Atkinson | Atkinson |
71 | |
|
|
72 | Burkes | Burkes |
73 | |
|
|
74 | Floyd-Steinberg | Floyd-Steinberg |
75 | |
|
|
76 | Sierra-2 | Sierra-2 |
77 | |
|
|
78 | Sierra-3 | Sierra-3 |
79 | |
|
|
80 | Sierra-Lite | Sierra-Lite |
81 | |
|
|
82 | Stucki | Stucki |
83 |
84 | ## Author
85 |
86 | * Endre Simo ([@simo_endre](https://twitter.com/simo_endre))
87 |
88 | ## License
89 |
90 | Copyright © 2018 Endre Simo
91 |
92 | This software is distributed under the MIT license found in the LICENSE file.
93 |
--------------------------------------------------------------------------------
/cmd/atkinson.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Atkinson",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 1.0 / 8.0, 1.0 / 8.0 },
13 | []float32{ 1.0 / 8.0, 1.0 / 8.0, 1.0 / 8.0, 0.0 },
14 | []float32{ 0.0, 1.0 / 8.0, 0.0, 0.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/burkes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Burkes",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 8.0 / 32.0, 4.0 / 32.0 },
13 | []float32{ 2.0 / 32.0, 4.0 / 32.0, 8.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0 },
14 | []float32{ 0.0, 0.0, 0.0, 0.0, 0.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/floydsteinberg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "FloydSteinberg",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 7.0 / 48.0, 5.0 / 48.0 },
13 | []float32{ 3.0 / 48.0, 5.0 / 48.0, 7.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0 },
14 | []float32{ 1.0 / 48.0, 3.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 1.0 / 48.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "FloydSteinberg",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 7.0 / 48.0, 5.0 / 48.0 },
13 | []float32{ 3.0 / 48.0, 5.0 / 48.0, 7.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0 },
14 | []float32{ 1.0 / 48.0, 3.0 / 48.0, 5.0 / 48.0, 3.0 / 48.0, 1.0 / 48.0 },
15 | },
16 | },
17 | },
18 | dither.Dither{
19 | "Stucki",
20 | dither.Settings{
21 | [][]float32{
22 | []float32{ 0.0, 0.0, 0.0, 8.0 / 42.0, 4.0 / 42.0 },
23 | []float32{ 2.0 / 42.0, 4.0 / 42.0, 8.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0 },
24 | []float32{ 1.0 / 42.0, 2.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0, 1.0 / 42.0 },
25 | },
26 | },
27 | },
28 | dither.Dither{
29 | "Atkinson",
30 | dither.Settings{
31 | [][]float32{
32 | []float32{ 0.0, 0.0, 1.0 / 8.0, 1.0 / 8.0 },
33 | []float32{ 1.0 / 8.0, 1.0 / 8.0, 1.0 / 8.0, 0.0 },
34 | []float32{ 0.0, 1.0 / 8.0, 0.0, 0.0 },
35 | },
36 | },
37 | },
38 | dither.Dither{
39 | "Burkes",
40 | dither.Settings{
41 | [][]float32{
42 | []float32{ 0.0, 0.0, 0.0, 8.0 / 32.0, 4.0 / 32.0 },
43 | []float32{ 2.0 / 32.0, 4.0 / 32.0, 8.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0 },
44 | []float32{ 0.0, 0.0, 0.0, 0.0, 0.0 },
45 | },
46 | },
47 | },
48 | dither.Dither{
49 | "Sierra-3",
50 | dither.Settings{
51 | [][]float32{
52 | []float32{ 0.0, 0.0, 0.0, 5.0 / 32.0, 3.0 / 32.0 },
53 | []float32{ 2.0 / 32.0, 4.0 / 32.0, 5.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0 },
54 | []float32{ 0.0, 2.0 / 32.0, 3.0 / 32.0, 2.0 / 32.0, 0.0 },
55 | },
56 | },
57 | },
58 | dither.Dither{
59 | "Sierra-2",
60 | dither.Settings{
61 | [][]float32{
62 | []float32{ 0.0, 0.0, 0.0, 4.0 / 16.0, 3.0 / 16.0 },
63 | []float32{ 1.0 / 16.0, 2.0 / 16.0, 3.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0 },
64 | []float32{ 0.0, 0.0, 0.0, 0.0, 0.0 },
65 | },
66 | },
67 | },
68 | dither.Dither{
69 | "Sierra-Lite",
70 | dither.Settings{
71 | [][]float32{
72 | []float32{ 0.0, 0.0, 2.0 / 4.0 },
73 | []float32{ 1.0 / 4.0, 1.0 / 4.0, 0.0 },
74 | []float32{ 0.0, 0.0, 0.0 },
75 | },
76 | },
77 | },
78 | }
79 |
80 | func main() {
81 | dither.Process(ditherers)
82 | }
--------------------------------------------------------------------------------
/cmd/sierra2.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Sierra-2",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 4.0 / 16.0, 3.0 / 16.0 },
13 | []float32{ 1.0 / 16.0, 2.0 / 16.0, 3.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0 },
14 | []float32{ 0.0, 0.0, 0.0, 0.0, 0.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/sierra3.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Sierra-3",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 5.0 / 32.0, 3.0 / 32.0 },
13 | []float32{ 2.0 / 32.0, 4.0 / 32.0, 5.0 / 32.0, 4.0 / 32.0, 2.0 / 32.0 },
14 | []float32{ 0.0, 2.0 / 32.0, 3.0 / 32.0, 2.0 / 32.0, 0.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/sierra_lite.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Sierra-Lite",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 2.0 / 4.0 },
13 | []float32{ 1.0 / 4.0, 1.0 / 4.0, 0.0 },
14 | []float32{ 0.0, 0.0, 0.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/cmd/stucki.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/esimov/dithergo"
5 | )
6 |
7 | var ditherers []dither.Dither = []dither.Dither{
8 | dither.Dither{
9 | "Stucki",
10 | dither.Settings{
11 | [][]float32{
12 | []float32{ 0.0, 0.0, 0.0, 8.0 / 42.0, 4.0 / 42.0 },
13 | []float32{ 2.0 / 42.0, 4.0 / 42.0, 8.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0 },
14 | []float32{ 1.0 / 42.0, 2.0 / 42.0, 4.0 / 42.0, 2.0 / 42.0, 1.0 / 42.0 },
15 | },
16 | },
17 | },
18 | }
19 |
20 | func main() {
21 | dither.Process(ditherers)
22 | }
--------------------------------------------------------------------------------
/dither_color.go:
--------------------------------------------------------------------------------
1 | package dither
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | )
7 |
8 | type Settings struct {
9 | Filter [][]float32
10 | }
11 |
12 | type Dither struct {
13 | Type string
14 | Settings
15 | }
16 |
17 | func (dither Dither) Color(input image.Image, errorMultiplier float32) image.Image {
18 | bounds := input.Bounds()
19 | img := image.NewRGBA(bounds)
20 | for x := bounds.Min.X; x < bounds.Dx(); x++ {
21 | for y := bounds.Min.Y; y < bounds.Dy(); y++ {
22 | pixel := input.At(x, y)
23 | img.Set(x, y, pixel)
24 | }
25 | }
26 | dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
27 |
28 | // Prepopulate multidimensional slices
29 | redErrors := make([][]float32, dx)
30 | greenErrors := make([][]float32, dx)
31 | blueErrors := make([][]float32, dx)
32 | for x := 0; x < dx; x++ {
33 | redErrors[x] = make([]float32, dy)
34 | greenErrors[x] = make([]float32, dy)
35 | blueErrors[x] = make([]float32, dy)
36 | for y := 0; y < dy; y++ {
37 | redErrors[x][y] = 0
38 | greenErrors[x][y] = 0
39 | blueErrors[x][y] = 0
40 | }
41 | }
42 |
43 | var qrr, qrg, qrb float32
44 | for x := 0; x < dx; x++ {
45 | for y := 0; y < dy; y++ {
46 | r32, g32, b32, a := img.At(x, y).RGBA()
47 | r, g, b := float32(uint8(r32)), float32(uint8(g32)), float32(uint8(b32))
48 | r -= redErrors[x][y] * errorMultiplier
49 | g -= greenErrors[x][y] * errorMultiplier
50 | b -= blueErrors[x][y] * errorMultiplier
51 |
52 | // Diffuse the error of each calculation to the neighboring pixels
53 | if r < 128 {
54 | qrr = -r
55 | r = 0
56 | } else {
57 | qrr = 255 - r
58 | r = 255
59 | }
60 | if g < 128 {
61 | qrg = -g
62 | g = 0
63 | } else {
64 | qrg = 255 - g
65 | g = 255
66 | }
67 | if b < 128 {
68 | qrb = -b
69 | b = 0
70 | } else {
71 | qrb = 255 - b
72 | b = 255
73 | }
74 | img.Set(x, y, color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)})
75 |
76 | // Diffuse error in two dimension
77 | ydim := len(dither.Filter) - 1
78 | xdim := len(dither.Filter[0]) / 2
79 | for xx := 0; xx < ydim+1; xx++ {
80 | for yy := -xdim; yy <= xdim-1; yy++ {
81 | if y+yy < 0 || dy <= y+yy || x+xx < 0 || dx <= x+xx {
82 | continue
83 | }
84 | // Adds the error of the previous pixel to the current pixel
85 | redErrors[x+xx][y+yy] += qrr * dither.Filter[xx][yy+ydim]
86 | greenErrors[x+xx][y+yy] += qrg * dither.Filter[xx][yy+ydim]
87 | blueErrors[x+xx][y+yy] += qrb * dither.Filter[xx][yy+ydim]
88 | }
89 | }
90 | }
91 | }
92 | return img
93 | }
94 |
--------------------------------------------------------------------------------
/dither_mono.go:
--------------------------------------------------------------------------------
1 | package dither
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | )
7 |
8 | func (dither Dither) Monochrome(input image.Image, errorMultiplier float32) image.Image {
9 | bounds := input.Bounds()
10 | img := image.NewGray(bounds)
11 | for x := bounds.Min.X; x < bounds.Dx(); x++ {
12 | for y := bounds.Min.Y; y < bounds.Dy(); y++ {
13 | pixel := input.At(x, y)
14 | img.Set(x, y, pixel)
15 | }
16 | }
17 | dx, dy := img.Bounds().Dx(), img.Bounds().Dy()
18 |
19 | // Prepopulate multidimensional slice
20 | errors := make([][]float32, dx)
21 | for x := 0; x < dx; x++ {
22 | errors[x] = make([]float32, dy)
23 | for y := 0; y < dy; y++ {
24 | errors[x][y] = 0
25 | }
26 | }
27 |
28 | for x := 0; x < dx; x++ {
29 | for y := 0; y < dy; y++ {
30 | pix := float32(img.GrayAt(x, y).Y)
31 | pix -= errors[x][y] * errorMultiplier
32 |
33 | var quantError float32
34 | // Diffuse the error of each calculation to the neighboring pixels
35 | if pix < 128 {
36 | quantError = -pix
37 | pix = 0
38 | } else {
39 | quantError = 255 - pix
40 | pix = 255
41 | }
42 |
43 | img.SetGray(x, y, color.Gray{Y: uint8(pix)})
44 |
45 | // Diffuse error in two dimension
46 | ydim := len(dither.Filter) - 1
47 | xdim := len(dither.Filter[0]) / 2
48 | for xx := 0; xx < ydim+1; xx++ {
49 | for yy := -xdim; yy <= xdim-1; yy++ {
50 | if y+yy < 0 || dy <= y+yy || x+xx < 0 || dx <= x+xx {
51 | continue
52 | }
53 | // Adds the error of the previous pixel to the current pixel
54 | errors[x+xx][y+yy] += quantError * dither.Filter[xx][yy+ydim]
55 | }
56 | }
57 | }
58 | }
59 | return img
60 | }
61 |
--------------------------------------------------------------------------------
/ditherer.go:
--------------------------------------------------------------------------------
1 | package dither
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "image"
7 | "image/color"
8 | _ "image/jpeg"
9 | "image/png"
10 | "log"
11 | "os"
12 | "time"
13 | )
14 |
15 | type file struct {
16 | name string
17 | }
18 |
19 | // Command line flags
20 | var (
21 | outputDir string
22 | colorType string
23 | treshold bool
24 | multiplier float64
25 | cmd flag.FlagSet
26 | )
27 |
28 | // Open reads the source image
29 | func (file *file) Open() (image.Image, error) {
30 | f, err := os.Open(file.name)
31 | if err != nil {
32 | return nil, err
33 | }
34 | defer f.Close()
35 |
36 | img, _, err := image.Decode(f)
37 | return img, err
38 | }
39 |
40 | // Grayscale converts an image to grayscale mode
41 | func (file *file) Grayscale(input image.Image) (*image.Gray, error) {
42 | bounds := input.Bounds()
43 | gray := image.NewGray(bounds)
44 |
45 | for x := bounds.Min.X; x < bounds.Dx(); x++ {
46 | for y := bounds.Min.Y; y < bounds.Dy(); y++ {
47 | pixel := input.At(x, y)
48 | gray.Set(x, y, pixel)
49 | }
50 | }
51 | return gray, nil
52 | }
53 |
54 | // tresholdDithering creates a tresholded image
55 | func (file *file) tresholdDithering(input *image.Gray) (*image.Gray, error) {
56 | var (
57 | bounds = input.Bounds()
58 | dithered = image.NewGray(bounds)
59 | dx = bounds.Dx()
60 | dy = bounds.Dy()
61 | )
62 |
63 | for x := 0; x < dx; x++ {
64 | for y := 0; y < dy; y++ {
65 | pixel := input.GrayAt(x, y)
66 | threshold := func(pixel color.Gray) color.Gray {
67 | if pixel.Y > 123 {
68 | return color.Gray{Y: 255}
69 | }
70 | return color.Gray{Y: 0}
71 | }
72 |
73 | dithered.Set(x, y, threshold(pixel))
74 | }
75 | }
76 | output, err := os.Create(outputDir + "/treshold.png")
77 | if err != nil {
78 | return nil, err
79 | }
80 | defer output.Close()
81 | err = png.Encode(output, dithered)
82 |
83 | if err != nil {
84 | log.Fatal(err)
85 | }
86 | return dithered, nil
87 | }
88 |
89 | // Process parses the command line inputs and calls the defined dithering method
90 | func Process(ditherers []Dither) {
91 | cmd = *flag.NewFlagSet("commands", flag.ExitOnError)
92 | cmd.StringVar(&outputDir, "o", "", "Output folder")
93 | cmd.StringVar(&colorType, "e", "all", "Generates & exports the color and greyscale mode halftone images. Options: 'all', 'color', 'mono'")
94 | cmd.BoolVar(&treshold, "t", true, "Option to export the tresholded image")
95 | cmd.Float64Var(&multiplier, "em", 1.18, "Error multiplier")
96 |
97 | log.SetPrefix("dithergo: ")
98 | log.SetFlags(0)
99 |
100 | cmd.Usage = func() {
101 | fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0])
102 | cmd.PrintDefaults()
103 | }
104 |
105 | if len(os.Args) == 1 {
106 | fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0])
107 | os.Exit(0)
108 | }
109 |
110 | if os.Args[1] == "--help" || os.Args[1] == "-h" {
111 | fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0])
112 | cmd.PrintDefaults()
113 | os.Exit(0)
114 | }
115 |
116 | // Parse flags before to use them
117 | cmd.Parse(os.Args[2:])
118 |
119 | if len(cmd.Args()) > 0 {
120 | cmd.Usage()
121 | log.Printf("missing input file.")
122 | os.Exit(0)
123 | }
124 |
125 | if len(outputDir) == 0 {
126 | cmd.Usage()
127 | log.Printf("missing output directory.")
128 | os.Exit(0)
129 | }
130 |
131 | input := &file{name: string(os.Args[1])}
132 | img, err := input.Open()
133 | if err != nil {
134 | log.Fatalf("Input error: %v", err)
135 | }
136 |
137 | // Channel to signal the completion event
138 | done := make(chan struct{})
139 |
140 | fmt.Print("Rendering image...")
141 | now := time.Now()
142 | progress(done)
143 |
144 | // Run the ditherer method
145 | func(input *file, done chan struct{}) {
146 | if cmd.Parsed() {
147 | if _, err := os.Stat(outputDir); os.IsNotExist(err) {
148 | os.Mkdir(outputDir, os.ModePerm)
149 | }
150 | _ = os.Mkdir(outputDir+"/color", os.ModePerm)
151 | _ = os.Mkdir(outputDir+"/mono", os.ModePerm)
152 |
153 | if treshold {
154 | gray, _ := input.Grayscale(img)
155 | input.tresholdDithering(gray)
156 | }
157 |
158 | for _, ditherer := range ditherers {
159 | dc := ditherer.Color(img, float32(multiplier))
160 | dg := ditherer.Monochrome(img, float32(multiplier))
161 | cex := outputDir + "/color/"
162 | gex := outputDir + "/mono/"
163 |
164 | switch colorType {
165 | case "all":
166 | generateOutput(ditherer, dc, cex)
167 | generateOutput(ditherer, dg, gex)
168 | case "color":
169 | generateOutput(ditherer, dc, cex)
170 | case "mono":
171 | generateOutput(ditherer, dg, gex)
172 | }
173 | }
174 | done <- struct{}{}
175 | }
176 | }(input, done)
177 |
178 | since := time.Since(now)
179 | fmt.Println("\nDone✓")
180 | fmt.Printf("Rendered in: %.2fs\n", since.Seconds())
181 | }
182 |
183 | // generateOutput render the generated image
184 | func generateOutput(dither Dither, img image.Image, exportDir string) {
185 | output, err := os.Create(exportDir + dither.Type + ".png")
186 | if err != nil {
187 | log.Fatal(err)
188 | }
189 | defer output.Close()
190 |
191 | err = png.Encode(output, img)
192 | if err != nil {
193 | log.Fatal(err)
194 | }
195 | }
196 |
197 | // progress visualize the rendering progress
198 | func progress(done chan struct{}) {
199 | ticker := time.NewTicker(time.Millisecond * 200)
200 |
201 | go func() {
202 | for {
203 | select {
204 | case <-ticker.C:
205 | fmt.Print(".")
206 | case <-done:
207 | ticker.Stop()
208 | }
209 | }
210 | }()
211 | }
212 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/esimov/dithergo
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/input/Lenna.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/input/Lenna.png
--------------------------------------------------------------------------------
/input/david.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/input/david.jpg
--------------------------------------------------------------------------------
/input/flower.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/input/flower.jpg
--------------------------------------------------------------------------------
/input/gopher.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/input/gopher.jpg
--------------------------------------------------------------------------------
/input/portal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/input/portal.jpg
--------------------------------------------------------------------------------
/output/color/Atkinson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Atkinson.png
--------------------------------------------------------------------------------
/output/color/Burkes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Burkes.png
--------------------------------------------------------------------------------
/output/color/FloydSteinberg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/FloydSteinberg.png
--------------------------------------------------------------------------------
/output/color/Sierra-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Sierra-2.png
--------------------------------------------------------------------------------
/output/color/Sierra-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Sierra-3.png
--------------------------------------------------------------------------------
/output/color/Sierra-Lite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Sierra-Lite.png
--------------------------------------------------------------------------------
/output/color/Stucki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/color/Stucki.png
--------------------------------------------------------------------------------
/output/mono/Atkinson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Atkinson.png
--------------------------------------------------------------------------------
/output/mono/Burkes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Burkes.png
--------------------------------------------------------------------------------
/output/mono/FloydSteinberg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/FloydSteinberg.png
--------------------------------------------------------------------------------
/output/mono/Sierra-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Sierra-2.png
--------------------------------------------------------------------------------
/output/mono/Sierra-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Sierra-3.png
--------------------------------------------------------------------------------
/output/mono/Sierra-Lite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Sierra-Lite.png
--------------------------------------------------------------------------------
/output/mono/Stucki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/dithergo/7f9ddf55e84884b02360fd2ddad64753992e71bb/output/mono/Stucki.png
--------------------------------------------------------------------------------