Created
May 18, 2014 23:16
-
-
Save standuprey/b2a600aba10d957dbaf0 to your computer and use it in GitHub Desktop.
cropme with touch event, unfortunately works only for left and right
This file contains 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
angular.module("cropme", ["ngSanitize", "ngTouch"]).directive "cropme", ($swipe, $window, $timeout, $rootScope) -> | |
minHeight = 100 # if destinationHeight has not been defined, we need a default height for the crop zone | |
borderSensitivity = 8 # grab area size around the borders in pixels | |
checkScopeVariables = (scope) -> | |
if scope.destinationHeight | |
if scope.ratio | |
throw "You can't specify both destinationHeight and ratio, destinationHeight = destinationWidth * ratio" | |
else | |
scope.ratio = destinationHeight / destinationWidth | |
else if scope.ratio | |
scope.destinationHeight = scope.destinationWidth * scope.ratio | |
if scope.ratio and scope.height and scope.destinationHeight > scope.height | |
throw "Can't initialize cropme: destinationWidth x ratio needs to be lower than height" | |
if scope.destinationWidth > scope.width | |
throw "Can't initialize cropme: destinationWidth needs to be lower than width" | |
if scope.ratio and not scope.height | |
scope.height = scope.destinationHeight | |
scope.type ||= "png" | |
offset = (el) -> | |
offsetTop = 0 | |
offsetLeft = 0 | |
while el | |
offsetTop += el.offsetTop | |
offsetLeft += el.offsetLeft | |
el = el.offsetParent | |
top: offsetTop | |
left: offsetLeft | |
template: """ | |
<div | |
class="step-1" | |
ng-show="state == 'step-1'" | |
ng-style="{'width': width + 'px', 'height': height + 'px'}"> | |
<dropbox ng-class="dropClass"></dropbox> | |
<div class="cropme-error" ng-bind-html="dropError"></div> | |
<div class="cropme-file-input"> | |
<input type="file"/> | |
<div | |
class="cropme-button" | |
ng-click="browseFiles()"> | |
Browse picture | |
</div> | |
<div class="cropme-or">or</div> | |
<div class="cropme-label" ng-class="iconClass">{{dropText}}</div> | |
</div> | |
</div> | |
<div | |
class="step-2" | |
ng-show="state == 'step-2'" | |
ng-style="{'width': width + 'px'}" | |
ng-mousemove="mousemove($event)" | |
ng-mouseleave="deselect()" | |
ng-class="{'col-resize': colResizePointer}"> | |
<img ng-src="{{imgSrc}}" ng-style="{'width': width + 'px'}"/> | |
<div class="overlay-tile" ng-style="{'top': 0, 'left': 0, 'width': xCropZone + 'px', 'height': yCropZone + 'px'}"></div> | |
<div class="overlay-tile" ng-style="{'top': 0, 'left': xCropZone + 'px', 'width': widthCropZone + 'px', 'height': yCropZone + 'px'}"></div> | |
<div class="overlay-tile" ng-style="{'top': 0, 'left': xCropZone + widthCropZone + 'px', 'right': 0, 'height': yCropZone + 'px'}"></div> | |
<div class="overlay-tile" ng-style="{'top': yCropZone + 'px', 'left': xCropZone + widthCropZone + 'px', 'right': 0, 'height': heightCropZone + 'px'}"></div> | |
<div class="overlay-tile" ng-style="{'top': yCropZone + heightCropZone + 'px', 'left': xCropZone + widthCropZone + 'px', 'right': 0, 'bottom': 0}"></div> | |
<div class="overlay-tile" ng-style="{'top': yCropZone + heightCropZone + 'px', 'left': xCropZone + 'px', 'width': widthCropZone + 'px', 'bottom': 0}"></div> | |
<div class="overlay-tile" ng-style="{'top': yCropZone + heightCropZone + 'px', 'left': 0, 'width': xCropZone + 'px', 'bottom': 0}"></div> | |
<div class="overlay-tile" ng-style="{'top': yCropZone + 'px', 'left': 0, 'width': xCropZone + 'px', 'height': heightCropZone + 'px'}"></div> | |
<div class="overlay-border" ng-style="{'top': (yCropZone - 2) + 'px', 'left': (xCropZone - 2) + 'px', 'width': widthCropZone + 'px', 'height': heightCropZone + 'px'}"></div> | |
</div> | |
<div class="cropme-actions" ng-show="state == 'step-2'"> | |
<button id="cropme-cancel" ng-click="cancel($event)">Cancel</button> | |
<button id="cropme-ok" ng-click="ok($event)">Ok</button> | |
</div> | |
<canvas | |
width="{{destinationWidth}}" | |
height="{{destinationHeight}}" | |
ng-style="{'width': destinationWidth + 'px', 'height': destinationHeight + 'px'}"> | |
</canvas> | |
""" | |
restrict: "E" | |
scope: | |
width: "=" | |
destinationWidth: "=" | |
height: "=?" | |
destinationHeight: "=?" | |
iconClass: "=?" | |
ratio: "=?" | |
type: "=?" | |
link: (scope, element, attributes) -> | |
scope.dropText = "Drop picture here" | |
scope.state = "step-1" | |
draggingFn = null | |
grabbedBorder = null | |
heightWithImage = null | |
zoom = null | |
elOffset = null | |
imageEl = element.find('img')[0] | |
canvasEl = element.find("canvas")[0] | |
ctx = canvasEl.getContext "2d" | |
startCropping = (imageWidth, imageHeight) -> | |
zoom = scope.width / imageWidth | |
heightWithImage = imageHeight * zoom | |
scope.widthCropZone = Math.round scope.destinationWidth * zoom | |
scope.heightCropZone = Math.round (scope.destinationHeight || minHeight) * zoom | |
scope.xCropZone = Math.round (scope.width - scope.widthCropZone) / 2 | |
scope.yCropZone = Math.round (scope.height - scope.heightCropZone) / 2 | |
$timeout -> elOffset = offset imageAreaEl | |
imageAreaEl = element[0].getElementsByClassName("step-2")[0] | |
checkScopeVariables scope | |
$input = element.find("input") | |
$input.bind "change", -> | |
file = @files[0] | |
scope.$apply -> scope.setFiles file | |
$input.bind "click", (e) -> | |
e.stopPropagation() | |
$input.val "" | |
scope.browseFiles = -> $input[0].click() | |
scope.setFiles = (file) -> | |
unless file.type.match /^image\// | |
return scope.dropError = "Wrong file type, please select an image." | |
scope.dropError = "" | |
reader = new FileReader | |
reader.onload = (e) -> | |
imageEl.onload = -> | |
width = imageEl.naturalWidth | |
height = imageEl.naturalHeight | |
errors = [] | |
if width < scope.width | |
errors.push "The image you dropped has a width of #{width}, but the minimum is #{scope.width}." | |
if scope.height and height < scope.height | |
errors.push "The image you dropped has a height of #{height}, but the minimum is #{scope.height}." | |
if scope.ratio and scope.destinationHeight > height | |
errors.push "The image you dropped has a height of #{height}, but the minimum is #{scope.destinationHeight}." | |
scope.$apply -> | |
if errors.length | |
scope.dropError = errors.join "<br/>" | |
else | |
$rootScope.$broadcast "cropme:loaded", width, height | |
scope.state = "step-2" | |
startCropping width, height | |
scope.$apply -> scope.imgSrc = e.target.result | |
reader.readAsDataURL(file); | |
moveCropZone = (coords) -> | |
scope.xCropZone = coords.x - elOffset.left - scope.widthCropZone / 2 | |
scope.yCropZone = coords.y - elOffset.top - scope.heightCropZone / 2 | |
checkBounds() | |
moveBorders = | |
top: (coords) -> | |
y = coords.y - elOffset.top | |
scope.heightCropZone += scope.yCropZone - y | |
scope.yCropZone = y | |
checkVRatio() | |
checkBounds() | |
right: (coords) -> | |
x = coords.x - elOffset.left | |
scope.widthCropZone = x - scope.xCropZone | |
checkHRatio() | |
checkBounds() | |
bottom: (coords) -> | |
y = coords.y - elOffset.top | |
console.log y, coords.y, elOffset.top | |
scope.heightCropZone = y - scope.yCropZone | |
checkVRatio() | |
checkBounds() | |
left: (coords) -> | |
x = coords.x - elOffset.left | |
scope.widthCropZone += scope.xCropZone - x | |
scope.xCropZone = x | |
checkHRatio() | |
checkBounds() | |
checkHRatio = -> scope.heightCropZone = scope.widthCropZone * scope.ratio if scope.ratio | |
checkVRatio = -> scope.widthCropZone = scope.heightCropZone / scope.ratio if scope.ratio | |
checkBounds = -> | |
scope.xCropZone = 0 if scope.xCropZone < 0 | |
scope.yCropZone = 0 if scope.yCropZone < 0 | |
if scope.widthCropZone < scope.destinationWidth * zoom | |
scope.widthCropZone = scope.destinationWidth * zoom | |
checkHRatio() | |
else if scope.destinationHeight and scope.heightCropZone < scope.destinationHeight * zoom | |
scope.heightCropZone = scope.destinationHeight * zoom | |
checkVRatio() | |
if scope.xCropZone + scope.widthCropZone > scope.width | |
scope.xCropZone = scope.width - scope.widthCropZone | |
if scope.xCropZone < 0 | |
scope.widthCropZone = scope.width | |
scope.xCropZone = 0 | |
checkHRatio() | |
if scope.yCropZone + scope.heightCropZone > heightWithImage | |
scope.yCropZone = heightWithImage - scope.heightCropZone | |
if scope.yCropZone < 0 | |
scope.heightCropZone = heightWithImage | |
scope.yCropZone = 0 | |
checkVRatio() | |
isNearBorders = (coords) -> | |
x = scope.xCropZone + elOffset.left | |
y = scope.yCropZone + elOffset.top | |
w = scope.widthCropZone | |
h = scope.heightCropZone | |
topLeft = { x: x, y: y } | |
topRight = { x: x + w, y: y } | |
bottomLeft = { x: x, y: y + h } | |
bottomRight = { x: x + w, y: y + h } | |
nearHSegment(coords, x, w, y, "top") or nearVSegment(coords, y, h, x + w, "right") or nearHSegment(coords, x, w, y + h, "bottom") or nearVSegment(coords, y, h, x, "left") | |
nearHSegment = (coords, x, w, y, borderName) -> | |
borderName if coords.x >= x and coords.x <= x + w and Math.abs(coords.y - y) <= borderSensitivity | |
nearVSegment = (coords, y, h, x, borderName) -> | |
borderName if coords.y >= y and coords.y <= y + h and Math.abs(coords.x - x) <= borderSensitivity | |
dragIt = (coords) -> | |
if draggingFn | |
scope.$apply -> draggingFn(coords) | |
scope.mousemove = (e) -> | |
scope.colResizePointer = isNearBorders({x: e.pageX, y:e.pageY}) | |
$swipe.bind angular.element(element[0].getElementsByClassName('step-2')[0]), | |
'start': (coords) -> | |
grabbedBorder = isNearBorders coords | |
if grabbedBorder | |
draggingFn = moveBorders[grabbedBorder] | |
else draggingFn = moveCropZone | |
dragIt coords | |
'move': (coords) -> | |
dragIt coords | |
'end': (coords) -> | |
dragIt coords | |
draggingFn = null | |
scope.deselect = -> draggingFn = null | |
scope.cancel = ($event) -> | |
$event.preventDefault() if $event | |
scope.dropText = "Drop files here" | |
scope.dropClass = "" | |
scope.state = "step-1" | |
scope.ok = ($event) -> | |
$event.preventDefault() if $event | |
scope.croppedWidth = scope.widthCropZone / zoom | |
scope.croppedHeight = scope.heightCropZone / zoom | |
$timeout -> | |
destinationHeight = scope.destinationHeight || scope.destinationWidth * scope.croppedHeight / scope.croppedWidth | |
ctx.drawImage imageEl, scope.xCropZone / zoom, scope.yCropZone / zoom, scope.croppedWidth, scope.croppedHeight, 0, 0, scope.destinationWidth, scope.destinationHeight | |
canvasEl.toBlob (blob) -> | |
$rootScope.$broadcast "cropme:done", blob | |
, 'image/' + scope.type | |
scope.$on "cropme:cancel", scope.cancel | |
scope.$on "cropme:ok", scope.ok | |
angular.module("cropme").directive "dropbox", -> | |
restrict: "E" | |
link: (scope, element, attributes) -> | |
dragEnterLeave = (evt) -> | |
evt.stopPropagation() | |
evt.preventDefault() | |
scope.$apply -> | |
scope.dropText = "Drop files here" | |
scope.dropClass = "" | |
dropbox = element[0] | |
scope.dropText = "Drop files here" | |
dropbox.addEventListener "dragenter", dragEnterLeave, false | |
dropbox.addEventListener "dragleave", dragEnterLeave, false | |
dropbox.addEventListener "dragover", ((evt) -> | |
evt.stopPropagation() | |
evt.preventDefault() | |
ok = evt.dataTransfer and evt.dataTransfer.types and evt.dataTransfer.types.indexOf("Files") >= 0 | |
scope.$apply -> | |
scope.dropText = (if ok then "Drop now" else "Only files are allowed") | |
scope.dropClass = (if ok then "over" else "not-available") | |
), false | |
dropbox.addEventListener "drop", ((evt) -> | |
evt.stopPropagation() | |
evt.preventDefault() | |
scope.$apply -> | |
scope.dropText = "Drop files here" | |
scope.dropClass = "" | |
files = evt.dataTransfer.files | |
scope.$apply -> | |
if files.length > 0 | |
for file in files | |
if file.type.match /^image\// | |
scope.dropText = "Loading image..." | |
scope.dropClass = "loading" | |
return scope.setFiles(file) | |
scope.dropError = "Wrong file type, please drop at least an image." | |
), false | |
# canvas-toBlob.js | |
# * A canvas.toBlob() implementation. | |
# * 2011-07-13 | |
# * | |
# * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr | |
# * License: X11/MIT | |
# * See LICENSE.md | |
# | |
#global self | |
#jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, | |
# plusplus: true | |
#! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js | |
((view) -> | |
"use strict" | |
Uint8Array = view.Uint8Array | |
HTMLCanvasElement = view.HTMLCanvasElement | |
is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i | |
base64_ranks = undefined | |
decode_base64 = (base64) -> | |
len = base64.length | |
buffer = new Uint8Array(len / 4 * 3 | 0) | |
i = 0 | |
outptr = 0 | |
last = [0, 0] | |
state = 0 | |
save = 0 | |
rank = undefined | |
code = undefined | |
undef = undefined | |
while len-- | |
code = base64.charCodeAt(i++) | |
rank = base64_ranks[code - 43] | |
if rank isnt 255 and rank isnt undef | |
last[1] = last[0] | |
last[0] = code | |
save = (save << 6) | rank | |
state++ | |
if state is 4 | |
buffer[outptr++] = save >>> 16 | |
# padding character | |
buffer[outptr++] = save >>> 8 if last[1] isnt 61 | |
# padding character | |
buffer[outptr++] = save if last[0] isnt 61 | |
state = 0 | |
# 2/3 chance there's going to be some null bytes at the end, but that | |
# doesn't really matter with most image formats. | |
# If it somehow matters for you, truncate the buffer up outptr. | |
buffer | |
base64_ranks = new Uint8Array([62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]) if Uint8Array | |
if HTMLCanvasElement and not HTMLCanvasElement::toBlob | |
HTMLCanvasElement::toBlob = (callback, type) -> #, ...args | |
type = "image/png" unless type | |
if @mozGetAsFile | |
callback @mozGetAsFile("canvas", type) | |
return | |
args = Array::slice.call(arguments, 1) | |
dataURI = @toDataURL.apply(this, args) | |
header_end = dataURI.indexOf(",") | |
data = dataURI.substring(header_end + 1) | |
is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)) | |
blob = undefined | |
if Blob.fake | |
# no reason to decode a data: URI that's just going to become a data URI again | |
blob = new Blob | |
if is_base64 | |
blob.encoding = "base64" | |
else | |
blob.encoding = "URI" | |
blob.data = data | |
blob.size = data.length | |
else if Uint8Array | |
if is_base64 | |
blob = new Blob([decode_base64(data)], | |
type: type | |
) | |
else | |
blob = new Blob([decodeURIComponent(data)], | |
type: type | |
) | |
callback blob | |
) self |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment