Created
March 2, 2020 09:22
-
-
Save stengoes/ea52d097256aeb8ec7029e485cefd76c to your computer and use it in GitHub Desktop.
resizeBicubic
This file contains hidden or 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
function resizeBicubic(image, newHeight, newWidth) { | |
/* Helper functions */ | |
function extrapolateImageBoundaries(image) { | |
const height = image.length; | |
const width = image[0].length; | |
// Linearly extrapolate top and bottom of the image | |
image[-2] = []; | |
image[-1] = []; | |
image[height] = []; | |
image[height + 1] = []; | |
for (var x = 0; x < width; x++) { | |
// Top | |
const topDelta = image[0][x] - image[1][x]; | |
image[-2][x] = image[0][x] + 2 * topDelta; | |
image[-1][x] = image[0][x] + topDelta; | |
// Bottom | |
const bottomDelta = image[height - 1][x] - image[height - 2][x]; | |
image[height][x] = image[height - 1][x] + bottomDelta; | |
image[height + 1][x] = image[height - 1][x] + 2 * bottomDelta; | |
} | |
// Linearly extrapolate left and right of the image | |
for (var y = -2; y < height + 2; y++) { | |
// Left | |
const leftDelta = image[y][0] - image[y][1]; | |
image[y][-2] = image[y][0] + 2 * leftDelta; | |
image[y][-1] = image[y][0] + leftDelta; | |
// Right | |
const rightDelta = image[y][width - 1] - image[y][width - 2]; | |
image[y][width] = image[y][width - 1] + rightDelta; | |
image[y][width + 1] = image[y][width - 1] + 2 * rightDelta; | |
} | |
return image; | |
} | |
function createBicubicInterpolator(values) | |
{ | |
// Bicubic coefficients (see https://en.wikipedia.org/wiki/Bicubic_interpolation) | |
const a00 = values[1][1], | |
a01 = (-1 / 2) * values[1][0] + (1 / 2) * values[1][2], | |
a02 = values[1][0] + (-5 / 2) * values[1][1] + 2 * values[1][2] + (-1 / 2) * values[1][3], | |
a03 = (-1 / 2) * values[1][0] + (3 / 2) * values[1][1] + (-3 / 2) * values[1][2] + (1 / 2) * values[1][3], | |
a10 = (-1 / 2) * values[0][1] + (1 / 2) * values[2][1], | |
a11 = (1 / 4) * values[0][0] + (-1 / 4) * values[0][2] + (-1 / 4) * values[2][0] + (1 / 4) * values[2][2], | |
a12 = (-1 / 2) * values[0][0] + (5 / 4) * values[0][1] + (-1) * values[0][2] + (1 / 4) * values[0][3] + (1 / 2) * values[2][0] + (-5 / 4) * values[2][1] + values[2][2] + (-1 / 4) * values[2][3], | |
a13 = (1 / 4) * values[0][0] + (-3 / 4) * values[0][1] + (3 / 4) * values[0][2] + (-1 / 4) * values[0][3] + (-1 / 4) * values[2][0] + (3 / 4) * values[2][1] + (-3 / 4) * values[2][2] + (1 / 4) * values[2][3], | |
a20 = values[0][1] + (-5 / 2) * values[1][1] + 2 * values[2][1] + (-1 / 2) * values[3][1], | |
a21 = (-1 / 2) * values[0][0] + (1 / 2) * values[0][2] + (5 / 4) * values[1][0] + (-5 / 4) * values[1][2] + (-1) * values[2][0] + values[2][2] + (1 / 4) * values[3][0] + (-1 / 4) * values[3][2], | |
a22 = values[0][0] + (-5 / 2) * values[0][1] + 2 * values[0][2] + (-1 / 2) * values[0][3] + (-5 / 2) * values[1][0] + (25 / 4) * values[1][1] + (-5) * values[1][2] + (5 / 4) * values[1][3] + 2 * values[2][0] + (-5) * values[2][1] + 4 * values[2][2] + (-1) * values[2][3] + (-1 / 2) * values[3][0] + (5 / 4) * values[3][1] + (-1) * values[3][2] + (1 / 4) * values[3][3], | |
a23 = (-1 / 2) * values[0][0] + (3 / 2) * values[0][1] + (-3 / 2) * values[0][2] + (1 / 2) * values[0][3] + (5 / 4) * values[1][0] + (-15 / 4) * values[1][1] + (15 / 4) * values[1][2] + (-5 / 4) * values[1][3] + (-1) * values[2][0] + 3 * values[2][1] + (-3) * values[2][2] + values[2][3] + (1 / 4) * values[3][0] + (-3 / 4) * values[3][1] + (3 / 4) * values[3][2] + (-1 / 4) * values[3][3], | |
a30 = (-1 / 2) * values[0][1] + (3 / 2) * values[1][1] + (-3 / 2) * values[2][1] + (1 / 2) * values[3][1], | |
a31 = (1 / 4) * values[0][0] + (-1 / 4) * values[0][2] + (-3 / 4) * values[1][0] + (3 / 4) * values[1][2] + (3 / 4) * values[2][0] + (-3 / 4) * values[2][2] + (-1 / 4) * values[3][0] + (1 / 4) * values[3][2], | |
a32 = (-1 / 2) * values[0][0] + (5 / 4) * values[0][1] + (-1) * values[0][2] + (1 / 4) * values[0][3] + (3 / 2) * values[1][0] + (-15 / 4) * values[1][1] + 3 * values[1][2] + (-3 / 4) * values[1][3] + (-3 / 2) * values[2][0] + (15 / 4) * values[2][1] + (-3) * values[2][2] + (3 / 4) * values[2][3] + (1 / 2) * values[3][0] + (-5 / 4) * values[3][1] + values[3][2] + (-1 / 4) * values[3][3], | |
a33 = (1 / 4) * values[0][0] + (-3 / 4) * values[0][1] + (3 / 4) * values[0][2] + (-1 / 4) * values[0][3] + (-3 / 4) * values[1][0] + (9 / 4) * values[1][1] + (-9 / 4) * values[1][2] + (3 / 4) * values[1][3] + (3 / 4) * values[2][0] + (-9 / 4) * values[2][1] + (9 / 4) * values[2][2] + (-3 / 4) * values[2][3] + (-1 / 4) * values[3][0] + (3 / 4) * values[3][1] + (-3 / 4) * values[3][2] + (1 / 4) * values[3][3]; | |
return (y, x) => { | |
// Check for valid range | |
if (y < 0 || y > 1 || x < 0 || x > 1) | |
throw "Error: cannot interpolate outside the unit square from (0, 0) to (1, 1). Got y =" + y + ", x=" + x + "."; | |
const y2 = y * y; | |
const y3 = y * y2; | |
const x2 = x * x; | |
const x3 = x * x2; | |
return (a00 + a01 * x + a02 * x2 + a03 * x3) + | |
(a10 + a11 * x + a12 * x2 + a13 * x3) * y + | |
(a20 + a21 * x + a22 * x2 + a23 * x3) * y2 + | |
(a30 + a31 * x + a32 * x2 + a33 * x3) * y3; | |
} | |
} | |
function createGridBicubicInterpolator(image, scaleY, scaleX, translateY, translateX) | |
{ | |
// Get image dimensions | |
const height = image.height; | |
const width = image.width; | |
// Create a grid of interpolators | |
const grid = []; | |
for (var y = -1; y < height; y++) | |
grid[y] = []; | |
return (y, x) => { | |
// Scale and translate y,x coordinates | |
y = (y * scaleY) + translateY; | |
x = (x * scaleX) + translateX; | |
// Check for valid range | |
if (y < -1 || y > height || x < -1 || x > width) | |
throw "Error: cannot interpolate outside the rectangle from (-1, -1) to (" + height + ", " + width + "). Got y =" + y + ", x=" + x + "."; | |
// Find the right grid cell | |
var y0 = Math.floor(y); | |
if (y === height) y0--; | |
var x0 = Math.floor(x); | |
if (x === width) x0--; | |
// Create only new interpolater when not already in grid (CACHE)! | |
if (!grid[y0][x0]) | |
{ | |
function val(y, x) | |
{ | |
// Take some extra care near the boundaries | |
if(y < 0) | |
{ | |
const dy = (0 - y); | |
// Top | |
if(x < 0) | |
{ | |
// Top left | |
const dx = (0 - x); | |
const dvaldx = image.data[0 * width + 1] - image.data[0 * width + 0]; | |
const dvaldy = image.data[1 * width + 0] - image.data[0 * width + 0]; | |
return image.data[0 * width + 0] - dx * dvaldx - dy * dvaldy; | |
} | |
else if(x >= width) | |
{ | |
// Top right | |
const dx = (x - width + 1); | |
const dvaldx = image.data[0 * width + width-1] - image.data[0 * width + width-2]; | |
const dvaldy = image.data[1 * width + width-1] - image.data[0 * width + width-1]; | |
return image.data[0 * width + width-1] - dx * dvaldx - dy * dvaldy; | |
} | |
else | |
{ | |
// Top center | |
const dval = image.data[1 * width + x] - image.data[0 * width + x]; | |
return image.data[0 * width + x] - dy * dval; | |
} | |
} | |
else if(y >= height) | |
{ | |
const dy = (y - height + 1); | |
// Bottom | |
if(x < 0) | |
{ | |
// Bottom left | |
const dx = (0 - x); | |
const dvaldx = image.data[(height-1) * width + 1] - image.data[(height-1) * width + 0]; | |
const dvaldy = image.data[(height-1) * width + 0] - image.data[(height-2) * width + 0]; | |
return image.data[(height-1) * width + 0] - dx * dvaldx - dy * dvaldy; | |
} | |
else if(x >= width) | |
{ | |
// Bottom right | |
const dx = (x - width + 1); | |
const dvaldx = image.data[(height-1) * width + width-1] - image.data[(height-1) * width + width-2]; | |
const dvaldy = image.data[(height-1) * width + width-1] - image.data[(height-2) * width + width-1]; | |
return image.data[(height-1) * width + width-1] - dx * dvaldx - dy * dvaldy; | |
} | |
else | |
{ | |
// Bottom center | |
const dvaldy = image.data[(height-1) * width + x] - image.data[(height-2) * width + x]; | |
return image.data[(height-1) * width + x] - dy * dvaldy; | |
} | |
} | |
else | |
{ | |
// Middle | |
if(x < 0) | |
{ | |
// Middle left | |
const dx = (0 - x); | |
const dval = image.data[y * width + 1] - image.data[y * width + 0]; | |
return image.data[y * width + 0] - dx * dval; | |
} | |
else if(x >= width) | |
{ | |
// Middle right | |
const dx = (x - width + 1); | |
const dval = image.data[y * width + width-1] - image.data[y * width + width-2]; | |
return image.data[y * width + width-1] - dx * dval; | |
} | |
else | |
{ | |
// Middle center | |
return image.data[y * width + x]; | |
} | |
} | |
} | |
const values = [ | |
[val(y0 - 1, x0 - 1), val(y0 - 1, x0), val(y0 - 1, x0 + 1), val(y0 - 1, x0 + 2)], | |
[val(y0 + 0, x0 - 1), val(y0 + 0, x0), val(y0 + 0, x0 + 1), val(y0 + 0, x0 + 2)], | |
[val(y0 + 1, x0 - 1), val(y0 + 1, x0), val(y0 + 1, x0 + 1), val(y0 + 1, x0 + 2)], | |
[val(y0 + 2, x0 - 1), val(y0 + 2, x0), val(y0 + 2, x0 + 1), val(y0 + 2, x0 + 2)] | |
]; | |
grid[y0][x0] = createBicubicInterpolator(values); | |
} | |
// Select the interpolator corresponding to that grid cell | |
const interpolator = grid[y0][x0]; | |
// Translate coordinates back to unit square (0, 0) - (1, 1) before returning. | |
return interpolator(y - y0, x - x0); | |
} | |
} | |
// Validate input arguments | |
if (image.depth != 1) | |
throw "Error: resize() does currently only support images with depth = 1. Got depth = " + image.depth + "."; | |
if (image.data.constructor !== Float32Array) | |
throw "Error: image data should be of type Float32Array!"; | |
// Initialize output | |
const newImage = { | |
"height": newHeight, | |
"width": newWidth, | |
"depth": image.depth, | |
"data": new Float32Array(newHeight*newWidth*image.depth) | |
}; | |
// Compute scale and translation needed for interpolation | |
const scaleY = (image.height / newHeight); | |
const scaleX = (image.width / newWidth); | |
const translateY = -0.5 + scaleY / 2; | |
const translateX = -0.5 + scaleX / 2; | |
// Create interpolator | |
const interpolate = createGridBicubicInterpolator(image, scaleY, scaleX, translateY, translateX); | |
// Apply interpolator | |
let idx = 0; | |
for (let y = 0; y < newHeight; y++) | |
for (let x = 0; x < newWidth; x++) | |
newImage.data[idx++] = interpolate(y, x); | |
// Return resized image | |
return newImage; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment