1 /* wavbreaker - A tool to split a wave file up into multiple wave.
2  * Copyright (C) 2002-2005 Timothy Robinson
3  * Copyright (C) 2007-2019 Thomas Perl
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 #include <gtk/gtk.h>
21 #include <math.h>
22 
23 #include "draw.h"
24 
25 static void draw_sample_surface(struct WaveformSurface *self, struct WaveformSurfaceDrawContext *ctx);
26 static void draw_summary_surface(struct WaveformSurface *self, struct WaveformSurfaceDrawContext *ctx);
27 
28 /**
29  * Generated using the following Python 3 snippet (with some editing):
30  *
31  * import colorsys
32  * for i in range(6):
33  *     print('    { %3d, %3d, %3d, },' % tuple(int(x*255)
34  *           for x in colorsys.hsv_to_rgb(i/8., 0.8, 0.8)))
35  **/
36 const unsigned char SAMPLE_COLORS_VALUES[][3] = {
37     { 204,  40,  40, },
38     { 204, 163,  40, },
39     { 122, 204,  40, },
40     {  40,  81, 204, },
41     {  40, 204, 204, },
42 };
43 
44 #define SAMPLE_COLORS G_N_ELEMENTS(SAMPLE_COLORS_VALUES)
45 
46 #define SAMPLE_SHADES 3
47 
48 GdkRGBA sample_colors[SAMPLE_COLORS][SAMPLE_SHADES];
49 GdkRGBA bg_color;
50 GdkRGBA nowrite_color;
51 
52 static inline void
set_cairo_source(cairo_t * cr,GdkRGBA color)53 set_cairo_source(cairo_t *cr, GdkRGBA color)
54 {
55     cairo_set_source_rgb(cr, color.red, color.green, color.blue);
56 }
57 
58 static inline void
fill_cairo_rectangle(cairo_t * cr,GdkRGBA * color,int width,int height)59 fill_cairo_rectangle(cairo_t *cr, GdkRGBA *color, int width, int height)
60 {
61     set_cairo_source(cr, *color);
62     cairo_rectangle(cr, 0.f, 0.f, (float)width, (float)height);
63     cairo_fill(cr);
64 }
65 
66 static inline void
draw_cairo_line(cairo_t * cr,float x,float y0,float y1)67 draw_cairo_line(cairo_t *cr, float x, float y0, float y1)
68 {
69     cairo_move_to(cr, x-0.5f, y0);
70     cairo_line_to(cr, x-0.5f, y1);
71 }
72 
73 static void
waveform_surface_static_init()74 waveform_surface_static_init()
75 {
76     static gboolean inited = FALSE;
77     if (inited) {
78         return;
79     }
80 
81     bg_color = (GdkRGBA){ .red = 1.f, .green = 1.f, .blue = 1.f };
82     nowrite_color = (GdkRGBA){ .red = 0.86f, .green = 0.86f, .blue = 0.86f };
83 
84     for (int i=0; i<SAMPLE_COLORS; i++) {
85         for (int x=0; x<SAMPLE_SHADES; x++) {
86             float factor_white = 0.5f*((float)x/(float)SAMPLE_SHADES);
87             float factor_color = 1.f-factor_white;
88             sample_colors[i][x].red = SAMPLE_COLORS_VALUES[i][0]/255.f*factor_color+factor_white;
89             sample_colors[i][x].green = SAMPLE_COLORS_VALUES[i][1]/255.f*factor_color+factor_white;
90             sample_colors[i][x].blue = SAMPLE_COLORS_VALUES[i][2]/255.f*factor_color+factor_white;
91         }
92     }
93 
94     inited = TRUE;
95 }
96 
waveform_surface_create_sample()97 struct WaveformSurface *waveform_surface_create_sample()
98 {
99     waveform_surface_static_init();
100 
101     struct WaveformSurface *surface = calloc(sizeof(struct WaveformSurface), 1);
102 
103     surface->draw = draw_sample_surface;
104 
105     return surface;
106 }
107 
waveform_surface_create_summary()108 struct WaveformSurface *waveform_surface_create_summary()
109 {
110     waveform_surface_static_init();
111 
112     struct WaveformSurface *surface = calloc(sizeof(struct WaveformSurface), 1);
113 
114     surface->draw = draw_summary_surface;
115 
116     return surface;
117 }
118 
waveform_surface_draw(struct WaveformSurface * surface,struct WaveformSurfaceDrawContext * ctx)119 void waveform_surface_draw(struct WaveformSurface *surface, struct WaveformSurfaceDrawContext *ctx)
120 {
121     surface->draw(surface, ctx);
122 }
123 
waveform_surface_invalidate(struct WaveformSurface * surface)124 void waveform_surface_invalidate(struct WaveformSurface *surface)
125 {
126     if (surface->surface) {
127         cairo_surface_destroy(surface->surface);
128         surface->surface = NULL;
129     }
130 
131     surface->width = 0;
132     surface->height = 0;
133 }
134 
waveform_surface_free(struct WaveformSurface * surface)135 void waveform_surface_free(struct WaveformSurface *surface)
136 {
137     if (surface->surface) {
138         cairo_surface_destroy(surface->surface);
139     }
140 
141     free(surface);
142 }
143 
moodbar_sample_color(MoodbarData * moodbar,float position)144 static GdkRGBA moodbar_sample_color(MoodbarData *moodbar, float position)
145 {
146     float index = position * moodbar->numFrames;
147     unsigned long iindex = index;
148     float fractional = index - iindex;
149     if (fractional == 0.f || iindex >= moodbar->numFrames - 1) {
150         return moodbar->frames[iindex];
151     } else {
152         GdkRGBA a = moodbar->frames[iindex];
153         GdkRGBA b = moodbar->frames[iindex+1];
154         return (GdkRGBA){
155             .red =   (1.f - fractional) * a.red   + fractional * b.red,
156             .green = (1.f - fractional) * a.green + fractional * b.green,
157             .blue =  (1.f - fractional) * a.blue  + fractional * b.blue,
158             .alpha = (1.f - fractional) * a.alpha + fractional * b.alpha,
159         };
160     }
161 }
162 
163 static void
draw_sample_surface(struct WaveformSurface * self,struct WaveformSurfaceDrawContext * ctx)164 draw_sample_surface(struct WaveformSurface *self, struct WaveformSurfaceDrawContext *ctx)
165 {
166     int xaxis;
167     int width, height;
168     int y_min, y_max;
169     int scale;
170     long i;
171 
172     int shade;
173 
174     GdkRGBA new_color;
175 
176     {
177         GtkAllocation allocation;
178         gtk_widget_get_allocation(ctx->widget, &allocation);
179 
180         width = allocation.width;
181         height = allocation.height;
182     }
183 
184     if (self->surface != NULL && self->width == width && self->height == height && self->offset == ctx->pixmap_offset &&
185         (ctx->moodbarData && ctx->moodbarData->numFrames) == self->moodbar) {
186         return;
187     }
188 
189     if (self->surface) {
190         cairo_surface_destroy(self->surface);
191     }
192 
193     self->surface = gdk_window_create_similar_surface(gtk_widget_get_window(ctx->widget),
194             CAIRO_CONTENT_COLOR, width, height);
195 
196     if (!self->surface) {
197         printf("surface is NULL\n");
198         return;
199     }
200 
201     cairo_t *cr = cairo_create(self->surface);
202     cairo_set_line_width(cr, 1.f);
203 
204     /* clear sample_surface before drawing */
205     fill_cairo_rectangle(cr, &bg_color, width, height);
206 
207     if (ctx->graphData->data == NULL) {
208         cairo_destroy(cr);
209         return;
210     }
211 
212     xaxis = height / 2;
213     if (xaxis != 0) {
214         scale = ctx->graphData->maxSampleValue / xaxis;
215         if (scale == 0) {
216             scale = 1;
217         }
218     } else {
219         scale = 1;
220     }
221 
222     /* draw sample graph */
223     int tb_index = 0;
224     GList *tbl = ctx->track_break_list;
225     for (i = 0; i < width && i < ctx->graphData->numSamples; i++) {
226         y_min = ctx->graphData->data[i + ctx->pixmap_offset].min;
227         y_max = ctx->graphData->data[i + ctx->pixmap_offset].max;
228 
229         y_min = xaxis + fabs((double)y_min) / scale;
230         y_max = xaxis - y_max / scale;
231 
232         /* find the track break we are drawing now */
233         while (tbl->next && (i + ctx->pixmap_offset) > ((TrackBreak *)(tbl->next->data))->offset) {
234             tbl = tbl->next;
235             ++tb_index;
236         }
237 
238         if (ctx->moodbarData && ctx->moodbarData->numFrames) {
239             set_cairo_source(cr, moodbar_sample_color(ctx->moodbarData, (float)(i+ctx->pixmap_offset) / (float)ctx->graphData->numSamples));
240             draw_cairo_line(cr, i, 0.f, height);
241             cairo_stroke(cr);
242         }
243 
244         for( shade=0; shade<SAMPLE_SHADES; shade++) {
245             TrackBreak *tb = tbl->data;
246             if (tb->write) {
247                 new_color = sample_colors[tb_index % SAMPLE_COLORS][shade];
248             } else {
249                 new_color = nowrite_color;
250             }
251 
252             set_cairo_source(cr, new_color);
253             draw_cairo_line(cr, i, y_min+(xaxis-y_min)*shade/SAMPLE_SHADES, y_min+(xaxis-y_min)*(shade+1)/SAMPLE_SHADES);
254             draw_cairo_line(cr, i, y_max-(y_max-xaxis)*shade/SAMPLE_SHADES, y_max-(y_max-xaxis)*(shade+1)/SAMPLE_SHADES);
255             cairo_stroke(cr);
256         }
257     }
258 
259     cairo_destroy(cr);
260 
261     self->width = width;
262     self->height = height;
263     self->offset = ctx->pixmap_offset;
264     self->moodbar = ctx->moodbarData && ctx->moodbarData->numFrames;
265 }
266 
267 static void
draw_summary_surface(struct WaveformSurface * self,struct WaveformSurfaceDrawContext * ctx)268 draw_summary_surface(struct WaveformSurface *self, struct WaveformSurfaceDrawContext *ctx)
269 {
270     int xaxis;
271     int width, height;
272     int y_min, y_max;
273     int min, max;
274     int scale;
275     int i, k;
276     int loop_end, array_offset;
277     int shade;
278 
279     float x_scale;
280 
281     GdkRGBA new_color;
282 
283     {
284         GtkAllocation allocation;
285         gtk_widget_get_allocation(ctx->widget, &allocation);
286         width = allocation.width;
287         height = allocation.height;
288     }
289 
290     if (self->surface != NULL && self->width == width && self->height == height &&
291         (ctx->moodbarData && ctx->moodbarData->numFrames) == self->moodbar) {
292         return;
293     }
294 
295     if (self->surface) {
296         cairo_surface_destroy(self->surface);
297     }
298 
299     self->surface = gdk_window_create_similar_surface(gtk_widget_get_window(ctx->widget),
300             CAIRO_CONTENT_COLOR, width, height);
301 
302     if (!self->surface) {
303         printf("summary_surface is NULL\n");
304         return;
305     }
306 
307     cairo_t *cr = cairo_create(self->surface);
308     cairo_set_line_width(cr, 1.f);
309 
310     /* clear sample_surface before drawing */
311     fill_cairo_rectangle(cr, &bg_color, width, height);
312 
313     if (ctx->graphData->data == NULL) {
314         cairo_destroy(cr);
315         return;
316     }
317 
318     xaxis = height / 2;
319     if (xaxis != 0) {
320         scale = ctx->graphData->maxSampleValue / xaxis;
321         if (scale == 0) {
322             scale = 1;
323         }
324     } else {
325         scale = 1;
326     }
327 
328     /* draw sample graph */
329 
330     x_scale = (float)(ctx->graphData->numSamples) / (float)(width);
331     if (x_scale == 0) {
332         x_scale = 1;
333     }
334 
335     int tb_index = 0;
336     GList *tbl = ctx->track_break_list;
337     for (i = 0; i < width && i < ctx->graphData->numSamples; i++) {
338         min = max = 0;
339         array_offset = (int)(i * x_scale);
340 
341         if (x_scale != 1) {
342             loop_end = (int)x_scale;
343 
344             for (k = 0; k < loop_end; k++) {
345                 if (ctx->graphData->data[array_offset + k].max > max) {
346                     max = ctx->graphData->data[array_offset + k].max;
347                 } else if (ctx->graphData->data[array_offset + k].min < min) {
348                     min = ctx->graphData->data[array_offset + k].min;
349                 }
350             }
351         } else {
352             min = ctx->graphData->data[i].min;
353             max = ctx->graphData->data[i].max;
354         }
355 
356         y_min = min;
357         y_max = max;
358 
359         y_min = xaxis + fabs((double)y_min) / scale;
360         y_max = xaxis - y_max / scale;
361 
362         /* find the track break we are drawing now */
363         while (tbl->next && array_offset > ((TrackBreak *)(tbl->next->data))->offset) {
364             tbl = tbl->next;
365             ++tb_index;
366         }
367 
368         if (ctx->moodbarData && ctx->moodbarData->numFrames) {
369             set_cairo_source(cr, moodbar_sample_color(ctx->moodbarData, (float)(array_offset) / (float)(ctx->graphData->numSamples)));
370             draw_cairo_line(cr, i, 0.f, height);
371             cairo_stroke(cr);
372         }
373 
374         for( shade=0; shade<SAMPLE_SHADES; shade++) {
375             TrackBreak *tb = tbl->data;
376             if (tb->write) {
377                 new_color = sample_colors[tb_index % SAMPLE_COLORS][shade];
378             } else {
379                 new_color = nowrite_color;
380             }
381 
382             cairo_set_source_rgb(cr, new_color.red, new_color.green, new_color.blue);
383             draw_cairo_line(cr, i, y_min+(xaxis-y_min)*shade/SAMPLE_SHADES, y_min+(xaxis-y_min)*(shade+1)/SAMPLE_SHADES);
384             draw_cairo_line(cr, i, y_max-(y_max-xaxis)*shade/SAMPLE_SHADES, y_max-(y_max-xaxis)*(shade+1)/SAMPLE_SHADES);
385             cairo_stroke(cr);
386         }
387     }
388 
389     cairo_destroy(cr);
390 
391     self->width = width;
392     self->height = height;
393     self->moodbar = ctx->moodbarData && ctx->moodbarData->numFrames;
394 }
395 
396