mirror of
				https://github.com/flynx/ImageGrid.git
				synced 2025-10-29 18:30:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			453 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| /**********************************************************************
 | |
| * 
 | |
| * Image Sorting API
 | |
| *
 | |
| *
 | |
| **********************************************************************/
 | |
| 
 | |
| // Used in sortImagesByFileNameSeqWithOverflow
 | |
| var PROXIMITY = 30
 | |
| var CHECK_1ST_PROXIMITY = false
 | |
| var OVERFLOW_GAP = PROXIMITY * 5
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * Helpers
 | |
| */
 | |
| 
 | |
| // create a generic distance comparison function
 | |
| //
 | |
| // NOTE: both distances are measured from a fixed point (start)...
 | |
| function makeDistanceCmp(start, get){
 | |
| 	if(get == null){
 | |
| 		return function(a, b){
 | |
| 			return Math.abs(start - a) - Math.abs(start - b)
 | |
| 		}
 | |
| 	} else {
 | |
| 		start = get(start)
 | |
| 		return function(a, b){
 | |
| 			return Math.abs(start - get(a)) - Math.abs(start - get(b))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // Make a cmp function to compare two gids by distance from gid.
 | |
| //
 | |
| // NOTE: this calculates the distance in a flat sequence...
 | |
| function makeGIDDistanceCmp(gid, get, data){
 | |
| 	return function(a, b){
 | |
| 		return getGIDDistance(gid, a, get, data) - getGIDDistance(gid, b, get, data)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // 2D distance from a specified gid comparison...
 | |
| //
 | |
| // XXX make this faster...
 | |
| // XXX this is fun, but do we actually need this?
 | |
| function makeGIDRibbonDistanceCmp(gid, get, data){
 | |
| 	data = data == null ? DATA : data
 | |
| 
 | |
| 	var _getDistance = makeGIDRibbonDistanceGetter(gid)
 | |
| 
 | |
| 	if(get == null){
 | |
| 		return function(a, b){
 | |
| 			return _getDistance(a) - _getDistance(b)
 | |
| 		}
 | |
| 	} else {
 | |
| 		return function(a, b){
 | |
| 			return _getDistance(get(a)) - _getDistance(get(b))
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // NOTE: this expects gids...
 | |
| function imageDateCmp(a, b, get, data){
 | |
| 	data = data == null ? IMAGES : data
 | |
| 	if(get != null){
 | |
| 		a = get(a)
 | |
| 		b = get(b)
 | |
| 	}
 | |
| 	b = data[b].ctime
 | |
| 	a = data[a].ctime
 | |
| 
 | |
| 	if(a == b){
 | |
| 		return 0
 | |
| 	} else if(a < b){
 | |
| 		return -1
 | |
| 	} else {
 | |
| 		return +1
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // NOTE: this expects gids...
 | |
| function imageNameCmp(a, b, get, data){
 | |
| 	data = data == null ? IMAGES : data
 | |
| 	if(get != null){
 | |
| 		a = get(a)
 | |
| 		b = get(b)
 | |
| 	}
 | |
| 	a = getImageFileName(a, data)
 | |
| 	b = getImageFileName(b, data)
 | |
| 	if(a == b){
 | |
| 		return 0
 | |
| 	} else if(a < b){
 | |
| 		return -1
 | |
| 	} else {
 | |
| 		return +1
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // Compare images by sequence number (in filename) or by filename
 | |
| //
 | |
| // Examples:
 | |
| // 	"1 file name", "012-file", "file 123 name", "DSC_1234"
 | |
| //
 | |
| // NOTE: if there are more than one sequence numbers in a filename then
 | |
| // 		only the first is considered.
 | |
| // NOTE: images with sequence number always precede images with plain 
 | |
| // 		filenames...
 | |
| function imageSeqOrNameCmp(a, b, get, data, get_seq){
 | |
| 	data = data == null ? IMAGES : data
 | |
| 	get_seq = get_seq == null ? getImageNameSeq : get_seq
 | |
| 	if(get != null){
 | |
| 		a = get(a)
 | |
| 		b = get(b)
 | |
| 	}
 | |
| 
 | |
| 	var aa = get_seq(a, data)
 | |
| 	var bb = get_seq(b, data)
 | |
| 
 | |
| 	// special case: seq, name
 | |
| 	if(typeof(aa) == typeof(123) && typeof(bb) == typeof('str')){ return -1 }
 | |
| 	// special case: name, seq
 | |
| 	if(typeof(aa) == typeof('str') && typeof(bb) == typeof(123)){ return +1 }
 | |
| 
 | |
| 	// get the names if there are no sequence numbers...
 | |
| 	// NOTE: at this point both a and b are either numbers or NaN's...
 | |
| 	a = isNaN(aa) ? getImageFileName(a, data) : aa
 | |
| 	b = isNaN(bb) ? getImageFileName(b, data) : bb
 | |
| 
 | |
| 	// do the actual comparison
 | |
| 	if(a == b){
 | |
| 		return 0
 | |
| 	} else if(a < b){
 | |
| 		return -1
 | |
| 	} else {
 | |
| 		return +1
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // Compate images by name XP-style
 | |
| //
 | |
| // This will consider sequence numbers if they are at the start of the 
 | |
| // filename.
 | |
| // 
 | |
| // Examples:
 | |
| // 	"1 file name", "012-file"
 | |
| //
 | |
| // NOTE: images with sequence number always precede images with plain 
 | |
| // 		filenames...
 | |
| function imageXPStyleFileNameCmp(a, b, get, data){
 | |
| 	return imageSeqOrNameCmp(a, b, get, data, getImageNameLeadingSeq)
 | |
| }
 | |
| 
 | |
| 
 | |
| // Sort action generator...
 | |
| //
 | |
| function sortVia(cmp){
 | |
| 	return function(reverse){
 | |
| 		return sortImages(cmp, reverse)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| // Get list of gids sorted by proximity to current gid
 | |
| //
 | |
| // NOTE: the distance used is the actual 2D distance...
 | |
| function getClosestGIDs(gid){
 | |
| 	gid = gid == null ? getImageGID() : gid
 | |
| 	//return DATA.order.slice().sort(makeGIDDistanceCmp(gid))
 | |
| 	return DATA.order.slice().sort(makeGIDRibbonDistanceCmp(gid))
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * Actions
 | |
| */
 | |
| 
 | |
| function reverseImageOrder(){
 | |
| 	DATA.order.reverse()
 | |
| 	DATA.ribbons.forEach(function(r){
 | |
| 		r.reverse()
 | |
| 	})
 | |
| 	reloadViewer()
 | |
| 	dataUpdated()
 | |
| 
 | |
| 	$('.viewer').trigger('reversedImageOrder')
 | |
| }
 | |
| 
 | |
| 
 | |
| // Sort images...
 | |
| //
 | |
| // NOTE: cmp can be a list of cmp functions, in this case they will be 
 | |
| // 		chained (see: chainCmp(..) for more details)
 | |
| // NOTE: using imageOrderCmp as a cmp function here will yield odd 
 | |
| // 		results -- in-place sorting a list based on relative element 
 | |
| // 		positions within itself is fun ;)
 | |
| function sortImages(cmp, reverse){
 | |
| 	cmp = cmp == null ? imageDateCmp : cmp
 | |
| 	cmp = cmp.constructor.name == 'Array' ? chainCmp(cmp) : cmp
 | |
| 
 | |
| 	DATA.order.sort(cmp)
 | |
| 	if(reverse){
 | |
| 		DATA.order.reverse()
 | |
| 	}
 | |
| 	updateRibbonOrder()
 | |
| 	dataUpdated()
 | |
| 
 | |
| 	$('.viewer').trigger('sortedImages', [cmp])
 | |
| }
 | |
| 
 | |
| 
 | |
| // shorthands...
 | |
| var sortImagesByDate = sortVia(imageDateCmp)
 | |
| var sortImagesByFileName = sortVia(imageNameCmp)
 | |
| var sortImagesByFileSeqOrName = sortVia(imageSeqOrNameCmp)
 | |
| var sortImagesByFileNameXPStyle = sortVia(imageXPStyleFileNameCmp)
 | |
| var sortImagesByDateOrSeqOrName = sortVia([
 | |
| 		imageDateCmp,
 | |
| 		imageSeqOrNameCmp
 | |
| 	])
 | |
| 
 | |
| 
 | |
| // Sort images by name while taking into account sequence overflows
 | |
| //
 | |
| // A name sequence overflow is when file name sequence overflows over
 | |
| // *9999 and then resets to *0001...
 | |
| //
 | |
| // For this to be applicable:
 | |
| // 	- ALL filenames must contain a sequence number
 | |
| // 		XXX do we need to make this more strict?
 | |
| // 			...for example make name.split(<seq>) equal for all files
 | |
| // 	- the total number of files in sequence is < 10K
 | |
| // 		XXX a simplification...
 | |
| // 			there could be more than 10K images but then we will need to
 | |
| // 			either take dates or folder names into account...
 | |
| // 	- the lowest filename in set must be near seq 0001
 | |
| // 	- the highest filename in set must be near seq 9999
 | |
| //		 XXX alternatively check the difference between first and 
 | |
| // 			last elements, if it is far greater than the number 
 | |
| // 			of elements then it's likely that we need to split...
 | |
| // 	- there must be a gap somewhere in the set
 | |
| // 		this gap size is roughly close to 10K - N where N is the total 
 | |
| // 		number of files in set
 | |
| // 		XXX a simplification...
 | |
| //
 | |
| // The gap size must be above overflow_gap, and if it is set to false 
 | |
| // then no limit is used (default: OVERFLOW_GAP).
 | |
| // If check_1st is false then also check the lowest sequence number
 | |
| // for proximity to 0001 (default: CHECK_1ST_PROXIMITY).
 | |
| //
 | |
| // NOTE: if any of the above conditions is not applicable this will
 | |
| // 		essentially revert to sortImagesByFileSeqOrName(...)
 | |
| // NOTE: this will cut at the largest gap between sequence numbers.
 | |
| //
 | |
| // XXX it would be a good idea to account for folder name sequencing...
 | |
| // XXX it's also a good idea to write an image serial number sort...
 | |
| // XXX is this overcomplicated???
 | |
| //
 | |
| // NOTE: I like this piece if code, if it works correctly no one will 
 | |
| // 		ever know it's here, if we replace it with the thee line dumb
 | |
| // 		sortImagesByFileName(...) then things get "annoying" every 10K 
 | |
| // 		images :)
 | |
| function sortImagesByFileNameSeqWithOverflow(reverse, proximity, overflow_gap, check_1st){
 | |
| 	proximity = proximity == null ? PROXIMITY : proximity
 | |
| 	overflow_gap = overflow_gap == null ? OVERFLOW_GAP : overflow_gap
 | |
| 	check_1st = check_1st == null ? CHECK_1ST_PROXIMITY : check_1st
 | |
| 
 | |
| 	// prepare to sort and check names...
 | |
| 	// NOTE: we do not usually have a filename seq 0000...
 | |
| 	if(DATA.order.length < 9999){
 | |
| 		var need_to_fix = true
 | |
| 
 | |
| 		function cmp(a, b){
 | |
| 			if(need_to_fix){
 | |
| 				if(typeof(getImageNameSeq(a)) == typeof('str') 
 | |
| 						|| typeof(getImageNameSeq(b)) == typeof('str')){
 | |
| 					need_to_fix = false
 | |
| 				}
 | |
| 			}
 | |
| 			return imageSeqOrNameCmp(a, b)
 | |
| 		}
 | |
| 
 | |
| 	// revert to normal sort my name...
 | |
| 	} else {
 | |
| 		// XXX make this more cleaver -- split the set into 10K chunks and
 | |
| 		// 		sort the chunks too...
 | |
| 		return sortImagesByFileName(reverse)
 | |
| 	}
 | |
| 
 | |
| 	DATA.order.sort(cmp)
 | |
| 
 | |
| 	// find and fix the gap...
 | |
| 	if(need_to_fix 
 | |
| 			// check if first and last are close to 0001 and 9999 resp.
 | |
| 			// XXX alternatively check the difference between first and 
 | |
| 			// 		last elements, if it is far greater than the number 
 | |
| 			// 		of elements then it's likely that we need to split...
 | |
| 			&& (!check_1st || getImageNameSeq(DATA.order[0]) <= proximity)
 | |
| 			&& getImageNameSeq(DATA.order[DATA.order.length-1]) >= 9999-proximity){
 | |
| 		// find the largest gap position...
 | |
| 		var pos = null
 | |
| 		var gap = 0
 | |
| 		for(var i=1; i<DATA.order.length; i++){
 | |
| 			var n_gap = Math.max(getImageNameSeq(DATA.order[i])-getImageNameSeq(DATA.order[i-1]), gap)
 | |
| 			if(n_gap != gap){
 | |
| 				pos = i
 | |
| 				gap = n_gap
 | |
| 			}
 | |
| 		}
 | |
| 		// split and rearrange the order chunks...
 | |
| 		if(overflow_gap === false || gap > overflow_gap){
 | |
| 			DATA.order = DATA.order.splice(pos).concat(DATA.order)
 | |
| 		}
 | |
| 	}
 | |
| 	if(reverse){
 | |
| 		DATA.order.reverse()
 | |
| 	}
 | |
| 
 | |
| 	updateRibbonOrder()
 | |
| 	dataUpdated()
 | |
| 	
 | |
| 	$('.viewer').trigger('sortedImagesByFileNameSeqWithOverflow')
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /************************************************** Manual sorting ***/
 | |
| 
 | |
| // Ordering images...
 | |
| // NOTE: this a bit more complicated than simply shifting an image 
 | |
| // 		left/right the DATA.order, we have to put it before or after
 | |
| // 		the prev/next image...
 | |
| function horizontalShiftImage(image, direction){
 | |
| 	image = image == null ? getImage() : $(image)
 | |
| 	var gid = getImageGID(image)
 | |
| 	var r = getRibbonIndex(image)
 | |
| 	var ri = DATA.ribbons[r].indexOf(gid)
 | |
| 
 | |
| 	// the image we are going to move relative to...
 | |
| 	var target = DATA.ribbons[r][ri + (direction == 'next' ? 1 : -1)]
 | |
| 
 | |
| 	// we can hit the end or start of the ribbon...
 | |
| 	if(target == null){
 | |
| 		return image
 | |
| 	}
 | |
| 
 | |
| 	// update the order...
 | |
| 	// NOTE: this is a critical section and must be done as fast as possible,
 | |
| 	// 		this is why we are using the memory to first do the work and 
 | |
| 	// 		then push it in...
 | |
| 	// NOTE: in a race condition this may still overwrite the order someone
 | |
| 	// 		else is working on, the data will be consistent...
 | |
| 	var order = DATA.order.slice()
 | |
| 	var from = order.indexOf(gid)
 | |
| 	order.splice(from, 1)
 | |
| 	order.splice(order.indexOf(target) + (direction == 'next'? 1 : 0), 0, gid)
 | |
| 	var to = order.indexOf(gid)
 | |
| 	// do the dirty work...
 | |
| 	// ...replace the order with the new order...
 | |
| 	DATA.order.splice.apply(DATA.order, [0, DATA.order.length].concat(order))
 | |
| 
 | |
| 	// just update the ribbons, no reloading needed...
 | |
| 	updateRibbonOrder(true)
 | |
| 
 | |
| 	// shift the images...
 | |
| 	getImage(target)[direction == 'prev' ? 'before' : 'after'](image)
 | |
| 
 | |
| 	// update stuff that changed, mainly order...
 | |
| 	updateImages()
 | |
| 	dataUpdated()
 | |
| 
 | |
| 	$('.viewer').trigger('horizontalShiftedImage', [gid, direction, from, to])
 | |
| 
 | |
| 	return image
 | |
| }
 | |
| function shiftImageLeft(image){
 | |
| 	return horizontalShiftImage(image, 'prev')
 | |
| }
 | |
| function shiftImageRight(image){
 | |
| 	return horizontalShiftImage(image, 'next')
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * Dialogs...
 | |
| */
 | |
| 
 | |
| function sortImagesDialog(){
 | |
| 
 | |
| 	updateStatus('Sort...').show()
 | |
| 
 | |
| 	var alg = 'Sort images by:'
 | |
| 	var rev = 'Descending'
 | |
| 
 | |
| 	cfg = {}
 | |
| 	cfg[alg] = [
 | |
| 		'Date |'
 | |
| 			+'fall back to file sequence then\n'
 | |
| 			+'file name when the earlier is equal.', 
 | |
| 		'Sequence number', 
 | |
| 		'Sequence number with overflow', 
 | |
| 		'File name' 
 | |
| 	]
 | |
| 	cfg[rev] = false
 | |
| 
 | |
| 	formDialog(null, '', 
 | |
| 			cfg,
 | |
| 			'OK', 
 | |
| 			'sortImagesDialog')
 | |
| 		.done(function(res){
 | |
| 			var reverse = res[rev]
 | |
| 			res = res[alg]
 | |
| 
 | |
| 			if(/Date/i.test(res)){
 | |
| 				var method = sortImagesByDateOrSeqOrName
 | |
| 
 | |
| 			} else if(/File name/i.test(res)){
 | |
| 				var method = sortImagesByFileNameXPStyle
 | |
| 
 | |
| 			} else if(/Sequence/i.test(res) && !/with overflow/.test(res)){
 | |
| 				var method = sortImagesByFileSeqOrName
 | |
| 
 | |
| 			} else if(/Sequence/i.test(res) && /with overflow/.test(res)){
 | |
| 				var method = sortImagesByFileNameSeqWithOverflow
 | |
| 
 | |
| 			} else {
 | |
| 				var method = sortImagesByFileName
 | |
| 			}
 | |
| 
 | |
| 			showStatusQ('Sorting by: '+res+'...')
 | |
| 
 | |
| 			method(reverse)
 | |
| 		})
 | |
| 		.fail(function(){
 | |
| 			showStatusQ('Sort: canceled.')
 | |
| 		})
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**********************************************************************
 | |
| * vim:set ts=4 sw=4 :                                                */
 |