Exporting Cubic Paths

Discuss SVG code, accessible via the XML Editor.
mikeashelby
Posts: 2
Joined: Wed Sep 16, 2015 7:07 am

Exporting Cubic Paths

Postby mikeashelby » Wed Sep 16, 2015 7:18 am

Hi,

I've been bashing my head against this for 24hours and there seem to be no answers that I can find.

Basically, I'm trying to convert hand drawn lines in inkscape to Android canvas paths (using Roman Nurik's SVG Parser algorithm). However, when I copy the SVG path data from inkscape into android it all appears incorrectly. however, loading said SVG into a browser is fine.

A problem with the algorithm? No, as loading data from gimp is fine.

Any ideas what might be going on here?

My best guess is that there's some issue with the relative positioning bit in the algorithm, but it seems to behave like I'd expect. I've pasted that code in the bottom too.

It'd be nice not to have to go via gimp with every one of these SVGs - so hopefully someone can shed some light!

Mike

Path Data from Inkscape:

m 76.428571,980.93364 c -6.457974,-3.14816 -14.113763,-4.36636 -21.11213,-2.44465 -5.255395,1.7711 -9.566057,5.66601 -12.985096,9.92918 -3.860239,5.09234 -6.618858,11.31544 -6.748316,17.76983 0.393758,9.4379 9.378738,17.1342 18.660733,16.9792 5.333419,-0.092 11.156101,-1.68 14.661052,-5.9507 3.504093,-5.4571 4.356843,-12.0806 5.197735,-18.38493 0.748875,-6.02282 1.067527,-12.11238 2.348042,-18.05803 1.45938,6.45521 -0.181845,13.07935 -1.481234,19.41996 -1.325591,6.0779 -3.388929,12.6011 -0.915871,18.669 1.262838,3.2956 4.405436,6.6312 8.241935,6.0059 1.295522,-0.2024 2.345532,-1.0901 3.06172,-2.1488

Path Data from Exported Standard SVG from inkscape (renders correctly in Chrome)
m 76.428571,980.93364 c -6.889253,-3.5627 -17.922438,-4.9003 -24.481516,-0.9809 -8.805509,5.1074 -15.110471,14.449 -16.336043,24.56986 -0.650029,8.9445 6.859479,17.0136 15.47339,18.3631 6.286812,1.1833 16.016458,-1.3892 18.77027,-7.2237 4.929541,-9.4018 4.293263,-24.63276 6.595919,-34.88846 1.82465,6.8346 -1.147558,17.648 -2.571582,24.29476 -1.236496,5.2917 -1.814733,11.3739 1.425864,16.1011 1.567378,2.6597 5.113898,4.7499 8.147238,3.3231 0.77782,-0.4101 1.41025,-1.0537 1.90503,-1.7729

Same Path Loaded Into Gimp and Exported from there:
M 74.64,71.07
C 74.15,71.79 73.52,72.43 72.74,72.84
69.70,74.27 66.16,72.18 64.59,69.52
61.35,64.79 61.93,58.71 63.16,53.42
64.59,46.77 67.56,35.96 65.74,29.13
63.43,39.38 64.07,54.61 59.14,64.01
56.39,69.85 46.66,72.42 40.37,71.24
31.76,69.89 24.25,61.82 24.90,52.87
26.12,42.75 32.43,33.41 41.23,28.30
47.79,24.39 58.83,25.72 65.71,29.29

Code: Select all

package com.mike.testapps.learntowrite;
/*
 * Copyright 2014 Google Inc.
 *
 * 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 android.graphics.Path;
        import android.graphics.PointF;
        import android.util.Log;

        import java.text.ParseException;

class SvgPathParser {
    private static final int TOKEN_ABSOLUTE_COMMAND = 1;
    private static final int TOKEN_RELATIVE_COMMAND = 2;
    private static final int TOKEN_VALUE = 3;
    private static final int TOKEN_EOF = 4;

    private int mCurrentToken;
    private PointF mCurrentPoint = new PointF();
    private int mLength;
    private int mIndex;
    private String mPathString;

    protected float transformX(float x) {
        return x;
    }

    protected float transformY(float y) {
        return y;
    }

    public Path parsePath(String s) throws ParseException {
        mCurrentPoint.set(Float.NaN, Float.NaN);
        mPathString = s;
        mIndex = 0;
        mLength = mPathString.length();

        PointF tempPoint1 = new PointF();
        PointF tempPoint2 = new PointF();
        PointF tempPoint3 = new PointF();

        Path p = new Path();
        p.setFillType(Path.FillType.WINDING);

        boolean firstMove = true;
        while (mIndex < mLength) {
            char command = consumeCommand();
            boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);
            switch (command) {
                case 'M':
                case 'm': {
                    // move command
                    boolean firstPoint = true;
                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1,
                                relative && !firstPoint);//mCurrentPoint.x != Float.NaN);
                        if (firstPoint) {
                            p.moveTo(tempPoint1.x, tempPoint1.y);
                            firstPoint = false;
                            if (firstMove) {
                                mCurrentPoint.set(tempPoint1);
                                firstMove = false;
                            }
                        } else {
                            p.lineTo(tempPoint1.x, tempPoint1.y);
                        }
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }

                case 'C':
                case 'c': {
                    // curve command
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }

                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1, relative);
                        consumeAndTransformPoint(tempPoint2, relative);
                        consumeAndTransformPoint(tempPoint3, relative);
                        Log.d("SVG Parser", "tempPoint1: " + tempPoint1.x + ", " + tempPoint1.y);
                        Log.d("SVG Parser", "tempPoint2: " + tempPoint2.x + ", " + tempPoint2.y);
                        Log.d("SVG Parser", "tempPoint3: " + tempPoint3.x + ", " + tempPoint3.y);
                        p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y,
                                tempPoint3.x, tempPoint3.y);
                    }
                    mCurrentPoint.set(tempPoint3);
                    break;
                }

                case 'L':
                case 'l': {
                    // line command
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }

                    while (advanceToNextToken() == TOKEN_VALUE) {
                        consumeAndTransformPoint(tempPoint1, relative);
                        p.lineTo(tempPoint1.x, tempPoint1.y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }

                case 'H':
                case 'h': {
                    // horizontal line command
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }

                    while (advanceToNextToken() == TOKEN_VALUE) {
                        float x = transformX(consumeValue());
                        if (relative) {
                            x += mCurrentPoint.x;
                        }
                        p.lineTo(x, mCurrentPoint.y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }

                case 'V':
                case 'v': {
                    // vertical line command
                    if (mCurrentPoint.x == Float.NaN) {
                        throw new ParseException("Relative commands require current point", mIndex);
                    }

                    while (advanceToNextToken() == TOKEN_VALUE) {
                        float y = transformY(consumeValue());
                        if (relative) {
                            y += mCurrentPoint.y;
                        }
                        p.lineTo(mCurrentPoint.x, y);
                    }
                    mCurrentPoint.set(tempPoint1);
                    break;
                }

                case 'Z':
                case 'z': {
                    // close command
                    p.close();
                    break;
                }
            }

        }

        return p;
    }

    private int advanceToNextToken() {
        while (mIndex < mLength) {
            char c = mPathString.charAt(mIndex);
            if ('a' <= c && c <= 'z') {
                return (mCurrentToken = TOKEN_RELATIVE_COMMAND);
            } else if ('A' <= c && c <= 'Z') {
                return (mCurrentToken = TOKEN_ABSOLUTE_COMMAND);
            } else if (('0' <= c && c <= '9') || c == '.' || c == '-') {
                return (mCurrentToken = TOKEN_VALUE);
            }

            // skip unrecognized character
            ++mIndex;
        }

        return (mCurrentToken = TOKEN_EOF);
    }

    private char consumeCommand() throws ParseException {
        advanceToNextToken();
        if (mCurrentToken != TOKEN_RELATIVE_COMMAND && mCurrentToken != TOKEN_ABSOLUTE_COMMAND) {
            throw new ParseException("Expected command", mIndex);
        }

        return mPathString.charAt(mIndex++);
    }

    private void consumeAndTransformPoint(PointF out, boolean relative) throws ParseException {
        out.x = transformX(consumeValue());
        out.y = transformY(consumeValue());
        if (relative) {
            out.x += mCurrentPoint.x;
            out.y += mCurrentPoint.y;
        }
    }

    private float consumeValue() throws ParseException {
        advanceToNextToken();
        if (mCurrentToken != TOKEN_VALUE) {
            throw new ParseException("Expected value", mIndex);
        }

        boolean start = true;
        boolean seenDot = false;
        int index = mIndex;
        while (index < mLength) {
            char c = mPathString.charAt(index);
            if (!('0' <= c && c <= '9') && (c != '.' || seenDot) && (c != '-' || !start)) {
                // end of value
                break;
            }
            if (c == '.') {
                seenDot = true;
            }
            start = false;
            ++index;
        }

        if (index == mIndex) {
            throw new ParseException("Expected value", mIndex);
        }

        String str = mPathString.substring(mIndex, index);
        try {
            float value = Float.parseFloat(str);
            mIndex = index;
            return value;
        } catch (NumberFormatException e) {
            throw new ParseException("Invalid float value '" + str + "'.", mIndex);
        }
    }
}

Lazur
Posts: 4418
Joined: Tue Jun 14, 2016 10:38 am

Re: Exporting Cubic Paths

Postby Lazur » Wed Sep 16, 2015 7:35 am

Hi.

The gimp path uses C and M, assuming they are absolute coordinates -you can set it up in the preferences at svg output (ShiftCtrl+P).
Also you will need to remove all the transformations in the file -set optimized transformations in the prefs (it's default)- ungroup objects, avoid resizing page to content?
Needs some try and error or a deeper knowledge of the svg specs.

mikeashelby
Posts: 2
Joined: Wed Sep 16, 2015 7:07 am

Re: Exporting Cubic Paths

Postby mikeashelby » Wed Sep 16, 2015 3:53 pm

Thanks! After sleeping on it thought it must be a problem with the relative coordinate logic somewhere in the SVG Parser. I'll give forcing absolute coordinates a go.

Thanks for the swift response!

Mike


Return to “SVG / XML Code”

Who is online

Users browsing this forum: No registered users and 3 guests