Image creation/manipulation is an essential part of UI design and, with Photoshop gone, GIMP and Inkscape came to the rescue. Almost all graphics used in the Apollo-NG realm are created with Inkscape. With many people already using Inkscape and it being a vector oriented tool creating SVGs, it was just a matter of time until the SVG standard and its implementations matured and spread. Some features, such as SMIL animation and SVG Fonts are not as widely supported. There are many SVG authoring tools, and export to SVG is supported by all major vector graphics authoring tools.
SVG 2 is currently under development, and will add new ease-of-use features to SVG, as well as more closely integrating with HTML, CSS, and the DOM, and deprecating features not supported by all browsers. The SVG Working Group is currently working in parallel on a set of modules for extending prior specifications and adding functionality to CSS, and the new SVG 2 specification will combine those modules with the rest of the SVG framework to work across the full range of devices and platforms.
Let's see about bypassing even Inkscape and learn with a simple real-world example about programming UI elements directly, as opposed to manually drawing in Inkscape and thereby giving our code the means to control the design itself, making another step towards better SDUI.
For a long time, GIFs, JPGs and PNGs were the dominating image formats for the web. Take a look at the H1 and H2 elements of this mission log for example: This used to be a PNG image, providing a seamless 45° angled pattern, which the browser (told by CSS) is to repeat on the x axis to fill the background space, which is much larger than the basic image itself, so we could get away with having a 500 byte sized pattern object to reduce bandwidth and page loading time while still giving the element a somewhat more natural contrast, instead of just a plain background color fill.
background: #3F3E3A url(/lib/tpl/apollo/images/panel_bg.png) repeat-x;
What are the problems with that solution?
In order to understand SVG/browser interaction and current limitations better, this simple PoC Demonstrator was created to see if it is indeed possible and feasible to substitute pixel based assets with in-css SVG asset delivery. The following two websites have been very helpful as a reference:
Let's start with a simple, contrast rich pattern of yellow and blue stripes and test if we can indeed use this SVG code to produce a background pattern we can repeat with CSS.
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <!-- Define the structure of the pattern to repeat --> <pattern id="stripes" x="0" y="0" width="20" height="20"> <!-- Create a yellow stripe (10x20) for the left half --> <rect x="0" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #fff533;"/> <!-- Create a blue stripe (10x20) for the right half --> <rect x="10" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #0060af;"/> </pattern> </defs> <!-- Create a rectangle with svg image size (20x20) and apply the stripes pattern to it --> <rect x="0" y="0" width="20" height="20" rx="0" ry="0" style="stroke: none; fill: url(#stripes);"/> </svg>
If you want to follow along, put this code into a file, save it with a .svg extension and open it in a browser or Inkscape. It should give you a 20px x 20px image with a 10px wide yellow and a 10px wide blue strip. Start playing with it, change some parameters and hit F5 to reload in the browser after you've saved your changes.
Unfortunately we cannot use this code directly in CSS, we have to encode the SVG source code with base64 in order to have a way to integrate into a stylesheet. On any GNU/Linux flavor you're almost certainly already equipped with the required tool for that (base64)
$ cat tilebg.svg | base64 -w0
PHN2ZyAgICAgICAgICAgIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIKICAgICAgICAgICAgICAgIHhtbG 5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgICAgICAgICAgICAgIHhtbG5zOnhsaW5r PSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KCjxkZWZzPgoKICAgIDwhLS0gRGVmaW5lIH RoZSBzdHJ1Y3R1cmUgb2YgdGhlIHBhdHRlcm4gdG8gcmVwZWF0IC0tPgogICAgPHBhdHRlcm4gICAg aWQ9InN0cmlwZXMiCiAgICAgICAgICAgICAgICB4PSIwIiB5PSIwIgogICAgICAgICAgICAgICAgd2 lkdGg9IjIwIiBoZWlnaHQ9IjIwIj4KCiAgICAgICAgPCEtLSBDcmVhdGUgYSB5ZWxsb3cgc3RyaXBl ICgxMHgyMCkgZm9yIHRoZSBsZWZ0IGhhbGYgLS0+CiAgICAgICAgPHJlY3QgICB4PSIwIiB5PSIwIg ogICAgICAgICAgICAgICAgd2lkdGg9IjEwIiBoZWlnaHQ9IjIwIgogICAgICAgICAgICAgICAgcng9 IjAiIHJ5PSIwIgogICAgICAgICAgICAgICAgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogI2ZmZj UzMzsiLz4KCiAgICAgICAgPCEtLSBDcmVhdGUgYSBibHVlIHN0cmlwZSAoMTB4MjApIGZvciB0aGUg cmlnaHQgaGFsZiAtLT4KICAgICAgICA8cmVjdCAgIHg9IjEwIiB5PSIwIgogICAgICAgICAgICAgIC Agd2lkdGg9IjEwIiBoZWlnaHQ9IjIwIgogICAgICAgICAgICAgICAgcng9IjAiIHJ5PSIwIgogICAg ICAgICAgICAgICAgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogIzAwNjBhZjsiLz4KCiAgICA8L3 BhdHRlcm4+Cgo8L2RlZnM+Cgo8IS0tIENyZWF0ZSBhIHJlY3RhbmdsZSB3aXRoIHN2ZyBpbWFnZSBz aXplICgyMHgyMCkKICAgICBhbmQgYXBwbHkgdGhlIHN0cmlwZXMgcGF0dGVybiB0byBpdCAtLT4KPH JlY3QgICAgICAgICAgIHg9IjAiIHk9IjAiCiAgICAgICAgICAgICAgICB3aWR0aD0iMjAiIGhlaWdo dD0iMjAiCiAgICAgICAgICAgICAgICByeD0iMCIgcnk9IjAiCiAgICAgICAgICAgICAgICBzdHlsZT 0ic3Ryb2tlOiBub25lOyBmaWxsOiB1cmwoI3N0cmlwZXMpOyIvPgo8L3N2Zz4K
Technically we could use the above output directly but when you look at the base64 you'll immediately recognize that there are a couple of recurring patterns there. Most of this is unnecessary white-spaces, CRs and comments which we don't really need, so lets use the power of the shell and create a one-liner to compress the code before encoding. We can keep the source files in a structured and readable state with comments/indents and won't have to worry about output size while coding:
$ cat tilebg.svg | sed -e :a -re 's/<!--.*?-->//g;/<!--/N;//ba' | tr -d '\n' | sed -e "s/[[:space:]]\+/ /g" | base64 -w0
PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3 N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPiA8cGF0 dGVybiBpZD0ic3RyaXBlcyIgeD0iMCIgeT0iMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj4gPHJlY3 QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwIiBoZWlnaHQ9IjIwIiByeD0iMCIgcnk9IjAiIHN0eWxlPSJz dHJva2U6IG5vbmU7IGZpbGw6ICNhNzAwMDA7Ii8+IDxyZWN0IHg9IjEwIiB5PSIwIiB3aWR0aD0iMT AiIGhlaWdodD0iMjAiIHJ4PSIwIiByeT0iMCIgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogIzc1 ODkwYzsiLz4gPC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMjAiIGhlaW dodD0iMjAiIHJ4PSIwIiByeT0iMCIgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogdXJsKCNzdHJp cGVzKTsiLz48L3N2Zz4=
Sweet. More than 50% reduction in this case. This can be easily automated or abstracted/shortened with an alias in your shell's configuration.
All we have to do now is to grab that string, paste it into our stylesheets or directly into HTML and tell the browser that we have used base64 to encode svg/xml image data, with this HTML/CSS code:
<div style="display: block; width: 100%; height: 100px; background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPiA8cGF0dGVybiBpZD0ic3RyaXBlcyIgeD0iMCIgeT0iMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj4gPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwIiBoZWlnaHQ9IjIwIiByeD0iMCIgcnk9IjAiIHN0eWxlPSJzdHJva2U6IG5vbmU7IGZpbGw6ICNmZmY1MzM7Ii8+IDxyZWN0IHg9IjEwIiB5PSIwIiB3aWR0aD0iMTAiIGhlaWdodD0iMjAiIHJ4PSIwIiByeT0iMCIgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogIzAwNjBhZjsiLz4gPC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHJ4PSIwIiByeT0iMCIgc3R5bGU9InN0cm9rZTogbm9uZTsgZmlsbDogdXJsKCNzdHJpcGVzKTsiLz48L3N2Zz4=') repeat !important; line-height: 100px; text-align: center; color: #fafafa"> Basic seamless yellow/blue SVG pattern background </div>
Now we use this exact code and try to render it:
If you don't believe it or think it's fakery, just hover over the element, use Right Click → Inspect Element and look at the code yourself. If it doesn't render (you don't see a yellow/blue striped image) then your browser probably doesn't support SVG.
If you want to decode some image again (for whatever reason) just use base64 -d like so:
$ cat svgtilebg.svgb64 | base64 -d > svgtilebg.svg
Ok. Now that we know, that we can actually apply this technique, it's time to see if we can easily move from the boring straight pattern to a positive 45° bottom-left to top-right rotation.
Let's take the same code from above and see how much we need to add/change in order to get the desired output pattern. Added/changed lines are marked for convenience:
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <!-- Define the structure of the pattern to repeat --> <pattern id="stripes" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"> <!-- Create a yellow stripe (10x20) for the left half --> <rect x="0" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #fff533;"/> <!-- Create a blue stripe (10x20) for the right half --> <rect x="10" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #0060af;"/> </pattern> </defs> <!-- Create a rectangle with svg image size (20x20) and apply the stripes pattern to it --> <rect x="0" y="0" width="28" height="28" rx="0" ry="0" style="stroke: none; fill: url(#stripes);"/> </svg>
Not really that much to do as you can see. The only quirk is the fact that we need to play with the output size in order to get the edges to align seamlessly in the CSS pattern repeat so you need to play a little with the output size (in this case 28 worked well). You also have to make sure that the SVG image size at the top is the same as the rect at the bottom for a seamless result. If you've followed along and have the code already in your editor, play around with it to get a better understanding.
After we've established that the principle is working and we can easily make changes, it's time to go ahead and try a less eye-cancer prone color combination:
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <!-- Define the structure of the pattern to repeat --> <pattern id="stripes" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"> <!-- Create a yellow stripe (10x20) for the left half --> <rect x="0" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #1f1e1a;"/> <!-- Create a blue stripe (10x20) for the right half --> <rect x="10" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #0f0e0a;"/> </pattern> </defs> <!-- Create a rectangle with svg image size (20x20) and apply the stripes pattern to it --> <rect x="0" y="0" width="28" height="28" rx="0" ry="0" style="stroke: none; fill: url(#stripes);"/> </svg>
Great, works as well, but now it's a little too dark. Let's brighten the base colors a bit and also add one highlighted edge at the left and a darkened edge at the right - just barely perceivable - to simulate more depth and a 3D kind of perception.
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <!-- Define the structure of the pattern to repeat --> <pattern id="stripes" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"> <!-- Create a yellow stripe (10x20) for the left half --> <rect x="0" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #2f2e2a;"/> <!-- Create a blue stripe (10x20) for the right half --> <rect x="10" y="0" width="10" height="20" rx="0" ry="0" style="stroke: none; fill: #1f1e1a;"/> <path d="M20,0 L20,20" style="stroke:#5f5e5a; stroke-width: 1px; fill:none;" /> <path d="M10,0 L10,20" style="stroke:#0f0e0a; stroke-width: 1px; fill:none;" /> </pattern> </defs> <!-- Create a rectangle with svg image size (20x20) and apply the stripes pattern to it --> <rect x="0" y="0" width="28" height="28" rx="0" ry="0" style="stroke: none; fill: url(#stripes);"/> </svg>
Perfect. From a users perspective it seems way more easy to change a couple of numbers in a text file (or even use variables for them) than to learn some counter-intuitive click path, walking through endless menus to make a small change, similar to the ease of OpenSCAD vs. old-school CAD breeds.
But the real benefit is the prospect of SDUI, to be able to cheaply create, inject and alter any kind of UI “design” element on-the-fly by backend or javascript code, thus bringing development and design closer together, which seems similar to a DevOps role to implement and teach software defined infrastructure. Of course there are still a couple of rules about design a developer-designer would have to learn combined with some practice to gain experience. However, the process and necessary things to learn can be broken down and adapted much faster. How will we call this role in the future? DevIgn?
Since we know now how to change the image by hand, we can simply teach a computer to do that :)
It's good to see a growing trend towards software defined user interfaces (SDUI) where we don't waste precious resources to design and produce a specific piece of hardware for humans to interact with directly. This was expensive, slow and inflexible. When we look at modern cars or the upcoming wearables like the LG Watch G R we see devices which provide “intelligent” displays and it's up to the software to draw any user interface we want to display, either a tachometer or a nice watch face. And they can be changed, updated and adapted to anyone's particular needs, if we get easy access to it.
UI design practices, especially for websites or web applications, went through a lot of hacks and technologies during the last 15 years. Beginning with simple HTML, frames, “invisible tables”, until finally CSS arrived. Back then UI design often was basically drawing something in Photoshop and then “slicing” the big image into many smaller GIFs or JPGs which would be rearranged in tables (no joke!). Interface design has always been just a matter of how to use visual hacks to fool the perceiving eye or to make the process more efficient by manipulating images in the browser, like bg-repeats for example.
Now fast forward to the present, where we actually have a plethora of open standards and ready-to-use software stacks to choose from. Based on HTML5, CSS3, JavaScript and modern open browsers like Chromium or Firefox, we can build anything, as long as we have the time to do so.
Due to the mission's clear demand to go full open-source, tools like Photoshop (or any other non-free closed-source tool) are no longer available. In the beginning it hurts a lot because many of us probably have spent a considerable part of their lifetime to learn how to use proprietary software first and are simply reluctant to let go of that time investment and learn something new. It should be mandatory for schools and universities to have an open-source software/technology first curriculum, instead of using tax money to buy licences from Microsoft and $else, only to force kids/students to use and train products and workflows of the limited proprietary commercial software ecosphere. And, most importantly: Scalability. When not using sprites, each PNG image is a separate request for browser/server to handle. And in case you're not sure why you want to avoid that, just step back and look at the big picture from an ops point of view here:
Let's say a website with 10 PNG UI elements is opened, the browser will get the html (request 1), interpret the html, load the stylesheet (request 2) and then go fetch the other assets (request 3-12) one by one. But each webserver has only a limited number of requests it can handle simultaneously.
When you could put your assets directly into the stylesheet, each connection would only perform 2 instead of 12 requests. As a result, the same webserver will be able to handle more than twice as many visitors before maxing out.
Additionally, more requests need more CPU, RAM, IO and therefore consume more power, not to mention the required bandwidth and power consumed in-transit by routers etc. This also applies to power constrained mobile devices like tablets or smartphones where battery-runtime is everything.
In short, minding these issues will help with:
This also scales in both directions, if you have a load-balanced multi webserver fronted with thousands of requests per second or just some tiny, low-power embedded system serving only a few clients. It's not perfect yet but it offers a great starting point in terms of flexibility and headroom for optimization.
Hopefully, you've enjoyed this introduction/experiment and could learn something from it or at least got some inspiration to solve something else in your particular problem domains.