dotNed

Welkom bij dotNed Inloggen | Aanmelden | Help
in Zoeken

Dennis' avonturen in .net

GetThumbnailImage

Het .net framework is groot. Ontzettend groot zelfs. Toen ik net begon met het werken in .net gebeurde het regelmatig dat ik erg goede stukken code produceerde, alleen maar om er achter te komen dat die functionaliteit allang kant en klaar in de een of andere class beschikbaar was.
Uiteraard, na verloop van tijd kwamen die momenten steeds minder voor: ik begon mijn weg in het framework behoorlijk te kennen. Ik voelde me zeker, ik wist de weg, ik weet wat er allemaal beschikbaar is. En toch…
Gisteren gebeurde het weer. Een collega stelde mij de vraag waar hij moest kijken als hij plaatjes wilde verkleinen. Ik vertelde hem dat hij even in System.Drawing.Image moest gaan kijken, en begon met hem te vertellen hoe je daar met GDI+ plaatjes kunt beïnvloeden. En toen zag ik hem staan: de method GetThumbnailImage. Ik had daar nog nooit van gehoord, maar het doet precies wat we nodig hadden! Het framework deed het weer: mijn mooie voorbeeld code is totaal overbodig gemaakt door een kant en klare method waar ik het bestaan niet eens van wist!
Maar aangezien ik toch graag voorbeelden lever, laat ik hier zien wat je met die method kunt doen.

We kennen het probleem: we hebben veel foto’s, en die willen we op een webpage zetten. Die foto’s zijn groot en dus willen we een thumbnail pagina maken waarin we een overzicht van de beschikbare foto’s geven.
Nu kunnen we in PhotoShop of Acrylic de foto’s inladen en een verkleinde kopie  opslaan in een aparte locatie opslaan. Op de thumbnails pagina laden we dan de kleine plaatjes in en die laten we linken naar de originele, grote foto’s.
Maar waarom zouden we het onszelf moeilijk maken? Waarom niet gewoon een repository met foto’s en dan de webapplicatie de thumbnails laten genereren? Dat was dus precies waar mijn collega naar op zoek was.
Om dat te bereiken maak ik een aparte pagina in mijn web applicatie, met de naam myImage.aspx. Dit is een aspx pagina die een plaatje teruggeeft. Dat heeft als gevolg dat je hem als ‘src’ kunt gebruiken in een <img> tag. De code is als volgt:

protected void Page_Load(object sender, EventArgs e)
    {
        // Haal het plaatje op
        string fileName = Server.MapPath("image/test.jpg");
        Image sourceImage = Image.FromFile( fileName );

        // Nu even de nieuwe afmetingen berekenen
        double percentage;
        if( !double.TryParse( Request.QueryString[ "percentage" ], out percentage ))
        {
            // Ergens ging iets fout, we nemen als default 50%
            percentage = 50; 
        }

        // Bereken de nieuwe waardes
        int imageHeight = (Int32)(sourceImage.Height * (percentage / 100));
        int imageWidth = (Int32)(sourceImage.Width * ( percentage / 100 ));

        // Stel de contenttype in als image/jpeg, zodat de aanroepende <img> 
        // tag er iets mee kan doen. 
        Response.ContentType = "image/jpeg";

        // Creeer de nutteloze callback
        Image.GetThumbnailImageAbort lCallBack = new Image.GetThumbnailImageAbort(this.ImageLoadAbort);

        // Maak de thumbnail aan
        Image thumbNail = sourceImage.GetThumbnailImage(imageWidth, 
            imageHeight, 
            lCallBack, 
            System.IntPtr.Zero);

        // en stream deze naar de response zodat hij in de <img> terecht komt
        thumbNail.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

        // That's all!
    }

    // Dit is de callback van de GetThumbnailImage. Wordt niet gebruikt, is toch nodig...
    private bool ImageLoadAbort(  )
    {
        return true;
    }

Je roept deze pagina aan door ergens in je pagina's de volgende regel op te nemen:

<img src="myImage.aspx?percentage=50" alt="Some image" />

En meer is er niet nodig!

Er zitten twee slimmigheidjes in deze code. Ten eerste maken we van de contenttype een ‘image/jpeg’. Dat houdt in dat de pagina dus geen standaard HTML teruggeeft, zoals gebruikelijk in een aspx, maar dat de inhoud een binair plaatje bevat.
Het tweede is het gebruik van de GetThumbnailImage method. Je geeft hem de afmetingen mee, je geeft hem het adres van een callback mee (die wordt niet gebruikt, maar is wel verplicht) en je geeft hem de data voor de callback mee (de callback wordt niet gebruikt dus de data kan IntPtr.Zero zijn). En dat plaatje streamen we naar de Response. Simpel, niet waar?
Het Framework blijft me verbazen….

Published Saturday, March 18, 2006 10:02 AM door dvroegop
Filed Under:

Comments

 

jeroenkok said:

Ben ook net achter deze methode gekomen. Wat mij opviel is dat deze sneller is dan vergelijkbare methodes van image processing componenten van derde partijen. Nog sneller is het uitlezen van de thumbnail uit de EXIF data van een jpeg afbeelding. In dat geval hoef je niet de gehele file uit te lezen.
March 18, 2006 6:00 PM
 

Daniel said:

Deze methode werkt wel, maar de kwaliteit is wel minder dan wanneer je zelf een functie maakt die een image resized. En dan kan natuurlijk ook online.

Je moet dan de ratio wel zelf bepalen maar je kan de 'InterpolationMode' wel zelf instellen en daarmee win je een hoop kwaliteit!

Maar je hebt gelijk: deze functie is handig en snel!
March 23, 2006 6:52 AM
 

Klaver4 said:

Hoi Dennis,

het valt me op dat je in je oplossing gebruik maakt van een .aspx pagina. Ik zou eigenlijk eerder verwachten dat je gebruik zou maken van een .ashx (simple handler) dan vermijd je de overhead van de hele page lifecycle, toch? Met behulp van zo'n simple handler wordt jouw oplossing iets als:

<!-- Bestand: MyImageHandler.ashx -->
<% WebHandler Language="C#" class="MyImageHandler" %>

using System;
using System.Web;

public class MyImageHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// Jouw code...
// Haal het plaatje op
string fileName = Server.MapPath("image/test.jpg");
Image sourceImage = Image.FromFile( fileName );

// Nu even de nieuwe afmetingen berekenen
double percentage;
if( !double.TryParse( Request.QueryString[ "percentage" ], out percentage ))
{
// Ergens ging iets fout, we nemen als default 50%
percentage = 50;
}

// Bereken de nieuwe waardes
int imageHeight = (Int32)(sourceImage.Height * (percentage / 100));
int imageWidth = (Int32)(sourceImage.Width * ( percentage / 100 ));

// Stel de contenttype in als image/jpeg, zodat de aanroepende <img>
// tag er iets mee kan doen.
Response.ContentType = "image/jpeg";

// Creeer de nutteloze callback
Image.GetThumbnailImageAbort lCallBack = new Image.GetThumbnailImageAbort(this.ImageLoadAbort);

// Maak de thumbnail aan
Image thumbNail = sourceImage.GetThumbnailImage(imageWidth,
imageHeight,
lCallBack,
System.IntPtr.Zero);

// en stream deze naar de response zodat hij in de <img> terecht komt
thumbNail.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);

// That's all!

}

public bool IsReusable
{
get
{
return false;
}
}

// Dit is de callback van de GetThumbnailImage. Wordt niet gebruikt, is toch nodig...
private bool ImageLoadAbort(  )
    {
        return true;
}
}
March 28, 2006 10:05 AM
 

Klaver4 said:

Oh ja, wel even Response vervangen door context.Response

Groeten
March 28, 2006 11:37 AM
 

dvroegop said:

Uhm.. oh ja :-) Niet bij stil gestaan. Die handlers, die wil ik nog wel eens vergeten. Goeie tip!

Maar het verhaal blijft staan: regelmatig gebeurt het dat ik code schrijf die al lang en breed in het framework zit.
March 31, 2006 7:17 PM
 

CHoogstede said:

Er is één klein issue met de gebruikte functie in deze code.
Er zijn afbeeldingen waar binnen het bestand een kleine versie van de afbeelding als thumbnail is opgeslagen. De functie GetThumbnailimage gebruikt die verkleinde afbeelding dan om zijn resultaat op te bouwen. Dus stel er is een jpg van 1024x768 met een embedded thumbnail van 102x77, dan krijg je als je getThumbnailimage gebruikt wellicht een file met de grootte die je wilt hebben, maar met erg slechte kwaliteit omdat de grote file niet wordt gedownscaled, maar de kleine thumbnail wordt geupscaled.

Een kleine truuk om dit te voorkomen is om de afbeelding voor het opvragen van de thumbnail 2x 180 graden te draaien.
January 25, 2008 2:24 PM
Anonymous comments are disabled

About dvroegop

Programmeert al sinds 1982. Microsoft Surface MVP.
Powered by Community Server, by Telligent Systems