Create a 1k Image for Commodore 64 with Ruby

At the end of the Episode #006 - Read Data you can see a low-resolution image made out of characters. It was made using the READ command to pick values from a large DATA sequence and then POKE them into the screen memory. As the data set is rather large, we would prefer to generate it automatically…

The screen of Commodore 64 can display 25 rows of 40 characters. If you fill it with fully opaque characters, it can be used to display a ridiculously low-resolution image (40x25 = 1k of huge pixels in total).

64bites logo made from colored characters

For example the image above can be generated by the following program:


        10 for y = 0 to 24
        20 for x = 0 to 39
        30 o = 40 * y + x
        40 poke 1024 + o, 160
        45 read c
        50 poke 55296 + o, c
        60 next x,y
        70 goto 70
        1000 data 14,14,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
        1010 data 9,9,9,9,9,9,9,9,14,14,14,9,9,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
        1020 data 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,9,9,14,9,9,9,2,2,2,2,2,2,2
        1030 data 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,9,9,9
        1040 data 9,9,9,9,9,9,9,9,9,9,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,1,1,1,1,9
        1050 data 9,9,9,9,9,9,9,9,9,9,9,9,9,8,8,8,8,8,8,1,1,1,1,1,1,1,1,1,8,8
        1060 data 8,8,8,8,1,1,12,1,1,12,8,8,8,8,8,8,8,9,9,9,9,9,9,8,8,8,8,8,1,1
        1070 data 12,12,12,12,12,12,1,1,12,8,8,8,8,1,1,12,8,1,1,12,8,8,8,8,8,8,8,9,9,9
        1080 data 9,9,9,9,9,9,9,9,1,1,11,9,9,9,9,9,9,11,11,9,9,9,1,1,11,9,9,1,1,11
        1090 data 9,9,9,9,9,9,9,9,9,9,9,9,9,7,7,7,7,7,1,1,15,1,1,1,1,1,1,7,7,7
        1100 data 7,1,1,15,7,7,7,1,1,15,7,7,7,7,7,7,7,9,9,9,9,9,9,7,7,7,7,7,1,1
        1110 data 1,1,1,1,1,1,1,1,7,7,1,1,15,7,7,7,7,1,1,15,7,7,7,7,7,7,7,9,9,9
        1120 data 9,9,9,9,9,9,9,9,1,1,11,11,11,11,11,11,1,1,11,9,1,1,1,1,1,1,1,1,1,1
        1130 data 1,9,9,9,9,9,9,9,9,9,9,9,9,5,5,5,5,5,1,1,12,5,5,5,5,5,1,1,12,5
        1140 data 1,1,1,1,1,1,1,1,1,1,1,12,5,5,5,5,5,9,9,9,9,9,9,5,5,5,5,5,1,1
        1150 data 1,1,1,1,1,1,1,1,12,5,5,12,12,12,12,12,12,1,1,12,12,12,5,5,5,5,5,9,9,9
        1160 data 9,9,9,9,9,9,9,9,9,1,1,1,1,1,1,1,1,11,11,9,9,9,9,9,9,9,9,1,1,11
        1170 data 9,9,9,9,9,9,9,9,9,9,9,9,9,6,6,6,6,6,6,6,12,12,12,12,12,12,12,12,6,6
        1180 data 6,6,6,6,6,6,6,6,12,12,6,6,6,6,6,6,6,9,9,9,9,9,9,6,6,6,6,6,6,6
        1190 data 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,9,9,9
        1200 data 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
        1210 data 9,9,9,9,9,9,9,9,9,9,9,9,9,9,1,9,9,9,9,9,9,9,1,1,9,9,9,9,9,9
        1220 data 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,1,9,9,9,9,9
        1230 data 9,9,9,9,9,9,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
        1240 data 9,9,9,9,1,9,1,1,1,9,9,9,1,9,9,1,1,1,1,1,9,9,9,1,1,1,1,9,9,9
        1250 data 1,1,1,1,1,9,9,9,9,9,9,9,9,9,1,1,9,9,1,1,9,9,1,1,9,9,1,9,9,9
        1260 data 9,9,1,9,9,9,1,1,9,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,1,9,9,9,9,1
        1270 data 9,9,1,1,9,9,1,9,9,9,9,1,1,1,1,1,1,1,9,1,1,1,1,1,1,1,9,9,9,9
        1280 data 9,9,9,9,1,1,9,9,1,1,9,9,1,1,9,9,1,9,9,1,9,9,1,9,9,9,9,9,9,9
        1290 data 9,9,9,9,1,1,9,9,9,9,9,9,9,9,1,9,1,1,1,9,9,9,1,1,9,9,1,1,1,1
        1300 data 9,9,9,1,1,1,1,9,9,9,1,1,1,1,1,9,9,9,9,9,14,9,9,9,9,9,9,9,9,9
        1310 data 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,14
        1320 data 14,14,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9
        1330 data 9,9,9,9,9,9,9,9,14,14 
        

That sounds like a lot of typing…

While we could create all those DATA statements by hand, there is a better way. If we have a small 40x25 image, we could use it to generate the required DATA statements. To do that we need to match the color of every pixel of the image with a corresponding index of that color in the C64 palette. To simplify this process, we'll use the 16x1 palette image filled with C64 colors in the correct order. Now if we go through each of the pixels in the first image and find the corresponding color in the palette image, the index of that color will be the correct C64 color number.

Algorithm

Let's see that in code

We will use a simple Ruby script to do that. First we'll require the chunky_png gem to be able to work with png files.


        require 'chunky_png'
        

Now we will read colors out of the palette.png file and store it's index in the colors hash.


        colors = {}
        palette = ChunkyPNG::Image.from_file('palette.png')
        (0...palette.dimension.width).each do |index|
          colors[palette[index, 0]] = index
        end
        

Then we'll iterate through each pixel of the image40x25.png file, row by row. Match the pixel's color to it's index. And store the index in a color_indices array.


        color_indices = []
        image = ChunkyPNG::Image.from_file('image40x25.png')
        (0...image.dimension.height).each do |y|
          (0...image.dimension.width).each do |x|
            color_indices << colors[image[x, y]]
          end
        end
        

Finally, we can use color_indices to create DATA statements. We'll print thirty indices per one DATA line to make sure we don't exceed the maximum limit of eighty characters.


        color_indices.each_slice(30).with_index do |row, index|
          puts "#{1000 + 10 * index} data #{row.join(",")}"
        end
        

Make your own images!

This zip file contains the required images palette.png, image25x40.png and the ruby script - make_data.rb that you can use to create low-resolution images yourself.

Have Fun!