Skip to content

Instantly share code, notes, and snippets.

@qinshulei
Last active September 20, 2022 07:29
Show Gist options
  • Save qinshulei/933c4648cc709179a49c to your computer and use it in GitHub Desktop.
Save qinshulei/933c4648cc709179a49c to your computer and use it in GitHub Desktop.
Convert Android VectorDrawable to png

转换方法:

  1. Convert Android VectorDrawable to SVG:

使用附件中的java程序。命令如下:

cp Vector2Svg.java path/to/xml_dir
javac Vector2Svg.java
java Vector2Svg ./*.xml
mkdir svg_dir
mv *.svg svg_dir

./*.xml为目标Android VectorDrawable的xml文件列表 然后程序会在对应目录生成同名的svg

  1. Convert SVG to PNG:

最简单的方法是直接使用Ubuntu的图片浏览器打开,然后另存为png

批量转换:

sudo apt-get install librsvg2-bin
cd svg_dir
for i in *; do rsvg-convert $i -o `echo $i | sed -e 's/svg$/png/'`; done

安装rsvg库,使用rsvg 转换svg到png.

附件中svg.tgz,是昨天图片的png结果。

/*
* Copyright (C) 2015. Jared Rummler <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
public class Vector2Svg {
public static void main(String[] args) {
if (args == null || args.length == 0 || args[0].equals("--help")) {
printUsage();
return;
}
for (String path : args) {
Vector2Svg converter = new Vector2Svg(new File(path));
if (!converter.createSvg()) {
System.out.println("Error creating SVG from " + path);
}
}
}
private static void printUsage() {
File jarFile =
new File(Vector2Svg.class.getProtectionDomain().getCodeSource().getLocation().getPath());
System.out.println("Convert Android VectorDrawable XML resource file to SVG");
System.out.println();
System.out.println(String.format("Usage: java -jar %s [FILE]...", jarFile.getName()));
}
private final File source;
private final File destination;
public Vector2Svg(File source) {
this(source, new File(source.getParent(), source.getName().replaceFirst("[.][^.]+$", ".svg")));
}
public Vector2Svg(File source, File destination) {
this.source = source;
this.destination = destination;
}
public boolean createSvg() {
try {
AndroidVectorDrawable drawable = getDrawable();
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element svg = doc.createElement("svg");
svg.setAttribute("viewBox", String.format("0 0 %.1f %.1f", drawable.width, drawable.height));
for (Group group : drawable.groups) {
Element g = doc.createElement("g");
for (VectorPath path : group.paths) {
Element child = doc.createElement("path");
if (path.fillColor != null) {
child.setAttribute("fill", path.fillColor);
}
child.setAttribute("d", path.pathData);
g.appendChild(child);
}
svg.appendChild(g);
}
for (VectorPath path : drawable.paths) {
Element child = doc.createElement("path");
if (path.fillColor != null) {
child.setAttribute("fill", path.fillColor);
}
child.setAttribute("d", path.pathData);
svg.appendChild(child);
}
doc.appendChild(svg);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(destination);
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.transform(source, result);
return true;
} catch (Exception e) {
return false;
}
}
private AndroidVectorDrawable getDrawable()
throws ParserConfigurationException, IOException, SAXException {
Document xml =
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(source);
xml.getDocumentElement().normalize();
Node vector = xml.getElementsByTagName("vector").item(0);
NamedNodeMap attributes = vector.getAttributes();
NodeList children = vector.getChildNodes();
double width = 0;
double height = 0;
for (int i = 0; i < attributes.getLength(); i++) {
if (attributes.item(i).getNodeName().equals("android:viewportHeight")) {
height = Double.parseDouble(attributes.item(i).getNodeValue());
} else if (attributes.item(i).getNodeName().equals("android:viewportWidth")) {
width = Double.parseDouble(attributes.item(i).getNodeValue());
}
}
List<VectorPath> paths = new ArrayList<>();
List<Group> groups = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
Node item = children.item(i);
if (item.getNodeName().equals("group")) {
List<VectorPath> groupPaths = new ArrayList<>();
for (int j = 0; j < item.getChildNodes().getLength(); j++) {
VectorPath path = getVectorPathFromNode(item.getChildNodes().item(j));
if (path != null) {
groupPaths.add(path);
}
}
if (!groupPaths.isEmpty()) {
groups.add(new Group(groupPaths));
}
} else {
VectorPath path = getVectorPathFromNode(item);
if (path != null) {
paths.add(path);
}
}
}
return new AndroidVectorDrawable(paths, groups, width, height);
}
private VectorPath getVectorPathFromNode(Node item) {
if (item.getNodeName().equals("path")) {
String pathData = null;
String fillColor = null;
for (int j = 0; j < item.getAttributes().getLength(); j++) {
Node node = item.getAttributes().item(j);
String name = node.getNodeName();
String value = node.getNodeValue();
if (name.equals("android:pathData")) {
pathData = value;
} else if (name.equals("android:fillColor") && value.startsWith("#")) {
fillColor = value;
}
}
if (pathData != null) {
return new VectorPath(pathData, fillColor);
}
}
return null;
}
private class VectorPath {
private String pathData;
private String fillColor;
private VectorPath(String pathData, String fillColor) {
this.pathData = pathData;
this.fillColor = fillColor;
}
}
private class Group {
private final List<VectorPath> paths;
public Group(List<VectorPath> paths) {
this.paths = paths;
}
}
private class AndroidVectorDrawable {
private final List<VectorPath> paths;
private final List<Group> groups;
private final double height;
private final double width;
private AndroidVectorDrawable(List<VectorPath> paths, List<Group> groups,
double width, double height) {
this.paths = paths;
this.groups = groups;
this.height = height;
this.width = width;
}
}
}
@ozmium
Copy link

ozmium commented Aug 4, 2017

On line 78, you need to add: svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");

On line 169 to 171, you need this:

} else if (name.equals("android:fillColor") && value.startsWith("#")) {
       if (value.length() == 9) {
           // ARGB (Alpha RGB) HEX format used, like "#FF664711".
           // Convert it to RGBA, like "#664711FF".
           fillColor = "#" + value.substring(3, 9) + value.substring(1,3);
       } else {
            fillColor = value;
       }
}

@martinellimarco
Copy link

Add a default Locale int String.format, like this:
svg.setAttribute("viewBox", String.format(Locale.ROOT, "0 0 %.1f %.1f", drawable.width, drawable.height));
otherwhise on certain Locale (for example italian) numbers will be formatted with , instead of . as decimal separator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment