{
  "schemaVersion": 1,
  "slug": "photo-print-size-calculator",
  "category": "everyday",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "fa37b9995b2bcf27299cd1adc3be3dff57a1e6466d7101f3127d3ec0badd043e",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/photo-print-size-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "PPI = min(Wpx ÷ W_in, Hpx ÷ H_in); MP = Wpx × Hpx ÷ 10^6",
    "vars": [
      [
        "PPI",
        "Pixels per inch"
      ],
      [
        "Wpx",
        "Image width (px)"
      ],
      [
        "Hpx",
        "Image height (px)"
      ],
      [
        "W_in",
        "Print width (in)"
      ],
      [
        "MP",
        "Megapixels"
      ]
    ],
    "note": "Max print at 300 PPI = Wpx ÷ 300 in; quality tiers at 150/200/240/300 PPI."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const ppi = printWidth > 0 && printHeight > 0 ? Math.min(imageWidthPx/printWidth, imageHeightPx/printHeight) : 0; const megapixels = imageWidthPx*imageHeightPx/1000000; const quality = ppi >= 300 ? 'Excellent — full photo quality' : ppi >= 240 ? 'Very good' : ppi >= 200 ? 'Good for most viewing' : ppi >= 150 ? 'Acceptable at arm length' : 'Soft — print smaller or resample'; const maxPrintAt300 = Math.max(imageWidthPx, 0)/300; const A = { '4x6': 1.5, '5x7': 1.4, '8x10': 1.25, square: 1 }; const target = A[frameAspect]; const cropNote = !target ? 'Keeping the original aspect ratio, so no pixels are lost' : Math.abs(target-imageWidthPx/imageHeightPx) < 0.05 ? 'The image ratio already matches this crop closely' : 'Cropping to this format discards part of the frame — check the composition'; return { ppi, megapixels, quality, requiredFor300: Math.round(printWidth*300)+' × '+Math.round(printHeight*300)+' px', cropLoss: cropNote, maxPrintAt300 };",
    "inputs": [
      {
        "name": "imageWidthPx",
        "type": "number",
        "default": 6000,
        "min": 1,
        "max": 100000,
        "step": 1
      },
      {
        "name": "imageHeightPx",
        "type": "number",
        "default": 4000,
        "min": 1,
        "max": 100000,
        "step": 1
      },
      {
        "name": "printWidth",
        "type": "number",
        "default": 16,
        "min": 0.1,
        "max": 200,
        "step": 0.5,
        "slider": true
      },
      {
        "name": "printHeight",
        "type": "number",
        "default": 12,
        "min": 0.1,
        "max": 200,
        "step": 0.5,
        "slider": true
      },
      {
        "name": "frameAspect",
        "type": "select",
        "default": "none",
        "options": [
          {
            "value": "none"
          },
          {
            "value": "4x6"
          },
          {
            "value": "5x7"
          },
          {
            "value": "8x10"
          },
          {
            "value": "square"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "ppi",
        "precision": 1,
        "unit": "ppi"
      },
      {
        "name": "megapixels",
        "precision": 1,
        "unit": "MP"
      },
      {
        "name": "quality",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "requiredFor300",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "cropLoss",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "maxPrintAt300",
        "precision": 1,
        "unit": "in"
      }
    ],
    "interpret": null
  }
}
