I have a task to replace all RBG instances in a string to the nearest monochrome values.
The complexity is slowing down the process and I'd like to know if there's a faster way to replace all the instances in the string.
As you can see bellow, for rounding up to white 255 I just deleted the paths to save time. But it's just a quick hack and not a solution to my problem.
The string is an SVG style text with a lot of data:
// Completely delete white paths:
//svg = svg.replace(/<path[^>]*?fill="rgb\(255[^>]*?\/>/g, '')
svg = svg.replace(/<path[^>]*?fill="rgb\(254[^>]*?\/>/g, '')
svg = svg.replace(/<path[^>]*?fill="rgb\(253[^>]*?\/>/g, '')
...
// Replace almost black with full black
svg = svg.replace(new RegExp('7,7,7', 'g'), '0,0,0')
svg = svg.replace(new RegExp('6,6,7', 'g'), '0,0,0')
svg = svg.replace(new RegExp('6,6,6', 'g'), '0,0,0')
...
As you can see from a part of the code, sometimes not all of the three RGB parameters are the same so it's really slowing down the code and I'm stressing out.
There's a treshold if any of the three RBG parameters is bellow 200, it will be all black 0, otherwise it should be white with all values at 255.
I have only limited experience with regex so this is about as much as I could do.
Any improvements would be really helpful.
Short answer
This is not really the way to create monochrome, and will usually not be the "nearest" monochrome either. Though I am not clear on what your actual goals are, so let's discuss.
Longer Answer
The R G and B channels do not contribute equally to the total luminance that makes a monochrome version of an image. And also, yes RGB values are not linear in nature. I see that your breakpoint is at value 200, I'm curious how you arrived at that and what the purpose is of splitting something into either black or white.
If you had monochrome values to start with, the middle contrast point is closer to 170. The middle grey breakpoint in terms of surface colors is usually considered to be 18% which works out to 118 in sRGB, due to the nonlinear encoding of the sRGB TRC.
If you want an accurate grayscale, then the procedure would be to first linearize and then apply coefficient to each of the channels and sum them to create a luminance Y.
The down and dirty simple version of the JS code to determine that value given a tuple of sRGB numerical values, finding the mid-point to flip, and related topics I discuss in this answer: https://stackoverflow.com/a/69869976/10315269
But here's a code snippet:
let Ys = Math.pow(sR/255.0,2.2) * 0.2126 +
Math.pow(sG/255.0,2.2) * 0.7152 +
Math.pow(sB/255.0,2.2) * 0.0722; // Andy's Easy Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4
This gives you the weighted light value from the monitor, which can then be used to determine a break point.
If we put this together with some regex to help parse values, then:
let container = document.getElementById('BWBlock');
function setSVGtoBW (myOriginalSVG = "<null SVG string>") {
let rex = /#([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/ig;
// regex to parse the hex string
// We are explicit about 0-9a-f instead of \w, because
// We no not want to replace ID calls like <use '#newLines'>
function findYflip (match, p1, p2, p3, offset, string) {
let Ys = Math.pow(parseInt(p1,16)/255.0,2.2) * 0.2126 +
Math.pow(parseInt(p2,16)/255.0,2.2) * 0.7152 +
Math.pow(parseInt(p3,16)/255.0,2.2) * 0.0722; // Andy's Easy Luminance Estimate for sRGB. For Rec709 HDTV change the 2.2 to 2.4, and use different coefficients for P3 etc!
return (Ys > 0.4) ? "#ffffff" : "#000000"; // luminance based color flipper.
};
return myOriginalSVG.replace(rex, findYflip)
};
// An svg for demo purposes
let placeholder = "<?xml version='1.1' encoding='UTF-8'?> <!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'> <svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 494.6 525.8'> <title>CIE 1931 xyY Gamut Comparison</title> <defs> <path id='locus' d='M 150,473 C 147,473 145,471 142,469 C 135,462 129,455 124,446 C 121,441 118,436 116,431 C 113,424 110,416 107,408 C 103,396 99,385 95,373 C 86,339 77,298 72,264 C 66,226 61,179 62,141 C 63,118 65,81 80,60 C 84,54 91,50 98,49 C 105,48 112,51 118,53 C 140,60 160,76 178,90 C 236,135 287,191 339,243 C 360,264 380,284 401,305 C 409,313 417,321 426,329 C 428,332 430,334 433,337 C 434,337 434,338 435,339 C 435,339 436,340 436,340'/> <path id='gamutStot' d='M 76.875,-30.750 L 153.75,-307.5 L 328,-169.125 Z'/> <g id='lines'> <use xlink:href='#gamutStot' stroke='#3377aa' fill='#eeccff' stroke-width='6' /> </g> <g id='labels'> <path d='M 520,-865 L 820,-865 L 820,-790 L 520,-790 Z' stroke='#eedd22' stroke-width='4' stroke-linejoin='round' fill='#3300aa'/> <path d='M 520,-865 L 820,-865 L 820,-790 L 520,-790 Z' stroke='#ffaa11' stroke-width='4' stroke-linejoin='round' fill='#6622ff' transform='translate(0,100)' /> <path d='M 520,-865 L 820,-865 L 820,-790 L 520,-790 Z' stroke='#6622ff' stroke-width='4' stroke-linejoin='round' fill='#ffaa11' transform='translate(0,200)' /> <path d='M 520,-865 L 820,-865 L 820,-790 L 520,-790 Z' stroke='#3300aa' stroke-width='4' stroke-linejoin='round' fill='#eedd22' transform='translate(0,300)' /> <text x='540' y='-10' text-anchor='start' fill='#3300aa' font-size='15'>Copyright © 2021 Myndex Research</text> </g> </defs> <use stroke='#234567' stroke-width='2' fill='#ccddee' xlink:href='#locus'/> <path stroke='#6622cc' stroke-width='2.75' stroke-linecap='square' fill='none' d='M 60,15 v 461 h 410 M 60,476 v 4 M 86,476 v 4 M 111,476 v 4 M 137,476 v 4 M 162,476 v 4 M 188,476 v 4 M 214,476 v 4 M 239,476 v 4 M 265,476 v 4 M 290,476 v 4 M 316,476 v 4 M 342,476 v 4 M 367,476 v 4 M 393,476 v 4 M 418,476 v 4 M 444,476 v 4 M 470,476 v 4 M 60,476 h -4 M 60,450 h -4 M 60,425 h -4 M 60,399 h -4 M 60,373 h -4 M 60,348 h -4 M 60,322 h -4 M 60,297 h -4 M 60,271 h -4 M 60,245 h -4 M 60,220 h -4 M 60,194 h -4 M 60,169 h -4 M 60,143 h -4 M 60,117 h -4 M 60,92 h -4 M 60,66 h -4 M 60,41 h -4 M 60,15 h -4'/> <path opacity='0.5' stroke='#88aaff' stroke-dasharray='4,4' stroke-width='3' fill='none' d='M 85.5,475.5 v -460.8 M 111.5,475.5 v -460.8 M 136.5,475.5 v -435.2 M 162.5,475.5 v -409.6 M 188.5,475.5 v -384 M 213.5,475.5 v -358.4 M 239.5,475.5 v -332.8 M 264.5,475.5 v -307.2 M 290.5,475.5 v -281.6 M 316.5,475.5 v -256 M 341.5,475.5 v -230.4 M 367.5,475.5 v -204.8 M 392.5,475.5 v -179.2 M 418.5,475.5 v -153.6 M 444.5,475.5 v -128 M 469.5,475.5 v -102.4 M 60.5,450.5 h 409.6 M 60.5,424.5 h 409.6 M 60.5,399.5 h 409.6 M 60.5,373.5 h 409.6 M 60.5,347.5 h 384 M 60.5,322.5 h 358.4 M 60.5,296.5 h 332.8 M 60.5,271.5 h 307.2 M 60.5,245.5 h 281.6 M 60.5,219.5 h 256 M 60.5,194.5 h 230.4 M 60.5,168.5 h 204.8 M 60.5,143.5 h 179.2 M 60.5,117.5 h 153.6 M 60.5,91.5 h 128 M 60.5,66.5 h 102.4 M 60.5,40.5 h 76.8 M 60.5,15.5 h 51.2 M 111.5,15.5 L 469.5,373.5'/> <g transform='translate(60,476)' stroke-width='4' stroke-linejoin='round' stroke='#FF4411' fill='#FF4411'> <use xlink:href='#lines'/> </g> <g transform='translate(60,476) scale(0.5125)' > <use xlink:href='#labels' stroke='none'/> </g> <g font-family='Times, serif' font-size='10'> <g fill='#6622cc'> <g text-anchor='middle'> <text x='240' y='505' font-size='30' font-style='italic'>x</text> </g> <g text-anchor='end'> <text x='40' y='251.4' font-size='30' font-style='italic'>y</text> </g> </g> </g> </svg> ";
let newBWsvg = setSVGtoBW(placeholder);
container.innerHTML = newBWsvg;
CODEPEN
I set this up at CodePen here: https://codepen.io/myndex/pen/rNzZWWB
Notice the regex:
let rex = /#([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/ig;
We are explicit about 0-9a-f instead of \w, because we no not want to replace ID calls references like <use '#newLines'> which could become <use '#000000es'> if we used (\w\w).
Greyscale
Also, you could now make this a greyscale converter, instead of black and white, by replacing the luminance color flipper return (Ys > 0.4) ? "#ffffff" : "#000000"; with instead a Y to sRGB conversion:
let sRGBgr = ((Math.pow(Ys,1/2.2)*255)&0xff).toString(16).padStart(2,'0');
return "#" + sRGBgr + sRGBgr + sRGBgr
This converts the Y back to an sRGB value, and concatenates it to an sRGB hex. The codepen for this greyscale version is https://codepen.io/myndex/pen/mdMGmVw
Can't seem to figure out how this "T-junction" would draw to left and right at same time instead of first to left, then to right.
What I've been trying to do;
<path class="path" fill="none" stroke="white" stroke-width="6" id="triangle" d="M 450,50 L 450,200 L350,200 550,200" />
JS-fiddle: https://fiddle.jshell.net/ewf9soax/
Thanks in advance
All you need to do is a slight tweak to your path definition.
d="M 450,50 L 450,200 L350,200
M 450,50 L 450,200 L550,200"
As Robert suggested, we split the path into two L-shaped subpaths. One going to the left, and one to the right. And since dash patterns apply to individual subpaths, and not the path as a whole, it'll automatically work.
You may want to update your stroke-dasharray length to compensate for the fact that the subpaths are now shorter than the original path.
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("triangle");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
body {
height: 2000px;
background: #f1f1f1;
}
#mySVG {
position: fixed;
top: 0:;
width: 900px;
height: 810px;
margin-left:-450px;background-color:green;left: 50%;z-index: 100000;
}
<svg class ="path" id="mySVG">
<path class="path" fill="none" stroke="white" stroke-width="6" id="triangle" d="M 450,50 L 450,200 L350,200
M 450,50 L 450,200 L550,200" />
Sorry, your browser does not support inline SVG.
</svg>
Updated fiddle
I'm currently trying to generate a SVG path representing a sine wave that fit the width of the webpage.
The algorithm I'm currently using is drawing small line to between two point which is drawing the sine wave.
The algorithm :
for(var i = 0; i < options.w; i++) {
var operator = ' M ';
d += operator + ((i - 1) * rarity + origin.x) + ', ';
d += (Math.sin(freq * (i - 1 + phase)) * amplitude + origin.y);
if(operator !== ' L ') { operator = ' L '; }
d += ' L ' + (i * rarity + origin.x) + ', ';
d += (Math.sin(freq * (i + phase)) * amplitude + origin.y);
}
Which generates a path for the svg :
M 9, 82.66854866662797 L 10, 102.5192336707523
M 10, 102.5192336707523 L 11, 121.18508371540987
M 11, 121.18508371540987 L 12, 129.88725786264592
M 12, 129.88725786264592 L 13, 124.53298763579338
M 13, 124.53298763579338 L 14, 107.64046998532105
M 14, 107.64046998532105 L 15, 87.15451991511547
M 15, 87.15451991511547 L 16, 72.70999984499424
M 16, 72.70999984499424 L 17, 71.10039326578718
M 17, 71.10039326578718 L 18, 83.08272330249196
M 18, 83.08272330249196 L 19, 103.02151290977501
The thing is, at the end of the sinus I wanted to draw a line to close the rest of the path (with the Z)
Sorry for my drawing skills ! :D
The reason for closing the path and having a path linked is to be able to fill this path with a background or a gradient
I found that I could represent the sine waves in a single path where it's linked
M0 50 C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50 Z
Which looks like this :
But the thing is the algorithm I'm using lets me play with the sine function so that I could animate this waves (which is something I need) and I dont see how to animate the representation of the sine waves.
So to sum up, either you can help me find a way to link all the lines drawed by the actual algorithm ? or a way to animate the other representation to draw a waves without caring about the sinus.
Thanks in advance for your help !
You can animate the sine wave by just making the path the width of two wavelengths and then moving it left or right.
<svg width="200" height="100" viewBox="0 0 200 100">
<defs>
<path id="double-wave"
d="M0 50
C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50
C 240 10, 260 10, 300 50 C 340 90, 360 90, 400 50
L 400 100 L 0 100 Z" />
</defs>
<use xlink:href="#double-wave" x="0" y="0">
<animate attributeName="x" from="0" to="-200" dur="3s"
repeatCount="indefinite"/>
</use>
</svg>
I'm animating the x attribute of a <use> here because IMO it is more obvious what is going on.
What we are doing is animating the position that our two-wavelength path is rendered. Once it has moved one wavelength of distance, it jumps back to it's original position and repeats. The effect is seamless because the two waveshapes are identical. And the rest of the wave is off the edge of the SVG.
If you want to see what's going on behaind the scenes, we can make the SVG wider so you can see what's going on off to the left and right of the original SVG.
<svg width="400" height="100" viewBox="-200 0 600 100">
<defs>
<path id="double-wave"
d="M0 50
C 40 10, 60 10, 100 50 C 140 90, 160 90, 200 50
C 240 10, 260 10, 300 50 C 340 90, 360 90, 400 50
L 400 100 L 0 100 Z" />
</defs>
<use xlink:href="#double-wave" x="0" y="0">
<animate attributeName="x" from="0" to="-200" dur="3s"
repeatCount="indefinite"/>
</use>
<rect width="200" height="100" fill="none" stroke="red" stroke-width="2"/>
</svg>
Below is an example of sine wave across the width of the svg. It creates a polyline via a parametric equation. Animation can be had by adjusting the amplitude and/or phase angle.
Edit - added animation to phase angle.
<!DOCTYPE HTML>
<html>
<head>
<title>Sine Wave</title>
</head>
<body onload=amplitudeSelected() >
<div style=background:gainsboro;width:400px;height:400px;>
<svg id="mySVG" width="400" height="400">
<polyline id="sineWave" stroke="black" stroke-width="3" fill="blue" ></polyline>
</svg>
</div>
Amplitide:<select id="amplitudeSelect" onChange=amplitudeSelected() >
<option selected>10</option>
<option>20</option>
<option>30</option>
<option>40</option>
<option>50</option>
<option>60</option>
<option>70</option>
<option>80</option>
<option>90</option>
<option>100</option>
</select>
<button onClick=animatePhaseAngle();this.disabled=true >Animate Phase Angle</button>
<script>
//---onload & select---
function amplitudeSelected()
{
var startPoint=[0,400]
var endPoint=[400,400]
var originX=0
var originY=200
var width=400
var amplitude=+amplitudeSelect.options[amplitudeSelect.selectedIndex].text
var pointSpacing=1
var angularFrequency=.02
var phaseAngle=0
var origin = { //origin of axes
x: originX,
y: originY
}
var points=[]
points.push(startPoint)
var x,y
for (var i = 0; i < width/pointSpacing; i++)
{
x= i * pointSpacing + origin.x
y= Math.sin(angularFrequency*(i + phaseAngle)) * amplitude + origin.y
points.push([x,y])
}
points.push(endPoint)
sineWave.setAttribute("points",points.join(" "))
}
//---buton---
function animatePhaseAngle()
{
setInterval(animate,20)
var seg=.5
var cntr=0
var cntrAmp=0
var startPoint=[0,400]
var endPoint=[400,400]
var originX=0
var originY=200
var origin = { //origin of axes
x: originX,
y: originY
}
var width=400
var pointSpacing=1
var angularFrequency=.02
setInterval(animate,10)
function animate()
{
phaseAngle=seg*cntr++
var amplitude=+amplitudeSelect.options[amplitudeSelect.selectedIndex].text
var points=[]
points.push(startPoint)
var x,y
for (var i = 0; i < width/pointSpacing; i++)
{
x= i * pointSpacing + origin.x
y= Math.sin(angularFrequency*(i + phaseAngle)) * amplitude + origin.y
points.push([x,y])
}
points.push(endPoint)
sineWave.setAttribute("points",points.join(" "))
}
}
</script>
</body>
</html>
I am have taken the exact code from ThreeJs Example which is for generating this City Model.
I have a generated a city boundaries SVG path from Google maps and would like to generate same kind of 3D object using above code. My SVG path code is below
"M -378463.90230276587 -216828.5204525995 L -378463.90230276587 -216828.5204525995 L -378468.4733200769 -216800.78018946826 L -378457.36255405867 -216726.32967956597 L -378453.2523036701 -216708.13985810167 L -378446.23084408935 -216645.84207578097 L -378446.40886767313 -216640.48042431887 L -378440.04714254953 -216593.4246023558 L -378428.4180137435 -216539.991347306 L -378408.5945640994 -216510.42896043573 L -378385.51956605876 -216467.31060126523 L -378379.14213297196 -216451.9325052259 L -378359.4495830217 -216424.30743382534 L -378348.87288775464 -216401.72985462152 L -378333.6361633847 -216381.90640497737 L -378337.86684149154 -216373.22513727797 L -378341.7990682963 -216369.96835289372 L -378342.2388912678 -216365.88428244408 L -378331.6883759395 -216327.93384318874 L -378324.9287157465 -216311.89601269213 L -378325.3318868038 -216300.2721198738 L -378321.78712309286 -216297.53893426526 L -378317.28940961056 -216296.21946535073 L -378303.3616821797 -216270.99771233014 L -378293.1933939575 -216243.98001550927 L -378293.88454434136 -216242.82286221522 L -378290.4863882877 -216243.61349636634 L -378276.29162548116 -216233.02632912374 L -378253.8973058489 -216247.0954282241 L -378224.3558629296 -216248.45678504065 L -378204.8989324284 -216228.3558280454 L -378170.660808492 -216241.304425766 L -378170.3675931776 -216235.0578923731 L -378170.22622150823 -216232.0943233032 L -378169.26279976114 -216211.52212740996 L -378152.6542465991 -216208.78370581358 L -378105.9858877301 -216201.0815678245 L -378090.63397162955 -216173.3308327178 L -378065.06664341706 -216168.73363546806 L -378045.64636483014 -216185.9233832709 L -378059.37512472627 -216241.5662251538 L -378059.1971011426 -216244.0742632889 L -378057.3906853668 -216269.52639977072 L -378057.05034616264 -216274.34874449397 L -378054.1391369704 -216315.39365251316 L -378045.7615565607 -216328.4836219031 L -377993.89386184997 -216365.8895184318 L -377991.3072838986 -216367.75353007295 L -377955.1789683823 -216371.94232027777 L -377921.14504796837 -216364.08833864375 L -377920.3229978907 -216362.99925319053 L -377915.61584489804 -216367.39224691782 L -377913.6942373916 -216371.958028241 L -377932.8213006642 -216381.0267590344 L -377936.1670968403 -216393.19519457928 L -377929.9310354229 -216427.5799261728 L -377939.4710051143 -216469.85529131463 L -377948.89578307513 -216477.70927294862 L -377959.25780284416 -216480.447694545 L -377976.6465181818 -216485.03965580696 L -377979.7881108354 -216489.22844601178 L -377976.1229194062 -216505.46000805535 L -377983.93501313817 -216532.14783764756 L -377982.971591391 -216552.4215822387 L -378001.82638330036 -216597.61862854837 L -378000.06709141436 -216607.12194632547 L -378003.0044805454 -216615.18013148196 L -377994.6426080991 -216627.98212154533 L -377998.3287434793 -216637.62681099182 L -378000.4283745695 -216641.8679610742 L -378139.4071975765 -216833.3689772615 L -378144.8997487325 -216840.72030407088 L -378258.3531314292 -216996.0772967787 L -378439.5811396393 -216981.68356643748 L -378444.60768788506 -216944.65466102716 L -378449.5399883511 -216925.05112286875 L -378452.2784099475 -216906.3272306534 L -378452.4773774823 -216884.08475466596 L -378456.9332030626 -216867.26676199373 L -378459.5302529896 -216845.6735484881 L -378462.4571701452 -216837.2540801764 L -378463.90230276587 -216828.5204525995 "
If i replace the SVG path from example to the above mentioned, it doesn't even show up. Being completely new to Both Three.js and SVG i am not sure what i am doing wrong.
So far, my guess is that the SVG coordinates i have in my example are very big and not able to see in viewport. For which i tried to use transform = translate(-378460px, -216828px) method. But no luck. Any suggestions ? Help would be really appreciated.
SVG coordinate system is for (x,y) is x unit to the right and y units down.
Three.js coordinate system for (x,y,z) is x unit to the right, y untis upwards and z units away from you.
You will need to do some sort of transformation either way.
If I am doing a SVG representation for my Three.js for 2D model where I am using the x & z axis as my SVG x,y axis, I will make my SVG mode have a transform="translate(width/w, height/2) scale(1,-1)"
This is to move the (0,0) to the middle of the svg and to make the x,y move in the same direction as the three.js model
You don't see your figure because it's far away from your camera view. I've made some changes in the code of the Threejs example.
1) Added a line which adds a name to a mesh containing an extruded geometry taken from the SVG path. With your SVG path code we will get just a single mesh with the name "mesh00" (in the addGeoObject function, the line is mesh.name = "mesh" + i + j;)
2) When I know the name of the figure, I can find it
var svgObj = scene.getObjectByName("mesh00",true);
3) And center it
svgObj.geometry.center();
Of course, it's up to you how you can make your figure visible for your camera. For example, you can move your camera to the figure or (like I did) bring the figure in front of the camera. The camera is positioned very close in the example, so I moved it further away.
jsfiddle example
PS. I applied OrbitControls to make viewing more comfortable.