-
-
Notifications
You must be signed in to change notification settings - Fork 525
/
svgManager.cpp
372 lines (299 loc) · 9.62 KB
/
svgManager.cpp
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/*
This file is part of Warzone 2100.
Copyright (C) 2008 Freddie Witherden
Copyright (C) 2008 Elio Gubser
Copyright (C) 2008-2011 Warzone 2100 Project
Warzone 2100 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 2 of the License, or
(at your option) any later version.
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "svgManager.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <svg-cairo.h>
static vector *svgImages = NULL;
/*
* Internal structures
*/
typedef struct _svgImage svgImage;
struct _svgImage
{
/// Path to the image
const char *filename;
/// libsvg-cairo internal parse tree
svg_cairo_t *svg;
/// Array of svgRenderedImages for the currently rendered sizes
vector *renders;
};
static void svgManagerFreeRender(void *renderedImage)
{
// Un-reference the pattern
cairo_pattern_destroy(((svgRenderedImage *) renderedImage)->pattern);
// Free the memory allocated for the structure
free(renderedImage);
}
static void svgManagerFreeImage(void *image)
{
// Free filename buffer
free(((svgImage *)image)->filename);
// Free the libcairo-svg parse tree
svg_cairo_destroy(((svgImage *) image)->svg);
// Release all renders of the image
vectorMapAndDestroy(((svgImage *) image)->renders, svgManagerFreeRender);
// Finally, free the memory allocated for our structure
free(image);
}
/**
* Loads the SVG image specified by filename and adds it to the global list
* of SVG images. The newly loaded image is then returned for rendering.
*
* This function should NOT be called directly; instead use svgManagerLoad.
*
* @param filename The path of the image to load.
* @return A pointer to the newly loaded SVG image on success, NULL otherwise.
*/
static svgImage *svgManagerLoadInternal(const char *filename)
{
svgImage *svg = malloc(sizeof(svgImage));
svg_cairo_status_t status;
// Set the filename of the newly loaded image
svg->filename = strdup(filename);
// Initialise the svg_cairo structure
status = svg_cairo_create(&svg->svg);
// Ensure that we were able to create the structure
assert(status == SVG_CAIRO_STATUS_SUCCESS);
// Parse the SVG
status = svg_cairo_parse(svg->svg, filename);
// Ensure that the SVG was successfully parsed
assert(status == SVG_CAIRO_STATUS_SUCCESS);
// Create a new vector to store size-specific renders of the image
svg->renders = vectorCreate();
// Add the newly loaded image to the images array and return it
return vectorAdd(svgImages, svg);
}
/**
* Loads and returns the SVG image specified by filename. This is a higher-level
* version of svgManagerLoad which checks the image cache before loading an
* image into memory.
*
* @param filename The path to the SVG image to load.
* @return A pointer to the newly loaded SVG image.
*/
static svgImage *svgManagerLoad(const char *filename)
{
svgImage *currSvg, *svg = NULL;
// See if the image exists in the cache at *any* size
vectorRewind(svgImages);
while ((currSvg = vectorNext(svgImages)))
{
// If the filenames match then we have found the image
if (strcmp(filename, currSvg->filename) == 0)
{
svg = currSvg;
break;
}
}
// If no image was found then go ahead and load/parse it
if (svg == NULL)
{
svg = svgManagerLoadInternal(filename);
}
assert(svg != NULL);
return svg;
}
/**
* A low-level version of svgManagerGetSize.
*
* @param image The image to get the rendered size for.
* @param policy The policy to use for working out the size of the image.
* @param ap An additional arguments required by the sizing policy.
* @return The rendered size of the image.
* @see svgManagerGetSize
*/
static size svgManagerGetSizeInternal(svgImage *image,
svgSizingPolicy policy,
va_list ap)
{
size size;
unsigned int sourceWidth, sourceHeight;
// Get the native size of the image
svg_cairo_get_size(image->svg, &sourceWidth, &sourceHeight);
switch (policy)
{
// Just use the source (native) size
case SVG_SIZE_NATIVE:
{
size.x = sourceWidth;
size.y = sourceHeight;
break;
}
// Scale such that the width is that of the first argument
case SVG_SIZE_WIDTH:
{
unsigned int width = va_arg(ap, unsigned int);
size.x = width;
size.y = ((float) width / (float) sourceWidth) * (float) sourceHeight;
break;
}
// Scale such that the height is that of first argument
case SVG_SIZE_HEIGHT:
{
unsigned int height = va_arg(ap, unsigned int);
size.x = ((float) height / (float) sourceHeight) * (float) sourceWidth;
size.y = height;
break;
}
// Just use the provided width and height (first and second arguments)
case SVG_SIZE_WIDTH_AND_HEIGHT:
{
unsigned int width = va_arg(ap, unsigned int);
unsigned int height = va_arg(ap, unsigned int);
size.x = width;
size.y = height;
break;
}
// Use the provided width and height as a guideline
case SVG_SIZE_WIDTH_AND_HEIGHT_FIT:
{
unsigned int width = va_arg(ap, unsigned int);
unsigned int height = va_arg(ap, unsigned int);
float maxWidth = ((float) height / (float) sourceHeight) * (float) sourceWidth;
float maxHeight = ((float) width / (float) sourceWidth) * (float) sourceHeight;
if (maxWidth <= width)
{
width = maxWidth;
}
else if (maxHeight <= height)
{
height = maxHeight;
}
size.x = width;
size.y = height;
break;
}
}
return size;
}
/**
* Renders the SVG image, svg, at (width,height) and adds it to the cache. The
* rendered image is then returned.
*
* @param svg The SVG image to render.
* @param width The width to render the image at.
* @param height The height to render the image at.
* @return A pointer to the newly rendered image on success, false othwewise.
*/
static svgRenderedImage *svgManagerRender(svgImage *svg, int width, int height)
{
svgRenderedImage *render = malloc(sizeof(svgRenderedImage));
unsigned int sourceWidth, sourceHeight;
cairo_t *cr;
cairo_surface_t *surface;
cairo_pattern_t *pattern;
// Create the surface
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
// Ensure the surface was created
assert(cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS);
// Create a context to draw onto the surface with
cr = cairo_create(surface);
// Ensure the context was created
assert(cairo_status(cr) == CAIRO_STATUS_SUCCESS);
// Get the native width/height of the SVG
svg_cairo_get_size(svg->svg, &sourceWidth, &sourceHeight);
// Scale the cairo context such that the SVG is rendered at the desired size
cairo_scale(cr, (double) width / (double) sourceWidth,
(double) height / (double) sourceHeight);
// Render the SVG to the context
svg_cairo_render(svg->svg, cr);
// Create a pattern out of the surface
pattern = cairo_pattern_create_for_surface(surface);
// Release the cairo context and surface as they are no longer needed
cairo_destroy(cr);
cairo_surface_destroy(surface);
// Save the rendering information in the vector
render->pattern = pattern;
render->patternSize.x = width;
render->patternSize.y = height;
// Add the newly rendered image to the renders array and return it
return vectorAdd(svg->renders, render);
}
void svgManagerInit()
{
// Make sure that we are not being called twice in a row
assert(svgImages == NULL);
svgImages = vectorCreate();
}
void svgManagerQuit()
{
// Ensure that we have not already been called
assert(svgImages != NULL);
// Release all rendered SVG images
vectorMapAndDestroy(svgImages, svgManagerFreeImage);
// Note that we have quit
svgImages = NULL;
}
void svgManagerBlit(cairo_t *cr, const svgRenderedImage *svg)
{
// Save the state of the current cairo context
cairo_save(cr);
// Set the current painting source to be the rendered SVG image
cairo_set_source(cr, svg->pattern);
// Draw a rectangle the size of the image
cairo_rectangle(cr, 0.0, 0.0, svg->patternSize.x, svg->patternSize.y);
// Fill the rectangle with the pattern
cairo_fill(cr);
// Finally, paint the rectangle to the surface
cairo_paint(cr);
// Restore the cairo context
cairo_restore(cr);
}
size svgManagerGetSize(const char *filename, svgSizingPolicy policy, ...)
{
size size;
svgImage *image = svgManagerLoad(filename);
va_list ap;
va_start(ap, policy);
size = svgManagerGetSizeInternal(image, policy, ap);
va_end(ap);
return size;
}
svgRenderedImage *svgManagerGet(const char *filename, svgSizingPolicy policy, ...)
{
svgImage *svg;
svgRenderedImage *currRender, *render = NULL;
size size;
va_list ap;
// Load the SVG image (or fetch it from the cache if it is already loaded)
svg = svgManagerLoad(filename);
// Determine the size to render the image at
va_start(ap, policy);
size = svgManagerGetSizeInternal(svg, policy, ap);
va_end(ap);
// See if the image exists at the desired size
while ((currRender = vectorNext(svg->renders)))
{
// If the sizes match then we have found the render
if (currRender->patternSize.x == size.x
&& currRender->patternSize.y == size.y)
{
render = currRender;
}
}
// If no render was found then render the SVG at the requested size
if (render == NULL)
{
render = svgManagerRender(svg, size.x, size.y);
}
// Return the final rendering
return render;
}