Skip to content

Instantly share code, notes, and snippets.

@subblue
Created October 24, 2015 16:16
Show Gist options
  • Save subblue/ceff472f8a2e7e4bfd5e to your computer and use it in GitHub Desktop.
Save subblue/ceff472f8a2e7e4bfd5e to your computer and use it in GitHub Desktop.
Small class for resizing images using the canvas object but by progressively reducing by half to generate a smoother end result
perf = window.performance || Date
class Resizer
constructor: (srcImg, @options = {}) ->
if typeof srcImg == 'string'
@loadImage(srcImg)
if srcImg.toString().indexOf('Blob') > -1
mimetype = Resizer.mimeType(@options.ext || 'jpg')
blob = new Blob([srcImg], type: mimetype)
src = (window.URL || window.webkitURL).createObjectURL(blob)
@loadImage(src)
else
@source = srcImg#.cloneNode()
@setOutputSize()
loadImage: (src) ->
@source = new Image()
@source.crossOrigin = ''
@source.onload = =>
# console.log 'Loaded image'
@setOutputSize()
@source.onerror = (error) =>
console.error 'Load error', error
@source.src = src
setOutputSize: ->
@options.scheme ?= 'fit'
@sw = @source.width # source width
@sh = @source.height # source height
@sr = @sw / @sh # source aspect ratio
@tw = @options.width || 512 # target width
@th = @options.height || 512 # target height
@tr = @tw / @th # target aspect ratio
@ox = 0 # offset
@oy = 0
if @options.scheme == 'pot'
# Resize to nearest power of two dimensions
wp = Math.log(@sw) / Math.log(2)
# If we are just under the 2^n size then round up, otherwise round down
wp = if wp % 1 > 0.95 then Math.ceil(wp) else Math.floor(wp)
@tw = @dw = Math.pow(2, wp)
# recalculate the height based on the pow 2 width
@th = @tw / @sr
hp = Math.log(@th) / Math.log(2)
hp = if hp % 1 > 0.95 then Math.ceil(hp) else Math.floor(hp)
@th = @dh = Math.pow(2, hp)
@options.fit = false
@options.fill = false
else if @options.scheme == 'fit'
if @tr >= @sr
# scale to height
@dh = @th
@dw = Math.floor(@dh * @sr)
@tw = @dw
else
# scale to width
@dw = @tw
@dh = Math.floor(@dw / @sr)
@th = @dh
else if @options.scheme == 'fill'
# fill
if @tr >= @sr
# scale to width
@dw = @tw
@dh = Math.floor(@dw / @sr)
@oy = -Math.floor((@dh - @th) / 2)
else
# scale to height
@dh = @th
@dw = Math.floor(@dh * @sr)
@ox = -Math.floor((@dw - @tw) / 2)
else
# stretch fit
@dw = @tw
@dh = @th
@resize()
resize: ->
# console.log "Resize - sw: #{@sw}, sh: #{@sh}, tw: #{@tw}, th: #{@th}, dw: #{@dw}, dh: #{@dh}"
w = @sw
h = @sh
buffer = @source
@steps = 0
t0 = perf.now()
# To maintain best reduction quality we progressively resize by half
# until we are close to the final size
while Math.min(w / @dw, h / @dh) > 2
@steps += 1
w = Math.floor(w / 2)
h = Math.floor(h / 2)
canvas = @getCanvas()
canvas.width = w
canvas.height = h
canvas.getContext('2d').drawImage(buffer, 0, 0, w, h)
buffer = canvas
# final positioning
@output = @getCanvas()
@output.width = @tw
@output.height = @th
@output.getContext('2d').drawImage(buffer, 0, 0, buffer.width, buffer.height, @ox, @oy, @dw, @dh)
@time = (perf.now() - t0).toFixed(2)
@options.callback(@) if @options.callback
@output
getCanvas: ->
document.createElement('canvas')
info: ->
"Source #{@sw}x#{@sh} resized to #{@tw}x#{@th} with #{@steps} steps via #{@options.scheme} in #{@time}ms"
export: (format = 'jpg', quality = 85) ->
@output.toDataURL(Resizer.mimeType(format), quality)
toBlob: (callback, format = 'jpg', quality) ->
@output.toBlob(callback, Resizer.mimeType(format), quality)
@mimeType: (ext = 'jpg') ->
mimeTypes =
'jpeg': 'image/jpeg'
'jpg': 'image/jpeg'
'png': 'image/png'
'gif': 'image/gif'
mimeTypes[ext] || mimeTypes['jpg']
module.exports = Resizer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment