/**********************************************************************
* 
* Web Component defining an image waveform/histogram view widget.
*
*
* Example:
*	
*	...
*	
* 
*
* XXX add docs and examples -- canvas-waveform.html is out outdated...
* XXX add worker support...
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('lib/object')
//---------------------------------------------------------------------
// image manipulation basics...
var Filters = 
module.Filters = {
	makeCanvas: function(w, h, canvas){
		var c = canvas || document.createElement('canvas')
		c.width = w
		c.height = h
		return c },
	// as input takes an HTML Image object...
	getPixels: function(img, tmp_canvas, w, h){
		var w = w || img.naturalWidth
		var h = h || img.naturalHeight
		var c = this.makeCanvas(w, h, tmp_canvas)
		var context = c.getContext('2d')
		if(img == null){
			context.rect(0, 0, w, h)
			context.fillStyle = "black"
			context.fill()
		} else {
			context.drawImage(img, 0, 0, w, h) }
		return context.getImageData(0, 0, c.width, c.height) },
	setPixels: function(c, data, w, h){
		w = c.width = w || data.width
		h = c.height = h || data.height
		var context = c.getContext('2d')
		context.putImageData(data, 0, 0) },
	// get image pixels normalized to a square of size s, rotated and flipped...
	//
	// NOTE: flip is applied to the image before it is rotated... (XXX ???)
	// XXX BUG: this is still wrong for images with exif orientation...
	// 		to reproduce:
	// 			loadImages: "L:/tmp/test/export-test/index-with-exif-rotation"
	// 			focusImage: 2
	// 			showMetadata
	getNormalizedPixels: function(img, tmp_canvas, s, rotate, flip){
		s = s || Math.max(img.naturalWidth, img.naturalHeight)
		rotate = rotate || 0
		;(rotate == 90 || rotate == 270)
			&& (flip = flip == 'horizontal' ?
					'vertical'
				: flip == 'vertical' ?
					'horizontal'
				: flip)
		var [h, v] = flip == 'both' ?
				[-1, -1]
			: flip == 'horizontal' ?
				[-1, 1]
			: flip == 'vertical' ?
				[1, -1]
			: [1, 1]
		var c = this.makeCanvas(s, s, tmp_canvas)
		var context = c.getContext('2d')
		context.rect(0, 0, s, s)
		context.fillStyle = 'black'
		context.fill()
		if(img){
			context.setTransform(h*1, 0, 0, v*1, s/2, s/2)
			context.rotate(rotate * Math.PI/180)
			context.drawImage(img, -s/2, -s/2, s, s) }
		return context.getImageData(0, 0, s, s) }, 
	filterImage: function(filter, image, var_args){
		var args = [this.getPixels(image)]
		for(var i=2; i
	:host {
		position: relative;
		display: inline-block;
		background: black;
		width: attr(image-width);
		height: attr(graph-height);
		padding-top: 16px;
		padding-bottom: 10px;
	}
	:host canvas {
		box-sizing: border-box;
		width: 100%;
		height: 100%;
		border-top: 1px dashed rgba(255, 255, 255, 0.2);
		border-bottom: 1px dashed rgba(255, 255, 255, 0.2);
	}
	:host .controls {
		display: inline-block;
		position: absolute;
		top: 2px;
		right: 2px;
		left: 2px;
	}
	:host .controls button {
		background: transparent;
		border: none;
		color: white;
		opacity: 0.7;
		float: right;
		font-size: 12px;
	}
	:host .controls button[disabled] {
		opacity: 0.3;
		user-select: none;
	}
	:host .controls button.current {
		text-decoration: underline;
		opacity: 0.9;
	}
	:host .controls button:hover:not([disabled]) {
		opacity: 1;
	}
	:host .hidden {
		position: absolute;
		width: 0;
		height: 0;
		opacity: 0;
		image-orientation: none;
	}
![]() `
var igImageGraph = 
module.igImageGraph = 
object.Constructor('igImageGraph', HTMLElement, {
	template: 'ig-image-graph',
	graphs: {
		waveform,
		histogram,
	},
	modes: ['luminance', 'color', 'R', 'G', 'B'],
	color_modes: ['normalized', 'white', 'point'],
	__init__: function(src){
		// shadow DOM
		var shadow = this.__shadow = 
			this.attachShadow({mode: 'open'})
		// get/create template...
		var tpl = document.getElementById(this.template)
		if(!tpl){
			var tpl = document.createElement('template')
			tpl.setAttribute('id', this.template)
			tpl.innerHTML = igImageGraph_template 
			document.head.appendChild(tpl) }
		shadow.appendChild(tpl.content.cloneNode(true)) },
	connectedCallback: function(){
		this.update_controls()
		this.image 
			|| this.update() },
	// attributes...
	get observedAttributes(){
		return [
			'src', 
			'mode', 
			'color',
			'graph',
			'orientation',
			'flipped',
			'nocontrols',
		]},
	attributeChangedCallback: function(name, from, to){
		name == 'nocontrols'
			&& this.update_controls()
		this.update() },
	get graph(){
		return this.getAttribute('graph') || 'waveform' },
	set graph(value){
		value in this.graphs
			&& this.setAttribute('graph', value)
		value == ''
			&& this.removeAttribute('graph') 
		this.update() },
	get src(){
		return this.getAttribute('src') },
	// XXX make this async...
	set src(value){
		var that = this
		this.__update_handler = this.__update_handler 
			|| this.update.bind(this)
		var url = typeof(value) == typeof('str')
		// get/create image...
		var img = this.image = 
			url ?
				//(this.image || document.createElement('img'))
				// XXX HACK: image-orientation only works if element is attached to DOM...
				(this.image || this.__shadow.querySelector('img'))
				: value
		img.removeEventListener('load', this.__update_handler)
		img.addEventListener('load', this.__update_handler)
		// set .src and img.src...
		this.setAttribute('src', 
			url ? 
				(img.src = value)
				: img.src) },
	get mode(){
		return this.getAttribute('mode') || 'color' },
	set mode(value){
		this.modes.includes(value)	
			&& this.setAttribute('mode', value) 
		value === undefined
			&& this.removeAttribute('color') 
		this.update_controls()
		this.update() },
	get color(){
		return this.getAttribute('color') || 'normalized' },
	set color(value){
		this.color_modes.includes(value)	
			&& this.setAttribute('color', value) 
		value === undefined
			&& this.removeAttribute('color') 
		this.update() },
	get orientation(){
		return this.getAttribute('orientation') || 0 },
	set orientation(value){
		;(['top', 'left', 'bottom', 'right'].includes(value)
				|| typeof(value) == typeof(123))
			&& this.setAttribute('orientation', value) 
		value == null
			&& this.removeAttribute('orientation') 
		this.update() },
	get flipped(){
		return this.getAttribute('flipped') },
	set flipped(value){
		;(['vertical', 'horizontal', 'both'].includes(value)
				|| typeof(value) == typeof(123))
			&& this.setAttribute('flipped', value) 
		value == null
			&& this.removeAttribute('flipped') 
		this.update() },
	get nocontrols(){
		return this.getAttribute('nocontrols') != null },
	set nocontrols(value){
		value ?
			this.setAttribute('nocontrols', '')
   			: this.removeAttribute('nocontrols') 
		this.update_controls()
		this.update() },
	// API...
	update_controls: function(){
		var that = this
		var mode = this.mode
		var controls = this.__shadow.querySelector('.controls')
		controls.innerHTML = ''
		// modes...
		var buttons = [
				// graph...
				function(){
					var button = document.createElement('button')
					button.classList.add('update')
					//button.innerHTML = '◑'
					button.innerHTML = '◪'
					button.onclick = function(){ 
						var g = that.graph = that.graph == 'waveform' ?
							'histogram'
							: 'waveform'
						var b = button.parentElement.querySelector('#orientation-button') || {}
						b.disabled = that.graph != 'waveform' }
					return button }(),
				// orientation...
				//
				// switch from vertical to horizontal and back, keeping 
				// only two orientations with top-to-top (default) and 
				// top-to-right (alternative) modes.
				//
				// the button arrow:
				// 	- indicates orientation
				// 	- points to top of image relative to waveform
				//
				function(){
					var button = document.createElement('button')
					button.setAttribute('id', 'orientation-button')
					button.classList.add('update')
					button.innerHTML = '🡑'
					// load button state...
					var _update = function(){
						Object.assign(button.style, 
							that.__rotated == null ?
								// top...
								{ 
									transform: '',
									marginTop: '-2px', 
								}
								// right...
								: {
									transform: 'rotate(90deg)',
									marginTop: '-1px',
								}) }
					_update()
					button.disabled = that.graph != 'waveform'
					// click -> do the rotation...
					button.onclick = function(){ 
						var o = that.__rotated
						var c = that.orientation*1
						that.orientation = o == null ?
							// rotate cw...
							(c + 90) % 360
							// restore...
							: o
						that.__rotated = o == null ?
							c
							: null
						_update() }
					return button }(),
				// modes...
				// ...generate mode toggles...
				...(this.nocontrols ? 
						[] 
						: this.modes)
					// mode buttons...
					.map(function(m){
						var button = document.createElement('button')
						button.innerText = m
						button.classList.add(m, ...(m == mode ? ['current'] : []))
						button.onclick = function(){ 
							that.mode = m }
						return button }),
				// reload...
				/*/ XXX do we actually need this???
				function(){
					var button = document.createElement('button')
					button.classList.add('update')
					button.innerHTML = '⟳'
					button.onclick = function(){ that.update() }
					return button }(),
				//*/
			]
			.reverse()
			.forEach(function(button){
				controls.appendChild(button) })
		return this },
	// XXX add option to update graph in a worker...
	// XXX show a spinner while updating...
	update: function(){
		var that = this
		var mode = this.mode
		// controls...
		// remove...
		if(!this.nocontrols){
			var controls = this.__shadow.querySelector('.controls')
			// current button state...
			var button = controls.querySelector('button.'+this.mode) 
			button 
				&& button.classList.add('current') }
		if(this.image){
			var orientation = this.orientation
			orientation = parseFloat(
				{top: 180, left: 90, bottom: 0, right: 270}[orientation] 
				|| orientation)
			var canvas = this.__shadow.querySelector('canvas.graph')
			var tmp_canvas = this.__shadow.querySelector('canvas.hidden')
			// XXX configurable...
			this.graphs[this.graph](this.image, 
				canvas, tmp_canvas, 
				this.mode, 
				this.color, 
				Math.round(orientation),
				this.flipped)
		} else if(this.src){
			this.src = this.src }
		return this },
})
window.customElements.define('ig-image-graph', igImageGraph)
/**********************************************************************
* vim:set ts=4 sw=4 :                               */ return module })
`
var igImageGraph = 
module.igImageGraph = 
object.Constructor('igImageGraph', HTMLElement, {
	template: 'ig-image-graph',
	graphs: {
		waveform,
		histogram,
	},
	modes: ['luminance', 'color', 'R', 'G', 'B'],
	color_modes: ['normalized', 'white', 'point'],
	__init__: function(src){
		// shadow DOM
		var shadow = this.__shadow = 
			this.attachShadow({mode: 'open'})
		// get/create template...
		var tpl = document.getElementById(this.template)
		if(!tpl){
			var tpl = document.createElement('template')
			tpl.setAttribute('id', this.template)
			tpl.innerHTML = igImageGraph_template 
			document.head.appendChild(tpl) }
		shadow.appendChild(tpl.content.cloneNode(true)) },
	connectedCallback: function(){
		this.update_controls()
		this.image 
			|| this.update() },
	// attributes...
	get observedAttributes(){
		return [
			'src', 
			'mode', 
			'color',
			'graph',
			'orientation',
			'flipped',
			'nocontrols',
		]},
	attributeChangedCallback: function(name, from, to){
		name == 'nocontrols'
			&& this.update_controls()
		this.update() },
	get graph(){
		return this.getAttribute('graph') || 'waveform' },
	set graph(value){
		value in this.graphs
			&& this.setAttribute('graph', value)
		value == ''
			&& this.removeAttribute('graph') 
		this.update() },
	get src(){
		return this.getAttribute('src') },
	// XXX make this async...
	set src(value){
		var that = this
		this.__update_handler = this.__update_handler 
			|| this.update.bind(this)
		var url = typeof(value) == typeof('str')
		// get/create image...
		var img = this.image = 
			url ?
				//(this.image || document.createElement('img'))
				// XXX HACK: image-orientation only works if element is attached to DOM...
				(this.image || this.__shadow.querySelector('img'))
				: value
		img.removeEventListener('load', this.__update_handler)
		img.addEventListener('load', this.__update_handler)
		// set .src and img.src...
		this.setAttribute('src', 
			url ? 
				(img.src = value)
				: img.src) },
	get mode(){
		return this.getAttribute('mode') || 'color' },
	set mode(value){
		this.modes.includes(value)	
			&& this.setAttribute('mode', value) 
		value === undefined
			&& this.removeAttribute('color') 
		this.update_controls()
		this.update() },
	get color(){
		return this.getAttribute('color') || 'normalized' },
	set color(value){
		this.color_modes.includes(value)	
			&& this.setAttribute('color', value) 
		value === undefined
			&& this.removeAttribute('color') 
		this.update() },
	get orientation(){
		return this.getAttribute('orientation') || 0 },
	set orientation(value){
		;(['top', 'left', 'bottom', 'right'].includes(value)
				|| typeof(value) == typeof(123))
			&& this.setAttribute('orientation', value) 
		value == null
			&& this.removeAttribute('orientation') 
		this.update() },
	get flipped(){
		return this.getAttribute('flipped') },
	set flipped(value){
		;(['vertical', 'horizontal', 'both'].includes(value)
				|| typeof(value) == typeof(123))
			&& this.setAttribute('flipped', value) 
		value == null
			&& this.removeAttribute('flipped') 
		this.update() },
	get nocontrols(){
		return this.getAttribute('nocontrols') != null },
	set nocontrols(value){
		value ?
			this.setAttribute('nocontrols', '')
   			: this.removeAttribute('nocontrols') 
		this.update_controls()
		this.update() },
	// API...
	update_controls: function(){
		var that = this
		var mode = this.mode
		var controls = this.__shadow.querySelector('.controls')
		controls.innerHTML = ''
		// modes...
		var buttons = [
				// graph...
				function(){
					var button = document.createElement('button')
					button.classList.add('update')
					//button.innerHTML = '◑'
					button.innerHTML = '◪'
					button.onclick = function(){ 
						var g = that.graph = that.graph == 'waveform' ?
							'histogram'
							: 'waveform'
						var b = button.parentElement.querySelector('#orientation-button') || {}
						b.disabled = that.graph != 'waveform' }
					return button }(),
				// orientation...
				//
				// switch from vertical to horizontal and back, keeping 
				// only two orientations with top-to-top (default) and 
				// top-to-right (alternative) modes.
				//
				// the button arrow:
				// 	- indicates orientation
				// 	- points to top of image relative to waveform
				//
				function(){
					var button = document.createElement('button')
					button.setAttribute('id', 'orientation-button')
					button.classList.add('update')
					button.innerHTML = '🡑'
					// load button state...
					var _update = function(){
						Object.assign(button.style, 
							that.__rotated == null ?
								// top...
								{ 
									transform: '',
									marginTop: '-2px', 
								}
								// right...
								: {
									transform: 'rotate(90deg)',
									marginTop: '-1px',
								}) }
					_update()
					button.disabled = that.graph != 'waveform'
					// click -> do the rotation...
					button.onclick = function(){ 
						var o = that.__rotated
						var c = that.orientation*1
						that.orientation = o == null ?
							// rotate cw...
							(c + 90) % 360
							// restore...
							: o
						that.__rotated = o == null ?
							c
							: null
						_update() }
					return button }(),
				// modes...
				// ...generate mode toggles...
				...(this.nocontrols ? 
						[] 
						: this.modes)
					// mode buttons...
					.map(function(m){
						var button = document.createElement('button')
						button.innerText = m
						button.classList.add(m, ...(m == mode ? ['current'] : []))
						button.onclick = function(){ 
							that.mode = m }
						return button }),
				// reload...
				/*/ XXX do we actually need this???
				function(){
					var button = document.createElement('button')
					button.classList.add('update')
					button.innerHTML = '⟳'
					button.onclick = function(){ that.update() }
					return button }(),
				//*/
			]
			.reverse()
			.forEach(function(button){
				controls.appendChild(button) })
		return this },
	// XXX add option to update graph in a worker...
	// XXX show a spinner while updating...
	update: function(){
		var that = this
		var mode = this.mode
		// controls...
		// remove...
		if(!this.nocontrols){
			var controls = this.__shadow.querySelector('.controls')
			// current button state...
			var button = controls.querySelector('button.'+this.mode) 
			button 
				&& button.classList.add('current') }
		if(this.image){
			var orientation = this.orientation
			orientation = parseFloat(
				{top: 180, left: 90, bottom: 0, right: 270}[orientation] 
				|| orientation)
			var canvas = this.__shadow.querySelector('canvas.graph')
			var tmp_canvas = this.__shadow.querySelector('canvas.hidden')
			// XXX configurable...
			this.graphs[this.graph](this.image, 
				canvas, tmp_canvas, 
				this.mode, 
				this.color, 
				Math.round(orientation),
				this.flipped)
		} else if(this.src){
			this.src = this.src }
		return this },
})
window.customElements.define('ig-image-graph', igImageGraph)
/**********************************************************************
* vim:set ts=4 sw=4 :                               */ return module })