piquinikis-color-picker
Version:
Angular-Material inspired color picker.
677 lines (509 loc) • 19.2 kB
JavaScript
// Video.js CoreObject for a quick and dirty inheritance method.
/////////////////////////////////
var obj = {};
obj.create = Object.create || function(obj){
//Create a new function called 'F' which is just an empty object.
function F() {}
//the prototype of the 'F' function should point to the
//parameter of the anonymous function.
F.prototype = obj;
//create a new constructor function based off of the 'F' function.
return new F();
};
var CoreObject = function(){};
/**
* Create a new object that inherits from this Object
*
* var Animal = CoreObject.extend();
* var Horse = Animal.extend();
*
* @param {Object} props Functions and properties to be applied to the new object's prototype
* @return {CoreObject} An object that inherits from CoreObject
* @this {*}
*/
CoreObject.extend = function(props){
var init, subObj;
props = props || {};
init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};
subObj = function(){
init.apply(this, arguments);
};
subObj.prototype = obj.create(this.prototype);
subObj.prototype.constructor = subObj;
// Make the class extendable
subObj.extend = CoreObject.extend;
// Make a function for creating instances
subObj.create = CoreObject.create;
// Extend subObj's prototype with functions and other properties from props
for (var name in props) {
if (props.hasOwnProperty(name)) {
subObj.prototype[name] = props[name];
}
}
return subObj;
};
/**
* Create a new instace of this Object class
*
* var myAnimal = Animal.create();
*
* @return {CoreObject} An instance of a CoreObject subclass
* @this {*}
*/
CoreObject.create = function(){
// Create a new object that inherits from this object's prototype
var inst = obj.create(this.prototype);
// Apply this constructor function to the new object
this.apply(inst, arguments);
// Return the new object
return inst;
};
var x = 0;
var GradientCanvas = CoreObject.extend( {
init: function( $element, $scope, restrictX ) {
// this.type = type;
// this.restrictX = restrictX;
this.offset = {
x: null,
y: null
};
// Colors values are 0-255, thus we need the height to be 255.
this.height = 256;
this.type = this.type || "gCanvas";
this.id = 'gCanvas('+ this.type + ')_' + ( ++x ) + '_' + Date.now();
// The element
this.$element = $element;
this.$element.prop('id', this.id );
// The current scope. Mainly used for firing events
this.$scope = $scope;
// The canvas, the context, and the marker.
this.canvas = this.$element.children()[0];
this.context = this.canvas.getContext('2d');
this.marker = this.$element.children()[1];
// The current color.
//this.color = $scope.data.color || tinycolor('#f00');
// Set the dimensions
this.$element.css({'height': this.height + 'px'});
this.canvas.height = this.height;
this.canvas.width = this.height;
//console.log("G CANVAS INIT", this, this.$scope );
this.boundEvents = {
onMouseDown: angular.bind( this, this.onMouseDown ),
mouseMove: angular.bind( this, this.onMouseMove ),
onColorSet: angular.bind( this, this.onColorSet ),
draw: angular.bind( this, this.draw )
};
// Events
this.$element.on( 'touchstart mousedown', this.boundEvents.onMouseDown );
// this.$scope.$on( 'mdColorPicker:colorSet', this.boundEvents.onColorSet );
this.$scope.data.hsva = this.$scope.data.hsva || this.$scope.data.color.toHsv();
if ( this.$scope.data.hsva.a === undefined ) {
this.$scope.data.hsva.a = this.$scope.data.color.getAlpha();
}
var initialValueWatch = this.$scope.$watch('data.color', angular.bind( this, function(color) {
if ( color === undefined ) {
return;
}
this.$scope.data.hsva.h = color.toHsv().h;
initialValueWatch();
} ), true );
this.$scope.$watch('data.color', this.boundEvents.onColorSet, true );
}
});
GradientCanvas.prototype.$window = angular.element( window );
GradientCanvas.prototype.applyHueLock = false;
/**
* GradientCanvas.prototype.draw - Overwrite this in sub class. Will fill with a "troublesome pink"
*
* @return {type} description
*/
GradientCanvas.prototype.draw = function() {
this.context.fillStyle = '#ff00ff';
this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );
};
/**
* GradientCanvas.prototype.getColorByMouse - Returns the color under the mouse.
*
* @param {Event} e Mouse or Touch Event
* @return {Color}
*/
GradientCanvas.prototype.getMouseCoordinates = function( e ) {
var te = e.touches && e.touches[0];
var pageX = te && te.clientX || e.clientX;
var pageY = te && te.clientY || e.clientY;
console.log( e, pageX, this.offset.x );
var x = Math.round( pageX - this.offset.x );
var y = Math.round( pageY - this.offset.y );
return this.adjustXY( x, y );
};
GradientCanvas.prototype.getColorByPoint = function( x, y ) {
// Stub
};
GradientCanvas.prototype.onMouseDown = function( e ) {
// Prevent highlighting
e.preventDefault();
e.stopImmediatePropagation();
this.$scope.previewUnfocus();
this.$element.css({ 'cursor': 'none' });
this.offset.x = this.canvas.getBoundingClientRect().left;
this.offset.y = this.canvas.getBoundingClientRect().top;
this.$window.on('touchmove mousemove', this.boundEvents.mouseMove );
this.$window.one('touchend mouseup', angular.bind(this, function (e) {
this.$window.off('touchmove mousemove', this.boundEvents.mouseMove);
this.$element.css({ 'cursor': 'crosshair' });
}));
// Set the color on click ( mouse down )
this.onMouseMove( e );
};
GradientCanvas.prototype.onMouseMove = function( e ) {
var coords = this.getMouseCoordinates( e );
var color = this.getColorByPoint( coords.x, coords.y );
this.$scope.$apply( angular.bind( this, function() {
this.$scope.data.color = tinycolor( color );
this.$scope.data.color.setAlpha( color.a );
this.$scope.data.hsva = color;
console.log( this.$scope.data.hsva.h );
}));
this.setMarkerCenter( coords.x, coords.y );
};
GradientCanvas.prototype.setMarkerCenter = function( x, y ) {
var xOffset = -1 * this.marker.offsetWidth / 2;
var yOffset = -1 * this.marker.offsetHeight / 2;
var xAdjusted, xFinal, yAdjusted, yFinal;
if ( y === undefined ) {
y = x;
x = 0;
}
var coords = this.adjustXY( x, y );
x = coords.x;
y = coords.y;
xAdjusted = x + xOffset;
yAdjusted = y + yOffset;
if ( y === undefined || this.ignoreX ) {
xFinal = 0;
yFinal = Math.round( Math.max( Math.min( this.height-1 + yOffset, yAdjusted), yOffset ) );
// Debug output
// console.log( "Raw: ", x+','+y, "Adjusted: ", xAdjusted + ',' + yAdjusted, "Final: ", xFinal + ',' + yFinal );
} else {
xFinal = Math.floor( Math.max( Math.min( this.height + xOffset, xAdjusted ), xOffset ) );
yFinal = Math.floor( Math.max( Math.min( this.height + yOffset, yAdjusted ), yOffset ) );
}
angular.element(this.marker).css({'left': xFinal + 'px' });
angular.element(this.marker).css({'top': yFinal + 'px'});
};
GradientCanvas.prototype.getMarkerCenter = function() {
var returnObj = {
x: this.marker.offsetLeft + ( Math.floor( this.marker.offsetWidth / 2 ) ),
y: this.marker.offsetTop + ( Math.floor( this.marker.offsetHeight / 2 ) )
};
return returnObj;
};
GradientCanvas.prototype.getImageData = function( x, y ) {
// Stub
};
GradientCanvas.prototype.adjustXY = function( x, y ) {
x = Math.max( 0, Math.min( x, this.canvas.width ) );
y = Math.max( 0, Math.min( y, this.canvas.height ) );
return { x: x, y: y };
};
var HueGradientCanvas = GradientCanvas.extend({
init: function ($element, color) {
this.type = 'hue';
this.ignoreX = true;
GradientCanvas.apply( this, arguments );
this.draw();
},
draw: function() {
// Create gradient
var hueGrd = this.context.createLinearGradient(90, 0.000, 90, this.height);
// Add colors
hueGrd.addColorStop(0, 'rgba(255, 0, 0, 1.000)');
hueGrd.addColorStop(1/6, 'rgba(255, 255, 0, 1.000)');
hueGrd.addColorStop(2/6, 'rgba(0, 255, 0, 1.000)');
hueGrd.addColorStop(3/6, 'rgba(0, 255, 255, 1.000)');
hueGrd.addColorStop(4/6, 'rgba(0, 0, 255, 1.000)');
hueGrd.addColorStop(5/6, 'rgba(255, 0, 255, 1.000)');
hueGrd.addColorStop(1, 'rgba(255, 0, 0, 1.000)');
// Fill with gradient
this.context.fillStyle = hueGrd;
this.context.fillRect( 0, 0, this.canvas.width, this.height );
},
getColorByPoint: function( x, y ) {
var currentHSV = this.$scope.data.color.toHsv();
var h = 360 * ( y / this.height );
var s = currentHSV.s;
var v = currentHSV.v;
var a = this.$scope.data.color.getAlpha();
return {
h: h,
s: s,
v: v,
a: a
};
},
onColorSet: function( e, args ) {
var hue = this.$scope.data.hsva.h || this.$scope.data.color.toHsv().h;
this.setMarkerCenter( this.canvas.height * ( hue / 360 ) );
}
});
var AlphaGradientCanvas = GradientCanvas.extend({
init: function( $element, $scope ) {
this.type = 'alpha';
this.ignoreX = true;
GradientCanvas.apply( this, arguments );
this.$scope.$watch( 'data.color', this.boundEvents.draw, true );
},
draw: function () {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Create gradient
var hueGrd = this.context.createLinearGradient(90, 0.000, 90, this.height);
var colorRGB = this.$scope.data.color.toRgb();
// Add colors
hueGrd.addColorStop(0, 'rgba(' + colorRGB.r + ',' + colorRGB.g + ',' + colorRGB.b + ', 1.000)');
hueGrd.addColorStop(1, 'rgba(' + colorRGB.r + ',' + colorRGB.g + ',' + colorRGB.b + ', 0.000)');
// Fill with gradient
this.context.fillStyle = hueGrd;
this.context.fillRect( 0, 0, this.canvas.width, this.height );
},
getColorByPoint: function( x, y ) {
var currentHSV = this.$scope.data.color.toHsv();
var h = currentHSV.h;
var s = currentHSV.s;
var v = currentHSV.v;
var a = ( this.height - y ) / this.height;
return {
h: h,
s: s,
v: v,
a: a
};
},
onColorSet: function( e, args ) {
//console.log('alpha onColorSet', this.$scope.data.color);
//this.draw();
var alpha = this.$scope.data.color.getAlpha();
this.$scope.data.hsva.a = alpha;
var pos = this.canvas.height - ( this.canvas.height * alpha );
this.setMarkerCenter( pos );
}
});
var SpectrumGradientCanvas = GradientCanvas.extend({
init: function( $element, $scope ) {
this.type = 'spectrum';
GradientCanvas.apply( this, arguments );
},
draw: function() {
console.log("DRAWING SPECTRUM!");
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
// White gradient
var whiteGrd = this.context.createLinearGradient(0, 0, this.canvas.width, 0);
whiteGrd.addColorStop(0.01, 'rgba(255, 255, 255, 1.000)');
whiteGrd.addColorStop(0.99, 'rgba(255, 255, 255, 0.000)');
// Black Gradient
var blackGrd = this.context.createLinearGradient(0, 0, 0, this.canvas.height);
blackGrd.addColorStop(0.01, 'rgba(0, 0, 0, 0.000)');
blackGrd.addColorStop(0.99, 'rgba(0, 0, 0, 1.000)');
// Fill with solid
console.log( "DRAW HUE", this.$scope.data );
this.context.fillStyle = 'hsl( ' + this.$scope.data.hsva.h + ', 100%, 50%)';
this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );
// Fill with white
this.context.fillStyle = whiteGrd;
this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );
// Fill with black
// Odd bug prevented selecting min, max ranges from all gradients
this.context.fillStyle = blackGrd;
this.context.fillRect( 0, 0, this.canvas.width, this.canvas.height );
},
getColorByPoint: function( x, y ) {
var currentHSV = this.$scope.data.color.toHsv();
var h = this.$scope.data.hsva.h;
var s = x / this.height;
var v = ( this.height - y ) / this.height;
var a = this.$scope.data.hsva.a;
return {
h: h,
s: s,
v: v,
a: a
};
},
onColorSet: function( e, args ) {
console.log( 'SET SPECTRUM COLOR', this.$scope.data );
var hsv = this.$scope.data.color.toHsv();
this.currentHue = this.$scope.data.hsva.h || hsv.h;
this.draw();
var posX = this.canvas.width * hsv.s;
var posY = this.canvas.height - ( this.canvas.height * hsv.v );
this.setMarkerCenter( posX, posY );
},
setColor: function( color ) {
this.draw();
}
});
var WheelGradientCanvas = SpectrumGradientCanvas.extend({
init: function( $element, $scope ) {
this.gray = 255;
this.type = 'wheel';
GradientCanvas.apply( this, arguments );
// Initial (and only) drawing
this.draw();
},
draw: function() {
this.context.clearRect(0, 0, this.width, this.height);
var r = this.height / 2;
var PI = Math.PI;
var TWO_PI = PI * 2;
new ConicalGradient()
.addColorStop(0 , [255, 0, 0]) // Red
.addColorStop(1 / 6, [255, 255, 0]) // Yellow
.addColorStop(2 / 6, [0, 255, 0]) // Green
.addColorStop(3 / 6, [0, 255, 255]) // Cyan
.addColorStop(4 / 6, [0, 0, 255]) // Blue
.addColorStop(5 / 6, [255, 0, 255]) // Violet
.addColorStop(1 , [255, 0, 0]) // Red
.fill( this.context, r, r, r, ( PI / 180 ), ( PI / 180 ), false);
console.log( "DRAW WHEEL", this.$scope.data );
var grayValueString = '255,255,255'; //'' + grayValue + ',' + grayValue + ',' + grayValue;
var centerGradient = this.context.createRadialGradient( r, r, r, r, r, 0 );
centerGradient.addColorStop( 0, 'rgba( 255,255,255, 0 )' );
centerGradient.addColorStop( 1, 'rgba( 255,255,255, 1 )' );
this.context.fillStyle = centerGradient;
this.context.fillRect( 0, 0, this.height, this.height );
},
getColorByPoint: function( x, y ) {
var adjusted = this.adjustXY(x, y);
var PI = Math.PI;
var xCart = adjusted.x - ( this.height / 2 );
var yCart = adjusted.y - ( this.height / 2 );
var s = Math.min( 1, Math.sqrt( xCart * xCart + yCart * yCart ) / ( this.height / 2 ) ) ;
var h = Math.atan2( yCart, xCart ) * ( 180 / PI );
// atan2 works in -180..180 so we need to recitify the negatives
h = (h > 0 ? h : 360 + h);
var v = this.$scope.data.hsva.v;
var a = this.$scope.data.hsva.a;
return {
h: h,
s: s,
v: v,
a: a
};
},
/**
* adjustXY - Limit the mouse coordinates to with in the wheel.
*
* @param {Int} x description
* @param {Int} y description
* @return {Object}
*/
adjustXY: function( x, y ) {
var radius = this.height / 2;
var scale = radius;
// Plot the values on a cartesian plane
var xCart = x - radius;// * radius;
var yCart = y - radius;// * radius;
// Get the absolute values for comparison
var xCartAbs = Math.abs( xCart );
var yCartAbs = Math.abs( yCart );
// Get the radius of the cartesian plot
var radiusCart = Math.min( this.height / 2, Math.sqrt( xCart * xCart + yCart * yCart ));
var xAdjusted = x;
var yAdjusted = y;
// Calculate the angle of the cartesian plot
if ( radiusCart >= radius ) {
var theta = Math.atan2( yCart, xCart );
// Get the new x,y plot inside the circle using the adjust radius from above
var xCoord = radius * Math.cos( theta );
var yCoord = radius * Math.sin( theta );
// Center in SVG
xAdjusted = xCoord + radius;
yAdjusted = yCoord + radius;
}
return { x: xAdjusted, y: yAdjusted };
},
onColorSet: function( e, args ) {
var hsv = this.$scope.data.hsva;
var hsl = this.$scope.data.color.toHsl();
var hue = hsv.h;
var sat = hsv.s;
var val = hsv.v;
var PI = Math.PI;
var TWO_PI = PI * 2;
var radius = ( this.height / 2 ) * hsv.s;
// Calculate the angle of the cartesian plot
var theta = hue * ( PI / 180 );
// Get the new x,y plot inside the circle using the adjust radius from above
var xCoord = radius * Math.cos( theta );
var yCoord = radius * Math.sin( theta );
xCoord = xCoord + this.height / 2;
yCoord = yCoord + this.height / 2;
//this.draw();
this.setMarkerCenter( xCoord, yCoord );
// this.$scope.$emit('mdColorPicker:wheelChange', { color: this.color });
},
setColor: function( color ) {
console.log( 'wheel', color );
//this.$scope.$broadcast('mdColorPicker:colorChange', { color: color });
}
});
var ValueGradientCanvas = GradientCanvas.extend({
init: function ($element, $scope) {
this.type = 'value';
this.ignoreX = true;
GradientCanvas.apply( this, arguments );
this.$scope.$watch( 'data.color', this.boundEvents.draw, true );
},
draw: function() {
this.context.clearRect(0, 0, this.width, this.height);
// Create gradient
var grayGrd = this.context.createLinearGradient(90, 0.000, 90, this.height);
var hsv = this.$scope.data.hsva;
var color = tinycolor({ h: hsv.h, s: hsv.s, v: 100});
// Add colors
grayGrd.addColorStop(0, color.toRgbString());
grayGrd.addColorStop(1, 'rgba(0,0,0,1)');
// Fill with gradient
this.context.fillStyle = grayGrd;
this.context.fillRect( 0, 0, this.canvas.width, this.height );
},
getColorByPoint: function( x, y ) {
var h = this.$scope.data.hsva.h;
var s = this.$scope.data.hsva.s;
var v = ( this.height - y ) / this.height;
var a = this.$scope.data.hsva.a;
return {
h: h,
s: s,
v: v,
a: a
};
},
onColorSet: function( e, args ) {
console.log( "DATA.COLOR CHANGE... setting", this.$scope.data.color.toHsv(), this.$element, e, args, arguments);
this.draw();
var hsv = this.$scope.data.color.toHsv();
this.$scope.data.hsva.v = hsv.v;
var y = this.canvas.height - ( this.canvas.height * hsv.v );
this.setMarkerCenter( y );
}
});
function GradientCanvasFactory( ) {
return function(canvasConstructor) {
return {
template: '<canvas width="100%" height="100%"></canvas><div class="md-color-picker-marker"></div>',
link: function( $scope, $element, $attrs ) {
// Create new instance of the gradient so the same gradient canvases can be used on separate tabs.
var gCanvas = new canvasConstructor( $element, $scope );
console.log( $scope );
}
};
};
}
angular.module('mdColorPickerGradientCanvas',[])
.factory('mdColorGradientCanvas', GradientCanvasFactory )
.directive( 'mdColorPickerHue', ['mdColorGradientCanvas', function( mdColorGradientCanvas ) { return new mdColorGradientCanvas( HueGradientCanvas ); }])
.directive( 'mdColorPickerAlpha', ['mdColorGradientCanvas', function( mdColorGradientCanvas ) { return new mdColorGradientCanvas( AlphaGradientCanvas ); }])
.directive( 'mdColorPickerSpectrum', ['mdColorGradientCanvas', function( mdColorGradientCanvas ) { return new mdColorGradientCanvas( SpectrumGradientCanvas); }])
.directive( 'mdColorPickerWheel', ['mdColorGradientCanvas', function( mdColorGradientCanvas ) { return new mdColorGradientCanvas( WheelGradientCanvas); }])
.directive( 'mdColorPickerValue', ['mdColorGradientCanvas', function( mdColorGradientCanvas ) { return new mdColorGradientCanvas( ValueGradientCanvas); }])
;