1.本人声明是在别人的一个老文章上面修改的。
2.原文 https://github.com/alexk111/ngImgCrop
3.我的使用
/*!
* ngImgCrop liugongliang yuanchuang
* ngImgCrop xinyifangwu
*
* Copyright (c) 2020
*
*
* Generated at liugongliang, December 3rd, 2020, 17:59: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 = 500;
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=500;
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*3/4], 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._size =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._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=1000;
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;
var ySize = hSize * 3 / 4;
return [
[this._x - hSize, this._y - ySize],
[this._x + hSize, this._y - ySize],
[this._x - hSize, this._y + ySize],
[this._x + hSize, this._y + ySize]
];
};
CropAreaSquare.prototype._calcSquareDimensions=function() {
var hSize = this._size / 2;
var ySize = hSize * 3 / 4;
return {
left: this._x-hSize,
top: this._y - ySize,
right: this._x+hSize,
bottom: this._y + ySize
};
};
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){
var hSize = size / 2;
var Sizeh = size * 3 / 4;
ctx.rect(centerCoords[0] - hSize, centerCoords[1] - hSize * 3 / 4, size, Sizeh);
ctx.rect(centerCoords[0] - hSize, centerCoords[1] - hSize * 3 / 4, size, Sizeh);
};
//一直加载
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=500;
this._cropCanvas=new CropCanvas(ctx);
this._image=new Image();
this._x = 0;
this._y = 0;
this._size = 500;
};
/* 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 = 0;
this._size = size;
this._size = Math.max(this._minSize, this._size);
this._dontDragOutside();
};
/* FUNCTIONS */
//定义不要拖到外面的尺寸
CropArea.prototype._dontDragOutside=function() {
var h=this._ctx.canvas.height,
w = this._ctx.canvas.width;
if (w >= h) {//宽大于高
var h1 = h;
h = h * 4 / 3;
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*3/8) { this._y=this._size*3/8;}
if (this._y > h1 - this._size * 3/8) { this._y = h1 - this._size*3/8; }//这里要修改尺寸
}else{//高大于宽
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*3/8) { this._y=this._size*3/8; }
if (this._y > h - this._size *3/ 8) { this._y = h - this._size * 3 / 8; }//这里要修改尺寸
}
};
CropArea.prototype._drawArea=function() {};
CropArea.prototype.draw=function() {
// draw crop area
this._cropCanvas.drawCropArea(this._image,[this._x,this._y],this._size,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]];
};
// 画填充多边形
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*3/4);
ctx.strokeRect(centerCoords[0] - scaledBoxSize / 2, centerCoords[1] - scaledBoxSize / 2, scaledBoxSize, scaledBoxSize*3/4);
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, 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);
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);
ctx.stroke();
ctx.clip();
ctx.restore();
// cropHost.setAreaMinSize(xRatio);
};
};
}]);
/**
* EXIF service
*/
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 + "]
";
} else {
strPretty += a + " : [" + data[a].length + " values]
";
}
} else {
strPretty += a + " : " + data[a] + "
";
}
}
}
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=[500,500],
maxCanvasDims=[500,500];
// Result Image size
var resImgSize=500;
// Result Image type
var resImgFormat='image/png';
// Result Image quality
var resImgQuality=null;
/* PRIVATE FUNCTIONS */
// 画那个裁剪框框
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]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
theArea.setX(ctx.canvas.width/2);
theArea.setY(ctx.canvas.height/2);
theArea.setSize(Math.max(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('<canvas></canvas>')[0];
temp_ctx = temp_canvas.getContext('2d');
temp_canvas.width = resImgSize;
// temp_canvas.height = 400;
temp_canvas.height = resImgSize*7/10;
if(image!==null){
temp_ctx.drawImage(image, (theArea.getX()-theArea.getSize()/2)*(image.width/ctx.canvas.width), (theArea.getY()-theArea.getSize()*3/8)*(image.height/ctx.canvas.height), theArea.getSize()*(image.width/ctx.canvas.width), theArea.getSize()*(image.height/ctx.canvas.height)*3/4, 0, 0, resImgSize, resImgSize*3/4);
}
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) {
const bgImg = new Image();
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(bgImg, 0, 0, 260, 900);
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]).css({'margin-left': -canvasDims[0]/2+'px', 'margin-top': -canvasDims[1]/2+'px'});
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();
}
size = -1;
};
this.setResultImageSize=function(size) {
size=parseInt(size,10);
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) {
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.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: '=',
changeOnFly: '=',
areaType: '@',
areaMinSize: '=',
resultImageSize: '=',
resultImageFormat: '@',
resultImageQuality: '=',
onChange: '&',
onLoadBegin: '&',
onLoadDone: '&',
onLoadError: '&'
},
template: '<canvas></canvas>',
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);
// 存储结果图像以检查其是否已更改
var storedResultImage;
//更新裁剪好的图片的url
var updateResultImage = function (scope) {
var resultImage=cropHost.getResultImageDataURI();
if(storedResultImage!==resultImage) {
storedResultImage=resultImage;
if(angular.isDefined(scope.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);
updateResultImage(scope);
});
scope.$watch('areaMinSize',function(){
cropHost.setAreaMinSize(scope.areaMinSize);
updateResultImage(scope);
});
scope.$watch('resultImageSize',function(){
cropHost.setResultImageSize(scope.resultImageSize);
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
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();
});
}
};
}]);
}());