Convert SVG path of a glyph to the character the glyph represents - javascript

This working CodePen demo shows a number which is represented as a vector.
May I ask how to convert the number vector (SVG Format) as shown in demo to a real number?
From my search on the web, Tesseract do not read vector graphics (SVG), can anyone show some demo or samples of how to read number vector?
For Example, this vector graphics code below should be read as a number 9 in the console.log
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1066.6667 800"
height="800"
width="1066.6667"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6" /><g
transform="matrix(1.3333333,0,0,-1.3333333,0,800)"
id="g10"><path
id="path20"
style="fill:none;stroke:#000000;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" /></g></svg>

As already commented, you need to convert the svg to a raster image for OCR.
Fortunately we have a lot of options to create a bitmap temporarily using <canvas>.
Apart from the fact, tesseract's OCR might struggle with different shapes, low contrasts – you might not get the desired result, if your svg contains many shapes.
Single Glyph
svg2PngAndOCR("svg");
function svg2PngAndOCR(selector) {
const svgEl = document.querySelector(selector);
let viewBox = svgEl.viewBox.baseVal;
let svgBB = svgEl.getBBox();
let svgW = viewBox.width ? viewBox.width : svgBB.width;
let svgH = viewBox.height ? viewBox.height : svgBB.height;
/**
* convert svg to png via canvas
*/
let blob = new Blob([svgEl.outerHTML], { type: "image/svg+xml" });
let URL = window.URL;
let blobURL = URL.createObjectURL(blob);
let tmpImg = new Image();
tmpImg.src = blobURL;
tmpImg.width = svgW;
tmpImg.height = svgH;
tmpImg.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = svgW;
canvas.height = svgH;
let context = canvas.getContext("2d");
context.drawImage(tmpImg, 0, 0, svgW, svgH);
let pngDataUrl = canvas.toDataURL();
let svgImg = document.createElement("img");
svgImg.width = svgW;
svgImg.height = svgH;
svgImg.class = "svgImg";
svgImg.src = pngDataUrl;
// just additional wrapping for example usage
let imgWrp = document.querySelector('.img-wrp');
if(!imgWrp){
imgWrp = document.createElement("div");
imgWrp.setAttribute("class", "img-wrp img-wrp-vanilla");
imgWrp.appendChild(svgImg);
document.body.appendChild(imgWrp);
}
/**
* OCR: recognize text
* via tesseract
*/
Tesseract.recognize(svgImg, "eng", {
//logger: (m) => console.log(m)
}).then(({ data: { text } }) => {
ocrOutput.textContent = text;
});
};
}
svg,
.img-wrp
{
border:1px solid #ccc;
width:40%;
height:auto;
display:inline-block;
}
img{
max-width:100%;
height:auto;
}
.hidden{
display:none
}
<script src='https://unpkg.com/tesseract.js#4.0.2/dist/tesseract.min.js'></script>
<p>OCR Output: <span id="ocrOutput"></span></p>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066.6667 800" height="800" width="1066.6667" xml:space="preserve" id="svg2" version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,800)" id="g10">
<path id="path20" style="fill:none;stroke:#000000;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" />
</g>
</svg>
Multiple elements
svg2PngAndOCR("svg");
function svg2PngAndOCR(selector) {
const svgEl = document.querySelector(selector);
let viewBox = svgEl.viewBox.baseVal;
let svgBB = svgEl.getBBox();
let svgW = viewBox.width ? viewBox.width : svgBB.width;
let svgH = viewBox.height ? viewBox.height : svgBB.height;
/**
* convert svg to png via canvas
*/
let blob = new Blob([svgEl.outerHTML], { type: "image/svg+xml" });
let URL = window.URL;
let blobURL = URL.createObjectURL(blob);
let tmpImg = new Image();
tmpImg.src = blobURL;
tmpImg.width = svgW;
tmpImg.height = svgH;
tmpImg.onload = () => {
let canvas = document.createElement("canvas");
canvas.width = svgW;
canvas.height = svgH;
let context = canvas.getContext("2d");
context.drawImage(tmpImg, 0, 0, svgW, svgH);
let pngDataUrl = canvas.toDataURL();
let svgImg = document.createElement("img");
svgImg.width = svgW;
svgImg.height = svgH;
svgImg.class = "svgImg";
svgImg.src = pngDataUrl;
// just additional wrapping for example usage
let imgWrp = document.querySelector('.img-wrp');
if(!imgWrp){
imgWrp = document.createElement("div");
imgWrp.setAttribute("class", "img-wrp img-wrp-vanilla");
imgWrp.appendChild(svgImg);
document.body.appendChild(imgWrp);
}
/**
* OCR: recognize text
* via tesseract
*/
Tesseract.recognize(svgImg, "eng", {
//logger: (m) => console.log(m)
}).then(({ data: { text } }) => {
ocrOutput.textContent = text;
});
};
}
svg,
.img-wrp
{
border:1px solid #ccc;
width:40%;
height:auto;
display:inline-block;
}
img{
max-width:100%;
height:auto;
}
.hidden{
display:none
}
<script src='https://unpkg.com/tesseract.js#4.0.2/dist/tesseract.min.js'></script>
<p>OCR Output: <span id="ocrOutput"></span></p>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1066.6667 800" height="800" width="1066.6667" xml:space="preserve" id="svg2" version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,800)" id="g10">
<path id="path20" style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" />
</g>
<path id="hello" transform="scale(1)" class="glyph" d="M4.8 200.5h-0.9v-3.3h-3v3.3h-0.9v-6.9h0.9v2.8h3v-2.8h0.9v6.9zm5.9-2.8q0 0.2 0 0.4l0 0h-3.4q0.1 0.9 0.5 1.3t1 0.4l0 0q0.4 0 0.7-0.1t0.6-0.3l0 0l0.4 0.5q-0.8 0.7-1.8 0.7l0 0q-1.1 0-1.7-0.7q-0.6-0.8-0.6-2l0 0q0-0.8 0.2-1.5q0.3-0.6 0.8-1q0.5-0.3 1.2-0.3l0 0q1 0 1.6 0.7q0.5 0.6 0.5 1.9l0 0zm-0.9-0.2v-0.1q0-0.8-0.3-1.2t-0.9-0.4l0 0q-1.1 0-1.3 1.7l0 0h2.5zm3.5 3.1q-0.6 0-0.9-0.3q-0.2-0.3-0.2-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0 0.3q0.1 0.1 0.3 0.1l0 0q0.2 0 0.3 0l0 0l0.3 0.6q-0.3 0.2-0.7 0.2l0 0zm2.9 0q-0.5 0-0.8-0.3t-0.3-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0.1 0.3q0 0.1 0.2 0.1l0 0q0.2 0 0.4 0l0 0l0.2 0.6q-0.3 0.2-0.7 0.2l0 0zm3.8-5.5q1.1 0 1.8 0.7q0.6 0.7 0.6 2l0 0q0 0.8-0.3 1.5q-0.3 0.6-0.8 0.9q-0.5 0.4-1.3 0.4l0 0q-1.1 0-1.7-0.8q-0.6-0.7-0.6-2l0 0q0-0.8 0.3-1.4q0.2-0.6 0.8-1q0.5-0.3 1.2-0.3l0 0zm0 0.7q-1.3 0-1.3 2l0 0q0 2 1.3 2l0 0q1.4 0 1.4-2l0 0q0-2-1.4-2l0 0zm12.7-2.2h0.9l-1.4 6.9h-1.2l-1.3-5.8l-1.3 5.8h-1.2l-1.4-6.9h1l1.1 6l1.3-6h1l1.4 6l1.1-6zm4.1 1.5q1.1 0 1.7 0.7t0.6 2l0 0q0 0.8-0.2 1.5q-0.3 0.6-0.9 0.9q-0.5 0.4-1.2 0.4l0 0q-1.1 0-1.8-0.8q-0.6-0.7-0.6-2l0 0q0-0.8 0.3-1.4t0.8-1q0.5-0.3 1.3-0.3l0 0zm0 0.7q-1.4 0-1.4 2l0 0q0 2 1.4 2l0 0q1.3 0 1.3-2l0 0q0-2-1.3-2l0 0zm6.1-0.7q0.3 0 0.6 0l0 0l-0.2 0.9q-0.2 0-0.5 0l0 0q-0.5 0-0.8 0.3q-0.3 0.4-0.4 1.1l0 0v3.1h-0.9v-5.3h0.7l0.1 1.1q0.2-0.6 0.6-0.9t0.8-0.3l0 0zm2.7 5.5q-0.5 0-0.8-0.3t-0.3-0.9l0 0v-6.3l0.9-0.1v6.4q0 0.2 0.1 0.3q0 0.1 0.2 0.1l0 0q0.2 0 0.4 0l0 0l0.2 0.6q-0.3 0.2-0.7 0.2l0 0zm5-7.6l0.9 0.1v7.4h-0.8l-0.1-0.8q-0.2 0.4-0.6 0.6q-0.4 0.3-0.9 0.3l0 0q-0.9 0-1.5-0.8q-0.5-0.7-0.5-2l0 0q0-0.8 0.3-1.4q0.2-0.6 0.7-1q0.5-0.3 1.1-0.3l0 0q0.8 0 1.4 0.6l0 0v-2.7zm-1.3 6.8q0.4 0 0.7-0.2q0.3-0.1 0.6-0.5l0 0v-2.6q-0.3-0.4-0.6-0.5q-0.2-0.2-0.6-0.2l0 0q-0.6 0-1 0.5q-0.3 0.5-0.3 1.5l0 0q0 1 0.3 1.5t0.9 0.5l0 0z " />
<use href="#hello" x="200" transform="scale(2)"/>
</svg>

Here's a rough cut but keep in mind that OCR is not always 100% accurate, and the results will be poor if the svg is too abstract.
// Convert to a base64 encoded image
const inputSvg = '<svg><path fill="black" d="m 294.09,426.05 -0.46,-1.38 -0.92,-0.92 -1.38,-0.46 h -0.46 l -1.37,0.46 -0.92,0.92 -0.46,1.38 v 0.46 l 0.46,1.37 0.92,0.92 1.37,0.46 h 0.46 l 1.38,-0.46 0.92,-0.92 0.46,-1.83 v -2.3 l -0.46,-2.29 -0.92,-1.38 -1.38,-0.46 h -0.91 l -1.38,0.46 -0.46,0.92" /></svg>';
const inputBase64Image = Buffer.from(inputSvg).toString('base64');
// Convert the encoded image to a buffer
const imageBuffer = Buffer.from(inputBase64Image, 'base64');
// Convert the buffer to a png
sharp(imageBuffer)
.png()
.toBuffer()
.then(buffer => {
console.log('SVG converted to PNG buffer');
// Configure the recognition process
const config = {
lang: 'eng',
oem: 1,
psm: 3,
};
// Recognise the character
tesseract.recognize(buffer, config)
.then(character => {
console.log("What is 6 + 3?", character);
})
.catch(error => {
console.error(error);
});
})
.catch(error => {
console.error(error);
});

You can use a machine learning or computer vision library such as TensorFlow.js or OpenCV. These libraries allow you to train a model using labelled examples of characters and then use the model to classify new, unseen characters based on their visual appearance.
Here's a high-level overview of the process:
Prepare a dataset of labelled examples of characters in the form of SVGs.
Train a machine learning model, such as a Convolutional Neural Network (CNN), using the labelled examples.
Use the trained model to classify new, unseen SVGs by running them through the model and determining the most likely character based on the model's output.
Keep in mind that this is a complex task and requires a solid understanding of machine learning and computer vision. If you are new to these fields, you may want to start with some tutorials or courses to familiarize yourself with the concepts before attempting to build a character recognition system.
https://www.tensorflow.org/js
https://opencv.org

Related

How to swap the position of two <path> elements inside an SVG

I have an <svg> included in my HTML file with a bunch of <path> elements.
My desired behavior is to be able to randomly shuffle the positioning of the <path> elements, and then to subsequently sort them back into their proper position.
Example: if I have 3 <path>s at positions 1, 2, and 3. For the shuffle functionality, I move path 1 to position 3, path 2 to position 1, and path 3 to position 2. Then I do some kind of visual sort (e.g. insertion sort), where I swap two <path>s' positions at a time until the <path>s are back in their proper place and the SVG looks normal again.
If these were "normal" HTML elements I would just set the x and y properties, but based on my research <path> elements don't have those properties, so I've resorted to using the transform: translate(x y).
With my current approach, the first swap works fine. But any subsequent swaps get way out of whack, and go too far in both directions.
If I'm just swapping two <path>s back and forth, I can get it to work consistently by keeping track of which element is in which position (e.g. elem.setAttribute('currPos', otherElem.id)), and when currPos == currElem.id, setting transform: translate(0 0), but when I start adding more elements, they end up moving to places where there previously wasn’t a <path> element.
My current code is below. For some reason the CSS transition isn’t working properly here but it works elsewhere (edit: it works fine on desktop just not on my phone)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getPos(elem) {
let rect = elem.getBoundingClientRect();
let x = rect.left + (rect.right - rect.left) / 2;
let y = rect.top + (rect.bottom - rect.top) / 2;
return [x, y];
}
async function swap(e1, e2, delayMs = 3000) {
let e1Pos = getPos(e1);
let e2Pos = getPos(e2);
console.log(e1Pos, e2Pos);
e2.setAttribute('transform', `translate(${e1Pos[0]-e2Pos[0]}, ${e1Pos[1]-e2Pos[1]})`);
e1.setAttribute('transform', `translate(${e2Pos[0]-e1Pos[0]}, ${e2Pos[1]-e1Pos[1]})`);
if (delayMs) {
await delay(delayMs);
}
}
let blackSquare = document.getElementById('black-square');
let redSquare = document.getElementById('red-square');
swap(blackSquare, redSquare)
.then(() => swap(blackSquare, redSquare))
.then(() => swap(blackSquare, redSquare));
* {
position: absolute;
}
path {
transition: transform 3s
}
<svg width="500" height="800" xmlns="http://www.w3.org/2000/svg">
<path id="black-square" d="M 10 10 H 90 V 90 H 10 L 10 10" fill="black" />
<path id="red-square" d="M 130 70 h 80 v 80 h -80 v -80" fill="red" />
<path id="green-square" d="M 20 120 h 80 v 80 h -80 v -80" fill="green" />
</svg>
You could achieve this by applying multiple translate transformations.
Lets say, the red square should be positioned at the black square position:
<path transform="translate(-130 -70) translate(10 10)" id="redSquare" d="M 130 70 h 80 v 80 h -80 v -80" fill="red" ></path>
translate(-130 -70) negates the square's original x,y offset and moves this element to the svg's coordinate origin.
The second transformation translate(10 10) will move this element to the black square's position.
const paths = document.querySelectorAll("path");
function shuffleEls(paths) {
/**
* get current positions and save to data attribute
* skip this step if data attribute is already set
*/
if(!paths[0].getAttribute('data-pos')){
paths.forEach((path) => {
posToDataAtt(path);
});
}
// shuffle path element array
const shuffledPaths = shuffleArr([...paths]);
for (let i = 0; i < shuffledPaths.length; i += 1) {
let len = shuffledPaths.length;
//let el1 = i>0 ? shuffledPaths[i-1] : shuffledPaths[len-1] ;
let el1 = shuffledPaths[i];
let el2 = paths[i];
copyPosFrom(el1, el2);
}
}
function posToDataAtt(el) {
let bb = el.getBBox();
let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height].map((val) => {
return +val.toFixed(2);
});
el.dataset.pos = [x, y].join(" ");
}
function copyPosFrom(el1, el2) {
let [x1, y1] = el1.dataset.pos.split(" ").map((val) => {
return +val;
});
let [x2, y2] = el2.dataset.pos.split(" ").map((val) => {
return +val;
});
/**
* original position is negated by negative x/y offsets
* new position copied from 2nd element
*/
el1.setAttribute(
"transform",
`translate(-${x1} -${y1}) translate(${x2} ${y2})`
);
}
function shuffleArr(arr) {
const newArr = arr.slice();
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr;
}
svg{
border:1px solid red;
}
path{
transition: 0.5s;
}
<p>
<button onclick="shuffleEls(paths)">shuffleAll()</button>
</p>
<svg width="500" height="800" xmlns="http://www.w3.org/2000/svg">
<path id="blackSquare" d="M 10 10 H 90 V 90 H 10 L 10 10" fill="black" />
<path id="redSquare" d="M 130 70 h 80 v 80 h -80 v -80" fill="red" />
<path id="greenSquare" d="M 20 120 h 80 v 80 h -80 v -80" fill="green" />
<path id="purpleSquare" d="M 250 10 h 80 v 80 h -80 v -80" fill="purple" />
</svg>
How it works
shuffleEls() gets each path's position via getBBox() and saves x and y coordinates to a data attribute
We shuffle the path element array
each path inherits the position from it's shuffled counterpart
swap positions:
let el1 = shuffledPaths[i];
let el2 = paths[i];
copyPosFrom(el1, el2);
Update: include previous transformations
If a <path> element is already transformed (e.g rotated), you probably want to retain it.
const paths = document.querySelectorAll(".pathToshuffle");
function revertShuffling(paths) {
if (paths[0].getAttribute('data-pos')) {
paths.forEach((path) => {
copyPosFrom(path, path);
});
}
}
//shuffleEls(paths)
function shuffleEls(paths) {
/**
* get current positions and save to data attribute
* skip this step if data attribute is already set
*/
if (!paths[0].getAttribute('data-pos')) {
paths.forEach((path) => {
posToDataAtt(path);
});
}
// shuffle path element array
const shuffledPaths = shuffleArr([...paths]);
let shuffledElCount = 0;
for (let i = 0; i < shuffledPaths.length; i += 1) {
let el1 = shuffledPaths[i];
let el2 = paths[i];
shuffledElCount += copyPosFrom(el1, el2);
}
// repeat shuffling if result is identical to previous one
if (shuffledElCount < 1) {
shuffleEls(paths);
}
}
function posToDataAtt(el) {
let bb = el.getBBox();
let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height].map((val) => {
return +val.toFixed(2);
});
// include other transformations
let style = window.getComputedStyle(el);
let matrix = style.transform != 'none' ? style.transform : '';
el.dataset.pos = [x + width / 2, y + height / 2, matrix].join("|");
}
function copyPosFrom(el1, el2) {
let [x1, y1, matrix1] = el1.dataset.pos.split("|");
let [x2, y2, matrix2] = el2.dataset.pos.split("|");
/**
* original position is negated by negative x/y offsets
* new position copied from 2nd element
*/
let transformAtt = `translate(-${x1} -${y1}) translate(${x2} ${y2}) ${matrix1}`;
// compare previous transformations to prevent identical/non-shuffled results
let transFormChange = el1.getAttribute('transform') != transformAtt ? 1 : 0;
el1.setAttribute("transform", transformAtt);
return transFormChange;
}
function shuffleArr(arr) {
let newArr = arr.slice();
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr;
}
svg {
border: 1px solid red;
}
path {
transition: 0.5s;
}
<p>
<button onclick="shuffleEls(paths)">shuffleAll()</button>
<button onclick="revertShuffling(paths)">revertShuffling()</button>
</p>
<svg width="500" height="800" xmlns="http://www.w3.org/2000/svg">
<path class="pathToshuffle" id="blackSquare" d="M 20 30 h 60 v 40 h -60 z" fill="#999" />
<path class="pathToshuffle" id="redSquare" d="M 130 70 h 80 v 80 h -80 v -80" fill="red" />
<path class="pathToshuffle" id="greenSquare" d="M 20 120 h 80 v 80 h -80 v -80" fill="green" />
<path class="pathToshuffle" transform="rotate(45 275 35)" id="purpleSquare" d="M 250 10 h 50 v 50 h -50 v -50" fill="purple" />
<path id="bg" d="M 10 10 H 90 V 90 H 10 L 10 10z
M 130 70 h 80 v 80 h -80 v -80z
M 20 120 h 80 v 80 h -80 v -80z
M 250 10 h 50 v 50 h -50 v -50z" fill="none" stroke="#000" stroke-width="1" stroke-dasharray="1 2"/>
</svg>
We can append a matrix() to the data attribute.
If paths have different sizes or aspect ratios, you can also set centered x/y coordinates according the the actual bounding box.
This way, all shuffled elements will be positioned around the same center points.
let style = window.getComputedStyle(el);
let matrix = style.transform!='none' ? style.transform : '';
el.dataset.pos = [x+width/2, y+height/2, matrix].join("|");
I think that is is easier to keep track of the positions if all the <path> elements have the same starting point (so, the same distance to 0,0) and then use transform/translate to position them. You can use elements transform matrix to find the position.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getPos(elem) {
let x = elem.transform.baseVal[0].matrix.e;
let y = elem.transform.baseVal[0].matrix.f;
return [x,y];
}
async function swap(e1, e2, delayMs = 3000) {
let e1Pos = getPos(e1);
let e2Pos = getPos(e2);
e2.setAttribute('transform', `translate(${e1Pos[0]} ${e1Pos[1]})`);
e1.setAttribute('transform', `translate(${e2Pos[0]} ${e2Pos[1]})`);
if (delayMs) {
await delay(delayMs);
}
}
let blackSquare = document.getElementById('black-square');
let redSquare = document.getElementById('red-square');
let greenSquare = document.getElementById('green-square');
swap(blackSquare, redSquare)
.then(() => swap(blackSquare, redSquare))
.then(() => swap(blackSquare, greenSquare));
path {
transition: transform 3s
}
<svg viewBox="0 0 500 400" width="500"
xmlns="http://www.w3.org/2000/svg">
<path id="black-square" d="M 0 0 H 80 V 80 H 0 Z"
fill="black" transform="translate(200 50)" />
<path id="red-square" d="M 0 0 H 80 V 80 H 0 Z"
fill="red" transform="translate(50 20)" />
<path id="green-square" d="M 0 0 H 80 V 80 H 0 Z"
fill="green" transform="translate(100 120)" />
</svg>

SVG arc slider for range input

My goal is to design an arc slider which looks something like that
I have the following structure of the template
<svg width="500" height="300">
<path id="track" stroke="lightgrey" fill="transparent" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 300 50
"/>
<path id="trackFill" fill="cyan" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 [some dynamic value?] [some dynamic value?]
"/>
<circle id="knob" fill="lightblue" cx="[dynamic, initial - 50]" cy="[dynamic, initial - 50]" r="25"/>
</svg>
knob - the control which user is supposed to drag in order to change the value
track - the full arc of the slide
trackFill - the portion of the slider path before the knob
Is it possible to make trackFill cover the portion of the slider before the knob as it is being dragged along the slider curve? If so which APIs or CSS rules will help me to achieve such a result?
Is it something like this you are after?
let svg = document.getElementById("slider");
let trackFill = document.getElementById("trackFill");
let knob = document.getElementById("knob");
let isDragging = false;
let sliderDragOffset = {dx: 0, dy: 0};
let ARC_CENTRE = {x: 175, y: 50};
let ARC_RADIUS = 125;
let sliderValue = 0;
setSliderValue(sliderValue);
function setSliderValue(value)
{
// Limit value to (0..sliderMax)
let sliderMax = track.getTotalLength();
sliderValue = Math.max(0, Math.min(value, sliderMax));
// Calculate new position of knob
let knobRotation = sliderValue * Math.PI / sliderMax;
let knobX = ARC_CENTRE.x - Math.cos(knobRotation) * ARC_RADIUS;
let knobY = ARC_CENTRE.y + Math.sin(knobRotation) * ARC_RADIUS;
// Adjust trackFill dash patter to only draw the portion up to the knob position
trackFill.setAttribute("stroke-dasharray", sliderValue + " " + sliderMax);
// Update the knob position
knob.setAttribute("cx", knobX);
knob.setAttribute("cy", knobY);
}
knob.addEventListener("mousedown", evt => {
isDragging = true;
// Remember where we clicked on knob in order to allow accurate dragging
sliderDragOffset.dx = evt.offsetX - knob.cx.baseVal.value;
sliderDragOffset.dy = evt.offsetY - knob.cy.baseVal.value;
// Attach move event to svg, so that it works if you move outside knob circle
svg.addEventListener("mousemove", knobMove);
// Attach move event to window, so that it works if you move outside svg
window.addEventListener("mouseup", knobRelease);
});
function knobMove(evt)
{
// Calculate adjusted drag position
let x = evt.offsetX + sliderDragOffset.dx;
let y = evt.offsetY + sliderDragOffset.dy;
// Position relative to centre of slider arc
x -= ARC_CENTRE.x;
y -= ARC_CENTRE.y;
// Get angle of drag position relative to slider centre
let angle = Math.atan2(y, -x);
// Positions above arc centre will be negative, so handle them gracefully
// by clamping angle to the nearest end of the arc
angle = (angle < -Math.PI / 2) ? Math.PI : (angle < 0) ? 0 : angle;
// Calculate new slider value from this angle (sliderMaxLength * angle / 180deg)
setSliderValue(angle * track.getTotalLength() / Math.PI);
}
function knobRelease(evt)
{
// Cancel event handlers
svg.removeEventListener("mousemove", knobMove);
window.removeEventListener("mouseup", knobRelease);
isDragging = false;
}
<svg id="slider" width="500" height="300">
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20" d="
M 50 50
A 125 125 0 0 0 300 50
"/>
</g>
<use id="trackFill" xlink:href="#track" stroke="cyan"/>
<circle id="knob" fill="lightblue" cx="50" cy="50" r="25"/>
</svg>
I've kept this code simple for clarity, but at the expense of some limitations.
It assumes there is only one slider per page. If you need more than that, you will have to keep the slider-specific values (eg sliderValue and, isDragging) separate. You could use data attributes for that. You would also need to switch from accessing the SVG elements via id attributes to another way (eg. class attributes), because id attributes must be unique on the page.
Here is a simple example:
const radius = 50;
const offsetX = 10;
const offsetY = 10;
// 0 <= pos <= 1
const setSliderPos = (svg, pos) => {
const angle = Math.PI * pos;
const x = offsetX + radius - Math.cos(angle) * radius;
const y = offsetY + Math.sin(angle) * radius;
svg.select('.knob').attr('cx', x).attr('cy', y);
svg.select('.first').attr('d', `M ${offsetX},${offsetY} A ${radius},${radius} 0 0 0 ${x},${y}`);
svg.select('.second').attr('d', `M ${x},${y} A ${radius},${radius} 0 0 0 ${offsetX + radius * 2},${offsetY}`);
}
setSliderPos(d3.select('#svg-1'), 0.3);
setSliderPos(d3.select('#svg-2'), 0.6);
setSliderPos(d3.select('#svg-3'), 1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="svg-1" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-2" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-3" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
To mark the progress you can use stroke-dasharray with a percentage; for example
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20"
stroke-dasharray="40% 60%"
d="M 50 50 A 125 125 0 0 0 300 50"/>
</g>
This will show 40% of the arc and hide 60% of the arc.
If you need to use two colors, for example the whole arc in grey and the progress in black, you need to use two arcs on top of one another; the one at the bottom would be the one you already have, and the one at the top would have a stroke in black and use stroke-dasharray as shown.

Get the bounding box of the intersection of 2 or more paths

In the following example I have in gold the intersection of 3 shapes (in this case I'm using circles but those 3 shapes can be anything) The golden intersection is the result of clipping with clip-path.
I would like to use the intersection as a symbol and for this I would need to know the bounding box of the intersection, i.e the red stroked rectangle.
If I'm using intersection.getBBox() I'm getting the bounding box before clipping.
How can I get the bounding box of the intersection?
console.log(intersection.getBBox())
svg{border:solid}
.circles{fill:none;stroke:black}
<svg id="svg" viewBox="-150 -150 300 300" width="300">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g class="circles">
<use xlink:href="#c1"/>
<use xlink:href="#c2"/>
<use xlink:href="#c3"/>
</g>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use fill="gold" xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
<rect x="-38" y="-42" width="75" height="74" stroke="red" fill="none"/>
</svg>
The main idea is this:
I'm taking the svg element, make it base64 and use it as the src attribute of an image.
I'm painting the svg element on a canvas with the same size as the svg element.
I get the image data from the canvas
loop through the image data and get:
the smallest x value of a black pixel
the smallest y value of a black pixel
the biggest x value of a black pixel
the biggest y value of a black pixel
I'm using using those values to build the new viewBox value for the intersection.
//the svg's viewBox
let vB = { x: -100, y: -100, w: 200, h: 200 };
//canvas
let ctx = c.getContext("2d");
//set the size of the canvas equal to the size of the svg element
c.width = vB.w;
c.height = vB.h;
// draw the svg element on the canvas
let xml = new XMLSerializer().serializeToString(svg);
// make it base64 and use it as the src attribute of the image
let img=new Image()
img.src = "data:image/svg+xml;base64," + btoa(xml);
img.onload = function() {
//paint the image on the canvas
ctx.drawImage(this, 0, 0);
//get the image data from the canvas
let imgData = ctx.getImageData(0, 0, vB.w, vB.h).data;
// x the smallest x value of a black pixel
// y the smallest y value of a black pixel
// X the biggest x value of a black pixel
// Y the biggest y value of a black pixel
let x = vB.w,
y = vB.h,
X = 0,
Y = 0;
let n = 0;
for (let i = 0; i < imgData.length; i += 4) {
n++
if (imgData[i + 3] != 0) {
//if the alpha (i+3) value of the pixel is not 0
let _y = Math.ceil(i / (4 * vB.w));
let _x = (i / 4) % vB.w;
if (_x < x) { x = _x; }
if (_y < y) { y = _y; }
if (_x > X) { X = _x; }
if (_y > Y) { Y = _y; }
}
if(n==imgData.length/4){
let newViewBox = `${x + vB.x} ${y + vB.y} ${X - x + 1} ${Y - y}`;
reuleaux.setAttribute("viewBox", newViewBox);
console.log(`viewBox="${newViewBox}"`);
}
}
}
svg,
canvas {
outline: 1px solid;
}
<svg id="svg" viewBox="-100 -100 200 200" width="200">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
</svg>
<!--<img id="img" width="200" height="200"/>-->
<canvas id="c"></canvas>
<svg id="reuleaux" viewBox="-100 -100 200 200" width="200" style="background:#dfdfdf">
<use xlink:href="#intersection"/>
</svg>
Unsure if this was the type of thing you were after.
let myBB = {
x: c2.getBBox().x,
get y() {
return c1.getBBox().y + this.width
},
get width() {
// return (posNum(c3.getBBox().x)) - (posNum(myBB.x));
let leftPointOfWidth = c2.getBBox().x;
let rightPointofWidth = c3.getBBox().x + c3.getBBox().width;
// 10000 to guarantee both positive numbers. very hacky
let mywidth = (rightPointofWidth + 10000) - (leftPointOfWidth + 10000);
return mywidth;
},
get height() {
return this.width;
}
}
I'm quite sure there's a better way to put it. And need to call the getters; They won't appear in the console.log(myBB)
Inputting the obtained coordinates and width gives the rect in yellow. (Pink rects are shoing centres of circles)

SVG displacement map filter

I was trying to implement fisheye-esque filter in my SVG and found this codepen:
http://codepen.io/johanberonius/pen/RopjYW
It works perfectly fine except I want this effect to be slightly "harder", but I couldn't change displacement map since it generated in js.
var canvas = document.getElementById('canvas'),
barrel = document.getElementById('filter-image'),
width = canvas.width,
height = canvas.height,
ctx = canvas.getContext('2d');
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var dx = x - 128,
dy = y - 128,
l = Math.sqrt(dx * dx + dy * dy),
a = l < 128 ? Math.asin(l/128) : 0,
z = l < 128 ? 255 - Math.cos(a) * 255 : 0,
r = l < 128 ? 128 + (dx / 128) * (z / 255) * 128 : 0,
g = l < 128 ? 128 + (dy / 128) * (z / 255) * 128 : 0,
o = l >= 124 ? Math.max(0, 1 - (l-124)/4) : 1;
ctx.fillStyle = 'rgba('+Math.floor(r)+','+Math.floor(g)+',0,'+o+')';
ctx.fillRect(x,y,1,1);
}
}
barrel.setAttribute('xlink:href', canvas.toDataURL());
var tx = 0,
ty = 0;
requestAnimationFrame(function updateAnimationFrame() {
tx += 0.027;
ty += 0.031;
barrel.setAttribute('x', 128 + Math.sin(tx) * 120);
barrel.setAttribute('y', 128 + Math.cos(ty) * 120);
requestAnimationFrame(updateAnimationFrame);
});
Formula is WAY too advanced for me to hack.
So I was wondering is there any way I can generate these types of maps or maybe someone can help me with a formula.
The displacement map could, at least roughly equivalent, be constructed as a SVG filter itself.
So you would think that you might be able to combine the drawing of the displacement map and its application to the image, in one SVG file. It turns out you can not, as browsers do not implement enable-background.
But, distributed among two files, it works. First, the displacement map:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" height="300" width="300">
<defs>
<filter id="barrel" x="-30%" y="-30%" width="160%" height="160%"
color-interpolation-filters="sRGB">
<feGaussianBlur result="result1" stdDeviation="10" />
<feMorphology operator="erode" radius="5" result="result5" />
<feColorMatrix result="result3" type="matrix"
values="0 0 0 -0.3 0.8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 " />
<feOffset result="result4" dy="-5" dx="5" />
<feColorMatrix result="result2" in="result5" type="matrix"
values="0 0 0 0 0 0 0 0 -0.3 0.8 0 0 0 0 0 0 0 0 0 1 " />
<feOffset dy="5" dx="-5" />
<feComposite result="result6" k3="1" k2="1" operator="arithmetic" in2="result4" />
</filter>
<clipPath id="cp" clipPathUnits="userSpaceOnUse">
<circle r="100" cx="150" cy="150" />
</clipPath>
</defs>
<circle clip-path="url(#cp)" filter="url(#barrel)"
cy="150" cx="150" r="100" />
</svg>
And secondly, the application to the image:
<svg width="512" height="512" viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="barrel">
<feImage id="filter-image" xlink:href="circle.svg" result="barrel"
x="64" y="64" width="256" height="256" />
<feDisplacementMap in2="barrel" in="SourceGraphic"
xChannelSelector="R" yChannelSelector="G" scale="64" />
<feComposite operator="in" in2="barrel"/>
</filter>
</defs>
<image xlink:href="https://i.imgsafe.org/3353aef52f.jpg"
x="0" y="0" height="512" width="512"/>
<image xlink:href="https://i.imgsafe.org/3353aef52f.jpg"
x="-16" y="-16" height="512" width="512" filter="url(#barrel)"/>
</svg>
I produced this with the Inkscape Filter editor, which gives a fair grafical interface for manipulating the filter components. You should probably experiment some more. Numbers to play around with probably are:
feGaussianBlur blur radius - the width of the colored border, it would be my first candidate for what you call "sharpness"
feMorphology erode radius should probably always be half the value of the blur radius
feOffset dx/dy displace the red and green components relative to each other
feColorMatrix Those number that are not zero could be variated. Look in the filter editor for an explanation.

Texture repeats and overlaps in three.js

I am creating a texture from svg files, mapping it on planes and returning the bitmap mesh to a callback. Problem is that first texture works fine but when I apply the second texture the plane has both first and second texture and this continues.
EDIT : What is really surprising is that console.log(mesh.material.map.image.src) is
showing the correct image for each icon(no overlap). Other than that each three.js group has just 2 children one for the overlay image and other for the blue background.
var scene = new THREE.Scene();
var cameraWidth = window.innerWidth;
var cameraHeight = window.innerHeight;
var camera = new THREE.OrthographicCamera(cameraWidth / -2, cameraWidth / 2, cameraHeight / 2, cameraHeight / -2, 0, 10000);
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x555555, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 100;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
var TURTLEPALETTEICON = '<svg xmlns="http://www.w3.org/2000/svg" width="55" height="55"> <path d="m 27.567493,45.252146 c -0.46948,0 -0.933016,-0.02903 -1.389761,-0.08296 l 1.178368,1.948634 1.161389,-1.918769 c -0.314968,0.02489 -0.629086,0.05309 -0.949996,0.05309 z" style="fill:none;stroke:#ffffff;stroke-width:1.20000005;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <path d="m 38.317981,14.929279 c -1.837168,0 -3.360217,1.289964 -3.68707,2.992219 1.578232,1.115757 2.934884,2.584076 3.968928,4.320343 1.939893,-0.142684 3.475677,-1.709721 3.475677,-3.641764 0,-2.027442 -1.682656,-3.670798 -3.757535,-3.670798 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <path d="m 38.787461,38.290488 c -1.039138,1.851575 -2.42805,3.426908 -4.072502,4.609029 0.442312,1.546298 1.878767,2.686942 3.603022,2.686942 2.07403,0 3.757535,-1.642527 3.757535,-3.669969 0,-1.870656 -1.437304,-3.397874 -3.288055,-3.626002 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <path d="m 16.340734,38.277215 c -1.912727,0.170889 -3.41625,1.724653 -3.41625,3.639275 0,2.026612 1.680958,3.669969 3.755837,3.669969 1.752271,0 3.212497,-1.177974 3.626793,-2.764091 -1.598607,-1.174655 -2.950165,-2.728419 -3.96638,-4.545153 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <path d="m 20.375881,18.007772 c -0.291196,-1.744563 -1.828678,-3.078493 -3.69556,-3.078493 -2.074879,0 -3.755837,1.643356 -3.755837,3.669968 0,1.97601 1.601155,3.575399 3.603872,3.655037 1.006876,-1.694789 2.319381,-3.139051 3.847525,-4.246512 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <path d="m 27.567493,15.62362 c 1.619832,0 3.164955,0.340948 4.599711,0.935742 0.629086,-0.892605 1.000085,-1.971862 1.000085,-3.138221 0,-3.058584 -2.537567,-5.5389654 -5.668563,-5.5389654 -3.130146,0 -5.667713,2.4803814 -5.667713,5.5389654 0,1.18461 0.383734,2.280457 1.032345,3.180529 1.463622,-0.62134 3.04525,-0.97805 4.704135,-0.97805 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.29999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> <g transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"> <path d="m 43.102,30.421 c 0,4.7344 -1.6452,9.2798 -4.5706,12.6275 -2.9254,3.3478 -6.8973,5.2305 -11.0344,5.2305 -4.1371,0 -8.109,-1.8827 -11.0344,-5.2305 -2.9254,-3.3477 -4.5706,-7.8931 -4.5706,-12.6275 0,-9.7966 7.0444,-17.858 15.605,-17.858 8.5606,0 15.605,8.0614 15.605,17.858 z" style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.42992032;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> </g> <g transform="matrix(0.8489685,0,0,0.82955893,4.2234061,5.2018707)" style="fill:#ffffff;fill-opacity:1;stroke:none"> <path d="m 25.875,33.75 -1.542,-4.625 3.164,-2.587 3.615,2.626 -1.487,4.669 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> <path d="m 27.501,41.551 c -3.968,-0.16 -5.543,-2.009 -5.543,-2.009 l 3.57,-4.163 4.465,0.168 3.132,4.12 c 0,0 -2.89,1.994 -5.624,1.884 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> <path d="m 18.453,33.843 c -0.849,-2.968 0.172,-6.884 0.172,-6.884 l 4,2.167 1.493,4.629 -3.582,4.233 c 0,-10e-4 -1.465,-1.99 -2.083,-4.145 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> <path d="m 19.458,25.125 c 0,0 0.5,-1.958 3.039,-3.822 2.237,-1.643 4.465,-1.72 4.465,-1.72 l -0.037,4.981 -3.521,2.75 -3.946,-2.189 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> <path d="M 32.084,27.834 28.625,24.959 29,19.75 c 0,0 1.834,-0.042 3.959,1.667 2.228,1.791 3.362,4.983 3.362,4.983 l -4.237,1.434 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> <path d="m 31.292,34.042 1.313,-4.464 4.187,-1.536 c 0,0 0.677,2.663 -0.042,5.667 -0.54,2.256 -2.084,4.361 -2.084,4.361 l -3.374,-4.028 z" style="fill:#ffffff;fill-opacity:1;stroke:none" /> </g> </svg>';
var PENPALETTEICON = '<svg xmlns="http://www.w3.org/2000/svg" width="55" height="55"> <path d="m 11.152285,41.709935 c 1.43401,0.788706 5.23977,1.402428 7.528553,1.290609 1.626167,-0.07945 3.914929,-0.479849 5.234137,-1.43401 2.238123,-1.618798 3.032695,-5.829627 5.090736,-7.671954 1.225701,-1.097229 3.231844,-2.444635 4.875634,-2.509518 2.003851,-0.07909 4.468168,1.514349 6.166244,2.581219 1.290216,0.810619 3.800127,3.369923 3.800127,3.369923" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1" /> <path d="m 35.566307,13.352385 2.437818,-2.366117 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.366117,2.509519 -0.896257,-1.505711 -2.186866,-2.366117 -1.50571,-1.075508 z" style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" /> <path d="m 32.877538,16.112854 2.294417,-2.079315 1.200984,0.84248 1.416085,1.200984 0.985882,1.272684 0.914181,1.487786 -2.079315,1.864214 -0.967957,-1.147208 -2.330267,-2.222716 -1.43401,-1.218909 z" style="fill:none;stroke:#ffffff;stroke-width:1px;stroke-opacity:1" /> <path d="m 13.423248,38.807621 0.891754,-3.80169 18.820708,-18.21056 -0.469344,-0.680549 2.252852,-2.135517 5.115854,5.115853 -2.229385,2.135517 -0.774419,-0.539747 -19.05538,18.022823 -3.70782,0.657082 -0.84482,-0.563212 z" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> <path d="m 33.482432,17.358584 3.660886,3.473148" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> <path d="m 10.935723,41.905293 2.769132,-2.816066" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> <path d="m 14.690478,35.287537 c 0,0 1.594197,0.393866 2.158983,0.93869 0.574138,0.553844 1.032558,2.158984 1.032558,2.158984" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" /> </svg>';
var NUMBERPALETTEICON = '<svg xmlns="http://www.w3.org/2000/svg" width="55" height="55"> <g transform="translate(6.1026134,-1.6740561)" style="fill:#ffffff;fill-opacity:1"> <text style="font-size:12px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan x="3.9423084" y="26.866751" id="tspan2386" style="font-size:18px;font-weight:normal;fill:#ffffff;fill-opacity:1;-inkscape-font-specification:AlArabiya">123</tspan></text> <text style="font-size:12px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"><tspan x="1.8153553" y="44.840736" style="font-size:18px;font-weight:normal;fill:#ffffff;fill-opacity:1;-inkscape-font-specification:AlArabiya">+–=</tspan></text> </g> </svg>';
var BOOLEANPALETTEICON = '<svg xmlns="http://www.w3.org/2000/svg" width="55" height="55" viewBox="0 0 55 55"><g style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:block"><path d="m 10,25 c 0,-6 6,-12 12,-12 6,0 12,6 12,12 0,6 -6,12 -12,12 -6,0 -12,-6 -12,-12 m 11,0 c 0,-6 6,-12 12,-12 6,0 12,6 12,12 0,6 -6,12 -12,12 -6,0 -12,-6 -12,-12" style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></svg>';
var PALETTEICONS = {
'turtle': TURTLEPALETTEICON,
'pen': PENPALETTEICON,
'number': NUMBERPALETTEICON,
'boolean': BOOLEANPALETTEICON
};
var output = document.getElementById('WebGL-output');
output.appendChild(webGLRenderer.domElement);
var x = 0;
var y = window.innerHeight / 2 - 27.5;
var buttons = {};
addIconOnScreen('pen');
addIconOnScreen('turtle');
addIconOnScreen('number');
addIconOnScreen('boolean');
function addIconOnScreen(name){
var img = new Image();
img.onload = function () {
var texture = new THREE.Texture(img);
texture.needsUpdate = true;
texture.minFilter = THREE.NearestFilter;
var material = new THREE.MeshBasicMaterial({
map: texture
});
material.transparent = true;
material.depthWrite = false;
var bitmap = new THREE.Mesh(new THREE.PlaneBufferGeometry(img.width, img.height), material);
bitmap.name = name;
bitmap.position.setX(0);
bitmap.position.setY(y);
scene.add(bitmap);
y-=55;
webGLRenderer.render(scene, camera);
};
img.src = 'data:image/svg+xml;base64,' + window.btoa(
unescape(encodeURIComponent(PALETTEICONS[name])));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
<div id="WebGL-output"></div>
I suspect that some kind of texture caching is taking place. When I load the new image I am overwriting the old texture cache. THREE.js is assuming that all the img I load are the same. Is there anyway to stop this texture cache?
EDIT : The other thing that I can think of is problem with closures. But I have gone through multiple times and it doesn't seem that way.
When image loaded, you need use canvas:
img.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
var texture = new THREE.Texture(canvas);
...
}
http://jsfiddle.net/wzy7m85y/
Seems to be a browser bug, see: https://code.google.com/p/chromium/issues/detail?id=553885
It's fixed in Chrome 48.0.2557.0 canary (64-bit)

Categories

Resources