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
- Conversión y pipeline de color: Convertidor avanzado
- Redimensionado final: Redimensionador de imágenes
- Gestión de color (entrega sRGB/CMYK): /es/articles/color-management-srgb-p3-cmyk-handoff-2025
Resumen
- 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.