9 Improvements to SVG Export

• Nov 9, 2015 - 17:00
Type
Functional
Severity
S5 - Suggestion
Status
closed
Project

See this forum topic for a discussion (towards the bottom of the thread): https://musescore.org/en/node/61746
I have now created pull request #2286: https://github.com/musescore/MuseScore/pull/2286

The 3 Features:
1) For each <path> and <polyline> element: an attribute indicating the element type. The value is an Element::Type cast as an integer. For example:

The "data-" prefix is a recommended practice for custom SVG attributes. I achieved this functionality by adding a function SvgGenerator::setElement(const Element* e). This sets a variable that the code can use when writing the SVG file.

2) Remove the group element wrappers (<g>) around everything. Each path and polyline element is separated by a newline and that's it. With the "data-type" as the first attribute specified you can see the element types sorted in Element::Type enumeration order. The purpose of this is mainly to improve the readability of the .svg file by removing unnecessary elements.

3) If an attribute value is the same as the SVG default value, omit that attribute from the element in the file. This reduces the file size for any score where the notation is black, for example, because the default for the SVG "fill" attribute is black. This also improves file readability, IMO.

I've attached two sample files, one is exported from MuseScore 2.0.2 (current release version), the other includes these 3 "improvements". You'll notice that the "improved" file has approximately 1/10th the number of lines as the 2.0.2 version.

Note that the code distinguishes between System and Measure bar lines. This causes the appearance of data-type="74" mixed in with data-type="6" in the exported file. 74 is Element::Type::SYSTEM, 6 is Element::Type::BAR_LINE

Attachment Size
2.0.2.svg 120.31 KB
test3i.svg 76.87 KB

Comments

Status (old) patch (code needs review) closed

This issue is being closed, and a new one is being opened that will include these same improvements plus additional ones.

OK, I thought you wanted me to close this issue and open a new one. Whatever works. I can reopen this one, if that's the way to go.

No, it's a good idea. This is a cool, easy to remember number. I'm still haggling with github support, and still awaiting some feedback on the set of 6 improvements. When all that is clear I'll update everything here. Thx.

This seems as good a place as any to post this for now: I deleted my entire MuseScore fork on GitHub, created a new fork, and cloned it to my client. Then in the github client I created a new branch. Then I opened the project in Qt Creator, ran the initial cmake, and now github client says I have 1567 changes, none of which are files I want to commit.
This is what happened to me previously, and how I took a wrong turn on the git highway. What should I do about all these changes to "compiled" files? Thanks in advance.

I think you mean to point me to the git workflow section of the handbook, don't you? here: https://musescore.org/en/developers-handbook/git-workflow

I'm following the compile instructions for sure, but now that I look at the git workflow instructions, I am not following those to the letter. I was hoping to avoid marrying myself to the git command line, but it looks like I'll have to make that commitment. If there's really something in the compile instructions you think I should look at, I will, but for now I'm going to start over with git and follow the command line workflow instructions to the letter. Thanks.

No, I meant to point you at where I did point you. If you gor example don't call the build directory build.qtc, git won't ignore them and record them as new/modified files. Same for the win32install directory

You're right, I missed that step. I was using MuseScore/build as the build directory because it already existed and seemed like the place to start. Thanks.

The Win32Install directory is mentioned, but I see no mention of .qtc suffix anywhere except the build directory. I searched the page for .qtc and qtc. I'm following the steps as we exchange these comments, so hopefully I'll be arriving to a correct state sometime soon.

I followed the instructions to the letter and now git looks more normal. Thanks for your assistance. If there's no feedback to my post on the other SVG topic over the next day or so, then I'll package all 6 changes and submit the pull request.

Title 3 improvements to SVG Export 9 Improvements to SVG Export
Status (old) closed active

This is a complete reset of the original issue:
1) No more group wrappers for anything - There are now zero SVG <g> elements in the exported files. This reduces file size and greatly improves file readability.

2) Default values not written to the file - If an element's attribute value is the same as the default for that attribute, omit the attribute setting from the file (for that element). This reduces the file size noticeably and eliminates masses of repeated settings of defaults like fill="#000000".

3) class="TypeName" - Each element's type name (Element::Type as a string) is assigned to the standard SVG class attribute. This enables CSS and other forms of extensibility to the file and the score. SvgPaintEngine now has a member of type Element*, set from saveSVG() for each element being "painted". That is how the element type is known. Having this Element* available creates additional possibilities for future enhancements to the SVG output.

4) No more transforms - MuseScore exports SVG path and polyline elements. For all intents and purposes, version 2.0.2 draws all of those paths and polylines relative to the coordinates 0,0 then applies a transform attribute to position them on the canvas. The new code simplifies the resulting file by eliminating the transform attribute entirely and adjusting the coordinates of each path/polyline to the correct location on the canvas. A very simple change in the code, as all the data necessary is already available in all the right places.

5) Staff lines drawn once per system, not once per measure - This has the greatest impact on piano-roll-style scores or scores with extremely wide pages. MuseScore draws staff lines by measure internally. The new code simply consolidates all the measures for each system, and only draws one line per system. Smaller files, vastly improved readability of staff lines in the file.

6) 1:1 Scaling - In today's code, the scaling in file.cpp/saveSVG() scales to 300dpi, which is based on the SvgGenerator being a "printer" and this being a pdf-style print job. SVG is scalable, it doesn't require pre-scaling. So the mag variable in SaveSVG() is now always set to 1. It could be removed completely, but setting it to 1 is a safe first pass. It can be eliminated after it's been proven in users' hands for a while.

7) Two changes to the basic layout of the SVG document in order to improve consistency and simplify code.
a) <svg> element width and height attributes, units changed from mm to px - SVG default is px (pixels), the code is converting pixels to millimeters in order to set the value, so why bother converting? The resulting file should display in exactly the same size/scale as previously, but simpler code and more SVG-like SVG.
b) Continuing where 7a) leaves off, setting the width and height values to be more precisely the same as the viewBox values. They are the same in 2.0.2, but with different precision. The new code ignores the internal size variable and uses the viewBox values for everything, because they have more precision.

8) Three changes to normalize the code with the latest 72dpi efforts:
a) Initialization of the SvgPaintEnginePrivate class, change:
resolution = 72;
to
resolution = Ms::DPI;
b)saveSVG() - Delete the 2nd line of code, which sets this same variable to the same default value:
printer.setResolution(DPI)
c) SvgGenerator::metric() -It was using a literal 25.4 value in two places. For example, this line of code:
return qRound(d->engine->size().height() * 25.4 / d->engine->resolution());
is now:
return qRound(d->engine->size().height() / Ms::DPMM);

9) SvgGenerator::metric() again - This time it's a now previously "unhandled metric": PdmDevicePixelRatio. I have set the return value to 1 for this newly added metric, as that seems to be the baseline value, per the Qt Help docs. v2.0.2 generates multiple warnings because of this:
default:
qWarning("SvgGenerator::metric(), unhandled metric %d\n", metric);

And finally, as a bonus: 10) All the unused gradient and font-related code is now commented out, as are the unused members of the SvgPaintEngine attributes structure.

Status (old) patch (code needs review) fixed

The PR has been merged in master by @wscheeer. See https://github.com/musescore/MuseScore/commit/313f17d8fb2beb489c57ecd8d…

Honestly, I'm happy with all these changes with one exception:

5) Staff lines drawn once per system, not once per measure. I think that will be hard to keep if we let user change number of stafflines on a measure basis. Also it's currently buggy if one hide a staff on a given measure. I can imagine that it doesn't handle the cuteaway staff very well either.

I'm not sure if we want to take the pain of dealing with this case or just keep writing staff lines per measure.

Attachment Size
test-stafftext-invisible.mscz 3.82 KB

This staff lines code is easily removed if you so desire. The code is isolated to two places: 1) the initial loop that handles staff lines (file.cpp lines 2551-2564, and 2) the switch statement that then ignores staff lines (lines 2577-2580).

I've never even considered the possibility of an invisible measure like the example you posted, but the code could be modified to handle it inside that first loop. I don't know what a"cutaway" staff is. Is that when the last measure of a score doesn't fill the width of the page, because that case should be handled. As for changing the # of staff lines on a per measure basis, I thought I raised that issue prior to submitting the pull request and no one complained. But if it's necessary, this code does not handle it, as you point out.

In any case, the handling of those cases would take place in that initial loop. Let me know if I can be of assistance in any way, either removing the code or attempting to handle these cases.

I created an issue to track the progress. See #92901: SVG regression : Invisible measure appears in SVG output

If you can fix the bug properly by taking into account the invisible measure and the cutaway staff, then it would also be possible to deal with the different stafflines per measure in the future. So let's explore this possibility. If it ends up being too complex, then we can always restore the code.

Cutaway is a property in Staff Properties. If it's checked, the empty measures in a staff are completely hidden. It's new in the master branch.

I was just in the process of replying when I saw your newest post. Yes, I can see how to handle this stuff - it complicates an otherwise simple loop, but it shouldn't be a big deal. The different # of staff lines may be easy enough to fit in during the rest of this patch, as the code will now need to review properties of every measure in the system anyway. So checking for another property, # of lines instead of visibility, should be straightforward at that point.

I'll switch over to that issue and post code when I have it available. I was going to simply use the same old, now merged, branch in github, but it looks like I should create a new one with the new issue number. Am I correct about that?

My first thought about fixing this is:
> If (cutaway=TRUE for staff OR invisible measure in system) then
>> iterate by measure within this system
The code is currently iterating by page, by system. So this would maintain that, but within the "by system" loop it could operate by measure or not, depending on the circumstances.

That's a very simple, very complete fix, and once I figure out how to determine if the number of staff lines has changed, it will include that case with ease as well.

I was just about to post the question: "How do I know how many lines there are in the staff?" Currently the "lines" variable is private inside class StaffLines.

I've already got separate cases setup for the cutaway vs. visible - I can check a staff's cutaway without having to iterate over all the measures. But I'll check out the code link and consider doing it all by measure visibility in one case.

Thanks!

Oops - I have never used the capture tool. I'll check that out. Hopefully it's something simple that I forgot to connect.

Is there anywhere I could have found an appropriate list of test cases prior to making the pull request? Just trying to figure out how we might have avoided this extra churn after the merge.

Yes, I see this now. I need to update one block of code in fotomode.cpp. This should be an extremely simple fix.

Should I make two branches, one for each issue number here? That doesn't sound right to me. Please let me know how to manage that part on github. I should have these both issues fixed shortly. I'm currently working on a branch I've named: #92901-SVG-Invisible-Measure.

You can make a single branch with two commits, one per issue. Please use "fix #XXXXX" in your commit message.

Regarding cutaway and invisible measure, Measure::visible(int staffIdx) already deals with cutaway and the different cases for a measure to be non visible. It's probably not needed to test the cutaway property yourself.

regarding cutaway vs. invisible: I agree and am in the process of working that very code. I'll post further details in the other issues' threads as I have the fixes posted to github.

FWIW, the "cutaway" flag is new since 2.0.2, something I added. As far as I could tell, there was only one place where this flag needed to be checked in laying out or drawing a score: in Measure::visible(int staffIdx). I'd have been very surprised if SVG export turned out to need additional special handling for this.

As for finding the number of staff lines for a staff, Staff::lines() is public.

Thanks for the info. Regarding staff lines: If you like, I can include code that checks for a change in the number of staff lines within a staff (within a system), even though that code will never execute until the UI feature is created. The code I have already committed to github for new issue #92901 could be easily adapted to add this feature internally now. That way it doesn't cause what I imagine to be an obscure problem in the future, if/when that UI feature is added.

EDIT: I see now that I cannot add this code because Staff::lines() does not take a parameter to indicate the measure, it only allows for one value for the staff for the entire score. So never mind.

Regarding Staff:lines(), indeed, there i no facility to allow this to change mid-score. There has been some discussion of allowing things like that, also maybe other mid-score changes to staff properties. This came up when I was working on getting mid-score transposition to work. I considered allowing *all* staff properties to change mid-score while I was at it. But in the end, it turned out that all the necessary framework was already in place for transposition, whereas allowing other staff properties to change mid-score would have been a much larger change. So I punted on that and just got transposition working. See #9352: Add ability to set transposition by range, and in particular my comments in response #4. I guess maybe special-casing "lines()" to make it take a tick as a parameter would be easier than allowing it for all staff properties, and probably would satisfy most use cases, but it will still be a reasonably big change and could easily turn out to be tricky.

Regarding the use of 72, my own personal opinion is that if there is an actual user-visible bug, then yes you should submit an issue. If it's just something that bothers you about the use of a numeric rather than symbolic constant, I'd just make the fix, maybe along with some semi-related PR.

Re: SVG Export and DPI
-This new SVG export code exports without any scaling.
-72dpi is fixed internally in MuseScore, but the user display scales to a higher resolution.
-The result is an exported SVG that is absolutely fine, but that by default does not display on the screen at the same size as the score in MuseScore.

You might have noticed that the exported SVG appears smaller on your screen than the score in MuseScore. I can "fix" that, and the fix does not involve scaling all of the svg elements individually, as MuseScore did previously. It is a simple viewBox/size attribute manipulation, at the top of the file. If someone points me in the right direction for the 72dpi to actual screen resolution conversion factor, this is a simple change. I am already doing something similar, but fixed resolution, for my animated exports. I don't really have an opinion on the correct action here for MuseScore in general, and it doesn't affect my animated files, so let me know if this is a desirable change and where to look for a conversion factor. Thanks.

EDIT: As I continue working on related code I have this thought:
To implement staff space (spatium) scaling via the viewBox/size attributes.

I can see how for image capture copy/paste into any office or desktop publishing app this could be useful and even important. This way the user has consistency on their monitor across MuseScore and the external app, and the final non-MuseScore document contains items that are scaled the way the user intended.
-For now, no scaling at all is applied when creating the SVG.
-Previously, MuseScore SVG scaled based on the ratio between the default staff spacing (SPATIUM20) and the actual staff spacing, but it did so by scaling all the values in every element.
The viewBox/size attributes are the best place to do this ...if you want MuseScore to scale the SVG at all.
This would be in addition to the scaling for hi res monitors mentioned above. It would achieve maximum equivalence between the default size of the exported SVG and the size of the score displayed on the user's monitor within MuseScore.

Don't know if it's still ok to make comments as I see the status reads "closed".

But I want to express that I find it better to have SVG width and height defined in mm than in px. (viewbox can remain as it is)
Say you have a DinA4 score and it exports now as SVG with px units.
If you import that SVG into LibreOffice or Inkscape you get a wrong image size.
LibreOffice:
height: 29.7cm become 23.79cm
width: 21cm become 16.83cm
Inkscape:
height: 29.7cm become 23.76001cm
width: 21cm become 16.80001cm

With mm you are more on the safe side regarding DTP.

I'm the one who wrote this code. This is certainly not a difficult change, but I think the real issue is that there are now two different requirements for SVG export, and the only way to resolve it is via a new user option of some sort.

That is if you're certain that the units being pixels is the source of the problem. Have you changed the viewBox, width, and height values to A4 numbers and applied the "mm" suffix in the svg file?

In my own version of MuseScore I have added a point/pixel option on the page layout dialog, so that points/pixels, which are currently the same in MuseScore at 72dpi, can be yet another base unit in MuseScore. Unfortunately that requires a change to the file format, which is not an easy thing to achieve in any opensource program. But adding that feature to MuseScore would pave a very logical path towards the viewBox, width, and height units for SVG files.

Otherwise there would need to be an option somewhere other than Page Layout to make this choice.

If you're serious about this you'll need to open a new issue, as this one is closed. If so, please include example file(s) and let's make sure that the issue is the pixel/point units, not something else. There are other sizing issues relating to screen size, but they shouldn't affect page dimensions.
If you are simply looking for a workaround you can obviously replace the pixel/point values in the viewBox, width, and height attribute values within the svg element itself, at the top of the file. I might be stating the obvious, but I want to make sure you have a way to function within the current release of MuseScore. You can use the page size values you know, be it A4 or letter or whatever, then follow that number with "mm". Or if you prefer you can inches, and then the suffix is "in". So viewBox="0 0 1920 1080" might become viewBox="0 0 35mm 25mm" (not that those numbers make any sense, but the formatting is correct). I hope that's clear or that you're already doing what I'm trying to communicate here.

I was editing my post above while you already replied :-)

Yes, for me it's sure easy to just write 297mm and 210mm as width and height. This is sufficient for correct import. The viewbox values can be untouched.
But perhaps the chosen px values could be thought over as it seems that both Libreoffice and Inkscape computes similar when converting from px to mm.
For Inkscape (and LibreOffice):
210mm ~ 744.09449px
297mm ~ 1052.36220px

It's also possible that there is a scaling factor that is distorting the numbers and it has nothing to do with the units being points/pixels or mm or inches. There is an outstanding scaling issue, and that's my concern with the way this is reported as a new issue.

Though if you're exporting the whole page, the Page Layout numbers in those same Page Layout units should cause the correct scaling regardless. If you're not exporting the whole page it's a different story.

There are various issues to consider in modifying the functionality, unfortunately. I would love to integrate points/pixels as Page Layout units in MuseScore, but my initial proposals about it were not greeted enthusiastically. As I hope I explained above, that would make this a straightforward upgrade to use the exact values/units used in the page layout.

And the viewBox should align with the width/height values, regardless of whether some programs ignore it. It's super simple to make align those values anyway.

Hi,

at first thank you for the good idea to have classes!!! Also that the stafflines aren't cut at each bar makes it really easier to edit a score in Inkscape. These are great improvements! Thank you!

Now back to the units thing.
I think I found the simple solution:
The values that are exported in the SVG as px will result in correct scaling when changed to pt.

(I knew these numbers looked familiar to me as I've seen them in PDFs.)
So a DinA4 page is exported in the SVG as
width="595.276px" height="841.89px"
If changed to
width="595.276pt" height="841.89pt"
it will import correctly in other apps.

Greets!

Your welcome.

Regarding units, that's an interesting idea, putting the "pt" suffix after the number. But there are two reasons why that doesn't solve the problem:

1) points and pixels may be the same inside MuseScore since the 72dpi change, but that doesn't mean they're the same anywhere else.

2) there is still an outstanding scaling issue causing the pixel/point numbers to be incorrect.

I think the real solution is to use the numbers/units as specified in the Page Layout dialog whenever you export a full score.

Marc, the three issues you mention above are now fixed/merged (PR 2552). Although 105336 was not auto-marked as fixed because of my mistaken omission of the issue number in the commit message.

The issue that @musikai raises is still open, and still requires further discussion relative to a real solution. It does relate to the scaling issue(s) that is still open here:

https://musescore.org/en/node/100781

Though that is a "bug report", not an "issue".

Hey Sideways, or anyone.
Musescore has been updated since this issue to allow for mid-system changes of staff lines and other things with Staff Type Change elements. Up for the task? As it is, SVG export doesn't honor these changes when they're placed in the middle of a system.

But another issue is mentioned here: https://musescore.org/en/node/317084 where the z-stacking order of staves is entirely omitted in the act of doing a first-pass of the staves before going on to the elements and then omitting staves in that next phase. It sounds nice at first, but remember some elements can be altered to have a different z-order so that the user can have something be "behind" the staff lines. Images especially come to mind. This SVG export code fails to involve this and in turn, a resulting SVG file will not look equivalent to in-app MuseScore file or PDF file. This ought to be changed, especially since the .com site uses the SVG results to show the online uploads.

I don't particularly want to do the code changes, but I will if I know no one else will do it...

In reply to by worldwideweary

I'm not sure if I'm available to work on this at this time. I can certainly assist you if you have any specific questions about the code. The staff lines code is designed to handle the most common cases with the greatest efficiency, but it should handle all the cases. In a situation where there are gaps in the staff lines, it processes them by measure, otherwise it processes them by system. I would think that the only change you would need to make is to enhance that if statement to check for staff type changes in addition to invisible staff lines.
As for the z-order, are you saying that by processing the staff lines separately this issue arises? If so, then it would probably be best to check for this extremely specific case and handle it as a special case. How often do z-order issues arise? This is the first I've heard of any.
The staff line code creates huge efficiencies in most SVG files, so please don't destroy it just to handle a few obscure cases. IMO it's important to handle the vast majority of cases cleanly, not to bloat all SVG files just because of a few obscure cases.

In reply to by sideways

Thanks for responding. The separate drawing of staff lines on first pass as it currently is demands there to be no accounting of staff z-order. The second pass which deals with all elements uses [std::stable_sort(pel.begin(), pel.end(), elementLessThan);] where elementsLessThan takes into account the z-order, but not so with the staff lines. I can't answer your question "how often do z-order issues arise?" as I don't have access to user telemetry data etc (nor would I want access), but I recognize that SVG output as it currently is is in not consistent with PDF/Musescore display. Obviously efficiency of file size is important, but it'd be also most appropriate if exported SVG files didn't have inconsistencies with the main program or PDF output when a user has had an image or any element have its z-stack order manually altered to be below "1200" which can be performed quite easily in the main app through the inspector.

Whoever fixes the bugs here would need to:
1) Check for Staff Type Changes as you said, but then need to traverse segments of a measure (maybe just the first segment? They can't currently be placed on any element but rather upon the whole measure, but I did read that someone suggested that that might be the case later to allow for midway measures to have a staff-type change, strange as that might sound) instead of a quick measure->isVisible() check.

2) Draw staff lines in the same area of the ordering of elements so that the 1200/1300 z-order is taken into account all the while some how attempting to do system-by-system when there's no reason not to do so.

If you think you have the time to fix these obscure but truly existing bugs you introduced later, please post. For me, it isn't obscure since I'd like to have images in the background, but I don't know other peoples' desires as relates to that. Although the file size definitely was less efficient, these problems wouldn't have manifest without the changes. It'd be cool if MuseScore could keep the SVG file-size efficiency while fixing the retrogressions (I say retrogressions because if the code hadn't changed, these issues would never arise, although without the added benefits of smaller file size)

As a test, I compared a one page score and noticed the resulting svg filesize of 320kb with your code. If I remove the separate passes and draw staff-lines as if they were like any other element fixing both bugs (since then indiscriminately the staff lines are all drawn measure by measure unfortunately, but the z-order is among that sort function mentioned at the top of this post), the file size goes up to 380kb, so there's definitely file-size reductions that would be nice to keep some how. A larger file from a symphony resulted in a file of 1.0mb that with this change became 1.1mb, so that's a good 100kbs potentially saved for a large score (per page though, as svg files are per page). I think by making the mentioned changes there are still some file-size efficiencies retained that you did regarding the rest of the code compared to how it was back in 2.0 days.

Out of curiosity, do you think it would be non-acceptable to have this difference in file size for your purposes of SVG use, or should further attempts be made to try to get per-system line drawing? I admit that I don't care too much about 100kb when compared with consistency and don't think of the difference as bloat since I don't store SVG files in bulk but only rather to potentially do some external edits and then afterwards more than likely convert results to PDF again, resulting in smaller files. Consistency is more important to me.

Excuse the length of this post :)

In reply to by worldwideweary

And what about 30 page scores for full orchestra? What about my horizontally scrolling scores that are one big system? What about people that want to edit their scores in Inkscape or a text editor (see previous comments in this issue)?

I had trouble fully understanding your post, but I have a basic sense of how to handle all this that I can summarize very briefly:
1) Change the if condition to include staff type changes as well as invisible staff lines
2) Do an initial scan for any elements that have a lower z-index than the staff lines (you can sort the elements in a page by z-order easily enough, AFAIK). Then "print" those to the SVG file prior to handling the staff lines. Then exclude them from later "printing".

Simple, right? I will take a look at the code in question sometime in the next few days and see if I can either make the change myself quickly enough, and if I can't I'll provide you with some detailed instructions. Can you give me a day or two to investigate this more thoroughly?

In reply to by sideways

Sure, there's no rush of course :) I'm just a contributor from time to time.

Update:
I had some time (as usual) today and I've got a Pull Request, but I would really appreciate it if you did a code review and maybe test a build (build artifacts come automatically at github now for all three platforms) and let me know your judgments about anything. I think your method you mentioned makes perfect sense, but I went a different route in doing one whole pass but still keeping practically all of your code after you expressed how important it is to keep the per system-line drawing.

You can leave comments at the other issue since it's probably more appropriate over there dealing with this specific situation: https://musescore.org/en/node/317084
and the github PR is https://github.com/musescore/MuseScore/pull/7443.

Thanks again for getting back to me on here. I originally thought this was a problem over at the .COM side and out of github's hands :-/ since that was where I first saw the situation of images not showing correctly by hiding staves.

I'll build your PR on my Windows 10 machine and check it out. If your single-pass code works right, then kudos for consolidating stuff. I can't promise an exact date, but I should be able to get back to you within the week, if not sooner.