/*! * ngImgCrop v0.3.2 * https://github.com/alexk111/ngImgCrop * * Copyright (c) 2014 Alex Kaul * License: MIT * * Generated at Wednesday, December 3rd, 2014, 3:54:12 PM */ (function() { 'use strict'; var crop = angular.module('ngImgCrop', []); crop.factory('cropAreaCircle', ['cropArea', function(CropArea) { var CropAreaCircle = function() { CropArea.apply(this, arguments); this._boxResizeBaseSize = 20; this._boxResizeNormalRatio = 0.9; this._boxResizeHoverRatio = 1.2; this._iconMoveNormalRatio = 0.9; this._iconMoveHoverRatio = 1.2; this._boxResizeNormalSize = this._boxResizeBaseSize * this._boxResizeNormalRatio; this._boxResizeHoverSize = this._boxResizeBaseSize * this._boxResizeHoverRatio; this._posDragStartX = 0; this._posDragStartY = 0; this._posResizeStartX = 0; this._posResizeStartY = 0; this._posResizeStartSize = 0; this._boxResizeIsHover = false; this._areaIsHover = false; this._boxResizeIsDragging = false; this._areaIsDragging = false; }; CropAreaCircle.prototype = new CropArea(); CropAreaCircle.prototype._calcCirclePerimeterCoords = function(angleDegrees) { var hSize = this._size / 2; var angleRadians = angleDegrees * (Math.PI / 180), circlePerimeterX = this._x + hSize * Math.cos(angleRadians), circlePerimeterY = this._y + hSize * Math.sin(angleRadians); return [circlePerimeterX, circlePerimeterY]; }; CropAreaCircle.prototype._calcResizeIconCenterCoords = function() { return this._calcCirclePerimeterCoords(-45); }; CropAreaCircle.prototype._isCoordWithinArea = function(coord) { return Math.sqrt((coord[0] - this._x) * (coord[0] - this._x) + (coord[1] - this._y) * (coord[1] - this._y)) < this._size / 2; }; CropAreaCircle.prototype._isCoordWithinBoxResize = function(coord) { var resizeIconCenterCoords = this._calcResizeIconCenterCoords(); var hSize = this._boxResizeHoverSize / 2; return (coord[0] > resizeIconCenterCoords[0] - hSize && coord[0] < resizeIconCenterCoords[0] + hSize && coord[1] > resizeIconCenterCoords[1] - hSize && coord[1] < resizeIconCenterCoords[1] + hSize); }; CropAreaCircle.prototype._drawArea = function(ctx, centerCoords, size) { ctx.arc(centerCoords[0], centerCoords[1], size / 2, 0, 2 * Math.PI); }; CropAreaCircle.prototype.draw = function() { CropArea.prototype.draw.apply(this, arguments); // draw move icon this._cropCanvas.drawIconMove([this._x, this._y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); // draw resize cubes this._cropCanvas.drawIconResizeBoxNESW(this._calcResizeIconCenterCoords(), this._boxResizeBaseSize, this._boxResizeIsHover ? this._boxResizeHoverRatio : this._boxResizeNormalRatio); }; CropAreaCircle.prototype.processMouseMove = function(mouseCurX, mouseCurY) { var cursor = 'default'; var res = false; this._boxResizeIsHover = false; this._areaIsHover = false; if (this._areaIsDragging) { this._x = mouseCurX - this._posDragStartX; this._y = mouseCurY - this._posDragStartY; this._areaIsHover = true; cursor = 'move'; res = true; this._events.trigger('area-move'); } else if (this._boxResizeIsDragging) { cursor = 'nesw-resize'; var iFR, iFX, iFY; iFX = mouseCurX - this._posResizeStartX; iFY = this._posResizeStartY - mouseCurY; if (iFX > iFY) { iFR = this._posResizeStartSize + iFY * 2; } else { iFR = this._posResizeStartSize + iFX * 2; } this._size = Math.max(this._minSize, iFR); this._boxResizeIsHover = true; res = true; this._events.trigger('area-resize'); } else if (this._isCoordWithinBoxResize([mouseCurX, mouseCurY])) { cursor = 'nesw-resize'; this._areaIsHover = false; this._boxResizeIsHover = true; res = true; } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { cursor = 'move'; this._areaIsHover = true; res = true; } this._dontDragOutside(); angular.element(this._ctx.canvas).css({ 'cursor': cursor }); return res; }; CropAreaCircle.prototype.processMouseDown = function(mouseDownX, mouseDownY) { if (this._isCoordWithinBoxResize([mouseDownX, mouseDownY])) { this._areaIsDragging = false; this._areaIsHover = false; this._boxResizeIsDragging = true; this._boxResizeIsHover = true; this._posResizeStartX = mouseDownX; this._posResizeStartY = mouseDownY; this._posResizeStartSize = this._size; this._events.trigger('area-resize-start'); } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { this._areaIsDragging = true; this._areaIsHover = true; this._boxResizeIsDragging = false; this._boxResizeIsHover = false; this._posDragStartX = mouseDownX - this._x; this._posDragStartY = mouseDownY - this._y; this._events.trigger('area-move-start'); } }; CropAreaCircle.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) { if (this._areaIsDragging) { this._areaIsDragging = false; this._events.trigger('area-move-end'); } if (this._boxResizeIsDragging) { this._boxResizeIsDragging = false; this._events.trigger('area-resize-end'); } this._areaIsHover = false; this._boxResizeIsHover = false; this._posDragStartX = 0; this._posDragStartY = 0; }; return CropAreaCircle; }]); crop.factory('cropAreaSquare', ['cropArea', function(CropArea) { var CropAreaSquare = function() { CropArea.apply(this, arguments); this.resSizeScale=1; this._resizeCtrlBaseRadius = 10; this._resizeCtrlNormalRatio = 0.75; this._resizeCtrlHoverRatio = 1; this._iconMoveNormalRatio = 0.9; this._iconMoveHoverRatio = 1.2; this._resizeCtrlNormalRadius = this._resizeCtrlBaseRadius * this._resizeCtrlNormalRatio; this._resizeCtrlHoverRadius = this._resizeCtrlBaseRadius * this._resizeCtrlHoverRatio; this._posDragStartX = 0; this._posDragStartY = 0; this._posResizeStartX = 0; this._posResizeStartY = 0; this._posResizeStartSize = 0; this._resizeCtrlIsHover = -1; this._areaIsHover = false; this._resizeCtrlIsDragging = -1; this._areaIsDragging = false; }; CropAreaSquare.prototype = new CropArea(); CropAreaSquare.prototype._calcSquareCorners = function() { var hSize = this._size / 2; return [ [this._x - hSize, this._y - hSize/this.resSizeScale], [this._x + hSize, this._y - hSize/this.resSizeScale], [this._x - hSize, this._y + hSize/this.resSizeScale], [this._x + hSize, this._y + hSize/this.resSizeScale] ]; }; CropAreaSquare.prototype._calcSquareDimensions = function() { var hSize = this._size / 2; return { left: this._x - hSize, top: this._y - hSize, right: this._x + hSize, bottom: this._y + hSize }; }; CropAreaSquare.prototype._isCoordWithinArea = function(coord) { var squareDimensions = this._calcSquareDimensions(); return (coord[0] >= squareDimensions.left && coord[0] <= squareDimensions.right && coord[1] >= squareDimensions.top && coord[1] <= squareDimensions.bottom); }; CropAreaSquare.prototype._isCoordWithinResizeCtrl = function(coord) { var resizeIconsCenterCoords = this._calcSquareCorners(); var res = -1; for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { var resizeIconCenterCoords = resizeIconsCenterCoords[i]; if (coord[0] > resizeIconCenterCoords[0] - this._resizeCtrlHoverRadius && coord[0] < resizeIconCenterCoords[0] + this._resizeCtrlHoverRadius && coord[1] > resizeIconCenterCoords[1] - this._resizeCtrlHoverRadius && coord[1] < resizeIconCenterCoords[1] + this._resizeCtrlHoverRadius) { res = i; break; } } return res; }; CropAreaSquare.prototype._drawArea = function(ctx, centerCoords, size,scale) { var hSize = size / 2; scale=scale||1; ctx.rect(centerCoords[0] - hSize, centerCoords[1] - hSize/scale, size, size/scale); }; CropAreaSquare.prototype.draw = function() { CropArea.prototype.draw.apply(this, arguments); // draw move icon this._cropCanvas.drawIconMove([this._x, this._y], this._areaIsHover ? this._iconMoveHoverRatio : this._iconMoveNormalRatio); // draw resize cubes var resizeIconsCenterCoords = this._calcSquareCorners(); for (var i = 0, len = resizeIconsCenterCoords.length; i < len; i++) { var resizeIconCenterCoords = resizeIconsCenterCoords[i]; this._cropCanvas.drawIconResizeCircle(resizeIconCenterCoords, this._resizeCtrlBaseRadius, this._resizeCtrlIsHover === i ? this._resizeCtrlHoverRatio : this._resizeCtrlNormalRatio); } }; CropAreaSquare.prototype.processMouseMove = function(mouseCurX, mouseCurY) { var cursor = 'default'; var res = false; this._resizeCtrlIsHover = -1; this._areaIsHover = false; if (this._areaIsDragging) { this._x = mouseCurX - this._posDragStartX; this._y = mouseCurY - this._posDragStartY; this._areaIsHover = true; cursor = 'move'; res = true; this._events.trigger('area-move'); } else if (this._resizeCtrlIsDragging > -1) { var xMulti, yMulti; switch (this._resizeCtrlIsDragging) { case 0: // Top Left xMulti = -1; yMulti = -1; cursor = 'nwse-resize'; break; case 1: // Top Right xMulti = 1; yMulti = -1; cursor = 'nesw-resize'; break; case 2: // Bottom Left xMulti = -1; yMulti = 1; cursor = 'nesw-resize'; break; case 3: // Bottom Right xMulti = 1; yMulti = 1; cursor = 'nwse-resize'; break; } var iFX = (mouseCurX - this._posResizeStartX) * xMulti; var iFY = (mouseCurY - this._posResizeStartY) * yMulti; var iFR; if (iFX > iFY) { iFR = this._posResizeStartSize + iFY; } else { iFR = this._posResizeStartSize + iFX; } var wasSize = this._size; this._size = Math.max(this._minSize, iFR); var posModifier = (this._size - wasSize) / 2; this._x += posModifier * xMulti; this._y += posModifier * yMulti; this._resizeCtrlIsHover = this._resizeCtrlIsDragging; res = true; this._events.trigger('area-resize'); } else { var hoveredResizeBox = this._isCoordWithinResizeCtrl([mouseCurX, mouseCurY]); if (hoveredResizeBox > -1) { switch (hoveredResizeBox) { case 0: cursor = 'nwse-resize'; break; case 1: cursor = 'nesw-resize'; break; case 2: cursor = 'nesw-resize'; break; case 3: cursor = 'nwse-resize'; break; } this._areaIsHover = false; this._resizeCtrlIsHover = hoveredResizeBox; res = true; } else if (this._isCoordWithinArea([mouseCurX, mouseCurY])) { cursor = 'move'; this._areaIsHover = true; res = true; } } this._dontDragOutside(); angular.element(this._ctx.canvas).css({ 'cursor': cursor }); return res; }; CropAreaSquare.prototype.processMouseDown = function(mouseDownX, mouseDownY) { var isWithinResizeCtrl = this._isCoordWithinResizeCtrl([mouseDownX, mouseDownY]); if (isWithinResizeCtrl > -1) { this._areaIsDragging = false; this._areaIsHover = false; this._resizeCtrlIsDragging = isWithinResizeCtrl; this._resizeCtrlIsHover = isWithinResizeCtrl; this._posResizeStartX = mouseDownX; this._posResizeStartY = mouseDownY; this._posResizeStartSize = this._size; this._events.trigger('area-resize-start'); } else if (this._isCoordWithinArea([mouseDownX, mouseDownY])) { this._areaIsDragging = true; this._areaIsHover = true; this._resizeCtrlIsDragging = -1; this._resizeCtrlIsHover = -1; this._posDragStartX = mouseDownX - this._x; this._posDragStartY = mouseDownY - this._y; this._events.trigger('area-move-start'); } }; CropAreaSquare.prototype.processMouseUp = function( /*mouseUpX, mouseUpY*/ ) { if (this._areaIsDragging) { this._areaIsDragging = false; this._events.trigger('area-move-end'); } if (this._resizeCtrlIsDragging > -1) { this._resizeCtrlIsDragging = -1; this._events.trigger('area-resize-end'); } this._areaIsHover = false; this._resizeCtrlIsHover = -1; this._posDragStartX = 0; this._posDragStartY = 0; }; return CropAreaSquare; }]); crop.factory('cropArea', ['cropCanvas', function(CropCanvas) { var CropArea = function(ctx, events) { this._ctx = ctx; this._events = events; this._minSize = 80; this.resSizeScale = 1; //图片剪切比例 this._cropCanvas = new CropCanvas(ctx); this._image = new Image(); this._x = 0; this._y = 0; this._size = 200; }; /* GETTERS/SETTERS */ CropArea.prototype.getImage = function() { return this._image; }; CropArea.prototype.setImage = function(image) { this._image = image; }; CropArea.prototype.getX = function() { return this._x; }; CropArea.prototype.setX = function(x) { this._x = x; this._dontDragOutside(); }; CropArea.prototype.getY = function() { return this._y; }; CropArea.prototype.setY = function(y) { this._y = y; this._dontDragOutside(); }; CropArea.prototype.getSize = function() { return this._size; }; CropArea.prototype.setSize = function(size) { this._size = Math.max(this._minSize, size); this._dontDragOutside(); }; CropArea.prototype.getMinSize = function() { return this._minSize; }; CropArea.prototype.setMinSize = function(size) { this._minSize = size; this._size = Math.max(this._minSize, this._size); this._dontDragOutside(); }; /* FUNCTIONS */ CropArea.prototype._dontDragOutside = function() { var h = this._ctx.canvas.height*this.resSizeScale, w = this._ctx.canvas.width; if (this._size > w) { this._size = w; } if (this._size > h) { this._size = h; } if (this._x < this._size / 2) { this._x = this._size / 2; } if (this._x > w - this._size / 2) { this._x = w - this._size / 2; } if (this._y < this._size / 2/this.resSizeScale) { this._y = this._size / 2/this.resSizeScale; } if (this._y > (h - this._size / 2)/this.resSizeScale) { this._y = (h - this._size / 2)/this.resSizeScale; } }; CropArea.prototype._drawArea = function() {}; CropArea.prototype.draw = function() { // draw crop area this._cropCanvas.drawCropArea(this._image, [this._x, this._y], this._size,this.resSizeScale, this._drawArea); }; CropArea.prototype.processMouseMove = function() {}; CropArea.prototype.processMouseDown = function() {}; CropArea.prototype.processMouseUp = function() {}; return CropArea; }]); crop.factory('cropCanvas', [function() { // Shape = Array of [x,y]; [0, 0] - center var shapeArrowNW = [ [-0.5, -2], [-3, -4.5], [-0.5, -7], [-7, -7], [-7, -0.5], [-4.5, -3], [-2, -0.5] ]; var shapeArrowNE = [ [0.5, -2], [3, -4.5], [0.5, -7], [7, -7], [7, -0.5], [4.5, -3], [2, -0.5] ]; var shapeArrowSW = [ [-0.5, 2], [-3, 4.5], [-0.5, 7], [-7, 7], [-7, 0.5], [-4.5, 3], [-2, 0.5] ]; var shapeArrowSE = [ [0.5, 2], [3, 4.5], [0.5, 7], [7, 7], [7, 0.5], [4.5, 3], [2, 0.5] ]; var shapeArrowN = [ [-1.5, -2.5], [-1.5, -6], [-5, -6], [0, -11], [5, -6], [1.5, -6], [1.5, -2.5] ]; var shapeArrowW = [ [-2.5, -1.5], [-6, -1.5], [-6, -5], [-11, 0], [-6, 5], [-6, 1.5], [-2.5, 1.5] ]; var shapeArrowS = [ [-1.5, 2.5], [-1.5, 6], [-5, 6], [0, 11], [5, 6], [1.5, 6], [1.5, 2.5] ]; var shapeArrowE = [ [2.5, -1.5], [6, -1.5], [6, -5], [11, 0], [6, 5], [6, 1.5], [2.5, 1.5] ]; // Colors var colors = { areaOutline: '#fff', resizeBoxStroke: '#fff', resizeBoxFill: '#444', resizeBoxArrowFill: '#fff', resizeCircleStroke: '#fff', resizeCircleFill: '#444', moveIconFill: '#fff' }; return function(ctx) { /* Base functions */ // Calculate Point var calcPoint = function(point, offset, scale) { return [scale * point[0] + offset[0], scale * point[1] + offset[1]]; }; // Draw Filled Polygon var drawFilledPolygon = function(shape, fillStyle, centerCoords, scale) { ctx.save(); ctx.fillStyle = fillStyle; ctx.beginPath(); var pc, pc0 = calcPoint(shape[0], centerCoords, scale); ctx.moveTo(pc0[0], pc0[1]); for (var p in shape) { if (p > 0) { pc = calcPoint(shape[p], centerCoords, scale); ctx.lineTo(pc[0], pc[1]); } } ctx.lineTo(pc0[0], pc0[1]); ctx.fill(); ctx.closePath(); ctx.restore(); }; /* Icons */ this.drawIconMove = function(centerCoords, scale) { drawFilledPolygon(shapeArrowN, colors.moveIconFill, centerCoords, scale); drawFilledPolygon(shapeArrowW, colors.moveIconFill, centerCoords, scale); drawFilledPolygon(shapeArrowS, colors.moveIconFill, centerCoords, scale); drawFilledPolygon(shapeArrowE, colors.moveIconFill, centerCoords, scale); }; this.drawIconResizeCircle = function(centerCoords, circleRadius, scale) { var scaledCircleRadius = circleRadius * scale; ctx.save(); ctx.strokeStyle = colors.resizeCircleStroke; ctx.lineWidth = 2; ctx.fillStyle = colors.resizeCircleFill; ctx.beginPath(); ctx.arc(centerCoords[0], centerCoords[1], scaledCircleRadius, 0, 2 * Math.PI); ctx.fill(); ctx.stroke(); ctx.closePath(); ctx.restore(); }; this.drawIconResizeBoxBase = function(centerCoords, boxSize, scale) { var scaledBoxSize = boxSize * scale; ctx.save(); ctx.strokeStyle = colors.resizeBoxStroke; ctx.lineWidth = 2; ctx.fillStyle = colors.resizeBoxFill; ctx.fillRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); ctx.strokeRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize); ctx.restore(); }; this.drawIconResizeBoxNESW = function(centerCoords, boxSize, scale) { this.drawIconResizeBoxBase(centerCoords, boxSize, scale); drawFilledPolygon(shapeArrowNE, colors.resizeBoxArrowFill, centerCoords, scale); drawFilledPolygon(shapeArrowSW, colors.resizeBoxArrowFill, centerCoords, scale); }; this.drawIconResizeBoxNWSE = function(centerCoords, boxSize, scale) { this.drawIconResizeBoxBase(centerCoords, boxSize, scale); drawFilledPolygon(shapeArrowNW, colors.resizeBoxArrowFill, centerCoords, scale); drawFilledPolygon(shapeArrowSE, colors.resizeBoxArrowFill, centerCoords, scale); }; /* Crop Area */ this.drawCropArea = function(image, centerCoords, size,scale, fnDrawClipPath) { var xRatio = image.width / ctx.canvas.width, yRatio = image.height / ctx.canvas.height, xLeft = centerCoords[0] - size / 2, yTop = centerCoords[1] - size / 2; ctx.save(); ctx.strokeStyle = colors.areaOutline; ctx.lineWidth = 2; ctx.beginPath(); fnDrawClipPath(ctx, centerCoords, size,scale); ctx.stroke(); ctx.clip(); // draw part of original image if (size > 0) { ctx.drawImage(image, xLeft * xRatio, yTop * yRatio, size * xRatio, size * yRatio, xLeft, yTop, size, size); } ctx.beginPath(); fnDrawClipPath(ctx, centerCoords, size,scale); ctx.stroke(); ctx.clip(); ctx.restore(); }; }; }]); /** * EXIF service is based on the exif-js library (https://github.com/jseidelin/exif-js) */ crop.service('cropEXIF', [function() { var debug = false; var ExifTags = this.Tags = { // version tags 0x9000: "ExifVersion", // EXIF version 0xA000: "FlashpixVersion", // Flashpix format version // colorspace tags 0xA001: "ColorSpace", // Color space information tag // image configuration 0xA002: "PixelXDimension", // Valid width of meaningful image 0xA003: "PixelYDimension", // Valid height of meaningful image 0x9101: "ComponentsConfiguration", // Information about channels 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel // user information 0x927C: "MakerNote", // Any desired information written by the manufacturer 0x9286: "UserComment", // Comments by user // related file 0xA004: "RelatedSoundFile", // Name of related sound file // date and time 0x9003: "DateTimeOriginal", // Date and time when the original image was generated 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally 0x9290: "SubsecTime", // Fractions of seconds for DateTime 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized // picture-taking conditions 0x829A: "ExposureTime", // Exposure time (in seconds) 0x829D: "FNumber", // F number 0x8822: "ExposureProgram", // Exposure program 0x8824: "SpectralSensitivity", // Spectral sensitivity 0x8827: "ISOSpeedRatings", // ISO speed rating 0x8828: "OECF", // Optoelectric conversion factor 0x9201: "ShutterSpeedValue", // Shutter speed 0x9202: "ApertureValue", // Lens aperture 0x9203: "BrightnessValue", // Value of brightness 0x9204: "ExposureBias", // Exposure bias 0x9205: "MaxApertureValue", // Smallest F number of lens 0x9206: "SubjectDistance", // Distance to subject in meters 0x9207: "MeteringMode", // Metering mode 0x9208: "LightSource", // Kind of light source 0x9209: "Flash", // Flash status 0x9214: "SubjectArea", // Location and area of main subject 0x920A: "FocalLength", // Focal length of the lens in mm 0xA20B: "FlashEnergy", // Strobe energy in BCPS 0xA20C: "SpatialFrequencyResponse", // 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 0xA214: "SubjectLocation", // Location of subject in image 0xA215: "ExposureIndex", // Exposure index selected on camera 0xA217: "SensingMethod", // Image sensor type 0xA300: "FileSource", // Image source (3 == DSC) 0xA301: "SceneType", // Scene type (1 == directly photographed) 0xA302: "CFAPattern", // Color filter array geometric pattern 0xA401: "CustomRendered", // Special processing 0xA402: "ExposureMode", // Exposure mode 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual 0xA404: "DigitalZoomRation", // Digital zoom ratio 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) 0xA406: "SceneCaptureType", // Type of scene 0xA407: "GainControl", // Degree of overall image gain adjustment 0xA408: "Contrast", // Direction of contrast processing applied by camera 0xA409: "Saturation", // Direction of saturation processing applied by camera 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera 0xA40B: "DeviceSettingDescription", // 0xA40C: "SubjectDistanceRange", // Distance to subject // other tags 0xA005: "InteroperabilityIFDPointer", 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image }; var TiffTags = this.TiffTags = { 0x0100: "ImageWidth", 0x0101: "ImageHeight", 0x8769: "ExifIFDPointer", 0x8825: "GPSInfoIFDPointer", 0xA005: "InteroperabilityIFDPointer", 0x0102: "BitsPerSample", 0x0103: "Compression", 0x0106: "PhotometricInterpretation", 0x0112: "Orientation", 0x0115: "SamplesPerPixel", 0x011C: "PlanarConfiguration", 0x0212: "YCbCrSubSampling", 0x0213: "YCbCrPositioning", 0x011A: "XResolution", 0x011B: "YResolution", 0x0128: "ResolutionUnit", 0x0111: "StripOffsets", 0x0116: "RowsPerStrip", 0x0117: "StripByteCounts", 0x0201: "JPEGInterchangeFormat", 0x0202: "JPEGInterchangeFormatLength", 0x012D: "TransferFunction", 0x013E: "WhitePoint", 0x013F: "PrimaryChromaticities", 0x0211: "YCbCrCoefficients", 0x0214: "ReferenceBlackWhite", 0x0132: "DateTime", 0x010E: "ImageDescription", 0x010F: "Make", 0x0110: "Model", 0x0131: "Software", 0x013B: "Artist", 0x8298: "Copyright" }; var GPSTags = this.GPSTags = { 0x0000: "GPSVersionID", 0x0001: "GPSLatitudeRef", 0x0002: "GPSLatitude", 0x0003: "GPSLongitudeRef", 0x0004: "GPSLongitude", 0x0005: "GPSAltitudeRef", 0x0006: "GPSAltitude", 0x0007: "GPSTimeStamp", 0x0008: "GPSSatellites", 0x0009: "GPSStatus", 0x000A: "GPSMeasureMode", 0x000B: "GPSDOP", 0x000C: "GPSSpeedRef", 0x000D: "GPSSpeed", 0x000E: "GPSTrackRef", 0x000F: "GPSTrack", 0x0010: "GPSImgDirectionRef", 0x0011: "GPSImgDirection", 0x0012: "GPSMapDatum", 0x0013: "GPSDestLatitudeRef", 0x0014: "GPSDestLatitude", 0x0015: "GPSDestLongitudeRef", 0x0016: "GPSDestLongitude", 0x0017: "GPSDestBearingRef", 0x0018: "GPSDestBearing", 0x0019: "GPSDestDistanceRef", 0x001A: "GPSDestDistance", 0x001B: "GPSProcessingMethod", 0x001C: "GPSAreaInformation", 0x001D: "GPSDateStamp", 0x001E: "GPSDifferential" }; var StringValues = this.StringValues = { ExposureProgram: { 0: "Not defined", 1: "Manual", 2: "Normal program", 3: "Aperture priority", 4: "Shutter priority", 5: "Creative program", 6: "Action program", 7: "Portrait mode", 8: "Landscape mode" }, MeteringMode: { 0: "Unknown", 1: "Average", 2: "CenterWeightedAverage", 3: "Spot", 4: "MultiSpot", 5: "Pattern", 6: "Partial", 255: "Other" }, LightSource: { 0: "Unknown", 1: "Daylight", 2: "Fluorescent", 3: "Tungsten (incandescent light)", 4: "Flash", 9: "Fine weather", 10: "Cloudy weather", 11: "Shade", 12: "Daylight fluorescent (D 5700 - 7100K)", 13: "Day white fluorescent (N 4600 - 5400K)", 14: "Cool white fluorescent (W 3900 - 4500K)", 15: "White fluorescent (WW 3200 - 3700K)", 17: "Standard light A", 18: "Standard light B", 19: "Standard light C", 20: "D55", 21: "D65", 22: "D75", 23: "D50", 24: "ISO studio tungsten", 255: "Other" }, Flash: { 0x0000: "Flash did not fire", 0x0001: "Flash fired", 0x0005: "Strobe return light not detected", 0x0007: "Strobe return light detected", 0x0009: "Flash fired, compulsory flash mode", 0x000D: "Flash fired, compulsory flash mode, return light not detected", 0x000F: "Flash fired, compulsory flash mode, return light detected", 0x0010: "Flash did not fire, compulsory flash mode", 0x0018: "Flash did not fire, auto mode", 0x0019: "Flash fired, auto mode", 0x001D: "Flash fired, auto mode, return light not detected", 0x001F: "Flash fired, auto mode, return light detected", 0x0020: "No flash function", 0x0041: "Flash fired, red-eye reduction mode", 0x0045: "Flash fired, red-eye reduction mode, return light not detected", 0x0047: "Flash fired, red-eye reduction mode, return light detected", 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode", 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 0x0059: "Flash fired, auto mode, red-eye reduction mode", 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode", 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode" }, SensingMethod: { 1: "Not defined", 2: "One-chip color area sensor", 3: "Two-chip color area sensor", 4: "Three-chip color area sensor", 5: "Color sequential area sensor", 7: "Trilinear sensor", 8: "Color sequential linear sensor" }, SceneCaptureType: { 0: "Standard", 1: "Landscape", 2: "Portrait", 3: "Night scene" }, SceneType: { 1: "Directly photographed" }, CustomRendered: { 0: "Normal process", 1: "Custom process" }, WhiteBalance: { 0: "Auto white balance", 1: "Manual white balance" }, GainControl: { 0: "None", 1: "Low gain up", 2: "High gain up", 3: "Low gain down", 4: "High gain down" }, Contrast: { 0: "Normal", 1: "Soft", 2: "Hard" }, Saturation: { 0: "Normal", 1: "Low saturation", 2: "High saturation" }, Sharpness: { 0: "Normal", 1: "Soft", 2: "Hard" }, SubjectDistanceRange: { 0: "Unknown", 1: "Macro", 2: "Close view", 3: "Distant view" }, FileSource: { 3: "DSC" }, Components: { 0: "", 1: "Y", 2: "Cb", 3: "Cr", 4: "R", 5: "G", 6: "B" } }; function addEvent(element, event, handler) { if (element.addEventListener) { element.addEventListener(event, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + event, handler); } } function imageHasData(img) { return !!(img.exifdata); } function base64ToArrayBuffer(base64, contentType) { contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg' base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, ''); var binary = atob(base64); var len = binary.length; var buffer = new ArrayBuffer(len); var view = new Uint8Array(buffer); for (var i = 0; i < len; i++) { view[i] = binary.charCodeAt(i); } return buffer; } function objectURLToBlob(url, callback) { var http = new XMLHttpRequest(); http.open("GET", url, true); http.responseType = "blob"; http.onload = function(e) { if (this.status == 200 || this.status === 0) { callback(this.response); } }; http.send(); } function getImageData(img, callback) { function handleBinaryFile(binFile) { var data = findEXIFinJPEG(binFile); var iptcdata = findIPTCinJPEG(binFile); img.exifdata = data || {}; img.iptcdata = iptcdata || {}; if (callback) { callback.call(img); } } if (img.src) { if (/^data\:/i.test(img.src)) { // Data URI var arrayBuffer = base64ToArrayBuffer(img.src); handleBinaryFile(arrayBuffer); } else if (/^blob\:/i.test(img.src)) { // Object URL var fileReader = new FileReader(); fileReader.onload = function(e) { handleBinaryFile(e.target.result); }; objectURLToBlob(img.src, function(blob) { fileReader.readAsArrayBuffer(blob); }); } else { var http = new XMLHttpRequest(); http.onload = function() { if (this.status == 200 || this.status === 0) { handleBinaryFile(http.response); } else { throw "Could not load image"; } http = null; }; http.open("GET", img.src, true); http.responseType = "arraybuffer"; http.send(null); } } else if (window.FileReader && (img instanceof window.Blob || img instanceof window.File)) { var fileReader = new FileReader(); fileReader.onload = function(e) { if (debug) console.log("Got file of length " + e.target.result.byteLength); handleBinaryFile(e.target.result); }; fileReader.readAsArrayBuffer(img); } } function findEXIFinJPEG(file) { var dataView = new DataView(file); if (debug) console.log("Got file of length " + file.byteLength); if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { if (debug) console.log("Not a valid JPEG"); return false; // not a valid jpeg } var offset = 2, length = file.byteLength, marker; while (offset < length) { if (dataView.getUint8(offset) != 0xFF) { if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset)); return false; // not a valid marker, something is wrong } marker = dataView.getUint8(offset + 1); if (debug) console.log(marker); // we could implement handling for other markers here, // but we're only looking for 0xFFE1 for EXIF data if (marker == 225) { if (debug) console.log("Found 0xFFE1 marker"); return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2); // offset += 2 + file.getShortAt(offset+2, true); } else { offset += 2 + dataView.getUint16(offset + 2); } } } function findIPTCinJPEG(file) { var dataView = new DataView(file); if (debug) console.log("Got file of length " + file.byteLength); if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) { if (debug) console.log("Not a valid JPEG"); return false; // not a valid jpeg } var offset = 2, length = file.byteLength; var isFieldSegmentStart = function(dataView, offset) { return ( dataView.getUint8(offset) === 0x38 && dataView.getUint8(offset + 1) === 0x42 && dataView.getUint8(offset + 2) === 0x49 && dataView.getUint8(offset + 3) === 0x4D && dataView.getUint8(offset + 4) === 0x04 && dataView.getUint8(offset + 5) === 0x04 ); }; while (offset < length) { if (isFieldSegmentStart(dataView, offset)) { // Get the length of the name header (which is padded to an even number of bytes) var nameHeaderLength = dataView.getUint8(offset + 7); if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1; // Check for pre photoshop 6 format if (nameHeaderLength === 0) { // Always 4 nameHeaderLength = 4; } var startOffset = offset + 8 + nameHeaderLength; var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength); return readIPTCData(file, startOffset, sectionLength); break; } // Not the marker, continue searching offset++; } } var IptcFieldMap = { 0x78: 'caption', 0x6E: 'credit', 0x19: 'keywords', 0x37: 'dateCreated', 0x50: 'byline', 0x55: 'bylineTitle', 0x7A: 'captionWriter', 0x69: 'headline', 0x74: 'copyright', 0x0F: 'category' }; function readIPTCData(file, startOffset, sectionLength) { var dataView = new DataView(file); var data = {}; var fieldValue, fieldName, dataSize, segmentType, segmentSize; var segmentStartPos = startOffset; while (segmentStartPos < startOffset + sectionLength) { if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) { segmentType = dataView.getUint8(segmentStartPos + 2); if (segmentType in IptcFieldMap) { dataSize = dataView.getInt16(segmentStartPos + 3); segmentSize = dataSize + 5; fieldName = IptcFieldMap[segmentType]; fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize); // Check if we already stored a value with this name if (data.hasOwnProperty(fieldName)) { // Value already stored with this name, create multivalue field if (data[fieldName] instanceof Array) { data[fieldName].push(fieldValue); } else { data[fieldName] = [data[fieldName], fieldValue]; } } else { data[fieldName] = fieldValue; } } } segmentStartPos++; } return data; } function readTags(file, tiffStart, dirStart, strings, bigEnd) { var entries = file.getUint16(dirStart, !bigEnd), tags = {}, entryOffset, tag, i; for (i = 0; i < entries; i++) { entryOffset = dirStart + i * 12 + 2; tag = strings[file.getUint16(entryOffset, !bigEnd)]; if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd)); tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); } return tags; } function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { var type = file.getUint16(entryOffset + 2, !bigEnd), numValues = file.getUint32(entryOffset + 4, !bigEnd), valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart, offset, vals, val, n, numerator, denominator; switch (type) { case 1: // byte, 8-bit unsigned int case 7: // undefined, 8-bit byte, value depending on field if (numValues == 1) { return file.getUint8(entryOffset + 8, !bigEnd); } else { offset = numValues > 4 ? valueOffset : (entryOffset + 8); vals = []; for (n = 0; n < numValues; n++) { vals[n] = file.getUint8(offset + n); } return vals; } case 2: // ascii, 8-bit byte offset = numValues > 4 ? valueOffset : (entryOffset + 8); return getStringFromDB(file, offset, numValues - 1); case 3: // short, 16 bit int if (numValues == 1) { return file.getUint16(entryOffset + 8, !bigEnd); } else { offset = numValues > 2 ? valueOffset : (entryOffset + 8); vals = []; for (n = 0; n < numValues; n++) { vals[n] = file.getUint16(offset + 2 * n, !bigEnd); } return vals; } case 4: // long, 32 bit int if (numValues == 1) { return file.getUint32(entryOffset + 8, !bigEnd); } else { vals = []; for (n = 0; n < numValues; n++) { vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd); } return vals; } case 5: // rational = two long values, first is numerator, second is denominator if (numValues == 1) { numerator = file.getUint32(valueOffset, !bigEnd); denominator = file.getUint32(valueOffset + 4, !bigEnd); val = new Number(numerator / denominator); val.numerator = numerator; val.denominator = denominator; return val; } else { vals = []; for (n = 0; n < numValues; n++) { numerator = file.getUint32(valueOffset + 8 * n, !bigEnd); denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd); vals[n] = new Number(numerator / denominator); vals[n].numerator = numerator; vals[n].denominator = denominator; } return vals; } case 9: // slong, 32 bit signed int if (numValues == 1) { return file.getInt32(entryOffset + 8, !bigEnd); } else { vals = []; for (n = 0; n < numValues; n++) { vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd); } return vals; } case 10: // signed rational, two slongs, first is numerator, second is denominator if (numValues == 1) { return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd); } else { vals = []; for (n = 0; n < numValues; n++) { vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * n, !bigEnd); } return vals; } } } function getStringFromDB(buffer, start, length) { var outstr = ""; for (var n = start; n < start + length; n++) { outstr += String.fromCharCode(buffer.getUint8(n)); } return outstr; } function readEXIFData(file, start) { if (getStringFromDB(file, start, 4) != "Exif") { if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4)); return false; } var bigEnd, tags, tag, exifData, gpsData, tiffOffset = start + 6; // test for TIFF validity and endianness if (file.getUint16(tiffOffset) == 0x4949) { bigEnd = false; } else if (file.getUint16(tiffOffset) == 0x4D4D) { bigEnd = true; } else { if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"); return false; } if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002A) { if (debug) console.log("Not valid TIFF data! (no 0x002A)"); return false; } var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd); if (firstIFDOffset < 0x00000008) { if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset + 4, !bigEnd)); return false; } tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd); if (tags.ExifIFDPointer) { exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd); for (tag in exifData) { switch (tag) { case "LightSource": case "Flash": case "MeteringMode": case "ExposureProgram": case "SensingMethod": case "SceneCaptureType": case "SceneType": case "CustomRendered": case "WhiteBalance": case "GainControl": case "Contrast": case "Saturation": case "Sharpness": case "SubjectDistanceRange": case "FileSource": exifData[tag] = StringValues[tag][exifData[tag]]; break; case "ExifVersion": case "FlashpixVersion": exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]); break; case "ComponentsConfiguration": exifData[tag] = StringValues.Components[exifData[tag][0]] + StringValues.Components[exifData[tag][1]] + StringValues.Components[exifData[tag][2]] + StringValues.Components[exifData[tag][3]]; break; } tags[tag] = exifData[tag]; } } if (tags.GPSInfoIFDPointer) { gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd); for (tag in gpsData) { switch (tag) { case "GPSVersionID": gpsData[tag] = gpsData[tag][0] + "." + gpsData[tag][1] + "." + gpsData[tag][2] + "." + gpsData[tag][3]; break; } tags[tag] = gpsData[tag]; } } return tags; } this.getData = function(img, callback) { if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) return false; if (!imageHasData(img)) { getImageData(img, callback); } else { if (callback) { callback.call(img); } } return true; } this.getTag = function(img, tag) { if (!imageHasData(img)) return; return img.exifdata[tag]; } this.getAllTags = function(img) { if (!imageHasData(img)) return {}; var a, data = img.exifdata, tags = {}; for (a in data) { if (data.hasOwnProperty(a)) { tags[a] = data[a]; } } return tags; } this.pretty = function(img) { if (!imageHasData(img)) return ""; var a, data = img.exifdata, strPretty = ""; for (a in data) { if (data.hasOwnProperty(a)) { if (typeof data[a] == "object") { if (data[a] instanceof Number) { strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n"; } else { strPretty += a + " : [" + data[a].length + " values]\r\n"; } } else { strPretty += a + " : " + data[a] + "\r\n"; } } } return strPretty; } this.readFromBinaryFile = function(file) { return findEXIFinJPEG(file); } }]); crop.factory('cropHost', ['$document', 'cropAreaCircle', 'cropAreaSquare', 'cropEXIF', function($document, CropAreaCircle, CropAreaSquare, cropEXIF) { /* STATIC FUNCTIONS */ // Get Element's Offset var getElementOffset = function(elem) { var box = elem.getBoundingClientRect(); var body = document.body; var docElem = document.documentElement; var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; var clientTop = docElem.clientTop || body.clientTop || 0; var clientLeft = docElem.clientLeft || body.clientLeft || 0; var top = box.top + scrollTop - clientTop; var left = box.left + scrollLeft - clientLeft; return { top: Math.round(top), left: Math.round(left) }; }; return function(elCanvas, opts, events) { /* PRIVATE VARIABLES */ // Object Pointers var ctx = null, image = null, theArea = null; // Dimensions var minCanvasDims = [100, 100], maxCanvasDims = [300, 300]; // Result Image size var resImgSize = 200; //图片剪切比例 var resSizeScale = 1; // Result Image type var resImgFormat = 'image/png'; // Result Image quality var resImgQuality = null; /* PRIVATE FUNCTIONS */ // Draw Scene function drawScene() { // clear canvas ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); if (image !== null) { // draw source image ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); ctx.save(); // and make it darker ctx.fillStyle = 'rgba(0, 0, 0, 0.65)'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.restore(); // draw Area theArea.draw(); } } // Resets CropHost var resetCropHost = function() { if (image !== null) { theArea.setImage(image); var imageDims = [image.width, image.height], imageRatio = image.width / image.height, canvasDims = imageDims; if (canvasDims[0] > maxCanvasDims[0]) { canvasDims[0] = maxCanvasDims[0]; canvasDims[1] = canvasDims[0] / imageRatio; } else if (canvasDims[0] < minCanvasDims[0]) { canvasDims[0] = minCanvasDims[0]; canvasDims[1] = canvasDims[0] / imageRatio; } if (canvasDims[1] > maxCanvasDims[1]) { canvasDims[1] = maxCanvasDims[1]; canvasDims[0] = canvasDims[1] * imageRatio; } else if (canvasDims[1] < minCanvasDims[1]) { canvasDims[1] = minCanvasDims[1]; canvasDims[0] = canvasDims[1] * imageRatio; } elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]); theArea.setX(ctx.canvas.width / 2); theArea.setY(ctx.canvas.height / 2); theArea.setSize(Math.min(200, ctx.canvas.width / 2, ctx.canvas.height / 2)); } else { elCanvas.prop('width', 0).prop('height', 0).css({ 'margin-top': 0 }); } drawScene(); }; /** * Returns event.changedTouches directly if event is a TouchEvent. * If event is a jQuery event, return changedTouches of event.originalEvent */ var getChangedTouches = function(event) { if (angular.isDefined(event.changedTouches)) { return event.changedTouches; } else { return event.originalEvent.changedTouches; } }; var onMouseMove = function(e) { if (image !== null) { var offset = getElementOffset(ctx.canvas), pageX, pageY; if (e.type === 'touchmove') { pageX = getChangedTouches(e)[0].pageX; pageY = getChangedTouches(e)[0].pageY; } else { pageX = e.pageX; pageY = e.pageY; } theArea.processMouseMove(pageX - offset.left, pageY - offset.top); drawScene(); } }; var onMouseDown = function(e) { e.preventDefault(); e.stopPropagation(); if (image !== null) { var offset = getElementOffset(ctx.canvas), pageX, pageY; if (e.type === 'touchstart') { pageX = getChangedTouches(e)[0].pageX; pageY = getChangedTouches(e)[0].pageY; } else { pageX = e.pageX; pageY = e.pageY; } theArea.processMouseDown(pageX - offset.left, pageY - offset.top); drawScene(); } }; var onMouseUp = function(e) { if (image !== null) { var offset = getElementOffset(ctx.canvas), pageX, pageY; if (e.type === 'touchend') { pageX = getChangedTouches(e)[0].pageX; pageY = getChangedTouches(e)[0].pageY; } else { pageX = e.pageX; pageY = e.pageY; } theArea.processMouseUp(pageX - offset.left, pageY - offset.top); drawScene(); } }; this.getResultImageDataURI = function() { var temp_ctx, temp_canvas; temp_canvas = angular.element('')[0]; temp_ctx = temp_canvas.getContext('2d'); temp_canvas.width = resImgSize; temp_canvas.height = resImgSize / resSizeScale; if (image !== null) { temp_ctx.drawImage(image, (theArea.getX() - theArea.getSize() / 2) * (image.width / ctx.canvas.width), (theArea.getY() - theArea.getSize() / 2 / resSizeScale) * (image.height / ctx.canvas.height), theArea.getSize() * (image.width / ctx.canvas.width), theArea.getSize() * (image.height / ctx.canvas.height) / resSizeScale, 0, 0, resImgSize, resImgSize / resSizeScale); } if (resImgQuality !== null) { return temp_canvas.toDataURL(resImgFormat, resImgQuality); } return temp_canvas.toDataURL(resImgFormat); }; this.setNewImageSource = function(imageSource) { image = null; resetCropHost(); events.trigger('image-updated'); if (!!imageSource) { var newImage = new Image(); if (imageSource.substring(0, 4).toLowerCase() === 'http') { newImage.crossOrigin = 'anonymous'; } newImage.onload = function() { events.trigger('load-done'); cropEXIF.getData(newImage, function() { var orientation = cropEXIF.getTag(newImage, 'Orientation'); if ([3, 6, 8].indexOf(orientation) > -1) { var canvas = document.createElement("canvas"), ctx = canvas.getContext("2d"), cw = newImage.width, ch = newImage.height, cx = 0, cy = 0, deg = 0; switch (orientation) { case 3: cx = -newImage.width; cy = -newImage.height; deg = 180; break; case 6: cw = newImage.height; ch = newImage.width; cy = -newImage.height; deg = 90; break; case 8: cw = newImage.height; ch = newImage.width; cx = -newImage.width; deg = 270; break; } canvas.width = cw; canvas.height = ch; ctx.rotate(deg * Math.PI / 180); ctx.drawImage(newImage, cx, cy); image = new Image(); image.src = canvas.toDataURL("image/png"); } else { image = newImage; } resetCropHost(); events.trigger('image-updated'); }); }; newImage.onerror = function() { events.trigger('load-error'); }; events.trigger('load-start'); newImage.src = imageSource; } }; this.setMaxDimensions = function(width, height) { maxCanvasDims = [width, height]; if (image !== null) { var curWidth = ctx.canvas.width, curHeight = ctx.canvas.height; var imageDims = [image.width, image.height], imageRatio = image.width / image.height, canvasDims = imageDims; if (canvasDims[0] > maxCanvasDims[0]) { canvasDims[0] = maxCanvasDims[0]; canvasDims[1] = canvasDims[0] / imageRatio; } else if (canvasDims[0] < minCanvasDims[0]) { canvasDims[0] = minCanvasDims[0]; canvasDims[1] = canvasDims[0] / imageRatio; } if (canvasDims[1] > maxCanvasDims[1]) { canvasDims[1] = maxCanvasDims[1]; canvasDims[0] = canvasDims[1] * imageRatio; } else if (canvasDims[1] < minCanvasDims[1]) { canvasDims[1] = minCanvasDims[1]; canvasDims[0] = canvasDims[1] * imageRatio; } elCanvas.prop('width', canvasDims[0]).prop('height', canvasDims[1]); var ratioNewCurWidth = ctx.canvas.width / curWidth, ratioNewCurHeight = ctx.canvas.height / curHeight, ratioMin = Math.min(ratioNewCurWidth, ratioNewCurHeight); theArea.setX(theArea.getX() * ratioNewCurWidth); theArea.setY(theArea.getY() * ratioNewCurHeight); theArea.setSize(theArea.getSize() * ratioMin); } else { elCanvas.prop('width', 0).prop('height', 0).css({ 'margin-top': 0 }); } drawScene(); }; this.setAreaMinSize = function(size) { size = parseInt(size, 10); if (!isNaN(size)) { theArea.setMinSize(size); drawScene(); } }; this.setResultImageSize = function(size, scale) { size = parseInt(size, 10); resSizeScale = scale || 1; if (!isNaN(size)) { resImgSize = size; } }; this.setResultImageFormat = function(format) { resImgFormat = format; }; this.setResultImageQuality = function(quality) { quality = parseFloat(quality); if (!isNaN(quality) && quality >= 0 && quality <= 1) { resImgQuality = quality; } }; this.setAreaType = function(type, scale) { var curSize = theArea.getSize(), curMinSize = theArea.getMinSize(), curX = theArea.getX(), curY = theArea.getY(); var AreaClass = CropAreaCircle; if (type === 'square') { AreaClass = CropAreaSquare; } theArea = new AreaClass(ctx, events); theArea.setMinSize(curMinSize); theArea.resSizeScale = scale||1; theArea.setSize(curSize); theArea.setX(curX); theArea.setY(curY); // resetCropHost(); if (image !== null) { theArea.setImage(image); } drawScene(); }; /* Life Cycle begins */ // Init Context var ctx = elCanvas[0].getContext('2d'); // Init CropArea theArea = new CropAreaCircle(ctx, events); // Init Mouse Event Listeners $document.on('mousemove', onMouseMove); elCanvas.on('mousedown', onMouseDown); $document.on('mouseup', onMouseUp); // Init Touch Event Listeners $document.on('touchmove', onMouseMove); elCanvas.on('touchstart', onMouseDown); $document.on('touchend', onMouseUp); // CropHost Destructor this.destroy = function() { $document.off('mousemove', onMouseMove); elCanvas.off('mousedown', onMouseDown); $document.off('mouseup', onMouseMove); $document.off('touchmove', onMouseMove); elCanvas.off('touchstart', onMouseDown); $document.off('touchend', onMouseMove); elCanvas.remove(); }; }; }]); crop.factory('cropPubSub', [function() { return function() { var events = {}; // Subscribe this.on = function(names, handler) { names.split(' ').forEach(function(name) { if (!events[name]) { events[name] = []; } events[name].push(handler); }); return this; }; // Publish this.trigger = function(name, args) { angular.forEach(events[name], function(handler) { handler.call(null, args); }); return this; }; }; }]); crop.directive('imgCrop', ['$timeout', 'cropHost', 'cropPubSub', function($timeout, CropHost, CropPubSub) { return { restrict: 'E', scope: { image: '=', resultImage: '=', resultBlob: '=', changeOnFly: '=', areaType: '@', areaMinSize: '=', resultImageSize: '=', sizeScale: '=', resultImageFormat: '@', resultImageQuality: '=', onChange: '&', onLoadBegin: '&', onLoadDone: '&', onLoadError: '&' }, template: '', controller: ['$scope', function($scope) { $scope.events = new CropPubSub(); }], link: function(scope, element /*, attrs*/ ) { // Init Events Manager var events = scope.events; // Init Crop Host var cropHost = new CropHost(element.find('canvas'), {}, events); // Store Result Image to check if it's changed var storedResultImage; function dataURItoBlob(dataURI) { var byteString, mimestring; if (dataURI.split(',')[0].indexOf('base64') !== -1) { byteString = atob(dataURI.split(',')[1]); } else { byteString = decodeURI(dataURI.split(',')[1]); } mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]; var content = new Array(); for (var i = 0; i < byteString.length; i++) { content[i] = byteString.charCodeAt(i); } return new Blob([new Uint8Array(content)], { type: mimestring }); } var updateResultImage = function(scope) { var resultImage = cropHost.getResultImageDataURI(); if (storedResultImage !== resultImage) { storedResultImage = resultImage; if (angular.isDefined(scope.resultImage)) { scope.resultBlob = dataURItoBlob(resultImage); scope.resultImage = resultImage; } scope.onChange({ $dataURI: scope.resultImage }); } }; // Wrapper to safely exec functions within $apply on a running $digest cycle var fnSafeApply = function(fn) { return function() { $timeout(function() { scope.$apply(function(scope) { fn(scope); }); }); }; }; // Setup CropHost Event Handlers events .on('load-start', fnSafeApply(function(scope) { scope.onLoadBegin({}); })) .on('load-done', fnSafeApply(function(scope) { scope.onLoadDone({}); })) .on('load-error', fnSafeApply(function(scope) { scope.onLoadError({}); })) .on('area-move area-resize', fnSafeApply(function(scope) { if (!!scope.changeOnFly) { updateResultImage(scope); } })) .on('area-move-end area-resize-end image-updated', fnSafeApply(function(scope) { updateResultImage(scope); })); // Sync CropHost with Directive's options scope.$watch('image', function() { cropHost.setNewImageSource(scope.image); }); scope.$watch('areaType', function() { cropHost.setAreaType(scope.areaType, scope.sizeScale); updateResultImage(scope); }); scope.$watch('areaMinSize', function() { cropHost.setAreaMinSize(scope.areaMinSize); updateResultImage(scope); }); scope.$watch('resultImageSize', function() { cropHost.setResultImageSize(scope.resultImageSize, scope.sizeScale); updateResultImage(scope); }); scope.$watch('resultImageFormat', function() { cropHost.setResultImageFormat(scope.resultImageFormat); updateResultImage(scope); }); scope.$watch('resultImageQuality', function() { cropHost.setResultImageQuality(scope.resultImageQuality); updateResultImage(scope); }); // Update CropHost dimensions when the directive element is resized $timeout(function() { cropHost.setMaxDimensions(element[0].clientWidth, element[0].clientHeight - 4); updateResultImage(scope); }, true) // scope.$watch( // function () { // return [element[0].clientWidth, element[0].clientHeight]; // }, // function (value) { // cropHost.setMaxDimensions(value[0],value[1]); // updateResultImage(scope); // }, // true // ); // Destroy CropHost Instance when the directive is destroying scope.$on('$destroy', function() { cropHost.destroy(); }); } }; }]); }());