Skip to content
Copied!
published on 2026-04-05

2. Output an Image

Ray Tracing in One Weekend (v3.2.3): 2 Output an Image

This chapter explains the PPM image format and presents the first rendering: a simple color gradient.

The C++ code outputs in PPM format to standard output. However, since this document uses WebAssembly, the function has been modified to return the result as a String.

r102-gradient/src/lib.rs
rust
pub fn render_image() -> String {
  let mut output = String::new();

  let image_width = 256;
  let image_height = 256;

  output.push_str("P3\n");
  output.push_str(&format!("{} {}\n", image_width, image_height));
  output.push_str("255\n");

  for j in (0..image_height).rev() {
    for i in 0..image_width {
      let r = i as f64 / (image_width - 1) as f64;
      let g = j as f64 / (image_height - 1) as f64;
      let b = 0.0;

      let ir = (255.999 * r) as i32;
      let ig = (255.999 * g) as i32;
      let ib = (255.999 * b) as i32;

      output.push_str(&format!("{} {} {}\n", ir, ig, ib));
    }
  }

  output
}

Differences Between C++ and Rust

Countdown loop

The original C++ renders from top to bottom by counting the row index j down from a large value to 0.

cpp
for (int j = image_height-1; j >= 0; --j) { ... }

Rust's .. range only goes in ascending order. To iterate in reverse, use the .rev() iterator adapter.

rust
for j in (0..image_height).rev() { ... }

(0..image_height) generates the range from 0 to image_height - 1, and .rev() reverses it.

Progress Indicator

The original book implements a progress indicator that periodically writes the remaining row count to std::cerr during rendering.

However, a progress indicator cannot be implemented for WebAssembly running inside VitePress. When a WASM function is called, JavaScript's event loop is blocked until that function returns completely. Updating the progress display requires DOM updates, but the browser will not perform any rendering while the event loop is blocked. As a result, it is impossible to display progress while the WASM function is looping internally.

Implementing a progress indicator would require splitting the work as follows:

  1. Process only one scanline inside WASM and return control
  2. Let the JavaScript event loop run to update the DOM
  3. Call WASM again for the next scanline

This approach is far more complex and is outside the scope of this document. Here, we adopt a simpler model: wait for the WASM computation to finish and then display the result all at once.