Conversation
The main motivation for this change was unnecessary double point in the icon path. The top point of the right corner of the icon are in fact two separate points. I’ve used Inkscape’s ‘Join selected nodes’ function to merge them. I’ve then rounded coordinates in the path to two decimal points (again using Inkscape) and finally using a simple regex eliminate unnecessary spaces in the `d` attribute. Working on that I've noticed that the clip paths don’t actually do anything: the paths do not go over the clip paths. I’ve thus removed the clip paths. The styles for the path were over-specified. `fill-opacity: 1`, `fill-rule: nonzero` and `stroke: none` are all initial values of the properties, so I’ve stripped them out. Finally, in the Logo images, the `width` attribute was specified without a unit. To match the `height` and the Icon images, I’ve added `pt` unit. Signed-off-by: Michal Nazarewicz <mina86@mina86.com>
|
Seems fine to me, but also these things haven't been a problem in the past 14 years so... 🤷🏼. I'll leave it to the current maintainers to decide. |
|
I’ve only noticed because importing it to FontForge results in an ‘Open Contour’ warning. |
|
Honestly, I don't quite see the point of this improvement... Why change something that served us well for so long? "Don't fix that which ain't broken..." |
|
Depends on what you consider ‘broken’. It does result in Open Contour error when importing the file in FontForge. While FF can deal with it, I figured might just as well fix it at the source. But yes, I agree that it’s not a big deal. |
|
So I had another look, and I don't think that it's an optimization, really, to just round the decimals, optimization for what? If it's optimization for making future changes or making this reusable in a nice way, then it would have been better to actually reconstruct the original design because clearly this was not designed standing on its tip, but it was designed rotated by 45°, on a regular grid, using non-rounded integers. Unfortunately, I failed to make this work with an inverse clip path; That would have been the very cleanest solution. But that would have relied on non-standard inverse clip paths. Besides, even Inkscape messes this up because it cannot apply a clip-path correctly when its (non-rotated) group contains a rotated rectangle. What I was able, however, was to reconstruct the original shapes and then construct a path that I find a lot nicer; It uses coordinates on the original grid as much as possible (path unions between rotated rectangles and circles partially ruin the beauty of this), then transforms the result into the desired size and rotation. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="92pt"
height="92pt"
viewBox="0 0 92 92"
version="1.1"
id="git-logo"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1">
<rect
style="fill:#f03c2e;fill-opacity:1"
id="background"
width="58"
height="58"
x="0"
y="0.23803365"
rx="5" />
<g
id="graph"
style="fill:#ffffff;fill-opacity:1">
<rect
id="main-branch"
width="5"
height="41"
x="38"
y="0" />
<rect
id="topic-branch"
width="5"
height="32"
x="38"
y="17"
transform="rotate(45,38,17)" />
<circle
id="branch-point"
cx="40.5"
cy="18"
r="6" />
<use
x="0"
y="0"
xlink:href="#branch-point"
id="main"
transform="translate(0,23)" />
<use
x="0"
y="0"
xlink:href="#branch-point"
id="topic"
transform="translate(-23,23)" />
</g>
</defs>
<path
id="logo"
style="fill:#f03c2e"
d="M 5,0
C 2.23,0 0,2.23 0,5
V 53
c 0,2.77 2.23,5 5,5
H 53
c 2.77,0 5,-2.23 5,-5
V 5
C 58,2.23 55.77,0 53,0
H 43
V 12.31
A 6,6 0 0 1 46.5,17.76 6,6 0 0 1 43,23.22
v 12.09
A 6,6 0 0 1 46.5,40.77
a 6,6 0 0 1 -6,6 6,6 0 0 1 -6,-6 6,6 0 0 1 3.5,-5.45
V 23.83
L 23.13,38.7
a 6,6 0 0 1 0.37,2.06 6,6 0 0 1 -6,6 6,6 0 0 1 -6,-6 6,6 0 0 1 6,-6 6,6 0 0 1 2.11,0.39
L 34.89,19.88
A 6,6 0 0 1 34.5,17.76 6,6 0 0 1 38,12.31
V 0
Z"
transform="scale(1.18) translate(-2 39) rotate(-45 0 0)" />
</svg>The definitions are obviously not strictly necessary. But it's nice to have them for future reconstruction purposes, no? |
To reiterate, my main motivation was to convert the path to a closed one. Rounding of the decimals was done automatically by Inkscape and all other changes were just drive-by low-hanging fruit.
Another idea is to use a mask image, although neither Inkscape nor FontForge understands masks so this does the opposite of what I wanted in the first place. Probably not the best approach. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="git-logo"
width="92pt" height="92pt" viewBox="0 0 78 78"
xmlns="http://www.w3.org/2000/svg">
<defs>
<mask id="mask">
<rect id="background"
width="58" height="58" rx="5"
fill="#fff" />
<g id="graph-lines" stroke="#000" stroke-width="5">
<line id="main-branch"
x1="40.5" y1="-1"
x2="40.5" y2="41" />
<line id="topic-branch"
x1="40.5" y1="18"
x2="17.5" y2="41" />
</g>
<g id="branch-points" fill="#000">
<circle id="branch-point"
cx="40.5" cy="18" r="6" />
<circle id="main"
cx="40.5" cy="41" r="6" />
<circle id="topic"
cx="17.5" cy="41" r="6" />
</g>
</mask>
</defs>
<rect width="58" height="58" fill="#f03c2e"
style="mask-image: url(#mask)"
transform="rotate(-45 39 39) translate(10 10)" />
</svg>Either way, I think using
Maybe a better approach is to have a separate source file, e.g. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="92pt" height="92pt" viewBox="0 0 78 78"
id="git-logo" xmlns="http://www.w3.org/2000/svg">
<rect id="background" fill="#f03c2e"
x="10" y="10" width="58" height="58" rx="5" />
<line id="main-branch" stroke="#fff" stroke-width="5"
x1="50.5" y1="0"
x2="50.5" y2="51" />
<line id="topic-branch" stroke="#fff" stroke-width="5"
x1="50.5" y1="28"
x2="27.5" y2="51" />
<circle id="branch-point" fill="#fff"
cx="50.5" cy="28" r="6" />
<circle id="main-start" fill="#fff"
cx="50.5" cy="51" r="6" />
<circle id="topic-start" fill="#fff"
cx="27.5" cy="51" r="6" />
</svg>and a script which generates other files: import shlex
import subprocess
ACTIONS = (
'file-open: Git-Icon-Source.svg',
'select-by-element: line',
'object-stroke-to-path',
'selection-ungroup',
'select-by-element: circle',
'path-union',
'select-by-element: rect',
'path-difference',
'transform-rotate: -45',
'object-set-attribute: id,git-logo',
'export-type: svg',
'export-plain-svg',
'object-set-attribute: fill,#f03c2e',
'export-filename: Git-Icon-1788C.svg',
'export-do',
'object-set-attribute: fill,#000',
'export-filename: Git-Icon-Black.svg',
'export-do',
'object-set-attribute: fill,#fff',
'export-filename: Git-Icon-White.svg',
'export-do',
)
def run(*cmd: str) -> None:
print('+ ' + ' '.join(shlex.quote(s) for s in cmd))
subprocess.check_call(cmd)
run('inkscape', '--actions=' + ';'.join(ACTIONS))
out_files = tuple(action[16:].strip()
for action in ACTIONS
if action.startswith('export-filename:'))
run('svgo', '-p2', '--multipass', *out_files)Upside is that people downloading the icon get minified file. Downside is that the source isn’t discoverable; also the Inkscape actions are a bit fragile so if source file is changed the script might need to be updated as well. |
|
@mina86 you have a good point here: Since there are so many near-duplicates that all need to be updated (in case of an update, that is, like the one to close the open contour), it makes sense to script that update. Seeing that Inkscape is a quite hefty dependency, I looked for alternatives, that might fit into what git-scm.com already uses in its automation (Ruby and Node.JS, essentially), and found // Generate the Git icon SVG using Paper.js boolean operations.
//
// Source geometry (on a 58x58 grid with origin at 0,0):
// - Rounded rectangle background: (0,0) 58x58, corner radius 5
// - Main branch: vertical line x=40.5, y=-1..41, stroke-width 5
// - Topic branch: diagonal (40.5,18) to (17.5,41), stroke-width 5
// - Three circles at (40.5,18), (40.5,41), (17.5,41), all r=6
//
// The result is placed into a 92x92 viewBox via:
// transform="scale(1.179487) translate(10 10) rotate(-45 29 29)"
// Design on 58x58, translate to center in 78x78, rotate -45 around
// the shape center, then scale by 92/78 to fill the 92x92 viewBox.
const paper = require('paper');
const { PaperOffset } = require('paperjs-offset');
const fs = require('fs');
const path = require('path');
paper.setup(new paper.Size(78, 78));
// Background: rounded rectangle
const bg = new paper.Path.Rectangle({
point: [0, 0],
size: [58, 58],
radius: 5,
});
// Branch lines (expand strokes into filled outlines)
const mainBranch = new paper.Path.Line({ from: [40.5, -1], to: [40.5, 41] });
const mainExp = PaperOffset.offsetStroke(mainBranch, 2.5, { cap: 'butt' });
mainBranch.remove();
const topicBranch = new paper.Path.Line({ from: [40.5, 18], to: [17.5, 41] });
const topicExp = PaperOffset.offsetStroke(topicBranch, 2.5, { cap: 'butt' });
topicBranch.remove();
// Circles
const branchPoint = new paper.Path.Circle({ center: [40.5, 18], radius: 6 });
const mainStart = new paper.Path.Circle({ center: [40.5, 41], radius: 6 });
const topicStart = new paper.Path.Circle({ center: [17.5, 41], radius: 6 });
// Unite all graph elements
let graph = mainExp.unite(topicExp);
graph = graph.unite(branchPoint);
graph = graph.unite(mainStart);
graph = graph.unite(topicStart);
// Subtract graph from background
const logo = bg.subtract(graph);
const pathData = logo.pathData;
// 92/78 scales the 78x78 intermediate space to fill the 92x92 viewBox
const transform =
`scale(${+(92 / 78).toFixed(6)}) translate(10 10) rotate(-45 29 29)`;
const colors = {
'1788C': '#f03c2e',
'Black': '#100f0d',
'White': '#fff',
};
const outDir = path.join(__dirname, 'static/images/logos/downloads');
for (const [variant, fill] of Object.entries(colors)) {
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="92pt" height="92pt"` +
` viewBox="0 0 92 92"><path fill="${fill}"` +
` transform="${transform}" d="${pathData}"/></svg>`;
const outPath = path.join(outDir, `Git-Icon-${variant}.svg`);
fs.writeFileSync(outPath, svg);
console.log(`Wrote ${outPath}`);
}The output is e.g. this minified SVG: |
|
LGTM.
On Tue, Mar 17 2026, Johannes Schindelin wrote:
```javascript
// Generate the Git icon SVG using Paper.js boolean operations.
//
// Source geometry (on a 58x58 grid with origin at 0,0):
// - Rounded rectangle background: (0,0) 58x58, corner radius 5
// - Main branch: vertical line x=40.5, y=-1..41, stroke-width 5
You can make it 0..41. I’ve used -1 in the mask-image version because
otherwise a faint line could remain as an artefact of rasterisation.
However, if all the unions and subtractions are done on the vectors,
this is no longer a worry.
// - Topic branch: diagonal (40.5,18) to (17.5,41), stroke-width 5
// - Three circles at (40.5,18), (40.5,41), (17.5,41), all r=6
//
// The result is placed into a 92x92 viewBox via:
// transform="scale(1.179487) translate(10 10) rotate(-45 29 29)"
// Design on 58x58, translate to center in 78x78, rotate -45 around
// the shape center, then scale by 92/78 to fill the 92x92 viewBox.
It’s easier to just set viewBox to `0 0 78 78` and let the image viewer
figure out the scaling itself. There’s no reason why the width and
height of viewBox need to match numeric value of width and height of the
image.
const mainBranch = new paper.Path.Line({ from: [40.5, -1], to: [40.5, 41] });
As per above, -1 → 0.
// 92/78 scales the 78x78 intermediate space to fill the 92x92 viewBox
const transform =
`scale(${+(92 / 78).toFixed(6)}) translate(10 10) rotate(-45 29 29)`;
As per above, scale can be dropped.
`<svg xmlns="http://www.w3.org/2000/svg" width="92pt" height="92pt"` +
If the whole thing is being redone, another thing to consider is whether
width and height are even needed.
…--
Best regards
ミハウ “𝓶𝓲𝓷𝓪86” ナザレヴィツ
«If at first you don’t succeed, give up skydiving»
|
The main motivation for this change was unnecessary double point in the icon path. The top point of the right corner of the icon are in fact two separate points. I’ve used Inkscape’s ‘Join selected nodes’ function to merge them. I’ve then rounded coordinates in the path to two decimal points (again using Inkscape) and finally using a simple regex eliminate unnecessary spaces in the
dattribute.Working on that I've noticed that the clip paths don’t actually do anything: the paths do not go over the clip paths. I’ve thus removed the clip paths.
The styles for the path were over-specified.
fill-opacity: 1,fill-rule: nonzeroandstroke: noneare all initial values of the properties, so I’ve stripped them out.Finally, in the Logo images, the
widthattribute was specified without a unit. To match theheightand the Icon images, I’ve addedptunit.Cc: @jasonlong