forked from codeliveroil/img
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathimage.go
176 lines (160 loc) · 4.33 KB
/
image.go
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Copyright (c) 2018 codeliveroil. All rights reserved.
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
package viz
import (
"image"
"image/draw"
"image/gif"
_ "image/jpeg"
_ "image/png"
"math"
"os"
"github.com/codeliveroil/img/terminal"
"github.com/nfnt/resize"
)
// Image is a representation of a (multi) picture
// image.
type Image struct {
// Path to image file.
Filename string
// Specify a file name to export the image to a shell script.
// For instance, this script can be used to display an image for motd.
ExportFilename string
// Specify a loop count to animate GIFs more than once or set to 0 to render the first picture only.
LoopCount int
//Specify a decimal point multiplier to increase or decrease the speed of the GIF.
DelayMultiplier float64
// Use specified width instead of automatically computing it. Height will be calculated according to the aspect ratio.
// This is useful in SSH sessions where screen resizes are not registered automatically.
UserWidth int
frames []frame
h int
w int
}
type frame struct {
picture [][]uint8
delay int
}
// Init initializes the visualization framework
// for drawing the image.
func (img *Image) Init() (err error) {
//Open image
file, err := os.Open(img.Filename)
if err != nil {
return err
}
firstFrame, imgFmt, err := image.Decode(file)
if err != nil {
return err
}
file.Close()
//Identify scale
iw := firstFrame.Bounds().Max.X
ih := firstFrame.Bounds().Max.Y
scale := 1.0
if img.UserWidth > 0 {
scale = float64(img.UserWidth) / float64(iw)
} else {
tw, th, err := terminal.Size()
if err != nil {
return err
}
if imgFmt == "gif" && img.LoopCount > 0 {
tw = 40
}
th = (th * 2) - 1 //-1 to account for the terminal prompt ($/#) that'll show up after the image is displayed
if tw < iw || th < ih { //scale down the image to fit the terminal
scaleW := float64(tw) / float64(iw)
scaleH := float64(th) / float64(ih)
scale = math.Min(scaleW, scaleH)
}
}
img.w = int(math.Floor(scale * float64(iw)))
img.h = int(math.Floor(scale * float64(ih)))
if img.h%2 != 0 { //make the height even since we will be painting y and y+1 in every iteration
img.h -= 1
}
//Scale image frames
appendImg := func(f image.Image, delayMS int) {
scaled := resize.Resize(uint(img.w), uint(img.h), f, resize.Lanczos3)
pic := make([][]uint8, img.w)
for x := 0; x < img.w; x++ {
pic[x] = make([]uint8, img.h)
for y := 0; y < img.h; y++ {
clr := scaled.At(x, y)
x256Clr := Colors.Index(clr)
pic[x][y] = uint8(x256Clr)
}
}
img.frames = append(img.frames, frame{
picture: pic,
delay: int(math.Ceil(float64(delayMS) * img.DelayMultiplier)), //GIFs will take long to render, so reduce the delay to achieve intended delay.
})
}
if imgFmt == "gif" && img.LoopCount > 0 {
file, err := os.Open(img.Filename)
if err != nil {
return err
}
g, err := gif.DecodeAll(file)
if err != nil {
return err
}
iw = g.Config.Width
ih = g.Config.Height
var prev *image.RGBA
canvas := image.NewRGBA(image.Rect(0, 0, iw, ih))
for i, frame := range g.Image {
draw.Draw(canvas, canvas.Bounds(), frame, image.ZP, draw.Over)
appendImg(canvas, g.Delay[i]*10)
switch g.Disposal[i] {
case gif.DisposalBackground:
canvas = image.NewRGBA(image.Rect(0, 0, iw, ih))
fallthrough
case gif.DisposalNone:
prev = &(*canvas)
case gif.DisposalPrevious:
if prev != nil {
canvas = prev
}
}
}
file.Close()
} else {
img.LoopCount = 1 //override incorrect user input for single picture images
appendImg(firstFrame, 0)
}
return nil
}
// Draw renders the image into one of the
// selected modes (stdout or file)
func (img *Image) Draw(canvas Canvas) error {
firstFrameDone := false
delay := 0
for i := 0; i < img.LoopCount; i++ {
for _, frame := range img.frames {
if firstFrameDone {
if err := canvas.LineUp(img.h / 2); err != nil {
return err
}
if err := canvas.Sleep(delay); err != nil {
return err
}
}
for y := 0; y < img.h; y = y + 2 {
for x := 0; x < img.w; x++ {
canvas.Paint(frame.picture[x][y], frame.picture[x][y+1])
}
err := canvas.NewLine()
if err != nil {
return err
}
}
firstFrameDone = true
delay = frame.delay
}
}
return canvas.Close()
}