Created
January 9, 2024 00:12
-
-
Save 0xlxy/9327719ef8301636c73c95ec85137c85 to your computer and use it in GitHub Desktop.
sharp.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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