Hello,
I draw an image on a html canvas object. The unpainted areas are transparent in the resulting image.
I managed to find a very simple method to find the pixels that will create the bounds of the painted surface even if not all areas are touching. I also wrote a small function to reduce the amount of vectors used to build this outline.
I also managed to build an extrude geometry and give it shape of the outline.
Next I mapped the texture on the geometry and organized the uv-mapping.
This really works just fine so far.
But on the geometry the transparent parts of the geometry appear black. I want those black parts to appear white.
If I enable transparency the black parts disappear but unfortunattaly the parts of the geometry that are not coverd by the image also disappear (turn transparent)
And if I just color this without attaching the texture I do get the outline geometry in any color I define (white in my develpment stage).
createFromImage(imageInfo) { //Ist fĂĽr Testzwecke, um zu sehen, wie die Konturen ĂĽberhaupt aussehen
let dataUrl = imageInfo.base64Url; //base64;png data-url
let wPixels = parseFloat(imageInfo.width); //Image Width in pixels
let hPixels = parseFloat(imageInfo.height); //Image Height in pixels
let imageData = imageInfo.imageData; // ctx = canvas.getContext('2d'); ctx.getImageData(0 , 0 , w , h);
let contourPoints = this.createContourFromImageData(imageData);
const shape = this.contourToShape(contourPoints);
const extrudeSettings = {
steps: 1,
depth: 1,
bevelEnabled: true,
bevelThickness: 1,
bevelSize: 1,
bevelOffset: 0,
bevelSegments: 3,
UVGenerator: {
generateTopUV: function (geometry, vertices, indexA, indexB, indexC) {
const ax = vertices[indexA * 3];
const ay = vertices[indexA * 3 + 1];
const bx = vertices[indexB * 3];
const by = vertices[indexB * 3 + 1];
const cx = vertices[indexC * 3];
const cy = vertices[indexC * 3 + 1];
return [
new THREE.Vector2(ax / wPixels, 1 - ay / hPixels),
new THREE.Vector2(bx / wPixels, 1 - by / hPixels),
new THREE.Vector2(cx / wPixels, 1 - cy / hPixels)
];
},
generateSideWallUV: function (geometry, vertices, indexA, indexB, indexC, indexD) {
// UV-Koordinaten für die Seitenwände definieren
const ax = vertices[indexA * 3];
const ay = vertices[indexA * 3 + 1];
const bx = vertices[indexB * 3];
const by = vertices[indexB * 3 + 1];
return [
new THREE.Vector2(ax / wPixels, ay / hPixels),
new THREE.Vector2(bx / wPixels, by / hPixels),
new THREE.Vector2(ax / wPixels, ay / hPixels),
new THREE.Vector2(bx / wPixels, by / hPixels)
];
}
}
};
let widthMesh = 5.0;
let heightMesh = widthMesh * (hPixels / wPixels);
let texture = new THREE.TextureLoader().load(dataUrl);
//texture.encoding = THREE.sRGBEncoding;
//texture.needsUpdate = true;
let geometry = new THREE.ExtrudeGeometry(shape , extrudeSettings);
geometry.attributes.uv.array.forEach((_, i, uvArray) => {
if (i % 2 === 1) {
// Y-Koordinate (v-Wert) invertieren
uvArray[i] = 1 - uvArray[i];
}
});
let material = new THREE.MeshStandardMaterial({
color: 0xffffff, // WeiĂź als Grundfarbe fĂĽr den Hintergrund
map: texture, // PNG-Textur
transparent: false, // Aktiviere Transparenz
opacity: 1, // Setze die Gesamt-Deckkraft
side: THREE.DoubleSide // Beide Seiten sichtbar
});
let mesh = new THREE.Mesh(geometry , material);
geometry.computeBoundingSphere();
let radius = geometry.boundingSphere.radius;
let scaleFactor = widthMesh / radius;
mesh.scale.set(scaleFactor , scaleFactor, scaleFactor);
mesh.position.set(0 , 4 , 0);
this.m_stage.add(mesh);
}
contourToShape(contourPoints) {
if(!contourPoints || contourPoints.length < 3) return null;
const shape = new THREE.Shape();
for(let i = 0 ; i < contourPoints.length ; i++) { //Kontur spiegeln
let pi = contourPoints[i];
pi.y *= -1;
}
let p0 = contourPoints[0];
let minX = p0.x;
let minY = p0.y;
for(let i = 1 ; i < contourPoints.length ; i++) {
let pi = contourPoints[i];
if(pi.x < minX) minX = pi.x;
if(pi.y < minY) minY = pi.y;
}
for(let i = 0 ; i < contourPoints.length ; i++) {
let pi = contourPoints[i];
pi.x = pi.x - minX;
pi.y = pi.y - minY;
}
shape.moveTo(p0.x , p0.y);
for(let i = 1 ; i < contourPoints.length ; i++) {
let pi = contourPoints[i];
shape.lineTo(pi.x , pi.y);
}
shape.lineTo(p0.x , p0.y);
return shape;
}
createContourFromImageData(imageData) {
let wPixels = imageData.width;
let hPixels = imageData.height;
const data = imageData.data;
const pointsL = [];
const pointsR = [];
for(let y = 0 ; y < hPixels ; y++) {
let left = null;
let right = null;
for(let x = 0 ; x < wPixels ; x++) {
const alpha = data[(y * wPixels + x) * 4 + 3];
if(alpha > 0) {
if(left === null) left = x;
right = x;
}
}
if(left !== null) {
pointsL.push({x: left , y:y});
pointsR.unshift({x:right , y:y});
}
}
let points = pointsL.concat(pointsR); //Die Punkte bilden nun ein linksläufiges Polygon
this.minimizeEdges(points);
return points;
}
//Betrachtet wird der Vector P[i]->P[i+1] und verglichen mit P[i+1] -> P[i+2]
//Haben beide Vektoren die gleiche Richtung kann der Punkt P[i+1] entfallen
minimizeEdges(points) {
let i = 0;
while(i < points.length - 2) {
let p0 = points[i];
let p1 = points[i+1];
let p2 = points[i+2];
let p10 = {x: p1.x-p0.x , y: p1.y-p0.y};
let p11 = {x: p2.x-p1.x , y: p2.y-p1.y};
//Normalisierung hier nicht notwendig und sorgt fĂĽr Rundungsfehler.
//Nicht notwendig, weil ein Vektor immer nur von einem Pix zu seinem Nachbarpixel geht.
//Im Zweifelsfall bleiben ein paar Vectoren übrig, die man auch noch hätte vereinheitlichen können,
//aber das ist erst interessant, wenn das System unbedingt optimiert werden soll.
let crossProduct = (p10.x * p11.y) - (p10.y * p11.x);
if(crossProduct == 0) {
points.splice(i , 1);
}else {
i++;
}
}
}
So this is the code I am using. I really hope you could use that code for your purposes and I also hope that you come up with any ideas to get the black parts white (or in fact card-box colored)
I was already asking ChatGPT but it wasn’t able to solve my problem. And now I am out of ideas except writing custom shaders, maybe, which I have never done before.
Just in case someone is wondering, I cropped the image and this is the function to create the “imageInfo” object that I take all the information from.
cropImageFromCanvas(ctx) {
var canvas = ctx.canvas,
w = canvas.width, h = canvas.height,
pix = {x:[], y:[]},
imageData = ctx.getImageData(0,0,canvas.width,canvas.height),
x, y, index;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
index = (y * w + x) * 4;
if (imageData.data[index+3] > 0) {
pix.x.push(x);
pix.y.push(y);
}
}
}
pix.x.sort(function(a,b){return a-b});
pix.y.sort(function(a,b){return a-b});
var n = pix.x.length-1;
w = 1 + pix.x[n] - pix.x[0];
h = 1 + pix.y[n] - pix.y[0];
var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h);
canvas.width = w;
canvas.height = h;
ctx.putImageData(cut, 0, 0);
var base64Url = canvas.toDataURL();
let imageDataCropped = ctx.getImageData(0 , 0 , w , h);
return {base64Url: base64Url , width: w , height: h , imageData: imageDataCropped};
}
Thanks in advice