Skip to content

Instantly share code, notes, and snippets.

@neara
Created February 22, 2015 09:43
Show Gist options
  • Select an option

  • Save neara/da2b499a2d94204aee3d to your computer and use it in GitHub Desktop.

Select an option

Save neara/da2b499a2d94204aee3d to your computer and use it in GitHub Desktop.
jQuery resize image plugin
$ = jQuery
# FIXME add support for resize without preview
$.fn.extend
# plugin name
resizeImage: (options) ->
# default settings
settings =
crop: false
cropWidth: 200 # in px
cropHeight: 200 # in px
cropWindowX: 10 # in px, crop window start position
cropWindowY: 10 # in px, crop window start position
cropPreview: null # img element to show crop result
cropBtn: '#cropBtn' # crops from the window and shows in cropPreview element
showPreview: true
imgTypes: /^(image\/jpeg|image\/png)$/i # allowed image types
maxSize: 20971520 # 20MB
maxWidth: 1024 # in px
maxHeight: 1024 # in px
form: $(@).parents('form')
previewContainer: null # will center the image inside this container
previewLabelId: 'imgPreviewLabel'
typeError: ->
sizeError: ->
# merge default settings with options
settings = $.extend settings, options
if settings.showPreview and not settings.imgPreview
throw 'Missing image preview elements'
cropWindow =
x: settings.cropWindowX
y: settings.cropWindowY
w: settings.cropWidth
h: settings.cropHeight
# extra vars to dragging calculations
px: settings.cropWindowX
py: settings.cropWindowY
ratio: 1
dragWhole: false # tracks when user is moving the crop window
# resize corners details
cornerSize: 6
cornerHoverSize: 10
cornersSizes: [6, 6, 6, 6]
cornersHoverState: [false, false, false, false]
cornersDragState: [false, false, false, false]
# tracks mouse movement over the image
iMouseX = 1
iMouseY = 1
originXCropWindow = settings.cropWindowX
originYCropWindow = settings.cropWindowY
form = $(settings.form)[0]
$imgPreview = $(settings.imgPreview)
# setting an id on label and preview element in order to center the image using blImage
if settings.previewContainer and $(settings.previewContainer).attr('id')
settings.previewLabelId = $(settings.previewContainer).attr('id')
else
$imgPreview.parents('label').attr 'id', 'imgPreviewLabel'
imgPreviewId = $imgPreview.attr('id')
if not imgPreviewId
$imgPreview.attr 'id', 'imgPreview'
imgPreviewId = 'imgPreview'
imgPreview = $imgPreview[0]
############ Methods ##########################
calculateRatio = (image, maxWidth, maxHeight, ratio=1) ->
"""
Recursively caclculate ratio for the image
1. Find what is bigger: image.width or image.height
2. Find if either image.width or image.height overflows maxWidth/maxHeigh
3. Calculate ratio based on biggest side (image.width or image.height)
4. Check if new image.width/image.height still overflows maxWidth/maxHeight
4.1 if there is still an overflow, recalculate ratio based on ratio from step 3
4.2 both width and height fit into maxWidth/maxHeight, return current ratio
`Special Case - CUBE` - image.width == image.height
Ratio will be calculated based on image.width, and recalculated as needed
`Special Case - image smaller than maxWidth/Height`
Return ratio will be 1, no calculations will be made.
"""
if image.width * ratio > maxWidth
ratio *= maxWidth / (image.width * ratio)
if image.height * ratio > maxHeight
ratio *= maxHeight / (image.height * ratio)
if image.width * ratio > maxWidth or image.height * ratio > maxHeight
return calculateRatio(image, maxWidth, maxHeight, ratio)
return ratio
detectedIOSDevice = ->
iosDevices = ['iPhone', 'iPad', 'iPod']
for i in iosDevices
if navigator.platform == i
return true
return false
detectedIOSSubsampling = (img) ->
naturalWidth = img.naturalWidth
naturalHeight = img.naturalHeight
if naturalHeight * naturalWidth > 1024 * 1024
canvas = document.createElement('canvas')
canvas.width = canvas.height = 1
ctx = canvas.getContext('2d')
ctx.drawImage(img, -naturalWidth + 1, 0)
return ctx.getImageData(0, 0, 1, 1).data[3] == 0
return false
detectVerticalSquash = (img, height) ->
canvas = document.createElement('canvas')
canvas.width = 1
canvas.height = height
ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
data = ctx.getImageData(0, 0, 1, height).data
startY = 0
endY = height
pointerY = height
while pointerY > startY
alpha = data[(pointerY - 1) * 4 + 3]
if alpha == 0
endY = pointerY
else
startY = pointerY
pointerY = (endY + startY) >> 1 # bynary right shift propagation by 1 bit
ratio = (pointerY / height)
return if ratio == 0 then 1 else ratio
resizeImage = (urlToLocalImage, MAX_WIDTH=settings.maxWidth, MAX_HEIGHT=settings.maxHeight) ->
###
This is a helper function for reducing uploaded image file size
via dimensions (width/height) change. Returns new url to changed image in
jpeg format. Returned url is of the datauri format: `data:<mimetype>;charset=<charset>;base64, ...`
charset is optional
`urlToLocalImage` - url to image in browser buffer
`MAX_WIDTH` - (optional) desired image width, default is 1024px
`MAX_HEIGHT` - (optional) desired image height, default is 1024px
###
maxWidth = MAX_WIDTH
maxHeight = MAX_HEIGHT
img = new Image()
img.src = urlToLocalImage
# calculate ratio
ratio = 1
if detectedIOSDevice()
ratio = detectVerticalSquash(img, img.naturalHeight)
else
ratio = calculateRatio(img, maxWidth, maxHeight)
factor = 1
if detectedIOSSubsampling(img)
factor = 2
canvas = document.createElement('canvas')
canvas.width = Math.ceil(img.width * ratio)
canvas.height = Math.ceil(img.height * ratio)
ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, canvas.width, canvas.height * factor)
dataUrl = canvas.toDataURL('image/jpeg', 0.7)
return dataUrl
appendResizedImgToForm = (url, imgName) ->
###
Appends or updates resized image and image file name to the statusForm
Those fields will be uploaded to server
###
$imgInput = $('input[name=image]')
if $imgInput.length == 0
resizedImg = document.createElement('input')
resizedImg.type = 'hidden'
resizedImg.name = 'image'
resizedImg.value = url
form.appendChild(resizedImg)
else
$imgInput.val(url)
$imgFileName = $('input[name=imageFileName]')
if $imgFileName.length == 0
imgFileName = document.createElement('input')
imgFileName.type = 'hidden'
imgFileName.name = 'imageFileName'
imgFileName.value = imgName
form.appendChild(imgFileName)
else
$imgFileName.val(imgName)
handleResize = (imgURL, imageName) ->
# this function will be fired when any read is done on the file object
# e.target.result contains the DataURL which i will use as a source of the imageURL
url = resizeImage(imgURL)
imgPreview.onload = (event) ->
$("\##{settings.previewLabelId} \##{imgPreviewId}").centerImage('inside')
imgPreview.src = url
appendResizedImgToForm(url, settings.fileName)
drawCropPreview = (image, r=cropWindow.ratio) ->
tmp_canvas = document.createElement('canvas')
tmp_canvas.width = cropWindow.w
tmp_canvas.height = cropWindow.h
tmp_ctx = tmp_canvas.getContext('2d')
tmp_ctx.drawImage(image,
cropWindow.x / r, cropWindow.y / r, cropWindow.w / r, cropWindow.h / r,
0, 0, cropWindow.w, cropWindow.h
)
croppedImgUrl = tmp_canvas.toDataURL('image/jpeg', 0.7)
$(settings.cropPreview).attr('src', croppedImgUrl)
appendResizedImgToForm(croppedImgUrl, settings.fileName)
drawSelectionPanel = (ctx, image) ->
ctx.strokeStyle = '#000'
ctx.lineWidth = 2
ctx.strokeRect(cropWindow.x, cropWindow.y, cropWindow.w, cropWindow.h)
# draw the image in crop window
if cropWindow.w > 0 and cropWindow.h > 0
r = cropWindow.ratio
ctx.drawImage(image,
cropWindow.x / r, cropWindow.y / r, cropWindow.w / r, cropWindow.h / r,
cropWindow.x, cropWindow.y, cropWindow.w, cropWindow.h)
# draw resize corners
ctx.fillStyle = '#fff'
ctx.fillRect(
cropWindow.x - cropWindow.cornersSizes[0],
cropWindow.y - cropWindow.cornersSizes[0],
cropWindow.cornersSizes[0] * 2,
cropWindow.cornersSizes[0] * 2
)
ctx.fillRect(
cropWindow.x + cropWindow.w - cropWindow.cornersSizes[1],
cropWindow.y - cropWindow.cornersSizes[1],
cropWindow.cornersSizes[1] * 2,
cropWindow.cornersSizes[1] * 2
)
ctx.fillRect(
cropWindow.x + cropWindow.w - cropWindow.cornersSizes[2],
cropWindow.y + cropWindow.h - cropWindow.cornersSizes[2],
cropWindow.cornersSizes[2] * 2,
cropWindow.cornersSizes[2] * 2
)
ctx.fillRect(
cropWindow.x - cropWindow.cornersSizes[3],
cropWindow.y + cropWindow.h - cropWindow.cornersSizes[3],
cropWindow.cornersSizes[3] * 2,
cropWindow.cornersSizes[3] * 2
)
drawScene = (ctx, image) ->
# clear canvas, prepare to draw the image with crop window
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
# draw the image on canvas
ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height)
# darken the image to highlight the window
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
ctx.fillRect 0, 0, ctx.canvas.width, ctx.canvas.height
drawSelectionPanel(ctx, image)
handleCrop = (imgURL) ->
$previewContainer = $(settings.previewContainer)
cropWindow.x = originXCropWindow
cropWindow.y = originYCropWindow
image = new Image()
image.onload = (event) ->
cropWindow.ratio = calculateRatio(image, $previewContainer.width(), $previewContainer.height())
if settings.crop and settings.cropPreview
$(settings.cropBtn).click (event) ->
event.preventDefault()
drawCropPreview(image)
canvas = document.createElement('canvas')
canvas.width = Math.ceil(image.width * cropWindow.ratio)
canvas.height = Math.ceil(image.height * cropWindow.ratio)
canvas.id = imgPreviewId
ctx = canvas.getContext('2d')
if canvas.width < cropWindow.w + cropWindow.x
cropWindow.w = canvas.width - cropWindow.x * 2
if canvas.height < cropWindow.h + cropWindow.y
cropWindow.h = canvas.height - cropWindow.y * 2
drawScene(ctx, image)
$previewContainer.empty()
$previewContainer.append(canvas)
$("\##{imgPreviewId}")
.mousemove (event) ->
draw = false
canvasOffset = $("\##{imgPreviewId}").offset()
iMouseX = Math.floor(event.pageX - canvasOffset.left)
iMouseY = Math.floor(event.pageY - canvasOffset.top)
if cropWindow.dragWhole
cropWindow.x = iMouseX - cropWindow.px
cropWindow.y = iMouseY - cropWindow.py
draw = true
# reset resize corners state
for i in [0...cropWindow.cornersSizes.length]
cropWindow.cornersHoverState[i] = false
cropWindow.cornersSizes[i] = cropWindow.cornerSize
# detect hover over resize corners
if iMouseX > cropWindow.x - cropWindow.cornerHoverSize and
iMouseX < cropWindow.x + cropWindow.cornerHoverSize and
iMouseY > cropWindow.y - cropWindow.cornerHoverSize and
iMouseY < cropWindow.y + cropWindow.cornerHoverSize
draw = true
cropWindow.cornersHoverState[0] = true
cropWindow.cornersSizes[0] = cropWindow.cornerHoverSize
if iMouseX > cropWindow.x + cropWindow.w - cropWindow.cornerHoverSize and
iMouseX < cropWindow.x + cropWindow.w + cropWindow.cornerHoverSize and
iMouseY > cropWindow.y - cropWindow.cornerHoverSize and
iMouseY < cropWindow.y + cropWindow.cornerHoverSize
draw = true
cropWindow.cornersHoverState[1] = true;
cropWindow.cornersSizes[1] = cropWindow.cornerHoverSize
if iMouseX > cropWindow.x + cropWindow.w - cropWindow.cornerHoverSize and
iMouseX < cropWindow.x + cropWindow.w + cropWindow.cornerHoverSize and
iMouseY > cropWindow.y + cropWindow.h - cropWindow.cornerHoverSize and
iMouseY < cropWindow.y + cropWindow.h + cropWindow.cornerHoverSize
draw = true
cropWindow.cornersHoverState[2] = true
cropWindow.cornersSizes[2] = cropWindow.cornerHoverSize
if iMouseX > cropWindow.x - cropWindow.cornerHoverSize and
iMouseX < cropWindow.x + cropWindow.cornerHoverSize and
iMouseY > cropWindow.y + cropWindow.h-cropWindow.cornerHoverSize and
iMouseY < cropWindow.y + cropWindow.h + cropWindow.cornerHoverSize
draw = true
cropWindow.cornersHoverState[3] = true
cropWindow.cornersSizes[3] = cropWindow.cornerHoverSize
# in case of dragging of resize cubes
# iFX - new crop window X position
# iFY - new crop window Y position
# iFW - new crop window width
# iFH - new crop window height
if cropWindow.cornersDragState[0]
# user dragged left upper corner
iFX = iMouseX - cropWindow.px
iFY = iMouseY - cropWindow.py
iFW = cropWindow.w + cropWindow.x - iFX
iFH = cropWindow.h + cropWindow.y - iFY
if cropWindow.cornersDragState[1]
# user dragged right upper corner
iFX = cropWindow.x
iFY = iMouseY - cropWindow.py
iFW = iMouseX - cropWindow.px - iFX
iFH = cropWindow.h + cropWindow.y - iFY
if cropWindow.cornersDragState[2]
# user dragged right bottom corner
iFX = cropWindow.x
iFY = cropWindow.y
iFW = iMouseX - cropWindow.px - iFX
iFH = iMouseY - cropWindow.py - iFY
if cropWindow.cornersDragState[3]
# user dragged left bottom corner
iFX = iMouseX - cropWindow.px
iFY = cropWindow.y
iFW = cropWindow.w + cropWindow.x - iFX
iFH = iMouseY - cropWindow.py - iFY
if iFW > cropWindow.cornerHoverSize * 2 and iFH > cropWindow.cornerHoverSize * 2
# user changed crop window size by dragging one of the corners
# update crop window to new size
cropWindow.w = iFW
cropWindow.h = iFH
cropWindow.x = iFX
cropWindow.y = iFY
draw = true
if draw
drawScene(ctx, image)
.mousedown (event) ->
event.stopPropagation()
event.stopImmediatePropagation()
canvasOffset = $("\##{imgPreviewId}").offset()
iMouseX = Math.floor(event.pageX - canvasOffset.left)
iMouseY = Math.floor(event.pageY - canvasOffset.top)
cropWindow.px = iMouseX - cropWindow.x
cropWindow.py = iMouseY - cropWindow.y
# register crop window movement
if iMouseX > cropWindow.x and iMouseX < cropWindow.x + cropWindow.w \
and iMouseY > cropWindow.y and iMouseY < cropWindow.y + cropWindow.h
cropWindow.dragWhole = true
return
# register resize of crop window
if cropWindow.cornersHoverState[0]
# user is hovering over upper left corner
cropWindow.px = iMouseX - cropWindow.x
cropWindow.py = iMouseY - cropWindow.y
if cropWindow.cornersHoverState[1]
# user is hovering over upper right corner
cropWindow.px = iMouseX - cropWindow.x - cropWindow.w
cropWindow.py = iMouseY - cropWindow.y
if cropWindow.cornersHoverState[2]
# user is hovering over bottom right corner
cropWindow.px = iMouseX - cropWindow.x - cropWindow.w
cropWindow.py = iMouseY - cropWindow.y - cropWindow.h
if cropWindow.cornersHoverState[3]
# user is hovering over bottom left corner
cropWindow.px = iMouseX - cropWindow.x
cropWindow.py = iMouseY - cropWindow.y - cropWindow.h
# register what corner is being dragged
for i in [0...cropWindow.cornersHoverState.length]
if cropWindow.cornersHoverState[i]
cropWindow.cornersDragState[i] = true
return false
.mouseup (event) ->
event.stopPropagation()
event.stopImmediatePropagation()
cropWindow.dragWhole = false
cropWindow.px = 0
cropWindow.py = 0
for i in [0...cropWindow.cornersDragState.length]
cropWindow.cornersDragState[i] = false
return false
image.src = imgURL
handleFileInput = (el) ->
###
Uploads the file from user device into browser buffer.
Checks that selected image is of image mimetype, and doesn't exceeds max
file size allowed. If the image is bigger than the maxFileSize,
upload will be aborted.
Automatically reduce image size, by changing image dimensions.
###
file = el.files[0]
if not settings.imgTypes.test file.type
console.log 'type not allowed'
settings.typeError()
return false
if file.size > settings.maxSize
console.log 'size too big'
settings.sizeError()
return false
# prepare filereader
fReader = new FileReader()
fReader.onload = (event) =>
settings.fileName = file.name
if settings.crop
handleCrop(event.target.result)
else
handleResize(event.target.result)
# trigger onload func
fReader.readAsDataURL(file)
return @.change ->
handleFileInput(@)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment