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); }
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.
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.
Ihan ok esimerkki, ainakin tuosta selviää miten homma hoituu. Mutta optimoinnin varaa tuossa olisi kyllä erittäin paljon. Nuo assembly "optimoinnit" vaikuttavat melko turhilta.
(Off-topic:) En ole vieläkään tajunnut, miksi SDL:n käyttäjiä oletetaan tekemään omat put/getpixelit, linet, rectfillit jne.
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.
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.
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ä.
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.
tuon faden saa kyllä toteutettua raudallakin...
ei toimi
Aihe on jo aika vanha, joten et voi enää vastata siihen.