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.