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