5

I am trying to make a spherical image of a planet with a night side in shadow, to be rendered in browsers for a simulation web page, but I have been fighting a strange visual problem that I cannot seem to figure out.

Here is an image snip of the SVG as it is displayed in Chrome (this happens in IE also) and then zoomed up 3x:

SVG Planet Terminator

You will notice that just above the day/night terminator, there is a faint darker horizontal band, about 1/3rd as wide as its distance above the terminator. That should not be there and I cannot figure why it is there or how to get rid of it.

Here is the SVG code in an HTML Snippet for easy viewing:

<div style="position:absolute; z-index:1; margin:15px; 
            width:640px; height:640px; 
            background-color:black">

  <svg id="svgEa" style="width:100%; height:100%;" viewBox="-5000 -5000 10000 10000" preserveAspectRatio="xMidYMid meet" clip-path="url(#svgEaClip)" transform="scale(1.0,1.0)" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                <!-- NOTE: All internal units are in KM (or %)  -->
    
                <defs id="defsEa">
                    <clipPath id="svgEaClip">
                        <rect width="100%" height="100%" />
                    </clipPath>
                    <linearGradient id="lgdEaSphere">
                        <stop style="stop-color:#ffffff;stop-opacity:1.00;" offset="0.00" id="stopEarthCenter" />
                        <stop style="stop-color:#dfffef;stop-opacity:1.00" offset="0.30" id="stopEarthInner" />
                        <stop style="stop-color:#91ffc8;stop-opacity:1.00" offset="0.31" id="stopEarthMid" />
                        <stop style="stop-color:#00A064;stop-opacity:1.00" offset="0.95264" id="stopEarthOuter" />
                        <stop style="stop-color:#44ffff;stop-opacity:0.66" offset="0.95264" id="stopAirInner" />
                        <stop style="stop-color:#44ffff;stop-opacity:0.10" offset="1.00" id="stopAirOuter"  />
                    </linearGradient>
                    <radialGradient id="rgdEaSphere"  xlink:href="#lgdEaSphere"
                        gradientUnits="userSpaceOnUse"
                        cx="0" cy="0"
                        fx="0" fy="0"
                        r="3339.05" 
                        />
    
                    <linearGradient id="lgdEaNightSide" 
                        x1="0%" y1="0%" x2="0%" y2="100%"
                        spreadMethod="pad" >
                        <stop style="stop-color:#000000;stop-opacity:0.7;" offset="0.00" id="stopEaMidnight" />
                        <!-- this stop seems to cause the artifact -->
                        <stop style="stop-color:#000000;stop-opacity:0.6;" offset="0.99" id="stopEaDusk" /> 
                        <stop style="stop-color:#000000;stop-opacity:0.5;" offset="1.00" id="stopEaTerminator" />
                    </linearGradient>
                </defs>
    
                <g id="gEaAll" transform="scale(1.2,1.2)" >
                    <g id="gEaSunFacing" >
                        <!-- contains everything that stays oriented with the Sun -->
                        <circle
                            id="cEarthAir"
                            cx="0" cy="0" r="3339.05"
                            style="display:inline;fill-opacity:1;fill:url(#rgdEaSphere)" />
                        <!--  overlay to give Earth a night side.  -->
                        <path id="pNight" 
                            style="stroke:none; fill:url(#lgdEaNightSide)"
                            d="M -3189.05,0 
                                A 3189.05,15945.25 0 1,1 3189.05,0
                                Z" 
                            />
                    </g>
                </g>
    
            </svg>

</div>

(If you want you can save this to an HTML (.html) file (add the html and body tags) and then run it in your browser to see it. Or save it to an SVG file (.svg), and remove the div and br tags, if you want to use any SVG tools on it.

This artifact is occurring right at where one of the linear gradient stops are, specifically #stopEaDusk at line 33, in the linear gradient #lgdEaNightSide, which is being used as the fill for the overlay path #pNight. This artifact may seem very faint, but it is real and more pronounced when not zoomed out by 300%. My actual SVG also has a complimentary #pDay overlay that I removed to simplify the code. But when it is added, I get three of these darkness-band artifacts, the one above, one at the corresponding spot on the other side (where I have another dawn/dusk stop) and one right at the midpoint where the two overlays meet. When they are all three visible, it looks horrible, so I cannot just ignore it.

Other points:

  • As far as I can tell, this is not just a visual illusion as suggested here, as it persists even when blown up. And if it is, I still need to fix it.
  • I do not believe that it is this old Chrome bug as suggested in this answer, because it appears differently, but mostly because I see it in IE also (though it might be fainter).

tl,dr: What is causing this darkness band and how can I get rid of it?


There seems to be some confusion about what the question is here, so allow me to clarify. The linear gradient lgdEaNightSide was written for an explicit effect, which it seems to be doing, but it is also producing an undesirable side-effect which is not intended, and to the best of my understanding of SVg should NOT be happening.

The desired effect is that the pNight overlay adds: 1. 70% darkness, at the top of its arc (which is very high and off-screen), shading smoothly to 60% darkness 99% of the way down. 2. Then shading quickly from 60% darkness 99% of the way down, to 50% darkness 100% of the way down. This is to provide a dusk-like transition area.

However, in addition to that, it is also adding the undesired effect: - at about 98.8% of the way down, it gets suddenly darker - then at about 99.2% of the way down, it gets suddenly lighter again.

This should not be happening, and as far as I know, the SVG I wrote should not be doing this.

What I am looking for is how to keep the first (desired) effect, while getting rid of the second (undesired) effect.

I do not need to be told that the second stop is causing the problem. I know that, I stated it above and I pointed it out in the code comments. I do not need an answer to tell me what I already know. What I am looking for is:

  1. how to fix it while retaining the visual effect that I originally wrote it for, and

  2. Why is it happening, when AFAIK, it should not be happening.

johannchopin
  • 13,720
  • 10
  • 55
  • 101
RBarryYoung
  • 55,398
  • 14
  • 96
  • 137

2 Answers2

3

There is nothing wrong with the code.

The banding exists because monitors do not have enough colors to represent the subtle change. You are trying to change 10% opacity over a large area which is not possible with most monitors.

You can determine this is a monitor issue by switching screens. Depending on the quality and the pixel density of the monitor, you will see slightly different results. Some browsers use dithering, which reduces the color banding with the tradeoff of pixelation.

The only way to work around the problem is to introduce more color variation like Prince was getting at.

An answer from another post does a great job of explaining this concept. It's worth a read, but I will quote the solutions to save people a click. https://stackoverflow.com/a/11274627/6794536

If you want to make it less obvious with your current 24-bit color monitor, you can:

  • Change your design to introduce a subtle color shift into your gradient (e.g. from dark blue to gray-green). This causes different RGB channels bits can transition at different times, breaking up your bands into a smaller differentiated colors.

  • Change your design to increase your dynamic range (e.g. from pure white to pure black) so that you have more color bars to work with.

  • Change your design to reduce the overall distance that the gradient occurs over, reducing the widths of the bands.

…or some combination of the above.

I implemented this strategy, but it is not perfect. If you really want to get rid of color banding, you will have to make other tradeoffs. For example, you may not be able to use pure black.

<div style="position:absolute; z-index:1; margin:15px; 
            width:640px; height:640px; 
            background-color:#0c0c26">

  <svg id="svgEa" style="width:100%; height:100%;" viewBox="-5000 -5000 10000 10000" preserveAspectRatio="xMidYMid meet" clip-path="url(#svgEaClip)" transform="scale(1.0,1.0)" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
                <!-- NOTE: All internal units are in KM (or %)  -->
    
                <defs id="defsEa">
                    <clipPath id="svgEaClip">
                        <rect width="100%" height="100%" />
                    </clipPath>
                    <linearGradient id="lgdEaSphere">
                        <stop style="stop-color:#ffffff;stop-opacity:1.00;" offset="0.00" id="stopEarthCenter" />
                        <stop style="stop-color:#dfffef;stop-opacity:1.00" offset="0.30" id="stopEarthInner" />
                        <stop style="stop-color:#91ffc8;stop-opacity:1.00" offset="0.31" id="stopEarthMid" />
                        <stop style="stop-color:#00A064;stop-opacity:1.00" offset="0.95264" id="stopEarthOuter" />
                        <stop style="stop-color:#44ffff;stop-opacity:0.66" offset="0.95264" id="stopAirInner" />
                        <stop style="stop-color:#44ffff;stop-opacity:0.10" offset="1.00" id="stopAirOuter"  />
                    </linearGradient>
                    <radialGradient id="rgdEaSphere"  xlink:href="#lgdEaSphere"
                        gradientUnits="userSpaceOnUse"
                        cx="0" cy="0"
                        fx="0" fy="0"
                        r="3339.05" 
                        />
    
                    <linearGradient id="lgdEaNightSide" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad">
                        <stop style="stop-color:#00033e;stop-opacity:0.7;" offset="0.00" id="stopEaMidnight"></stop>
                        <!-- this stop seems to cause the artifact -->
                        <stop style="stop-color:#090d24;stop-opacity:0.6;" offset="0.99" id="stopEaDusk"></stop> 
                        <stop style="stop-color:#000;stop-opacity:0.5;" offset="1.00" id="stopEaTerminator"></stop>
                    </linearGradient>
                </defs>
    
                <g id="gEaAll" transform="scale(1.2,1.2)" >
                    <g id="gEaSunFacing" >
                        <!-- contains everything that stays oriented with the Sun -->
                        <circle
                            id="cEarthAir"
                            cx="0" cy="0" r="3339.05"
                            style="display:inline;fill-opacity:1;fill:url(#rgdEaSphere)" />
                        <!--  overlay to give Earth a night side.  -->
                        <path id="pNight" 
                            style="stroke:none; fill:url(#lgdEaNightSide)"
                            d="M -3189.05,0 
                                A 3189.05,15945.25 0 1,1 3189.05,0
                                Z" 
                            />
                    </g>
                </g>
    
            </svg>

</div>
wizardzeb
  • 1,546
  • 2
  • 14
  • 30
0

It is because you are using an extra stop value in the gradient of overlay. As you are using linear gradient(lgdEaNightSide) in overlay to give earth a night side as below.

You can see you are using two offset values, one at 0.99 and other at 1. This is the reason you are getting faint darker horizon band.

<linearGradient id="lgdEaNightSide" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad">
  <stop style="stop-color:#000000;stop-opacity:0.7;" offset="0.00" id="stopEaMidnight" />
  <!-- this stop seems to cause the artifact -->
  <stop style="stop-color:#000000;stop-opacity:0.6;" offset="0.99" id="stopEaDusk" />
  <stop style="stop-color:#000000;stop-opacity:0.5;" offset="1.00" id="stopEaTerminator" />
</linearGradient>

Just delete an extra stop value and it will work, please find working code below:

<div style="position:absolute; z-index:1; margin:15px; 
    width:640px; height:640px; 
    background-color:black">

  <svg id="svgEa" style="width:100%; height:100%;" viewBox="-5000 -5000 10000 10000" preserveAspectRatio="xMidYMid meet" clip-path="url(#svgEaClip)" transform="scale(1.0,1.0)" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <!-- NOTE: All internal units are in KM (or %)  -->

      <defs id="defsEa">
        <clipPath id="svgEaClip">
          <rect width="100%" height="100%" />
        </clipPath>
        <linearGradient id="lgdEaSphere">
          <stop style="stop-color:#ffffff;stop-opacity:1.00;" offset="0.00"
                id="stopEarthCenter" />
          <stop style="stop-color:#dfffef;stop-opacity:1.00" offset="0.30"
                id="stopEarthInner" />
          <stop style="stop-color:#91ffc8;stop-opacity:1.00" offset="0.31"
                id="stopEarthMid" />
          <stop style="stop-color:#00A064;stop-opacity:1.00" offset="0.95264"
                id="stopEarthOuter" />
          <stop style="stop-color:#44ffff;stop-opacity:0.66" offset="0.95264"
                id="stopAirInner" />
          <stop style="stop-color:#44ffff;stop-opacity:0.10" offset="1.00"
                id="stopAirOuter" />
        </linearGradient>
        <radialGradient id="rgdEaSphere" xlink:href="#lgdEaSphere"
                        gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0"
                        fy="0" r="3339.05" />

        <linearGradient id="lgdEaNightSide" x1="0%" y1="0%" x2="0%" y2="100%"
                        spreadMethod="pad">
          <stop style="stop-color:#000000;stop-opacity:0.7;" offset="0.00"
                id="stopEaMidnight" />
          <!-- this stop seems to cause the artifact -->
          <stop style="stop-color:#000000;stop-opacity:0.5;" offset="1.00"
                id="stopEaTerminator" />
        </linearGradient>
      </defs>

      <g id="gEaAll" transform="scale(1.2,1.2)">
        <g id="gEaSunFacing">
          <!-- contains everything that stays oriented with the Sun -->
          <circle id="cEarthAir" cx="0" cy="0" r="3339.05"
                  style="display:inline;fill-opacity:1;fill:url(#rgdEaSphere)" />
          <!--  overlay to give Earth a night side.  -->
          <path id="pNight" style="stroke:none; fill:url(#lgdEaNightSide)" d="M -3189.05,0 
                        A 3189.05,15945.25 0 1,1 3189.05,0
                        Z" />
        </g>
      </g>

    </svg>

</div>
Jasdeep Singh
  • 7,901
  • 1
  • 11
  • 28
  • That stop is there because it is supposed to be, it is changing to a faster gradient from dark to light. It needs to be there, and as I understand it, it should *not* be causing this dark band. That stop should mark where it start to *lighten* faster. It should not make anything darker, and it definitely causing it to darken for a short while, and then return to a lighter shade. – RBarryYoung Apr 20 '20 at 17:00
  • If you think that there is something wrong with that stop, then please explain what it is. But that stop is intentional and is there for an effect that I want. The problem is that it is somehow also causing an effect that I do not want (the darkness band), which does not seem to conform to the SVG standards, doc or functionality as I understand it. My understanding of SVG is that this should not happen. If there is something wrong with SVG or my understanding, that is fine, but it needs to be explained, or at least, how to fix it and still keep the effect that I want. – RBarryYoung Apr 20 '20 at 17:05
  • 1
    As we know creates a gradient, shift in color. The extra tag creates another gradient (shift of color, gradual change). This is the reason, extra stop is producing an extra change in color.It is definitely resolving your issue, let me think of creating a demo to show you. The thing I am not able to understand is Why is it intentional and what is it doing? – Jasdeep Singh Apr 20 '20 at 17:13
  • Please explain why that stop produces that effect. I know how stops work, and I know what that stop is supposed to do and it is not supposed to do this. I want the last 1% of the shape to get lighter from 60% opacity to 50% opacity. That's what that stop is supposed to do. The way that it is written, it should do that. There's nothing in that stop that should cause it to get suddenly darker at 98.8%, then suddenly lighter again at 99.2% of the height, as I understand it.. – RBarryYoung Apr 20 '20 at 17:20
  • 1
    The stop produces that effect because the offset values are so close to each other(0.99 to 1) that it does not give a gradient effect. If you change the offset value of second to something else like 9, you can see the difference. I have also created a jsfiddle here, you can check by manipulating the offset values - https://jsfiddle.net/szwnfxky/2/ – Jasdeep Singh Apr 20 '20 at 17:52
  • Didn't my comment answer your question? – Jasdeep Singh Apr 20 '20 at 17:57
  • Not at all. I know that the second stop is causing the problem, I stated that in my original question, and its even pointed-out in the comments of my code. The question is *why* is it getting darker, when the SVG code is supposed to be making it lighter, and how can I get rid of this problem and still get the effect I want? Plus, your example exhibits the sames problems as my SVG, possibly even worse. – RBarryYoung Apr 20 '20 at 18:03
  • I will end this discussion by the final comment. You are adding an extra stop which will add gradient and offset values are so close that you can see a fine line. Seems like you don't want to understand this thing. – Jasdeep Singh Apr 20 '20 at 18:15