Redazione
a- a+

Generare CSS Sprite con ASP.NET in automatico

Vediamo come generare fogli di stile CSS Sprites utilizzando pagine ASP.NET . Codici ed esempio.

Quello che andremo ora a descrivere è un algoritmo per racchiudere una serie di rettangoli di larghezza e altezza differenti in un unico rettangolo chiuso, evitando sovrapposizioni tra di loro, ma sfruttando al meglio l’intero spazio contenuto nel contenitore.
Questo tipo di algoritmo è utile per la generazione dei fogli di stile CSS Sprites, particolarmente utili per velocizzare il caricamento delle immagini all’interno di una pagina web.

Con i fogli di stile CSS Sprites invece di caricare tutte le immagini una alla volta, si può fare in modo che tutte le immagini siano raggruppate dentro una più grande, chiamata “sprite”. In questo modo il carico viene effettuato tutto in una volta. Ciò evita il sovraccarico del browser, in quanto non dovrà fare una richiesta per ogni immagine al server per quante sono le immagini, ma ne farà solo una per tutte.
Per creare uno sprite l’aggiunta e la rimozione delle immagini potrebbe essere realizzata, manualmente ma questo comporterebbe un lavoro extra per chi deve gestire il sito, dovendo ogni volta accedere via FTP alle pagine, modificarle e ricaricarle sul server. Molto meglio sarebbe che lo sprite venisse generato automaticamente, e idealmente che venisse creato al momento della richiesta della pagina. Tutto ciò può essere fatto utilizzando le pagine ASP.NET.

Per ottenere questo risultato, abbiamo bisogno di un algoritmo che organizzi e componga le immagini in uno sprite in modo tale che minimizzi la dimensione dello stesso. Ovvero più piccola è la dimensione, minore sarà il tempo di caricamento dello sprite sul browser e meno banda verrà occupata. Si procede nel seguente modo:

1)     Ordiniamo i rettangoli per altezza decrescente, dal più grande al più piccolo

2)     Creiamo un rettangolo vuoto che ha un altezza pari all’altezza del più alto rettangolo (altezza = max delle altezze dei singoli rettangoli) e come larghezza sia illimitato.

3)     Sistemiamo i rettangoli uno ad uno nel rettangolo chiuso vuoto che abbiamo appena creato, partendo con i rettangoli più alti e finendo con i rettangoli più bassi.

4)     Fissiamo la larghezza del rettangolo contenitore, pari alla larghezza complessiva occupata dai singoli rettangoli. Spostiamo il bordo destro del rettangolo verso sinistra fino a toccare il bordo destro del rettangolo più a destra. In questo modo, il rettangolo non sarà più ampio del necessario.

5)     Se si è riusciti ad inserire tutti i rettangoli nel contenitore si hanno due casi:

a.     il rettangolo che si è appena creato è il più piccolo rettangolo contenitore di successo;

b.     si diminuisce la larghezza del rettangolo di una unità e si prova a racchiudere di nuovo i singoli rettangoli.
Tuttavia, se non si riesce a sistemare  tutti i rettangoli, vorrà dire che il rettangolo che funge da contenitore è ovviamente troppo piccolo. In questo caso, si aumenta l’altezza del rettangolo di uno.

In ogni caso, se non si riesce a inserire tutti i rettangoli, per ovvie ragioni il rettangolo contenitore è troppo piccolo, quindi aumentiamo l’altezza di una unità e ripetiamo i passi dal numero 1). Va notato  che ridurre la larghezza e aumentare l’altezza significa che stiamo cercando di racchiudere i rettangoli dal più basso e più largo al più alto e più stretto. Ad esempio, se riduciamo di due punti la larghezza e aumentiamo l’altezza si ottiene una sequenza del tipo:

Se l’area totale (larghezza x altezza) del rettangolo contenitore è inferiore alla superficie totale di tutti i rettangoli che si sta cercando di inserire al suo interno, questo non è ovviamente praticabile. Aumentando l’altezza di una unità significa che aumentiamo l’area del contenitore fino ad ottenere un rettangolo tale che possa racchiude tutti gli altri rettangoli.  Fatto ciò si può passare alla fase successiva. Se il contenitore così ottenuto ora è troppo grande si diminuisce di una unità la larghezza e si prosegue con il punto 5b, stando attenti che però non diventi troppo piccolo. Se la larghezza è quella giusta si prosegue.

Se il nuovo rettangolo contenitore è più stretto del rettangolo più ampio, ci si può fermare, e fissare il rettangolo così ottenuto come il miglior rettangolo che si è trovato finora. Ora che il nuovo rettangolo che racchiude tutti gli altri non è né troppo piccolo né troppo grande, si può tornare al punto 3), per verificare che sia possibile inserire tutti i rettangoli al suo interno.

L’algoritmo di cui abbiamo appena discusso è stato implementato nel metodo Mapping nella classe

MapperOptimalEfficiency.

 Per rendere il codice facilmente aggiornabile e riusabile, si è preferito usare il C# procedendo nel seguente modo:

1)     Il rettangolo contenitore viene definito utilizzando l’interfaccia IMapper di C#. Si utilizza un singolo metodo di mappatura. Questo metodo richiede una collezione di oggetti che implementano IImageInfo, e restituisce un oggetto che implementa ISprite. In altre parole, come input utilizziamo una collection di immagini che si desidera combinare in uno sprite, e l’output che otteniamo è lo sprite stesso.

2)     L’oggetto IImageInfo non fa altro che esporre la larghezza e l’altezza dell’immagine che è tutto ciò che il  rettangolo contenitore esige. Si noti che di per sé questo oggetto non rappresenta una immagine reale associata a un URL di un’immagine, o via dicendo. Si chiama IImageInfo piuttosto che IImage, perché lo spazio dei nomi System.Drawing contiene già una classe denominata Image.

3)     L’oggetto ISprite restituito dal metodo Mapping espone la larghezza, l’altezza e l’area dello sprite, e la collection di immagini contenute all’interno della sprite. Simile a IImageInfo, non definisce un’immagine, perché il codice implementa un rettangolo contenitore che viene impiegato sui rettangoli, non sulle immagini fisiche.

Il seguente codice C# descrive e crea il rettangolo contenitore

public interface IMapper<S> where S : class, ISprite, new()
{
    S Mapping(IEnumerable<IImageInfo> images);
}
public interface IImageInfo
{
    int Width { get; }
    int Height { get; }
}
public interface ISprite
{
    // Larghezza sprite
    int Width { get; }

    // Altezza dello sprite
    int Height { get; }

    // Area dello sprite
    int Area { get; }

    // Contiene le posizioni di tutte le singole immagini all’interno della   
    // sprite (trattate come rettangoli)
    List<IMappedImageInfo> MappedImages { get; }

    // Aggiunge un immagine allo SpriteInfo, e aggiorna
    // la larghezza e l’altezza dello SpriteInfo.
    void AddMappedImage(IMappedImageInfo mappedImage);
}
public interface IMappedImageInfo
{
    int X { get; }
    int Y { get; }
    IImageInfo ImageInfo { get; }
}

Per approfondimenti e per il codice completo si veda questo link.



Ti potrebbe interessare anche

commenta la notizia

C'è 1 commento
Pier Paolo
Condividi le tue opinioni su questo articolo!