Skip to content

DPI / PPI Calculator — Zig source

Compute screen pixel density (PPI/DPI), dot pitch, physical dimensions, and aspect ratio from any resolution. Solve for PPI from a diagonal, or derive the diagonal from a known PPI - all in your browser.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! =============================================================================
//! dpi-ppi.zig — CosmoDev polyglot showcase port of the `dpi-ppi` tool
//! -----------------------------------------------------------------------------
//! Language : Zig (0.13, standard library only)
//! Source:   ported from src/lib/dpi-ppi.ts (the canonical, live TypeScript
//!            lib); mirrors src/tool-sources/dpi-ppi/{python.py,rust.rs}
//! License  : display source — part of CosmoDev's polyglot tool pages
//!            (dev.cosmolabs.org). Shown verbatim alongside the JS/TS/Go/Rust/
//!            Python ports and the other language ports.
//! -----------------------------------------------------------------------------
//! Display pixel-density math. Diagonal length in pixels, pixels-per-inch
//! computed from a known physical diagonal, physical width/height/diagonal in
//! both inches and millimeters, dot pitch in millimeters per pixel, and a
//! reduced aspect ratio — all derived from a pixel resolution.
//!
//! Pure, deterministic, side-effect free. A zero divisor does not panic:
//! f64 division follows IEEE-754, so x / 0.0 yields ±inf and 0.0 / 0.0
//! yields nan — callers should guard with std.math.isFinite before display.
//! (The Python port routes divisions through a safe_div wrapper only because
//! Python raises ZeroDivisionError for float division.)
//! =============================================================================

const std = @import("std");

/// Fixed conversion factor between inches and millimeters.
const mm_per_inch: f64 = 25.4;

/// Pixels-per-inch result.
pub const Ppi = struct {
    ppi: f64,
};

/// Physical dimensions of a display, in inches and millimeters.
pub const PhysicalSize = struct {
    width_in: f64,
    height_in: f64,
    diagonal_in: f64,
    width_mm: f64,
    height_mm: f64,
};

/// Diagonal length in pixels: √(w² + h²).
/// The sign of the inputs is irrelevant because both terms are squared.
pub fn diagonalPixels(w: f64, h: f64) f64 {
    return @sqrt(w * w + h * h);
}

/// Pixels per inch, given a resolution and a known physical diagonal in inches.
pub fn computePpi(w: f64, h: f64, diagonal_inches: f64) Ppi {
    return .{ .ppi = diagonalPixels(w, h) / diagonal_inches };
}

/// Derive inches and millimeters from a resolution and a ppi. The diagonal is
/// recomputed from the derived inch dimensions so it stays consistent with
/// the width/height values.
pub fn physicalSize(w: f64, h: f64, ppi: f64) PhysicalSize {
    const width_in = w / ppi;
    const height_in = h / ppi;
    return .{
        .width_in = width_in,
        .height_in = height_in,
        .diagonal_in = diagonalPixels(width_in, height_in),
        .width_mm = width_in * mm_per_inch,
        .height_mm = height_in * mm_per_inch,
    };
}

/// Dot pitch (pixel size) in millimeters per pixel: 25.4 / ppi.
pub fn dotPitch(ppi: f64) f64 {
    return mm_per_inch / ppi;
}

/// Saturating float→i64 truncation, mirroring Rust's `as i64` cast semantics
/// (NaN → 0, ±out-of-range → min/max int). Avoids Zig's runtime safety trap
/// on pathological ±inf inputs; real display sizes are finite.
fn truncToI64(x: f64) i64 {
    const t = @trunc(x);
    if (std.math.isNan(t)) return 0;
    if (t >= 9223372036854775808.0) return std.math.maxInt(i64);
    if (t < -9223372036854775808.0) return std.math.minInt(i64);
    return @intFromFloat(t);
}

/// Integer greatest-common-divisor via Euclid's algorithm, used to reduce
/// aspect ratios. Inputs are truncated toward zero and taken in absolute
/// value first, so the sign and fractional part of the inputs never matter.
fn gcd(a: f64, b: f64) i64 {
    var x = truncToI64(@abs(a));
    var y = truncToI64(@abs(b));
    while (y != 0) {
        const t = y;
        y = x % y;
        x = t;
    }
    return x;
}

/// Reduced aspect ratio as "w:h" (e.g. "16:9"), written into `buf` — a buffer
/// the caller supplies and owns; the returned slice points into `buf`, or is
/// the static "-" literal. Returns "-" when either side is not a positive
/// number (this also covers NaN, since every comparison with NaN is false),
/// when the ratio will not reduce, or (as graceful degradation) when `buf`
/// is too small to hold the formatted pair.
pub fn aspectRatio(w: f64, h: f64, buf: []u8) []const u8 {
    if (!(w > 0) or !(h > 0)) {
        return "-";
    }
    const g = gcd(w, h);
    if (g == 0) {
        return "-";
    }
    const wi = truncToI64(w);
    const hi = truncToI64(h);
    return std.fmt.bufPrint(buf, "{d}:{d}", .{ wi / g, hi / g }) catch "-";
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →