White suckers were running the weir on Highland Creek in Morningside Park on their way upstream to spawn.
Blog
-
it’s white sucker time!
lookit that little sucker go! white suckers say hello -
plotter stuff on that Brother printer
That Brother laser printer you bought can also pretend it’s a plotter. One of the requirements embedded in a PCL-compatible printer is an implementation of HP-GL/2. This is a slightly modified version of the page description language used by HP’s pen plotters. With care, you can make proofs on a laser printer.
Take, for example, this figure drawn in HP-GL:
just some plotter stuff … It’s made up of familiar commands:
IN;SP1;PU4318,5988;
PD3971,5388,4664,5388,4318,5988;
PU4279,6026;
PD3957,5335,4716,5402,4279,6026;
…But add some magic header bytes (0x1b, 0x45, 0x1b, 0x25, 0x30, 0x42) and some trailer bytes (0x1b, 0x25, 0x30, 0x41, 0x1b, 0x45), and your printer understands it’s a PCL file.
The file, complete with header and trailer, is here:
You can print it like this:
lp -o raw hpgl-rotatey.hpgl
which produces a page like this:
full page scan of that HP-GL file as printed on a Brother MFC-L2750DW HP-GL/2, on mono lasers at least, has some differences to the version used on plotters. The biggest difference is that there’s just one pen. You can change the pattern and line attributes of this pen, but you don’t get to change to multiple pens with different colours.
The manual for Brother’s HP-GL implementation lives here: Chapter 4: HP-GL/2 Graphics Language. Happy plotting!
-
SparkFun Retired Products Archive Reference
In late 2024, SparkFun Electronics relaunched their website. In doing so, they deleted roughly 20 years of archived product information, along with all associated datasheets, schematics and tutorials. Luckily, the Internet Archive’s Wayback Machine has good records of the site, and I was able to recover links to 5934 deleted products.
So here’s the list: SparkFun Retired Products Archive Reference.
-
Snow-loving Solar Marble Machine
Martin Raynsford / Solarbotics Solar Marble Machine loving glare off deep snow Still going strong after more than a decade in the front window, the Solar Marble Machine has been running flat out all day because of the glare from the deep snow outside. It might normally do one click a day, if any at all.
-
MicroPython Benchmarks
Somewhat predictably, my Parallel MicroPython Benchmarking thing got out of hand, and I’ve been scrabbling around jamming the benchmark code on every MicroPython board I can find.
So despite WordPress’s best efforts in thwarting me from having a table here, my results are as follows, from fastest to slowest:
Board Interpreter CPU @ Frequency / MHz Time / s DevEBox STM32H7xx micropython 1.20.0 STM32H743VIT6 @ 400 3.7 Metro M7 micropython 1.24.1 MIMXRT1011DAE5A @ 500 4.3 S3 PRO micropython 1.25.0.preview ESP32S3 @ 240 8.9 Raspberry Pi Pico 2 W micropython 1.25.0.preview RP2350 @ 150 10.3 ItsyBitsy M4 Express micropython 1.24.1 SAMD51G19A @ 120 12.3 pyboard v1.1 micropython 1.24.1 STM32F405RG @ 168 13.0 C3 mini micropython 1.25.0.preview ESP32-C3FH4 @ 160 13.2 HUZZAH32 – ESP32 micropython 1.24.1 ESP32 @ 160 15.4 S2 mini micropython 1.25.0.preview ESP32-S2FN4R2 @ 160 17.4 Raspberry Pi Pico W micropython 1.24.1 RP2040 @ 125 19.8 WeAct BlackPill STM32F411CEU6 micropython 1.24.0.preview STM32F411CE @ 96 21.4 W600-PICO micropython 1.25.0.preview W600-B8 @ 80 30.7 LOLIN D1 mini micropython 1.24.1 ESP8266 @ 80 45.6 Yes, I was very surprised that the DevEBox STM32H7 at 400 MHz was faster than the 500 MHz MIMXRT1011 in the Metro M7. What was even more impressive is that the STM32H7 board was doing all the calculations in double precision, while all the others were working in single.
As for the other boards, the ESP32 variants performed solidly, but the ESP8266 in last place should be retired. The Raspberry Pi Pico 2 W was fairly nippy, but the original Raspberry Pi Pico is still a lowly Cortex-M0+, no matter how fast you clock it. The STM32F4 boards were slower than I expected them to be, frankly. And yay! to the plucky little W600: it comes in second last, but it’s the cheapest thing out there.
All of these benchmarks were made with the same code, but with two lines changed:
- The I2C specification, which is a minor syntax change for each board;
- The input trigger pin. Some boards like these as numbers, some take them as strings. Pro tip for W600 users: don’t use D0 for an input that’s tied to ground, unless you want the board to go into bootloader mode …
I’d hoped to run these tests on the SAMD21 little micro-controllers (typically 48 MHz Cortex-M0), but they don’t have enough memory for MicroPython’s framebuf module, so it’s omitted from the build. They would likely have been very slow, though.
In the spirit of fairness, I also benchmarked CircuitPython on a Arduino Nano RP2040 Connect, which has the same processor as a Raspberry Pi Pico:
Board Interpreter CPU @ Frequency / MHz Time / s Arduino Nano RP2040 Connect circuitpython 9.2.3 RP2040 @ 125 18.0 So it’s about 10% quicker than MicroPython, but I had to muck around for ages fighting with CircuitPython’s all-over-the-shop documentation and ninny syntax changes. For those that like that sort of thing, I guess that’s the sort of thing they like.
-
The 100 Doors Problem, on a very small computer
The 100 Doors problem running on an (emulated) unexpanded ZX81 I learned about this simple computer problem from Michael Doornbos: Just for fun, the 100 door problem on several different systems
Yeah, it’s pretty neat to be able to do that on a Commodore VIC-20 with 5K of RAM. But how about a ZX81 with only 1K? With screen memory that moves around depending on how much stuff you have on the screen? No problem:
that’s it: that’s the whole program The tricky part is printing just enough to the screen that you have enough memory to store the array and still have enough memory for you program. I did that by printing four lines of “🮐” characters (CHR$ 136 on the ZX81, U+1FB90) and moving the cursor down just far enough that later output wouldn’t zap my data. The screen address (given by the D_FILE pointer at 16396) is used as an array of 100 characters.
The ZX81’s (non-ASCII) character set has a nice quirk that Space is CHR$ 0, and inverse video Space (“█”) is at CHR$ 128. So you can use NOT to toggle the value.
Here’s the program listing, with Unicode characters:
10 REM 100DOORS1K SCRUSS 2025 20 FOR I=1 TO 128 30 PRINT "🮐"; 40 NEXT I 50 PRINT AT 3,0;"🮐" 60 LET D=PEEK 16396+PEEK 16397*256 70 FOR J=1 TO 100 80 POKE D+J,0 90 NEXT J 100 FOR I=1 TO 100 110 FOR J=I TO 100 STEP I 120 POKE D+J,128*NOT PEEK (D+J) 130 NEXT J 140 NEXT I 150 FOR I=1 TO 100 160 IF PEEK (D+I) THEN PRINT I, 170 NEXT I
The ZX81 program image plus the listing in zmakebas format is included here:
-
Parallel MicroPython Benchmarking
On the left, a Raspberry Pi Pico 2W. On the right, a Raspberry Pi Pico. Each is connected to its own small OLED screen. When a button is pressed, both boards calculate and display the Mandelbrot set, along with its completion time. Needless to say, the Pico 2 W is quite a bit quicker. the before screens … Pico 2 comes in at 10.3 seconds, original Pico at 19.8 seconds Stuff I found out setting this up:
- some old OLEDs, like these surplus pulse oximeter ones, don’t have pull-up resistors on their data lines. These I’ve carefully hidden behind the displays, but they’re there.
- Some MicroPython ports don’t include the complex type, so I had to lose the elegant z→z²+C mapping to some ugly code.
- Some MicroPython ports don’t have os.uname(), but sys.implementation seems to cover most of the data I need.
- On some boards, machine.freq() is an integer value representing the CPU frequency. On others, it’s a list. Aargh.
These displays came from the collection of the late Tom Luff, a Toronto maker who passed away late 2024 after a long illness. Tom had a huge component collection, and my way of remembering him is to show off his stuff being used.
Source:
# benchmark Mandelbrot set (aka Brooks-Matelski set) on OLED # scruss, 2025-01 # MicroPython # -*- coding: utf-8 -*- from machine import Pin, I2C, idle, reset, freq # from os import uname from sys import implementation from ssd1306 import SSD1306_I2C from time import ticks_ms, ticks_diff # %%% These are the only things you should edit %%% startpin = 16 # pin for trigger configured with external pulldown # I2C connection for display i2c = machine.I2C(1, freq=400000, scl=19, sda=18, timeout=50000) # %%% Stop editing here - I mean it!!!1! %%% # maps value between istart..istop to range ostart..ostop def valmap(value, istart, istop, ostart, ostop): return ostart + (ostop - ostart) * ( (value - istart) / (istop - istart) ) WIDTH = 128 HEIGHT = 64 TEXTSIZE = 8 # 16x8 text chars maxit = 120 # DO NOT CHANGE! # value of 120 gives roughly 10 second run time for Pico 2W # get some information about the board # thanks to projectgus for the sys.implementation tip if type(freq()) is int: f_mhz = freq() // 1_000_000 else: # STM32 has freq return a tuple f_mhz = freq()[0] // 1_000_000 sys_id = ( implementation.name, ".".join([str(x) for x in implementation.version]).rstrip( "." ), # version implementation._machine.split()[-1], # processor "%d MHz" % (f_mhz), # frequency "%d*%d; %d" % (WIDTH, HEIGHT, maxit), # run parameters ) p = Pin(startpin, Pin.IN) # displays I have are yellow/blue, have no pull-up resistors # and have a confusing I2C address on the silkscreen oled = SSD1306_I2C(WIDTH, HEIGHT, i2c) oled.contrast(31) oled.fill(0) # display system info ypos = (HEIGHT - TEXTSIZE * len(sys_id)) // 2 for s in sys_id: ts = s[: WIDTH // TEXTSIZE] xpos = (WIDTH - TEXTSIZE * len(ts)) // 2 oled.text(ts, xpos, ypos) ypos = ypos + TEXTSIZE oled.show() while p.value() == 0: # wait for button press idle() oled.fill(0) oled.show() start = ticks_ms() # NB: oled.pixel() is *slow*, so only refresh once per row for y in range(HEIGHT): # complex range reversed because display axes wrong way up cc = valmap(float(y + 1), 1.0, float(HEIGHT), 1.2, -1.2) for x in range(WIDTH): cr = valmap(float(x + 1), 1.0, float(WIDTH), -2.8, 2.0) # can't use complex type as small boards don't have it dammit) zr = 0.0 zc = 0.0 for k in range(maxit): t = zr zr = zr * zr - zc * zc + cr zc = 2 * t * zc + cc if zr * zr + zc * zc > 4.0: oled.pixel(x, y, k % 2) # set pixel if escaped break oled.show() elapsed = ticks_diff(ticks_ms(), start) / 1000 elapsed_str = "%.1f s" % elapsed # oled.text(" " * len(elapsed_str), 0, HEIGHT - TEXTSIZE) oled.rect( 0, HEIGHT - TEXTSIZE, TEXTSIZE * len(elapsed_str), TEXTSIZE, 0, True ) oled.text(elapsed_str, 0, HEIGHT - TEXTSIZE) oled.show() # we're done, so clear screen and reset after the button is pressed while p.value() == 0: idle() oled.fill(0) oled.show() reset()
(also here: benchmark Mandelbrot set (aka Brooks-Matelski set) on OLED – MicroPython)
I will add more tests as I get to wiring up the boards. I have so many (too many?) MicroPython boards!
Results are here: MicroPython Benchmarks
-
Trumpeter swan got harmonics
Isolated Trumpeter swan «parp» call, recorded at Bluffer’s Park, Toronto at 2025-01-09, 13:41 via Merlin app on Google Pixel 8 -
BASIC-52 on tiny CH552 boards via Linux
draft post, published for usefulness not polish
- Hackaday project that introduced me to the CH552 and BASIC-52: Single Chip Computer | Hackaday.io
- Boards that I’ve got this working with:
- CH552 Stick from DeqingSun/ch55xduino (DIY, surface-mount);
- WeAct Studio CH552T Mini Core Board;
- WAVGAT CH552G core board.
- You’ll need an external USB UART with 5 V power, as this port doesn’t support serial over the USB
- Project source (yes, it’s a Google Drive link, and any docs are in Japanese): CH55x. If that doesn’t work, the original redirect is from CH552Eでモニタプログラムを動かしてみました | きょうのかんぱぱ. If neither of those work, here’s my download of the archive: CH552T&G-20240805T141304Z-001.zip
- Binary file (zip): basic52s.bin (hiyodori5, version 7th May 2023, converted from hex so you don’t have to mess with srec_cat or makebin)
- Build cjacker/ch55x-isptool to upload the code (which doesn’t accept hex)
- Plug in the CH552 board. You may have to do something with the boot/reset button to make it turn up as the right USB ID (4348:55e0 WinChipHead).
- Program the board:
sudo ch55xisptool basic52s.bin
- Disconnect the board, and wire it up to the USB UART (5 V, GND, TX → RXD [P3.0], RX → TXD [P3.1])
- Hit return a few times to get a prompt
- PWM is on P1.2, INT1 is on P3.3. See Hackaday project to see how to access I²C, and also do things with SFR values using RDSFR / WRSFR. PORT3 is at SFR(0B0H)
please ignore the following for now …
Who wouldn’t want to run a solid BASIC interpreter on a $3 development board? So maybe there are a couple of drawbacks:
- there’s no way to save the program to non-volatile storage: you have to be connected through a serial terminal at all times; and
- you’ve got about 600 bytes for the whole program, with no way to expand it.
Despite these limitations, there’s some futile fun to be had. I’ll show you how to flash BASIC-52 onto one of these development boards, and give a quick intro to what you can do with it.
BASIC-52
Intel released the first version of BASIC-52 for their 8051 family of microcontrollers in 1984. They produced a chip (8052AH-BASIC) with the interpreter burnt into mask ROM in 1985. The source code was released into the public domain, and various features such as I²C support were added by the community around 2000.
As befits an embedded language, BASIC-52 supports pin management, timers and interrupts. It’s also a fairly full-featured BASIC interpreter with floating point support and mostly familiar keywords and functions. Because it’s designed for very limited memory use, its string handling is quite unlike any other BASIC dialect. It has one character array that you can treat as a string, and a few functions to work with characters, but that’s about all.
A most useful reference is Intel’s MCS BASIC-52 Versions 1 & 1.1 Operating and Reference Manual. Another helpful guide is Jan Axelson’s The Microcontroller Idea Book. I found out about both of these references from Single Chip Computer — Hackaday.io, which also introduced me the possibilities of running BASIC-52 on the CH552.
CH552
Deqing Sun’s CH552 Stick, from the ch55xduino project You might know WCH (aka QinHeng Electronics) from their inexpensive CH341 USB serial adapters and other interface boards. What you might not realize is that all of their older interface chips are based on an optimized 8051 design
-
Cheap NeoPixels at the Dollar Store
Exhibit A:
also known as “Monster BASICS Sound reactive RGB+IC Color Flow LED strip”. It’s $5 or so at Dollarama, and includes a USB cable for power and a remote control. It’s two metres long and includes 60 RGB LEDs. Are these really super-cheap NeoPixel clones?
I’m going to keep the USB power so I can power it from a power bank, but otherwise convert it to a string of smart LEDs. We lose the remote control capability.
Pull back the heatshrink at the USB end:
… and there are our connectors. We want to disconnect the blue Din (Data In) line from the built in controller, and solder new wires to Din and GND to run from a microcontroller board.
Maybe not the best solder job, but there are new wires feeding through the heatshrink and soldered onto the strip.
Here’s the heatshrink pushed back, and everything secured with a cable tie.
Now to feed it from standard MicroPython NeoPixel code, suitably jazzed up for 60 pixels.
A pretty decent result for $5!
-
Brother Canada laser cartridge return label
Hey! I made this post in mid 2024. I suspect this label will expire someday. I have no way of checking. So if this label still works, great. I’ve had one possible report of it not working in March 2025. Also, I can’t help you with other Brother supplies, so don’t ask.
So you bought that Brother laser printer like everyone told you to. And now it’s out of toner, so you replaced the cartridge. If you were in the USA, you could return the cartridge for free using the included label. But in Canada … it’s a whole deal including registering with Brother and giving away your contact details and, and, and …
Anyway, my dear fellow Canadians, I went through the process and downloaded the label PDF so you don’t have to:
If you have an address label printer (large, 100×150 mm-ish), I even cropped it for you (with only minor font corruption):
I’m pretty sure this is a generic label, since:
- the file is dated sometime in 2023, and wasn’t generated directly for my download;
- I got a different tracking number when I handed the thing in at the post office.
But if they do complain, you know what to do: brother.ca/en/environment
-
Thousand Days: Concept
reference copy: Thousand Days: Concept on github.
Stewart Russell – scruss.com — 2024-03-26, at age 19999 days …
Summary
One’s thousand day(s) celebration occurs every thousand days of a person’s life. They are meant to be a recognition of getting this far, and are celebrated at the person’s own discretion.
Who is this for?
- Maybe your birthday’s on a day associated with an unpleasant event. Your thousand day will never coincide with your birthday.
- Maybe your birthday’s in the middle of winter, or in another part of the year that you’re not keen on. Your thousand day is every 2 years and 3 seasons, so it shifts back by a season every time it happens.
Quantities and scale
1000 days is approximately:
- 2.738 years
- 2 years 269 days
- 2 years 8.85 months
- 2 years, 3 seasons.
4000 days is just shy of 11 years.
Disadvantages
Compared to regular birthdays, thousand days:
- must be calculated; they’re not intuitive when they’re going to happen. But we have computers and calendar reminders for that …
- can be used to work out your actual date of birth, if someone knows that you’re going to be x000 days old on a particular day. It’s possible to know someone’s birthday, but not know their age.
Implementations
Web
My ancient Your 1000 Day Birthday Calculator, first published in 2002 and untouched since 2010.
Shell
So it turns out that GNU date can handle arbitrary date maths quite well. For example:
date --iso-8601=date --date="1996-11-09 + 10000 days"
returns 2024-03-27.
Other Ways
Excel or any other spreadsheet will do, too. Although not for too many years back
People with the same thousand day as you
This is an idea for finding people who have a thousand day on the same day as you. I suggest using 1851-10-01 as a datum, because:
- nothing particularly interesting happened that day;
- it’s conveniently 43000 days before my birthday.
then calculate
( (birth_date - 1851-10-01) mod 1000 ) + 1
This results in a number 1 – 1000. Everyone with the same number shares a 1000 day birthday with you.
Why not 0 – 999?
- No-one deserves to be a zero;
- Wouldn’t be much of a thousand day if it only went up to 999, would it?
Incomplete list of people with day = 1
There are more, but these were found from Wikipedia’s year pages
- Momo Hirai, born 1996-11-09 (-10000 days)
- Memphis Depay, born 1994-02-13 (-9000 days)
- Mikhail Surenovich Aloyan, born 1988-08-23 (-7000 days)
- Segundo Cernadas, born 1972-03-20 (-1000 days)
- Sissel Kyrkjebø, born 1969-06-24 (0 days)
- Maria Canals-Barrera, born 1966-09-28 (+1000 days)
- Pernell Whitaker, born 1964-01-02 (+2000 days)
- [DONDI](https://en.wikipedia.org/wiki/Dondi_(graffiti_artist%29), born 1961-04-07 (+3000 days)
- Richard Legendre, born 1953-01-19 (+6000 days)
- Arnold Schwarzenegger, born 1947-07-30 (+8000 days)
- Keith Emerson, born 1944-11-02 (+9000 days)
- Harvey Keitel, born 1939-05-13 (+11000 days)
- Agnès Varda, born 1928-05-30 (+15000 days)
- Shoista Mullojonova, born 1925-09-03 (+16000 days)
- Lucian Freud, born 1922-12-08 (+17000 days)
- Huang Feili, born 1917-06-17 (+19000 days)
- Kikuko, Princess Takamatsu, born 1911-12-26 (+21000 days)
- Walter O’Malley, born 1903-10-09 (+24000 days)
- León de Greiff, born 1895-07-22 (+27000 days)
- Geoffrey Fisher, born 1887-05-05 (+30000 days)
- Sara Teasdale, born 1884-08-08 (+31000 days)
- Lee de Forest, born 1873-08-26 (+35000 days)
- Carl Nielsen, born 1865-06-09 (+38000 days)
Licence
🅭 2024, Stewart Russell, scruss.com
This work is licensed under CC BY-SA 4.0.
There are no trademarks, patents, official websites, social media or official anythings attached to this concept. Please take the idea and do good with it.
So why aren’t you implementing this further?
I’ve had this idea kicking around my head for at least the last 20 years. For $REASONS, it turns out I’m not very good at implementing stuff. I’d far rather someone else took this idea and ran with it than let it sit undeveloped.
-
We used to have to *wait* for our memes …
It must be Wednesday somewhere … If you really must try this on your on Amstrad CPC:
-
“… that Chinese instruction manual font”
(this is an old post from 2021 that got caught in my drafts somehow)
Mike asked:
To which I suggested:
Not very helpful links, more of a thought-dump:
- Why does Chinese printing of Latin characters always use the same font? – writing fonts curiosity | Ask MetaFilter
- The Roman typefaces used in Chinese and Japanese text | Hacker News
First PostScript font: STSong (华文宋体) was released in 1991, making it the first PostScript font by a Chinese foundry [ref: Typekit blog — Pan-CJK Partner Profile: SinoType]. But STSong looks like Garamond(ish).
Maybe source: GB 5007.1-85 24×24 Bitmap Font Set of Chinese Characters for Information Exchange. Originally from 1985, this is a more recent version: GB 5007.1-2010: Information technology—Chinese ideogram coded character set (basic set)—24 dot matrix font.
-
The Potato
… is a thing to help soldering DIN connectors. I had some made at JLCPCB, and will have them for sale at World of Commodore tomorrow.
Sven Petersen’s “The Potato” – front. DIN7 connector not included Sven Petersen’s “The Potato” – back You can get the source from svenpetersen1965/DIN-connector_soldering-aid-The-Potato. I had the file Rev. 0/Gerber /gerber_The_Potato_noFrame_v0a.zip made, and it seems to fit connector pins well.
Each Potato is made up of two PCBs, spaced apart by a nylon washer and held together by M3 nylon screws.
-
can we…?
This is a mini celebratory post to say that I’ve fixed the database encoding problems on this blog. It looks like I will have to go through the posts manually to correct the errors still, but at least I can enter, store and display UTF-8 characters as expected.
“? µ ° × — – ½ ¾ £ é?êè”, he said with some relief.
Postmortem: For reasons I cannot explain or remember, the database on this blog flipped to an archaic character set: latin1, aka ISO/IEC 8859-1. A partial fix was effected by downloading the entire site’s database backup, and changing all the following references in the SQL:
- CHARSET=latin1 → CHARSET=utf8mb4
- COLLATE=latin1_german2_ci → COLLATE=utf8mb4_general_ci
- COLLATE utf8mb4_general_ci → COLLATE utf8mb4_general_ci
- latin1_general_ci → utf8mb4_general_ci
- COLLATE latin1_german2_ci → COLLATE utf8mb4_general_ci
- CHARACTER SET latin1 → CHARACTER SET utf8mb4
For additional annoyance, the entire SQL dump was too big to load back into phpmyadmin, so I had to split it by table. Thank goodness for awk!
#!/usr/bin/awk -f BEGIN { outfile = "nothing.sql"; } /^# Table: / { # very special comment in WP backup that introduces a new table # last field is table_name, # which we use to create table_name.sql t = $NF gsub(/`/, "", t); outfile = t ".sql"; } { print > outfile; }
The data still appears to be confused. For example, in the post Compose yourself, Raspberry Pi!, what should appear as “That little key marked “Compose”” appears as “That little key marked “Composeâ€Â”. This isn’t a straight conversion of one character set to another. It appears to have been double-encoded, and wrongly too.
Still, at least I can now write again and have whatever new things I make turn up the way I like. Editing 20 years of blog posts awaits … zzz
-
Autumn in Canada: NAPLPS
NAPLPS rendered in PP3 My OpenProcessing demo “autumn in canada”, redone as a NAPLPS playback file. Yes, it would have been nice to have outlined leaves, but I’ve only got four colours to play with that are vaguely autumnal in NAPLPS’s limited 2-bit RGB.
Played back via dosbox and PP3, with help from John Durno‘s very useful Displaying NAPLPS Graphics on a Modern Computer: Technical Note.
This file only displays 64 leaves, as more leaves caused the emulated Commodore 64 NAPLPS viewer I was running to crash.
-
The glorious futility of generating NAPLPS in 2023
Yeah! Actual real NAPLPS made by me! NAPLPS — an almost-forgotten videotex vector graphics format with a regrettable pronunciation (/nap-lips/, no really) — was really hard to create. Back in the early days when it was a worthwhile Canadian initiative called Telidon (see Inter/Access’s exhibit Remember Tomorrow: A Telidon Story) it required a custom video workstation costing $$$$$$. It got cheaper by the time the 1990s rolled round, but it was never easy and so interest waned.
I don’t claim what I made is particularly interesting:
suspiciously canadian but even decoding the tutorial and standards material was hard. NAPLPS made heavy use of bitfields interleaved and packed into 7 and 8-bit characters. It was kind of a clever idea (lower resolution data could be packed into fewer bytes) but the implementation is quite unpleasant.
A few of the references/tools/resources I relied on:
- The NAPLPS: videotex/teletext presentation level protocol syntax standard. Long. Quite dull and abstract, but it is the reference
- The 1983 BYTE Magazine article series NAPLPS: A New Standard for Text and Graphics. Also long and needlessly wordy, with digressions into extensions that were never implemented. Contains a commented byte dump of an image that explains most concepts by example
- Technical specifications for NAPLPS graphics — aka NAPLPS.ASC. A large text file explaining how NAPLPS works. Fairly clear, but the ASCII art diagrams aren’t the most obvious
- TelidonP5 — an online NAPLPS viewer. Not perfect, but helpful for proofing work
- Videotex – NAPLPS Client for the Commodore 64 Archived — a terminal for the C64 that supports (some) NAPLPS. Very limited in the size of file it can view
- John Durno has spent years recovering Telidon / NAPLPS works. He has published many useful resources on the subject
Here’s the fragment of code I wrote to generate the NAPLPS:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # draw a disappointing maple leaf in NAPLPS - scruss, 2023-09 # stylized maple leaf polygon, quite similar to # the coordinates used in the Canadian flag ... maple = [ [62, 2], [62, 35], [94, 31], [91, 41], [122, 66], [113, 70], [119, 90], [100, 86], [97, 96], [77, 74], [85, 114], [73, 108], [62, 130], [51, 108], [39, 114], [47, 74], [27, 96], [24, 86], [5, 90], [11, 70], [2, 66], [33, 41], [30, 31], [62, 35], ] def colour(r, g, b): # r, g and b are limited to the range 0-3 return chr(0o74) + chr( 64 + ((g & 2) << 4) + ((r & 2) << 3) + ((b & 2) << 2) + ((g & 1) << 2) + ((r & 1) << 1) + (b & 1) ) def coord(x, y): # if you stick with 256 x 192 integer coordinates this should be okay xsign = 0 ysign = 0 if x < 0: xsign = 1 x = x * -1 x = ((x ^ 255) + 1) & 255 if y < 0: ysign = 1 y = y * -1 y = ((y ^ 255) + 1) & 255 return ( chr( 64 + (xsign << 5) + ((x & 0xC0) >> 3) + (ysign << 2) + ((y & 0xC0) >> 6) ) + chr(64 + ((x & 0x38)) + ((y & 0x38) >> 3)) + chr(64 + ((x & 7) << 3) + (y & 7)) ) f = open("maple.nap", "w") f.write(chr(0x18) + chr(0x1B)) # preamble f.write(chr(0o16)) # SO: into graphics mode f.write(colour(0, 0, 0)) # black f.write(chr(0o40) + chr(0o120)) # clear screen to current colour f.write(colour(3, 0, 0)) # red # *** STALK *** f.write( chr(0o44) + coord(maple[0][0], maple[0][1]) ) # point set absolute f.write( chr(0o51) + coord(maple[1][0] - maple[0][0], maple[1][1] - maple[0][1]) ) # line relative # *** LEAF *** f.write( chr(0o67) + coord(maple[1][0], maple[1][1]) ) # set polygon filled # append all the relative leaf vertices for i in range(2, len(maple)): f.write( coord( maple[i][0] - maple[i - 1][0], maple[i][1] - maple[i - 1][1] ) ) f.write(chr(0x0F) + chr(0x1A)) # postamble f.close()
There are a couple of perhaps useful routines in there:
colour(r, g, b)
spits out the code for two bits per component RGB. Inputs are limited to the range 0–3 without error checkingcoord(x, y)
converts integer coordinates to a NAPLPS output stream. Best limited to a 256 × 192 size. Will also work with positive/negative relative coordinates.
Here’s the generated file: