Compare commits

...

2 Commits

Author SHA1 Message Date
9348c535b7 Add best Memory cache usage info 2025-09-14 21:07:19 +02:00
b34bdfea4c Update README.md 2025-09-14 20:35:17 +02:00
2 changed files with 167 additions and 123 deletions

184
README.md
View File

@@ -1,143 +1,113 @@
# Visionneuse de Vignettes en PureBasic / Thumbnail Viewer in PureBasic
# PureBasic Advanced Thumbnail Gadget
Ce projet est un composant d'interface utilisateur avancé pour PureBasic qui permet d'afficher des centaines ou des milliers de vignettes d'images de manière fluide et efficace. Il est conçu pour être facilement intégré dans n'importe quelle application nécessitant une galerie d'images performante.
A powerful and flexible thumbnail viewer component for PureBasic, designed for high performance with asynchronous image loading and caching. This gadget is perfect for applications that need to display a large number of images without freezing the user interface, such as image browsers, file managers, or digital asset managers.
*This project is an advanced UI component for PureBasic that allows displaying hundreds or thousands of image thumbnails smoothly and efficiently. It is designed to be easily integrated into any application requiring a high-performance image gallery.*
## Fonctionnalités / Features ✨
* **Chargement Asynchrone :** Les images sont chargées en arrière-plan grâce au multithreading, ce qui évite de figer l'application.
* ***Asynchronous Loading:*** *Images are loaded in the background using multithreading, which prevents the application from freezing.*
* **Système de Cache :** Un cache intelligent conserve les images chargées en mémoire pour un réaffichage instantané et gère la libération de la mémoire pour les images qui ne sont plus visibles.
* ***Caching System:*** *An intelligent cache keeps loaded images in memory for instant redisplay and manages memory release for images that are no longer visible.*
* **Défilement Fluide :** Le défilement vertical est optimisé pour une expérience utilisateur agréable, même avec de nombreuses vignettes.
* ***Smooth Scrolling:*** *Vertical scrolling is optimized for a pleasant user experience, even with many thumbnails.*
* **Modes d'Affichage :** Les images peuvent être ajustées pour s'adapter, remplir ou s'étirer dans l'espace de la vignette (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`).
* ***Display Modes:*** *Images can be adjusted to fit, fill, or stretch within the thumbnail area (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`).*
* **Dessin Vectoriel :** L'utilisation du sous-système `VectorDrawing` assure un rendu de haute qualité et une bonne compatibilité avec les écrans à haute résolution (HiDPI).
* ***Vector Drawing:*** *The use of the `VectorDrawing` subsystem ensures high-quality rendering and compatibility with high-resolution (HiDPI) displays.*
* **Sélection Multiple :** Support de la sélection d'une ou plusieurs vignettes (y compris la sélection de plage avec la touche **Majuscule**).
* ***Multiple Selection:*** *Supports selecting one or more thumbnails (including range selection with the **Shift** key).*
\!
-----
## Structure du Projet / Project Structure 🏗️
## Features
Le code est organisé en plusieurs modules logiques pour une meilleure clarté et maintenance.
*The code is organized into several logical modules for better clarity and maintainability.*
* **`Core`** : Définit les structures de données de base utilisées dans le projet. / *Defines the basic data structures used in the project.*
* **`ImgTools`** : Fournit des fonctions utilitaires pour le redimensionnement et le positionnement des images. / *Provides utility functions for image resizing and positioning.*
* **`Cache`** : Gère toute la logique de chargement en arrière-plan et de mise en cache des images. / *Manages all the background loading and image caching logic.*
* **`Thumbs`** : Contient le gadget principal (`ThumbsGadget`) et gère l'affichage, les événements et les interactions utilisateur. / *Contains the main gadget (`ThumbsGadget`) and handles display, events, and user interactions.*
* [cite\_start]**Asynchronous Loading**: Uses a pool of worker threads to load images in the background, ensuring the UI remains responsive at all times[cite: 11, 126, 175].
* [cite\_start]**Image Caching**: An intelligent cache manages loaded images, freeing memory by cleaning up unused thumbnails automatically[cite: 82, 232, 233].
* **Dynamic Customization**:
* Adjust thumbnail **size** in real-time.
* [cite\_start]Switch between **display styles**: `Fit`, `Fill`, and `Stretch` on the fly[cite: 48, 436].
* [cite\_start]**High-DPI Aware**: The interface is fully scaled for high-resolution displays, ensuring crisp visuals[cite: 296, 597].
* **Advanced User Interaction**:
* [cite\_start]Smooth, pixel-based scrolling[cite: 290].
* [cite\_start]Multi-selection with **Shift** for ranges and **Ctrl** for individual toggling[cite: 496, 506].
* [cite\_start]**Easy Integration**: Built with a simple callback system, allowing you to easily feed it data from any source (local files, network, database, etc.)[cite: 254, 562, 564].
* **Self-Contained Modules**: The code is organized into logical modules (`Cache`, `Thumbs`, `ImgTools`) for better readability and maintenance.
-----
## Prérequis et Compilation / Prerequisites and Compilation ⚙️
## Requirements
Pour compiler et utiliser ce projet, vous aurez besoin de :
*To compile and use this project, you will need:*
* **PureBasic 6.04 LTS (x64)** ou une version plus récente. / *or a more recent version.*
* **Système d'exploitation / OS :** Windows (testé principalement sur cette plateforme / *tested mainly on this platform*).
### Options du Compilateur / Compiler Options
Assurez-vous que les options suivantes sont activées dans les paramètres de votre compilateur.
*Make sure the following options are enabled in your compiler settings.*
***Activer le support des threads (Thread-Safe)** : Essentiel pour le système de cache. / *Essential for the caching system.*
***Activer la prise en charge DPI (DPI-Aware)** : Pour un affichage correct sur les écrans modernes. / *For correct display on modern screens.*
* **PureBasic Compiler** (developed with v6.21 x64).
* **Compiler Options**: The following options must be enabled for your project:
* [cite\_start]**Thread-Safe**: This is mandatory for the multi-threaded cache to work correctly[cite: 10, 13].
* [cite\_start]**DPI-Aware**: Required for correct UI scaling on modern displays[cite: 597].
-----
## Comment l'utiliser ? / How to Use It? 🚀
## How to Use
L'intégration du gadget dans votre propre fenêtre est simple. Voici un exemple de base.
*Integrating the gadget into your own window is simple. Here is a basic example.*
Integrating the thumbnail gadget into your own PureBasic project is straightforward. [cite\_start]The included test code (`CompilerIf #PB_Compiler_IsMainFile`) serves as a practical example[cite: 552, 553].
### Step 1: Initialize the System
In your main program, before creating the gadget, initialize the image cache.
```purebasic
;- Incorporez les modules du projet ici / Include the project's modules here
; Enable necessary image decoders
UseJPEGImageDecoder()
UsePNGImageDecoder()
Global NewList CurrentList.s()
Global CurrentListMutex.i = CreateMutex()
; Initialize the cache system
Cache::InitCache()
```
; 1. Créez une fonction de rappel (callback) pour charger les chemins d'images
; 1. Create a callback function to load the image paths
Procedure CallBackLoadFiles(GadgetId.i, Index.i, Length.l)
Protected n.l, TmpIndex.i
Protected *Ptr.Core::FileData
### Step 2: Implement the Data Callback Function
LockMutex(CurrentListMutex)
For n = 1 To Length
TmpIndex = Index + n - 1
If TmpIndex >= 0 And TmpIndex < ListSize(CurrentList())
SelectElement(CurrentList(), TmpIndex)
Create a procedure that the `Thumbs` gadget will call whenever it needs to load image data. This function receives a starting `Index` and the `Length` (number of items) to load. Your job is to provide a pointer to the image data for each index.
; On demande au cache de préparer le fichier / Ask the cache to prepare the file
*Ptr = Cache::GetFileDataFromCache(CurrentList())
; On lie le pointeur de données à l'index de la vignette / Link the data pointer to the thumbnail index
Thumbs::AddImageToThumb(GadgetId, TmpIndex, *Ptr)
EndIf
```purebasic
; This is your custom function to link your list of files to the gadget
Procedure MyCallBackLoad(GadgetId.i, Index.i, Length.l)
Protected i
For i = 0 To Length - 1
Protected CurrentIndex = Index + i
; 1. Get the file path from your own list (e.g., a List() or Array())
SelectElement(MyFileList(), CurrentIndex)
Protected FilePath.s = MyFileList()
; 2. Ask the cache for a pointer to the file's data structure
Protected *Ptr.Core::FileData = Cache::GetFileDataFromCache(FilePath)
; 3. Give this pointer back to the Thumbs gadget for the correct index
Thumbs::AddImageToThumb(GadgetId, CurrentIndex, *Ptr)
Next
Thumbs::LimitIndex(GadgetId, ListSize(CurrentList()))
UnlockMutex(CurrentListMutex)
; 4. Update the gadget's scroll limits
Thumbs::LimitIndex(GadgetId, ListSize(MyFileList()))
EndProcedure
```
### Step 3: Create the Gadget
; 2. Créez votre fenêtre et le gadget / Create your window and the gadget
If OpenWindow(0, 0, 0, 800, 600, "Exemple de Vignettes / Thumbnail Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget)
Create the `ThumbsGadget` in your window and pass a pointer to your callback function.
; Créez le gadget en spécifiant la taille des vignettes et la fonction de rappel
; Create the gadget, specifying the thumbnail size and the callback function
Thumbs::ThumbsGadget(1, 0, 0, WindowWidth(0), WindowHeight(0), 128, @CallBackLoadFiles())
```purebasic
; Create the gadget and link it to your callback
Thumbs::ThumbsGadget(#MyThumbsGadget, 0, 0, 800, 600, 128, @MyCallBackLoad())
```
; 3. Remplissez votre liste de chemins d'images / Populate your list of image paths
LockMutex(CurrentListMutex)
ExamineDirectory(0, "C:\Your\Images", "*.jpg")
While NextDirectoryEntry(0)
AddElement(CurrentList())
CurrentList() = "C:\Your\Images\" + DirectoryEntryName(0)
Wend
FinishDirectory(0)
UnlockMutex(CurrentListMutex)
### Step 4: Clean Up on Exit
; 4. Forcez la mise à jour pour afficher les nouvelles images / Force an update to display the new images
Thumbs::ForceUpdate(1)
When your application closes, make sure to free the resources used by the gadget and the cache.
; Boucle d'événements classique / Classic event loop
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Quit = 1
Case #PB_Event_SizeWindow
ResizeGadget(1, 0, 0, WindowWidth(0), WindowHeight(0))
EndSelect
Until Quit = 1
Thumbs::FreeThumbsGadget(1)
EndIf
```purebasic
; Clean up before closing the window
Thumbs::FreeThumbsGadget(#MyThumbsGadget)
Cache::QuitCache()
```
-----
## Auteur / Author ✍️
## Code Structure
* **Thyphoon**
The project is divided into several modules, each with a specific responsibility:
## Licence / License 📜
* [cite\_start]**`Module Helpers`**: Contains simple utility functions like `Min()` and `Max()`[cite: 16].
* [cite\_start]**`Module ImgTools`**: Provides image manipulation utilities, primarily the `ImageToContainer` procedure for calculating scaling dimensions[cite: 43, 58, 59].
* **`Module Cache`**: The core of the background processing. [cite\_start]It manages a thread pool to load images and a central map to cache them, preventing duplicate loading and managing memory[cite: 82, 98, 126].
* **`Module Thumbs`**: The main UI component. [cite\_start]It handles drawing, scrolling, user input (clicks, keyboard), and communication with the cache via callbacks[cite: 252, 253].
Ce projet est distribué sous une licence libre et non restrictive. Vous pouvez l'utiliser, le modifier et le distribuer sans contrainte. Un crédit est apprécié mais non obligatoire.
-----
*This project is distributed under a free and unrestricted license. You can use, modify, and distribute it without constraints. Credit is appreciated but not required.*
## Author & License
* [cite\_start]**Author**: Thyphoon [cite: 4]
* **License**: Free and unrestricted. [cite\_start]Credit is appreciated but not required[cite: 6]. [cite\_start]Please feel free to share any improvements[cite: 8].

View File

@@ -153,6 +153,14 @@ EndModule
DeclareModule Cache
EnableExplicit
; Structure to hold cache statistics / Structure pour contenir les statistiques du cache <--- MODIFIED
Structure CacheStats
TotalItems.i ; Total items in cache (loaded or not) / Nombre total d'éléments (chargés ou non)
LoadedInMemory.i ; Number of images currently in memory / Nombre d'images actuellement en mémoire
WaitingToLoad.i ; Number of images in the loading queue / Nombre d'images dans la file d'attente
MemoryUsed.q ; Total memory used by loaded images in bytes / Mémoire totale utilisée par les images chargées en octets
EndStructure
; A prototype for a callback function to load media. Allows custom loading logic. / Prototype pour une fonction de callback pour charger un média. Permet une logique de chargement personnalisée.
Prototype.i CallBackLoadMedia(*Ptr.Core::FileData)
@@ -169,6 +177,7 @@ DeclareModule Cache
Signal.b ; A flag that is set to True when an image has been loaded, to notify the main thread. / Un indicateur mis à Vrai quand une image a été chargée, pour notifier le thread principal.
QuitMutex.i ; Mutex to protect access to the Quit flag. / Mutex pour protéger l'accès à l'indicateur Quit.
Quit.b ; A flag to signal all worker threads to terminate. / Un indicateur pour signaler à tous les threads de travail de se terminer.
TotalMemoryUsed.q ; Running total of memory used by images / Total de la mémoire utilisée par les images
EndStructure
Global Param.Param ; Global instance of the parameter structure. / Instance globale de la structure de paramètres.
@@ -180,6 +189,8 @@ DeclareModule Cache
Declare.i GetFileDataFromCache(FilePath.s, Image.i = 0) ; Retrieves file data from the cache, or queues it for loading if not present. / Récupère les données d'un fichier depuis le cache, ou le met en file d'attente de chargement s'il n'est pas présent.
Declare.b GetSignalAndReset() ; Checks if a new image is ready and resets the signal. / Vérifie si une nouvelle image est prête et réinitialise le signal.
Declare QuitCache() ; Properly shuts down the cache system and all worker threads. / Arrête proprement le système de cache et tous les threads de travail.
Declare GetStats(*Stats.CacheStats) ; Get current cache statistics / Récupère les statistiques actuelles du cache
EndDeclareModule
Module Cache
@@ -195,6 +206,7 @@ Module Cache
Param\SignalMutex = CreateMutex()
Param\QuitMutex = CreateMutex()
Param\NewTaskSemaphore = CreateSemaphore(0)
Param\TotalMemoryUsed = 0 ; Initialize memory counter / Initialise le compteur mémoire
Dim Param\WorkerThreads(#WORKER_THREADS - 1)
EndProcedure
@@ -249,6 +261,11 @@ Module Cache
ResizeImage(*Ptr\Image, result\Width, result\Height, #PB_Image_Smooth)
*Ptr\State = 1 ; State = 1 means "Loaded". / État = 1 signifie "Chargé".
; Securely add the new image's memory size to the total / Ajoute de manière sécurisée la taille mémoire de la nouvelle image au total
LockMutex(Param\CacheListMutex)
Param\TotalMemoryUsed + (result\Width * result\Height * (ImageDepth(*Ptr\Image) / 8))
UnlockMutex(Param\CacheListMutex)
; Safely set the signal to notify the main thread that a new image is ready for display. / Met à jour de manière sécurisée le signal pour notifier le thread principal qu'une nouvelle image est prête à être affichée.
LockMutex(Param\SignalMutex)
Param\Signal = #True
@@ -407,6 +424,8 @@ Module Cache
; Instead of destroying the structure, we only free the image / Au lieu de détruire la structure, on libère seulement l'image
; and reset the state. The pointer remains valid. / et on réinitialise l'état. Le pointeur reste valide.
If IsImage(*Ptr\Image)
; Subtract the image's memory size from the total before freeing it / Soustrait la taille mémoire de l'image du total avant de la libérer
Param\TotalMemoryUsed - (ImageWidth(*Ptr\Image) * ImageHeight(*Ptr\Image) * (ImageDepth(*Ptr\Image) / 8))
FreeImage(*Ptr\Image)
*Ptr\Image = 0
*Ptr\State = 0 ; State = "Not Loaded" / État = "Non chargé"
@@ -418,6 +437,29 @@ Module Cache
Next
UnlockMutex(Param\CacheListMutex)
EndProcedure
; GetStats - Collects and returns the current statistics of the cache. / Collecte et retourne les statistiques actuelles du cache.
Procedure GetStats(*Stats.CacheStats)
If *Stats
LockMutex(Param\CacheListMutex)
*Stats\TotalItems = MapSize(Param\CacheList())
*Stats\MemoryUsed = Param\TotalMemoryUsed ; Read the pre-calculated total / Lit le total pré-calculé
Protected LoadedCount.i = 0
ForEach Param\CacheList()
If Param\CacheList()\State > 0
LoadedCount + 1
EndIf
Next
*Stats\LoadedInMemory = LoadedCount
UnlockMutex(Param\CacheListMutex)
LockMutex(Param\LoadListMutex)
*Stats\WaitingToLoad = ListSize(Param\LoadList())
UnlockMutex(Param\LoadListMutex)
EndIf
EndProcedure
EndModule
; =================================================
@@ -1071,6 +1113,20 @@ EndModule
; =================================================
CompilerIf #PB_Compiler_IsMainFile
; Procedure: FormatBytes - Converts a byte count into a human-readable string (KB, MB, GB) / Convertit un nombre d'octets en une chaîne lisible (Ko, Mo, Go).
Procedure.s FormatBytes(bytes.q)
If bytes >= 1024 * 1024 * 1024 ; GB / Go
ProcedureReturn StrD(bytes / (1024.0 * 1024.0 * 1024.0), 2) + " GB"
ElseIf bytes >= 1024 * 1024 ; MB / Mo
ProcedureReturn StrD(bytes / (1024.0 * 1024.0), 2) + " MB"
ElseIf bytes >= 1024 ; KB / Ko
ProcedureReturn StrD(bytes / 1024.0, 2) + " KB"
Else ; Bytes / Octets
ProcedureReturn Str(bytes) + " B"
EndIf
EndProcedure
; Enumeration for the main window and gadgets. / Énumération pour la fenêtre principale et les gadgets.
Enumeration
#Win_main
@@ -1079,10 +1135,11 @@ CompilerIf #PB_Compiler_IsMainFile
#Gdt_ThumbA
#Gdt_ThumbB
#Gdt_ThumbSize
#Gdt_FrameStyle ; <--- Ajoutez
#Gdt_OptionFit ; <--- Ajoutez
#Gdt_OptionFill ; <--- Ajoutez
#Gdt_OptionStretch ; <--- Ajoutez
#Gdt_FrameStyle
#Gdt_OptionFit
#Gdt_OptionFill
#Gdt_OptionStretch
#StatusBar_Main
EndEnumeration
Global NewList CurrentList.s() ; A global list to hold the file paths of the images to be displayed. / Une liste globale pour contenir les chemins des fichiers des images à afficher.
@@ -1144,6 +1201,11 @@ CompilerIf #PB_Compiler_IsMainFile
Cache::InitCache() ; Initialize the caching system. / Initialise le système de cache.
; Create the thumbnail gadget, passing our callback function. / Crée le gadget de vignettes, en passant notre fonction de callback.
Thumbs::ThumbsGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50, 128, @CallBackLoadFromIndexB())
If CreateStatusBar(#StatusBar_Main, WindowID(#Win_main))
AddStatusBarField(400)
EndIf
; On ajoute un timer qui se déclenchera toutes les 500ms (2 fois par seconde)
AddWindowTimer(#Win_main, 1, 500)
Repeat
Event = WaitWindowEvent()
@@ -1199,6 +1261,18 @@ CompilerIf #PB_Compiler_IsMainFile
ResizeGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50)
EndIf
If Event = #PB_Event_Timer And EventTimer() = 1
Define Stats.Cache::CacheStats
Cache::GetStats(@Stats)
; Format the text to display, now including memory usage
; Formate le texte à afficher, en incluant maintenant l'utilisation mémoire
Define StatText.s = "Cache Items: " + Stats\TotalItems + " | In Memory: " + Stats\LoadedInMemory + " | Queue: " + Stats\WaitingToLoad
StatText + " | Mem. Used: " + FormatBytes(Stats\MemoryUsed)
StatusBarText(#StatusBar_Main, 0, StatText)
EndIf
Until Event = #PB_Event_CloseWindow
; Clean up all resources before exiting. / Nettoie toutes les ressources avant de quitter.
@@ -1214,9 +1288,9 @@ CompilerEndIf
; EnableXP
; DPIAware
; IDE Options = PureBasic 6.21 (Windows - x64)
; CursorPosition = 1192
; FirstLine = 1149
; Folding = -------
; CursorPosition = 441
; FirstLine = 434
; Folding = --------
; EnableThread
; EnableXP
; DPIAware