Skip to content

Instantly share code, notes, and snippets.

@transitive-bullshit
Last active September 16, 2024 04:35
Show Gist options
  • Save transitive-bullshit/37bac5e741eaec60e983 to your computer and use it in GitHub Desktop.
Save transitive-bullshit/37bac5e741eaec60e983 to your computer and use it in GitHub Desktop.
Angular service to resize images with antialiasing for use with canvas.
angular.module('demo').service('imageService', function ($http, $q, $timeout) {
var NUM_LOBES = 3
var lanczos = lanczosGenerator(NUM_LOBES)
// resize via lanczos-sinc convolution
this.resize = function (img, width, height) {
var self = { }
self.type = "image/png"
self.quality = 1.0
self.resultD = $q.defer()
self.canvas = document.createElement('canvas')
self.ctx = getContext(self.canvas)
self.ctx.imageSmoothingEnabled = true
self.ctx.mozImageSmoothingEnabled = true
self.ctx.oImageSmoothingEnabled = true
self.ctx.webkitImageSmoothingEnabled = true
if (img.naturalWidth <= width || img.naturalHeight <= height) {
console.log("FAST resizing image", img.naturalWidth, img.naturalHeight, "=>", width, height)
self.canvas.width = width
self.canvas.height = height
self.ctx.drawImage(img, 0, 0, width, height)
resolveLanczos(self)
} else {
console.log("SLOW resizing image", img.naturalWidth, img.naturalHeight, "=>", width, height)
self.canvas.width = img.naturalWidth
self.canvas.height = img.naturalHeight
self.ctx.drawImage(img, 0, 0, self.canvas.width, self.canvas.height)
self.img = img
self.src = self.ctx.getImageData(0, 0, self.canvas.width, self.canvas.height)
self.dest = {
width: width,
height: height
}
self.dest.data = new Array(self.dest.width * self.dest.height * 4)
self.ratio = img.naturalWidth / width
self.rcpRatio = 2 / self.ratio
self.range2 = Math.ceil(self.ratio * NUM_LOBES / 2)
self.cacheLanc = {}
self.center = {}
self.icenter = {}
$timeout(function () { applyLanczosColumn(self, 0) })
}
return self.resultD.promise
}
function applyLanczosColumn (self, u) {
self.center.x = (u + 0.5) * self.ratio
self.icenter.x = self.center.x | 0
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio
self.icenter.y = self.center.y | 0
var a, r, g, b
a = r = g = b = 0
var norm = 0
var idx
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width) continue
var fX = (1000 * Math.abs(i - self.center.x)) | 0
if (!self.cacheLanc[fX]) {
self.cacheLanc[fX] = {}
}
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height) continue
var fY = (1000 * Math.abs(j - self.center.y)) | 0
if (self.cacheLanc[fX][fY] === undefined) {
self.cacheLanc[fX][fY] = lanczos(Math.sqrt(Math.pow(fX * self.rcpRatio, 2) + Math.pow(fY * self.rcpRatio, 2)) / 1000)
}
var weight = self.cacheLanc[fX][fY]
if (weight > 0) {
idx = (j * self.src.width + i) * 4
norm += weight
r += weight * self.src.data[idx + 0]
g += weight * self.src.data[idx + 1]
b += weight * self.src.data[idx + 2]
a += weight * self.src.data[idx + 3]
}
}
}
idx = (v * self.dest.width + u) * 4
self.dest.data[idx + 0] = r / norm
self.dest.data[idx + 1] = g / norm
self.dest.data[idx + 2] = b / norm
self.dest.data[idx + 3] = a / norm
}
if (++u < self.dest.width) {
if (u % 16 === 0) {
$timeout(function () { applyLanczosColumn(self, u) })
} else {
applyLanczosColumn(self, u)
}
} else {
$timeout(function () { finalizeLanczos(self) })
}
}
function finalizeLanczos (self) {
self.canvas.width = self.dest.width
self.canvas.height = self.dest.height
//self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height)
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height)
var idx
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 4
self.src.data[idx + 0] = self.dest.data[idx + 0]
self.src.data[idx + 1] = self.dest.data[idx + 1]
self.src.data[idx + 2] = self.dest.data[idx + 2]
self.src.data[idx + 3] = self.dest.data[idx + 3]
}
}
self.ctx.putImageData(self.src, 0, 0)
resolveLanczos(self)
}
function resolveLanczos (self) {
var result = new Image()
result.onload = function () {
self.resultD.resolve(result)
}
result.onerror = function (err) {
self.resultD.reject(err)
}
result.src = self.canvas.toDataURL(self.type, self.quality)
}
// resize by stepping down
this.resizeStep = function (img, width, height, quality) {
quality = quality || 1.0
var resultD = $q.defer()
var canvas = document.createElement( 'canvas' )
var context = getContext(canvas)
var type = "image/png"
var cW = img.naturalWidth
var cH = img.naturalHeight
var dst = new Image()
var tmp = null
//resultD.resolve(img)
//return resultD.promise
function stepDown () {
cW = Math.max(cW / 2, width) | 0
cH = Math.max(cH / 2, height) | 0
canvas.width = cW
canvas.height = cH
context.drawImage(tmp || img, 0, 0, cW, cH)
dst.src = canvas.toDataURL(type, quality)
if (cW <= width || cH <= height) {
return resultD.resolve(dst)
}
if (!tmp) {
tmp = new Image()
tmp.onload = stepDown
}
tmp.src = dst.src
}
if (cW <= width || cH <= height || cW / 2 < width || cH / 2 < height) {
canvas.width = width
canvas.height = height
context.drawImage(img, 0, 0, width, height)
dst.src = canvas.toDataURL(type, quality)
resultD.resolve(dst)
} else {
stepDown()
}
return resultD.promise
}
function getContext (canvas) {
var context = canvas.getContext('2d')
context.imageSmoothingEnabled = true
context.mozImageSmoothingEnabled = true
context.oImageSmoothingEnabled = true
context.webkitImageSmoothingEnabled = true
return context
}
// returns a function that calculates lanczos weight
function lanczosGenerator (lobes) {
var recLobes = 1.0 / lobes
return function (x) {
if (x > lobes) return 0
x *= Math.PI
if (Math.abs(x) < 1e-16) return 1
var xx = x * recLobes
return Math.sin(x) * Math.sin(xx) / x / xx
}
}
})
angular.module('demo').controller('ExampleCtrl', function (imageService) {
// EXAMPLE USAGE
// NOTE: it's bad practice to access the DOM inside a controller,
// but this is just to show the example usage.
// resize by lanczos-sinc filter
imageService.resize($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
// resize by stepping down image size in increments of 2x
imageService.resizeStep($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
})
@AlexanderTserkovniy
Copy link

AlexanderTserkovniy commented May 3, 2016

Hey!

I am facing strange behaviour when using this solution. It renders me only a line of ~1px from an image, don't you know why is that? In the DOM it takes the whole space of the image, but visible is only one uppest line and everything else is filled with void. Using the resize method.

P.S. The resizeStep works well for me.

@evonsdesigns
Copy link

evonsdesigns commented Jun 13, 2016

ResizeStep works awesome! What about resizing while maintaining aspect ratio? It looks like it stretches to fit the expected size. I ended up using https://github.com/danschumann/limby-resize

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment