Skip to content

Instantly share code, notes, and snippets.

@stengoes
Created March 2, 2020 09:22
Show Gist options
  • Save stengoes/ea52d097256aeb8ec7029e485cefd76c to your computer and use it in GitHub Desktop.
Save stengoes/ea52d097256aeb8ec7029e485cefd76c to your computer and use it in GitHub Desktop.
resizeBicubic
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