X-Git-Url: http://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=example.c;h=aa71e233cee108f95056f3d6d9edd18371c5fbb2;hb=f9d9d432a8511172a75e0ebc12eb2ab32bb75b8d;hp=d6ec4a71c9a43ef92c58439ca0bda04b6e8e7a7d;hpb=311cae99852698b5473bd092ec2c2602b2c6972a;p=libsvgtiny-pixbuf.git diff --git a/example.c b/example.c index d6ec4a7..aa71e23 100644 --- a/example.c +++ b/example.c @@ -1,6 +1,399 @@ -#include +#include /* fopen, fprintf, fread, printf */ +#include /* malloc */ + +#include +#include +#include +#include + +/* + * The width and height of the viewport that we'll render the SVG + * into. The final "picture" may not actually be this size; based on + * the height, width, viewBox, and preserveAspectRatio attributes in + * the SVG itself, libsvgtiny may scale, stretch, offset, etc. the + * paths to make them fit nicely into the viewport. + */ +#define VIEWPORT_WIDTH 512 +#define VIEWPORT_HEIGHT 512 + +/* Convenient typedefs for libsvgtiny */ +typedef struct svgtiny_diagram diagram_t; +typedef struct svgtiny_shape shape_t; + + +/** + * @brief Render an svgtiny path using cairo. + * + * @param cr + * A pointer to a valid cairo context. + * + * @param path + * A pointer to an svgtiny shape that will be rendered on the + * cairo context's target surface. + */ +static void render_path(cairo_t* cr, shape_t* path) { + unsigned int j; + + cairo_new_path(cr); + for (j = 0; j != path->path_length; ) { + switch ((int) path->path[j]) { + case svgtiny_PATH_MOVE: + cairo_move_to(cr, + path->path[j + 1], + path->path[j + 2]); + j += 3; + break; + case svgtiny_PATH_CLOSE: + cairo_close_path(cr); + j += 1; + break; + case svgtiny_PATH_LINE: + cairo_line_to(cr, + path->path[j + 1], + path->path[j + 2]); + j += 3; + break; + case svgtiny_PATH_BEZIER: + cairo_curve_to(cr, + path->path[j + 1], + path->path[j + 2], + path->path[j + 3], + path->path[j + 4], + path->path[j + 5], + path->path[j + 6]); + j += 7; + break; + default: + printf("error "); + j += 1; + } + } + if (path->fill != svgtiny_TRANSPARENT) { + cairo_set_source_rgba(cr, + svgtiny_RED(path->fill) / 255.0, + svgtiny_GREEN(path->fill) / 255.0, + svgtiny_BLUE(path->fill) / 255.0, + 1); + cairo_fill_preserve(cr); + } + if (path->stroke != svgtiny_TRANSPARENT) { + cairo_set_source_rgba(cr, + svgtiny_RED(path->stroke) / 255.0, + svgtiny_GREEN(path->stroke) / 255.0, + svgtiny_BLUE(path->stroke) / 255.0, + 1); + cairo_set_line_width(cr, path->stroke_width); + cairo_stroke_preserve(cr); + } +} + +/** + * @brief Parse an SVG file into a diagram_t structure. + * + * @param fp + * A pointer to an open file stream. + * + * @return If successful, a pointer to a @c diagram_t structure is + * returned; if not, @c NULL is returned. You are expected to @c + * svgtiny_free the result if it is valid. + */ +static diagram_t* svgtiny_diagram_from_file(FILE* fp, int width, int height) { + diagram_t* diagram; + + size_t bytecount; + char* buffer; + size_t bytesread; + svgtiny_code code; + + /* Find the size of the file stream */ + fseek(fp, 0L, SEEK_END); + bytecount = ftell(fp); + rewind(fp); + + buffer = malloc(bytecount); + if (!buffer) { + fprintf(stderr, "Unable to allocate %zd bytes\n", bytecount); + return NULL; + } + + bytesread = fread(buffer, 1, bytecount, fp); + if (bytesread != bytecount) { + fprintf(stderr, "Read only %zd of %zd bytes from stream\n", + bytesread, + bytecount); + } + fclose(fp); + + diagram = svgtiny_create(); + if (!diagram) { + fprintf(stderr, "svgtiny_create() failed\n"); + return NULL; + } + + code = svgtiny_parse(diagram, buffer, bytecount, "", width, height); + free(buffer); + + if (code != svgtiny_OK) { + fprintf(stderr, "svgtiny_parse failed: "); + switch (code) { + case svgtiny_OUT_OF_MEMORY: + fprintf(stderr, "svgtiny_OUT_OF_MEMORY"); + break; + case svgtiny_LIBDOM_ERROR: + fprintf(stderr, "svgtiny_LIBDOM_ERROR"); + break; + case svgtiny_NOT_SVG: + fprintf(stderr, "svgtiny_NOT_SVG"); + break; + case svgtiny_SVG_ERROR: + fprintf(stderr, "svgtiny_SVG_ERROR: line %i: %s", + diagram->error_line, + diagram->error_message); + break; + default: + fprintf(stderr, "unknown svgtiny_code %i", code); + break; + } + fprintf(stderr, "\n"); + return NULL; + } + + return diagram; +} + +/** + * @brief Create a cairo context from a libsvgtiny diagram. + * + * @param diagram + * A pointer to a valid libsvgtiny diagram. + * + * @return If successful, a pointer to a @c cairo_t context structure + * is returned; if not, @c NULL is returned. You are expected to @c + * cairo_destroy the result if it is valid. + */ +static cairo_t* cairo_context_from_diagram(diagram_t* diagram) { + cairo_t* cr; + cairo_surface_t* surface; + cairo_status_t crs; + unsigned int i; + + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + diagram->width, + diagram->height); + + crs = cairo_surface_status(surface); + if (crs != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, + "cairo_image_surface_create failed: %s\n", + cairo_status_to_string(crs)); + cairo_surface_destroy(surface); + return NULL; + } + + cr = cairo_create(surface); + crs = cairo_status(cr); + + /* Immediately destroy the surface which is now accessible as + cr->target */ + cairo_surface_destroy(surface); + + if (crs != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, + "cairo_create failed: %s\n", + cairo_status_to_string(crs)); + cairo_destroy(cr); + return NULL; + } + + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + /* Loop through the shapes in the diagram... */ + for (i = 0; i != diagram->shape_count; i++) { + + /* If this shape is a path, just render it. */ + if (diagram->shape[i].path) { + render_path(cr, &diagram->shape[i]); + } + + /* If this shape is text... */ + if (diagram->shape[i].text) { + /* Figure out what color to use from the R/G/B components of the + shape's stroke. */ + cairo_set_source_rgba(cr, + svgtiny_RED(diagram->shape[i].stroke) / 255.0, + svgtiny_GREEN(diagram->shape[i].stroke) / 255.0, + svgtiny_BLUE(diagram->shape[i].stroke) / 255.0, + 1); + /* Then move to the actual position of the text within the + shape... */ + cairo_move_to(cr, + diagram->shape[i].text_x, + diagram->shape[i].text_y); + + /* and draw it. */ + cairo_show_text(cr, diagram->shape[i].text); + } + } + + + /* Check the status again just for good measure? */ + crs = cairo_status(cr); + if (crs != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, + "cairo error: %s\n", + cairo_status_to_string(crs)); + cairo_destroy(cr); + return NULL; + } + + return cr; +} int main(int argc, char** argv) { - printf("Hello, world!\n"); + char* svgpath; + char* pngpath; + FILE* fp; + + diagram_t* diagram; + cairo_t* cr = 0; + + GdkPixbuf* pb; + + /* Parse arguments, and maybe print usage */ + if (argc < 3) { + printf("Usage: %s INPUT OUTPUT\n", argv[0]); + printf("Convert an SVG file (INPUT) to a PNG file (OUTPUT)\n"); + return 2; + } + + svgpath = argv[1]; + pngpath = argv[2]; + + fp = fopen(svgpath, "rb"); + if (!fp) { + perror(svgpath); + return 1; + } + + diagram = svgtiny_diagram_from_file(fp, VIEWPORT_WIDTH, VIEWPORT_HEIGHT); + if (!diagram) { + return 1; + } + + cr = cairo_context_from_diagram(diagram); + if (!cr) { + svgtiny_free(diagram); + return 1; + } + + /* We're using the viewport width and height and not the diagram + * width/height for the image. The diagram can be of a different + * size and aspect ratio than the viewport, and our main use case is + * for icons that are generally square and reasonably sized. If the + * diagram is "small," then we want to scale it up until it fits + * nicely in the viewport before rendering it. That's as opposed to + * rendering the image small, and letting GDK scale it up. Of course + * this reasoning makes the assumption that the viewport is usually + * larger than the diagram. + */ + pb = gdk_pixbuf_get_from_surface(cairo_get_target(cr), + 0, + 0, + VIEWPORT_WIDTH, + VIEWPORT_HEIGHT); + + + if (pb) { + gdk_pixbuf_save(pb, pngpath, "png", NULL, NULL); + g_object_unref(pb); + } + return 0; } + + +static GdkPixbuf* gdk_pixbuf_from_svg_file_stream(FILE *fp, GError **error) { + diagram_t* diagram; + cairo_t* cr = 0; + + GdkPixbuf* pb; + + diagram = svgtiny_diagram_from_file(fp, VIEWPORT_WIDTH, VIEWPORT_HEIGHT); + if (!diagram) { + return NULL; + } + + cr = cairo_context_from_diagram(diagram); + if (!cr) { + svgtiny_free(diagram); + return NULL; + } + + + /* We're using the viewport width and height and not the diagram + * width/height for the image. The diagram can be of a different + * size and aspect ratio than the viewport, and our main use case is + * for icons that are generally square and reasonably sized. If the + * diagram is "small," then we want to scale it up until it fits + * nicely in the viewport before rendering it. That's as opposed to + * rendering the image small, and letting GDK scale it up. Of course + * this reasoning makes the assumption that the viewport is usually + * larger than the diagram. + */ + pb = gdk_pixbuf_get_from_surface(cairo_get_target(cr), + 0, + 0, + VIEWPORT_WIDTH, + VIEWPORT_HEIGHT); + + + if (!pb) { + fprintf(stderr, "gdk_pixbuf_get_from_surface failed!\n"); + } + + return pb; +} + + +G_MODULE_EXPORT void fill_vtable(GdkPixbufModule* module); +void fill_vtable(GdkPixbufModule* module) { + module->load = gdk_pixbuf_from_svg_file_stream; +} + +G_MODULE_EXPORT void fill_info(GdkPixbufFormat *info); +void fill_info(GdkPixbufFormat* info) { + /* Borrowed from librsvg-2.40.21 */ + static const GdkPixbufModulePattern signature[] = { + { " name = "svg"; + info->signature = (GdkPixbufModulePattern*) signature; + info->description = "Scalable Vector Graphics"; + info->mime_types = (gchar**) mime_types; + info->extensions = (gchar**) extensions; + info->flags = GDK_PIXBUF_FORMAT_SCALABLE; + info->license = "AGPL3"; +} +