mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-31 11:20:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			441 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			441 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			HTML
		
	
	
		
			Executable File
		
	
	
	
	
| <html>
 | |
| <style>
 | |
| 
 | |
| button,
 | |
| details {
 | |
| 	border: solid 1px gray;
 | |
| 	border-radius: 4px;
 | |
| 	margin: 1px;
 | |
| 	background: white;
 | |
| }
 | |
| details {
 | |
| 	display: inline-block;
 | |
| 	width: 508px;
 | |
| 	min-width: 500px;
 | |
| 	overflow: visible;
 | |
| }
 | |
| .panel {
 | |
| 	box-shadow: 5px 5px 30px -5px rgba(0,0,0,0.5);
 | |
| 	opacity: 0.95;
 | |
| }
 | |
| .close-panel {
 | |
| 	display: inline-block;
 | |
| 	position: absolute;
 | |
| 	right: 5px;
 | |
| 	cursor: hand;
 | |
| }
 | |
| .close-panel:hover {
 | |
| 	color: black;
 | |
| 	font-weight: bold;
 | |
| 	text-shadow: 1px 1px 2px 0px rgba(0,0,0,0.5);
 | |
| }
 | |
| details details {
 | |
| 	width: 500px;
 | |
| 	margin: 3px;
 | |
| 	border: solid 1px silver;
 | |
| 	box-shadow: none;
 | |
| }
 | |
| details > div {
 | |
| 	margin: 10px;
 | |
| }
 | |
| summary {
 | |
| 	padding-left: 3px;
 | |
| 	background: silver;
 | |
| }
 | |
| details details summary {
 | |
| 	background: #ddd;
 | |
| }
 | |
| input[type=range] {
 | |
| 	width: 300px;
 | |
| }
 | |
| button:active {
 | |
| 	background: silver;
 | |
| }
 | |
| button.reset {
 | |
| 	border: solid 1px silver;
 | |
| }
 | |
| button.reset:hover {
 | |
| 	border: solid 1px gray;
 | |
| }
 | |
| /*
 | |
| button.state:focus {
 | |
| 	background: silver;
 | |
| }
 | |
| */
 | |
| /* XXX this is not live...
 | |
| input[type=range]:after {
 | |
| 	content: attr(value);
 | |
| 	color: black;
 | |
| }
 | |
| */
 | |
| div > span:first-child {
 | |
| 	display: inline-block;
 | |
| 	width: 100px;
 | |
| }
 | |
| 
 | |
| /* range syling... */
 | |
| input[type='range'] {
 | |
| 	-webkit-appearance: none !important;
 | |
| 	background: silver;
 | |
| 	height: 4px;
 | |
| 	border: solid 1px gray;
 | |
| 	border-radius: 2px;
 | |
| }
 | |
| input[type='range']::-webkit-slider-thumb {
 | |
| 	-webkit-appearance: none !important;
 | |
| 	background: white;
 | |
| 	height: 15px;
 | |
| 	width: 15px;
 | |
| 	border: solid 1px gray;
 | |
| 	border-radius: 50%;
 | |
| }
 | |
| 
 | |
| .value {
 | |
| 	-webkit-appearance: none !important;
 | |
| 	display: inline-block;
 | |
| 	width: 40px;
 | |
| 	text-align: right;
 | |
| 	margin-left: 5px;
 | |
| 	margin-right: 5px;
 | |
| 	background: white;
 | |
| 	border: none;
 | |
| 	border-radius: 2px;
 | |
| }
 | |
| input::-webkit-outer-spin-button,
 | |
| input::-webkit-inner-spin-button {
 | |
| 	-webkit-appearance: none !important;
 | |
| }
 | |
| 
 | |
| 
 | |
| .title {
 | |
| 	cursor: move;
 | |
| }
 | |
| 
 | |
| 
 | |
| </style>
 | |
| <script src="jquery.js"></script>
 | |
| <script src="jquery-ui.js"></script>
 | |
| <script>
 | |
| 
 | |
| // XXX this exists in jli, remove when not needed...
 | |
| jQuery.fn.sortChildren = function(func){
 | |
| 	return $(this).each(function(_, e){
 | |
| 		return $(e).append($(e).children().detach().get().sort(func))
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| // XXX make this add new filters in the correct position...
 | |
| function updateFilter(e, f, v, order){
 | |
| 	e = $(e)
 | |
| 	var state = e
 | |
| 		.css('-webkit-filter')
 | |
| 	state = state == 'none' ? '' : state + ' '
 | |
| 	// update existing filter...
 | |
| 	if(RegExp(f).test(state)){
 | |
| 		state = state.replace(RegExp(f+'\\s*\\([^\\)]*\\)'), f+'('+v+')')
 | |
| 	// add new filter...
 | |
| 	} else {
 | |
| 		state += f+'('+v+')'
 | |
| 		state = sortFilterStr(state, order)
 | |
| 	}
 | |
| 	e.css({
 | |
| 		'-webkit-filter': state,
 | |
| 	})
 | |
| 	return v
 | |
| }
 | |
| function resetFilter(e, f){
 | |
| 	e = $(e)
 | |
| 	var state = e
 | |
| 		.css('-webkit-filter')
 | |
| 	state = state == 'none' ? '' : state + ' '
 | |
| 	state = state.replace(RegExp(f+'\\s*\\([^\\)]*\\)'), '').trim()
 | |
| 	e.css({
 | |
| 		'-webkit-filter': state,
 | |
| 	})
 | |
| 	return e
 | |
| }
 | |
| function sortFilterStr(state, order){
 | |
| 	order = order == null ? getSliderOrder() : order
 | |
| 	state = state.split(/\s+/)
 | |
| 	state.sort(function(a, b){
 | |
| 		a = order.indexOf(a.replace(/\(.*/, ''))
 | |
| 		b = order.indexOf(b.replace(/\(.*/, ''))
 | |
| 		return a - b
 | |
| 	})
 | |
| 	return state.join(' ')
 | |
| }
 | |
| function getSliderOrder(){
 | |
| 	return $('.filter-list').sortable('toArray')
 | |
| }
 | |
| // NOTE: this will return only the set filters...
 | |
| function getFilterOrder(target){
 | |
| 	return $(target)
 | |
| 		.css('-webkit-filter')
 | |
| 		.split(/\s*\([^\)]*\)\s*/g)
 | |
| 		.slice(0, -1)
 | |
| }
 | |
| function sortFilterSliders(order){
 | |
| 	return $('.filter-list').sortChildren(function(a, b){
 | |
| 		a = order.indexOf(a.id)
 | |
| 		b = order.indexOf(b.id)
 | |
| 		return a - b
 | |
| 	})
 | |
| }
 | |
| 
 | |
| 
 | |
| function resetPrevRange(e){
 | |
| 	e = $(e).prev('input[type=range]')
 | |
| 	e.val(e.attr('default'))
 | |
| 		.change()
 | |
| }
 | |
| 
 | |
| // XXX order the sliders...
 | |
| function loadSliderState(target){
 | |
| 	var res = $(target)
 | |
| 		.css('-webkit-filter')
 | |
| 	var state = res
 | |
| 		.split(/\s*\(\s*|\s*\)\s*/g)
 | |
| 		.reverse()
 | |
| 		.slice(1)
 | |
| 	// reset sliders do defaults...
 | |
| 	$('input[type=range]').each(function(i, e){
 | |
| 		e = $(e)
 | |
| 		e.val(e.attr('default')).change()
 | |
| 	})
 | |
| 	// set the saved values...
 | |
| 	while(state.length > 0){
 | |
| 		// XXX avoid using ids...
 | |
| 		var e = $('[filter='+state.pop()+']')
 | |
| 		if(e.prop('normalize')){
 | |
| 			e.val(v2r(parseFloat(state.pop()))).change()
 | |
| 		} else {
 | |
| 			e.val(parseFloat(state.pop())).change()
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| var DEFAULT_FILTER_ORDER = [
 | |
| 	'brightness',
 | |
| 	'contrast',
 | |
| 	'saturate',
 | |
| 	'hue-rotate',
 | |
| 	'grayscale',
 | |
| 	'invert',
 | |
| 	'sepia'
 | |
| ]
 | |
| 
 | |
| function saveState(target){
 | |
| 	var l = $('.state').length
 | |
| 	var state = $(target).css('-webkit-filter')
 | |
| 	$('<button></button>')
 | |
| 		.text(l)
 | |
| 		.addClass('state '+l)
 | |
| 		.attr({
 | |
| 			state: state,
 | |
| 			sliders: getSliderOrder().join(' ')
 | |
| 		})
 | |
| 		// load state...
 | |
| 		.click(function(){
 | |
| 			loadSliderState($(target).css('-webkit-filter', state))
 | |
| 			sortFilterSliders($(this).attr('sliders').split(' '))
 | |
| 		})
 | |
| 		.appendTo($('.states'))
 | |
| }
 | |
| function clearStates(){
 | |
| 	$('.state').remove()
 | |
| }
 | |
| 
 | |
| 
 | |
| function makeAbsRange(text, filter, target, min, max, dfl, step, translate, normalize){
 | |
| 	min = min == null ? 0 : min
 | |
| 	max = max == null ? 1 : max
 | |
| 	dfl = dfl == null ? min : dfl
 | |
| 	step = step == null ? 0.01 : step
 | |
| 	translate = translate == null ? function(v){return v} : translate
 | |
| 	normalize = normalize == null ? false : true
 | |
| 
 | |
| 	var elem = $('<div class="control range"></div>')
 | |
| 		.attr({
 | |
| 			id: filter,
 | |
| 		})
 | |
| 	$('<span class="title"/>')
 | |
| 		.html(text)
 | |
| 		.appendTo(elem)
 | |
| 	var range = $('<input type="range">')
 | |
| 		.attr({
 | |
| 			filter: filter,
 | |
| 			min: min,
 | |
| 			max: max,
 | |
| 			step: step,
 | |
| 			default: dfl,
 | |
| 		})
 | |
| 		.prop('normalize', normalize)
 | |
| 		.val(dfl)
 | |
| 		.change(function(){
 | |
| 			var val = this.valueAsNumber
 | |
| 			value.val(val)
 | |
| 			updateFilter(target, filter, translate(val))
 | |
| 		})
 | |
| 		.appendTo(elem)
 | |
| 	var value = $('<input type="number" class="value"/>')
 | |
| 		.attr({
 | |
| 			min: min,
 | |
| 			max: max,
 | |
| 			step: step,
 | |
| 		})
 | |
| 		.val(dfl)
 | |
| 		.change(function(){
 | |
| 			range.val($(this).val()).change()
 | |
| 		})
 | |
| 		.appendTo(elem)
 | |
| 	$('<button class="reset">×</button>')
 | |
| 		.click(function(){
 | |
| 			range.val(dfl).change()
 | |
| 			resetFilter(target, filter)
 | |
| 		})
 | |
| 		.appendTo(elem)
 | |
| 	return elem
 | |
| }
 | |
| function makeLogRange(text, filter, target){
 | |
| 	return makeAbsRange(text, filter, target, -100, 100, 0, 0.1, r2v, true)
 | |
| }
 | |
| 
 | |
| var C = 43.47
 | |
| 
 | |
| function r2v(r){
 | |
| 	return Math.pow(Math.E, r/C)
 | |
| }
 | |
| 
 | |
| function v2r(v){
 | |
| 	return Math.log(v)*C
 | |
| }
 | |
| 
 | |
| function makeControls(target){
 | |
| 	// tool panel...
 | |
| 	var panel = $('<details open/>')
 | |
| 		.addClass('panel')
 | |
| 		.css({
 | |
| 			position: 'absolute',
 | |
| 			top: '100px',
 | |
| 			left: '100px',
 | |
| 		})
 | |
| 		.append($('<summary>Edit</summary>')
 | |
| 			.append($('<span/>')
 | |
| 				.addClass('close-panel')
 | |
| 				.click(function(){
 | |
| 					$(this).parents('.panel').hide()
 | |
| 					return false
 | |
| 				})
 | |
| 				.html('×')))
 | |
| 		.draggable({
 | |
| 			containment: $('body'),
 | |
| 			scroll: false,
 | |
| 		})
 | |
| 
 | |
| 	// wrapper for sub-panels...
 | |
| 	var content = $('<span class="content">')
 | |
| 		.sortable({
 | |
| 			forcePlaceholderSize: true,
 | |
| 			start: function(e, ui){
 | |
| 				 ui.placeholder.height(ui.helper.outerHeight());
 | |
| 			},
 | |
| 		})
 | |
| 		.appendTo(panel)
 | |
| 
 | |
| 	// filters...
 | |
| 	$('<details open/>')
 | |
| 		.append($('<summary>Filters</summary>'))
 | |
| 		.append($('<div/>')
 | |
| 			.append($('<div class="filter-list"/>')
 | |
| 				.append(makeLogRange('Brightness:', 'brightness', target))
 | |
| 				.append(makeLogRange('Contrast:', 'contrast', target))
 | |
| 				.append(makeLogRange('Saturation:', 'saturate', target))
 | |
| 				.append(makeAbsRange('Hue:', 'hue-rotate', target, -180, 180, 0, 0.5, function(v){ return v+'deg' }))
 | |
| 				.append(makeAbsRange('Grayscale:', 'grayscale', target))
 | |
| 				.append(makeAbsRange('Invert:', 'invert', target))
 | |
| 				.append(makeAbsRange('Sepia:', 'sepia', target))
 | |
| 				.sortable({
 | |
| 					axis: 'y',
 | |
| 				})
 | |
| 				.on('sortstop', function(){
 | |
| 					// update image filter order...
 | |
| 					var img = $(target)
 | |
| 					img.css('-webkit-filter', sortFilterStr(img.css('-webkit-filter')))
 | |
| 				}))
 | |
| 			.append($('<hr>'))
 | |
| 			.append('Reset: ')
 | |
| 			.append($('<button>Values</button>')
 | |
| 				.click(function(){
 | |
| 					$('.reset').click()
 | |
| 				}))
 | |
| 			.append($('<button>Order</button>')
 | |
| 				.click(function(){
 | |
| 					sortFilterSliders(DEFAULT_FILTER_ORDER)
 | |
| 				}))
 | |
| 			.append($('<button>All</button>')
 | |
| 				.click(function(){
 | |
| 					$('.reset').click()
 | |
| 					sortFilterSliders(DEFAULT_FILTER_ORDER)
 | |
| 				})))
 | |
| 			//.append($('<hr>'))
 | |
| 			//.append($('<div>NOTE: order of filters is segnificant, use '+
 | |
| 			//	'dragging to arrange them as needed.</div>')))
 | |
| 		.appendTo(content)
 | |
| 
 | |
| 	// snapshots...
 | |
| 	$('<details open/>')
 | |
| 		.append($('<summary>Snapshots</summary>'))
 | |
| 		.append($('<div/>')
 | |
| 			.append($('<div class="states"/>'))
 | |
| 			.append($('<hr>'))
 | |
| 			.append($('<button/>')
 | |
| 				.click(function(){ saveState(target) })
 | |
| 				.text('Save'))
 | |
| 			.append($('<button/>')
 | |
| 				.click(function(){ clearStates() })
 | |
| 				.text('Clear')))
 | |
| 		.appendTo(content)
 | |
| 
 | |
| 	return panel
 | |
| }
 | |
| 
 | |
| function reloadControls(target){
 | |
| 	clearStates()
 | |
| 	var state = loadSliderState(target)
 | |
| 	// nothing set -- default sort...
 | |
| 	if(state == 'none'){
 | |
| 		sortFilterSliders(DEFAULT_FILTER_ORDER)
 | |
| 
 | |
| 	// load existing sort state...
 | |
| 	} else {
 | |
| 		sortFilterSliders(getFilterOrder(target).concat(DEFAULT_FILTER_ORDER))
 | |
| 	}
 | |
| 	// make a snapshot...
 | |
| 	saveState(target)
 | |
| }
 | |
| 
 | |
| $(function(){
 | |
| 	var target = '#image'
 | |
| 
 | |
| 	var panel = makeControls(target).hide()
 | |
| 
 | |
| 	$('body').append(panel)
 | |
| 
 | |
| 	$(target)
 | |
| 		.click(function(){
 | |
| 			panel.show()
 | |
| 		})
 | |
| 
 | |
| 	saveState(target)
 | |
| })
 | |
| 
 | |
| </script>
 | |
| <body>
 | |
| 	<img id="image" src="image.jpg" >
 | |
| 	<p>click image to show editor panel...</p>
 | |
| </body>
 | |
| </html>
 |