Skrive og hente en pixel fra skærmRAM i mode 12h (640x480x16)
Jeg har længe prøvet at finde ud af at skrive og læse pixels fra Skærmrammen i mode 12h, men jeg kan ikke finde ud af det, jeg har fundet forskellig kode rundt om på internettet som virker, men problemet er at det ikke er særligt godt dokumenteret og at det hele er skrevet i inline assembler (som jeg ikke forstår).
Er der en som kunne tænke sig at lave et putpixel og getpixel eksempel i enten PASCAL eller C/C++ med en hel masse kommentare?
Kommentarene gør ikke så meget hvis det hele er skrevet uden brug af assembler.
Lyder det sådan???? Nej, men jeg kan da se om jeg kan finde eksemplet derhjemme.
Men ellers kan det være, at nogle af de hårde fyre, som stikker næsen forbi engang imellem kan hjælpe. Hvis du venter et par dage, kan det være at tknudsen, skovlunde eller måske soepro svarer:-)
Min hukommelse er meget ulden, men jeg mener at huske at grafik-mode skærmhukommelsen ligger på B800 og x antal bytes frem - hver byte svarende til 2 pixels (8 bit = 2 * 4, når antal farver er 16 - hvoraf den ene er baggrund.
Du kan derfor definere en pointer til et char array på (640 * 480 / 2) bytes, og sætte den til at pege på adresse B800. Herefter kan du hente og sætte de enkelte pixels direkte vha. den pointer:
char * ptr[640 * 480 / 2]; : ptr = MK_FP(0x0, 0xB800); : int getpixel(int x, int y) { int pixelNbr = x * 480 + y; if (pixelNbr % 2 == 0) /*Even pixels are in the upper 4 bits. */ return *(ptr+pixelNbr) / 0xF; else return *(ptr+pixelNbr) % 0xF; }
putpixel er selvfølgelig helt analog dertil. Den faktiske adresse kan du se i f.eks. 'PC Alt-i-een' - og vil givet afhænge af mode'n og (hvem ved) dit skærmkort. (Det er jo fordelen ved at bruge BGI - der behover man ikke vide disse ting. På den anden side har du vel allerede gjort dig disse overvejelser.)
Her er i øvrigt eksemplet jeg tænke på. Det går ikke direkte på dit spørgsmål, men kommer da indenom, og så bekræfter det hvad soepro skriver.
/***NOTE: This is an interrupt service routine. You can NOT compile this program with Test Stack Overflow turned on and get an executable file which will operate correctly. Due to the nature of this function the formula used to compute the number of paragraphs may not necessarily work in all cases. Use with care! Terminate Stay Resident (TSR) programs are complex and no other support for them is provided. Refer to the MS-DOS technical documentation for more information. */
/* reduce heaplength and stacklength to make a smaller program in memory */ extern unsigned _heaplen = 1024; extern unsigned _stklen = 512;
void interrupt ( *oldhandler)(void);
void interrupt handler(void) { unsigned int (far *screen)[80]; static int count;
/* For a color screen the video memory is at B800:0000. For a monochrome system use B000:000 */ screen = MK_FP(0xB800,0);
/* increase the counter and keep it within 0 to 9 */ count++; count %= 10;
/* put the number on the screen */ screen[0][79] = count + '0' + ATTR;
/* call the old interrupt handler */ oldhandler(); }
int main(void) {
/* get the address of the current clock tick interrupt */ oldhandler = getvect(INTR);
/* install the new interrupt handler */ setvect(INTR, handler);
/* _psp is the starting address of the program in memory. The top of the stack is the end of the program. Using _SS and _SP together we can get the end of the stack. You may want to allow a bit of saftey space to insure that enough room is being allocated ie: (_SS + ((_SP + safety space)/16) - _psp) */ keep(0, (_SS + (_SP/16) - _psp)); return 0; }
Jeg er bange for at du blander tekstmode og grafik mode sammen... Der er ikke noget der hedder baggrundsfarve i grafik mode. Desuden kan jeg fortælle dig at eksemplet desværre ikke virker.
Og så er B800 IKKE den korrekte adresse, hvis vi skal køre pixels i mode 12h. Prøv med C800h eller E800h. Iøvrigt skal MK_FP(0x0, 0xB800) ændres til MK_FP(0xB800, 0x0).
Det kan du måske nok mene, men farve 0 er vel at betragte som en slags baggrundsfarve. Bl.a. NCC kan da ændre på den faktiske farve af de 16 farver, sådan at farve 0 ikke længere er sort. Hvis jeg skal få din kode-stump til at fungere, må du lægge den herud. (Skift af mode osv. aner jeg ikke hvordan man går, uden at bruge BGI.
Jeg har tidligere skrevet at et EGA/VGA skærmkort har grafik hukommelse startende ved 0xA000. DER FINDES IKKE BAGGRUNDSFARVER I GRAFIK MODE. Farve 0 er SORT, men det gør den ikke til en baggrundsfarve. Grunden til at NCC kan ændre farverne er at man kan bruge forskellige paletter... Dette gør heller ikke nogen farver til baggrundsfarver. DET ER KUN I TEKSTMODE DER FINDES BAGGRUNDSFARVER.
Det kan jo ikke lade sig gøre at tegne en pixel med en farve og en baggrundsfarve.
Jeg har skrevet noget kode som VIRKER! problemet er bare at det kun kan skrive pixels til skærmrammen, jeg har endnu ikke fundet ud af at læse pixels fra skærmrammen.
Kode start: #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h>
/************************************** * Sæt grafik kor i 'mode' mode * * 0x03 = Tekst med farver (alm. dos) * * 0x12 = grafik 640x480x16 * * 0x13 = grafik 320x200x256 * **************************************/ void entermode(unsigned char mod) { union REGS regs;
regs.h.ah = 0x00;//Skift mode regs.h.al = mode;//Mode der skal skiftes til int86(0x10, ®s, ®s);//Udfør interrupt. }
void putpix(unsigned long x, unsigned long y, unsigned char color) { unsigned char far *screenptr; //Pointer der skal bruges til at skrive til skærmen med unsigned char bits=0x80;//Indeholder de bit som der skal ændres unsigned long xy=(y-1)*640+(x-1);//xy indeholder hvilket pixelnummber der skal ændres.
screenptr = (unsigned char far *)MK_FP(0xA000, xy/8);//Sætter pointeren til den rigtige adresse //1 byte i skærmrammen svare til 8 pixels på skærmen.
bits = bits >> (xy%8);//udregn det bit der skal farves;
outport(0x3CE, 0x0005); Set skrive mode 0 outport(0x3CE, 0x0F01); Enable set/reset for alle bitplaner
outportb(0x3CE, 0x00);//gør klar til valg af farve. outportb(0x3CF, color);//Vælg farven som pixelen skal have
outportb(0x3CE, 0x08);//gør klar til valg af pixels der skal farves outportb(0x3CF, bits);//vælg pixel der skal farves
bits = *screenptr;//Gem hvad der står på adressen *screenptr = bits;//opdatér indholdet af adressen }
void main(void) { int x, y;
entermode(0x12);//gå i grafik mode (640x480x16
//Fyld skærmen med en farve for (y = 1; y <= 480; y++) for (x = 1; x <= 640; x++) putpix(x, y, 1);//Tegn blå
//vent på at bruger har set det flotte resultat getch(); getch();
bits = *screenptr;//Gem hvad der står på adressen *screenptr = bits;//opdatér indholdet af adressen
Hvorfor skriver du 2 gange getch(). Hvis det er pga udvidede tastaturkoder, så er følgende smartere, så man altid kun skal trykke én gang for at afslutte:
Som du kan se i min kode så farven og hvilken pixel der skal sættes bestemt af outport'ene, men man skal stadig skrive til skærmrammen... Jeg er ikke klar over hvorfor, men hvis man ikke først læser fra skærmrammen og derefter bruger den samme variabel til at skrive til skærmrammen igen så virker det ikke.
2 gange getch() Det er bare fordi nå jeg sidder og skriver/tester min kode så trykker jeg til ALT+F9 (Borland's editor) for at køre koden og nogen gange er det ikke nok med kun en getch()
Jeg kan godt se at if(!getch()) getch(); ville være smartere, men lige precis den del af koden er ikke noget jeg gider at koncentrere mig om... Det på nuværende tidspunkt kun getpixel jeg er interesseret i.
Som sagt har jeg ikke forstand på dette, og jeg ved ikke om jeg er helt i skoven, men jeg kiggede da lidt på programmet.
Jeg kopierede putpix() og lavede en funktion int getpix(). Så vidt jeg kan se, så læser du faktisk pixelfarven lige før du skriver den igen. Jeg ved bare ikke lige præcis hvordan værdien af bits skal afkodes, for at få farven.
Jeg mener, at det var sådan funktionen så ud:
int getpix(unsigned long x, unsigned long y) { unsigned char far *screenptr; //Pointer der skal bruges til at skrive til skærmen med unsigned char bits=0x80;//Indeholder de bit som der skal ændres unsigned long xy=(y-1)*640+(x-1);//xy indeholder hvilket pixelnummber der skal ændres.
screenptr = (unsigned char far *)MK_FP(0xA000, xy/8);//Sætter pointeren til den rigtige adresse //1 byte i skærmrammen svare til 8 pixels på skærmen.
bits = bits >> (xy%8);//udregn det bit der skal farves;
bits = *screenptr;//Gem hvad der står på adressen // *screenptr = bits;//opdatér indholdet af adressen return bits; // Her skal der være en eller anden "kode-aftastning" af værdien af bits }
FOR SJOV: Prøv at ændre kaldet i main() til: putpix(x, y, x*y%2); Men andre funktioner (f.eks. x*y) giver interessante effekter ;-)
Soepro >> Jeg synes bare at der er lidt lusket at forsøge at få poinkt ved at svare på en kryptisk måde og så håbe på at vedkommende der skal have et svar er dum nok til at falde for sådan et billigt trick.
Jeg tror Bjarke har fat i den lange ende. Basalt set er der ikke den store forskel på skrivning og læsning af hukommelsen. For at skrive til 'frame bufferen' må du nødvendigvis have adgang til dette hukommelse, hvilket så også gør dig i stand til at læse fra den ved at variablerne omkring ligehedstegnet. I en kommentar i den viste kode står der: //1 byte i skærmrammen svare til 8 pixels på skærmen.
Det kan jeg ikke rigtig få til at passe, da du køre i 16 farver - hvilket svare til 4 bits per pixel altså 2 pixels per byte.
Uden at have teste det kan du prøve med følgende pseudo-agtige kode:
unsigned char far* lpVideoBuffer;
lpVideoBuffer = (unsigned char far* ) 0xA0000000L
Herved har du fat i video hukommelsen og derefter kan læse koordinaterne ved at bruge:
byte bFarve = lpVideoBuffer[640*y+x]
(eller optimeret:
bFarve = lpVideoBuffer[((y<<9) + (y<<7)) + x]
og så har du to pixler i bFarve - henholdsvis x,y og (x+1),(y+1). For at læse dem kan du eventuel bare AND værdien ud:
Her findes x,y
byte bEnFarve = bFarve & 0x0F
og her x+1, y+1
byte bEnFarve = bFarve & 0xF0
Det er alt sammen teoretisk men mon ikke det virker alligevel...(Hvis ikke så er det vel egentlig bare ærgeligt...:=))
skovlunde > Jeg kan ikke læse farven direktre fra skærmrammen fordi hvis det var tilfædedet skulle vinduet som jeg arbejder med (0xA000 ->) være noget større end 64000 bytes, og det er det ikke.
Der er en grund til at folk udvikler til windows istedet for dos.
Under it "real os" kunne du bruge www.ggi-project.org hvis du absolut _vil_ sidde og nørkle med lav-niveau ting men uden at vide noget om hardwaren. I praksis er et højere-niveau interface som www.gtk.org -- eller java's indbyggede -- at foretrække.
> Kan man læse pixelfarver fra skærmram i mode 12h?
Ja.
Når du siger mode 12h er det (formentlig!?) via borland og bios kald. Begge dele sidder og skjuler hardwaren - og det er derfor de er der. Jeg ved ikke om de har kald til det du vil. Har de ikke det må du gå direkte til hardwaren. Der kan man alt, og derfor er svaret ja. Men er det nødvendigt kører du et OS som ikke kan tilstrækkeligt.
bjarke >> Det er da rigtigt at det er nemmere at lave grafik i windows eller lignende, men når jeg nu så gerne vil sidde og nørkle med lav-niveau ting så er windows ikke så smart da det også kan gøre det end del mere besværligt at komme i kontakt med hardware direkte.
mahh>> Tjaa selvfølgelig kan du det - bare ikke med den metode som jeg gav dig..:). Jeg var udelukkende vant til at arbejde med Mode 13h i DOS, så derfor snakkede jeg før tanken om den famøse 64k ramme slog ned i mig..:)
Så snart du ikke er i Mode 13h er din hukommelse bygget op som en 'planar struktur' hvor hukommelsen er spredt ud over de fire planer video hukommelses planer i VGA kortet. Her står jeg af..:). Jeg har en gang for længe siden leget med det, og så vidt jeg husker er det ikke sport sjovt. Jeg mener nu stadig du skal prøve at gå Bjarkes vej og 'vende' din 'putpix' funktion så den læser i stedet for at skrive. Det burde være ret let - især fordi du har plan komplekset klaret via funktionskald og ikke skal sidde og nørkle med den komplekse matematik selv. Jeg kan ikke selv eksperimentere med det, da jeg for længt har sendt mine DOS kompilere på pension..(og jeg fortryder ikke...:)
Følgende brugte jeg da jeg skulle finde ud af hvordan man skriver til skærmen, der står også noget om hvordan man skal læse fra skærmen, men jeg kan ikke fatte det. Måske kan nogen af jer:
There are 4 bitplanes, each holding a bit of the color number. Each bit in a plane is a pixel. The pixel at (0,0) is at offset 0, bit 7. Éach line is 640 pixel=640 bits=80 bytes. So (0,1) is at offset 80, bit 7. If you have a line from (2,0)-(6,0) (bits 00111110) in color 10, (1010), the first byte of the bitplanes would look like this:
Now, what would happen if we put a white pixel at (0,0)? Exactly. Bit 7 of all the planes becomes 1.
Now how to access the bitplanes. The VGA card has a 64k window at 0xA000:0, so not enough for each (64k) plane. Here the ports come in use. alle ports we need are in the VGA card a sort of array. To access an element, you first send the index, then the data, or send the index and read the data. For most ports the data port is at the address of the index port +1. Example: The index port of the Graphics Controller (part of the VGA interface) is at 0x3CE. The data port is at (index+1), so 0x3CF. If we want to write a 4 to index 2, we do: outportb(0x3CE, 2); /*index*/ outportb(0x3CF, 4); /* data */ But there is a way to do it with one port command. There's also a outport() 'array', and if the addressed port isn't a 16-bit one, it sends the lo byte to <address>, and the hight byte to <address>+1. Just what we need. The example becomes: outport(0x3CE, 0x0402); /* index in low byte, data in high byte */
Back to the bitplanes. To select which bitplanes are accessed by memory writes (not reads!), you can write to index 2 of the sequencer (at 0x3C4). If we want to put the line in the first example (from (2,0)-(6,0), color 10), we can do: outport(0x3C4, 0x0A02); /* index 2 of the sequencer: select bitplanes 1,3 */ mem(0xA000:0) = 0x3E; But everything at (0,0), (1,0) and (7,0) will be destroied, and worse, if there was already something at, say (2,0), in bitplane 0 or 2 (the ones we didn't select), the values remain there, creating some ugly color or so.
the VGA card has 4 internal 8 bit latches, one for each plane. If you do a read, no matter what will be returned to the processor, these latches are loaded with the bits from all planes, from the selected address.
Also there're so called write modes. These are the way the VGA card interprets the byte written to the VGA memory. You set the write mode with the mode register, index 5 from the Graphics Controller (0x3C#). there are four modes:
Write mode 0 This is the default mode. With the bitmask register, index 8 from the Graphics Controller (0x3CE), you can select the bits used from the byte, the other bits are from the registers. Each byte you write will go to all bitplanes, unless you put a 1 in the corresponding bit in the Enable Set/Reset register, index 1 at the Graphics Controller (0x3CE). If you've put a 1 in that register, that plane will have the bit of the corresponing bit in Set/Reset register, index 0 at 0x3CE, and the CPU byte doesn't matter. So if you put 0x0F (all planes) in the Enable Set/Reset register, the desired color in the Set/Reset register, and the desired bits in the bitmask register, you can put pixel in that color, preserving other pixels in that byte. Example (againt the same line):
outport(0x3CE, 0x0005); /* Index 5 of the Grah Contr., set write mode 0 */ outport(0x3CE, 0x0F01); /* Index 1 of the Graph Contr., Enable Set/Reset for all planes */ outport(0x3CE, 0xA00); /* Index 0 of the Graph Contr., set with color 10 */ outport(0x3CE, 0x3E08); /* index 8 of the Graph Contr., set only bits 2-6 */ dummy = mem(0xA000:0); /* load latches */ mem(0xA000:0) := dummy; /* Byte doesn't matter */
Write mode 1 Nothing is done with the register or the CPU byte. All the latches are directly copied to the addressed byte. Usefull for screen to screen copy (e.g. scrolling) Example to copy (0,0-7-0) to (8,0)-(15,0): outport(0x3CE, 0x0105); /* Set write mode 1 */ Dummy = mem(0xA000:0); /* Load latches with pixels (0,0)-(7,0) */ mem(0xA000:1) = Dummy; /* Write latches to pixel (8,0)-(15,0) */
Write mode 2 The lower 4 bits of the CPU byte are the bits for each plane. If set for a plane, all bits selected with the Bitmask register are set, if clear, they're cleared. So you can do the same as in the example from mode 0, but with fewer instructions: outport(0x3CE, 0x0205); /* Set write mode 2*/ outport(0x3CE, 0x3E08); /* Set color 10 */ mem(0xA000:0) = 0x3E; /* Set bits 2-6 */
There's also a Function select register (index 3, 0x3CE). bits 3,4 indicate the way the bits that you want to change (selected by Bitmask register (write mode 0,2) or CPU byte (write mode 3)) are modified. 00 Not modified 01 ANDed with latches 02 ORed with latches 03 XOR with latches For write mode 0 and 3 bits 0-2 indicate how many times the bits you want to change are shifted to right. Bits rolling out the byte appear on the left.
there are two read modes, for the byte returned to the CPU on a memory read. You select them with the same register as the write modes (index 5, 0x3CE), but in bit 3.
Read mode 0: The byte from the planes selected with te Read Map Select register (index 4, 0x3CE) is returned.
Read mode 1: Each bit in the CPU byte is set if the color in the Color Compare register (index 2, 0x3CE) is equal to the color of the corresponding bit. To exclude a bit of the color value, clear that bit the in Color Don't care register (index 7, 0x3CE), if set that bit from the color will be used.
bjarke >> Det er der ikke noget specielt morsomt i, men når der nu ikke er nogen til at forklare mig hvordan jeg skal gøre så må man jo affinde sig med hvad man har... Det kunne jo også tænkes at der var en eller anden som lige pludselig fik en lys idé eller kunne huske et eller andet når vedkommende så koden?
De 16 farver ligger i fire bit planer... hvert bitplan har en af bits'ne 4bit giver 16 muligheder.
For at skrive en pixel må du derfor først læse de omkring liggende syv pixels+den du vil ændre, ned i en char(unsigned) derefter rette den enebit af hver fire bytes... og skrive dem tilbage...(Ellers overskriver du de nærliggende pixels)
Jeg formoder at bitplanerne ligger efterfølgende??? positionen af den ene betydende byte er mem[SEGB8000:(y*640+x) div 8] (håber du kan læse det jeg er pascal/delphi programmør) hvordan man får de sidste fire husker jeg ikke prøv +(640*480) div 8 og se om du ikke har endnu en... div er pascal for heltals division hvor man mister resten. MEM[SEGMENT:OFFSEGMENT] er også pascal notation for directe hukommelses adgang. i protected mode bruges segb800 som er en system variabel, ellers bruges $B800 eller 0xB800.
Jeg har engang skrevet noget asm til mode 12h - jeg kan huske jeg brugte den writemode, hvor man skal skifte plan 4 gange for at få sat alle bits -det gjorde det tilgengæld rigtigt nemt at lave \"highlight\" effekter - memset i ét plan :) Jeg prøver lige at grave sourcen frem - dét virker i hvertfald ;)
hmm ... forstår godt det sjove i selv at skrive koden fra begyndelsen, men helt ærligt vil det nok være mere effektivt for dig at bruge DirectX, som er vildt nemt. Desuden er det nemt at optimere mod. Venlig hilsen Thue
I øvrigt går direkte adressering desværre ikke i Win2000. Så man kan ikke lige putte lidt smart assembler ind hvor man har brug for det. Virkelig irriterende, men det er efter integrationen med NT teknologi.
maaske man skulle overveje sdl ? www.libsdl.org crossplatform grafik lib bruges til spil / demoer og der er tutorials til at komme godt i gang :).. // Lars Advice A/S
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.