Specifically, MacPaint files. Why MacPaint? 1. With a maximum size of 576 pixels wide by 720 pixels tall, MacPaint images fit on a modern spreadsheet when using cells as pixels, and 2. MacPaint prototyped the lossless PackBits compression algorithm, which is the default compression scheme for TIFF.
PackBits is a signed-byte file structure with flag-counter bytes interspersed with data bytes. If the flagbyte is negative, the next byte (always a data byte) is repeated, zero-based, the two’s-complement of the flagbyte times. If the flagbyte is a positive number, the next number of bytes are copied directly. Each data byte represents the status of eight pixels, with 1 indicating a black pixel and 0 representing a white pixel, as shown below.
The PackBits algorithm is defined here. The following code, cross-posted to Wikipedia, handles Apple’s demonstration case.
Dim File As Variant
Dim MyOutput As String
Dim Count As Long
Dim i As Long, j As Long
File = “FE AA 02 80 00 2A FD AA 03 80 00 2A 22 F7 AA”
File = Split(File, ” “)
For i = LBound(File) To UBound(File)
Count = Application.WorksheetFunction.Hex2Dec(File(i))
Select Case Count
Case Is >= 128
Count = 256 – Count ‘Two’s Complement
For j = 0 To Count ‘zero-based
MyOutput = MyOutput & File(i + 1) & ” “
Next j
i = i + 1 ‘Adjust the pointer
Case Else
For j = 0 To Count ‘zero-based
MyOutput = MyOutput & File(i + j + 1) & ” “
Next j
i = i + j ‘Adjust the pointer
End Select
Next i
Debug.Print MyOutput
‘AA AA AA 80 00 2A AA AA AA AA 80 00 2A 22 AA AA AA AA AA AA AA AA AA AA’
End Sub
It works out then that a PackBits stream is a flagbyte followed by data bytes followed by a flagbyte followed by data bytes… Each flagbyte indirectly points to the location of the next flagbyte.
Excel’s VBA does not have signed bytes, but if we look at the flagbyte’s value, and “Select Case” all flagbytes greater or equal to 128 (b10000000) we have a test that handles negative flag bytes. The two’s complement of a number N is 2B-N, where B is the word size, in our case, 8. In other words, the two’s-complement of the negative flagbyte is 256 minus the value of the flagbyte.
The MacPaint file has a 512-byte header that we can ignore. It also can have, as data bytes, internal bytes that would otherwise be read as end-of-file. We handle that by reading the file in for binary access.
The basic approach is to read in the file similarly to a text file. Turn the flagbytes characters into ascii, with the ascii as the count to copy or to take the two’s complement of. Byte(513) has to be a flagbyte. The data bytes are input as characters, turned to ascii, then turned to byte-strings. The cells are turned black or white by looping through the byte-string. After 576 1’s and 0’s (or 72 data bytes) a new row is started. The file structure does not reveal the length of the data, but we can infer that from the file’s length.
Dim TotalBytes As Long
Dim Buffer As String
Dim File As Variant
Dim Char As String * 1
Dim NextChar As String * 1
Dim NextInt As Integer
Dim NextByte As String * 8
Dim Count As Long
Dim i As Long, j As Long, r As Long, c As Long, b As Long
Dim Rng As Range
File = Application.InputBox(“Enter the full path to the MacPaint file.”, “Path to the MacPaint file…”, _
“Macintosh HD:Users:User:Downloads:GREAT WAVE.mac”, , , 2) ‘ Your path here
If File = False Then Exit Sub
Open File For Binary Access Read As #1
TotalBytes = FileLen(File)
Buffer = Input(TotalBytes, #1)
Close #1
If TotalBytes = 0 Then
MsgBox “Exiting!”, vbCritical + vbOKOnly, “File not found!”
Exit Sub
End If
c = 1
r = 1
Application.ScreenUpdating = False
For i = 513 To TotalBytes ‘skip the header
Char = VBA.Mid$(Buffer, i, 1)
Count = Asc(Char)
Select Case Count
Case Is >= 128
Count = 256 – Count ‘Two’s Complement
NextChar = VBA.Mid$(Buffer, i + 1, 1)
NextInt = Asc(NextChar)
NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
For j = 0 To Count ‘zero-based repeat of the next byte
For b = 1 To 8
If VBA.Mid$(NextByte, b, 1) = “1” Then
Worksheets(“Sheet4”).Cells(r, c).Interior.ColorIndex = 1 ‘Black
Else
Worksheets(“Sheet4”).Cells(r, c).Interior.ColorIndex = 2 ‘White
End If
c = c + 1
If c > 576 Then ‘a new row
c = 1
r = r + 1
End If
Next b
Next j
i = i + 1 ‘adjust the counter
Case Else
For j = 0 To Count ‘zero-based copy of Count bytes
NextChar = VBA.Mid$(Buffer, i + j + 1, 1)
NextInt = Asc(NextChar)
NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
For b = 1 To 8
If VBA.Mid$(NextByte, b, 1) = “1” Then
Worksheets(“Sheet4”).Cells(r, c).Interior.ColorIndex = 1
Else
Worksheets(“Sheet4”).Cells(r, c).Interior.ColorIndex = 2
End If
c = c + 1
If c > 576 Then ‘a new row
c = 1
r = r + 1
End If
Next b
Next j
i = i + j ‘adjust the counter
End Select
Next i
Set Rng = Worksheets(“Sheet4”).Range(“A1:VE720”)
Rng.ColumnWidth = 1
Rng.RowHeight = 12
ActiveWindow.Zoom = 10
Application.ScreenUpdating = True
Exit Sub
What I don’t like about the code is that for the case of Count>=128, the repetitive bytes, I’m parsing it Count times. Rather I should parse it once and copy it Count times. I just don’t see how. I know somebody out there does.
If you want the MacPaint copy of Hokusai’s The Great Wave off Kanagawa shown above, it’s here. A nice thing about the Excel code is that you can change the color indices to suit. An appropriate blue might be color index 32, RBG(0,0,255).
To show why PackBits is still used, the lossless JPEG used above for HTML purposes is 211 Kbytes. The lossless Mac counterpart is 17 Kbytes. While a MacPaint file can define 576*720 pixels (0.4 megapixels), its maximum size is 512 + (72*90) bytes, or just over 51 Kbytes.
…mrt
Nice. I put zeros and ones in the cells and used conditional formatting to color the cells. I also moved the byte loop into a separate procedure. It takes about 5 seconds on my machine.
Dim TotalBytes As Long
Dim Buffer As String
Dim File As String
Dim Char As String * 1
Dim NextChar As String * 1
Dim NextInt As Integer
Dim NextByte As String * 8
Dim Count As Long
Dim i As Long, j As Long, r As Long, c As Long
Dim Rng As Range
Dim aOutput() As Long
Dim sh As Worksheet
Set sh = ActiveWorkbook.Worksheets.Add
File = “C:UsersDickDownloadsGREAT WAVE.mac” ‘ Your path here
Open File For Binary Access Read As #1
TotalBytes = FileLen(File)
Buffer = Input(TotalBytes, #1)
Close #1
If TotalBytes = 0 Then
MsgBox “Exiting!”, vbCritical + vbOKOnly, “File not found!”
Exit Sub
End If
ReDim aOutput(1 To 720, 1 To 576)
c = 1: r = 1
Application.ScreenUpdating = False
For i = 513 To TotalBytes ‘skip the header
Char = VBA.Mid$(Buffer, i, 1)
Count = Asc(Char)
Select Case Count
Case Is >= 128
Count = 256 – Count ‘Two’s Complement
NextChar = VBA.Mid$(Buffer, i + 1, 1)
NextInt = Asc(NextChar)
NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
For j = 0 To Count ‘zero-based repeat of the next byte
FillArray aOutput, c, r, NextByte
Next j
i = i + 1 ‘adjust the counter
Case Else
For j = 0 To Count ‘zero-based copy of Count bytes
NextChar = VBA.Mid$(Buffer, i + j + 1, 1)
NextInt = Asc(NextChar)
NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
FillArray aOutput, c, r, NextByte
Next j
i = i + j ‘adjust the counter
End Select
Next i
Set Rng = sh.Range(“A1”).Resize(UBound(aOutput, 1), UBound(aOutput, 2))
Rng.Value = aOutput
Rng.FormatConditions.Add(xlCellValue, xlEqual, 1).Interior.Color = vbBlue
Rng.FormatConditions.Add(xlCellValue, xlEqual, 0).Interior.Color = vbWhite
Rng.ColumnWidth = 1
Rng.RowHeight = 12
ActiveWindow.Zoom = 10
Application.ScreenUpdating = True
End Sub
Sub FillArray(ByRef aOutput() As Long, ByRef c As Long, ByRef r As Long, NextByte As String)
Dim b As Long
For b = 1 To 8
aOutput(r, c) = Mid$(NextByte, b, 1)
c = c + 1
If c > 576 Then ‘a new row
c = 1
r = r + 1
End If
Next b
End Sub
Hi Dick –
Every accountant needs this one. The file date on my computer says that it’s been around since June 1986. Along the way, it’s picked up some extra bytes, meaning the inference from the filesize can’t be trusted. For my code, it just fills the extra five or so rows with the garbage it is. For your code, it runs out of bounds. The fixes are easy and similar. Under the line
Put the line
Two times for me, once for you. And above the line
Put the same line, once for each of us.
Back when I was on the industrial side of the military/industrial complex, we had a concept of “team programming.” One senior software engineer would program with one junior engineer side-by-side. While the firm wouldn’t get the same SLOC out as from two engineers working separately, the quality of the SLOC was higher. That’s kind of what happened here, with me being the figurative junior engineer.
Thanks for the insights.
…mrt
Hi Michael,
I’m a regular reader of DDOE (not so regular contributor, sorry Dick). Nice to meet you, online that is.
Just out of curiosity, how do you know of Hokusai? Or do you just like the picture?
Andrew
Hi Andrew –
Mostly the later. Seems there are many copies. I saw it too long ago in the British Museum. From there, I learned the name. I don’t know any more about Hokusai than what I read in Wikipedia. I didn’t do the original scan, but I kept it because of the relation to a visit abroad. The scan is really a poor rendition as you have to know the picture to see the boats being swamped by the waves.
It’s easy, mostly, to keep electronic souvenirs. This one was on an 800k floppy disc. I moved it onto newer technology while I could still resurrect a computer that would read 800k floppies and write 1.4M floppies. It was sneaker net at its finest. Read the 800k discs to the old computer’s hard drive, write to the 1.4M disc, USB floppy-drive copy to a USB/Firewire external hard drive to my laptop, erase the disc, and repeat. I set it on the kitchen table and just kept walking that disc around. Many files got corrupted at sometime along the way, but the two I linked to work great.
Nice to meet you, too.
…mrt
Hi Michael,
It certainly is a good picture and, many years after it was made, still a very popular one. I actualy have Hokusai’s 36 Views of Mount Fuji (actually 46) as my screensaver at work. It’s a great way to get an insight into what society in Japan was like at that particular time.
Andrew