I am working on modifying an example of warp.js to allow for warping using a grid of control point dots (like the AI "free distort" tool)
I have had tried myself, I have abused ChatGPT and even paid a guy on Fiverr but no one and no bot has been able to help.
const svg = document.getElementById('svg8');
const controlPath = document.getElementById('control-path');
const warp = new Warp(svg);
const width = svg.width.baseVal.value;
const height = svg.height.baseVal.value;
// Define the number of columns and rows in the grid
const numColumns = 5;
const numRows = 5;
// Calculate the grid cell size
const cellWidth = width / (numColumns - 1);
const cellHeight = height / (numRows - 1);
// Create the initial control points grid
const controlPoints = [];
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numColumns; col++) {
const x = col * cellWidth;
const y = row * cellHeight;
controlPoints.push([x, y]);
}
}
// Funny things happen when control points are positioned perfectly on other points... buff it out
const controlBuffer = 0.1;
for (let i = 0; i < controlPoints.length; i++) {
const point = controlPoints[i];
if (point[0] === 0) point[0] -= controlBuffer;
if (point[1] === 0) point[1] -= controlBuffer;
if (point[0] === width) point[0] += controlBuffer;
if (point[1] === height) point[1] += controlBuffer;
}
// Compute weights from control points
warp.transform(function(v0, V = controlPoints) {
const A = [];
const W = [];
const L = [];
// Find distances
for (let i = 0; i < V.length; i++) {
const j = (i + 1) % V.length;
const vi = V[i];
const vj = V[j];
const dx = vj[0] - vi[0];
const dy = vj[1] - vi[1];
const r = Math.sqrt(dx ** 2 + dy ** 2);
W[i] = r;
}
// Find angles
for (let i = 0; i < V.length; i++) {
const j = (i + 1) % V.length;
const vi = V[i];
const vj = V[j];
const dx = vj[0] - v0[0];
const dy = vj[1] - v0[1];
const r = Math.sqrt(dx ** 2 + dy ** 2);
const angle = Math.atan2(dy, dx);
A[i] = angle;
W[i] = r;
}
// Normalize weights
const Ws = W.reduce((a, b) => a + b, 0);
for (let i = 0; i < V.length; i++) {
L[i] = W[i] / Ws;
}
// Save weights to the point for use when transforming
return [...v0, ...L];
});
// Function to reposition the control points based on the mean value coordinates
function reposition([x, y, ...W], V = controlPoints) {
let nx = 0;
let ny = 0;
let s = 0;
// Recreate the points using mean value coordinates
for (let i = 0; i < V.length; i++) {
const weight = W[i];
const point = V[i];
nx += weight * point[0];
ny += weight * point[1];
s += weight;
}
nx /= s;
ny /= s;
return [nx, ny, ...W];
}
// Function to draw the control shape
function drawControlShape(element = controlPath, V = controlPoints) {
const path = [];
for (let row = 0; row < numRows; row++) {
const rowPoints = V.slice(row * numColumns, (row + 1) * numColumns);
path.push(`M${rowPoints[0][0]} ${rowPoints[0][1]}`);
for (let col = 1; col < numColumns; col++) {
path.push(`L${rowPoints[col][0]} ${rowPoints[col][1]}`);
}
}
for (let col = 0; col < numColumns; col++) {
const colPoints = [];
for (let row = 0; row < numRows; row++) {
colPoints.push(V[row * numColumns + col]);
}
path.push(`M${colPoints[0][0]} ${colPoints[0][1]}`);
for (let row = 1; row < numRows; row++) {
path.push(`L${colPoints[row][0]} ${colPoints[row][1]}`);
}
}
element.setAttribute('d', path.join(''));
}
// Add code to draw dots at each control point
const svgControl = document.getElementById('svg-control');
for (let i = 0; i < controlPoints.length; i++) {
const [x, y] = controlPoints[i];
const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
dot.setAttribute('cx', x);
dot.setAttribute('cy', y);
dot.setAttribute('r', 5);
dot.setAttribute('fill', 'red');
svgControl.appendChild(dot);
}
warp.interpolate(40);
drawControlShape();
//warp.transform(reposition);
// Get the SVG circle elements
var dots = document.querySelectorAll('#svg-control circle');
// Function to initiate dragging
function startDrag(event) {
// Store the current dot and its initial position
const activeDot = event.target;
let initialX = event.clientX;
let initialY = event.clientY;
let offsetX = parseInt(activeDot.getAttribute('cx'));
let offsetY = parseInt(activeDot.getAttribute('cy'));
// Get the index of the active dot in the dots NodeList
const index = Array.from(dots).indexOf(activeDot);
// Add event listeners for mouse move and mouse up events
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
// Function to handle dragging
function drag(event) {
// Calculate the distance moved by the mouse
const deltaX = event.clientX - initialX;
const deltaY = event.clientY - initialY;
// Update the position of the dot based on the mouse movement
const newX = offsetX + deltaX;
const newY = offsetY + deltaY;
activeDot.setAttribute('cx', newX);
activeDot.setAttribute('cy', newY);
// Update the controlPoints array with the new positions
controlPoints[index] = [newX, newY];
// Redraw the control shape
drawControlShape();
// Update the warp transformation based on the new control points
warp.transform(reposition);
}
// Function to end dragging
function endDrag() {
// Remove the event listeners for mouse move and mouse up events
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', endDrag);
}
}
// Add event listeners for mouse interactions on each dot
dots.forEach(dot => {
dot.addEventListener('mousedown', startDrag);
});
svg {
position: absolute;
top: 0px;
left: 0px;
}
#svg8,
#svg-control {
position: absolute;
top: 0px;
left: 0px;
overflow: visible;
}
#svg-control {
position: absolute;
z-index: 9000000;
top: 0;
left: 0;
}
#control-path {
fill: none;
stroke: red;
stroke-width: 1px;
}
body {
padding: 10px;
}
<script src="https://cdn.jsdelivr.net/npm/warpjs@1.0.8/dist/warp.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 105" height="500px" width="500px" id="svg8">
<g fill="#97C024" stroke="#97C024" stroke-linejoin="round" stroke-linecap="round">
<path d="M14,40v24M81,40v24M38,68v24M57,68v24M28,42v31h39v-31z" stroke-width="12"/>
<path d="M32,5l5,10M64,5l-6,10 " stroke-width="2"/>
</g>
<path d="M22,35h51v10h-51zM22,33c0-31,51-31,51,0" fill="#97C024"/>
<g fill="#FFF">
<circle cx="36" cy="22" r="2"/>
<circle cx="59" cy="22" r="2"/>
</g>
</svg>
<svg id="svg-control" width="120" height="120">
<path id="control-path" d="M50 50L100 50L100 100L50 100Z" stroke="black" fill="transparent"/>
</svg>
Here is a jsfiddle I have made for it: https://jsfiddle.net/b70mdz5L/
The bug is it warps to almost nothing depending on the svg element from just touching a control point.
Here is the original example that I simplified(had it working) and modified. https://pavellaptev.github.io/warp-svg/
I'd like to think I exhausted all of my option before just spamming the forum. So if anyone could help I would really appreciated it.