' 89AACA2CC9D37D6949D087AAEDB12E8FD7B446F5C7030FC24085EB0CA1930F913491BB3B69FF779DF3088D110F115AABE8377669AD039417FC002B48A9B5B12B ' sRipeTech ' 37917A7AF4 2024-11-18 14:29:22 dbclose() newscript dir = app$path ' ----------------------------------- const appVersion = "2024-11-18" const appTitle = "Visually Alike" const appDescription = appTitle & "-Image Finder" dim myWindow = sob ("application","NEW", appDescription ) ' ----------------------------------- const TabChar = chr(0x09) const crChar = chr(0x0a) const readOnlyRGB = rgb ( 237, 229, 225 ) const greenBtnRGB = rgb ( 0x00, 0xff, 0x00 ) ' ----------------------------------- dim decodeInfo ' https://www.codeproject.com/Articles/5362105/Perceptual-Hash-based-Image-Comparison-Coded-in-pl ' ---------------- enum markNone 0 enum markKeep enum markAlikes enum markRemove enum markEqual FUNCTION ClickMyWindow ( sobid ) dim res IF ( dbsilentquery ("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' ") ) ThEn IF ( isDbOpen ) then res = MSGBOX ( "Save database before closing?", 3 , "Warning") ' returns 0 =left btn, 1 = mid btn , 2 = right btn SELECT case res CASE 0 ' yes cbDbsaveAs (sobid) CASE 1 ' no CASE 2 ' cancel EXIT function CASE else EXIT function END select END IF END if ' delete the SOB myWindow and automatically deletes all of its child SOBs sob ( myWindow, "delete") quit END function ' Associate the callback with myWindow sob ( myWindow , "ON" , "CLICK" , "ClickMyWindow" ) ' ----------------------------------------------------- ' add a menu bar myWindow dim myMenuBar = sob (myWindow , "add" , "menu" , "bar" ) ' add horizonal menus to myMenuBar dim myMenuFile = sob (myMenuBar , "add" , "menu" , "Horizontal" , "File" ) dim myMenuDB = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Database" ) dim myReDoAlike = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Recalculate Alikes" ) dim myMenuActions = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Actions" ) dim myMenuOptions = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Options" ) IF ( xlAllowed ) then dim myMenuXL = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Excel" ) END if dim myMenuFont = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Font" ) dim myMenuHelp = sob (myMenuBar , "add" , "menu" , "Horizontal" , "Help" ) ' ----------------------------------------------------- FUNCTION ClickNew ( sobid ) drop table if exists masterTbl drop table if exists myTable drop table if exists inputTbl vacuum sob ( dbSaveSOB , "SET" , "MENU.GREY" , 1) END function sob ( myMenuFile , "add" , "menu" , "Vertical" , "New" ) sob ( -1 , "ON" , "CLICK" , "ClickNew") ' ----------------------------------------------------- dim LastProcessedFileName = "" sob ( myMenuFile , "add" , "menu" , "Vertical" , "Exit" ) sob ( -1 , "ON" , "CLICK" , "ClickMyWindow") ' ----------------------------------------------------- FUNCTION cbSinglePopDefaultFileHandling ( sobid ) shellExecute ( dbSilentQuery ( "select path||name from masterTbl where self = " & popUpSelf ) ) END function FUNCTION cbSinglePopOpenFolder ( sobid ) shellExecute ( dbSilentQuery ( "select path from masterTbl where self = " & popUpSelf ) ) END function FUNCTION cbSinglePopMark ( sobid ) dim UseCanvas = canvas( SOB( ImageSob , "GET", "CANVAS.LOWER" ) , "CLONE") dim Mark = dbSilentQuery ("Select marked from masterTbl where self = " & popUpSelf ) IF ( Mark == markRemove ) then Mark = markNone ELSE Mark = markRemove END if dbSilentQuery ( "Update masterTbl set marked = " & mark & " where self = " & popUpSelf ) sob ( ImageSob, "EMPTY") IF ( Mark == markRemove ) then sob( ImageSob, "SET", "Graphics.upper.canvas", Canvas(RemoveCanvas,"CLONE") ) END if SOB( ImageSob , "SET", "GRAPHICS.LOWER.CANVAS" , useCanvas ) END function FUNCTION cbSinglePopKeep ( sobid ) dim UseCanvas = canvas( SOB( ImageSob , "GET", "CANVAS.LOWER" ) , "CLONE") dim Mark = dbSilentQuery ("Select marked from masterTbl where self = " & popUpSelf ) IF ( Mark == markKeep ) then Mark = markNone ELSE Mark = markKeep END if dbSilentQuery ( "Update masterTbl set marked = " & mark & " where self = " & popUpSelf ) sob ( ImageSob, "EMPTY") IF ( Mark == markKeep ) then sob( ImageSob, "SET", "Graphics.upper.canvas", Canvas(KeepCanvas,"CLONE") ) END if SOB( ImageSob , "SET", "GRAPHICS.LOWER.CANVAS" , useCanvas ) END function FUNCTION cbSinglePopEqual ( sobid ) dim UseCanvas = canvas( SOB( ImageSob , "GET", "CANVAS.LOWER" ) , "CLONE") dim Mark = dbSilentQuery ("Select marked from masterTbl where self = " & popUpSelf ) IF ( Mark == markEqual ) then Mark = markNone ELSE Mark = markEqual END if dbSilentQuery ( "Update masterTbl set marked = " & mark & " where self = " & popUpSelf ) sob ( ImageSob, "EMPTY") IF ( Mark == markEqual ) then sob( ImageSob, "SET", "Graphics.upper.canvas", Canvas(EqualCanvas,"CLONE") ) END if SOB( ImageSob , "SET", "GRAPHICS.LOWER.CANVAS" , useCanvas ) END function ' ----------------------------------- dim ImageFloatWindow = sob ("application", "NEW", appTitle &"-Image Grid: self / width*height / size / dpi /distance") FUNCTION ClickCMRWindow ( sobid) actionOnCancel ( sobid) END FUNCTION ' Associate the callback with myWindow sob ( ImageFloatWindow , "ON" , "CLICK" , "ClickCMRWindow" ) ' ----------------------------------------------------- dim MultiSelectionPopup = sob ( ImageFloatWindow, "add", "Menu", "popup") ' ----------------------------------------------------- dim singleSelectionPopup = sob ( ImageFloatWindow, "add", "Menu", "popup") sob (singleSelectionPopup , "add", "menu","Vertical", "Toggle REMOVE Mark") sob ( -1 ,"ON","press", "cbSinglePopMark") sob (singleSelectionPopup , "add", "menu","Vertical", "Toggle KEEP Mark") sob ( -1 ,"ON","press", "cbSinglePopKeep") sob (singleSelectionPopup , "add", "menu","Vertical", "Toggle EQUAL Mark") sob ( -1 ,"ON","press", "cbSinglePopEqual") ' ----------------------------------------------------- SOB ( singleSelectionPopup , "ADD" , "MENU" , "SPACER" ) ' ----------------------------------------------------- sob (singleSelectionPopup , "add", "menu","Vertical", "Default File handling") sob ( -1 ,"ON","press", "cbSinglePopDefaultFileHandling") sob (singleSelectionPopup , "add", "menu","Vertical", "Open folder") sob ( -1 ,"ON","press", "cbSinglePopOpenFolder") sob ( ImageFloatWindow,"SET" ,"SHOW",0) ' ----------------------------------- FUNCTION cbHelpApplication ( sobid ) sob ( ImageFloatWindow,"SET" ,"SHOW",0) END function sob ( ImageFloatWindow,"ON","CLICK", "cbHelpApplication") ' ----------------------------------------------------- enum iFLTName 0 enum iFLTdetails enum iFltImage enum iFltFields FUNCTION initialise_matrix ( pSob , mArray ) dim index , iloop FOR iloop = 0 to (ubound(mArray) / iFltFields ) index = iloop * iFltFields sob ( pSob , "ADD", "CONTAINER", "COLUMN.W" ) mArray(index+iFLTName) = sob ( -1 , "ADD", "LABEL", " " ) mArray(index+iFLTdetails) = sob ( -1 , "ADD", "LABEL", " " ) mArray(index+iFltImage) = sob ( -1 , "ADD", "graphics.holder",0,64,64) sob ( mArray(index+iFltImage) , "ON" , "CLICK" , "cbClickImage" ) sob ( mArray(index+iFltImage) , "ON" , "MOUSECLICKS", "cbImageMOUSECLICKS") sob ( mArray(index+iFltImage), "SET", "DATA", 0 ) NEXT iloop END FUNCTION dim fltFrame = sob ( ImageFloatWindow,"ADD", "container", "FRAME") dim sobOvlHolder = sob ( fltFrame,"ADD", "container", "OVERLAY") dim fltMatrix1 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 1 ) dim fltSobs1 = HandleArray(1*iFltFields) initialise_matrix ( fltMatrix1 , fltSobs1 ) dim fltMatrix2 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 2 ) dim fltSobs2 = HandleArray(2*iFltFields) initialise_matrix ( fltMatrix2 , fltSobs2 ) dim fltMatrix4 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 2 ) dim fltSobs4 = HandleArray(4*iFltFields) initialise_matrix ( fltMatrix4 , fltSobs4 ) dim fltMatrix6 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 3 ) dim fltSobs6 = HandleArray(6*iFltFields) initialise_matrix ( fltMatrix6 , fltSobs6 ) dim fltMatrix8 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 4 ) dim fltSobs8 = HandleArray(8*iFltFields) initialise_matrix ( fltMatrix8 , fltSobs8 ) dim fltMatrix9 = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 3 ) dim fltSobs9 = HandleArray(9*iFltFields) initialise_matrix ( fltMatrix9 , fltSobs9 ) dim fltMatrixX = sob ( sobOvlHolder, "ADD", "CONTAINER", "MATRIX.c", 3 ) dim fltSobsX = HandleArray(9*iFltFields) initialise_matrix ( fltMatrixX , fltSobsX ) constant nbrImageGroups = 9 dim CopyMoveDeleteSOB = sob ( sobOvlHolder, "ADD", "CONTAINER", "FRAME") ' ----------------------------------------------------- dim CMColumnSOB = sob ( CopyMoveDeleteSOB, "ADD", "CONTAINER", "COLUMN.WH") sob ( -1 , "ADD", "SPACE",1,1 ) dim cmdSOBaction = sob ( -1 , "ADD", "LABEL", " " ) sob(-1, "SET", "RGB", -1 ) sob ( -1 , "ADD", "SPACE",1,1 ) ' ---------------- sob ( CMColumnSOB , "ADD", "CONTAINER", "ROW" ) sob ( -1 , "ADD", "SPACE",1,1 ) sob ( -1 , "ADD", "LABEL","Select the group of files to be acted upon." ) dim TargetRowSOB = sob ( CMColumnSOB, "ADD", "CONTAINER", "ROW.WH") dim actionQuery dim actionType enum actionNone 0 enum actionCopy enum actionMove enum actionRemove dim markerType = markNone ' -------------------------------- FUNCTION ActionOnKeeps( sobid ) markerType = markKeep END Function dim cmdSOBKeeps = sob ( TargetRowSOB , "ADD", "BUTTON", "RADIO" , "Keeps" ) sob ( -1 , "ON", "CLICK", "ActionOnKeeps") ' -------------------------------- FUNCTION ActionOnEquals ( sobid ) markerType = markEqual END Function dim cmdSOBEquals = sob ( TargetRowSOB , "ADD", "BUTTON", "RADIO" , "Equals" ) sob ( -1 , "ON", "CLICK", "ActionOnEquals") ' -------------------------------- FUNCTION ActionOnAlikes( sobid ) markerType = markAlikes END Function dim cmdSOBAlikes = sob ( TargetRowSOB , "ADD", "BUTTON", "RADIO" , "Alikes" ) sob ( -1 , "ON", "CLICK", "ActionOnAlikes") ' -------------------------------- FUNCTION ActionOnRemoves( sobid ) markerType = markRemove END Function dim cmdSOBRemoves = sob ( TargetRowSOB , "ADD", "BUTTON", "RADIO" , "Removes" ) sob ( -1 , "ON", "CLICK", "ActionOnRemoves") ' ----------------------------------------------------- sob ( CMColumnSOB , "ADD", "SPACE",1,1 ) sob (CMColumnSOB , "ADD", "LABEL","Where clause" ) dim whereClauseCopySob = sob ( CMColumnSOB , "ADD", "LABEL"," " ) ' ---------------- sob ( CMColumnSOB , "ADD", "SPACE",1,1 ) FUNCTION cbGetDestination ( sobid ) sob( dstFolderSob , "SET", "TITLE" , getPath() ) END FUNCTION sob ( CMColumnSOB , "ADD", "BUTTON", "PUSH","Click to change/set Destination" ) sob ( -1 , "ON", "CLICK", "cbGetDestination") dim dstFolderSob = sob ( CMColumnSOB , "ADD", "LABEL"," " ) sob ( dstFolderSob , "EMPTY") ' ----------------------------------------------------- sob ( CMColumnSOB , "ADD", "SPACE",1,1 ) sob (CMColumnSOB , "ADD", "LABEL","Action" ) dim ExecuteOrCancelSOB = sob ( CMColumnSOB, "ADD", "CONTAINER", "ROW.WH") FUNCTION ActionOnCancel( sobid ) sob (ImageFloatWindow, "SET", "TITLE", appTitle & "-Image Grid: self / width*height / size / dpi / distance") sob( ImageFloatWindow , "SET", "SHOW", lastImageFloatWindowState ) IF ( lastUsedOvl ) then sob( lastUsedOvl, "SET", "SHOW", lastImageFloatWindowState ) ELSE sob( ImageFloatWindow, "SET", "SHOW", 0 ) END if sob( myWindow , "SET", "SHOW", 1 ) END Function dim cmdSOBCancel = sob ( ExecuteOrCancelSOB , "ADD", "BUTTON", "PUSH" , "Cancel" ) sob ( -1 , "ON", "CLICK", "ActionOnCancel") ' -------------------------------- FUNCTION CopyFiles ( useQuery ) dim fso = createobject ("Scripting.FileSystemObject") IF ( ! fso ) THEN report( "Could not create the File System Object " ) EXIT function END IF dim targetFolder = sob( dstFolderSob , "GET", "TITLE" ) WITHQUERY ( useQuery ) IF ( isFile ( wqtext(1) & wqtext(2) ) ) then TRY fso.copyFile ( wqtext(1) & wqtext(2) , targetFolder & wqText(2) ) END try IF ( isFile (targetFolder & wqtext(2) ) ) then ELSE report ( "Copy file failure: " & chr(0x09) & wqtext(1) & wqtext(2) & chr(0x09) & targetFolder & chr(0x09) ) END IF ELSE report ("not a file " & wqtext(1) & wqtext(2) ) END if END withquery END FUNCTION ' -------------------------------- FUNCTION MoveFiles ( useQuery ) dim fso = createobject ("Scripting.FileSystemObject") IF ( ! fso ) THEN report( "Could not create the File System Object " ) EXIT function END IF dim targetFolder = sob( dstFolderSob , "GET", "TITLE" ) WITHQUERY ( useQuery ) IF ( isFile ( wqtext(1) & wqtext(2) ) ) then TRY fso.MoveFile ( wqtext(1) & wqtext(2) , targetFolder ) END try IF ( isFile (targetFolder & wqtext(2) ) ) then sql("delete from masterTbl where self = " & wqInt(3) ) ELSE report ( "Move file failure: " & chr(0x09) & wqtext(1) & wqtext(2) & chr(0x09) & targetFolder & chr(0x09) ) END IF ELSE report ("not a file " & wqtext(1) & wqtext(2) ) END if END withquery END FUNCTION FUNCTION ActionOnExecute (sobid ) IF ( ! markerType ) then Message("No SOURCE selected" ) EXIT function END if IF ( ! dbsilentquery ("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' ") ) ThEn Message("No folder(s)/file(s) have been provided" ) EXIT function END if dim WhereClause = sob ( whereClauseCopySob, "GET", "TITLE") IF length(WhereClause) then actionQuery = "SELECT path , name, self from masterTbl where marked = " & markerType & " and " & whereClause ELSE actionQuery = "SELECT path , name, self from masterTbl where marked = " & markerType END IF TRY SELECT case actionType CASE actionNone report ("Action = none") CASE actionRemove report ("Action = remove") WITHQUERY ( actionQuery ) DeleteFile ( wqtext(1) & wqText(2) ) sql ("delete from masterTbl where self = " & wqInt(3) ) END WITHQUERY drop table if exists xHashTbl create virtual table temp.xHashTbl using bk64tree insert into xHashTbl (metric , link) select phash , rowid from masterTbl where type = 'file' and not phash is NULL CASE actionCopy IF ( sob( dstFolderSob , "GET", "TITLE" ) == "" ) then cbGetDestination (sobid) END if IF ( sob( dstFolderSob , "GET", "TITLE" ) == "" ) then EXIT function END if CopyFiles ( actionQuery ) CASE actionMove IF ( sob( dstFolderSob , "GET", "TITLE" ) == "" ) then cbGetDestination (sobid) END if IF ( sob( dstFolderSob , "GET", "TITLE" ) == "" ) then EXIT function END if MoveFiles ( actionQuery ) CASE ELSE report ("Action = else") END select END TRY ActionOnCancel( sobid ) ' triggers a return to normal working END Function sob ( ExecuteOrCancelSOB , "ADD", "SPACE",1,1 ) sob ( ExecuteOrCancelSOB , "ADD", "SPACE",1,1 ) sob ( ExecuteOrCancelSOB , "ADD", "BUTTON", "PUSH" , "Execute" ) sob ( -1 , "ON", "CLICK", "ActionOnExecute") ' -------------------------------- sob ( CopyMoveDeleteSOB , "ADD", "SPACE",1,1 ) ' -------------------------------- dim lastImageFloatWindowState FUNCTION CopyMoveRemoveCommon ( title , doAction ) actionType = doAction dim WhereClause = sob ( SobWhereClause , "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if sob ( whereClauseCopySob, "SET", "TITLE" ,WhereClause ) SELECT CASE actionType CASE actionmove sob (cmdSOBaction, "SET", "RGB" , rgb(0xff, 0x7f, 0x7f) ) CASE actionremove sob (cmdSOBaction, "SET", "RGB" , rgb(0xff, 0x00, 0x00) ) CASE actionCopy sob (cmdSOBaction, "SET", "RGB" , rgb(0x00, 0xff, 0x00) ) CASE else sob (cmdSOBaction, "SET", "RGB" , rgb(0x00, 0xff, 0x00) ) END Select sob ( cmdSOBaction , "SET", "TITLE" , "Action: " & toUpper(title) ) lastImageFloatWindowState = sob ( ImageFloatWindow,"GET" ,"SHOW" ) sob ( ImageFloatWindow,"SET" ,"SHOW",1) sob ( ImageFloatWindow,"SET" ,"TITLE","Copy / move / remove") sob( CopyMoveDeleteSOB, "SET", "SHOW", 1 ) sob( myWindow , "SET", "SHOW", 0 ) END Function ' ----------------------------------------------------- FUNCTION cbTableCOPY ( sobid ) CopyMoveRemoveCommon ("Copy" , actionCopy) END Function sob ( myMenuActions , "add" , "menu" , "Vertical" , "Copy" ) sob ( -1 , "ON" , "CLICK" , "cbTableCOPY") ' -------------------------------- FUNCTION cbTableMOVE ( sobid ) CopyMoveRemoveCommon ("Move" , actionMove) END Function sob ( myMenuActions , "add" , "menu" , "Vertical" , "Move" ) sob ( -1 , "ON" , "CLICK" , "cbTableMOVE") ' -------------------------------- FUNCTION cbTableRemoves ( sobid ) CopyMoveRemoveCommon ("Remove", actionRemove) END Function sob ( myMenuActions , "add" , "menu" , "Vertical" , "Remove" ) sob ( -1 , "ON" , "CLICK" , "cbTableRemoves") ' ----------------------------------------------------- FUNCTION cbAlikesAllFiles ( sobid ) sob ( dbLV, "SET", "SHOW", 0 ) drop table if exists xHashTbl create virtual table temp.xHashTbl using bk64tree update masterTbl set neighbour= "" insert into xHashTbl (metric , link) select phash , rowid from masterTbl where type = 'file' and not phash is NULL update masterTbl set neighbour = 0 > update masterTbl set neighbour = >> eval (' select substr ( "0000" || min (distance), -4 ) >> , link from xHashTbl ( ' || pHash || ', freestring = freestring & Alikeness >> ) where not link = ' || self || ' ') >> where not pHash is null sql(freestring) update masterTbl set distance = substr( neighbour, 1,4) * 1 update masterTbl set neighbour= substr( neighbour, 5) * 1 sob ( sobCurrentShow , "TRIGGER") sob ( dbLV, "SET", "SHOW", 1 ) END function sob ( myReDoAlike , "add" , "menu" , "Vertical" , "Using all files" ) sob ( -1 , "ON" , "CLICK" , "cbAlikesAllFiles") ' -------------------------------- FUNCTION cbAlikesOnlyUnmarked ( sobid ) sob ( dbLV, "SET", "SHOW", 0 ) drop table if exists xHashTbl create virtual table temp.xHashTbl using bk64tree update masterTbl set neighbour= "" insert into xHashTbl (metric , link) select phash , rowid from masterTbl where type = 'file' and not phash is NULL and marked = 0 update masterTbl set neighbour = 0 > update masterTbl set neighbour = >> eval (' select substr ( "0000" || min (distance), -4 ) >> , link from xHashTbl ( ' || pHash || ', freestring = freestring & Alikeness >> ) where not link = ' || self || ' ') >> where not pHash is null and marked = 0 sql(freestring) update masterTbl set distance = substr( neighbour, 1,4) * 1 update masterTbl set neighbour= substr( neighbour, 5) * 1 sob ( sobCurrentShow , "TRIGGER") sob ( dbLV, "SET", "SHOW", 1 ) END function sob ( myReDoAlike , "add" , "menu" , "Vertical" , "Using unmarked files" ) sob ( -1 , "ON" , "CLICK" , "cbAlikesOnlyUnmarked") ' ----------------------------------------------------- dim dbSaveSOB FUNCTION cbDBsave ( sobid ) DBSave () END Function dbSaveSOB = sob ( myMenuDB , "add" , "menu" , "Vertical" , "Save DB" ) sob ( -1 , "ON" , "CLICK" , "cbDBsave") sob ( dbSaveSOB , "SET" , "MENU.GREY" , 1) FUNCTION cbDBsaveAs ( sobid ) DBSaveAs ( ,1) sob ( dbSaveSOB , "SET" , "MENU.GREY" , 0) END Function sob ( myMenuDB , "add" , "menu" , "Vertical" , "Save DB as" ) sob ( -1 , "ON" , "CLICK" , "cbDBsaveAs") ' ----------------------------------------------------- FUNCTION getPropertiesWIA ( FilePath , FileName ) dim fs, folder dim objIF = CreateObject("WIA.ImageFile") dim objV = CreateObject("WIA.Vector") TRY objIF.LoadFile ( filePath & fileName ) CATCH Report( "File error, or file not supported by WIA.IMAGEFILE" & chr(0x0a) & chr(0x0a) ) EXIT function END try dim txt , limit , name dim prop , i = 0 ,j , oV FOR each prop in objIF.properties i++ WITH prop IF ( ( .PropertyID <> 20507) And ( .PropertyID <> 20624) And ( .PropertyID <> 20625)) then name = .name ' ---------------------------------------------- ' align the text returnValue = name & spaces(30 - length(name) ) ' ---------------------------------------------- IF .IsVector == False Then IF .type == 1006 Then TRY returnValue = ( returnValue & .Value & " " & .Value.Numerator & "/" & .Value.Denominator & " " ) CATCH returnValue = ( returnValue & "1066 " & .Value ) END TRY ELSE TRY IF ( .PropertyID == 274 ) then returnValue = ( returnValue & .Value & " " & getOrientation ( .Value ) ) ELSE returnValue = ( returnValue & .Value ) END if CATCH returnValue = ( returnValue & " .Value ?? " ) END try END If ELSE ' isVector oV = .Value txt = "" limit = oV.Count IF ( limit > 64 ) then limit = 64 END if FOR j = 1 To limit IF ( j == 1 ) then txt = txt & istype(ov.item(j) ) & " " END if TRY txt = txt & hex( oV.Item(j) ) & " " CATCH txt = txt & " ? " END try NEXT j IF ( oV.Count > 64 ) then txt = txt & "..." END if txt = Trim(txt) returnValue =( returnValue & txt ) END If report ( returnValue) END iF END with NEXT prop SOB ( decodeInfo , "SET" , "ROW" , "TOP" , 1 ) END function ' ----------------------------------------------------- FUNCTION cbClickImage ( sobid ) dim rid = sob ( sobid,"GET", "DATA") IF ( rid == 0 ) then EXIT function END IF dim path, name report() path = dbSilentQuery ( "select path from masterTbl where self = " & rid ) name = dbSilentQuery ( "select name from masterTbl where self = " & rid ) report ( "File: " & path & name) report("Rowid " & rid ) SOB ( decodeInfo , "SET" , "SHOW" , 0 ) getPropertiesWIA ( path , name ) SOB ( decodeInfo , "SET" , "ROW" , "TOP" , 1 ) SOB ( decodeInfo , "SET" , "SHOW" , 1 ) END function ' ----------------------------------------------------- FUNCTION cbImageMOUSECLICKS ( sobid, clickId , row, col ) popUpSelf = sob ( sobid, "get", "data") IF ( popUpSelf == 0 ) then EXIT function END if ImageSob = sobid ' get the position of the mouse and use that for the position of the Popup menu sob(singleSelectionPopup,"SET","POSITION", sob ( ImageFloatWindow , "get" , "cursor.x" ), sob ( ImageFloatWindow , "get" , "cursor.y" ) ) sob(singleSelectionPopup,"set","show",1) END function ' ----------------------------------------------------- dim RemoveCanvas = canvas ( "new", "BLANK", 64,64) canvas ( RemoveCanvas, "flood", rgb (1,1,1) ) canvas ( RemoveCanvas, "SET", "PEN", "RGB" , rgb (0xff,0,0) ) canvas ( RemoveCanvas, "SET", "PEN", "WIDTH" , 4 ) canvas ( RemoveCanvas, "SET", "PEN", "STYLE" , 0 ) ' solid line canvas ( RemoveCanvas, "LINE", 8,8, 56,56 ) canvas ( RemoveCanvas, "LINE", 8,56, 56,8 ) dim EqualCanvas = canvas ( "new", "BLANK", 64,64) canvas ( EqualCanvas, "flood", rgb (1,1,1) ) canvas ( EqualCanvas, "SET", "PEN", "RGB" , rgb (00,0xff,00) ) canvas ( EqualCanvas, "SET", "PEN", "WIDTH" , 4 ) canvas ( EqualCanvas, "SET", "PEN", "STYLE" , 0 ) ' solid line canvas ( EqualCanvas, "LINE", 12,31, 52,31 ) canvas ( EqualCanvas, "LINE", 12,41, 52,41 ) dim KeepCanvas = canvas ( "new", "BLANK", 64,64) canvas ( KeepCanvas, "flood", rgb (1,1,1) ) canvas ( KeepCanvas, "SET", "PEN", "RGB" , rgb (00,0xff,00) ) canvas ( KeepCanvas, "SET", "PEN", "WIDTH" , 4 ) canvas ( KeepCanvas, "SET", "PEN", "STYLE" , 0 ) ' solid line canvas ( KeepCanvas, "LINE", 56,08, 16,48 ) canvas ( KeepCanvas, "LINE", 16,48, 15,36 ) ' ----------------------------------------------------- dim lastUsedOvl = 0 FUNCTION displayRootAndNearests ( sobid ) dim useOvl, usefltSobs static dim lastArray = 0 sob ( ImageFloatWindow,"SET" ,"SHOW",1) sob ( ImageFloatWindow,"TOP" ) dim pHash = dbSilentQuery ( "select pHash from masterTbl where self = " & popUpSelf ) dim cnt = dbSilentQuery ("select count(*) from xHashTbl (" & pHash & "," & Alikeness & ")" ) dim i ' ----------------------------------------- ' be tidy, clean up after previous IMAGE grid was displayed IF ( lastArray ) then FOR i = 0 to ubound( lastArray) sob ( lastArray (i) , "empty") sob ( lastArray (i), "SET", "DATA", 0 ) NEXT i END if SELECT CASE cnt CASE 1 useOvl = fltMatrix1 usefltSobs = fltSobs1 CASE 2 useOvl = fltMatrix2 usefltSobs = fltSobs2 CASE 3 , 4 useOvl = fltMatrix4 usefltSobs = fltSobs4 CASE 5, 6 useOvl = fltMatrix6 usefltSobs = fltSobs6 CASE 7, 8 useOvl = fltMatrix8 usefltSobs = fltSobs8 CASE 9 useOvl = fltMatrix9 usefltSobs = fltSobs9 CASE ELSE useOvl = fltMatrixX usefltSobs = fltSobsX END select lastUsedOvl = useOVL sob( useOVL, "SET", "SHOW", 1 ) set lastArray = usefltSobs ' ----------------------------------------- ' display the ROOT image dim path0, name0, fsize0 , marked0 WITHQUERY ("select path, name, size, marked from masterTbl where self = " & popUpSelf ) path0 = wqText(1) name0 = wqText(2) fsize0 = wqInt (3) marked0 = wqInt(4) END WITHQUERY IF ( marked0 == markRemove ) then sob( usefltSobs( iFltImage ), "SET", "Graphics.upper.canvas", Canvas(RemoveCanvas,"CLONE") ) ELSEIF ( marked0 == markKeep ) then sob( usefltSobs( iFltImage ), "SET", "Graphics.upper.canvas", Canvas(KeepCanvas,"CLONE") ) ELSEIF ( marked0 == markEqual ) then sob( usefltSobs( iFltImage ), "SET", "Graphics.upper.canvas", Canvas(EqualCanvas,"CLONE") ) END if sob( usefltSobs(iFltName), "SET", "TITLE" , name0 ) sob( usefltSobs(iFltDetails), "SET", "TITLE" , cstr(popUpSelf) & "/" & getFilePropertiesWH ( path0, name0) ) sob( usefltSobs(iFltImage), "SET", "DATA", popUpSelf ) sob( usefltSobs(iFltImage), "SET", "Graphics.lower", dbSilentQuery( "select path||name from masterTbl where self = " & popUpSelf & " limit " & nbrImageGroups - 1) ) ' ----------------------------------------- dim path, name, fsize , marked dim iFlt = 1 WITHQUERY ( "select link, distance from xHashTbl (" & pHash & "," & Alikeness & ") where not link = " & popUpSelf & " order by distance asc limit " & (nbrImageGroups -1) ) path = dbSilentQuery ( "select path from masterTbl where self = " & wqInt(1) ) name = dbSilentQuery ( "select name from masterTbl where self = " & wqInt(1) ) marked = dbSilentQuery ( "select marked from masterTbl where self = " & wqInt(1) ) fsize = dbSilentQuery ( "select size from masterTbl where self = " & wqInt(1) ) sob( usefltSobs( (iFlt*iFltFields)+iFltName ), "SET", "TITLE" , name ) sob( usefltSobs( (iFlt*iFltFields)+iFltDetails ), "SET", "TITLE" , wqText(1) & "/" & getFilePropertiesWH ( path, name) & "/" & wqtext(2) ) IF ( marked == markRemove ) then sob( usefltSobs((iFlt*iFltFields)+iFltImage ), "SET", "Graphics.upper.canvas", Canvas(RemoveCanvas,"CLONE") ) ELSEIF ( marked == markKeep ) then sob( usefltSobs((iFlt*iFltFields)+iFltImage ), "SET", "Graphics.upper.canvas", Canvas(KeepCanvas ,"CLONE") ) ELSEIF ( marked == markEqual ) then sob( usefltSobs((iFlt*iFltFields)+iFltImage ), "SET", "Graphics.upper.canvas", Canvas(EqualCanvas ,"CLONE") ) END IF sob( usefltSobs( (iFlt*iFltFields)+iFltImage ), "SET", "Graphics.lower", path & name ) sob( usefltSobs( (iFlt*iFltFields)+iFltImage ), "SET", "DATA", wqint(1) ) iFlt++ END withQuery sob( useOVL, "SET", "SHOW", 1 ) sob ( ImageFloatWindow,"SET" ,"SHOW",1) END function ' ----------------------------------------------------- dim ShowAlikesSob FUNCTION cbDBload ( sobid ) dbClose() dbExtension ("bk64tree",1) dbExtension ("popcount",1) dbExtension ("eval",1) IF ( dbopen("memory", selectDBfile() ) ) then drop table if exists xHashTbl create virtual table temp.xHashTbl using bk64tree insert into xHashTbl (metric , link) select phash , rowid from masterTbl where type = 'file' and not phash is NULL sob ( ShowAlikesSob , "TRIGGER") sob ( dbSaveSOB , "SET" , "MENU.GREY" , 0) ELSE sob ( dbSaveSOB , "SET" , "MENU.GREY" , 1) END if END Function sob ( myMenuDB , "add" , "menu" , "Vertical" , "Load DB" ) sob ( -1 , "ON" , "CLICK" , "cbDBload") ' IMPORTANT ' If you are going to use POPUPs then they need to be created ' here ! dim myPopUp = sob ( myWindow, "add", "Menu", "popup") ' ----------------------------------------------------- FUNCTION cbByHeader ( sobid ) SOB (dbLV, "SET" , "COLUMN" , "WIDTH" , -2 ) END Function sob ( myMenuOptions , "add" , "menu" , "Vertical", "Layout -by column heading" ) sob (-1, "TRIGGER") sob (-1, "ON" , "CLICK" , "cbByHeader" ) FUNCTION cbByText ( sobid ) SOB (dbLV, "SET" , "COLUMN" , "WIDTH" , -1 ) END Function sob ( myMenuOptions , "add" , "menu" , "Vertical", "Layout -by column text" ) sob (-1, "ON" , "CLICK" , "cbByText" ) dim sobCurrentShow FUNCTION ShowRowCount () sob( sobCount , "SET", "TITLE", cstr(SOB ( dbLV , "GET" , "ROWS" )) ) END Function FUNCTION cbShowFiles ( sobid ) dim WhereClause sobCurrentShow = sobId sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' ") ) ThEn WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then TRY ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where " & whereClause ) ) , 2) sql (" SELECT self, * from masterTbl where " & whereClause ) CATCH report ("Invalid WHERE clause ?") END try ELSE ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl " ) ) , 2) SELECT self, * from masterTbl END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END Function sob ( myMenuOptions , "add" , "menu" , "Vertical", "Show all files" ) sob ( -1 , "ON" , "CLICK" , "cbShowFiles" ) ' ----------------------------------------------------- FUNCTION cbShowMarked ( sobid ) sobCurrentShow = sobId dim cnt sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' " ) ) then dim WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where not marked = " & markNone & " and " & whereClause ) ) , 2) TRY sql (" SELECT self, * from masterTbl where not marked = " & markNone & " and " & whereClause ) CATCH report ("Invalid WHERE clause ?") END try ELSE ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where not marked = " & markNone ) ) , 2) sql ( "SELECT self, * from masterTbl where not marked = " & markNone ) END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END Function FUNCTION cbShowEqual (sobid) sobCurrentShow = sobId sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' " ) ) then dim WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markEqual & " and " & whereClause ) ) , 2) TRY sql (" SELECT self, * from masterTbl where marked = " & markEqual & " and " & whereClause ) CATCH report ("Invalid WHERE clause ?") END try ELSE ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markEqual ) ) , 2) sql ( "SELECT self, * from masterTbl where marked = " & markEqual ) END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END FUNCTION FUNCTION cbShowRemove ( sobid ) sobCurrentShow = sobId sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' " ) ) then dim WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markRemove & " and " & whereClause ) ) , 2) TRY sql (" SELECT self, * from masterTbl where marked = " & markRemove & " and " & whereClause ) CATCH report ("Invalid WHERE clause ?") END try ELSE ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markRemove ) ) , 2) sql ( "SELECT self, * from masterTbl where marked = " & markRemove ) END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END Function FUNCTION cbShowKeep ( sobid ) sobCurrentShow = sobId sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' " ) ) then dim WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markKeep & " and " & whereClause ) ) , 2) TRY sql (" SELECT self, * from masterTbl where marked = " & markKeep & " and " & whereClause ) CATCH report ("Invalid WHERE clause ?") END try ELSE ProgressText(cstr( dbsilentquery ("SELECT count(*) FROM masterTbl where marked = " & markKeep ) ) , 2) sql ( "SELECT self, * from masterTbl where marked = " & markKeep ) END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END Function sob ( myMenuOptions , "add" , "menu" , "Vertical", "Show marked files" ) sob ( -1 , "ON" , "CLICK" , "cbMarkedFiles" ) ' ----------------------------------------------------- FUNCTION cbShowAlikes( sobid ) sobCurrentShow = sobAlikeRadio sob (sobAlikeRadio, "SET", "CHECK", 1 ) sob ( dbLv, "SET", "SHOW", 0) IF ( dbsilentquery ("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl'")) THEN dim WhereClause = sob ( sobWhereClause, "GET", "TITLE") WhereClause = trim (WhereClause) IF ( ! sob(sobCheckFilter, "GET", "CHECK" ) ) then WhereClause = "" END if ProgressShow(1) IF length(WhereClause) then ProgressText(cstr( dbsilentquery (" SELECT count(*) from masterTbl where neighbour > 0 and " & whereClause & " order by distance asc" ) ) , 2) sql ("SELECT self, path, name, size, self, neighbour , distance from masterTbl where neighbour > 0 and " & whereClause & " order by distance asc" ) ELSE ProgressText(cstr( dbsilentquery (" SELECT count(*) from masterTbl where neighbour > 0 order by distance asc" ) ) , 2) SELECT self, path, name, size, self, neighbour , distance from masterTbl where neighbour > 0 order by distance asc END IF ProgressShow(0) END if ShowRowCount () sob ( dbLv, "SET", "SHOW", 1) END Function ShowAlikesSob = sob ( myMenuOptions , "add" , "menu" , "Vertical", "Show alike files" ) sob ( -1 , "ON" , "CLICK" , "cbShowAlikes" ) ' ----------------------------------------------------- FUNCTION cbClearReport( sobid ) report() END Function sob ( myMenuOptions , "add" , "menu" , "Vertical", "Clear report panel" ) sob ( -1 , "ON" , "CLICK" , "cbClearReport" ) ' ----------------------------------------------------- dim sobXLstart , sobXLstop, sobxlWrite, sobxlWriteLV FUNCTION cbxlStart( sobid ) returnValue = 1 TRY exl.visible returnValue = 0 CATCH TRY xlopen () returnValue = 0 CATCH END try END try SOB (sobXLstart , "SET" , "Menu.grey" , !returnValue ) SOB (sobXLstop , "SET" , "Menu.grey" , returnValue ) SOB (sobxlWrite , "SET" , "Menu.grey" , returnValue ) SOB (sobxlWriteLv , "SET" , "Menu.grey" , returnValue ) END Function FUNCTION cbxlStop( sobid ) TRY exl.visible xlclose () exl.quit set exl = nothing CATCH END try returnValue = 0 SOB (sobXLstart , "SET" , "Menu.grey" , !returnValue ) SOB (sobXLstop , "SET" , "Menu.grey" , returnValue ) SOB (sobxlWrite , "SET" , "Menu.grey" , returnValue ) SOB (sobxlWriteLv , "SET" , "Menu.grey" , returnValue ) END Function FUNCTION cbxlwriteAll( sobid ) TRY exl.visible qrWriteToXL ("select * from masterTbl") xlTableFormat() CATCH END try END Function FUNCTION cbxlwriteListview( sobid ) TRY exl.visible qrWriteToXL ("listview") xlTableFormat() CATCH END try END Function IF ( xlAllowed ) then sobXLstart = sob ( myMenuXL , "add" , "menu" , "Vertical", "Start" ) sob (-1 , "ON" , "CLICK" , "cbxlStart" ) SOB (-1 , "SET" , "Menu.grey" , 0 ) sobxlWrite = sob ( myMenuXL , "add" , "menu" , "Vertical", "Send files to Excel" ) sob (-1 , "ON" , "CLICK" , "cbxlwriteAll" ) SOB (-1 , "SET" , "Menu.grey" , 1 ) sobxlWriteLV = sob ( myMenuXL , "add" , "menu" , "Vertical", "Send listview to Excel" ) sob (-1 , "ON" , "CLICK" , "cbxlwriteListview" ) SOB (-1 , "SET" , "Menu.grey" , 1 ) sobXLstop = sob ( myMenuXL , "add" , "menu" , "Vertical", "Stop" ) sob (-1 , "ON" , "CLICK" , "cbxlStop" ) SOB (-1 , "SET" , "Menu.grey" , 1 ) END if ' ----------------------------------------------------- dim sobFontSize = sob ( myMenuFont , "add" , "menu" , "Vertical", " " ) sob ( -1 , "SET", "menu.GREY", 1 ) FUNCTION setFontSize ( useValue ) sob( sobFontSize , "SET", "TITLE", "Font size = " & useValue ) SOB (decodeInfo , "SET" , "ROW" , "TOP" ,0 ) END function FUNCTION cbFontBigger( sobid ) IF ( fontSize < 48) then fontSize = fontSize + 2 END if setFontSize ( fontSize) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "bigger" ) sob (-1 , "ON" , "CLICK" , "cbFontBigger" ) FUNCTION cbFontDefault( sobid ) fontSize = APP$ARG ( asc("F") ) setFontSize ( fontSize) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", " default size" ) sob (-1 , "ON" , "CLICK" , "cbFontDefault" ) sob (-1 , "TRIGGER") FUNCTION cbFontSmaller( sobid ) IF ( fontSize > 3 ) then fontSize = fontSize - 2 END if setFontSize ( fontSize) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "smaller" ) sob (-1 , "ON" , "CLICK" , "cbFontSmaller" ) ' ----------------------------------------------------- SOB ( myMenuFont , "ADD" , "MENU" , "SPACER" ) ' ----------------------------------------------------- dim sobFontBold = sob ( myMenuFont , "add" , "menu" , "Vertical", " " ) sob ( -1 , "SET", "menu.GREY", 1 ) FUNCTION setFontBold ( useValue ) sob( sobFontBold , "SET", "TITLE", "Font bold = " & useValue ) SOB( decodeInfo , "SET" , "ROW" , "TOP" ,0 ) END function FUNCTION cbBoldDarkest( sobid ) fontBold = fontBold + 1000 setFontBold (fontBold) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "darkest" ) sob (-1 , "ON" , "CLICK" , "cbBoldDarkest" ) FUNCTION cbBoldDarker( sobid ) fontBold = fontBold + 100 setFontBold (fontBold) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "darker" ) sob (-1 , "ON" , "CLICK" , "cbBoldDarker" ) FUNCTION cbBoldDefault( sobid ) fontBold = 400 setFontBold (fontBold) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", " default bold" ) sob (-1 , "ON" , "CLICK" , "cbBoldDefault" ) FUNCTION cbBoldlighter( sobid ) fontBold = fontBold - 100 setFontBold (fontBold) END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "lighter" ) sob (-1 , "ON" , "CLICK" , "cbBoldlighter" ) ' ----------------------------------------------------- SOB ( myMenuFont , "ADD" , "MENU" , "SPACER" ) ' ----------------------------------------------------- FUNCTION cbNewInstance( sobid ) dim tmpNIDir = dir() dir = app$path NewInstance ( "\ F" & fontSize & " B" & FontBold ) dir= tmpNIDir END Function sob ( myMenuFont , "add" , "menu" , "Vertical", "New instance with this FONT SIZE/BOLD" ) sob (-1 , "ON" , "CLICK" , "cbNewInstance" ) ' ----------------------------------------------------- FUNCTION ClickFunctionalDescription ( sobid ) sob( decodeInfo , "empty") dim txt = "Find visually identical/alike image files." & crChar & appVersion & crChar txt = txt & crChar txt = txt & crChar & "You specify the folder(s)/file(s) that you want to be analysed for visual" & crChar txt = txt & "alikeness by dragging and dropping them on the green bar above." & crChar txt = txt & crChar & "1) They are placed in a temporary table inputTbl." & crChar txt = txt & crChar & "2) A perceptual hash is generated for the image files ( jpg, jpeg, bmp, gif)" & crChar txt = txt & " This can take time (~ about 400 image files per minute) " & crChar txt = txt & " IF you cancel this operation you can safely re-drag the folder(s)/file(s) and" & crChar txt = txt & " the process will carry on from where it was when cancelled." & crChar txt = txt & crChar & "3) The inputTbl (with pHash values) is copied into the master table masterTbl. " & crChar txt = txt & crChar & "4) All image files with a pHash value in masterTbl are then entered in to a Burkhard Keller tree. " & crChar txt = txt & " The BK-tree is used to identify, for each pHashed file, its nearest neighbour provided that the " & crChar txt = txt & " neighbour is within a user specified distance (the visual delta value)." & crChar txt = txt & " My experience suggests that a visual delta of 12 is a reasonable value." & crChar txt = txt & " Two images with a delta < 11 are likely to be alike." & crChar txt = txt & " Two images with a delta > 12 are unlikely to be alike." & crChar txt = txt & " Two images with a delta of 11 or 12 are borderline." & crChar txt = txt & crChar & "5) The masterTbl is then queried to find all cases where 2 images have a delta of 0, " & crChar txt = txt & " in which case the smaller file is automatically MARKED as Equal." & crChar txt = txt & crChar & "It is then up to you!" & crChar txt = txt & crChar & "You can change the MARK associated with a pHashed file." & crChar txt = txt & " 0 = no mark." & crChar txt = txt & " 1 = Keep mark = a green tick sign on the image" & crChar txt = txt & " 2 = Equal mark = a green equals sign on the image" & crChar txt = txt & " 3 = Remove mark = a red cross on the image" & crChar txt = txt & crChar & "You can trigger a copy/move/remove action on a set of marked images." & crChar sob (decodeInfo,"add", "row" , txt ) SOB (decodeInfo,"SET" , "ROW" , "TOP" ,0 ) END Function sob ( myMenuHelp , "add" , "menu" , "Vertical", "What it does" ) sob ( -1 , "ON" , "CLICK" , "ClickFunctionalDescription" ) ' ----------------------------------------------------- SOB ( myMenuHelp , "ADD" , "MENU" , "SPACER" ) ' ----------------------------------------------------- FUNCTION ClickAbout ( sobid ) sob( decodeInfo , "empty") dim txt = "Find visually identical/alike image files." & crChar & appVersion & crChar txt = txt & crChar & "Drag and drop Drop folders and/or files on this bar on the green bar above." & crChar txt = txt & crChar txt = txt & crChar & "A -perceptual- hash of the image files (jpg, jpeg, bmp, gif) will be calculated. " & crChar txt = txt & "You can then specify the degree to which two images can be different but still be" & crChar txt = txt & "considered to be visually -alike-. " & crChar txt = txt & crChar txt = txt & crChar & "You can then select an image file (double right mouse on the listview) and display it and its -alike- images." & crChar txt = txt & crChar txt = txt & crChar & "This program was written in the pwScripter script language." & crChar txt = txt & crChar & "For more about pwScripter please visit https://www.ripetech.com" txt = txt & crChar & "( menu: help / Browse website )" & crChar sob (decodeInfo,"add", "row" , txt ) SOB (decodeInfo,"SET" , "ROW" , "TOP" ,0 ) END Function dim sobAbout = sob ( myMenuHelp , "add" , "menu" , "Vertical", "About" ) sob ( -1 , "ON" , "CLICK" , "ClickAbout" ) ' ----------------------------------------------------- ' Define a callback to handle a click on the Help.Contact menu option (not yet defined) FUNCTION ClickContact ( sobid ) sob( decodeInfo , "empty") dim txt = "E-Mail: george.salisbury@ripetech.com" txt = txt & crChar & "Double right click on the info panel to return to last decode, " & crChar sob (decodeInfo,"add", "row" , txt ) END Function sob ( myMenuHelp , "add" , "menu" , "Vertical" , "Contact" ) sob ( -1 ,"ON","CLICK", "ClickContact") ' ----------------------------------------------------- SOB ( myMenuHelp , "ADD" , "MENU" , "SPACER" ) ' ----------------------------------------------------- ' Define a callback to handle a click on the Help.Contact menu option (not yet defined) FUNCTION ClickOnline ( sobid ) ShellExecute ( "https://www.ripetech.com" ) END Function sob ( myMenuHelp , "add" , "menu" , "Vertical" , "Browse website" ) sob ( -1 ,"ON","CLICK", "ClickOnline") FUNCTION ClickDnLoad ( sobid ) ShellExecute ( "https://ripetech.com/downloads-and-script-files" ) END Function sob ( myMenuHelp , "add" , "menu" , "Vertical" , "Browse download website" ) sob ( -1 ,"ON","CLICK", "ClickDnLoad") ' ----------------------------------------------------- ' Define a callback to handle a click on the Help.Contact menu option (not yet defined) FUNCTION ClickShowpwScripter ( sobid ) SOB (sobid , "SET" , "CHECK" , ! SOB (sobid , "GET" , "CHECK" ) ) SOB (pwSwindow , "SET" , "show" , SOB (sobid , "GET" , "CHECK" ) ) END Function function EnablepwScripter() SOB ( myMenuHelp , "ADD" , "MENU" , "SPACER" ) sob ( myMenuHelp , "add" , "menu" , "Vertical" , "Show pwScripter" ) sob ( -1 ,"ON","CLICK", "ClickShowpwScripter") sob ( -1 ,"SET" , "CHECK" , 1) sob ( -1 ,"Trigger") end function EnablepwScripter() ' ----------------------------------------------------- ' Create a Container as a ROW of objects that have the same WIDTH dim appCOL = sob ( myWindow, "ADD", "CONTAINER", "COLUMN.w") SOB ( "OVERRIDE" , "STYLE+" , 0x00002000) dim btnFolder = sob ( appCOL , "add" , "button" , "push" , chr(0xa) ) sob ( -1 , "SET", "TITLE" , "Drop folders and/or files on this bar." ) sob (btnFolder,"ON","DROP.FILE", "cbBtn_Folder") sob (btnFolder,"SET", "RGB" , greenBtnRGB ) ' ----------------------------------------------------- ' DATABASE part of the APP ' ----------------------------------------------------- dbExtension ("bk64tree",1) dbExtension ("popcount",1) dbExtension ("eval",1) dbopen ( "memory") FUNCTION processFile ( fname ) report(fname) sql ( replace ( " select hashfile(§f , 'phash' ) " , "§f", sqlString( fname) ) ) report ( hex ( qrSingleValue )) LastProcessedFileName = fname END function ' ---------------- dim top2Bottom = sob( appCOL , "add", "container", "panel.h" , " ", 20 ) dim topP = sob( top2Bottom , "GET", "panel.F") dim BottomP = sob( top2Bottom , "GET", "panel.S") ' ---------------- decodeInfo = sob(topP,"add","Edit.rows" , "define the minimum width", 10 ) RedirectReportingTo ( decodeInfo ) sob (-1 , "SET" , "RW" , 0 ) sob (-1,"empty") sob (-1,"add", "row" , crChar ) sob (-1,"add", "row" , crChar ) sob (-1,"SET", "RGB" , readOnlyRGB) ' change the FONT of the decodeInfo panel to a monospaced font! font("default") font("SET" ,"HEIGHT" , APP$ARG ( asc("F") ) ) font("SET" ,"WEIGHT" , APP$ARG ( asc("B") ) ) font("SET" ,"FaceName" , "Courier New" ) dim myFont = Font("CREATE") sob ( decodeInfo, "SET", "FONT" , myFont ) setFontBold ( fontBold ) ' use a Double right mouse click as a trigger to decode the current file again FUNCTION cbdecodeInfo_mouseclicks ( sobid, clickValue, row , col ) IF ! (clickValue == 6) then EXIT function END if IF (LastProcessedFileName == "") then EXIT function END if sob (decodeInfo,"empty") processFile ( LastProcessedFileName ) END function sob ( decodeInfo,"ON","MOUSECLICKS", "cbdecodeInfo_mouseclicks") ' ---------------- dim dbLV dim Alikeness = 12 FUNCTION reDoDistance ( ) sob ( sobAlikeness, "SET", "TITLE" , cstr(Alikeness)) IF ( ! dbsilentquery ("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='masterTbl' ") ) then EXIT function END if TRY IF ! ( dbSilentQuery( "select count (*) from masterTbl ") ) then EXIT function END if report() CATCH EXIT function END try dim rid ProgressText("Calculate the distances between pHash values", 1 ) progressTick( -1 * dbSilentQuery( "select count (*) from masterTbl where not phash is null") ) progressShow (1) update masterTbl set neighbour = 0 > update masterTbl set neighbour = >> eval (' select substr ( "0000" || min (distance), -4 ) >> , link from xHashTbl ( ' || pHash || ', freestring = freestring & Alikeness >> ) where not link = ' || self || ' ') >> where not pHash is null sql(freestring) update masterTbl set distance = substr( neighbour, 1,4) * 1 update masterTbl set neighbour= substr( neighbour, 5) * 1 report( "Number of files with a neighbour within a distance of " & Alikeness & " is := " & dbSilentQuery( "select count (*) from masterTbl where neighbour > 0") ) progressShow (0) sob( sobCurrentShow , "trigger") END function ' ----------------------------------------------------- ' Use the Shell object to get details from the target file ' ----------------------------------------------------- FUNCTION getProperty ( oFolder, oFile, index ) string dim i , tmpStr returnValue = "" dim res = oFolder.GetDetailsOf ( oFile, index) IF ( res <> "" ) then res = replace ( res , chr(0x09), "" ) ' often has a leading character > 0x7f FOR i = 0 to length (res) -1 tmpstr = mid(res, i ,1 ) IF ( asc ( tmpstr ) < 0x080 ) then returnValue = returnValue & tmpstr END IF NEXT i IF ( index <> 1 ) then returnValue = trim( left(returnValue, instr(returnValue, " " ) ) ) END IF END if END function ' ---------------- FUNCTION getFilePropertiesWH ( FilePath , FileName ) dim objShell = CreateObject ("Shell.Application") dim objFolder = objShell.Namespace(FilePath) dim objFile = objFolder.parsename (FileName ) returnValue = getProperty ( objFolder, objFile, 176 ) ' width returnValue = returnValue & "*" & getProperty ( objFolder, objFile, 178 ) ' height returnValue = returnValue & "/" & getProperty ( objFolder, objFile, 1 ) ' size returnValue = returnValue & "/" & getProperty ( objFolder, objFile, 175 ) &" dpi" ' size END function ' ----------------------------------------------------- FUNCTION hashTheInputTbl ( ) dim totalWaiting = dbSilentQuery ( "select count(*) from inputTbl ") dim TotalTime dim StartTime = HiResTime dim handledCount = 0 dbSilentQuery( "update inputTbl set self = rowid " ) constant chunkSize = 100 update inputTbl set self = rowid TRY DO drop table if exists chunkTbl dbSilentQuery ( "create table chunkTbl as select * from inputTbl limit " & chunkSize ) dbSilentQuery ( "delete from inputTbl where self in ( select self from chunkTbl )") dbSilentQuery ( "update chunkTbl set pHash = hashFile (path||name , 'phash' ) where EXT in ( 'jpg' , 'jpeg' , 'bmp' , 'gif' ) ") dbSilentQuery ( "Update chunkTbl set marked = " & markNone ) insert into masterTbl select * from chunkTbl handledCount = handledCount + chunkSize TotalTime = HiResDelta totalWaiting = totalWaiting - chunkSize ProgressText ( "Time to go: " & cint((TotalTime * totalWaiting )/ handledCount / hiresTick ) & " secs" , 1) LOOP while dbSilentQuery ( "select count (*) from inputTbl" ) END try drop table if exists chunkTbl drop table if exists inputTbl dbSilentQuery( "update masterTbl set self = rowid " ) END function FUNCTION findSmallerDeltaZeros () dim selfR, NeighbourR, sizeR WITHQUERY ( "Select self, neighbour, size from masterTbl where not neighbour = 0 and ( marked= " & markNone & " or marked= " & markKeep & ") and distance = 0 " ) selfR = WQInt (1) NeighbourR = WQInt (2) sizeR = WQInt (3) WITHQUERY ( " Select size from masterTbl where marked = " & markNone & " and self = " & NeighbourR ) IF ( wqInt(1) > sizeR ) then ELSE dbSilentQuery ( " update masterTbl set marked = " & markEqual & " where self = " & NeighbourR ) END if END WITHQUERY END WITHQUERY END function FUNCTION processInput ( ) ProgressClear( ) ProgressText(appDescription, 0 ) progressShow (1) delete from inputTbl where not type like 'file' alter table inputTbl add column pHash alter table inputTbl add column self default 0 alter table inputTbl add column Neighbour default 0 alter table inputTbl add column distance default 0 alter table inputTbl add column marked default 0 create table if not exists masterTbl as select * from inputTbl limit 0 ' just in case we accidentally copy in a file or folder twice delete from inputTbl where path||name in ( select path||name from masterTbl ) ProgressText("Create the pHash values", 1 ) progressTick( -1 * dbSilentQuery( "select count (*) from inputTbl where EXT in ( 'jpg' , 'jpeg' , 'bmp' , 'gif' ) ") ) hashTheInputTbl ( ) ' ----------------------------------------------------- ' the xHashTbl table only has 2 columns ' the pHash of a specific file ' and some value that identifies that file such as its ROWID in inputTbl update masterTbl set self = rowid dim rid drop table if exists xHashTbl create virtual table temp.xHashTbl using bk64tree insert into xHashTbl (metric , link) select phash , rowid from masterTbl where type = 'file' and not phash is NULL reDoDistance () findSmallerDeltaZeros () END Function FUNCTION readInfrom ( inputSpec ) dbfileList ( , , inputSpec , -1 ) create table if not exists inputTbl as select * from myTable limit 0 insert into inputTbl select * from myTable drop table if exists myTable END function FUNCTION cbBtn_Folder ( sobid ) dim dropCnt = GetDropCount dim i drop table if exists inputTbl FOR i = 1 to dropCnt SELECT case isFile ( getDropData(i) ) CASE 0 report () report ( getDropData(i) ) EXIT function CASE ELSE ' file readInfrom ( getDropData(i) ) END select NEXT i processInput () END Function ' ----------------------------------------------------- ' create an empty PopUp dim popUpSelf = 0 dim imageSob dim lpHash = 0 dim rpHash = 0 ' ---------------- FUNCTION cbSinglePopDefaultFileHandlingA ( sobid ) shellExecute ( dbSilentQuery ( "select Path||name from masterTbl where self = " & popUpSelf )) END function sob (myPopUp , "add", "menu","Vertical", "Default File handling") sob ( -1 ,"ON","press", "cbSinglePopDefaultFileHandlingA") ' ---------------- FUNCTION cbSinglePopSelfAndNeighbours ( sobid ) displayRootAndNearests ( popUpSelf ) END function sob (myPopUp , "add", "menu","Vertical", "Self and neighbours") sob ( -1 ,"ON","press", "cbSinglePopSelfAndNeighbours") FUNCTION cbOpenPopup ( sobid ) ' get the position of the mouse and use that for the position of the Popup menu sob(myPopup,"SET","POSITION", sob ( myWindow , "get" , "cursor.x" ), sob ( myWindow , "get" , "cursor.y" ) ) ' show the popup menu sob(myPopUp,"set","show",1) END Function ' ----------------------------------------------------- dim lowerCol = sob ( BottomP, "ADD", "CONTAINER", "COLUMN.w") dim sobAlikeness FUNCTION MoreAlike ( sobid ) IF ( Alikeness > 2 ) then Alikeness = Alikeness- 1 reDoDistance () END if END FUNCTION FUNCTION LessAlike ( sobid ) IF ( Alikeness < 63 ) then Alikeness = Alikeness+ 1 reDoDistance () END if END FUNCTION FUNCTION SameAlike ( sobid ) reDoDistance () END FUNCTION dim lvBtnRow = sob ( lowerCol, "ADD", "CONTAINER", "ROW") sob ( lvBtnRow , "add" , "label" , "visual delta:" ) sob ( -1 , "SET", "RGB", 0x0ffffff) sob ( lvBtnRow , "add" , "button" , "push" , "<>" ) sob ( -1 ,"ON","CLICK", "LessAlike") sob ( lvBtnRow , "add" , "label" , " " ) sob ( -1 , "SET", "RGB", 0x0ffffff) sob ( lvBtnRow , "add" , "label" , "display:" ) sob ( -1 , "SET", "RGB", 0x0ffffff) dim sobAll = sob ( lvBtnRow , "add" , "button" , "radio" , "All" ) sob ( -1 ,"ON","CLICK", "cbShowFiles") dim sobAlikeRadio = sob ( lvBtnRow , "add" , "button" , "radio" , "Alike" ) sob ( -1 ,"ON","CLICK", "cbShowAlikes") sob ( lvBtnRow , "add" , "button" , "radio" , "Keep" ) sob ( -1 ,"ON","CLICK", "cbShowKeep") sob ( lvBtnRow , "add" , "button" , "radio" , "Remove" ) sob ( -1 ,"ON","CLICK", "cbShowRemove") sob ( lvBtnRow , "add" , "button" , "radio" , "Equal" ) sob ( -1 ,"ON","CLICK", "cbShowEqual") sob ( lvBtnRow , "add" , "button" , "radio" , "Marked" ) sob ( -1 ,"ON","CLICK", "cbShowMarked") dim sobCount = sob ( lvBtnRow , "add" , "label" , " " ) SOB ( lvBtnRow, "ADD" , "SPACE" , 1,1 ) sob ( sobAll , "TRIGGER") ' ---------------- FUNCTION cbShowAgain ( sobid ) sob ( sobCurrentShow , "TRIGGER") END function FUNCTION cbCheckFilter ( sobId ) sob ( sobid , "SET", "check" , ! sob (sobid, "GET", "Check") ) END function dim lvBtnRowA = sob ( lowerCol, "ADD", "CONTAINER", "ROW") dim sobCheckFilter = sob ( lvBtnRowA , "add" , "button" , "check" , " " ) sob ( -1 ,"ON","CLICK", "cbCheckFilter") sob ( lvBtnRowA , "add" , "button" , "push" , "Filter =" ) sob ( -1 ,"ON","CLICK", "cbShowAgain") dim SobWhereClause = sob ( lvBtnRowA , "add" , "EDIT" , " " ) sob ( SobWhereClause, "EMPTY") dbLV = SOB ( lowerCol , "ADD" , "LISTVIEW" , "define the minimum width" , 12 ) RedirectDBlv ( dbLV ) ' ---------------- SOB (pwSwindow , "SET" , "show" , 0 ) sob ( sobAbout, "trigger") ' ----------------------------------------------------- FUNCTION doMultSelectionAction ( markType ) dim selCnt = SOB ( dbLV , "GET" , "SELECTION", "COUNT" ) dim i , self, realRow FOR i = 1 to selCnt realRow = sob ( dbLV, "GET", "SELECTION" , "ROW" , i ) self = sob( dbLV, "GET", "CELL", realRow, 1 ) dbSilentQuery("update masterTbl set marked = " & markType & " where self = " & self & " and not pHash is NULL") NEXT i END FUNCTION ' ---------------- FUNCTION cbMultiPopRemove ( sobid ) doMultSelectionAction ( markRemove ) END FUNCTION ' ---------------- FUNCTION cbMultiPopKeep ( sobid ) doMultSelectionAction ( markKeep ) END FUNCTION ' ---------------- FUNCTION cbMultiPopEqual ( sobid ) doMultSelectionAction ( markEqual ) END FUNCTION ' ---------------- FUNCTION cbMultiPopNone ( sobid ) doMultSelectionAction ( markNone ) END FUNCTION ' ---------------- sob (MultiSelectionPopup , "add", "menu","Vertical", "Toggle REMOVE Mark") sob ( -1 ,"ON","press", "cbMultiPopRemove") sob (MultiSelectionPopup , "add", "menu","Vertical", "Toggle KEEP Mark") sob ( -1 ,"ON","press", "cbMultiPopKeep") sob (MultiSelectionPopup , "add", "menu","Vertical", "Toggle Equal Mark") sob ( -1 ,"ON","press", "cbMultiPopEqual") sob (MultiSelectionPopup , "add", "menu","Vertical", "Remove mark") sob ( -1 ,"ON","press", "cbMultiPopNone") ' ---------------- FUNCTION openMultiPopup ( sobid ) sob(MultiSelectionPopup,"SET","POSITION", sob ( ImageFloatWindow , "get" , "cursor.x" ), sob ( ImageFloatWindow , "get" , "cursor.y" ) ) sob(MultiSelectionPopup,"set","show",1) END Function ' ----------------------------------------------------- FUNCTION cbdblvMOUSECLICKS ( sobid, clickId , row, col ) dim inPath = sob( sobid, "get" , "cell", row, 2 ) dim inName = sob( sobid, "get" , "cell", row, 3 ) popUpSelf = cInt(sob( sobid, "get" , "cell", row, 1 )) dim fName = inPath & inName dim phash , tmpImage IF ( ! dbSilentQuery ( " Select count(*) from masterTbl where not phash is null and self = " & popUpSelf ) ) THEN EXIT function END if SELECT CASE clickId CASE 0 ' dbl left cbOpenPopup(sobid) CASE 6 ' dbl right IF ( SOB ( dbLV , "GET" , "SELECTION", "COUNT" ) > 1 ) then openMultiPopup ( sobid) ELSE displayRootAndNearests(sobid) SOB ( sobid, "SET" , "SELECTION" , "ROW" , row ) END if CASE 7 ' single right END SELECT END function sob ( dbLV , "ON" , "MOUSECLICKS", "cbdblvMOUSECLICKS") sob(myWindow,"set","shrink",1) dir = app$path