May 2, 2025

Ulam Spirals (C++)

Ulam

DownloadOpen

Ulam spirals are a favourite topic because I saw them myself by accident when I first ran this code across the number line. It is super cool and was the thing that made me want to make this website.

This version is not particularly dense plotting only 100,000 primes but you can still see the diagonal white lines crossing the page. The more primes the more obvious the pattern (below 1,000,000). You hit a pixelation limit anyway but it just is obvious that there is a diagonal pattern which seems impossible given that primes are supposedly occuring at intervals that aren't known.

The code is perhaps the best code on the site just because it renders something that I believe is signficant. Ulam Spirals are an underrated secret sauce.

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <vector>
#include "primes.h"
#include "drawing.h"

int main() {
    const int WINDOW_WIDTH = 800;
    const int WINDOW_HEIGHT = 600;
    const int BIN_SIZE = 10; 
    int numberOfPrimes;

    std::cout << "Enter the number of prime numbers to generate: ";
    std::cin >> numberOfPrimes;

    std::vector<int> primes = generatePrimes(numberOfPrimes);

    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return 1;
    }

    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        std::cerr << "IMG_Init Error: " << IMG_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    SDL_Window* window = SDL_CreateWindow("Prime Number Density", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, 0);
    if (!window) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
    if (!renderer) {
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        SDL_DestroyWindow(window);
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, WINDOW_WIDTH, WINDOW_HEIGHT);
    if (!texture) {
        std::cerr << "SDL_CreateTexture Error: " << SDL_GetError() << std::endl;
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Set the texture as the render target
    SDL_SetRenderTarget(renderer, texture);

    // Clear the texture with a white background
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    SDL_RenderClear(renderer);

    // Draw the primes with a red color
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    for (int prime : primes) {
        SDL_RenderDrawPoint(renderer, prime % WINDOW_WIDTH, prime / WINDOW_WIDTH);
    }

    // Draw the grid with fainter lines
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 50);
    for (int x = 0; x < WINDOW_WIDTH; x += BIN_SIZE) {
        drawDottedLine(renderer, x, 0, x, WINDOW_HEIGHT); // Vertical lines
    }
    for (int y = 0; y < WINDOW_HEIGHT; y += BIN_SIZE) {
        drawDottedLine(renderer, 0, y, WINDOW_WIDTH, y); // Horizontal lines
    }

    // Reset the render target to the default (window)
    SDL_SetRenderTarget(renderer, NULL);

    // Copy the texture to the renderer
    SDL_RenderCopy(renderer, texture, NULL, NULL);
    // Update the window with the renderer's contents
    SDL_RenderPresent(renderer);

    // Create a surface for saving the PNG
    SDL_Surface* surface = SDL_CreateRGBSurface(0, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
    if (!surface) {
        std::cerr << "Could not create surface: " << SDL_GetError() << std::endl;
        SDL_DestroyTexture(texture);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        IMG_Quit();
        SDL_Quit();
        return 1;
    }

    // Set the texture as the render target to read the pixels
    SDL_SetRenderTarget(renderer, texture);
    SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888, surface->pixels, surface->pitch);
    SDL_SetRenderTarget(renderer, NULL); // Reset render target to the default

    // Save the surface as a PNG file
    if (IMG_SavePNG(surface, "prime_grid.png") != 0) {
        std::cerr << "Could not save PNG: " << IMG_GetError() << std::endl;
    }

    SDL_FreeSurface(surface);
    SDL_DestroyTexture(texture);

    bool running = true;
    SDL_Event event;
    while (running) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            }
        }
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    IMG_Quit();
    SDL_Quit();

    return 0;
}