Między styczniem 2022 a marcem 2026 do Ansela trafiło 297 commitów (nie licząc merge’y) dotyczących siatki stołu podświetlanego, jej miniatur oraz potoku (pipeline) ich renderowania i buforowania. Tak długo, jak się dało, próbowałem radzić sobie z niechlujnym kodem stołu podświetlanego z Darktable, jedynie odtłuszczonym, ale niestety był to czysty dług techniczny i działał boleśnie wolno.

Rzeczywiście, Darktable „radzi sobie" z kiepską jakością swojego stołu podświetlanego, zmniejszając jego rozmiar: lewy i prawy panel boczny zajmują dużą część powierzchni wyświetlania, przez co stołowi podświetlanemu zostaje jeszcze mniej obszaru do przemalowania. Ponieważ Ansel usunął prawy panel boczny, scalając jego zawartość z lewym panelem i menu globalnym, powierzchni do malowania było więcej, pracy dla procesora więcej, a fatalny projekt stołu podświetlanego stał się tym bardziej szkodliwy.

Ten projekt jest już ukończony. Ten artykuł to mapa tego, co się zmieniło, co zostało usunięte, co przeprojektowano, jak działa nowe zachowanie i co użytkownicy faktycznie na tym zyskują.

Krótka historia złego projektu

Aby zrozumieć, dlaczego przepisanie z lat 2022-2026 ostatecznie dotknęło tak wielu plików, warto cofnąć się o krok dalej.

Widok stołu podświetlanego, src/views/lighttable.c, gromadził obowiązki od ponad dekady. Zaczynał jako miejsce obsługujące siatkę menedżera plików i podstawowe powiększanie, po czym stopniowo wchłonął pełny podgląd, przypięty podgląd, selekcję, pamięć stanu paneli, grupowanie, zmianę kolejności metodą przeciągnij-i-upuść, nawigację klawiaturą, zasady wyświetlania nakładek, koordynację taśmy filmowej i mnóstwo routingu skrótów. Jeśli spojrzysz na historię sprzed 2022 roku, wiele commitów dotyczy po prostu powstrzymywania tych elementów przed wzajemną walką: skoki przesunięcia przy powiększaniu, błędy przewijania podglądu, brak synchronizacji zaznaczenia, selekcja i podgląd wchodzące ponownie w na wpół uszkodzonym stanie, niepoprawnie przywracana widoczność paneli, glitche obramowań grup oraz oceny lub etykiety barwne nieoczekiwanie przesuwające siatkę.

Pokazuje to więc całkowitą nieumiejętność śledzenia cyklu życia zbuforowanego obrazu (czy w ogóle jakichkolwiek danych w tym zapomnianym przez Boga oprogramowaniu), przy jednoczesnym pełnym komforcie i rozluźnieniu wobec tego. Rozwiązałem ten problem, upraszczając asynchroniczność w tle, ale inną opcją byłoby użycie sygnału. Nie mogę pojąć faktu, że 3 goście, którzy pracowali nad tym przez ponad 10 lat, piszą kod w C dwa razy dłużej niż ja. W 2018 roku nie wiedziałem nawet, czym jest GLib.

Jeśli cię to interesuje, oto jak sobie z tym poradziłem:

  • widżety miniatur przechowują zmienną stanu reprezentującą ważność ich własnego obrazu,
  • gdy GTK chce przemalować miniaturę, jeśli zmienna stanu mówi „nieważny", widżet miniatury uruchamia wątek w tle obliczający potok tworzący brakujący obraz,
  • gdy wątek w tle zwraca wynik, umieszcza obraz wyjściowy w miniaturze, która go wywołała, przez wskaźnik, na (zbuforowanej) powierzchni, aktualizuje zmienną stanu na „ważny", a następnie wysyła zdarzenie GTK queue redraw na widżecie (a nie na całej siatce),
  • jeśli widżet jest niewidoczny, nic się nie dzieje (GTK nie kolejkuje zdarzeń przemalowania do niewidocznych widżetów). Następnym razem, gdy GTK zechce przemalować obraz, użyje zbuforowanej powierzchni z wnętrza widżetu,
  • zmiany historii i metadanych wysyłają sygnał „image info changed", który publikuje identyfikator obrazu. Thumbtable ma jeden obsługiwacz sygnału nasłuchujący tego, który odnajduje miniaturę po identyfikatorze w siatce (z tablicy haszującej, więc O(N)) i resetuje jej zmienną stanu na „nieważny". Ponownie, jeśli zmiana dotyczy 100 obrazów i wszystkie są niewidoczne, nic się nie dzieje.

Architektura wydaje się bardziej złożona, ale w rzeczywistości to mniej kodu i jest po prostu solidna: nie ma zgadywania.

Przepisanie z czasów COVID próbowało zrefaktoryzować to do src/dtgtk/thumbtable.c, który wkrótce stał się częścią tego samego dryfu. Gdy pojawiła się dedykowana abstrakcja thumbtable, stała się kontrolerem ruchu między stanem kolekcji, przewijaniem, przesunięciami, aktywnymi obrazami, nawigacją klawiaturą, przeciąganiem-i-upuszczaniem, płynnym przewijaniem, widocznością nakładek, powiększalnym układem, synchronizacją selekcji i synchronizacją taśmy filmowej, wymyślając na nowo wiele natywnych funkcji GTK w gorszy i niekompletny sposób (typowo dla Darktable). Historia sprzed 2022 roku jest pełna commitów naprawiających najechanie myszą po przewinięciu, przeliczających wiersze i przesunięcia, utrzymujących zaznaczone obrazy w widoku po zmianach kolekcji, unikających kwadratowego opóźnienia przy Home/End, naprawiających wyrównanie w trybie powiększalnym, sprawiających, by ukryte lub zwinięte obrazy nie psuły logiki aktywnego obrazu, oraz ograniczających liczbę emitowanych sygnałów, ponieważ zbyt wiele niezależnych aktualizacji było ze sobą łańcuchowanych. Innymi słowy, thumbtable.c już wcześniej stał się miejscem, w którym logika widoku, czas życia miniatur i nawigacja po kolekcji były zmuszone się spotkać, na długo przed rozpoczęciem przepisania w 2022 roku.

Refaktoryzacja thumbtable sprawiła, że pojawił się nowy plik, gdy stary kod stołu podświetlanego i taśmy filmowej zaczęto wyodrębniać do wspólnego widżetu miniatury i thumbtable jako obiektu bazowego tego wszystkiego: src/dtgtk/thumbnail.c. W zasadzie był to właściwy kierunek: zamiast rysować miniatury osobno w kilku miejscach, jeden widżet mógł scentralizować aktywację, zaznaczanie, nakładki, gwiazdki, flagi odrzucenia, przeciąganie-i-upuszczanie, obramowania grup i zachowanie powiększalne. Ale bardzo szybko podążył podobną ścieżką: historia sprzed 2022 roku pokazuje, jak szybko ten wspólny widżet stał się przeciążony. Wkrótce zajmował się rozszerzonymi nakładkami, wywołaniami zwrotnymi aktualizacji MIPMAP, złączami selekcji, paskami przewijania, obsługą aktywnego obrazu, wywołaniami zwrotnymi taśmy filmowej, nawigacją pełnego podglądu i rozmieszczaniem ikon dostrojonym w CSS. Innymi słowy, thumbnail.c nie rysował po prostu miniatury. Stał się wspólną linią uskoku między stołem podświetlanym, taśmą filmową, selekcją i podglądem.

Taśma filmowa zaczynała jako dedykowany pasek nawigacyjny, po czym zyskała wyśrodkowywanie na aktywnym obrazie, przeciąganie-i-upuszczanie, interakcje z mapą, operacje kopiuj/wklej, płynne przewijanie, poprawki HiDPI, dostosowywanie CSS, a w końcu części nowego systemu wywołań zwrotnych miniatur. Ponownie, żadna z tych funkcji sama w sobie nie jest nierozsądna. Problem w tym, że taśma filmowa skończyła z akurat taką ilością niestandardowego zachowania, by rozejść się ze stołem podświetlanym, wciąż próbując ponownie wykorzystać część tej samej maszynerii miniatur.

Na dodatek istniał niespójny paradygmat „zaznaczania" w Darktable, sprzedawany jako „przepływ pracy bez klikania", dzięki któremu wiele operacji zapisu można było obsłużyć bez jawnego zaznaczania obrazu (obrazów), których miały dotyczyć. Prowadziło to do wielu niepożądanych efektów i wypadków, skutkujących utratą danych (przypisanie złej oceny w gwiazdkach do złego obrazu, co sprawiało, że znikał on z bieżącej kolekcji, jeśli filtrowało się ją według oceny) oraz żmudnych sesji cofania. Ale cała heurystyka zaznaczania obrazów też była krucha: przesunięcie myszy w oknie mogło ukraść fokus obrazowi, który jawnie zablokowałeś kliknięciem lub zaznaczeniem z klawiatury, ale nie zawsze.

Nie wspominając o tym, że owo „działanie przy najechaniu myszą" wyzwalało kilka zapytań SQL do bazy danych biblioteki przy najechaniu na nowy obraz, aby pobrać zaktualizowane metadane obrazu i odświeżyć zawartość modułów metadanych (metadane, metadane EXIF i IPTC, tagi). Wszystko to dlatego, że nie dało się ich zbuforować, a nie dało się ich zbuforować, ponieważ Darktable nie potrafi śledzić cyklu życia swoich danych, więc musi cały czas wszystko odświeżać. I przy okazji wyczerpie ci baterię.

Kiedy więc rozpoczęło się przepisywanie po odgałęzieniu Ansela w 2022 roku, problemem nie było to, że jeden zły commit zepsuł stół podświetlany. Problem polegał na tym, że trzy warstwy historii nakładały się na siebie:

  • lighttable.c wciąż dźwigający bagaż kilku (bezużytecznych) trybów przeglądania, zakodowanych tam jako refleksje po fakcie,
  • thumbtable.c skupiający logikę przewijania, przesunięcia, aktywnego obrazu i nawigacji po kolekcji, która częściowo pokrywała się z backendem kolekcji i zaznaczania,
  • thumbnail.c skupiający coraz więcej współdzielonego zachowania GUI oraz kodu SQL.

Właśnie dlatego późniejsza praca musiała mieć charakter architektoniczny. W tym momencie nie było realnego sposobu, by dalej naprawiać objawy jeden po drugim.

Zmiany front-endu

Selekcja nigdy (koncepcyjnie) nie była naprawdę osobnym widokiem

Jednym z ważnych elementów tego odejmowania był tryb selekcji . W lutym 2023 dedykowane widoki selekcji i podglądu zostały usunięte, ponieważ stały się mocno przeplecione ze skrótami, przełączaniem widoków i rozgałęzianiem dla przypadków szczególnych. Kod był splątany i uciążliwy, ale prawdziwy problem tkwił w samym projekcie: było to przeinżynierowane rozwiązanie znacznie prostszego problemu, rozwiązane w niewłaściwej warstwie.

Potrzeba polegała na wyizolowaniu dowolnego zestawu zdjęć, niekoniecznie sąsiadujących ze sobą w bieżącej kolekcji, aby zdecydować, które z nich będzie tym zachowanym. Nie wymagało to nowego układu (ani dwóch…); wymagało filtra ograniczającego kolekcję do dowolnego zaznaczenia. Mieliśmy już filtry ograniczające kolekcje według oceny, etykiety barwnej, statusu edytowany/nieedytowany itd.

Ta różnica ma znaczenie. Dedykowany widok selekcji powiela problemy, które stół podświetlany i tak musi rozwiązać: który zestaw obrazów jest aktywny, jak przywracane jest zaznaczenie przy wychodzeniu z widoku selekcji, jak routowane są skróty, jak inicjalizowany jest stan powiększenia i miniatur oraz co się dzieje, gdy wracasz do siatki. Każde ulepszenie trzeba wtedy zaimplementować dwukrotnie, raz w logice stołu podświetlanego i raz w logice selekcji, a obie wersje się rozjeżdżają. Nie wspominając o tym, że tryb selekcji miał tryb statyczny i tryb dynamiczny, które różniły się zarówno sposobem interakcji, jak i implementacją, a bardzo niewielu użytkowników rozumiało, o co w nich chodzi.

W Anselu selekcja powróciła jako filtr kolekcji, najpierw jako prosty sposób na zawężenie bieżącego zestawu, a później wprost jako przycisk filtra Ogranicz do zaznaczenia. Oznacza to, że intencja od strony użytkownika pozostała ta sama (redukcja bieżącej kolekcji do dowolnego zaznaczenia obrazów), ale implementacja zmieniła się całkowicie: zamiast wchodzić w specjalny widok, pozostajesz na stole podświetlanym i mówisz narzędziu filtrującemu, aby pokazało tylko aktualnie zaznaczone obrazy lub tylko obrazy spełniające bieżące kryterium zawężania.

image
Nowy, ujednolicony pasek narzędzi filtrowania. Kryteria filtrowania są włączające, a ikony zachowują się jak przyciski wyboru: odznacz, aby ukryć pasujące obrazy, zaznacz wszystko, aby pokazać wszystko (menu kontekstowe pod prawym przyciskiem daje skrót do zrobienia tego w jednym kroku). Po najechaniu pojawiają się podpowiedzi z dodatkowymi szczegółami. Pierwsza ikona odświeża bieżącą kolekcję zależnie od filtrów. Na przykład, jeśli pokazujesz obrazy z oceną 3 gwiazdek i obniżysz ocenę obrazu do 2 gwiazdek, nie zostanie on automatycznie usunięty z bieżącej kolekcji, dopóki nie odświeżysz jej ręcznie.

Dla użytkowników korzyść jest praktyczna. Selekcję można teraz komponować z resztą logiki filtrowania, zamiast żeby żyła poza nią. Możesz łączyć ją z ocenami, etykietami barwnymi lub wyszukiwaniem tekstowym, ponieważ jest po prostu kolejnym filtrem kolekcji. Obsługa skrótów jest prostsza, bo o ich własność rywalizuje o jeden specjalny widok mniej. A poprawki przewijania, zaznaczania, czasu życia miniatur czy zachowania nakładek automatycznie przynoszą korzyść również selekcji, ponieważ selekcja korzysta teraz z tej samej infrastruktury stołu podświetlanego zamiast z równoległej.

Była to również okazja do gruntownej przebudowy paska narzędzi filtrowania, który mieszał przyciski i logikę: lista rozwijana dla ocen (od odrzucony do 5 gwiazdek), powiązana z listą rozwijaną porównania (≠ = > < >= <=), ale przyciski przełączane dla etykiet barwnych. Darktable 4.0 zastąpił to przeinżynierowanym zestawem konfigurowalnych widżetów używających niestandardowych paradygmatów GUI, które trzeba było objaśniać w podpowiedziach. Ansel spłaszczył ten projekt: wszystko jest przyciskiem przełączanym działającym w trybie „włącz" w obrębie swojej grupy, umożliwiając złożone zaznaczenia bez złożonego GUI:

  1. masz 3 grupy (oceny, etykiety barwne, status edycji), plus ogranicz do zaznaczenia i wyszukiwanie tekstowe,
  2. pomiędzy tymi grupami filtry są wykluczające, czyli tworzą logiczne AND,
  3. wewnątrz tych grup filtry są addytywne, czyli tworzą logiczne OR.

Na przykład na powyższym zrzucie ekranu wszystko jest włączone, więc filtrowanie jest w praktyce wyłączone (wpuszczamy wszystko). W poniższym przykładzie wpuszczamy wszystkie obrazy, które mają ustawioną etykietę barwną i zostały już wyedytowane, niezależnie od ich oceny (wszystkie przyciski ocen są włączone):

image

Zaznaczanie: mieszanie stanów backendu i GUI zawsze kopnie cię w tyłek

Obrazy zgrupowane były kolejną ukrytą linią uskoku. Część starej logiki próbowała uwzględniać grupowanie w zapytaniach SQL, przy generowaniu kolekcji obrazów z bazy danych biblioteki, ale obramowania grup, stany najechania, zachowanie zaznaczania i faktyczna widoczność to pojęcia GUI. Ta niespójność tworzyła subtelne błędy: mogły zostać zaznaczone wszystkie obrazy z grupy albo niektóre pominięte zgodnie z regułami heurystycznymi, które nie odpowiadały temu, co użytkownik widział na ekranie.

Naprawiłem to, przenosząc decyzje o wyświetlaniu grup z powrotem do kodu GUI. SQL dostarcza listę obrazów; interfejs decyduje, którzy członkowie grupy są widoczni, zwinięci lub najechani. Potem funkcje dodane w 2025 roku stały się znacznie prostsze: obramowania grup można było zaimplementować od nowa, uczynić opcjonalnymi, rozszerzyć na taśmę filmową i uczynić bardziej widocznymi przy najechaniu. Podpowiedzi można było wypełniać leniwie, dopiero gdy użytkownik na nie najedzie. Jedno zapytanie SQL na miniaturę w czasie inicjalizacji również mogło zniknąć, ponieważ interfejs miał już wystarczająco dużo lokalnego stanu, by wiedzieć, kiedy informacja o grupie jest faktycznie potrzebna.

Dla użytkowników korzyść nie jest abstrakcyjna. Obramowania grup znaczą teraz to, co pokazują, zaznaczenia dokładnie odpowiadają widocznemu stanowi grupy, a obrazy zgrupowane nie sprawiają już wrażenia, jakby były zarządzane w połowie przez bazę danych, a w połowie przez interfejs.

Kolejnym ukrytym problemem było zaznaczanie. W starym kodzie zaznaczanie było zbyt splątane z wewnętrznymi elementami kolekcji, surowym dostępem SQL i logiką awaryjną. Brzmi to niegroźnie, dopóki obraz nie zostanie usunięty z bieżącej kolekcji, grupa nie zostanie zwinięta albo nie nastąpi przełączenie widoku, podczas gdy inna część kodu wciąż zakłada poprzedni model zaznaczania. W Darktable zaznaczenia obrazów były obsługiwane jako backend SQL, ponieważ są przechowywane w bibliotece, aby przywrócić je przy następnej sesji. To nie jest problem, dopóki backend zaznaczania nie zostanie zhakowany, by zgadywać, które obrazy są widoczne w GUI, a GUI faktycznie obsługuje zaznaczenie przez backend. To najgorszy możliwy podział funkcjonalności między warstwami backendu i GUI.

Dlatego rok 2025 zawiera cały klaster commitów związanych z zaznaczaniem: przepisanie API zaznaczania, usunięcie starego pojęcia main_image, ujednolicenie getterów/setterów aktywnego obrazu, naprawę zaznaczania zakresowego, przywracanie stanu zaznaczenia przy przełączaniu widoków oraz dodanie awaryjnych rozwiązań opartych na rowid, gdy scroll_to_selected nie może już odnaleźć oryginalnego identyfikatora obrazu. Przepisanie zmieniło architekturę: thumbtable oblicza teraz widoczne, sensowne dla użytkownika zaznaczenia i przekazuje listę identyfikatorów obrazów do warstwy zaznaczania, zamiast żeby GUI próbowało po fakcie odtworzyć znaczenie ze stanu kolekcji. API SQL zaznaczania stało się prostym backendem typu „zapisz do / przywróć z bazy danych".

Konkretna korzyść dla użytkownika jest taka, że zaznaczanie zakresowe Shift + kliknięcie oraz „obraz, na którym działać" są tym, co widzisz, jest tym, co dostajesz (WYSIWYG): nie możesz zaznaczyć obrazu, który nie jest widoczny na ekranie, a backend nie musi zgadywać, co jest widoczne. Przewijanie z powrotem do zaznaczonego obrazu zawodzi teraz znacznie rzadziej w przypadkach brzegowych. Ukryty problem nie polegał na jednym zepsutym skrócie; polegał na tym, że semantyka zaznaczania mieszkała w niewłaściwej warstwie.

Rozwiązaniem jest jasne oddzielenie tego, co należy do backendu i zarządzania biblioteką, od tego, co należy do stanów i interakcji GUI, oraz zapewnienie sztywnego interfejsu między nimi. Dzięki temu różne części kodu są lepiej odgrodzone, odporne na zmiany w innych częściach i zarządzane centralnie.

Glitche najechania i fokusu wynikały ze zderzenia kilku poprawnych systemów

Najechanie myszą, najechanie klawiaturą, obsługa fokusu i kliknięć — każde z nich zaimplementowano z uzasadnionych powodów, ale nie miały wspólnego, jednego punktu arbitrażu. Darktable ma teraz nawet dumny slogan marketingowy wypisany w tle stołu podświetlanego, gdy kolekcja jest pusta, wychwalający ów „bezklikowy" przepływ pracy, który pozwala nadpisać metadane obrazu bez chęci, a nawet bez wiedzy o tym. Wygląda mi to jak firma samochodowa reklamująca auto zaprojektowane tak, byś mógł obsługiwać kierownicę kolanami (a rękami pić piwo).

Właśnie dlatego starsze zachowanie stołu podświetlanego mogło sprawiać wrażenie niespójnego bez żadnego pojedynczego oczywistego błędu: było zepsute z założenia. Kliknięcie przycisku nakładki, aby ocenić obraz, mogło również zaznaczyć całą miniaturę, ale ocenianie z klawiatury już nie. Zarówno ruch klawiaturą, jak i ruch myszą mogły uważać, że są właścicielem „bieżącego" obrazu, i wyzwalać wszelkiego rodzaju destrukcyjne zmiany metadanych jedynie przez najechanie na zdjęcie i bez proszenia o potwierdzenie. Filtrowanie obrazów według oceny w gwiazdkach i przypadkowa zmiana oceny obrazu mogły sprawić, że nieoczekiwanie znikał. Ukryte widżety wciąż mogły otrzymywać logikę związaną z najechaniem. GTK-owy fokus mógł zdecydować jedno, podczas gdy warstwa widoku oczekiwała czegoś innego.

Poprawki w tym miejscu dotyczyły mniej dodawania nowych funkcji, a bardziej wyboru jednego właściciela stanu. Rozdzielanie stanu najechania zostało scentralizowane. Przeglądanie z klawiatury zyskało sensowny punkt startowy i jawną ścieżkę anulowania za pomocą Shift+Ctrl+A. Wyświetlanie fokusu uczyniono jawnym. Ukryte widżety przestały uczestniczyć w logice najechania, której nie mogły wyświetlić. Klikanie przycisków nakładek przestało przeciekać do zaznaczania miniatur. Żadne (nad)pisywanie metadanych nie odbywa się już bez jawnego naciśnięcia przycisku, a zdarzenia najechania są w całości tylko do odczytu.

Ansel ma dwie proste zasady:

  • każde działanie, które (nad)pisze (meta)dane, wykonywane jest wyłącznie na jawnie zaznaczonych obrazach,
  • jawne zaznaczenie następuje wyłącznie przez interakcję z czymś „twardym": kliknięciem myszy lub naciśnięciem klawisza.

Zdarzenia najechania są zatem zarezerwowane dla zdarzeń tylko do odczytu.

Korzyść dla użytkowników polega na tym, że stół podświetlany reaguje bardziej jak pojedynczy model interakcji. Ukryty problem nie polegał na tym, że najechanie było zepsute. Polegał na tym, że kilka rodzajów najechania było jednocześnie „poprawnych", a przez to wspólnie błędnych.

Zmiany backendu

Zmiana rodzica thumbtable pomiędzy stołem podświetlanym a taśmą filmową

Jednym z głębiej ukrytych problemów była własność widżetów. W Darktable ta sama siatka thumbtable miała zmienianego rodzica między kontekstami stołu podświetlanego i taśmy filmowej. Na papierze pozwalało to uniknąć duplikacji kodu i mnożenia widżetów, i brzmi to sprytnie. W praktyce sprawiało, że stan przewijania, inicjalizacja miniatur, odśmiecanie pamięci i czas życia zdarzeń zależały od tego, gdzie widżet ostatnio przebywał. Nie wspominając o tym, że zmiana rodzica była wolna, więc przechodzenie tam i z powrotem między stołem podświetlanym a ciemnią było opóźnione o około 1 s — czas potrzebny GTK na przeliczenie nowych rozmiarów miniatur, ewentualne poproszenie pamięci podręcznej o nowe miniatury i przemalowanie widżetów.

Ujawniało się to w bardzo konkretny sposób. Pozycja przewijania mogła skakać lub stawać się niespójna. Miniatury mogły być ukrywane, pokazywane lub niszczone w niewłaściwym momencie. Odśmiecanie pamięci stawało się trudniejsze do ogarnięcia, ponieważ hierarchia widżetów nie była stabilna. Oczywiście wszystko to rozwiązywano commit po commicie, ale pozostawiając kod w stanie nieutrzymywalnej złożoności.

Prawdziwym rozwiązaniem było zaprzestanie prób bycia sprytnym. Stół podświetlany i taśma filmowa mają teraz osobne thumbtable. Oznacza to nieco więcej jawnej struktury w kodzie, ale znacznie mniej przypadkowych stanów. Korzyść dla użytkowników widać w stabilniejszym przewijaniu, mniejszej liczbie glitchy układu przy zmianach widoku oraz taśmie filmowej, która zachowuje się jak porządne rodzeństwo stołu podświetlanego, a nie jak ponownie użyty fragment.

Zmiany menu i układu nie były kosmetyką

To, że menu globalne stało się naprawdę globalne w kwietniu 2025 roku, nie było zabiegiem brandingowym. Wcześniej wiele poleceń było zaimplementowanych tak, jakby należały do lokalnych modułów, nawet gdy w rzeczywistości były działaniami na poziomie aplikacji. Niektóre z nich były całkowicie ukryte za skrótami klawiaturowymi i znane tylko tym, którzy czytali dokumentację. Wiele z nich odkryłem dopiero przy usuwaniu ich kodu. To samo dotyczyło informacji o obrazie, przełączania widoków i starych przyborników widoków. Mieszkały w miejscach, które łatwo raz obudować kodem, ale trudno spójnie utrzymywać w różnych widokach.

Przeniesienie informacji o obrazie do paska menu globalnego, przeniesienie przełącznika widoków w wyraźniejsze miejsce na najwyższym poziomie, a później usunięcie paska narzędzi na dole na środku — wszystko to były konsekwencje tej samej diagnozy: środek interfejsu powinien należeć do interakcji z obrazem, a nie do historycznych pozostałości. Usunięcie osi czasu i decyzja o niepokazywaniu pustej taśmy filmowej to mniejsze przykłady tej samej logiki. Nawet otwieranie zaznaczonego obrazu w ciemni klawiszem Enter pasuje tutaj: dostosowuje zachowanie do podstawowego zadania stołu podświetlanego, zamiast zachowywać przypadkowe historyczne nawyki.

Korzyść dla użytkownika to nie „menu jest ładniejsze". Chodzi o to, że polecenia znajdują się w miejscach odpowiadających ich zasięgowi, a sam stół podświetlany ma mniej elementów sterujących rywalizujących z siatką. Nie wspominając o tym, że „moduły", które w rzeczywistości są (niejednorodnymi) siatkami przycisków, to po prostu menu w przebraniu o gorszym projekcie, więc teraz zamieniono je w pozycje menu.

Menu globalne było też okazją, by przenieść do GUI funkcje, które do tej pory żyły wyłącznie w ukrytych skryptach powłoki (!): wstępne wczytywanie miniatur bieżącej kolekcji oraz czyszczenie miniatur z pamięci podręcznej na dysku. Były one od lat zgłaszane w GUI, ale nie pasowały nigdzie w projekcie interfejsu Darktable stawiającym moduły na pierwszym miejscu.

Ale… ów prawy pasek boczny modułów, które nie chciały być menu, był błogosławieństwem, ponieważ zabierał sporo obszaru do zamalowania obrazami i sprawiał, że miniatury były mniejsze. Jego usunięcie ukazało kiepską wydajność rysowania miniatur w całej okazałości: nic nie było buforowane, ani powierzchnie obrazów, ani metadane, i wszystko było cały czas pobierane od nowa. Przepisanie było nieuniknione; architektura była zepsuta.

Powiększanie stało się użyteczne dopiero po ujednoliceniu rozmiarów miniatur

Wczesna próba powiększalnego stołu podświetlanego zawiodła z powodu strukturalnego: geometria miniatur, zbuforowane rozmiary mipmap i nawigacja na poziomie widoku nie współdzieliły tego samego modelu. Właśnie dlatego tę funkcję trzeba było najpierw usunąć. Gdybym dalej dopieszczał ją na miejscu, rezultatem byłaby jedynie lepiej wyglądająca zepsuta funkcja.

Późniejsza praca naprawiła łańcuch zależności w odwrotnej kolejności. Backend widoku zyskał rusztowanie dla powiększania stołu podświetlanego, następnie pamięć podręczna miniatur nauczyła się bardziej spójnie rozumować o rozmiarach miniatur, przygotowano API miniatur w pełnej rozdzielczości, po czym na tym dodano ograniczanie (clamping), przesuwanie (panning) oraz automatyczne przesuwanie oparte na barycentrum. Dopiero gdy niższe warstwy uzgodniły rozmiar obrazu i strategię pobierania, powiększenie 200% stało się funkcją, którą da się obronić.

Korzyść dla użytkowników polega na tym, że powiększanie jest teraz powiązane z pamięcią podręczną i nawigacją, zamiast z nimi walczyć. Przeciąganie w celu przesuwania, Shift + przeciąganie po widocznych miniaturach oraz automatyczne przesuwanie w stronę barycentrum szczegółów — wszystko działa, ponieważ leżący u podstaw potok wie, czym mają być powiększone miniatury.

Mylące znaczenie słowa „zoom" na stole podświetlanym zostało zaktualizowane: terminu „zoom" używano zarówno do powiększania zewnętrznego (liczba obrazów w wierszu, co pośrednio wpływa na ich widoczny rozmiar), jak i do powiększania wewnętrznego (powiększenie w obrębie kadru obrazu). Teraz więc mamy kolumny dla powiększania zewnętrznego oraz zoom dla powiększenia.

image

Nie zważaj na krzyżyk w miejscu symbolu - w spinbuttonie kolumn, to błąd GTK z motywem Breeze KDE/Plasma .

Umożliwiło to zaimplementowanie funkcji, którą miałem w głowie od dawna: barycentrycznego automatycznego przesuwania przy powiększeniu miniatury.

image
Powiększenie dopasowane
image
Włączenie powiększenia 100 %

Widać, że po włączeniu powiększenia 100% obrazy są automatycznie wyrównywane według swojej treści, mimo zupełnie różnego kadrowania i proporcji. Wykorzystuje to analizę cech opartą na dekompozycji falkowej, z której obliczamy współrzędne barycentrum szczegółów. Nie jest to idealne, ponieważ nie trafia w tę samą część twarzy na różnych obrazach, ale daje nam twarz w każdym przypadku. To projekt, który od dawna odkładałem na później, ale nie był możliwy przy poprzednim projekcie stołu podświetlanego.

Nie trzeba dodawać, że przeciąganie wewnątrz powiększonych miniatur zostało przywrócone, a przeciąganie wewnątrz wszystkich powiększonych miniatur również dodano (Shift+przeciąganie). Ta funkcja była od dawna wyczekiwana w Darktable, ale najwyraźniej nie była możliwa przy ich gównianym projekcie.

Samozadane spowolnienie metadanych

Stary stół podświetlany wciąż zadawał bazie danych te same pytania, po jednej miniaturze naraz. Przy małej kolekcji łatwo tego nie zauważyć. Przy większej ujawnia się to jako mikroprzycięcia przy otwieraniu widoku, pokazywaniu nakładek, najeżdżaniu na grupy czy odświeżaniu danych zależnych od historii. Ukryty problem nie polegał na tym, że surowe SQL było wolne w wartościach bezwzględnych; chodziło o powtarzalność i moment wykonywania tych zapytań.

Poprawka przyszła etapami. Metadane tylko do odczytu zaczęto buforować ze struktur obrazu. Następnie, w lutym 2026, metadane całej kolekcji można było pobrać jednym zapytaniem SQL zamiast jednym zapytaniem na obraz. Thumbtable mógł zasiać pamięć podręczną obrazów z kolekcji, którą miał zaraz wyświetlić. Buforowanie informacji o miniaturach zrefaktoryzowano do funkcji pomocniczej, a później scalono z powrotem do dt_image_t, aby metadane nie musiały odbijać się między równoległymi strukturami. Nawet drobne zmiany, takie jak usunięcie pingów hasha historii przy przemalowaniu, mają tu znaczenie, ponieważ eliminują niewidoczne rundy komunikacji z gorących ścieżek.

Usunięto wiele bezużytecznych zapytań SQL na miniaturę, wraz z kodem SQL w kodzie GUI stołu podświetlanego, więc warstwy funkcjonalne są teraz właściwie rozdzielone.

Korzyść dla użytkownika jest konkretna: otwieranie dużych kolekcji rzadziej się zacina, wyświetlanie z licznymi nakładkami rzadziej się waha, a aktualizacje historii lub metadanych propagują się z mniejszą liczbą widocznych pauz.

Pamięć podręczna mipmap też wymagała naprawy

Pamięć podręczna mipmap to warstwa, która wczytuje obrazy raw i wstępnie wygenerowane miniatury do pamięci RAM i opróżnia je, gdy pamięci zaczyna brakować. Było to jedno z najgłębiej ukrytych źródeł widocznych błędów. Gdy wszystko się zgrywało, działało wystarczająco dobrze. Gdy plik źródłowy był mniejszy niż oczekiwano, gdy osadzony podgląd JPEG był dziwny, gdy hash historii był niezdefiniowany lub gdy unieważnianie pamięci podręcznej na dysku pozostawało w tyle za edycjami, użytkownik nie doświadczał „błędu pamięci podręcznej". Widział nieaktualne miniatury, uszkodzone podglądy lub obrazy, które odmawiały odświeżenia.

Właśnie dlatego tak duża część prac nad pamięcią podręczną w 2025 roku wygląda chirurgicznie. Logikę alokacji buforów przepisano, bo własność była zbyt zaplątana. Poprawiono obsługę zbyt małych danych wejściowych, ponieważ założenia o rozmiarze wejścia przeciekały zbyt daleko. Osadzone podglądy przestały być tak agresywnie odrzucane, ponieważ zbyt małe podglądy wciąż były przydatne dla spójności między poziomami powiększenia. Zaczęto używać podglądów JPEG z plików towarzyszących (sidecar), gdy są obecne, zamiast miniatur osadzonych w raw. Unieważnianie pamięci podręcznej utwardzono, a zbuforowane miniatury były naprawdę usuwane z dysku, gdy powinny. Ponownie wygenerowane mipmapy zaczęły zapisywać swój hash z powrotem do bazy danych w synchronizacji ze stanem historii, a przypadek niezdefiniowanego hasha historii przestał pozostawiać mipmapy niezapisane.

Najbardziej podstępny błąd, jaki znalazłem, był zagrzebany w bezsensownej złożoności przeplatania thumbtable, widoku i pamięci podręcznej mipmap. Podczas gdy renderowanie miniatur było oddelegowane do osobnego wątku dla wydajności (i słusznie), a można było uruchomić więcej niż jeden wątek, aby (rzekomo) przetwarzać kilka miniatur naraz (być może przy użyciu kilku GPU), wszystkie wątki przetwarzające oraz GUI w rzeczywistości rywalizowały o zablokowanie pamięci podręcznych mipmap i obrazów, co jedynie sprawiało, że GUI się zacinało, gdy potoki były uruchomione. Tylko dlatego, że pracowałem przy założeniu, iż przepisałem rzeczy zgodnie z podręcznikiem, i wiedziałem, że podręcznikowy sposób powinien być szybszy niż to, drążyłem dalej, aż znalazłem, dlaczego wciąż nie było tak szybko, jak oczekiwano, upraszczając przy okazji wszystko warstwa po warstwie.

Korzyść dla użytkowników polega na tym, że pamięć podręczna jest bardziej godna zaufania. Po edycjach stół podświetlany rzadziej pokazuje przestarzałą miniaturę. Przy kłopotliwych plikach generowanie podglądu rzadziej zawodzi. Przy wielokrotnych wizytach w tej samej kolekcji pamięć podręczna na dysku zachowuje się bardziej jak pamięć podręczna, a mniej jak archiwum nieaktualnych zrzutów ekranu. A wszystko to bez konieczności ciągłego odświeżania/przeliczania/przemalowywania wszystkiego tylko dla pewności.

Ponadto opcje używania osadzonych plików JPEG lub wymuszenia ponownego przeliczenia przeniesiono z preferencji do menu globalnego i można je zmieniać w trakcie działania programu:

image

To kolejny przykład, w którym uporządkowanie i uproszczenie backendu utorowało drogę do rozszerzenia front-endu i nowych funkcji, które po prostu mają sens.

Bezpieczeństwo wątków i wyścigi (race conditions)

Klasyczny powód, dla którego te błędy przetrwały tak długo, jest taki, że wymagały, by użytkownik był szybszy niż kod: szybko przewinąć, opuścić widok, gdy miniatury są wciąż budowane, zmienić rozmiar, gdy powierzchnia w tle jest wciąż wytwarzana, albo zamknąć widżet tuż przed tym, jak wątek roboczy wypchnie aktualizację.

Właśnie dlatego praca nad bezpieczeństwem wątków w latach 2025-2026 ma znaczenie. Pobieranie miniatur przeniesiono głębiej do zadań w tle, a później jawnie oddelegowano, aby renderowanie w ciemni mogło zachować priorytet. Niszczenie miniatur przeniesiono do bezpieczniejszych punktów sprzątania. Zadania w tle wytwarzające miniatury nauczyły się anulować same siebie, gdy obsługiwany przez nie widżet znikał. Powierzchnie obrazów zabezpieczono muteksami. Zwolnione wskaźniki zerowano. Bufory obrazów przeniesiono na lepiej zarządzane ścieżki alokacji. Sensem tego wszystkiego nie było „więcej wątków"; chodziło o powstrzymanie starych wątków przed pisaniem do martwego stanu lub rywalizacją o blokady.

Rezultatem od strony użytkownika jest mniej segfaultów, mniej losowych glitchy podczas szybkiego przewijania i mniej przypadków, w których interfejs wydaje się ścigać sam ze sobą pod obciążeniem.

Usunięto również kilka blokad wątków, dodawanych przez lata w celu łatania problemów. Wiele z nich było zbędnych (choć nie było tego widać pod szaloną złożonością całości), niektóre aktywnie szkodziły wydajności, a wszystkie ukrywały złe projektowanie. Mamy teraz mniej punktów blokowania, ale wynik jest bardziej czytelny i bardziej odporny.

W efekcie widżety miniatur są teraz w pełni zamkniętymi obiektami. Wewnętrznie zarządzają własnymi wątkami potoku renderowania miniatur, które oddziałują bezpośrednio z ich własną, buforowaną powierzchnią obrazu, więc mogą je samodzielnie tworzyć lub zabijać. Ten buforowany obraz jest unieważniany tylko wtedy, gdy zmienia się historia obrazu, co jest jawnie sygnalizowane przez zaplecze historii wywoływania. Ponieważ widżet miniatury zna swój własny stan (widoczny lub nie, wymagający odświeżenia obrazu lub nie, rozmiar, tryb wyostrzania obszaru ostrości itd.), staje się o wiele bardziej odporny niż przy próbie obsługi tych wszystkich rzeczy z warstw wyższego poziomu, które nie potrafią się ze sobą komunikować.

Nie było to możliwe przy projekcie Darktable, ponieważ nieustannie dynamicznie dodawał i usuwał widżety miniatur z bieżącego widoku stołu podświetlanego, w zależności od położenia przewijanej linii, co było próbą radzenia sobie ze spowolnieniami spowodowanymi przez wszystkie wątki rywalizujące o dostęp do bufora. Ale poza tym widżety miniatur w Darktable próbowały natychmiast pozyskać obraz z bufora mipmap w chwili utworzenia, co powodowało skoki użycia procesora i operacji wejścia-wyjścia pamięci, i faktycznie zamrażało interfejs. Jednak „naprawa" tego poprzez skrócenie oczekiwanego czasu życia widżetów miniatur uniemożliwiła pozostawienie im wewnętrznego zarządzania własnym stanem, więc musiało to być robione przez warstwy wysokiego poziomu, które musiały komunikować się ze sobą, aby aktualizować stany — co gdzieniegdzie faktycznie się działo, ale kosztem nieznośnej złożoności.

Zamiast tego wszystkie miniatury w Ansel są inicjalizowane naraz, ale leniwie pozyskują obraz z bufora mipmap dopiero, gdy staną się widoczne, a następnie buforują go wewnętrznie. Dzięki temu przewijanie tablicy miniatur pozostaje responsywne, nawet przy kolekcji 500 obrazów, które wszystkie generują swój obraz.

Podglądy importu poprawione jako efekt uboczny

Okno importu było dotknięte tą pracą z tego samego powodu co stół podświetlany: potrzebuje szybkiego wyodrębniania podglądów, inspekcji metadanych i sensownego zachowania awaryjnego przy plikach obsługiwanych tylko częściowo. Po przepisaniu ścieżek mipmap i miniatur wczytywanie podglądu raw w imporcie stało się szybsze, a obsługa TIFF/DNG również się poprawiła.

To dobry przykład tego, dlaczego przepisanie musiało nastąpić nisko w stosie. Jeśli leżąca u podstaw maszyneria podglądów jest nieefektywna lub krucha, zarówno stół podświetlany, jak i import dziedziczą to samo cierpienie. Gdy tę maszynerię przepisano, oba skorzystały.

Globalne zmiany architektoniczne

W moim poprzednim artykule pokazałem, jak bufor roboczego potoku sprawia, że przechodzenie tam i z powrotem między stołem podświetlanym a ciemnią jest niemal natychmiastowe, ponieważ nie trzeba ponownie przeliczać całego obrazu. Praca wykonana tutaj nad interfejsem rozwiązuje ten sam problem opóźnień przy przełączaniu widoków, ale na poziomie interfejsu. Nie ma więc opóźnienia przy przełączaniu między oboma widokami.

Jest to ważne, ponieważ opóźnienie przy przełączaniu widoków było wykorzystywane jako wymówka do dublowania funkcji (modułów/przyborników) między stołem podświetlanym a ciemnią, co jedynie zwiększa bałagan w interfejsie. Tak więc cała ta poprawa zaplecza umożliwia ulepszenie projektu interfejsu poprzez wyspecjalizowanie każdego widoku do jednego zadania:

  • obsługa metadanych dla stołu podświetlanego (oraz oczywiście selekcja),
  • edycja obrazu dla ciemni.

W rezultacie przyborniki metadanych i znaczników zostały usunięte z ciemni.

Ile linii kodu zaoszczędziło przepisanie?

Licząc tylko linie niebędące komentarzami ani pustymi za pomocą cloc i porównując ostatnie drzewo przed 1 stycznia 2022 z bieżącym drzewem dla omawianych tutaj głównych plików, przepisanie zaoszczędziło łącznie 4 714 linii kodu.

Zakres tego zliczenia to:

  • cały katalog data/themes/ sprzed 2022, w porównaniu z dzisiejszym data/themes/ansel.css,
  • src/common/mipmap_cache.[ch],
  • src/dtgtk/thumbtable.[ch],
  • src/dtgtk/thumbnail.[ch],
  • src/views/view.[ch],
  • src/views/lighttable.c,
  • src/libs/collect.c,
  • src/libs/tools/filter.c,
  • plus pliki usunięte całkowicie przez przeprojektowanie lub porządki w interfejsie kolekcji: src/dtgtk/culling.[ch], src/libs/tools/view_toolbox.c, src/libs/collect.h oraz src/libs/recentcollect.c.

W ramach tego zakresu suma spadła z 15 257 linii kodu sprzed 2022 do 10 543 dzisiaj. Przepisanie zaoszczędziło więc łącznie 4 714 linii kodu. Największe oszczędności wynikły z całkowitego usunięcia widoku selekcji (src/dtgtk/culling.c) (-1 406 linii), zmniejszenia stołu podświetlanego (src/views/lighttable.c) (-976), usunięcia 7 starych arkuszy stylów motywów i połączenia ich w jeden (-864 w całym data/themes/), zmniejszenia src/dtgtk/thumbnail.c (-460), src/dtgtk/thumbtable.c (-458), usunięcia src/libs/recentcollect.c (-358) oraz zmniejszenia src/views/view.c (-314). Niektóre pliki rzeczywiście urosły, zwłaszcza src/libs/tools/filter.c (+215) i src/libs/collect.c (+157), ponieważ częścią celu było przeniesienie zachowań specjalnych, takich jak selekcja i stare gałęzie interfejsu kolekcji, z powrotem do prostszej, współdzielonej infrastruktury, zamiast utrzymywania ich w równoległych widokach i pobocznych modułach.

Liczba linii nie jest sama w sobie miarą jakości. Wiele przepisań jedynie przenosi kod z miejsca na miejsce. Ale tutaj liczba pokrywa się ze zmianą projektu: mniej zdublowanych zachowań, mniej równoległych widoków, mniej kompensujących hacków i mniej miejsc, w których interfejs, warstwa widoku i bufor musiały rozwiązywać ten sam problem dwukrotnie.

Wszystko, co naprawiłem, naprawiłem poprzez uproszczenie logiki i kodu. Żadne obejścia nie były dozwolone.

Testy wydajności

Wszystkie czasy wykonania obliczono na laptopie Lenovo ThinkPad P51 (procesor Intel Xeon E3-1505M v6 @ 3.00GHz, karta graficzna Nvidia Quadro M2200 z 4 GB VRAM, 32 GB RAM, wyświetlacz 4K), z procesorem w trybie wydajności, Linux Fedora 41 z pulpitem KDE/Plasma. Czasy wykonania potoku pikseli nie są porównywane (poza zakresem; zobacz poprzedni artykuł). Ansel Master wzięto z commita 09749f1d  (21 lutego 2026).

OpisAnsel MasterDarktable 5.0
Czas od uruchomienia aplikacji do narysowania ostatniej miniatury na stole podświetlanym (ta sama kolekcja)2,12 s7,49 s
Czas przełączenia ze stołu podświetlanego do ciemni (ten sam obraz)0,2 s1,2 s
Czas przewinięcia (początek->koniec) przez tę samą kolekcję 471 obrazów*0,7 s5,0 s

*: miniatury wstępnie wczytane do bufora dyskowego w obu przypadkach, 5 kolumn miniatur na rząd, rozdzielczość 4K, brak prawego paska bocznego.

Jako „naprawę" Darktable 5.x pobłogosławił nas wspaniałym ekranem powitalnym, który jest bardziej wyznaniem niż czymkolwiek innym.

Poniższe zmierzono na baterii, w trybie oszczędzania energii, z aplikacją pozostawioną bezczynnie (bez interakcji użytkownika) przez 5 minut, przy użyciu Intel Powertop. Bazowe zużycie całego bezczynnego systemu operacyjnego to 1,6% procesora. (Moc podano tylko dla aplikacji, % procesora podano dla całego systemu):

WidokAnsel MasterDarktable 5.0
Stół podświetlany1,8% procesora, moc: 0,85 mW2,7% procesora, moc: 103 mW
Ciemnia1,8% procesora, moc: 7,65 mW1,8% procesora, moc: 22 mW

Te liczby przedstawiają bazowe zużycie mocy samego interfejsu (GTK, procesy w tle, zaplanowane liczniki czasu itd.). Darktable traci wydajność przez interfejs, a żmudna praca wykonana w latach 2023–2024 nad optymalizacją modułów przetwarzania pikseli dla dodatkowych 15–50 ms jest kompletnie bez znaczenia.

Podsumowanie

W tym momencie jestem głęboko przekonany, że „projekt" Darktable przyciąga jedynie „programistów", którzy nie potrafiliby dokonać poznawczego rozróżnienia między interfejsem a zapleczem, nawet gdyby zależało od tego ich życie. Tak więc problemy interfejsu rozwiązuje się w zapleczu, problemy zaplecza rozwiązuje się w interfejsie, a to sprawia, że złożoność kodu wymyka się z czasem spod kontroli, co później usprawiedliwia dodawanie nowych funkcji poprzez hakowanie najmniejszej ilości kodu w bazie kodu, której już nikt nie rozumie. Nie wspominając o tym, że nic z tego nie było udokumentowane, więc musiałem to boleśnie inżynierować wstecznie przez kilka lat, rekurencyjnie upraszczając trochę tu i trochę tam, aż w końcu zbiegło się to do czystej całościowej logiki.

Nie trzeba dodawać, że wszystkie obejścia i „szybkie naprawy", które dodano, zostały usunięte. Wszystkie pochodziły z okresu po 2020 roku, co pokazuje niepokojący trend degradacji jakości kodu.

Ten projekt porządkowy ukradł mi 4 lata życia, nie dał mi żadnej przyjemności, a ludzie, którzy wprowadzili wszystkie regresje, które boleśnie naprawiałem, muszą zostać pociągnięci do odpowiedzialności za konsekwencje swoich działań. Istnieje ogromna różnica między nieposiadaniem wystarczająco dużo czasu, by zrobić coś porządnie, a zużyciem mnóstwa roboczogodzin na pogorszenie sytuacji. A potem odmawianie przyznania, że pogorszyło się sprawy, odmawianie zaakceptowania, że istnieje problem, i karmienie własnego błędu potwierdzenia poprzez wysłuchiwanie wyłącznie entuzjastycznych opinii jest ostatecznym dowodem głupoty.

Darktable zdegradował się do gówna, a ja właśnie wyjaśniłem, technicznie, dlaczego. Zespół Darktable i jego poboczne forum tech-brosów  chcieliby wmówić ludziom, że wściekłem się na nich, ponieważ nie chcieli przyjąć moich zmian, i że to wszystko jest problemem interpersonalnym. Dla laika, który nie rozumie tego, co napisałem tutaj i w poprzednich artykułach, łatwiej jest wierzyć w interpersonalny gniew niż zrozumieć, jak następstwo złych decyzji technicznych na przestrzeni kilku lat uczyniło mnie ofiarą problemów, które oni stworzyli, ponieważ byłem tu jedynym facetem na pełen etat, zależnym od tego na życie. Wściekłem się na nich, ponieważ raz za razem srali mi na progu, a ja musiałem wielokrotnie sprzątać. To forma przemocy, którą naprawdę trudno dostrzec i rozpoznać, ponieważ nie przejawia się materialnie: to sposób na utrudnianie ci życia, codziennie, krok po kroku, tylko dlatego, że banda amatorskich facetów w średnim wieku bez żadnych umiejętności chciała być częścią czegoś fajnego, nie zdając sobie sprawy ze szkodliwego wpływu swoich wkładów na cały projekt.

I to na mnie spadło sprzątanie tego bałaganu, ponieważ najwyraźniej byłem jedynym, którego obchodziły regresje, dziwne losowe błędy i awarie, najgorsze „innowacje" interfejsu, które zniechęciły nawet moją własną żonę do używania Darktable, bo jest po prostu przytłaczający, oraz wszystkie nowe spowolnienia, które ciągle się nawarstwiają na przestrzeni lat. Jak powiedział mi Chris Elston na czacie IRC Darktable w 2022 roku, zanim opuściłem go na zawsze, o rzeczach, które naprawiłem w 2019 roku, a które oni znowu zepsuli w 2022: „zamknij się i napraw to". Jeśli to nie jest przemoc, to nie wiem, co nią jest. A teraz próbują rozgłaszać, że to ja byłem toksyczny. Cały zespół jest toksyczny. Ich niedbała kultura pracy jest toksyczna. Ich sposób ekscytowania się wszystkim, byle było nowe, bez rozważania kosztu utrzymania, dublowania funkcji i ogólnego przytłaczania użytkownika jest toksyczny. Ich brak troski o przyszłość projektu i o konsekwencje dokonywanych wyborów jest toksyczny.

A szczególnie toksyczne jest to, że na discuss.pixls.us każdy post wyrażający pochwały dla mnie lub pochwały dla Ansel zostaje oznaczony i ukryty. Nie ma wolności słowa na forum wolnego oprogramowania. Po prostu zamienili komunizm w stalinizm.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.