/magic.luau Secret
Last active
December 8, 2024 23:07
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
local Magic = {} | |
-----------------------------------------------------FILTERS-------------------------------------------------- | |
local function mks2013(x) | |
if x < 0 then | |
x = -x | |
end | |
if x >= 2.5 then | |
return 0 | |
end | |
if x >= 1.5 then | |
return -0.125 * (x - 2.5) * (x - 2.5) | |
end | |
if x >= 0.5 then | |
return 0.25 * (4 * x * x - 11 * x + 7) | |
end | |
return 1.0625 - 1.75 * x * x | |
end | |
--local function lanczos3(x) | |
-- if x < 0 then | |
-- x = -x | |
-- end | |
-- if x >= 3.0 then | |
-- return 0 | |
-- end | |
-- if x < 1.19209290E-07 then | |
-- return 1 | |
-- end | |
-- local xPi = x * math.pi | |
-- return (math.sin(xPi) / xPi) * math.sin(xPi / 3) / (xPi / 3) | |
--end | |
local function toFixedPoint(num) | |
return math.round(num * (bit32.lshift(1, 14) - 1)) | |
end | |
local function createFiltersMks(srcSize, destSize, scale) | |
local scaleInverted = 1 / scale | |
local scaleClamped = math.min(1.0, scale) | |
local srcWindow = 2.5 / scaleClamped | |
local destPixel, srcPixel, srcFirst, srcLast, filterElementSize, floatFilter, fxpFilter, total, pxl, idx, floatVal, filterTotal, filterVal | |
local leftNotEmpty, rightNotEmpty, filterShift, filterSize | |
local maxFilterElementSize = math.floor((srcWindow + 1) * 2) | |
local packedFilter = buffer.create(((maxFilterElementSize + 2) * destSize) * 2) | |
local packedFilterPtr = 0 | |
for destPixel = 0, destSize - 1 do | |
srcPixel = (destPixel + 0.5) * scaleInverted | |
srcFirst = math.max(0, math.floor(srcPixel - srcWindow)) | |
srcLast = math.min(srcSize - 1, math.ceil(srcPixel + srcWindow)) | |
filterElementSize = srcLast - srcFirst + 1 | |
floatFilter = buffer.create(filterElementSize * 4) | |
fxpFilter = buffer.create(filterElementSize * 2) | |
total = 0 | |
for pxl = srcFirst, srcLast do | |
floatVal = mks2013(((pxl + 0.5) - srcPixel) * scaleClamped) | |
total += floatVal | |
buffer.writef32(floatFilter, (pxl - srcFirst) * 4, floatVal) | |
end | |
filterTotal = 0 | |
for idx = 0, filterElementSize - 1 do | |
filterVal = buffer.readf32(floatFilter, idx * 4) / total | |
filterTotal += filterVal | |
buffer.writei16(fxpFilter, idx * 2, toFixedPoint(filterVal)) | |
end | |
pcall(function() | |
buffer.writei16(fxpFilter, bit32.rshift(destSize, 1) * 2, buffer.readi16(fxpFilter, bit32.rshift(destSize, 1) * 2) + toFixedPoint(1 - filterTotal)) | |
end) | |
leftNotEmpty = 0 | |
while leftNotEmpty < filterElementSize and buffer.readi16(fxpFilter, leftNotEmpty * 2) == 0 do | |
leftNotEmpty += 1 | |
end | |
if leftNotEmpty < filterElementSize then | |
rightNotEmpty = filterElementSize - 1 | |
while rightNotEmpty > 0 and buffer.readi16(fxpFilter, rightNotEmpty * 2) == 0 do | |
rightNotEmpty -= 1 | |
end | |
filterShift = srcFirst + leftNotEmpty | |
filterSize = rightNotEmpty - leftNotEmpty + 1 | |
buffer.writei16(packedFilter, packedFilterPtr * 2, filterShift) | |
packedFilterPtr += 1 | |
buffer.writei16(packedFilter, packedFilterPtr * 2, filterSize) | |
packedFilterPtr += 1 | |
for idx = leftNotEmpty, rightNotEmpty do | |
buffer.writei16(packedFilter, packedFilterPtr * 2, buffer.readi16(fxpFilter, idx * 2)) | |
packedFilterPtr += 1 | |
end | |
else | |
buffer.writei16(packedFilter, packedFilterPtr * 2, 0) | |
packedFilterPtr += 1 | |
buffer.writei16(packedFilter, packedFilterPtr * 2, 0) | |
packedFilterPtr += 1 | |
end | |
end | |
return packedFilter | |
end | |
-----------------------------------------------------CONVOLVE------------------------------------------------------------- | |
local function clampNegative(i) | |
if i >= 0 then | |
return i | |
end | |
return 0 | |
end | |
local function clampTo8(i) | |
if i < 0 then | |
return 0 | |
end | |
if i > 255 then | |
return 255 | |
end | |
return i | |
end | |
local function convolveHor(src, dest, srcW, srcH, destW, filters) | |
local r, g, b, a | |
local filterPtr, filterShift, filterSize | |
local srcPtr, srcY, destX, filterVal | |
local srcOffset, destOffset = 0, 0 | |
for srcY = 0, srcH - 1 do | |
filterPtr = 0 | |
for destX = 0, destW - 1 do | |
filterShift = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
filterSize = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
srcPtr = math.modf(srcOffset + (filterShift * 4)) | |
r, g, b, a = 0, 0, 0, 0 | |
for filterSize = filterSize, 1, -1 do | |
filterVal = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
a = math.modf(a + filterVal * buffer.readu8(src, srcPtr + 3)) | |
b = math.modf(b + filterVal * buffer.readu8(src, srcPtr + 2)) | |
g = math.modf(g + filterVal * buffer.readu8(src, srcPtr + 1)) | |
r = math.modf(r + filterVal * buffer.readu8(src, srcPtr)) | |
srcPtr = math.modf(srcPtr + 4) | |
end | |
buffer.writeu16(dest, destOffset * 2 + 6, clampNegative(bit32.rshift(a, 7))) | |
buffer.writeu16(dest, destOffset * 2 + 4, clampNegative(bit32.rshift(b, 7))) | |
buffer.writeu16(dest, destOffset * 2 + 2, clampNegative(bit32.rshift(g, 7))) | |
buffer.writeu16(dest, destOffset * 2, clampNegative(bit32.rshift(r, 7))) | |
destOffset = math.modf(destOffset + srcH * 4) | |
end | |
destOffset = math.modf((srcY + 1) * 4) | |
srcOffset = math.modf((srcY + 1) * srcW * 4) | |
end | |
end | |
local function convolveVert(src, dest, srcW, srcH, destW, filters) | |
local r, g, b, a | |
local filterPtr, filterShift, filterSize | |
local srcPtr, srcY, destX, filterVal | |
local srcOffset, destOffset = 0, 0 | |
for srcY = 0, srcH - 1 do | |
filterPtr = 0 | |
for destX = 0, destW - 1 do | |
filterShift = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
filterSize = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
srcPtr = math.modf(srcOffset + (filterShift * 4)) | |
r, g, b, a = 0, 0, 0 ,0 | |
for filterSize = filterSize, 1, -1 do | |
filterVal = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
a = math.modf(a + filterVal * buffer.readu16(src, srcPtr * 2 + 6)) | |
b = math.modf(b + filterVal * buffer.readu16(src, srcPtr * 2 + 4)) | |
g = math.modf(g + filterVal * buffer.readu16(src, srcPtr * 2 + 2)) | |
r = math.modf(r + filterVal * buffer.readu16(src, srcPtr * 2)) | |
srcPtr = math.modf(srcPtr + 4) | |
end | |
r = bit32.rshift(r, 7) | |
g = bit32.rshift(g, 7) | |
b = bit32.rshift(b, 7) | |
a = bit32.rshift(a, 7) | |
buffer.writeu8(dest, destOffset + 3, clampTo8(bit32.rshift(a + bit32.lshift(1, 13), 14))) | |
buffer.writeu8(dest, destOffset + 2, clampTo8(bit32.rshift(b + bit32.lshift(1, 13), 14))) | |
buffer.writeu8(dest, destOffset + 1, clampTo8(bit32.rshift(g + bit32.lshift(1, 13), 14))) | |
buffer.writeu8(dest, destOffset, clampTo8(bit32.rshift(r + bit32.lshift(1, 13), 14))) | |
destOffset = math.modf(destOffset + srcH * 4) | |
end | |
destOffset = math.modf((srcY + 1) * 4) | |
srcOffset = math.modf((srcY + 1) * srcW * 4) | |
end | |
end | |
------------------------------------------ FOR ALPHA IMAGES, NOT FOR CAT ----------------------------------------------------- | |
local function convolveHorWithPre(src, dest, srcW, srcH, destW, filters) | |
local r, g, b, a, alpha | |
local filterPtr, filterShift, filterSize | |
local srcPtr, srcY, destX, filterVal | |
local srcOffset, destOffset = 0, 0 | |
for srcY = 0, srcH - 1 do | |
filterPtr = 0 | |
for destX = 0, destW - 1 do | |
filterShift = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
filterSize = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
srcPtr = math.modf(srcOffset + (filterShift * 4)) | |
r, g, b, a = 0, 0, 0, 0 | |
for filterSize = filterSize, 1, -1 do | |
filterVal = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
alpha = buffer.readu8(src, srcPtr + 3) | |
a = math.modf(a + filterVal * alpha) | |
b = math.modf(b + filterVal * buffer.readu8(src, srcPtr + 2) * alpha) | |
g = math.modf(g + filterVal * buffer.readu8(src, srcPtr + 1) * alpha) | |
r = math.modf(r + filterVal * buffer.readu8(src, srcPtr) * alpha) | |
srcPtr = math.modf(srcPtr + 4) | |
end | |
b = math.modf(b / 255) | |
g = math.modf(g / 255) | |
r = math.modf(r / 255) | |
buffer.writeu16(dest, destOffset * 2 + 6, clampNegative(bit32.rshift(a, 7))) | |
buffer.writeu16(dest, destOffset * 2 + 4, clampNegative(bit32.rshift(b, 7))) | |
buffer.writeu16(dest, destOffset * 2 + 2, clampNegative(bit32.rshift(g, 7))) | |
buffer.writeu16(dest, destOffset * 2, clampNegative(bit32.rshift(r, 7))) | |
destOffset = math.modf(destOffset + srcH * 4) | |
end | |
destOffset = math.modf((srcY + 1) * 4) | |
srcOffset = math.modf((srcY + 1) * srcW * 4) | |
end | |
end | |
local function convolveVertWithPre(src, dest, srcW, srcH, destW, filters) | |
local r, g, b, a | |
local filterPtr, filterShift, filterSize | |
local srcPtr, srcY, destX, filterVal | |
local srcOffset, destOffset = 0, 0 | |
for srcY = 0, srcH - 1 do | |
filterPtr = 0 | |
for destX = 0, destW - 1 do | |
filterShift = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
filterSize = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
srcPtr = math.modf(srcOffset + (filterShift * 4)) | |
r, g, b, a = 0, 0, 0, 0 | |
for filterSize = filterSize, 1, -1 do | |
filterVal = buffer.readi16(filters, filterPtr * 2) | |
filterPtr += 1 | |
a = math.modf(a + filterVal * buffer.readu16(src, srcPtr * 2 + 6)) | |
b = math.modf(b + filterVal * buffer.readu16(src, srcPtr * 2 + 4)) | |
g = math.modf(g + filterVal * buffer.readu16(src, srcPtr * 2 + 2)) | |
r = math.modf(r + filterVal * buffer.readu16(src, srcPtr * 2)) | |
srcPtr = math.modf(srcPtr + 4) | |
end | |
r = bit32.rshift(r, 7) | |
g = bit32.rshift(g, 7) | |
b = bit32.rshift(b, 7) | |
a = bit32.rshift(a, 7) | |
a = clampTo8(bit32.rshift(a + bit32.lshift(1, 13), 14)) | |
if a > 0 then | |
r = math.modf(r * 255 / a) | |
g = math.modf(g * 255 / a) | |
b = math.modf(b * 255 / a) | |
end | |
buffer.writeu8(dest, destOffset + 3, a) | |
buffer.writeu8(dest, destOffset + 2, clampTo8(bit32.rshift(b + bit32.lshift(1, 13), 14))) | |
buffer.writeu8(dest, destOffset + 1, clampTo8(bit32.rshift(g + bit32.lshift(1, 13), 14))) | |
buffer.writeu8(dest, destOffset, clampTo8(bit32.rshift(r + bit32.lshift(1, 13), 14))) | |
destOffset = math.modf(destOffset + srcH * 4) | |
end | |
destOffset = math.modf((srcY + 1) * 4) | |
srcOffset = math.modf((srcY + 1) * srcW * 4) | |
end | |
end | |
-------------------------------------MAIN FUNCTION--------------------------------------------------------------------- | |
local function resetAlpha(dst, width, height) | |
local ptr, len = 3, math.modf(width * height * 4) | |
while ptr < len do | |
buffer.writeu8(dst, ptr, 0xFF) | |
ptr = math.modf(ptr + 4) | |
end | |
end | |
local function hasAlpha(src, width, height) | |
local ptr, len = 3, math.modf(width * height * 4) | |
while ptr < len do | |
if buffer.readu8(src, ptr) ~= 255 then | |
return true | |
end | |
ptr = math.modf(ptr + 4) | |
end | |
return false | |
end | |
function Magic:resize(src, width, height, toWidth, toHeight) | |
local scaleX = toWidth / width | |
local scaleY = toHeight / height | |
local dest = buffer.create(toWidth * toHeight * 4) | |
local filtersX = createFiltersMks(width, toWidth, scaleX) | |
local filtersY = createFiltersMks(height, toHeight, scaleY) | |
local tmp = buffer.create(toWidth * height * 4 * 2) | |
if hasAlpha(src, width, height) then | |
convolveHorWithPre(src, tmp, width, height, toWidth, filtersX) | |
convolveVertWithPre(tmp, dest, height, toWidth, toHeight, filtersY) | |
else | |
convolveHor(src, tmp, width, height, toWidth, filtersX) | |
convolveVert(tmp, dest, height, toWidth, toHeight, filtersY) | |
resetAlpha(dest, toWidth, toHeight) | |
end | |
return dest | |
end | |
return Magic |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment