The goal of this warmup is to ensure you can make programs that read text files and produce PNG files in the language of your choice and have those run successfully on the submission server. In theory this is a mix of copying code we provide, copying examples from your language’s official documentation, and writing some basic file processing code such as you did in CS1 and CS2. In practice it will likely mean working around a few setup challenges.

1 Overview

You will submit at least three files:

  • A Makefile with (at least) two targets: build, which accepts no arguments, and run, which accepts a single input file argument.

    Running

    make build
    make run file=inputfilename.txt

    should execute your code on inputfilename.txt.

  • Code, in whatever language you prefer, including any necessary support libraries that make build cannot obtain.

  • A file named implemented.txt that contains the test cases you expect to pass, one per line.

You do not need to submit the example input or output files: just your code, the Makefile, and implemented.txt.

If all of your files are in the same directory, you can submit them as-is (but must submit all of them in one go; piecemeal uploads will not work).

If you need a specific directory structure, upload a .zip or .tar that contains those files. The logic for handling submissions is

  1. If you submitted a Makefile directly, we use that
  2. Otherwise if you submitted a tarball or zip archive, we extract that and
    1. If extracting it provides a Makefile, we use that
    2. Otherwise if extracting it created exactly one directory, we enter that and
      1. If there’s a Makefile in that directory, we use that
      2. Otherwise, report an upload format error
    3. Otherwise, report an upload format error
  3. Otherwise, report an upload format error

The input files your code will read are ASCII text files; after processing an input file, your code will produce one or more RGBA PNG files.

2 What to code

2.1 Reading Input

Each run, your program will be given one command-line argument, which is the path to an input file.

Each input file will have a number of lines; each line will have a keyword first, followed by zero or more extra pieces of information separated by whitespace (some mix of ' ' and '\t'). Lines should be read in order, first to last, because some lines refer to the lines above them.

Ignore blank lines and lines starting with anything other than a keyword you know. Always strip leading and trailing space from a line before parsing it.

In this assignment input files might look like

png 200 300 outfilename.png
position 2   20 200  150 90  40 290
ignore this line since "ignore" is not a keyword you know 
likewise ignore this line, which also starts with an unknown keyword

color 4  255 127 0 255  0 127 255 127  0 0 0 255
drawPixels 2

You do not need to have error checking code. For example, if a png keyword is not followed by exactly two positive integers and one string ending .png, your code is welcome to break in any way you wish.

2.1.1 Keywords

Each homework will define its own set of keywords. For this warmup MP, these are:

png width height filename

Every file will begin with png

png will be followed by two positive integers, width and height, and a filename. You should write a RGBA png image of the specified width and height (see [Image file creation]). You should write the file in the default directory. The initial color of every pixel in the image should be transparent black (0, 0, 0, 0).

You may assume the filename contains only non-whitespace ASCII characters and already has the appropriate .png ending.

position 2 x_0 y_0 x_1 y_1

Provides a buffer of pixel locations, giving 2 coordinates (x and y) for each.

You may assume that every x has a y, that 0 \le x_i \lt \text{\it width}, and that 0 \le y_i \lt \text{\it height}.

color 4 r_0 g_0 b_0 \alpha_0 r_1 g_1 b_1 \alpha_1

Provides a buffer of colors, giving 4 coordinates (red, green, blue, alpha) for each.

You may assume that every red has a corresponding green, blue, and alpha and that all values are between 0 and 255, inclusive.

drawPixels n

Draws n pixels from the most-recently-provided buffers.

You may assume that this only comes after a position and color keyword and that n is a positive integer that does not exceed the number of position and color coordinates provided in them.

3 Get three files working

You should be able to pass both of the following. To get credit for them, also submit a file names implemented.txt with the following in it:

simple
messy

All test input files, with their reference output files, can be downloaded as a zip

warmup-simple.txt

This file’s contents are

png 5 8 simple.png
position 2 0 1 1 2 2 3 3 4 4 5 3 3 2 4
color 4 255 255 255 255 127 255 255 255 170 170 255 255 255 255 255 255 200 120 3 255 0 0 0 255 0 0 0 255
drawPixels 7

and it should produce this image file: .

That’s so small it’s almost impossible to see, so let’s zoom in and put a striped background behind the image so you can tell the difference between transparent and white:

warmup-messy1.txt

This file’s contents are

png 5 8 messy1.png
color 4   127 255 255 127   170 170 255 255   200 120 3 255   0 0 0 191   255 255 255 255
nothing here 23425 56
position 2   1   2             2   3             4   5         2 4           1   1             3 6
drawPixels 4

and it should produce this image file:

warmup-messy2.txt

This file’s contents are

png 8 8 messy2.png
color 4 127 255 255 127 170 170 255 255 200 120 3 255 0 0 0 191 255 255 255 255
position 2 1 2 2 3 4 5 0 0 2 4
drawPixels 5
position 2 0 0 0 7 7 7 7 0 6 1
drawPixels 5

and it should produce this image file:

4 Submission and Feedback

This warmup is not graded by a human. Rather, each time you submit your code to submission site it will run automated tests and add information about their outcomes to that site. There will be a delay of up to an hours before that information is visible.

Feel free to re-submit until you get positive status results. Please refer questions to the campus forum or office hours.

5 Tips

5.1 Creating PNG files

You will create an 8-bit RGBA PNG image with a specified width and height. The way to do this will vary by language. You must use a technique that supports setting individual pixels to specific colors, not one with higher-level shape drawing functions (we’ll write those functions ourselves).

Some optional parts of homeworks 2 and 3 will include reading image files too. If you want to do those then, it might make sense to learn how now.

The following is example code to write PNGs taken from past semester’s student submissions. I don’t claim they are optimal, but they did work.

5.1.1 C

Past students have used miniz.c and LodePNG. Miniz can only write images (not read them) so it won’t let you do the texture-based optional parts of assignments. Lodepng does not have this limitation.

An example using LodePNG:
#include "lodepng.h"

int main(int argc, char *argv[]){
    /* ... */
    unsigned char *image = calloc(width * height * 4, sizeof(unsigned char));
    /* ... */
    image[((y * width) + x)*4 + 0] = red;
    image[((y * width) + x)*4 + 1] = green;
    image[((y * width) + x)*4 + 2] = blue;
    image[((y * width) + x)*4 + 3] = alpha;
    /* ... */
    lodepng_encode32_file(filename, image, width, height);
    free(image);
} 
An example using miniz
#include "miniz.h"

int main(int argc, char *argv[]){
    /* ... */
    unsigned char *image = calloc(width * height * 4, sizeof(unsigned char));
    /* ... */
    image[((y * width) + x)*4 + 0] = red;
    image[((y * width) + x)*4 + 1] = green;
    image[((y * width) + x)*4 + 2] = blue;
    image[((y * width) + x)*4 + 3] = alpha;
    /* ... */
    size_t size;
    void* data = tdefl_write_image_to_png_file_in_memory((void*) image, width, height, 4, &size);
    FILE* out_file = fopen(filename, "wb");
    fwrite(data, 1, size, out_file);
    fclose(out_file);
    free(data);
    free(image);
} 

5.1.2 C++

All methods that work for C also work for C++, and most students used one of those. An OO interface is also available through CImg. CImg depends on libpng, which some students have difficulty installing; the two C libraries do not depend on libpng and have worked for every student that has tried it so far.

An example using CImg:
#define cimg_use_png
#define cimg_display 0
#include "CImg.h"

int main(int argc, char *argv[]){
    /* ... */
    cimg_library::CImg<unsigned char> image(width, height, 1, 4);
    /* ... */
    image(x,y,0,0) = red;
    image(x,y,0,1) = green;
    image(x,y,0,2) = blue;
    image(x,y,0,3) = alpha;
    /* ... */
    image.save_png(filename.c_str());
} 

5.1.3 C#

The testing server is running mono on Linux, not Microsoft’s C# implementation. If you chose to use C#, you are responsible for ensuring it runs on Linux with mono.

The relevant class is System.Drawing.Bitmap.

An example
using System.Drawing;
class ClassName {
    static void Main(string[] args) {
        // ...
        Bitmap img = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        // ...
        img.SetPixel(x,y, Color.FromARGB(red,green,blue,alpha));
        // ...
        img.save(filename);
    }
}

5.1.4 Dart

The relevant package is image/image.dart.

An example
import 'package:image/image.dart';
import 'dart:io';

void main(List<String> args) {
    // ...
    image = Image(width, height);
    // ...
    image.setPixelRgba(x, y, red, green, blue);
    // ...
    File(filename).writeBytesAsSync(encodePng(image));
}

5.1.5 Go

The relevant package is image.

An example
import (
 "image/png"
 "image/color"
 "io"
)
func main() {
    // ...
    img := image.NewRGBA(image.Rect(0, 0, width, height))
    // ...
    img.Set(x, y, color.RGBA{red, green, blue, alpha})
    // ...
    w, err := os.Create(img.filename)
    png.Encode(w, img)
}

5.1.6 Haskell

Students have reported success using Phll

An example
import qualified Data.ByteString.Lazy as B
import Phll

pixels = flip map [0..width] $
         \x -> flip map [0..height] $
               \y -> (red, green, blue, alpha)

main = B.writeFile filename $ B.pack $ Phll.png_rgba pixels

5.1.7 Java

The relevant libraries are Color from java.awt, BufferedImage and WritableRaster from java.awt.image and ImageIO from javax.imageio package. Do not use java.awt.Graphics.

An example
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.WriteableRaster;
import javax.imageio.ImageIO;

class ClassName {
    public static void main(String[] args) {
        // ...
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        WritableRaster raster = b.getRaster();
        // ...
        raster.SetPixel(x,y, new Color(red,green,blue,alpha));
        // ...
        ImageIO.write(image, "png", new File(filename));
    }
}

5.1.8 Python

The relevant library is pillow. To install, do pip install pillow or pip3 install pillow from the command line on your machine (not in your Makefile because we’ve pre-installed pillow, but not pip, on the testing server). Do not use the ImageDraw module.

An example
from PIL import Image
# ...
image = Image.new("RGBA", (width, height), (0,0,0,0))
# ...
image.im.putpixel((x,y), (red, green, blue, alpha))
# ...
image.save(filename)

5.1.9 Rust

The relevant library is image::RgbaImage and image:Rgba.

An example
use image::{Rgba,RgbaImage};
use std::io::{BufRead, BufReader};
fn main() {
    // ...
    let mut image = RgbaImage::from_pixel(width, height, Rgba([0, 0, 0, 0]));
    // ...
    image.put_pixel(x, y, Rgba([red, green, blue, alpha]));
    // ...
    image.save(filename).unwrap();
}

5.1.10 Typescript

The relevant library is jimp.

An example
import Jimp from 'jimp';
const run = async () => {
    const image = new Jimp(width, height);
    // ...
    // make rgba a 32-bit integer of the form 0xRRGGBBAA
    image.setPixelColor(rgba, x, y);
    // ...
    await image.writeAsync(filename);
};
run().catch(console.error);

5.1.11 Other

In earlier semesters we’ve also had students use D, Kotlin, and Scala. In principle I am open to any language I can get to work on the testing server; just let me know what you’d like.

5.2 Makefile

You will submit your code and a Makefile. We will run your Makefile on a Linux server. It is your responsibility to see that the Makefile and your code work in a Linux environment. Following are minimal Makefiles you might use as a baseline. We recommend using make’s more advanced operations (separate .o targets, pattern rules, variables, etc) if you understand them.

Note that Makefile indentation must be in tabs, not spaces and that the file name must be exactly Makefile with no filename extension.

C Makefile
.PHONEY: build, run

build: program

run: program
    ./program $(file)

program: main.c lodepng.c
    clang -O3 -I. main.c lodepng.c -o program
C++ Makefile
.PHONEY: build, run

build: program

run: program
    ./program $(file)

program: main.cpp
    clang++ -O3 -I. main.cpp -o program

Note: some libraries may require more flags. For example, CImg.h requires -lpng and -lm.

C# Makefile
.PHONEY: build, run

build: program.exe

run: program.exe
    mono program.exe $(file)

program.exe: program.cs
    mcs -r:System.Drawing -pkg:gtk-sharp-2.0 program.cs
Dart Makefile
.PHONEY: build, run

build:
    pub get

run:
    dart program.dart $(file)
Go Makefile
.PHONEY: build, run

build:
    go build -o bin/main main.go

run:
    ./bin/main $(file)
Haskell Makefile
.PHONEY: build, run

build:

run:
    runghc program.hs $(file)
Java Makefile
.PHONEY: build, run

build: Program.class

run: Program.class
    java Program $(file)

Program.class: Program.java
    javac Program.java
Java Makefile with packages

If your code is in a package (as many IDEs will make it), you’ll need a slightly more involved Makefile.

If the .java files contain package some.name; then they will be in some path path/prefix/some/name. Put the Makefile (and implemented.txt) in path/prefix/ as follows:

SRC = $(wildcard some/name/*.java)
CLS = $(SRC:.java=.class)

.PHONEY: build, run

build: $(CLS)
    javac $(SRC)

run: $(CLS)
    java some.name.ClassWithMain $(file)

the SRC uses the wildcard function to find all Java files in that one package; if you use several packages, you will need to add additional wildcards there.

Python Makefile
.PHONEY: build, run

build:

run:
    python program.py $(file)
Rust Makefile
.PHONEY: build, run

build:
    cargo build

run:
    cargo run $(file)
Typescript Makefile
.PHONEY: build, run

build:
    npm install && npm run build

run:
    npm start $(file)

5.3 Image Comparison

Think your output looks like the reference output? Maybe so, but like is a fuzzy idea and sometimes we’ll hold you to a higher standard of similarity than your eye is trained to see.

Enter ImageMagick (or its less popular but faster clone, GraphicsMagick). ImageMagick is a collection of versatile command-line tools for manipulating images, including many forms of image comparison.

During grading, we use ImageMagick to create comparison images containing

  • your image, student.png
  • the image we expect, ref.png
  • an image that highlights any differences between them in red, ae.png
  • an image that shows all color differences, rawdiff.png
  • an image that magnifies color differences, diff.png

We create those images and stick them together into one large image to look at during grading using the following commands:

compare -fuzz 2% student.png ref.png ae.png
composite student.png ref.png -alpha off -compose difference rawdiff.png
convert rawdiff.png -level 0%,8% diff.png
convert +append ref.png student.png ae.png rawdiff.png diff.png look_at_this.png

Note that some tasks are permissive of some differences while others will be more strict. For example, consider this image:

It is similar to its reference image, but the outline is not the same (a few missing pixels along the left edge, touching in the middle) and there’s visible horizontal banding in the color error. If this input was meant to test shading and overlap, those would result in lost points. It it were meant to measure positioning and perspective, they’d not be a concern.