Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Assembly, C++: Kuva toiseksi kuvaksi, ristiinhäivytys, crossfade

Sivun loppuun

peki [09.08.2004 19:16:10]

#

Vanha kunnon kuva toiseksi kuvaksi fade toteutettuna SDL:llä. Vääntelin tämän ensin VB.NETille, kyllästyin hitauteen ja porttasin C++:lle.

Algoritmi, jota käytän perustuu lineaariseen interpolaatioon. a * (1-x) + b * x eli toisin sanoen "painotettuun keskiarvoon". Muutin tässä kaavan kuitenkin käyttämään kokonaislukua väliltä 0-100, jotta assembleriksi muuttaminen olisi helpompaa, eikä tarvitsisi käyttää liukuluku komentoja. Pikseli siis interpoloidaan toiseen pikseliin käyttäen "blender" muuttujaa painottajana. Muutettuna siis näin:
a * (100-x) / 100 + b * x / 100.

Kyseistä kaavaa voi soveltaa hyvin moneen tarkoitukseen. Sillä voi piirtää erittäin helposti viivoja, tehdä väriliukuja ja monia muita graafisia juttuja.

Valmis ohjelma löytyy täältä: http://koti.mbnet.fi/peku1/ImageFade.zip. Tarvitset vielä kaksi bmp formaatissa olevaa kuvaa samaan kansioon. Niiden on oltava nimetty "kuva1.bmp" ja "kuva2.bmp". Kuvien koolla ei ole muita rajoituksia, kuin: kuva2 on oltava suurempi tai yhtäsuuri, kuin kuva1. Järjen käyttö kuvien koon kanssa on kuitenkin sallittua. ;)

Jos ohjelma tuntuu hitaalta, siirtele ikkunaa ympäriinsä hetken aikaa. Jostain syystä SDL nopeutuu silloin huomattavasti ainakin itselläni.

#include <stdlib.h>
#include <memory.h>
#include "SDL.h"
#include <math.h>

enum {
  SCREENWIDTH = 0,
  SCREENHEIGHT = 0,
  SCREENBPP = 32,
  SCREENFLAGS = SDL_ANYFORMAT
};

int blender = 0;
int suunta = 1;

SDL_Surface *kuva1, *kuva2;

 // Palauttaa pikselin (x,y) arvon
 // Pinnan täytyy olla lukittuna tätä tehdessä!
 // "_fastcall" käskee kääntäjää välittämään parametrit rekistereissä. Kutsuminen on siis nopeampaa.
Uint32 _fastcall getpixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    // p on osoitin pikseliin, jonka haluamme kopioida
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;       // ei pitäisi tapahtua.
    }
}

 // Aseta pikseli (x, y) annettuun arvoon
 // Pinnan täytyy olla lukittuna tätä tehdessä!
void _fastcall putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
{
    int bpp = surface->format->BytesPerPixel;
    // p on osoitin pikseliin, jonka haluamme asettaa
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        *p = pixel;
        break;

    case 2:
        *(Uint16 *)p = pixel;
        break;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
            p[0] = (pixel >> 16) & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = pixel & 0xff;
        } else {
            p[0] = pixel & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = (pixel >> 16) & 0xff;
        }
        break;

    case 4:
        *(Uint32 *)p = pixel;
        break;
    }
}

void DrawScene(SDL_Surface* surface)
{
    // Lukitse pinta, jotta voimme käsitellä pikseleitä suoraan
    if ( SDL_MUSTLOCK(surface) ) {
        if ( SDL_LockSurface(surface) < 0 ) {
            fprintf(stderr, "Can't lock the screen: %s\n", SDL_GetError());
            return;
        }
    }

    // muuttujat väreille ja niiden komponenteille
    Uint32 color, color2;
    int R,G,B,R2,G2,B2;

    // käännetään faden suunta, jos täytyy
    if(blender > 100 - suunta)
        suunta = -1;
    else if(blender < 0 - suunta)
        suunta = 1;

    blender += suunta;

    // käydään läpi kuvan pikselit
    for(int x=0;x<kuva1->w;x++)
        for(int y=0;y<kuva1->h;y++)
        {
            // Napataan värit
            color = getpixel(kuva1, x,y);
            color2 = getpixel(kuva2, x,y);


            // Muutin alkuperäisen koodini assembleriksi, jotta värien hajoittaminen ja interpolaation laskeminen olisi nopeampaa

            //R = (color >> 16) & 0xff;
            //R2 = (color2 >> 16) & 0xff;
            __asm
            {
                mov eax, color    ; siirretään eax -rekisteriin väri
                shr eax, 16        ; shiftataan eaxia 16 bittiä oikealle
                and eax, 0xff    ; andataan 0xff (255)
                mov R, eax        ; siirrä R:ään eax

                mov eax, color2    ; sama värille 2
                shr eax, 16
                and eax, 0xff
                mov R2, eax
            }
            //G = (color >> 8) & 0xff;
            //G2 = (color2 >> 8) & 0xff;
            __asm
            {
                mov eax, color
                shr eax, 8
                and eax, 0xff
                mov G, eax
                mov eax, color2
                shr eax, 8
                and eax, 0xff
                mov G2, eax
            }
            //B = color & 0xff;
            //B2 = color2 & 0xff;
            __asm
            {
                mov eax, color
                and eax, 0xff
                mov B, eax
                mov eax, color2
                and eax, 0xff
                mov B2, eax
            }

            // interpoloidaan pikseli kohti toista pikseliä
            // käytetään lineaarista interpolaatiota: a * (1-x) + b * x

            //R = R * (100-blender) / 100 + R2 * blender / 100;
            _asm
            {
                mov ecx, 100        ; säilyttää numeroa 100

                mov eax, 100        ; siirrä eaxiin 100
                sub eax, blender    ; (100-blender)
                mul R                ; R * (100-blender)
                div ecx                ; R * (100-blender) / 100 (ei voi jakaa suoraan numerolla 100)
                mov ebx, eax        ; muistiin eax
                mov eax, R2            ; siirrä R2 eaxiin
                mul blender            ; R2 * blender
                div ecx                ; R2 * blender / 100
                add eax, ebx        ; lisää ebx eaxiin
                mov R, eax            ; siirrä R väriksi eax
            }
            //G = G * (100-blender) / 100 + G2 * ( blender) / 100;
            _asm
            {
                mov eax, 100        ; siirrä eaxiin 100
                sub eax, blender    ; (100-blender)
                mul G                ; G * (100-blender)
                div ecx                ; G * (100-blender) / 100 (ei voi jakaa suoraan numerolla 100)
                mov ebx, eax        ; muistiin eax
                mov eax, G2            ; siirrä G2 eaxiin
                mul blender            ; G2 * blender
                div ecx                ; G2 * blender / 100
                add eax, ebx        ; lisää ebx eaxiin
                mov G, eax            ; siirrä G väriksi eax
            }
            //B = B * (100-blender) / 100 + B2 * ( blender) / 100;
            _asm
            {
                mov eax, 100        ; siirrä eaxiin 100
                sub eax, blender    ; (100-blender)
                mul B                ; B * (100-blender)
                div ecx                ; B * (100-blender) / 100 (ei voi jakaa suoraan numerolla 100)
                mov ebx, eax        ; muistiin eax
                mov eax, B2            ; siirrä B2 eaxiin
                mul blender            ; B2 * blender
                div ecx                ; B2 * blender / 100
                add eax, ebx        ; lisää ebx eaxiin
                mov B, eax            ; siirrä B väriksi eax
            }
            putpixel(surface, x, y, SDL_MapRGB(surface->format, R, G, B));
        }

    // poistetaan lukitus
    if ( SDL_MUSTLOCK(surface) ) {
        SDL_UnlockSurface(surface);
    }

    //päivitä pinta
    SDL_UpdateRect(surface, 0, 0, 0, 0);
}

int main(int argc, char* argv[])
{
  //Alusta SDL
  SDL_Init(SDL_INIT_VIDEO);

  //Aseta exit funktio
  atexit(SDL_Quit);

  // lataa bittikartat
  kuva1 = SDL_LoadBMP("kuva1.bmp");
  kuva2 = SDL_LoadBMP("kuva2.bmp");

  //luo ikkuna
  SDL_Surface* pSurface = SDL_SetVideoMode ( kuva1->w, kuva1->h, SCREENBPP, SCREENFLAGS );

  SDL_Event event;
  //Tapahtumakäsittelijä
  for (;;)
  {
    //Katso, jos tapahtuma
    if ( SDL_PollEvent ( &event ) )
    {
      //Sellainen löytyi -> tsekkaa, onko quit
      if ( event.type == SDL_QUIT ) break;
    }
    // piirrä
    DrawScene(pSurface);
  }

  // vapautellaan
  SDL_FreeSurface(kuva1);
  SDL_FreeSurface(kuva2);
  SDL_FreeSurface(pSurface);

  return(0);
}

Metabolix [11.08.2004 15:05:12]

#

Tuota pikselien yhdistystä voi vielä optimoida:
B = B * (100 - blender) / 100 + B2 * (blender) / 100;
=>
B = (B * 100 - (B - B2) * blender) / 100;

Assemblynä siis:

_asm
{
	mov ecx, 100;
	mov edx, B;
	push edx; Jako nollaa edx:n, otetaan talteen

	mov eax, edx;
	mul ecx;
	mov ebx, eax;
	pop edx;

	mov eax, edx;
	sub eax, B2; tähän vielä tarkistus, ettei luku käänny ympäri.
	mul blender;

	sub ebx, eax;
	mov eax, ebx;

	div ecx;
	mov B, eax;
}

Testasin molemmat assynpätkät ja C-koodin;
Asetin testiohjelman prioriteetiksi "Reaaliaikainen"
for (X = 0; X < 4294967295; X++) {Lasku}
Tulos oli seuraava:
Alkuperäinen: n. 210 sekuntia (= 3 min 30 sek)
Minun versioni: n. 130 sekuntia (= 2 min 10 sek)
C-koodini: n. 110 sekuntia (= 1 min 50 sek)

Ainut huono puoli Assemblyssäni on, että koska en oikein osaa Assemblyä, tuo ei toimi jos B > B2, koska luku kääntyy ympäri (siis -1 = 4294967295), mutta tarkistuksen lisääminen tuskin hidastaisi juurikaan; jakolasku on hitain toimitus.

MOT: Assy ei tänään kannattanutkaan; pelkkä C on nopeampi.

peki [11.08.2004 17:59:27]

#

En noita nopeuksia testaillut itse erikseen, sillä tutkin ms vc++:n tekemää koodia ja huomasin sen käyttäneen suoraan muistia, eikä tehneen laskutoimituksia rekistereissä.
Käänsin siis koodin käyttämään rekistereitä.
Oletin heti, että suora rekisterilaskenta olisi erittäin paljon tehokkaampaa. Olin näköjään väärässä.
Jääköön siis koodivinkin soveltajan kontolle käyttää mitä tahansa näistä kolmesta versiosta.
Aikojen ja selkeyden perusteella kääntyisin itse luonnollisesti Metabolixin optimoidun kaavan puoleen. :P

Kiitos huomautuksesta joka tapauksessa.

thefox [12.08.2004 12:07:30]

#

Ihan ok esimerkki, ainakin tuosta selviää miten homma hoituu. Mutta optimoinnin varaa tuossa olisi kyllä erittäin paljon. Nuo assembly "optimoinnit" vaikuttavat melko turhilta.

akx [12.08.2004 12:09:15]

#

(Off-topic:) En ole vieläkään tajunnut, miksi SDL:n käyttäjiä oletetaan tekemään omat put/getpixelit, linet, rectfillit jne.

thefox [12.08.2004 12:13:06]

#

Minusta on ihan hyvä että SDL on pyritty pitämään suhteellisen yksinkertaisena. Noita put- ja getpixeleitä sun muita linejä löytyy kyllä muista kirjastoista *jos* niitä haluaa käyttää. Minusta tämä on parempi ajattelutapa kuin kaikkea-kaikille.

peki [12.08.2004 16:21:12]

#

Nämä eri funktiot on myös hauska toteuttaa itse.
Saa täsmälleen sellaisen, kuin itse haluaa ja voi optimoida sen juuri omaan ohjelmaansa sopivaksi.

Spirits [12.08.2004 20:55:36]

#

lainaus:

MOT: Assy ei tänään kannattanutkaan; pelkkä C on nopeampi.

Tai sitten ei. Kun vähän vielä lisää optimoi tuota koodinpätkää niin siihen saadaan vielä lisää nopeutta. Hyvä optimointi on muuttaa laskut käyttämään fixed-point aritmetiikkaa. Tässä tapauksessa siten että desimaali osalle 12 bittiä ja 20 bittiä kokonaisluvulle. Näin kertolaskun takia. Kertolaskussahan bittien määrä kaksinkertaistuu joten tulos on 24 bittiä desimaaliosalle ja ylimmät 8 kokonaisluvulle.

Tuon fade laskun voi muuttaa seuraavaan muotoon.

R=R1*blend1+R2*blend2;
G=G1*blend1+G2*blend2;
B=B1*blend1+B2*blend2;

missä

blend1=(int)((float)(100-blender)*40.96);
blend2=(int)((float)blender*40.96);

ja R1=color:n R komponentti muodossa 0xRR000 eli alimmat 12 bittiä nollia ja bitit 12-19 sisältävät R:n. Näin samoin kaikille muille väreille.

Tämä kaikki koodina:

unsigned int blend1,blend2;

blend1=(int)((float)(100-blender)*40.96);
blend2=(int)((float)blender*40.96);

jonnekin ennen tuota luuppia. Ja itse luuppi:

__asm {
	//Red
	mov eax,color
	mov ebx,color2

	shr eax,4			;shiftataan R:t oikelle kohdille
	shr ebx,4

	and eax,0xff000		;nollataan alimmat 12 bittiä
	and ebx,0xff000

	imul eax,blend1
	imul ebx,blend2		;fade lasku

	add eax,ebx
	shr eax,24			;shiftataan oikealle kohdalle

	mov R,eax

	//Green
	mov eax,color
	mov ebx,color2

	shl eax,4
	shl ebx,4

	and eax,0xff000
	and ebx,0xff000

	imul eax,blend1
	imul ebx,blend2

	add eax,ebx
	shr eax,24

	mov G,eax

	//Blue
	mov eax,color
	mov ebx,color2

	shl eax,12
	shl ebx,12

	and eax,0xff000
	and ebx,0xff000

	imul eax,blend1
	imul ebx,blend2

	add eax,ebx
	shr eax,24

	mov B,eax
	}

Toistettaessa tuo luuppi 67108863(0x3ffffff) kertaa aikaa kului seuraavasti;

Alkuperäinen: 65.109001 sek
Yl.olev. ASM: 4.609000 sek
Yl.olev.C:nä: 8.391000 sek

Itse ohjelma ei hirvittävästi nopeudu, koska tuossa luupissa käytetään noita putpixel,getpixel-kutsuja. Tuota voisi nopeuttaa muuttamalla nuo kutsut siten että voisi suoraan lukea/kirjoittaa piirtopuskuriin. Toisaalta tuon luupin voisi vielä kirjoittaa MMX-ASM:lla tai sitten vielä keksiä lisää tapoja nopeuttaa muuten sitä.

Metabolix [15.08.2004 22:14:59]

#

Nytpä tiedän, miksi C-koodi oli parempi: Kääntäjä osaa käyttää matikkaprossua erikseen, jolloin laskut ovat aavistuksen nopeampia.
Tuosta saisi vielä optimoitua kun tekisi kaiken asmilla.

kArpo [16.08.2004 09:07:05]

#

tuon faden saa kyllä toteutettua raudallakin...

apsu [06.11.2009 08:38:52]

#

ei toimi


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta