Skip to content

Instantly share code, notes, and snippets.

@0xlxy
Created January 9, 2024 00:12
Show Gist options
  • Save 0xlxy/9327719ef8301636c73c95ec85137c85 to your computer and use it in GitHub Desktop.
Save 0xlxy/9327719ef8301636c73c95ec85137c85 to your computer and use it in GitHub Desktop.
sharp.js
const sharp = require("sharp");
const main = async (scale) => {
const originalImage = await sharp("text.png").ensureAlpha().raw();
const { width, height } = await originalImage.metadata();
const image = await originalImage
.resize({
width: Math.floor(width * scale),
height: Math.floor(height * scale),
fit: "contain",
})
.toBuffer({ resolveWithObject: true });
// inflate effect
// const kTop = 0.1,
// kBottom = 0.1,
// kLeft = 0.1,
// kRight = 0.1;
// const distortedImage = applyBarrelDistortion(image, kLeft, kRight, kTop, kBottom);
// arc / arc flip effect
// const curveFactor = 0.05; // for arc flip use positive value, for arc use negetive value */
// const widthExpansionFactor = 0.45;
// const distortedImage = applyArcDistortion(image, curveFactor, widthExpansionFactor);
// arc lower effect
// const curveFactor = 0.6;
// const distortedImage = applyArcLowerDistortion(image, curveFactor);
// perspective skew effect
const kTop = -0.09,
kBottom = 0.09,
kLeft = 0.09,
kRight = -0.09;
const distortedImage = applySkewEffect(image, kRight, kLeft, kTop, kBottom);
await distortedImage
.resize({
width,
height,
fit: "contain",
})
.toFile("outputs.png");
};
main(10);
const applyBarrelDistortion = (srcImage, kLeft, kRight, kTop, kBottom) => {
const { width, height, channels } = srcImage.info;
const centerX = width / 2;
const centerY = height / 2;
const newImage = Buffer.alloc(width * height * channels, 0x00000000);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (width * y + x) * channels;
let deltaX = (x - centerX) / centerX;
let deltaY = (y - centerY) / centerY;
// Apply barrel distortion formula
let kH = x < centerX ? kLeft : kRight;
let kV = y < centerY ? kTop : kBottom;
let deltaR = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
let factorH = 1.0 + kH * Math.pow(deltaR, 2);
let factorV = 1.0 + kV * Math.pow(deltaR, 2);
// Convert back to image coordinates
let sourceX = Math.floor(centerX + deltaX * factorH * centerX);
let sourceY = Math.floor(centerY + deltaY * factorV * centerY);
if (sourceX >= 0 && sourceX < width && sourceY >= 0 && sourceY < height) {
let srcIdx = (width * sourceY + sourceX) * channels;
newImage[idx] = srcImage.data[srcIdx];
newImage[idx + 1] = srcImage.data[srcIdx + 1];
newImage[idx + 2] = srcImage.data[srcIdx + 2];
newImage[idx + 3] = srcImage.data[srcIdx + 3];
}
}
}
return sharp(newImage, { raw: { width, height, channels } }).toFormat("png");
};
const applyArcDistortion = (srcImage, curveFactor, widthExpansionFactor) => {
const { width, height, channels } = srcImage.info;
if (curveFactor > 0) widthExpansionFactor = -(1 / (widthExpansionFactor + 1));
else widthExpansionFactor += 1;
const curveHeight = Math.abs(width * curveFactor);
const newHeight = Math.floor(height + curveHeight);
const newWidth = Math.floor(
width * (widthExpansionFactor > 0 ? widthExpansionFactor : 1 / Math.abs(widthExpansionFactor))
);
const newImage = Buffer.alloc(newWidth * newHeight * channels, 0x00000000);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (width * y + x) * channels;
const newY = y + (curveFactor >= 0 ? 1 : -1) * curveHeight * Math.sin((Math.PI * x) / width);
const adjustedY = curveFactor >= 0 ? newY : newY + curveHeight;
if (adjustedY >= 0 && adjustedY < newHeight) {
let widthScale;
if (widthExpansionFactor > 0) {
widthScale = 1 + ((newHeight - adjustedY) / newHeight) * (widthExpansionFactor - 1);
} else {
widthScale = 1 + (adjustedY / newHeight) * (1 / Math.abs(widthExpansionFactor) - 1);
}
const newX = (x - width / 2) * widthScale + newWidth / 2;
if (newX >= 0 && newX < newWidth) {
const adjustedIdx = (newWidth * Math.floor(adjustedY) + Math.floor(newX)) * channels;
newImage[adjustedIdx] = srcImage.data[idx];
newImage[adjustedIdx + 1] = srcImage.data[idx + 1];
newImage[adjustedIdx + 2] = srcImage.data[idx + 2];
newImage[adjustedIdx + 3] = srcImage.data[idx + 3];
// Interpolation logic
const gapSize = Math.ceil(widthScale) - 1;
for (let i = 1; i <= gapSize; i++) {
const interpolatedX =
widthExpansionFactor > 0 ? Math.floor(newX - i) : Math.floor(newX + i);
if (interpolatedX >= 0 && interpolatedX < newWidth) {
const interpolatedIdx =
(newWidth * Math.floor(adjustedY) +
(widthExpansionFactor > 0 ? interpolatedX + 1 : interpolatedX - 1)) *
channels;
const adjustedIdx = (newWidth * Math.floor(adjustedY) + interpolatedX) * channels;
const blendRatio = i / (gapSize + 1);
newImage[adjustedIdx] = Math.floor(
newImage[interpolatedIdx] * blendRatio + srcImage.data[idx] * (1 - blendRatio)
);
newImage[adjustedIdx + 1] = Math.floor(
newImage[interpolatedIdx + 1] * blendRatio +
srcImage.data[idx + 1] * (1 - blendRatio)
);
newImage[adjustedIdx + 2] = Math.floor(
newImage[interpolatedIdx + 2] * blendRatio +
srcImage.data[idx + 2] * (1 - blendRatio)
);
newImage[adjustedIdx + 3] = 255;
}
}
}
}
}
}
return sharp(newImage, {
raw: { width: newWidth, height: newHeight, channels },
}).toFormat("png");
};
const applyArcLowerDistortion = (srcImage, curveFactor) => {
const { width, height, channels } = srcImage.info;
const curveHeight = Math.abs(width * curveFactor);
const newHeight = Math.floor(height + curveHeight);
const newImage = Buffer.alloc(width * newHeight * channels, 0x00000000);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (width * y + x) * channels;
const increasingFactor = y / height;
const newY =
y +
(curveFactor >= 0 ? 1 : -1) *
curveHeight *
increasingFactor *
Math.sin((Math.PI * x) / width);
const adjustedY = curveFactor >= 0 ? newY : newY + curveHeight;
if (adjustedY >= 0 && adjustedY < newHeight) {
const adjustedIdx = (width * Math.floor(adjustedY) + x) * channels;
newImage[adjustedIdx] = srcImage.data[idx];
newImage[adjustedIdx + 1] = srcImage.data[idx + 1];
newImage[adjustedIdx + 2] = srcImage.data[idx + 2];
newImage[adjustedIdx + 3] = srcImage.data[idx + 3];
if (Math.floor(adjustedY) + 1 < newHeight) {
const nextIdx = (width * (y + 1) + x) * channels;
const adjustedIdx = (width * (Math.floor(adjustedY) + 1) + x) * channels;
newImage[adjustedIdx] = (srcImage.data[idx] + srcImage.data[nextIdx]) / 2;
newImage[adjustedIdx + 1] = (srcImage.data[idx + 1] + srcImage.data[nextIdx + 1]) / 2;
newImage[adjustedIdx + 2] = (srcImage.data[idx + 2] + srcImage.data[nextIdx + 2]) / 2;
newImage[adjustedIdx + 3] = 255;
}
}
}
}
return sharp(newImage, {
raw: { width, height: newHeight, channels },
}).toFormat("png");
};
const applySkewEffect = (srcImage, kRight, kLeft, kTop, kBottom) => {
const { width, height, channels } = srcImage.info;
const rightSkewAmount = Math.abs(kRight) * width;
const leftSkewAmount = Math.abs(kLeft) * width;
const topSkewAmount = Math.abs(kTop) * height;
const bottomSkewAmount = Math.abs(kBottom) * height;
const newImage = Buffer.alloc(width * height * channels, 0x00000000);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let rightSkewRatio = (height - y) / height;
let leftSkewRatio = y / height;
let topSkewRatio = x / width;
let bottomSkewRatio = (width - x) / width;
if (kRight < 0) rightSkewRatio = y / height;
if (kLeft < 0) leftSkewRatio = (height - y) / height;
if (kTop < 0) topSkewRatio = (width - x) / width;
if (kBottom < 0) bottomSkewRatio = x / width;
const rightSkewEffect = rightSkewRatio * rightSkewAmount * (x / width);
const leftSkewEffect = leftSkewRatio * leftSkewAmount * ((width - x) / width);
const topSkewEffect = topSkewRatio * topSkewAmount * ((height - y) / height);
const bottomSkewEffect = bottomSkewRatio * bottomSkewAmount * (y / height);
const newX = Math.round(x - rightSkewEffect + leftSkewEffect);
const newY = Math.round(y + topSkewEffect - bottomSkewEffect);
const idx = (width * y + x) * channels;
const newIdx = (width * newY + newX) * channels;
newImage[newIdx] = srcImage.data[idx];
newImage[newIdx + 1] = srcImage.data[idx + 1];
newImage[newIdx + 2] = srcImage.data[idx + 2];
newImage[newIdx + 3] = srcImage.data[idx + 3];
}
}
return sharp(newImage, { raw: { width, height, channels } }).toFormat("png");
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment