warzone2100/lib/betawidget/src/svgManager.c

369 lines
9.5 KiB
C

/*
This file is part of Warzone 2100.
Copyright (C) 2008 Freddie Witherden
Copyright (C) 2008 Elio Gubser
Copyright (C) 2008-2009 Warzone Resurrection 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 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 = 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
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 = maxWidth;
}
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;
}