Develop­ment

SVG Inline Image Extraction and Replacement with Ruby

SVG Inline Image Extraction and Replacement with Ruby

Recently, we’ve been doing some pretty cool work with Scalable Vector Graphic (SVG) files on one of our bigger projects. SVGs are essentially XML files that hold information to display a graphic. In some cases, these files need to hold rasterized images–photos–as part of an image that mixes both vector art and imagery.

This platform uses SVG files to build print assets, some of which contain several rasterized images. On the back-end, we are processing the graphics for customizations; on the front-end, we are displaying the images to the end-user. This means that inline images need to reside in application memory at some point in the workflow, which posed a problem for our processing speed.

Faced with this potential complication, we needed a solution that delivered a highly responsive front-end-by extracting the raster images before pushing the files through the processor.

Our team built the Svg Inline File Extractor Ruby gem to facilitate that exact process. When the gem is installed in an application or gemset, running the class method .binary_images returns the decoded raw raster image files. Calling with .inline_images will return an array of objects with various useful instance methods.

  svg_file = File.open([some file path]).read
  extractor = SvgInlineFileExtractor::SvgFile.new(svg_file)

  # inline_images returns nokogiri objects for each inline image in the SVG file
  inline_images = extractor.inline_images
  inline_images.each do |inline_image|
    url = [Code to persist image to S3, returns URL]

    # write the image URL to the HREF of the image tag in the SVG file
    inline_image.href_contents = url
  end

For our project, we used this method to extract the inline raster images and upload them to an S3 bucket. When the SVG file is displayed on the front-end, the end user’s browser simply reads the reference to the S3 images and displays them as part of the SVG file. This seamless solution allows us to process customized graphics while keeping our front-end user interface blazing fast.

Conversely, we also found that many image processing libraries are not able to deal with linked images. We needed to create finalized files for image processing software, so for any given SVG file we’ve processed, we had to add the rasterized images back inline in the SVG file.

This feature was built out as the second part of the Ruby gem. To accomplish this with the library, the platform instantiates a new SvgFile object from the gem with the raw SVG file string:

  raw_svg = File.open([some file path]).read # read in the raw SVG XML
  extractor = SvgInlineFileExtractor::SvgFile.new(raw_svg)
  extractor.inline_images(true).each { |image| image.set_binary_image_from_uri! }
    rendered_svg = extractor.rendered_svg
  end

We overloaded the inline_images method with a boolean parameter, false returning Base64 encoded image data, and true sending back all of the actual image tags from the SVG file (including URIs). There is a todo to give each of these options their own methods for clarity. While iterating through the inline images, calling #set_binary_image_from_uri! takes the referenced image URI, downloads the referenced image (or references from a local machine), base64 encodes, and places them back in the SVG file as inline.

This setup has allowed us to cut our image rendering time from 2-5 seconds per SVG file to less than half a second per file. Our client has been thrilled with the results, and we are excited to deliver a final product that doesn’t sacrifice performance for full functionality.

pin-icon phone