-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprocess.go
232 lines (198 loc) · 7.62 KB
/
process.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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package main
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"gl.ocelotworks.com/ocelotbotv5/image-renderer/helper"
"gl.ocelotworks.com/ocelotbotv5/image-renderer/stage"
"image"
"log"
"os"
"sync"
"time"
"github.com/fogleman/gg"
"gl.ocelotworks.com/ocelotbotv5/image-renderer/entity"
"gl.ocelotworks.com/ocelotbotv5/image-renderer/filter"
)
const _defaultDelay = 10
// Performance metrics
var (
processDuration = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: "image_renderer",
Name: "process_duration",
Help: "Duration taken for the entire processing",
})
componentDrawDuration = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: "image_renderer",
Name: "component_draw_duration",
Help: "Duration taken to stack component images",
})
beforeRenderFilterDuration = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: "image_renderer",
Name: "filter_before_render_duration",
Help: "Duration taken to process BeforeRender filters",
})
)
// ProcessImage processes an incoming ImageRequest and outputs a finished ImageResult
func ProcessImage(request *entity.ImageRequest) *entity.ImageResult {
processDurationStart := time.Now()
stage.ProcessBeforeStackingFilters(request)
componentFrameDelays, componentFrameImages, exception := stage.MapComponentFrames(request)
if exception != nil {
return &entity.ImageResult{Error: "get_image"}
}
// holds all the contexts for each frame of the final output image
outputContexts := make([]*gg.Context, 0)
// the delay for each frame of the output image
var outputDelay []int
// Used to determine if the diff should be calculated
shouldDiff := false
for comp, component := range request.ImageComponents {
componentDrawStart := time.Now()
// Only components with a background should be diffed
if component.URL != "" && component.Background != "" {
shouldDiff = true
}
frameContexts := make(chan *gg.Context)
frameImages := componentFrameImages[comp]
frameDelay := componentFrameDelays[comp]
componentDelay := frameDelay
// Check for relative width/height and set to the correct value
if ppw, ok := component.Position.Width.(string); ok {
component.Position.Width = helper.GetRelativeDimension(request.Width, ppw)
fmt.Println("Transforming width to ", component.Position.Width)
}
if pph, ok := component.Position.Height.(string); ok {
component.Position.Height = helper.GetRelativeDimension(request.Height, pph)
fmt.Println("Transforming height to ", component.Position.Height)
}
frameCount := 1
var wg sync.WaitGroup
go (func() {
// If there are no frames in this image, create a new blank context of the correct width/height
if len(frameImages) == 0 {
ctx := gg.NewContext(int(component.Position.Width.(float64)), int(component.Position.Height.(float64)))
if comp == 0 {
if component.Background != "" {
ctx.SetHexColor(component.Background)
ctx.DrawRectangle(0, 0, float64(request.Width), float64(request.Height))
ctx.Fill()
}
}
frameContexts <- ctx
close(frameContexts)
} else {
// create an image context for the image (or each frame for a gif)
//frameContexts = make([]*gg.Context, len(frameImages))
frameCount = len(frameImages)
for _, img := range frameImages {
dx := (*img).Bounds().Dx()
dy := (*img).Bounds().Dy()
ctx := gg.NewContext(dx, dy)
// this is a replacement for me figuring out the actual problems
if component.Background != "" {
ctx.SetHexColor(component.Background)
ctx.DrawRectangle(0, 0, float64(dx), float64(dy))
ctx.Fill()
}
ctx.DrawImage(*img, 0, 0)
frameContexts <- ctx
}
close(frameContexts)
}
})()
frameNum := 0
// get the image context for each frame (only 1 frame if not a gif)
for inputFrameCtx := range frameContexts {
// loop over a gif and apply it to all canvases (or apply a static image to every frame)
//inputFrameCtx := frameContexts[frameNum%len(frameContexts)]
// Only apply the filter to the first frame of animated GIFs
//if frameNum == 0 || len(frameContexts) > 1 {
// apply any filters set for the component
for _, filterObject := range component.Filters {
// check the filter exists and apply it
var filterObj interface{}
var ok bool
if filterObj, ok = filter.Filters[filterObject.Name]; !ok {
log.Println("Unknown filter type", filterObject)
continue
}
if processFilter, ok := filterObj.(filter.BeforeRender); ok {
log.Println("Applying filter", filterObject.Name, filterObject.Arguments)
beforeRenderFilterStart := time.Now()
processFilter.BeforeRender(inputFrameCtx, filterObject.Arguments, frameNum, component)
beforeRenderFilterDuration.Observe(float64(time.Since(beforeRenderFilterStart).Milliseconds()))
}
}
//}
// check if there is an existing context for this frame
var outputCtx *gg.Context
if frameNum < len(outputContexts) {
outputCtx = outputContexts[frameNum]
} else {
// Check for a MaxWidth param, or default to 1920 and resize the image accordingly
if request.MaxWidth > -1 && component.Position.Width != nil && component.Position.Height != nil {
if request.MaxWidth == 0 {
request.MaxWidth = 1920
}
componentWidth := int(component.Position.Width.(float64))
componentHeight := int(component.Position.Height.(float64))
if componentWidth > request.MaxWidth {
component.Position.Height = float64(request.MaxWidth * componentHeight / componentWidth)
component.Position.Width = float64(request.MaxWidth)
}
}
if request.Width == 0 && component.Position.Width != nil {
request.Width = int(component.Position.Width.(float64))
}
if request.Height == 0 && component.Position.Height != nil {
request.Height = int(component.Position.Height.(float64))
}
outputCtx = gg.NewContext(request.Width, request.Height)
outputContexts = append(outputContexts, outputCtx)
}
// set the delay for this frame if one doesn't exist yet
if frameNum >= len(outputDelay) {
delay := _defaultDelay
// check if one exists from the input frames and set to that
if frameNum < len(componentDelay) {
delay = componentDelay[frameNum]
}
outputDelay = append(outputDelay, delay)
}
if request.Debug {
inputFrameCtx.SetLineWidth(10)
inputFrameCtx.SetHexColor("#ff0000")
inputFrameCtx.DrawRectangle(0, 0, float64(inputFrameCtx.Width()), float64(inputFrameCtx.Height()))
inputFrameCtx.Stroke()
inputFrameCtx.DrawStringWrapped(fmt.Sprintf("%dx%d", inputFrameCtx.Width(), inputFrameCtx.Height()), float64(inputFrameCtx.Width()), float64(inputFrameCtx.Height()), 1, 1, float64(inputFrameCtx.Width()), 1, gg.AlignLeft)
}
stage.RotateAndResize(inputFrameCtx, outputCtx, component)
// (Slow) optimisation for animated gifs
if shouldDiff && comp == len(request.ImageComponents)-1 {
stage.GIFOptimise(outputCtx, frameNum, &wg)
}
frameNum++
componentDrawDuration.Observe(float64(time.Since(componentDrawStart).Milliseconds()))
}
log.Println("Waiting for diff to finish...")
wg.Wait()
log.Println("Done!")
}
outputImages := make([]image.Image, len(outputContexts))
for i, canvas := range outputContexts {
outputImages[i] = canvas.Image()
}
if os.Getenv("DEBUG_DISABLE_RESPONSE") == "1" {
return &entity.ImageResult{Error: "debug"}
}
output := OutputImage(outputImages, outputDelay, !shouldDiff, request)
processDuration.Observe(float64(time.Since(processDurationStart).Milliseconds()))
return output
}
func max(i, i2 int) int {
if i < i2 {
return i2
}
return i
}