Kirjautuminen

Haku

Tehtävät

Opasarkisto: SDL 1.2: Osa 2 - Grafiikka

  1. Osa 1 - Perusteet
  2. Osa 2 - Grafiikka
  3. Osa 3 - Syötteet
  4. Osa 4 - Lisäkirjastot

Kirjoittaja: Heikki (2004).

⚠ Huomio! Tämä opas on vanhentunut. Oppaan sisältöön ei voi enää luottaa. Opas on säilytetty vain sen historiallisen arvon vuoksi. ⚠


Opassarjan ensimmäisessä osassa emme tehneet juuri mitään, mutta nyt alamme jo pääsemään asiaan! Tässä osassa käymme lävitse grafiikkaan liittyvät toimenpiteet.

Bittikartat

Bittikartat ovat pakkaamattomia kuvatiedostoja (pääte .bmp). Bittikarttoja varten SDL:ässä on valmis latausfunktio, SDL_LoadBMP, joka lataa kuvatiedoston kuvapinnalle joka on tyyppiä SDL_Surface. Tähän mennessä olet nähnyt SDL_Surface:n ensimmäisen osan esimerkkikoodissa, jossa kuvapinnalle laitetaan näytön sisältö:

SDL_Surface *naytto;
naytto = SDL_SetVideoMode(1024, 768, 32, SDL_HWSURFACE|SDL_FULLSCREEN); //nyt naytto sisältää näytöllä olevan kuvan

Mutta kuten jo sanoin, kuvapinnoilla voi olla muutakin kuin näytön sisältö. Tässä tapauksessa tallennamme siihen bittikartan sisällön. Eli tutustutaanpas nyt SDL_LoadBMP-funktioon:

SDL_Surface *kuva;
kuva = SDL_LoadBMP("kuvatiedosto.bmp");

Ja katsos, tämähän on yksinkertaista! Ainoa huomionarvoisa asia on se, että meidän on käytettävä kuvapinnan osoitinta. Jos siis korvaisit rivin SDL_Surface *kuva; rivillä SDL_Surface kuva; koodi ei toimisi.

SDL_LoadBMP() varaa tarvittavan muistin, johon SDL_Surface-tyyppinen kuva-osoitin osoittaa. Tämä muisti pitää vapauttaa jahka kuvapintaa ei enää tarvita, viimeistään ohjelman lopussa. Vapautus hoituu funktiolla SDL_FreeSurface:

SDL_FreeSurface(kuva);

Mutta entäs sitten? Se, että meillä on kuva muistissa, ei paljon lohduta. Joten nyt täytyy piirtää se näytölle! Ja mitenkäs se sitten tapahtuu? No, lue pari riviä alaspäin niin opit!

Piirtelyä

SDL:llä on mahdollista käsitellä ruutua kahdella tavalla: kaksoispuskuroinnilla ja ilman. Se, käytätkö kaksoispuskurointia, vaikuttaa piirtotapaan. Kaksoispuskurointia käytetään yleensä peliohjelmoinnissa, ja sen avulla ehkäistään välkkymistä, ja piirtelykin sujuu nopeasti.

Alustettaessa näyttöä SDL_SetVideoMode-funktiolla kerrotaan, käytetäänkö kaksoispuskurointia vai ei. Kaksoispuskurointi otetaan käyttöön lipulla SDL_DOUBLEBUF. Ilman mainittua lippua kaksoispuskurointia ei käytetä. Tässä esimerkkinä ikkunan luonti sekä kaksoispuskuroinnilla että ilman:

SDL_Surface *naytto;

// ei kaksoispuskurointia:
naytto = SDL_SetVideoMode(1024, 768, 32, SDL_HWSURFACE|SDL_FULLSCREEN);

// kaksoispuskurointi
naytto = SDL_SetVideoMode(1024, 768, 32, SDL_HWSURFACE|SDL_FULLSCREEN|SDL_DOUBLEBUF);

Kaikkien lippujen selitykset löytyvät oppaan ensimmäisestä osasta.

Nyt meillä on sitten kaksoispuskurointi päällä tai poissa. On aika piirtää kuvapinnalle kuva, joten tehdäänpä funktio joka piirtää ruudulle kuvan.

// funktio piirtää parametrina annetun kuvan ruudulle (toinen parametri) tiettyyn kohtaan (3. ja 4. parametri)
void PiirraKuva(SDL_Surface *kuva, SDL_Surface *naytto, int x, int y)
{
  SDL_Rect alue; // mille alueellä näyttöä kuva piirretään
  alue.x = x; // koordinaatit
  alue.y = y;
  SDL_BlitSurface(kuva, NULL, naytto, &alue); // piirto
}

Tätä funktiota voitasiin kutsua esimerkiksi näin:

SDL_Surface *naytto;
SDL_Surface *kuva;

naytto = SDL_SetVideoMode(1024, 768, 30, SDL_HWSURFACE|SDL_DOUBLEBUF);

kuva = SDL_LoadBMP("kuva.bmp");

// ruudulle:
PiirraKuva(kuva, naytto, 50, 50);

// kuvan vapautus:
SDL_FreeSurface(kuva);

Tämä piirtäisi bittikartan kuva.bmp sisällön ruudulle kohtaan 50,50. Mutta kun käännät tämän koodin, huomaat yhden asian: ruutu pysyy tyhjänä! Bugi? Surkea kirjasto? Ei kumpikaan, sinulta puuttuu vielä yksi tärkeä komento: SDL_Flip(naytto); joka ns. flippaa kuvapinnat (tästä lisää kohta). Kokeileppa kirjoittaa tuo komento funktiokutsun jälkeen ja kääntää koodi. Ja tadaa... kaikki toimii! Kuva näkyy! Jos taas et käytä kaksoispuskurointia, on käytettävä toista funktiota, joka esitellään myöhemmin tässä oppaassa.

Kannattaa laittaa koodin loppuun funktiokutsu SDL_Delay(5000) joka odottaa tietyn ajan, esim. 5000 ms (5 sekuntia). Muuten ruutu vain vilahtaa näkyvissä.

Miksi tämä sitten tarvitaan? Selostan tähän väliin hieman kaksoispuskuroinnin teoriaa: Kaksoispuskuroinnissa on nimen mukaisesti kaksi kuvapintaa (ns. framea): toinen on näytön sisältö, ja toisessa on seuraava piirrettävä kuva, johon kaikki piirto-operaatiot kohdistetaan. Kun kuva on piirretty, se on ainoastaan näkymättömällä kuvapinnalla. Se saadaan näkyviin vaihtamalla näkyvää kuvapintaa (ns. flippaus). SDL:ssä tämä hoituu SDL_Flip-funktiolla. Eikö tämä sitten hidasta ohjelmaa? Ei, sillä funktio ainoastaan vaihtaa näytöllä näkyvän ja näkymättömän kuvapinnan osoitteita muistissa. Ja tadaa, seuraavan kerran kun näytönohjain piirtää jotain, tulee ruudulle uusi kuva. Nyt kuva ei välky, sillä kaikki piirretään ruudulle "kerralla".

Entä sitten ilman kaksoispuskurointia? Sekin on mahdollista, ja joissain harvoissa tapauksissa jopa hyödyllistä. Jos muutokset koskevat vain tiettyä osaa ruudusta, saadaan yksoispuskuroinnilla jopa jonkinlaista nopeusetua, kun sillä voidaan tehdä piirron jälkeen kuvapinnan "päivitys" vain tiettyyn alueeseen ruudusta. Tässä yksinkertainen esimerkki joka selostaa asian:

SDL_Surface *kuva;
kuva = SDL_LoadBMP("kuva.bmp");
int x = 1, y = 5;
SDL_Rect alue; // alue mihin kuva piirretään näytöllä
alue.x = x;
alue.y = y;

if (SDL_BlitSurface( kuva, NULL, naytto, &alue ) < 0) { // huomaa sama piirtofunktio kuin käytettäessä kaksoispuskurointia
	printf("Virhe %s\n", SDL_GetError());
}
SDL_FreeSurface(kuva);

// sitten tärkein ero kaksoispuskurointiin: näytön päivitys; funktio päivittää muuttuneen alueen.
SDL_UpdateRect( naytto, x, y, kuva->w, kuva->h );

SDL_Delay(5000); // odotellaan

Käyttämämme SDL_UpdateRect päivittää muuttuneen alueen. Parametrina annetaan päivitettävä kuvapinta, päivitysalueen vasemman yläkulman koordinaatit sekä alueen leveys ja korkeus.

Tarkempaa piirtämistä

Nyt käyttämämme funktiot piirtävät aina koko bittikartan. Aina tähän ei ole syytä, esimerkiksi animoinnissa on kätevää näyttää vain tietty osa kuvasta. Saatat jo keksiä miten tämä toteutetaan. Meidän tulee vain hieman muokata SDL_BlitSurface:lle parametrina antamaamme SDL_Rect-aluetta. Tässä sitten piirtofunktio, joka piirtää ruudulle tietyn alueen kuvasta:

void PiirraKuva(SDL_Surface *kuva, SDL_Surface *naytto, int kuvax, int kuvay, int leveys, int korkeus, int nayttox, int nayttoy)
{
	SDL_Rect kuvaalue; // alue, mikä kuvasta piirretään
	kuvaalue.x = kuvax;
	kuvaalue.y = kuvay;
	kuvaalue.h = korkeus;
	kuvaalue.w = leveys;

	SDL_Rect nayttoalue; // alue näytöllä, jolle kuva piirretään
	nayttoalue.x = nayttox;
	nayttoalue.y = nayttoy;

	SDL_BlitSurface(kuva, &kuvaalue, naytto, &nayttoalue);
}

Jo aiemminkin käyttämämme SDL_BlitSurface ottaa siis parametrina lähdekuvapinnan osoittimen, osoittimen aluetietueeseen jonka alueelta piirretään, kohdekuvapinnan osoittimen ja osoittimen alueeseen, jolle kuva piirretään. Kotiläksynä voitkin kokeilla kutsua tätä funktiota erilaisilla arvoilla ja katsella mitä tapahtuu. Voit myös kokeilla, mitä tapahtuu jos määrittelet nayttoalue:elle h:n ja w:n.

Jos joku ei vielä hoksannut, niin SDL_Rect-tietotyypin alueella on neljä kokonaislukuarvoa: x-koordinaatti (x), y-koordinaatti (y), korkeus (h) ja leveys (w).

Läpinäkyvyys

Äskeinen bittikartan piirto jättää meille erään ongelman: piirrämme jokaisen pikselin bittikartan tietyltä alueelta. Jos esimerkiksi piirrät pelaajahahmon, sen ympärille jää taustaväriä, joka pitää saada pois näkyvistä. Tämä onnistuu asettamalla tietty väri läpinäkyväksi:

SDL_SetColorKey(kuva, SDL_SRCCOLORKEY, SDL_MapRGB(kuva->format, 0,0,0));

Funktiolle annetaan parametrina osoitin pintaan, josta tietty väri asetetaan läpinäkyväksi. Tämän jälkeen annetaan ohjausliput. Viimeinen parametri kertoo, mikä väri asetetaan läpinäkyväksi. Tässä tapauksessa se on RGB:nä 0,0,0 eli musta.

Viimeisin muokkaus 3.5.2006


Kommentit

Juice [13.12.2004 21:10:49]

#

Hieno ja perusteellinen opas, mutta löysin yhden typon:

P*ska kijasto?

Heikki [22.12.2004 10:43:34]

#

Korjattu.

TeeVee [31.12.2004 09:42:23]

#

Kun tuossa on läpinäkyvyydestä, en ymmärrä mihin kohtaan ohjelmassa tuo SDL_SetColorKey tulisi laittaa?
Kokeiltu on jo kohtuullisen paljon ja hermot menee kun mikään ei taas(kaan) suostu toimimaan :)

Heikki [02.01.2005 14:17:15]

#

Ennen SDL_BlitSurface()-funktiota.

Metabolix [13.01.2005 07:38:46]

#

Minäkin löysin typon, peräti koodista:
kuvaalue.x = kvuax;

Heikki [30.01.2005 11:59:38]

#

Korjattu.

ZcMander [18.02.2005 21:39:33]

#

Mistä tuon SDL_gfx.dll saa ku sitä ei ollu paketissa? Eikä myöskään siinä omatekemässä, tai siis Heikinhän se on...

PFe2005 [28.02.2005 20:01:55]

#

En ymmärrä miten tuota kuvaa saa näkymään.

Heikki [03.03.2005 00:32:54]

#

Jos käytät kaksoispuskurointia, muistathan käyttää SDL_Flip(naytto)-funktiota?

Megant92 [09.03.2005 17:09:14]

#

Aivan yskinkertaista.

hunajavohveli [15.03.2005 22:36:26]

#

Onko värisyvyys varmasti tarkoitus määritellä 30-bittiseksi noilla parilla rivillä? Ettei kuitenkin 32-bittiseksi, kuten ensimmäisessä tapauksessa? Hyvä opas joka tapauksessa. Sain kaiken toimimaan juuri niin kuin tuossa neuvotaan.

Heikki [08.04.2005 23:46:33]

#

Tosiaan 32-bittinenhän sen pitäisi olla. Typoja tullut :(

TeeVee [09.04.2005 23:16:32]

#

___ yskinkertainen esimerkki____

Heikki [15.04.2005 22:46:34]

#

Korjattu, kylläpäs sinä nyt etsit niitä typoja.

aloitteleva [26.06.2005 23:24:48]

#

Mitä kaikkia tän tapasia tarvitaan?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL.h"

ku nois se heittää niin miljoona errorii

Heikki [03.07.2005 01:58:28]

#

Alussa on C:n standardikirjastoja, sitten string.h on C++:n standardi-merkkijonokirjasto ja SDL.h sisältää SDL:n funktioiden esittelyt.

Esimerkiksi stdio.h:ta tarvitaan muistaakseni esim. printf()-funktioon (en ole C-koodari). Jotta voisit käyttää SDL:ää, täytyy tuo SDL.h sisällyttää, tosin useammin toimii #include <SDL.h> tai #include <SDL/SDL.h>

Juan [14.08.2005 13:12:22]

#

SDL_BlitSurface(kuva, &kuvaalue, naytto, &nayttoalue);
SDL_BlitSurface ottaa siis parametrina lähdekuvapinnan osoittimen, viittauksen aluetietueeseen jonka alueelta piirretään, kohdekuvapinnan osoittimen ja viittauksen alueeseen, jolle kuva piirretään

eikös sille kuitenkin anneta viittauksien sijasta osoittimet..

SDL_Rect kuvaalue; // alue, mikä kuvasta piirretään
SDL_Rect * osoitin = &kuvaalue;

C++Amatööri [16.08.2005 18:18:46]

#

voiko gif kuvia ladata näytölle

aWW [01.09.2005 15:41:51]

#

Voi, jos käytät lisäkirjastoa SDL_image

Heikki [09.09.2005 09:54:18]

#

Juhan: Noinhan se tietysti menee, korjattu :)

Vilikki [27.12.2005 15:48:07]

#

Yhden typoillun kohdan löysin minäkin, "Meidän tulee vain hieman mukata". Ilmeisesti muokkauksesta kuitenkin kyse :)

Mainio opas kuitenkin kokonaisuudessaan.

Heikki [28.12.2005 10:37:29]

#

Korjattu nyt tämäkin typo. Seuraavan kerran kun kirjoitan oppaan, lupaan käyttää oikolukua...

TeeVee [14.01.2006 22:11:13]

#

heikki kirjoitti:

Korjattu, kylläpäs sinä nyt etsit niitä typoja.

Eikö se kerro siitä, että lueskelen paljon opasta :) Kyllä niitä aina putkahtaa. Kun opasta lukee paljon niin ne tarttuu silmiin ;))

osku91 [14.03.2006 17:55:00]

#

Yritin tehdä jutun joka piirtää kuvan ohjelmaan mutta se ei toimi. Mikä on vikana?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL.h"
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
SDL_Surface *screen = NULL;

void piirrakuva(SDL_Surface *ukko, SDL_Surface *naytto, int x, int y)
{
     SDL_Rect alue;
     alue.x = x;
     alue.y = y;
     SDL_BlitSurface(ukko, NULL, naytto, &alue);
}

int main(int argc, char *argv[]) {
    if( SDL_Init(SDL_INIT_VIDEO) < 0 )
    {
        fprintf(stderr, "SDL:n alustus ei onnistunut: %s\n", SDL_GetError());
        return 0;
    }
    SDL_Surface * naytto;
    naytto = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 16, SDL_HWSURFACE);

    SDL_Surface * ukko;
    ukko=SDL_LoadBMP("kuva1.bmp");
    piirrakuva(ukko, naytto, 100, 100);

    SDL_Delay(5000);
    SDL_Quit();
    return 0;
}

Heikki [14.03.2006 20:08:04]

#

Auttaisi huomattavasti jos kertoisit miten ei toimi, esim. meneekö kääntäjästä läpi tms.

No, nyt kun tuota katsoin niin missään et päivitä näyttöä. Et näemmä käytä kaksoispuskurointia, joten sinun on käytettävä SDL_UpdateRect()-funktiota (esitelty tässä osassa). Tai sitten voit ottaa käyttöön kaksoispuskuroinnin (SDL_DOUBLEBUF ikkunan asetuksiin) ja piirron jälkeen kutsut funktiota SDL_Flip(naytto)

osku91 [14.03.2006 21:21:55]

#

Vikana oli että kuva ei tullut näkyviin, mutta nyt sain sen toimimaan laittamalla SDL_DOUBLEBUF ja SDL_Flip(naytto).

regu [16.10.2006 08:15:08]

#

Hyvä opas. Mulla oli vain vaikeuksia alussa ymmärtää oikea kohta tuolle SDL_Flip-funktiolle :)

SirSami [04.08.2007 18:30:18]

#

Toimiiko tuo PiirräKuva funktio vaikka pinta: kuva korvattaisiin jollain muulla nimellä?

Mega-Ohjelmoija [21.08.2007 08:16:07]

#

Jep,erittäin yksinkertaista.

Zakki [25.11.2007 18:38:30]

#

mul ei ainakaan toimi, vaik laitan ton flipin

Viitapiru [10.08.2008 19:30:43]

#

Mikähän mahtaa olla vialla kun ohjelma kaatuu heti käynnistyksen yhteydessä?

Tässä koko koodi:

#include <SDL/SDL.h>

int main(int argc, char *argv[])
{
void PiirraKuva(SDL_Surface *kuva, SDL_Surface *naytto, int x, int y);


SDL_Surface *naytto;
SDL_Surface *kuva;

naytto = SDL_SetVideoMode(1024, 768, 30, SDL_HWSURFACE|SDL_DOUBLEBUF);

kuva = SDL_LoadBMP("kuva.bmp");

PiirraKuva(kuva, naytto, 50, 50);
SDL_Flip(naytto);

SDL_Delay(5000);

return 0;
}

void PiirraKuva(SDL_Surface *kuva, SDL_Surface *naytto, int x, int y)
{
     SDL_Rect alue;
     alue.x = x;
     alue.y = y;
     SDL_BlitSurface(kuva, NULL, naytto, &alue);
}

EDIT: Ongelma selvisi. En ollut alustanut SDL:ää.

ankzilla [25.09.2009 20:52:34]

#

Typo report:

  SDL_Rect alue; // mille alueellä näyttöä kuva piirretään

TheEsko [27.09.2009 11:50:53]

#

Mulla Windows XP havaitsee seuraavassa ohjelmassa virheen, ja sulkee ohjelman.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL/SDL.h>
#include <windows.h>

SDL_Surface *screen = NULL;



//kuvan piirtäminen
void PiirraKuva(SDL_Surface *kuva, SDL_Surface *naytto, int x, int y)
{
  SDL_Rect alue;
  alue.x = x;
  alue.y = y;
  SDL_BlitSurface(kuva, NULL, naytto, &alue);
  SDL_Flip(naytto);
}

//pääohjelma
int
main (int argc, char *argv[])
{

SDL_Surface *naytto;
SDL_Surface *kuva;

naytto = SDL_SetVideoMode(200, 200, 30, SDL_HWSURFACE|SDL_DOUBLEBUF);

kuva=SDL_LoadBMP("kuvatiedosto.bmp");

// ruudulle:
PiirraKuva(kuva, naytto, 10, 10);


    return 0;
}

Weggo [28.09.2010 18:54:51]

#

huomasin tämmösen kivan pikku bugin, eli jos sulla on 7 (en tiiä vistasta) ni pelkkä SLD_LoadBMP("kuvatiedosto.bmp") ei riitä, tulee null surface error. Vähän aikaa kun googlasin löysin ongelmaan ratkasun -> eli sun pitää kirjottaa SLD_LoadBMP("/kuvatiedosto.bmp") että kuvaa tulee esille. Tämmönen pieni bugi/ w/e on päässy ilmestymään

Jori [14.06.2015 19:28:44]

#

Tuo TheEskon ohjelma toimii, kun poistaa #include <windows.h> ja laittaa lisää aikaa eli laitoin SDL_Delay(5000);
Toimii ainakin linuxissa.

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta