Todo comenzó con una contradicción pequeña pero constante: las portadas del blog se veían bien, pero pesaban demasiado. Cada nueva imagen repetía el mismo proceso de abrir una herramienta web, subir el archivo, esperar, descargar otra copia y comprobar si la calidad seguía intacta.
El problema no era comprimir una imagen. Era depender de ese recorrido cada vez que aparecía un PNG nuevo.
La solución terminó siendo un pequeño laboratorio local con dos etapas: pngquant reduce la complejidad visual y OxiPNG reorganiza el archivo resultante para ocupar todavía menos espacio. El mismo proceso pudo vivir en un VPS Linux y dentro de Termux, directamente en Android.
El ahorro llegó a rondar entre el 60 y el 80 % en imágenes adecuadas. No fue una constante ni una promesa matemática. Cada PNG reaccionó según sus colores, transparencias, degradados y optimización previa. Lo interesante no fue alcanzar una cifra exacta, sino descubrir dónde desaparecían realmente los bytes.
El peso del PNG ocultaba dos problemas distintos
Un PNG puede ser pesado por la cantidad de información visual que conserva y por la forma en que esa información está codificada. Tratar ambas causas como si fueran una sola conduce a resultados limitados.
La primera herramienta del recorrido, pngquant, transforma imágenes PNG de 24 o 32 bits en archivos de paleta de 8 bits con canal alfa. Para hacerlo analiza los colores, construye una paleta optimizada y vuelve a asignar los píxeles. Sus mantenedores indican que este proceso produce con frecuencia archivos entre un 60 y un 80 % más pequeños.
La segunda, oxipng, no intenta repetir esa cuantización. Explora filtros y estrategias de compresión para encontrar una representación más eficiente del mismo PNG. Está escrita en Rust, trabaja en paralelo y su optimización puede conservar intacta la representación visual.
| Etapa | Qué transforma | Naturaleza del cambio |
|---|---|---|
pngquant | La cantidad y distribución de colores | Pérdida visual controlada |
oxipng | La estructura y codificación del PNG | Sin pérdida visual en este flujo |
Vistas juntas, las herramientas forman una cadena y no una competencia. La primera simplifica el contenido; la segunda busca la mejor manera de empaquetar esa versión simplificada.
La primera transformación ocurrió dentro de la paleta
pngquant consiguió la reducción grande porque dejó de tratar cada posible color como indispensable.
Una portada con sombras, reflejos y degradados puede contener miles de variaciones muy próximas entre sí. Muchas existen en los datos del archivo, aunque el ojo apenas pueda distinguirlas. La cuantización agrupa esas variaciones alrededor de una paleta más pequeña.
El rango elegido fue:
pngquant \ --quality=70-90 \ --speed=1 \ --strip \ --force \ --output "$output" \ "$input"--quality=70-90 no ordena que el archivo final pese entre el 70 y el 90 % del original. El límite superior es la calidad que pngquant intenta alcanzar; el inferior es el umbral que se niega a cruzar. Si el resultado cae por debajo del mínimo, la herramienta no guarda la conversión y termina con el código 99.
La opción --speed=1 dedica más trabajo a buscar una paleta eficiente. El proyecto establece 4 como equilibrio predeterminado y aconseja conservarlo en la mayoría de escenarios. Aquí elegí 1 porque las portadas finales se procesan una sola vez: algunos segundos adicionales importan menos que conservar calidad y obtener un archivo pequeño.
--strip añadió otra decisión: no copiar chunks opcionales del PNG. Eso elimina metadatos y reduce algunos bytes más, pero también puede retirar información de procedencia. Esa consecuencia aparecería de nuevo al final del experimento.
OxiPNG encontró espacio donde ya no parecía haberlo
Después de la cuantización, OxiPNG todavía pudo reducir el archivo sin volver a modificar su apariencia.
La segunda etapa fue más silenciosa:
oxipng \ -o 4 \ --strip safe \ "$output"El nivel -o 4 explora un conjunto intermedio-alto de posibilidades. Subir más puede aumentar considerablemente el tiempo a cambio de reducciones cada vez menores. --strip safe elimina metadatos considerados prescindibles para representar la imagen.
No añadí --alpha. La documentación presenta esa opción dentro de una configuración útil para la web, pero puede modificar los valores RGB ocultos en píxeles completamente transparentes. Esos valores normalmente no se ven, aunque preferí no tocarlos.
El mismo flujo tuvo que sobrevivir en dos mundos Linux
El script Bash era portátil; los binarios no necesariamente lo eran. Esa diferencia quedó clara cuando el proceso salió del VPS y llegó a Termux.
En Fedora y Ubuntu, la ruta más estable fue comenzar por los repositorios de la distribución:
# Fedorasudo dnf install pngquant oxipng
# Ubuntu o Debiansudo apt updatesudo apt install pngquant oxipngSi OxiPNG no está disponible o su versión es demasiado antigua, el siguiente paso es revisar sus releases oficiales, identificar la arquitectura con uname -m y descargar el asset correspondiente. Para un servidor x86_64, el nombre esperado contiene:
x86_64-unknown-linux-musl.tar.gzEl binario puede instalarse en la ruta común del sistema:
tar -xzf oxipng-*-x86_64-unknown-linux-musl.tar.gzsudo install -m 0755 oxipng-*/oxipng /usr/local/bin/oxipngoxipng --versionLa versión no debe quedar incrustada para siempre en un tutorial. La página de releases es la fuente para comprobar el número actual, la arquitectura disponible y, cuando el proyecto los publique, los checksums.
Termux reveló que Android no es otra distribución de escritorio
Termux comparte el kernel de Android, pero su espacio de usuario, cargador y biblioteca C no son los de Ubuntu o Fedora.
Los paquetes de Termux se construyen normalmente contra bionic, la biblioteca C de Android. Un ejecutable dinámico preparado para glibc puede tener la arquitectura correcta y aun así fallar porque busca un cargador que no existe en el teléfono. La documentación del entorno de ejecución de Termux explica esta frontera.
pngquant no presentó misterio porque ya existía como paquete nativo:
pkg updatepkg install pngquant -ypngquant --versionOxiPNG sí convirtió la instalación en parte del descubrimiento. cargo install oxipng falló durante la compilación de una dependencia nativa. Sin conservar y estudiar el registro completo, no sería correcto culpar exclusivamente a zopfli, al NDK o a bionic.
La salida práctica fue un release estático para aarch64-unknown-linux-musl. En la prueba original se utilizó la versión 10.2.1; al repetirla debe sustituirse por la versión publicada en el release más reciente.
uname -mcd "$PREFIX/bin"
curl -fLO \ https://github.com/oxipng/oxipng/releases/download/v10.2.1/oxipng-10.2.1-aarch64-unknown-linux-musl.tar.gz
tar -xzf oxipng-10.2.1-aarch64-unknown-linux-musl.tar.gz \ --strip-components=1 \ --wildcards '*/oxipng'
chmod 0755 oxipngrm oxipng-10.2.1-aarch64-unknown-linux-musl.tar.gzoxipng --versioncurl -f cumple una función pequeña pero importante: si GitHub responde con un error HTTP, la descarga falla en vez de guardar una página HTML bajo el nombre de un archivo comprimido.
El destino también importó. $PREFIX/bin es la ubicación de ejecutables de Termux, según su estructura del sistema de archivos. /sdcard sirve para leer y guardar imágenes, pero sus restricciones impiden tratarlo como un directorio Unix normal para ejecutar programas.
El experimento terminó convertido en un solo comando
Cuando ambos binarios estuvieron disponibles, toda la investigación quedó condensada en un script pequeño y repetible.
Guárdalo como ~/comprimir-png.sh:
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then echo "Uso: $0 imagen.png [salida.png]" exit 1fi
input=$1
if [[ ! -f "$input" ]]; then echo "Error: no existe el archivo: $input" exit 1fi
if [[ "${input,,}" != *.png ]]; then echo "Error: el archivo debe tener extensión .png" exit 1fi
if [[ $# -eq 2 ]]; then output=$2else filename=$(basename "$input") directory=$(dirname "$input") name=${filename%.*} output="$directory/${name}-comprimida.png"fi
if [[ "$input" == "$output" ]]; then echo "Error: la salida no puede sobrescribir el original" exit 1fi
for command_name in pngquant oxipng; do if ! command -v "$command_name" >/dev/null 2>&1; then echo "Error: $command_name no está instalado" exit 1 fidone
original_size=$(stat --format='%s' "$input")
echo "Cuantizando colores..."pngquant \ --quality=70-90 \ --speed=1 \ --strip \ --force \ --output "$output" \ "$input"
echo "Aplicando optimización final..."oxipng -o 4 --strip safe "$output"
compressed_size=$(stat --format='%s' "$output")
if (( original_size > 0 )); then reduction=$((100 - compressed_size * 100 / original_size))else reduction=0fi
echoecho "Original: $(numfmt --to=iec-i --suffix=B "$original_size")"echo "Comprimido: $(numfmt --to=iec-i --suffix=B "$compressed_size")"echo "Reducción: ${reduction}%"echo "Resultado: $output"Después de marcarlo como ejecutable:
chmod +x ~/comprimir-png.shel recorrido completo cabe en una línea:
~/comprimir-png.sh portada.pngEl original permanece intacto y el resultado aparece como portada-comprimida.png. También puede elegirse otro destino:
~/comprimir-png.sh portada.png portada-optimizada.pngEn Termux, después de conceder acceso al almacenamiento compartido con termux-setup-storage, la misma herramienta alcanza las Descargas de Android:
~/comprimir-png.sh /sdcard/Download/imagen.pngLa automatización expuso que no existe un preset universal
Cuando el script comenzó a procesar imágenes distintas, el porcentaje dejó de ser la métrica principal y la calidad visual tomó su lugar.
| Tipo de imagen | Rango inicial sugerido | Qué conviene vigilar |
|---|---|---|
| Portadas 3D, fotografías y degradados | 80-95 | Bandas en sombras y luces suaves |
| Imágenes generales para web | 70-90 | Variaciones en colores cercanos |
| Iconos y gráficos planos | 50-80 | Bordes y transparencias degradadas |
Estos rangos son puntos de partida, no leyes. Una imagen con ocho colores no necesita la misma tolerancia que una portada con niebla, piel o reflejos. El original debe conservarse y la copia debe revisarse a tamaño completo sobre fondos claros y oscuros.
El procesamiento por lotes llegó después, cuando una sola imagen dejó de ser el caso normal:
find src/assets/blog \ -type f \ -iname '*.png' \ ! -iname '*-comprimida.png' \ -print0 |while IFS= read -r -d '' image; do ~/comprimir-png.sh "$image"done-print0 conserva correctamente los nombres con espacios. La exclusión de *-comprimida.png evita que una ejecución futura vuelva a comprimir sus propios resultados.
Al quitar metadatos también puede desaparecer la procedencia
La reducción del archivo tiene un costo menos visible: los chunks opcionales pueden contener información que no participa en la imagen, pero sí en su historia.
La especificación C2PA establece que un manifiesto embebido en PNG puede almacenarse en el chunk auxiliar privado caBX. Al reconstruir la imagen y descartar chunks opcionales, esa credencial normalmente deja de acompañar a la copia transformada y su firma ya no puede validarla.
Esto tampoco garantiza que desaparezca cualquier mecanismo de procedencia. Pueden existir manifiestos externos, registros remotos o marcas resistentes incorporadas en los píxeles. El script optimiza un PNG; no es una herramienta para borrar rastros.
El resultado fue más que un archivo pequeño
El verdadero resultado fue convertir una tarea web repetitiva en una operación local, observable y automatizable.
El viaje del PNG dejó varias conclusiones:
pngquantproduce la reducción principal al simplificar la paleta.- OxiPNG aprovecha la estructura resultante sin repetir la pérdida visual.
- El script Bash puede ser el mismo en Linux y Termux.
- Los ejecutables dependen de la arquitectura y del entorno donde correrán.
- El ahorro debe evaluarse junto con la calidad y los metadatos conservados.
- La automatización vale más cuando mantiene intacto el original.
Antes de recurrir a capas de compatibilidad o implementaciones alternativas de glibc en Termux, el orden razonable es buscar el paquete con pkg, comprobar releases oficiales compatibles, verificar arquitectura y procedencia, y solo entonces considerar una compilación específica para Android o un entorno proot.
Al final, una portada dejó de viajar a una página web externa. Ahora atraviesa dos herramientas locales, conserva su original y regresa más ligera con una explicación medible de lo que ocurrió. Ese pequeño cambio resume una idea que aplico con frecuencia: las mejores automatizaciones no esconden el proceso; lo vuelven repetible, verificable y propio.
