Hi, EDIT (1) I am looking for `Screen.Mod' module (mainly its `Screen.Grub' command) hinted in PO2013 pictures. I have completed early version of `Bitmaps.Mod' supporting basic set of raster operations. I wish to share pictures and code. As we may not post attachments to the forum, I could expose to GitHub, there is also `WikiBook' run by Lyall E. Please advise. Many thanks. -- Tomas Kral <thomas.kral@email.cz>
Hi Tomas,
I am looking for `Screen.Mod' module (mainly its `Screen.Gr[a]b' command) hinted in PO2013 pictures. I have completed early version of `Bitmaps.Mod' supporting basic set of raster operations.
It's an interesting exercise. The following is a routine I sent to Prof. Wirth which I believe is what he used (I had it in System; it seems he chose to put it in a separate module called Screen; you may want to add it to your Bitmaps.Mod instead). You should easily be able to change this to your needs, e.g. whether you want white-on-black or black-on-white, or any other colours for that matter... With thanks to Florian Negele for the bit-reversal code! HTH Paul MODULE System; ... PROCEDURE Grab*; VAR S: Texts.Scanner; F: Files.File; R: Files.Rider; x, y, a, bits: INTEGER; BEGIN GetArg(S); IF S.class = Texts.Name THEN Texts.WriteString(W, "System.Grab "); Texts.WriteString(W, S.s); Texts.Append(Oberon.Log, W.buf); F := Files.New(S.s); IF F # NIL THEN Files.Set(R, F, 0); (*Windows Bitmap File Header*) Files.Write(R, "B"); Files.Write(R, "M"); Files.WriteInt(R, 14+40+8+128*768); (*file size*) Files.WriteInt(R, 0); (*reserved*) Files.WriteInt(R, 14+40+8); (*offset of bits*) (*Windows Bitmap WinHeader*) Files.WriteInt(R, 40); (*header size*) Files.WriteInt(R, 1024); Files.WriteInt(R, 768); (*w,h*) Files.WriteInt(R, 00010001H); (*planes, bpp*) Files.WriteInt(R, 0); (*no compression*) Files.WriteInt(R, 0); (*size of bitmap - can be 0 if uncompressed*) Files.WriteInt(R, 15000); Files.WriteInt(R, 15000); (*h&vres in ppm*) Files.WriteInt(R, 2); Files.WriteInt(R, 2); (*colours used, important*) (*colour table*) Files.WriteInt(R, 0); Files.WriteInt(R, 0FFFFFFH); (*padded rgb*) (*bitmap*) a := Display.base; FOR y := 0 TO 767 DO FOR x := 0 TO 124 BY 4 DO SYSTEM.GET(a, bits); INC(a, 4); bits := SYSTEM.MSK(ROR(bits, 4), 00F0F0F0FH) + SYSTEM.MSK(ROR(bits, 28), 0F0F0F0F0H); bits := SYSTEM.MSK(ROR(bits, 2), 033333333H) + SYSTEM.MSK(ROR(bits, 30), 0CCCCCCCCH); bits := SYSTEM.MSK(ROR(bits, 1), 055555555H) + SYSTEM.MSK(ROR(bits, 31), 0AAAAAAAAH); Files.WriteInt(R, bits) END END; Files.Register(F); Texts.WriteString(W, " done.") ELSE Texts.WriteString(W, " failed.") END; EndLine END END Grab; ... System.Grab Screen0.bmp
Sorry Tomas, that was more of an exercise than I intended, it appears there's no SYSTEM.MSK anymore (maybe Prof. Wirth removed it after seeing my code)... ;-) I found another implementation for the inner loop, which better corresponds with the kind of manipulations in Display.Mod: FOR x := 0 TO row-4 BY 4 DO SYSTEM.GET(a, bits); INC(a, 4); (*reverse bits*) bits := ROR(bits, 4) * MASK4 + ROR(bits, 28) * (-MASK4); bits := ROR(bits, 2) * MASK2 + ROR(bits, 30) * (-MASK2); Files.WriteSet(R, ROR(bits, 1) * MASK1 + ROR(bits, 31) * (-MASK1)) END where CONST MASK4 = {0..3, 8..11, 16..19, 24..27}; (*0F0F0F0F*) MASK2 = {0, 1, 4, 5, 8, 9, 12, 13, 16, 17, 20, 21, 24, 25, 28, 29}; (*33333333*) MASK1 = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30}; (*55555555*) Apologies, Paul
Hi Tomas,
I am looking for `Screen.Mod' module (mainly its `Screen.Gr[a]b' command) hinted in PO2013 pictures. I have completed early version of `Bitmaps.Mod' supporting basic set of raster operations.
It's an interesting exercise. The following is a routine I sent to Prof. Wirth which I believe is what he used (I had it in System; it seems he chose to put it in a separate module called Screen; you may want to add it to your Bitmaps.Mod instead). You should easily be able to change this to your needs, e.g. whether you want white-on-black or black-on-white, or any other colours for that matter...
With thanks to Florian Negele for the bit-reversal code!
HTH Paul
MODULE System; ...
PROCEDURE Grab*; VAR S: Texts.Scanner; F: Files.File; R: Files.Rider; x, y, a, bits: INTEGER; BEGIN GetArg(S); IF S.class = Texts.Name THEN Texts.WriteString(W, "System.Grab "); Texts.WriteString(W, S.s); Texts.Append(Oberon.Log, W.buf); F := Files.New(S.s); IF F # NIL THEN Files.Set(R, F, 0); (*Windows Bitmap File Header*) Files.Write(R, "B"); Files.Write(R, "M"); Files.WriteInt(R, 14+40+8+128*768); (*file size*) Files.WriteInt(R, 0); (*reserved*) Files.WriteInt(R, 14+40+8); (*offset of bits*) (*Windows Bitmap WinHeader*) Files.WriteInt(R, 40); (*header size*) Files.WriteInt(R, 1024); Files.WriteInt(R, 768); (*w,h*) Files.WriteInt(R, 00010001H); (*planes, bpp*) Files.WriteInt(R, 0); (*no compression*) Files.WriteInt(R, 0); (*size of bitmap - can be 0 if uncompressed*) Files.WriteInt(R, 15000); Files.WriteInt(R, 15000); (*h&vres in ppm*) Files.WriteInt(R, 2); Files.WriteInt(R, 2); (*colours used, important*) (*colour table*) Files.WriteInt(R, 0); Files.WriteInt(R, 0FFFFFFH); (*padded rgb*) (*bitmap*) a := Display.base; FOR y := 0 TO 767 DO FOR x := 0 TO 124 BY 4 DO SYSTEM.GET(a, bits); INC(a, 4); bits := SYSTEM.MSK(ROR(bits, 4), 00F0F0F0FH) + SYSTEM.MSK(ROR(bits, 28), 0F0F0F0F0H); bits := SYSTEM.MSK(ROR(bits, 2), 033333333H) + SYSTEM.MSK(ROR(bits, 30), 0CCCCCCCCH); bits := SYSTEM.MSK(ROR(bits, 1), 055555555H) + SYSTEM.MSK(ROR(bits, 31), 0AAAAAAAAH); Files.WriteInt(R, bits) END END; Files.Register(F); Texts.WriteString(W, " done.") ELSE Texts.WriteString(W, " failed.") END; EndLine END END Grab; ...
System.Grab Screen0.bmp
-- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
Hi Paul, Thank you for this mighty piece of code. I will try it, hopefully I find some time to share `Bitmaps.Mod' at my GIT repo tomorrow. -- Tomas Kral <thomas.kral@email.cz>
Hi Paul, This made the trick in the end, but I fear is not much effective as the `ReverseByte' is called four times per display word. The masking code of yours seems four times faster, if could be updated for big endian somehow? PROCEDURE ReverseByte(wd: INTEGER): INTEGER; VAR i: INTEGER; rwd, t: SET; BEGIN rwd := {}; FOR i := 0 TO 7 DO t := SYSTEM.VAL(SET, ASR(wd, i))*{0}; t := SYSTEM.VAL(SET, LSL(SYSTEM.VAL(INTEGER, t), 7-i)); rwd := rwd+t; END RETURN SYSTEM.VAL(INTEGER, rwd) END ReverseByte; ... (*little endian: high, low words*) hwd := bits MOD 10000H; lwd := bits DIV 10000H; (*big endian: high, low bytes, bit reversed*) bits := ReverseByte(lwd DIV 100H)*1000000H + ReverseByte(lwd MOD 100H)*10000H + ReverseByte(hwd DIV 100H)*100H + ReverseByte(hwd MOD 100H); ... Why do we need bit reversal at all? Why the bitmap is stored as inverse video? Many thanks -- Tomas Kral <thomas.kral@email.cz>
Hi Tomas
Am 17.09.2017 um 15:58 schrieb Tomas Kral <thomas.kral@email.cz>:
Why do we need bit reversal at all? Why the bitmap is stored as inverse video?
If the only OS you use is Oberon you are free to write and read the screen memory 1:1 to a file without any reversal or inverting. However, if you want to exchange the picture of your screen with some other OSes (Windows, Mac, Linux, OS/2, iOS, AmigaOS, ...) you have to use a well defined file format. There are two possibilities: a) Either you define the picture format yourself and all other OSes have to understand it. (Unless you’re Microsoft or Apple this is rather unrealistic) b) you take an already defined picture format and convert that external file format to your internal memory representation. The BMP file format is license-free, well documented and supported by almost all OSes. So, it’s a good choice to exchange picture data from one device to an other. Unfortunately however, the BMP file specification defines: (http://www.cs.ucc.ie/j.bowen/cs1107/imageFormats/bmp/formatDesc.html) ==== Pixel Data encoding for 1bit / black & white images BitCount = 1 Compression = 0 Every byte holds 8 pixels, its highest order bit representing the leftmost pixel of those. There are 2 color table entries. Some readers will ignore them though, and assume that 0 is black and 1 is white. If you are storing black and white pictures you should stick to this, with any other 2 colors this is not an issue. Remember padding with zeros up to a 32bit boundary (This can be up to 31 zeros/pixels!) ==== Here you have it! As PO2013 defines the bit usage in memory differently than BMP you have to write code to convert the BMP definition to the PO2013 definition. A possible alternative to do this bit reversal is as follows: you define an array „rev" of 256 bytes, pre-populate it with the reversed bit values and while reading do: Files.Read(pix); pix := rev[pix]; br Jörg
On Mon, 18 Sep 2017 08:53:15 +0200 Jörg <joerg.straube@iaeth.ch> wrote:
As PO2013 defines the bit usage in memory differently than BMP
Hi Joerg, Yes, only now did I realise, although I've had in front of my eyes all the time. MODULE Display; CONST black* = 0; white* = 1; (*black = background*) I found inverting colours as such when writing bits words to file. Files.WriteSet(R, SYSTEM.VAL(SET, bits) / {0..31}) (*inverse video*) Thank you. -- Tomas Kral <thomas.kral@email.cz>
Hi Paul, Making first steps with `Screen.Grab', the code compiles and does things, however bits are not reversed as intended, they appear as if mirrored, on my RPI platform. I have recoded reversing part as follows, not sure if better or faster, but the output is identical to shifting and masking inner loop, though both not correct for RPI. (*little endian: high, low words*) hwd := bits MOD 10000H; lwd := bits DIV 10000H; (*big endian: high, low bytes*) bits := (lwd DIV 100H)*1000000H + (lwd MOD 100H)*10000H + (hwd DIV 100H)*100H + (hwd MOD 100H); Files.WriteInt(R, bits) ... Also looking for Files.WriteInt for inspiration. PROCEDURE WriteInt*(VAR R: Rider; x: INTEGER); BEGIN WriteByte(R, x MOD 100H); WriteByte(R, x DIV 100H MOD 100H); WriteByte(R, x DIV 10000H MOD 100H); WriteByte(R, x DIV 1000000H MOD 100H) END WriteInt; Many thanks. -- Tomas Kral <thomas.kral@email.cz>
Hi, I am trying to revive an old post. Getting Oberon in colour, there is now a need for `Screen.Grab' handling 4-bit framebuffer. So you may guess what I struggle with at the moment :-) Before duplicating the effort, anyone has coded already? Reading BMP specs may help, but is there a nice small 4-bit indexed bitmap somewhere, so I could correctly populate the header, colour table, depth, etc? As my tries so far usually end up in oversized colour table, invalid BMP format etc. Still not sure what to do with pixel data. The docs says 1-bit (Monochrome Bitmap) "done and works" Each byte of data represents 8 pixels. The most significant bit maps to the left most pixel in the group of eight. Any unused bits are set to zero. 4-bit (16 Color Bitmap) "to do" Each byte of data represents 2 pixels. The most significant nibble maps to the left most pixel in the group of two. Any unused nibble is set to zero. Many thanks. -- Tomas Kral <thomas.kral@email.cz>
On Tue, 23 Oct 2018 19:12:10 +0200 Tomas Kral <thomas.kral@email.cz> wrote:
I could correctly populate the header, colour table, depth, etc? As my tries so far usually end up in oversized colour table, invalid BMP format etc.
Not quite there, wrong colours in palette, image is shifted, GIMP opens it though, other viewers not. -- Tomas Kral <thomas.kral@email.cz>
You already reached a lot. Which header did you select? I would take the simplest one: 12 byte BITMAPCOREHEADER. The order of the color channels in the color table is blue, green, red. Jörg
Am 23.10.2018 um 22:21 schrieb Tomas Kral <thomas.kral@email.cz>:
On Tue, 23 Oct 2018 19:12:10 +0200 Tomas Kral <thomas.kral@email.cz> wrote:
I could correctly populate the header, colour table, depth, etc? As my tries so far usually end up in oversized colour table, invalid BMP format etc.
Not quite there, wrong colours in palette, image is shifted, GIMP opens it though, other viewers not.
-- Tomas Kral <thomas.kral@email.cz> <Screen.Grab.png> -- Oberon@lists.inf.ethz.ch mailing list for ETH Oberon and related systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
You already reached a lot. Thank you for your encouragement. This is an improved version, that seems having correct header. Colours are still shifted both in palette and indices.
I store BGR as is in little endian, so as BMP expects. I fear instead of byte reversal, I may need nibble reversal which would be terribly slow, unless some magic mask and lookup used. (*Windows Bitmap File Header*) Files.Write(R, "B"); Files.Write(R, "M"); Files.WriteInt(R, 14+40+8+128*(*7*)68*4); (*file size 768 real, 68 for faster testing*) Files.WriteInt(R, 0); (*reserved*) Files.WriteInt(R, 14+40+(*2*)16*4); (*offset of bits, 2*4 - mono, 16*4 - 4bit*) (*Windows Bitmap WinHeader*) Files.WriteInt(R, 40); (*header size*) Files.WriteInt(R, 1024); Files.WriteInt(R, 768); (*w,h*) Files.WriteInt(R, 00040001H); (*planes, bpp, should really be 0001 0004, but 0001 0004 makes oversized palette*) Files.WriteInt(R, 0); (*no compression*) Files.WriteInt(R, 0); (*size of bitmap - can be 0 if uncompressed*) Files.WriteInt(R, 15000); Files.WriteInt(R, 15000); (*h&vres in ppm*) Files.WriteInt(R, 16); Files.WriteInt(R, 16); (*colours used, important, 2/2 - mono, 16/16 - 4bit*) (*fixed colour table `00BBGGRRH' 00padded rgb little endian*) Files.WriteInt(R, 0000000H); Files.WriteInt(R, 0000080H) (*red*); Files.WriteInt(R, 0008000H); Files.WriteInt(R, 0008080H); Files.WriteInt(R, 0800000H);(*blue*) Files.WriteInt(R, 0800080H); Files.WriteInt(R, 0808000H);(*green*) Files.WriteInt(R, 0808080H); Files.WriteInt(R, 0C0C0C0H); Files.WriteInt(R, 00000FFH); Files.WriteInt(R, 000FF00H); Files.WriteInt(R, 000FFFFH); Files.WriteInt(R, 0FF0000H); Files.WriteInt(R, 0FF00FFH); Files.WriteInt(R, 0FFFF00H); Files.WriteInt(R, 0FFFFFFH); (*actual colour table*) (*FOR a := ms TO me BY 4 DO SYSTEM.GET(a, m); Files.WriteInt(R, m) END;*) -- Tomas Kral <thomas.kral@email.cz>
Tomas As I said I would only take the BITMAPCOREHEADER (12 byte) instead of the BITMAPINFOHEADER (40 byte); I'm lazy and don't want to populate a lot of fields 😊 But this is only minor. One problem is, your color table is wrong: The order of the color channels in the BMP files has to be: first byte B, second byte G, third byte R, fourth byte 0. As you decided to use "WriteInt" (instead of Write / WriteByte), you have to take care of the endianness of WriteInt, or in other words: you have to know in what sequence WriteInt writes the four bytes of an INTEGER to the file. The sequence in WriteInt is: LSB first, MSB last. So for a color table in a BMP file use red is WriteInt(00FF0000H); green is WriteInt(0000FF00H); blue is WriteInt(000000FFH); or generally WriteInt(00rrggbbH); Jörg -----Original Message----- From: Oberon <oberon-bounces@lists.inf.ethz.ch> On Behalf Of Tomas Kral Sent: Wednesday, October 24, 2018 11:47 AM To: oberon@lists.inf.ethz.ch Subject: Re: [Oberon] FPGA - Screen.Mod
You already reached a lot. Thank you for your encouragement. This is an improved version, that seems having correct header. Colours are still shifted both in palette and indices.
I store BGR as is in little endian, so as BMP expects. I fear instead of byte reversal, I may need nibble reversal which would be terribly slow, unless some magic mask and lookup used. (*Windows Bitmap File Header*) Files.Write(R, "B"); Files.Write(R, "M"); Files.WriteInt(R, 14+40+8+128*(*7*)68*4); (*file size 768 real, 68 for faster testing*) Files.WriteInt(R, 0); (*reserved*) Files.WriteInt(R, 14+40+(*2*)16*4); (*offset of bits, 2*4 - mono, 16*4 - 4bit*) (*Windows Bitmap WinHeader*) Files.WriteInt(R, 40); (*header size*) Files.WriteInt(R, 1024); Files.WriteInt(R, 768); (*w,h*) Files.WriteInt(R, 00040001H); (*planes, bpp, should really be 0001 0004, but 0001 0004 makes oversized palette*) Files.WriteInt(R, 0); (*no compression*) Files.WriteInt(R, 0); (*size of bitmap - can be 0 if uncompressed*) Files.WriteInt(R, 15000); Files.WriteInt(R, 15000); (*h&vres in ppm*) Files.WriteInt(R, 16); Files.WriteInt(R, 16); (*colours used, important, 2/2 - mono, 16/16 - 4bit*) (*fixed colour table `00BBGGRRH' 00padded rgb little endian*) Files.WriteInt(R, 0000000H); Files.WriteInt(R, 0000080H) (*red*); Files.WriteInt(R, 0008000H); Files.WriteInt(R, 0008080H); Files.WriteInt(R, 0800000H);(*blue*) Files.WriteInt(R, 0800080H); Files.WriteInt(R, 0808000H);(*green*) Files.WriteInt(R, 0808080H); Files.WriteInt(R, 0C0C0C0H); Files.WriteInt(R, 00000FFH); Files.WriteInt(R, 000FF00H); Files.WriteInt(R, 000FFFFH); Files.WriteInt(R, 0FF0000H); Files.WriteInt(R, 0FF00FFH); Files.WriteInt(R, 0FFFF00H); Files.WriteInt(R, 0FFFFFFH); (*actual colour table*) (*FOR a := ms TO me BY 4 DO SYSTEM.GET(a, m); Files.WriteInt(R, m) END;*) -- Tomas Kral <thomas.kral@email.cz>
Joerg, Thank you.
take the BITMAPCOREHEADER (12 byte) instead of the BITMAPINFOHEADER (40 byte); Yes, my BMP definition is bit vague, where is `BITMAPCOREHEADER' defined?
One problem is, your color table is wrong: or generally WriteInt(00rrggbbH);
True, I now swap BB for RR and the map looks good hopefully or at least is better, but indices are still wrong, I guess I need to either rearrange the nibble indices, or reshuffle map order, somehow? (* swap BB for RR*) m := m MOD 100H * 10000H + m DIV 10000H + m DIV 100H MOD 100H * 100H; (*can get it shorter?*) Map is in this order, same in which `E' letters are put out. 00000000 black 00000080 dark red 00008000 green 00008080 yellow 00800000 deep blue 00800080 magenta 00808000 cyan 00808080 grey 00C0C0C0 light grey 000000FF red 0000FF00 light green 0000FFFF light yellow 00FF0000 blue 00FF00FF light violet 00FFFF00 light cyan 00FFFFFF white -- Tomas Kral <thomas.kral@email.cz>
Tomas
take the BITMAPCOREHEADER (12 byte) instead of the BITMAPINFOHEADER (40 byte); Yes, my BMP definition is bit vague, where is `BITMAPCOREHEADER' defined?
Google is your friend :-)
One problem is, your color table is wrong: or generally WriteInt(00rrggbbH);
True, I now swap BB for RR and the map looks good hopefully or at least is better, but indices are still wrong, I guess I need to either rearrange the nibble indices, or reshuffle map order, somehow?
According to the bmp definition: for BPP = 4, the left-most pixel is the most-significant nibble. br Jörg
According to the bmp definition: for BPP = 4, the left-most pixel is the most-significant nibble.
Thank you for bearing with me. Nibbles seems right as otherwise I would get garbled text, but I don't. Testing to reorder the map, see pict, which then shows the right cols in BMP. Oberon order 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 BMP order 15 7 3 11 1 9 5 13 0 8 4 12 2 10 6 14 I guess for invert video, I just go the opposite? 14 6 10 2 12 4 8 0 13 5 9 1 11 3 7 15 -- Tomas Kral <thomas.kral@email.cz>
participants (4)
-
Jörg -
Jörg Straube -
Paul Reed -
Tomas Kral