Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ELKS Image Viewer #2205

Open
rafael2k opened this issue Jan 31, 2025 · 24 comments
Open

ELKS Image Viewer #2205

rafael2k opened this issue Jan 31, 2025 · 24 comments

Comments

@rafael2k
Copy link
Contributor

Today I released the first version of ELKS Viewer, version 0.1!:
https://github.com/rafael2k/elks-viewer/releases/tag/0.1

ELKS Viewer is composed by standalone viewers:

  • bmpview: Supports 1, 4, 8 and 24 bits BMP (run-length encoding also supported)
  • ppmview: Supports PPM and PGM formats
  • jpgview: A JPEG viewer which supports coloured and grayscale images

Only mode VGA 0x13 (320x200 256 colors) is supported for now.

@ghaerr
Copy link
Owner

ghaerr commented Jan 31, 2025

Very cool!! Can you post some screenshots on your repo?

@ghaerr
Copy link
Owner

ghaerr commented Jan 31, 2025

@rafael2k,

I read through all your ELKS Viewer source - that's some fantastic graphics programming you've done, very impressive!! Everything is nicely done, very easy to read and understand. Nice work on all the palette functions!

I'm seeing now how you could possibly get the whole project ported over from OWC to ELKS C86, by just rewriting/renaming graphics.c using C86 asm statements in C, in particular plot_pixel which is very close to write_vid, which we've already got written in C86 ASM. I think that plan should work well, as long as you can keep all far declarations out of any header files, since C86 doesn't support them. If you stay in 320x200 palette mode, you might be able to use a wrapper function for C86 that sets DS or ES and uses that to address a far memory array alloc'd through fmemalloc. I haven't tested malloc on C86 yet but it should work, but _fmalloc won't be able to be ported since it uses the __far keyword. But we could use fmemalloc and a wrapper to set DS/ES to access for larger image or also direct CGA/VGA access.

But first just getting graphics.c ported to C86; looks pretty straightforward, except possible for char __far *get_video_address. Do you need that function in other source? This could also possibly be got around by providing a C86 wrapper function that loads DS with a passed segment then uses a passed offset to reference a byte/word etc. Another idea would be to update the C86 compiler itself to have an internal function that would generate code to load DS:offset and return a word or byte. I'll have look into how that might be done depending on which way you take your project.

I also notice in your not-used-yet dither.c there's some functionality to write a 4-bit VGA color value in planar mode (setpixel). That code could come in really handy in graphics.c and support a 16-color 640x480 mode, which would really help for other drawing (not images though). If that routine looks too slow, we could also pull the Nano-X 4-bit/16 color 640x480 EGA/VGA driver code out and adapt that for C86/OWC.

Anyways, very nice stuff, thanks for posting it!

@rafael2k
Copy link
Contributor Author

rafael2k commented Jan 31, 2025

Yay! Thanks for the kind words.

I'll do just last default palette adjustments and then add mode 12 (16-color 640x480 mode). The code is pretty portable, and after tests with dosbox-x with 8086 emulation (including 4.77 MHz clock), I can say the tools have pretty good speed. Of course lots of basic things are missing, like scaling (full-screen mode), centering, zoom, etc. But with time we get there.

And yes - lets port to C86, I agree, that would be cool, I'll be able develop on ELKS (on my thinkpad T430) for ELKS.
: )

When I look at demoscene 8086 samples (usually for DOS, but that could be easily portable to ELKS), a book8088 can be an arts machine!

@ghaerr
Copy link
Owner

ghaerr commented Jan 31, 2025

then add mode 12 (16-color 640x480 mode).

I've been looking at Nano-X and have found a very old 640x480 mode 12 driver I'd written decades ago - in MASM source nonetheless! Give me a few days and I'll port that over to OWC and C86 and add it to examples/vgatest.c. You can then use that as the basis for your higher-resolution graphics. The code will also support 640x350 EGA mode.

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 2, 2025

Some screenshots:

8-bit BMP created with ImageMagick:
Image
Image

24-bit JPG:

Image
Image

ps: the colors don't look so good in the 24-bit RGB because of the computational cheap method I'm using to convert to 24-bit RGB to 8-bit palette.

Grayscale JPG:

Image
Image

@ghaerr
Copy link
Owner

ghaerr commented Feb 2, 2025

That's very impressive, better graphics than I had imagined. I guess the difference between the BMP and JPG are that the BMP was created from an original with a custom palette, while the JPG is decoded then displayed into a standard palette?

BTW, I have the VGA 640x480 16-color (mode 12h) draw functions completed and working for C86. They're written in AS86 assembly language and should be very fast. I'll be posting a PR on 8086-toolchain with vgatest.c used to call it. The routines in draw pixel, draw horizontal line, draw vertical line, draw filled rectangle and read pixel.

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 2, 2025

That's very impressive, better graphics than I had imagined. I guess the difference between the BMP and JPG are that the BMP was created from an original with a custom palette, while the JPG is decoded then displayed into a standard palette?

Indeed, the 8-bit bmp was created from an original 24-bit image with ImageMagick, which chose an optimized palette, and no conversion is done (by bmpview), while the colored JPG is using the fast 24-RGB to 8-bit with a standard palette conversion.

BTW, I have the VGA 640x480 16-color (mode 12h) draw functions completed and working for C86. They're written in AS86 assembly language and should be very fast. I'll be posting a PR on 8086-toolchain with vgatest.c used to call it. The routines in draw pixel, draw horizontal line, draw vertical line, draw filled rectangle and read pixel.

Thanks!
: ))

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 4, 2025

I got a bit confused here in the 4-bit routine drawpixel:

        mov     ah, arg1+4[bp]  ; color pixel value
        out     dx, ax

        mov     al, #8          ; set bit mask register 8
        mov     ah, ch          ; [load bit mask into register 8]
        out     dx, ax

It is a assumed here the standard 4-bit palette is being used, right? How does exactly this bit mask work? I'm too used to planar modes I suspect.

ps: and I need to keep supporting OW compiler (for jpg), but I also need to keep in place all support for "paletted" operation for 4-bit and 8-bit too, as this is needed for fast operation of bmpviewer. I already ported the palette manipulation funtions to C86 - all good.

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 4, 2025

When trying to compile bmpview with C86:

make -f Makefile.c86 bmpview
/home/rafael2k/programs/devel/8086-toolchain/host-bin/cpp86 -0  -I/home/rafael2k/programs/devel/elks/libc/include -I/home/rafael2k/programs/devel/elks/elks/include -I/home/rafael2k/programs/devel/elks/libc/include/c86 -D__LIBC__ -D__HAS_NO_FLOATS__=1 -D__HAS_NO_LONGLONG__ -o bmpview.i bmpview.c
/home/rafael2k/programs/devel/8086-toolchain/host-bin/c86 -g -O -bas86 -warn=4 -lang=c99 -align=yes -separate=yes -stackopt=minimum -peep=all -stackcheck=no  bmpview.i bmpview.as
"graphics.h", line 63: warning [4]: definition of 'index' hides an earlier definition
"graphics.h", line 64: warning [4]: definition of 'index' hides an earlier definition
"bmpview.c", line 92: warning [2]: size of parameter 2 changed by prototype on function fseek
c86: regx86.c:168: g_push: Assertion `0' failed.
TERMINATING: - signal 6 received

Compiler is exploding, may be on casts. We need to investigate.

"graphics.c" compiles, but with warnings:

# make -f Makefile.c86 graphics.o
/home/rafael2k/programs/devel/8086-toolchain/host-bin/cpp86 -0  -I/home/rafael2k/programs/devel/elks/libc/include -I/home/rafael2k/programs/devel/elks/elks/include -I/home/rafael2k/programs/devel/elks/libc/include/c86 -D__LIBC__ -D__HAS_NO_FLOATS__=1 -D__HAS_NO_LONGLONG__ -o graphics.i graphics.c
/home/rafael2k/programs/devel/8086-toolchain/host-bin/c86 -g -O -bas86 -warn=4 -lang=c99 -align=yes -separate=yes -stackopt=minimum -peep=all -stackcheck=no  graphics.i graphics.as
"graphics.c", line 132: warning [4]: definition of 'index' hides an earlier definition
"graphics.c", line 150: warning [4]: definition of 'index' hides an earlier definition
"graphics.c", line 190: warning [4]: argument 'uint' implicitly declared 'int'
"graphics.c", line 200: warning [4]: definition of 'index' hides an earlier definition
"graphics.c", line 213: warning [4]: definition of 'index' hides an earlier definition
/home/rafael2k/programs/devel/8086-toolchain/host-bin/as86 -0 -O -j -w-  -o graphics.o -l graphics.lst graphics.as
rm graphics.i graphics.as

@ghaerr
Copy link
Owner

ghaerr commented Feb 4, 2025

It is a assumed here the standard 4-bit palette is being used, right? How does exactly this bit mask work? I'm too used to planar modes I suspect.

No - the vga-4bp.s is not a linear mode nor a palette mode, is using the hard-to-understand 4-plane VGA mode, which requires fiddling with various VGA registers then writing into each of the VGA planes. The "color" in this case is a 4-bit color (0-15), which correspond to the standard 0-15 colors in CGA 16-color palette modes though. That may be what your question was?

I need to keep supporting OW compiler (for jpg),

I plan on supporting OWC as well, but haven't had time to convert this new C86 AS86 routine to WASM format and also accept input via registers, instead of on the stack. Of course, a pragma aux could be used to cause OWC to push the parameters on the stack before calling, which might be the best first approach. Supporting three assemblers is a pain in the butt. I will get back to this but am knee deep trying to get Nano-X working with a window manager and other issues.

but I also need to keep in place all support for "paletted" operation for 4-bit and 8-bit too, as this is needed for fast operation of bmpviewer.

So it seems that what you're requesting is a 4-bit palette mode, an 8-bit palette mode, and then in addition the higher resolution 4-bit planar mode, all for VGA?

This gets complicated in a hurry for the 4-bit palette and planar modes, depending on how you want to keep the source images in memory (or whether they're not in memory, but created on the fly directly to the display). We can talk in depth about that if you tell me how you plan on manipulating the images.

The C86 graphics.c functions don't support any memory-image containers or fast blit image output. Instead, they just draw pixels directly to the screen, a very slow and ancient method of doing things. Of course, on ancient hardware, sometimes it's too slow to decode into memory and then copy that (possibly with conversion) to a display.

In the newer Microwindows, everything got complicated quickly in this regard and a design choice was made to keep all pixmaps (internal images) in a "normalized" format, which was usually 32bpp ARGB. Then, a large host of conversion blitters (fast output routines) were written that would convert ARGB to 565, 555, 332, palette, planar-4, etc. This allowed all the image converters to be vastly simplified. More on that also if you're interested.

I already ported the palette manipulation funtions to C86 - all good.

Nice!

@ghaerr
Copy link
Owner

ghaerr commented Feb 4, 2025

Compiler is exploding, may be on casts. We need to investigate.

"graphics.c" compiles, but with warnings:

Sounds like you've run into yet another C86 compiler bug. It would probably be worth it to follow the BYU advice here, at least for the time being: don't use casts and break down complex expressions into simpler expressions, even if a temp variable needs to be used.

Try that route. If you find a single statement (preferably without casts) that breaks C86, post it and I can add that to my list of C86 things to investigate.

Good luck!

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 4, 2025

Found many issues in C86, and workarounded at least in ppmview to check if the graphics function were working - and yes! It worked! But the speed is slower than with OW. I have a feeling I'll do a last port to ia16-gcc to compare speed.

I opened some issues in 8086-toolchain with the errors I noticed.

I'm with fingers crossed for a Nano-X window manager! I'm always taking a look on it, it is very cool, I want to use it more.

btw, right now elks-viewer always write directly to screen, no tool buffers the whole encoded or decoded image on memory (apart of video memory, already in the appropriate format).

@ghaerr
Copy link
Owner

ghaerr commented Feb 5, 2025

right now elks-viewer always write directly to screen, no tool buffers the whole encoded or decoded image on memory

That's definitely the way to do things on limited RAM machines; I've been thinking a lot about this during the recent Nano-X port. Keep RAM-based bitmaps (pixmaps) not only uses up tons of memory which we don't have, but there's no easy way to quickly display them when running in VGA 4-plane mode, since a fast REP MOVSB or memcpy won't work. The newer Nano-X has lots of support for buffered windows and offscreen pixmaps, but the cost of conversion between RGB and planar is very high for our slow 8086 targets! I'm going to take a pass at optionally disabling these features in order to keep the code size down - we're currently at 55k code size in the Nano-X server - almost out of space.

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 5, 2025

ELKS Viewer ppmview and bmpview are able to be compiled - after porting using your routines - with C86!

Nano-X! Soon I'll start playing with it, after finishing the viewer. Would the large model be a good candidate for Nano-X server?

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 6, 2025

I can confirm that the C86 compiled bmpview performs very well, almost same speed of owc produced binary, and ~20% more of size than owc -Os.
bmpview size:

  • 12216 bytes the C86/as86/ld86
  • 9008 bytes the OWC large model -Os

Image

@ghaerr
Copy link
Owner

ghaerr commented Feb 6, 2025

Nano-X! Soon I'll start playing with it, after finishing the viewer. Would the large model be a good candidate for Nano-X server?

Are you thinking of porting Nano-X to OWC? I would say there's not much reason to do that, as it was recently discovered that our ancient ELKS version can't support the window manager without heavy refactoring, and I just got the main Microwindows repo version runnable on ELKS. Nano-X isn't malloc-heavy, so small pointers are fine; we are almost out of the first 64k code segment, but ia16-elf-gcc supports an additional far text segment in medium model, so there's not much point in porting to another compiler, especially since there's a number of other problems to fix in the short term.

If you want to start getting involved with Nano-X, I would suggest looking briefly at the client-side demos in nanox/demos/*.c, they're quite old, but give an example of how a client process opens the connection with GrOpen, then proceeds to make graphics calls with GrNewWindow, GrText, etc. Its designed so that clients can be linked with the server for a standalone graphics program, but the resulting size will always be way larger than the stuff we're doing with the VGA library.

In general, for fast operation, a Nano-X client doesn't usually deal with graphical bits on the client side, but instead just sends "high level instructions" over to the server for drawing. This doesn't have to be the case, but otherwise all the graphics pixel data would have to be sent over a UNIX socket, which is very slow. This also implies that the image loading and decoding routines should reside on the server. There's still lots of room for improvement on that end, but currently for debugging purposes all that's turned off for the time being. We can discuss more as you become a bit more familiar with it and understand what you'd like to use Nano-X for or what you'd like to add to it.

@ghaerr
Copy link
Owner

ghaerr commented Feb 6, 2025

I can confirm that the C86 compiled bmpview performs very well, almost same speed of owc produced binary, and ~20% more of size than owc -Os.

I suppose that sounds about right, given that C86 does very little optimization. My inspection of C86 code produced shows that each and every C statement "starts from scratch" literally re-loading any registers from their stack holding places, without regard for registers already having a usable value. Unless a tight loop is being performed, this may not matter much, depending on the real need for speed.

To be fair in comparison with C86 though you'd probably want to use small mode OWC, right?

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 7, 2025

I can confirm that the C86 compiled bmpview performs very well, almost same speed of owc produced binary, and ~20% more of size than owc -Os.

I suppose that sounds about right, given that C86 does very little optimization. My inspection of C86 code produced shows that each and every C statement "starts from scratch" literally re-loading any registers from their stack holding places, without regard for registers already having a usable value. Unless a tight loop is being performed, this may not matter much, depending on the real need for speed.

To be fair in comparison with C86 though you'd probably want to use small mode OWC, right?

I'll try now with small model on OWC.

@rafael2k
Copy link
Contributor Author

I finally managed to understand (to some extent) and port the 4-bit code to OW. Indeed... not easy to understand the VGA operation!

@rafael2k
Copy link
Contributor Author

rafael2k commented Feb 10, 2025

Now with modes 0x10, 0x12 and 0x13 supported, may be I can also choose a CGA mode, so we cover virtually all supported ELKS graphics configuration? I can see the options are modes 0x4, 0x5 and 0x6. May be mode 0x5?

I plan to use function pointers, as you suggested, in order to keep the code clean with all different compilers, modes and color space conversions.

ps: I found this code for CGA (and Hercules!) which seems ok for the needs of the image viewer: https://github.com/cyningstan/cgalib/blob/master/src/screen.c , it compiles with OpenWatcom already, and could also enable first class monochrome support too.

@ghaerr
Copy link
Owner

ghaerr commented Feb 11, 2025

not easy to understand the VGA operation!

You can say that again! I'm still struggling with "one last" Nano-X drawing bug and the NX drivers use two different VGA write modes, definitely hard to keep it all straight.

may be I can also choose a CGA mode, so we cover virtually all supported ELKS graphics configuration? I can see the options are modes 0x4, 0x5 and 0x6. May be mode 0x5?

Nano-X is using mode 6 (640x480 2-color monochrome), but frankly it's pretty ugly, unless I suppose you're actually on a B&W screen (does anybody even have or use those anymore?) This choice was probably made because running Nano-X on 320x200 (4-color mode 4 or 4-greyscale mode 5) doesn't really cut it for a windowing system.

I would say that for images it would be better to support 320x200 4-color mode 4. But frankly, its also hardly worth it. For those tinkerers that might actually want to see something graphical on their CGA, we have Nano-X, but I doubt anyone actually uses that either.

I plan to use function pointers, as you suggested, in order to keep the code clean with all different compilers, modes and color space conversions.

Cool! It would be nice to try to keep application source code clean by using a single unchanging function call name, like fill_rect or draw_pixel for instance, that automatically binds with a set of function pointers possibly using a macro, or something trickier keeping compiler or display differences in a single struct, all with the same name. Nothing would have to be initialized with separate calls nor using tons of . or -> member access stuff in the applications graphics source, and the linker would essentially bind the function pointers to their appropriate routines at link time, providing fast access using a single function pointer and no other indirections.

@rafael2k
Copy link
Contributor Author

not easy to understand the VGA operation!

You can say that again! I'm still struggling with "one last" Nano-X drawing bug and the NX drivers use two different VGA write modes, definitely hard to keep it all straight.

After some days of struggling I managed to get the 4-bit mode working with optimized palette for grayscale or embedded image palette. I think I'll stop adding support for new modes for modes, at least for now. Still some bugs here and there, but things are starting to settle down.

may be I can also choose a CGA mode, so we cover virtually all supported ELKS graphics configuration? I can see the options are modes 0x4, 0x5 and 0x6. May be mode 0x5?

Nano-X is using mode 6 (640x480 2-color monochrome), but frankly it's pretty ugly, unless I suppose you're actually on a B&W screen (does anybody even have or use those anymore?) This choice was probably made because running Nano-X on 320x200 (4-color mode 4 or 4-greyscale mode 5) doesn't really cut it for a windowing system.

Nice to know. I'm eager to see the Nano-X window manager.
: )

I would say that for images it would be better to support 320x200 4-color mode 4. But frankly, its also hardly worth it. For those tinkerers that might actually want to see something graphical on their CGA, we have Nano-X, but I doubt anyone actually uses that either.

Agreed. I'll leave CGA support for later if there is interest.

I plan to use function pointers, as you suggested, in order to keep the code clean with all different compilers, modes and color space conversions.

Cool! It would be nice to try to keep application source code clean by using a single unchanging function call name, like fill_rect or draw_pixel for instance, that automatically binds with a set of function pointers possibly using a macro, or something trickier keeping compiler or display differences in a single struct, all with the same name. Nothing would have to be initialized with separate calls nor using tons of . or -> member access stuff in the applications graphics source, and the linker would essentially bind the function pointers to their appropriate routines at link time, providing fast access using a single function pointer and no other indirections.

Indeed, that is the plan. For draw_pixel and palette conversion functions.
At link time and runtime, with some function pointer configuration in "set_mode()" and some at "load_palette" (or something like this), all hid under the hood.

@rafael2k
Copy link
Contributor Author

@ghaerr, I noticed you updated recently the vga 4bp code in nano-x. Should I bring it to elks-viewer, or the optimization is irrelevant for such usage?

@ghaerr
Copy link
Owner

ghaerr commented Feb 19, 2025

I noticed you updated recently the vga 4bp code in nano-x. Should I bring it to elks-viewer, or the optimization is irrelevant for such usage?

No, the optimizations in Nano-X were in the VGA-to-VGA blit code, which is used for scrolling the VGA RAM. Also, the toolchain VGA graphics code is written in AS86 assembly while the Nano-X driver is written completely in C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants