Making FTI icons out of SVGs

mach_kernel

Administrator
Dec 26, 2019
18
21
3
New York City
github.com
The latest release of sgug-rse brought us launchers for RSE apps in the standard icon catalog. This was relatively easy to set up by wrapping the `update-desktop-database` scripts to copy stuff into the catalog directory for IRIX. With a placeholder icon set, it looks like this:



The next step is to get some actual icons in. Let's get started!

What are SGI icons?

`*.fti` files are SGI's vector graphics format. IRIX 5.1 (1993) was the debut of the Indigo Magic Desktop -- so if you're wondering why they didn't use SVGs, it's because this was a whole 5 years before the 1998 SVG standard was developed. Here are some docs on the FTI standard and use with the SGI desktop:

Let's crack one of these files open:

Code:
#Path 0
color(7);
bgnpolygon();
vertex(8.051858,44.963826);
vertex(8.140266,46.948369);
vertex(9.011796,49.985860);
vertex(10.550670,53.866927);
# ... more vertices
endoutlinepolygon(12);

You may recognize that directives such as `bgnpolygon` [match IRIS GL functions](https://docs.microsoft.com/en-us/windows/win32/opengl/opengl-functions-and-their-iris-gl-equivalents)!

Code:
$ grep -rni 'bgnpolygon' /usr/include/
/usr/include/gl/dlproto.h:313:extern void       gl_i_bgnpolygon( void );
/usr/include/gl/dlproto.h:314:extern void       gl_c_bgnpolygon( void );
/usr/include/gl/gl.h:1017:extern void     bgnpolygon( void );
`color(7)` corresponds to plain white. According to the specs linked above, valid values for the colors range from 0-15 for primary colors and -16 to -255 for extended colors. Below is a screenshot showing IconSmith and FTI editor's palettes:




At first I wrote 0-15 in BIN on some paper and tried to see if there was some clever scheme to use only 8 bits (e.g. `rrrggbbb`) to encode the color, but was unsuccessful. I searched endlessly for a color palette. I looked at IRIS GL headers and did find consts for each one of the 15 primary colors. I saw some really old example code online where people built color tables, but still don't really understand how this works (can someone please explain?). Only after a considerable amount of time playing with the FTI editor I began to notice:

  • The leftmost vertical row of extended colors causes the index to jump by `16`
  • Moving right within a row is `(-y * 16) + x`
  • Each column sort of looks like the color from the row of originals
  • The white column specifically like watered down versions of the colors before the initial white tile
After what was days of struggling I finally got it: each color from the 15 initial ones is mixed with every other by doing `avg(r, r'), avg(g, g'), avg(b, b')`. The `x` offset previously mentioned corresponds to the iteration offset in the primary color table. The colormap can then be generated with this Python code:

Python:
# Color
PRIMARY_COLORS = [
  (0, 0, 0),
  (255, 0, 0),
  (0, 255, 0),
  (255, 255, 0),
  (0, 0, 255),
  (255, 0, 255),
  (0, 255, 255),
  (255, 255, 255),
  (85, 85, 85),
  (198, 113, 113),
  (113, 198, 113),
  (142, 142, 56),
  (113, 113, 198),
  (142, 56, 142),
  (56, 142, 142),
  (170, 170, 170)
]
color_map = {}
for i, (r, g, b) in enumerate(PRIMARY_COLORS):
  color_map[i] = (r, g, b)
  base = -16 * i
  # zip keeps going until one collection runs out
  for j, (rm, gm, bm) in zip(range(0, i), PRIMARY_COLORS):
    mixed_index = base - j
    color_map[mixed_index] = (
      (r + rm) / 2,
      (g + gm) / 2,
      (b + bm) / 2
    )
So: if we have the vertices for a given path we can choose to make a line/path or polygon, and specify a stroke or fill, now in colors we understand.

XDG Desktop Enties

`/usr/sgug/share/applications` includes [XDG Desktop Menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html) formatted application entries that ship with each package for inclusion into your DM's launcher. RSE ships with a script to parse these and generate small scripts in the catalog directory that exec `Exec`.

For example `ddd.desktop`:

Code:
[Desktop Entry]
Version=1.0
Name=Data Display Debugger
Comment=Graphical debugger frontend
Comment[fr]=Interface graphique pour débogueur
Exec=ddd
Terminal=false
Type=Application
Icon=ddd
Categories=Development;
Icons live in `/usr/sgug/share/icons`. We need vector icons. Inconveniently the majority are PNGs, but most projects have an SVG icon (either official or fanfic) that is easily found online. Some even ship with packages we already have:

Code:
$ find /usr/sgug/share/icons -iname 'ddd*' -type f
/usr/sgug/share/icons/hicolor/48x48/apps/ddd.png
$ tree /usr/sgug/share/icons/hicolor/scalable/apps/
hicolor/scalable/apps/
|-- barrier.svg
|-- emacs.svg
|-- filled-xterm.svg
|-- mini.xterm.svg
|-- pidgin.svg
|-- xterm-color.svg
`-- xterm.svg
Make FTI out of SVG?

Most of the heavy lifting is done by the `<path>` line commands. Quoting the docs:

SVG defines 6 types of path commands, for a total of 20 commands:
MoveTo: M, m
LineTo: L, l, H, h, V, v
Cubic Bézier Curve: C, c, S, s
Quadratic Bézier Curve: Q, q, T, t
Elliptical Arc Curve: A, a
ClosePath: Z, z
FTI has support for arcs, but trying to map a path from SVG would require an operator who understands this link better. I think we can fake it though: if you keep adding sides to a polygon it will slowly start to resemble a circle. Get 100 or so points from each SVG path and your curves will probably look fine (especially icon sized).

Python has a great package for parsing SVG: svg.path:

All of these objects have a .point() function which will return the coordinates of a point on the path, where the point is given as a floating point value where 0.0 is the start of the path and 1.0 is end.
Using Python's XML parser and our shiny new tool, I was able to produce this FTI icon from this Pidgin svg! The first one shows 10 samples, then 50, 100, and 1000 (the last one is especially fun because it's all the overlapping yellow vertex circles from the editor!):



Hey! That looks like a Pidgin! Let's fix the scale next: we can find the maximum extents of the SVG (x/y) and then scale it to the 100x100 spec'd FTI grid. We need to fix the orientation of the image too, so let's reflect across the x axis by subtracting 100 from each y coordinate (the maximum allowed extent). Coordinate geometry saves the day!

Python:
def fix_scale(self):
  max_real, max_imag, max_final = 0, 0, 0
  for fti_path in self.fti_paths:
    for point in fti_path.points:
      max_real = max(max_real, point.real)
      max_imag = max(max_imag, point.imag)
  max_final = max(max_imag, max_real)
  scale = float(100) / max_final
  # ...apply scale to points
Unfortunately this only works if the SVG contains one figure (i.e. all the paths are related to one central 'thing'). Compare the Pidgin below with what happens with the vim logo next to it:



Still, this is quite the headstart towards getting a good SGI icon; we can always load up our generated icon in an editor to do last mile tasks. Looking at a few SVGs, color is proving to be a quite grueling task, because it can come from:

  • `stroke` or `fill` DOM attributes
  • CSS classes or inlined into `style` tags
  • `linearGradient` DOM objects referenced by `url(#foo)` where `#foo` is the gradient ID
  • CSS colors can be represented as
    • `#rrggbb` or shorter variants like `#rrgg`
    • `rgb(x,y,z)`
    • rgba and others
  • What makes something a polygon versus a path?
The polygon question is solved by naively choosing to use `bgnpolygon()` for everything that has a fill present. Using tinycss2's color parser, I managed to abstract color resolution to two coarse categories:

  • From attributes (in order)
    • `stroke` or `fill`
    • Follow DOM IDs to gradient objects, and walk all attribute values until we reify a valid color3 object
  • From CSS using regexes (as terrible as it sounds)


Now that we have RGB values for each path, the last challenge is going to be bucketing the color values. We have a palette of ~130 colors, which means that we won't be able to represent every single color that is encountered. Let's just choose the closest one:

Python:
def rgb2index(self, fr, fg, fb):
  avg, color = 500, 0
  for fti_index, (r, g, b) in self.color_map.items():
    cur = abs(r - fr) + abs(g - fg) + abs(b - fb)
    if cur < avg:
      avg = cur
      color = int(fti_index)
  return color

Putting all of this together, we get a result:



Using the generated FTIs

XDG desktop files that want to theme icons of files matching a certain type usually include a MIME type directive:

Code:
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
SGI's analog is tag:

tag is used to set, clear or query the tag number in a MIPS executable or
shell script that follows the convention of #!/bin/sh or #!/bin/csh on
the first line. The tag number is used by the IRIX Interactive Desktop
to determine the type of a file and thus display the appropriate icon and
have it exhibit the correct behavior when the user interacts with it.
`tag my_stuff.sh`

On flat shell scripts, it does this:
Code:
#!/usr/sgug/bin/sh
#Tag 0x121212121
It also works on binary files (this tag is the same, `0x7398CD9` is its hex representation). Wasn't able to find it annotated in `readelf` output. Oh well, it does do something:
Code:
< 0000520    0000    0001    0739    8cd9    0000    0000    0000    0000
<          \0  \0  \0 001  \a   9 214 331  \0  \0  \0  \0  \0  \0  \0  \0
---
> 0000520    0000    0000    0000    0000    0000    0000    0000    0000
>          \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
Not really being sure which tag values are appropriate for which files, I thought it was best to tag everything with `0x0`. To make sure the icon appears, we need to make an `*.tr` entry in `/usr/lib/filetype`.

Two cool things about this:
  • The match syntax is pretty expressive
  • `if (opened)` block shows how overlays are added to your icon to show them as if they were appearing on a little platform. So -- you can compose the icons!

Code:
TYPE Pidgin
    MATCH       glob("/usr/lib/desktop/iconcatalog/pages/C/RSE/pidgin") && tag == 0x00000000;
    LEGEND      :216:Unix command
    SUPERTYPE   Executable
    CMD OPEN    $LEADER $REST
    CMD ALTOPEN launch -c "$LEADER $REST"
    CMD DROP    $TARGET $SELECTED
    ICON        {
        if (opened) {
            include("../iconlib/generic.exec.open.fti");
        } else {
            include("../iconlib/generic.exec.closed.fti");
        }
    include("/usr/lib/filetype/install/iconlib/pidgin.rse.fti");
}
Afterwards, we have to run `make` after our changes:
Code:
$ cd /usr/lib/filetype
$ make -u
Creating /usr/lib/mime.types and /usr/lib/mailcap ...
Done building the .otr files.
Log in and out, and you should see your shiny new icons! Not too shabby!



You can find the repo with the `svg2fti` tool here: https://github.com/mach-kernel/svg2fti

Let's make some icons!
 
Last edited:

Elf

Storybook
Feb 4, 2019
499
134
43
Very nice work and great documentation! :)

I have to say, we should keep that Pidgin icon as-is even if some of those issues get figured out in the future.
 

About us

  • Silicon Graphics User Group (SGUG) is a community for users, developers, and admirers of Silicon Graphics (SGI) products. We aim to be a friendly hobbyist community for discussing all aspects of SGIs, including use, software development, the IRIX Operating System, and troubleshooting, as well as facilitating hardware exchange.

User Menu