Lo facevo in modo simile al modo in cui mi stai proponendo dove ho calcolato un rettangolo di delimitazione per il tratto, capito quali pixel sono stati modificati, memorizzando i loro valori prima della modifica e registrando una voce di annullamento che avrebbe scambiato il vecchio valori di pixel con i nuovi valori di pixel su annulla / ripeti. E per i grandi colpi l'ho persino ottimizzato in modo che invece di una sub-immagine del rettangolo gigante per il tratto, lo spezzasse in regioni rettangolari più piccole.
Questo è un bel po 'di lavoro per una sola operazione legata all'immagine! È arrivato al punto in cui stavo spendendo più o meno tempo implementando la logica annullabile di ogni operazione come l'operazione stessa e trascorrendo decisamente molto più tempo a eseguire il debug della logica di annullamento errata rispetto alla logica dell'operazione stessa.
Così mi è venuto in mente un approccio molto più semplice che mi permettesse di applicare lo stesso approccio per annullare le operazioni di immagine e renderle non distruttive: un approccio per dominarle tutte. E questo si riduce al solo pensare a un'immagine come una raccolta di riquadri immagine, diciamo 64x64 pixel ciascuno:
Ora,primachel'utenteiniziadipingere,puoisemplicementecopiarel'interadannatacosainunavocediannullamento,adeccezionedellacopiasolodipuntatoricontaticonriferimentoaciascunaporzionedell'immagine.Quindi,mentrel'utenteiniziaadipingeresull'immagine,sirendonoletesseredell'immaginechesonocambiateunivocheconunapprocciosimileaquellodellestrutturedidatipersistentiimmutabiliosipuòusarelacopiaperscritturapertessera(qualunquetipodiinterfacciaeschemadiutilizzosiadattatu,ilpuntoèevitaredicopiarel'immaginenellasuainterezzaquandovengonomodificatesoloalcuneparti):
Orapuoisemplicementeassegnareletesseredell'immaginechesonostatetoccatedall'operazione(letesserescuresopralequalisonostatetoccatedall'utente).Puoicopiaresuperficialmenteletesserenontrattate.
Conquestoapproccio,indipendentementedall'applicazionedell'immaginechevieneapplicataindipendentementedaldisegnodiformeetrattiodaunfiltroimmagineapplicatoaunaselezioneomascheradiimmagine,lalogicadiannullamentosiriduceaquesta:
beforeuseroperation:copyimagetoundoentryonundo/redo:swapuserimagewithundoimage
Questoètutto.Potrebbenonessereteoricamenteottimale,mailragazzosemplificalecoseperquantoriguardal'editingnondistruttivoel'annullamento.Ilpuntoèottimizzarelacopiainmodotalecheognipiastrellachenonvienetoccatadall'utentevengacopiatainmodosuperficialeinmododapotercopiareleimmaginiadestraesinistrasenzapreoccuparsidifaresploderel'usodellamemoriaesprecaretempoeccessivocopiandotuttoancoraeancora.
Oraapplicoquestoapprocciogeneraleall'annullamentoeall'editingnondistruttivopertutto.Bastacopiarelacosainanticipoequindiapplicarel'operazionedell'utente.Ciòspostaladifficoltàdallacorrettezzaall'ottimizzazione.Ilprossimopassoèquellodiottimizzarerendendolacopiaabuonmercatoperlepartichenonsonostatemodificate.
Unmetododiannullamentoperregolarlitutti
Inquestigiorniinrealtàfavoriscoinmodouniformequestometodopertuttiitipidiapplicazionidisparatedisistemidiannullamento.Ilmodomiglioreperfarlofunzionarecorrettamenteècopiaretuttiidatirilevantinellostackdiannullamento,aquelpuntol'implementazionediventacosì:
Beforeuseroperation:old_state=current_stateAfteruseroperation:ifold_state!=current_state:clearredosstackpusholdstatetoundostackOnundo:pushcurrentstatetoredostacksetcurrentstatetotopofundostackpopfromundostackOnredo:pushcurrentstatetoundostacksetcurrentstatetotopofredostackpopfromredostack
Ciòspostatuttelepreoccupazionisull'ottimizzazione(lacopiaeilconfrontodell'equivalenza)piuttostochesullacorrettezza.Esonoriuscitoaottimizzarloasufficienzaancheperidatichenormalmentevannodagigabyteusandometodicomedescrittonell'approccioprecedenteoinalternativastrutturecompattechecatturanocomei"delta" (differenze tra due strutture) fino al punto in cui il sistema di annullamento in realtà richiede anche meno memoria di quando stavo usando lo schema di comando.
E l'ottimizzazione di pesanti strutture di copia (senza la copia profonda di tutto) e la possibilità di confrontarle rapidamente per l'equivalenza ha spesso degli usi più pratici al di fuori del sistema di annullamento per altre cose come la sicurezza delle eccezioni, la sicurezza del thread, non- editing distruttivo, ecc., dal momento che se riesci a copiare le cose in modo economico, allora tendi ad avere meno motivi per causare effetti collaterali nelle tue funzioni in generale.