diff --git a/lib/betawidget/src/platform/sdl/init.c b/lib/betawidget/src/platform/sdl/init.c index 8cbed1c83..2b54c3e7b 100644 --- a/lib/betawidget/src/platform/sdl/init.c +++ b/lib/betawidget/src/platform/sdl/init.c @@ -21,6 +21,7 @@ #include "init.h" #include "../../widget.h" +#include "../../svgManager.h" #include "../../window.h" #include @@ -32,6 +33,9 @@ void widgetSDLInit() // Set the screen size windowSetScreenSize(surface->w, surface->h); + + // Initialise the SVG manager + svgManagerInit(); // Create an initial window vector to store windows windowSetWindowVector(vectorCreate()); @@ -44,5 +48,8 @@ void widgetSDLQuit() // Release all active windows vectorMapAndDestroy(windowVector, (mapCallback) widgetDestroy); + + // Release any cached SVG images + svgManagerQuit(); } diff --git a/lib/betawidget/src/svgManager.c b/lib/betawidget/src/svgManager.c new file mode 100644 index 000000000..c445308f2 --- /dev/null +++ b/lib/betawidget/src/svgManager.c @@ -0,0 +1,299 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 2008 Freddie Witherden + Copyright (C) 2008 Elio Gubser + Copyright (C) 2008 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 +#include +#include + +#include + +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. + * + * @param filename The path of the image to load. + * @return A pointer to the newly loaded SVG image on success, NULL otherwise. + */ +static svgImage *svgManagerLoad(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); +} + +/** + * 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); +} + +svgRenderedImage *svgManagerGet(const char *filename, int width, int height) +{ + svgImage *currSvg, *svg = NULL; + svgRenderedImage *currRender, *render = 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 = svgManagerLoad(filename); + } + + // Fill out any missing size information + if (width == 0 || height == 0) + { + unsigned int sourceWidth, sourceHeight; + + svg_cairo_get_size(svg->svg, &sourceWidth, &sourceHeight); + + // No size information provided, render at source + if (width == 0 && height == 0) + { + width = sourceWidth; + height = sourceHeight; + } + // No width given, compute from height + else if (width == 0) + { + width = ((float) height / (float) sourceHeight) * (float) sourceWidth; + } + // No height given, compute from width + else // (height == 0) + { + height = ((float) width / (float) sourceWidth) * (float) sourceHeight; + } + } + + // 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 == width + && currRender->patternSize.y == height) + { + render = currRender; + } + } + + // If no render was found then render the SVG at the requested size + if (render == NULL) + { + render = svgManagerRender(svg, width, height); + } + + // Return the final rendering + return render; +} + +svgRenderedImage *svgManagerGetWithWidth(const char *filename, int width, + int *height) +{ + // Render the image + svgRenderedImage *render = svgManagerGet(filename, width, 0); + + // If height is non-NULL copy the height into it + if (height) + { + *height = render->patternSize.y; + } + + // Return the render + return render; +} + +svgRenderedImage *svgManagerGetWithHeight(const char *filename, int height, + int *width) +{ + // Render the image + svgRenderedImage *render = svgManagerGet(filename, 0, height); + + // If width is non-NULL copy the width into it + if (width) + { + *width = render->patternSize.x; + } + + // Return the render + return render; +} diff --git a/lib/betawidget/src/svgManager.h b/lib/betawidget/src/svgManager.h new file mode 100644 index 000000000..577a0c665 --- /dev/null +++ b/lib/betawidget/src/svgManager.h @@ -0,0 +1,115 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 2008 Freddie Witherden + Copyright (C) 2008 Elio Gubser + Copyright (C) 2008 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 +*/ + +#ifndef SVG_MANAGER_H +#define SVG_MANAGER_H + +#include "internal-cairo.h" +#include "vector.h" +#include "geom.h" + +/* + * Forward declarations + */ +typedef struct _svgRenderedImage svgRenderedImage; + +/** + * Represents a rendered SVG image which is ready to be blitted/composited onto + * a widget. + */ +struct _svgRenderedImage +{ + /// Bitmap cairo_pattern_t containing the rendered image + cairo_pattern_t *pattern; + + /// Size of the rendered image + size patternSize; +}; + +/** + * Initialises the SVG manager. This must be called before any SVG images are + * loaded/used. + */ +void svgManagerInit(void); + +/** + * Uninitialises the SVG manager. Once this method has been called the result of + * attempting to call any svgManager* methods (without a call to svgManagerInit + * first) is undefined. Undefined behaviour is also encountered if one attempts + * to call this method without having first called svgManagerInit. + */ +void svgManagerQuit(void); + +/** + * Composites the rendered SVG image, svg, onto the cairo context, cr, at the + * current translation position. The state of the cairo context is unchanged by + * this method. + * + * @param cr The cairo context to composite the image onto. + * @param svg The rendered SVG image to composite. + */ +void svgManagerBlit(cairo_t *cr, const svgRenderedImage *svg); + +/** + * Loads and renders the SVG image, filename, at a size of (width,height). In + * order to improve performance the SVG manager makes use of an image cache. + * Hence, repeated calls to this method with the same parameters will not + * result in any kind of performance penalty. + * + * If both width and height are 0 then the image will be rendered at its native + * size. If just width is 0 then the rendered width is the native width + * multiplied by the height scale factor. The same applies when the height is 0 + * except that the width scale factor is used instead. + * + * @param filename The path to the SVG image to render. + * @param width The width to render the image at, 0 for auto-select. + * @param height The height to render the image at, 0 for auto-select. + * @return A pointer to the rendered SVG image on success or NULL on failure. + */ +svgRenderedImage *svgManagerGet(const char *filename, int width, int height); + +/** + * A convenience wrapper around svgManagerGet which automatically computes the + * desired image-height based off the provided width. + * + * @param filename The path to the SVG image to render. + * @param width The width to render the image. + * @param height The resulting height of the image, may be NULL. + * @return A pointer to the rendered SVG image on success or NULL on failure. + * @see svgManagerGet + */ +svgRenderedImage *svgManagerGetWithWidth(const char *filename, int width, + int *height); + +/** + * A convenience wrapper around svgManagerGet which automatically computes the + * desired image-width based off the provided height. + * + * @param filename The path to the SVG image to render. + * @param height The height to render the image. + * @param width The resulting width of the image, may be NULL. + * @return A pointer to the rendered SVG image on success or NULL on failure. + * @see svgManagerGet + */ +svgRenderedImage *svgManagerGetWithHeight(const char *filename, int height, + int *width); + +#endif /*SVG_MANAGER_H*/