Help loading GR, HGR, DGR AND DHGR image files in the Apple II

  • Nominations will close March 25th. If you'd like to join the board and influence how TinkerDifferent runs in the next year, put your name in now!
  • Hey Guest, MARCHintosh 2026 is upon us. Check out community projects, join GlobalTalk, and have fun!

joevt

Tinkerer
Mar 5, 2023
249
100
43
Are there any conversion programs that emulate a color composite display's NTSC interpretation of the Apple II's 14MHz square wave by emulating the analog circuitry or at least performing the same signal transformations?
https://codeandlife.com/2012/10/09/composite-video-decoding-theory-and-practice/
http://www.kreativekorp.com/miscpages/a2info/munafo.shtml
Found this:
https://www.reddit.com/r/software/comments/hh0pt8 which points to:
https://www.reenigne.org/misc/ntsc.zip
which exists on GitHub at:
https://github.com/reenigne/reenigne/tree/master/ntsc

It has two command line executables avi2ntsc.exe and ntsc2avi.exe.

ntsc2avi.exe converts 8 bit PCM 14.318MHz sampled composite video to AVI using FFTs, etc.

It should be simple enough to generate the source samples from the Apple II video buffers. I don't know how well it works or what the results look like.

I suppose you could use avi2ntsc.exe to create some test data for ntsc2avi.exe and to see what the source samples from the Apple II should look like (the sync and color burst parts - everything else comes directly from Apple II memory).

For an Apple II emulator, the ntsc2avi scan line code should probably be converted to a pixel shader.
 
Last edited:

InexorableTash

New Tinkerer
Mar 20, 2026
1
0
1
Hey there, unofficial maintainer of A2DeskTop here. Sorry for the bump.

I tried A2D using Apple //jse. I don't think A2D supports loading lores or double lores images?

At the time, no, but it was added in v1.5, released in 2025.

There is source code for the HR to DHR conversion routine at https://github.com/a2stuff/a2d/blob...a1171a799b7a4/desk.acc/show.image.file.s#L525
I'm not sure if it's totally accurate. It uses lookup tables for address calculation and for HR to DHR bit conversion. Neither are necessary but might speed up the conversion process. I'm not sure what it's doing with hibitset and spill bits. It works from right to left which is opposite from the way I would do it.
If you work left-to-right and consider the half-pixel shift introduced by the high or "palette" bit in the HR data, then you can construct a perfect 560x192 monochrome DHR representation of the HR screen by sometimes spilling a bit from one byte-pair to the next byte-pair on the right. If you only care about monochrome displays then this is sufficient. But if you look at the result with a color display then the colors are wrong!

This is because for rows displayed with the 80-column hardware (text, DLR, DHR; can be mixed with 80-col text and single-hires/single-lores) the display starts 7 "pixels" earlier. You can see this on real hardware, although most emulators skip it. This means the alignment of the monochrome bitmap and the NTSC color clock shifts by something other than 4 "pixels", so suddenly instead of 11001100... on the left edge of the screen being interpreted by the display hardware as purple it gets interpreted as blue!

To compensate for this, you can either shift the entire display 3 bits to the right or 1 bit to the left. Either way syncs up the monochrome bitmap with the color clock and you get the right colors again. You've lost either 1 or 3 bits columns of monochrome data but that's life. Shifting 1 bit to the left loses less data, so that's probably the way to go. So now our process for each row is:
  • Step 1: Work left-to-right, converting each HR byte to a DHR byte pair, and *sometimes* spill a bit to the right across byte boundaries, depending on the "palette" bit.
  • Step 2: Work right-to-left, shifting everything a bit to the left, and *always* spill a bit to the left across byte boundaries.
If you want to minimize the work then you should do these both at once using a lookup table, which means you work right-to-left and *sometimes* spill a bit to the *left*. Hopefully that explains why my code does it that way.

I realized my code had some mishandled edge cases so I just reimplemented it and made the documentation clearer. Thanks for looking! The tweaks will be in the next v1.6 build.

The loading process for a DHGR image is simpler. It loads main first. Copies that to aux? Then loads aux. Both are by address order. Maybe that's for one type of DHGR image (the butterfly sample). Probably need to slow it down to be sure. Or try to decipher that assembly source file.

There were two standards for DHR files; single file with aux first then main, or two files. By far the most common was the single file. The aux half is first as ProDOS can load directly into main memory but not aux memory, so first load the aux half into main memory, copy memory main>aux, then load the main half and you're done.
 

joevt

Tinkerer
Mar 5, 2023
249
100
43
Right. DHR and HR may have the same width, but they start at different phases of the color clock. Because of this, for the purpose of converting HR to DHR for display in a Apple II, some monochrome data must be lost in the process.

You don't need to lose monochrome data when converting to a non-Apple II format or when displaying the data in an Apple II emulator in a matching display mode.

Do DHR and HR actually have the same width? One would need a real Apple II to test:
Code:
HGR : HCOLOR=7 : HPLOT 279,0 to 279,191
Is the resulting color a HR color (50% DHR color) or a darker (25%) DHR color? If the former, then that means HR width is 561 DHR dots instead of 560.

Another similar test:
Code:
HGR : HCOLOR=7 : HPLOT 278,0 to 278,191 : HPLOT 279,0 to 279,191
Is the resulting color white? Or is the color a light (75%) DHR color? If the former, then that means HR width is 561 DHR dots instead of 560.

Each HR byte produces 14 DHR bits or dots. Each HR dot becomes two DHR dots.

If the palette bit is 1 then it shifts all the dots in the current HR byte right by half a HR dot (one DHR bit). The left most DHR dot is copied from the right most dot of the previous HR byte (regardless of the previous byte's palette bit).

If the palette bit is 0 then there is no extending/copying of the right most dot of the previous HR byte.

Using the above rules, you see that when the palette bit does not change, the HR dots are output as expected and you get normal HR colors (50% brightness).

If the palette bit changes from 1 to 0, then it can truncate the right most HR dot of the previous HR byte.
If the palette bit changes from 0 to 1, then it can extend the right most HR dot of the previous HR byte.
These effects can result in a DHR color (25% or 75% brightness).