Resolución de impresión y distancia de visión — PPI/DPI 2025

Publicado: 19 sept 2025 · Tiempo de lectura: 5 min · Por el equipo editorial de Unified Image Tools

Resolución de impresión y distancia de visión — PPI/DPI 2025

Ajustar “cuanto más alta mejor” la resolución provoca archivos innecesariamente grandes, tiempos de proceso largos y costos de impresión más altos. En cambio, quedarte corto degrada visiblemente la calidad. Este artículo resume, desde la visión humana y la técnica de impresión, cómo fijar una resolución práctica y eficiente según el caso.

Fundamentos y definiciones

PPI vs DPI correctamente

PPI (Pixels Per Inch)

  • Densidad de píxeles de una imagen digital (entrada)
  • Afecta la calidad de la imagen de trabajo y el tamaño en píxeles

DPI (Dots Per Inch)

  • Resolución física del dispositivo de impresión (salida)
  • Caracteriza hardware del impresor y densidad de gotas/toner

Cálculo desde distancia de visión

# Calculadora de resolución mínima desde distancia de visión
import math

class PrintResolutionCalculator:
  def __init__(self):
    # 1 minuto de arco ≈ visión 20/20
    self.visual_acuity_arcmin = 1.0
    self.eye_lens_accommodation = 0.3  # factor de reserva

  def calculate_minimum_resolution(self, viewing_distance_cm, print_type='offset'):
    """Devuelve PPI teórico/práctico/recomendado desde distancia de visión"""
    viewing_distance_inch = viewing_distance_cm / 2.54
    min_angle_rad = math.radians(self.visual_acuity_arcmin / 60.0)
    min_resolvable_distance_inch = viewing_distance_inch * math.tan(min_angle_rad)
    theoretical_max_ppi = 1.0 / min_resolvable_distance_inch

    # Ajustes por proceso de impresión
    adjustment_factors = {
      'offset': 0.7,
      'digital': 0.6,
      'inkjet': 0.5,
      'laser': 0.65,
      'newspaper': 0.4,
      'billboard': 0.3,
    }
    practical_max_ppi = theoretical_max_ppi * adjustment_factors.get(print_type, 0.6)

    return {
      'theoretical_max_ppi': round(theoretical_max_ppi, 1),
      'practical_max_ppi': round(practical_max_ppi, 1),
      'recommended_ppi': round(practical_max_ppi * 1.2, 1),  # margen 20%
      'viewing_distance_cm': viewing_distance_cm,
      'print_type': print_type,
    }

  def recommend_resolution_by_use_case(self, use_case):
    """Casos de uso comunes y PPI recomendado"""
    use_case_configs = {
      'business_card': {'viewing_distance': 25, 'print_type': 'offset', 'notes': 'Texto pequeño → alta nitidez'},
      'brochure': {'viewing_distance': 35, 'print_type': 'offset', 'notes': 'Equilibrio foto/legibilidad'},
      'poster_a1': {'viewing_distance': 100, 'print_type': 'digital', 'notes': 'Se ve de lejos → menor PPI viable'},
      'magazine': {'viewing_distance': 40, 'print_type': 'offset', 'notes': 'Lectura prolongada → confort visual'},
      'billboard': {'viewing_distance': 500, 'print_type': 'inkjet', 'notes': 'Gran formato, lejanía'},
      'photo_print': {'viewing_distance': 30, 'print_type': 'inkjet', 'notes': 'Calidad fotográfica máxima'},
    }
    if use_case not in use_case_configs:
      raise ValueError(f'Caso no soportado: {use_case}')
    cfg = use_case_configs[use_case]
    base = self.calculate_minimum_resolution(cfg['viewing_distance'], cfg['print_type'])
    if use_case == 'business_card':
      base['recommended_ppi'] *= 1.5
    elif use_case == 'photo_print':
      base['recommended_ppi'] *= 1.3
    elif use_case == 'billboard':
      base['recommended_ppi'] *= 0.8
    base['use_case'] = use_case
    base['notes'] = cfg['notes']
    return base

calculator = PrintResolutionCalculator()
for case in ['business_card', 'brochure', 'poster_a1', 'magazine', 'photo_print']:
  print(calculator.recommend_resolution_by_use_case(case))

Visión humana y condiciones

class VisualAcuityModel:
  def age_adjusted_acuity(self, age, base_acuity=1.0):
    if age < 20:
      return base_acuity * 1.1
    if age < 40:
      return base_acuity * 1.0
    if age < 60:
      return base_acuity * (0.9 - (age - 40) * 0.01)
    return max(base_acuity * (0.7 - (age - 60) * 0.015), 0.3)

  def lighting_adjustment(self, lux):
    if lux < 50: return 0.6
    if lux < 200: return 0.8
    if lux < 1000: return 1.0
    return 1.1

  def effective_need(self, viewing_distance_cm, age=30, acuity=1.0, lux=500):
    base = PrintResolutionCalculator().calculate_minimum_resolution(viewing_distance_cm)
    age_factor = self.age_adjusted_acuity(age, acuity)
    light_factor = self.lighting_adjustment(lux)
    total = age_factor * light_factor
    adjusted = base['practical_max_ppi'] / total
    return {**base, 'adjusted_recommended_ppi': round(adjusted * 1.2, 1)}

Específicos por tecnología de impresión

Offset: relación LPI ↔ PPI

class OffsetPrintOptimizer:
  def __init__(self):
    self.screen_lpi = {
      'newspaper': 85,
      'magazine': 133,
      'art_book': 150,
      'fine_art': 175,
      'packaging': 120,
    }

  def optimal_ppi(self, print_type, qf=1.5):
    if print_type not in self.screen_lpi:
      raise ValueError('Tipo no soportado')
    lpi = self.screen_lpi[print_type]
    ppi = lpi * qf
    ppi = 150 if ppi < 150 else 350 if ppi > 350 else ppi
    return round(ppi, 1)

Digital/Inkjet

class InkjetOptimizer:
  def __init__(self):
    self.printers = {
      'consumer_inkjet': {'native_dpi': 300, 'range': (150, 300), 'papers': ['plain','photo','matte']},
      'professional_inkjet': {'native_dpi': 600, 'range': (240, 360), 'papers': ['photo','fine_art','canvas']},
      'large_format': {'native_dpi': 150, 'range': (72, 150), 'papers': ['banner','poster','vinyl']},
    }

  def recommend(self, printer_type, paper_type, content='photo'):
    spec = self.printers[printer_type]
    base = sum(spec['range']) / 2
    paper_adj = {'plain':0.8,'photo':1.0,'matte':0.9,'fine_art':1.1,'canvas':0.85,'banner':0.7,'vinyl':0.75}.get(paper_type,1.0)
    content_adj = {'photo':1.0,'illustration':1.1,'text':1.2,'mixed':1.05}.get(content,1.0)
    ppi = max(spec['range'][0], min(spec['range'][1], base*paper_adj*content_adj))
    return round(ppi,1)

Motor de decisión práctico

class ResolutionDecisionEngine:
  def round_common(self, val):
    commons = [72,96,150,200,240,300,360,400]
    closest = min(commons, key=lambda x: abs(x-val))
    return closest if abs(closest-val)/val <= 0.2 else round(val/10)*10

  def decide(self, specs):
    calc = PrintResolutionCalculator().calculate_minimum_resolution(
      specs['viewing_distance_cm'], specs['print_method']
    )
    visual = calc['recommended_ppi']
    if specs['print_method'] in ['offset','digital']:
      tech = OffsetPrintOptimizer().optimal_ppi(specs.get('product_type','magazine'))
    else:
      tech = InkjetOptimizer().recommend(
        {'small':'consumer_inkjet','medium':'professional_inkjet','large':'large_format'}.get(specs.get('size_category','medium'),'professional_inkjet'),
        specs.get('paper_type','photo'), specs['content_type']
      )
    audience = {'general':1.0,'elderly':0.8,'professional':1.2,'children':0.9}[specs['target_audience']]
    budget = {'low':0.8,'medium':1.0,'high':1.15}[specs['budget_level']]
    final = self.round_common(sorted([visual, tech])[0] * audience * budget)
    return {'recommended_ppi': final}

Coste y eficiencia

Reduce PPI cuando la distancia lo permita para ahorrar tamaño/tiempo/costo; súbelo en piezas con texto fino o inspección cercana. Evalúa el impacto en tamaño de archivo y tiempo de proceso antes de fijar PPI.

Verificación de calidad y solución de problemas

import cv2, numpy as np

class ResolutionQualityValidator:
  def calculate_sharpness(self, gray):
    var = cv2.Laplacian(gray, cv2.CV_64F).var()
    return {'variance': round(var,2), 'normalized': round(min(var/1000,1.0),3)}

  def adequacy(self, current_ppi, viewing_distance_cm):
    req = PrintResolutionCalculator().calculate_minimum_resolution(viewing_distance_cm)
    ratio = current_ppi/ req['recommended_ppi']
    return {'ratio': round(ratio,2), 'status': 'ok' if ratio>=1 else 'marginal' if ratio>=0.8 else 'low'}

Integración con herramientas

Resumen

  1. Calcula desde distancia de visión y proceso, 2) ajusta por uso y audiencia, 3) prioriza valores prácticos comunes (150/240/300/360), 4) valida con muestras reales y costes. Así evitas exceso de PPI y garantías la calidad percibida donde importa.