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.

EtapaQué transformaNaturaleza del cambio
pngquantLa cantidad y distribución de coloresPérdida visual controlada
oxipngLa estructura y codificación del PNGSin pérdida visual en este flujo
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
00K00KMIT
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
00K00KMIT

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:

Terminal window
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:

Terminal window
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:

Terminal window
# Fedora
sudo dnf install pngquant oxipng
# Ubuntu o Debian
sudo apt update
sudo apt install pngquant oxipng

Si 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.gz

El binario puede instalarse en la ruta común del sistema:

Terminal window
tar -xzf oxipng-*-x86_64-unknown-linux-musl.tar.gz
sudo install -m 0755 oxipng-*/oxipng /usr/local/bin/oxipng
oxipng --version

La 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:

Terminal window
pkg update
pkg install pngquant -y
pngquant --version

OxiPNG 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.

Terminal window
uname -m
cd "$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 oxipng
rm oxipng-10.2.1-aarch64-unknown-linux-musl.tar.gz
oxipng --version

curl -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 1
fi
input=$1
if [[ ! -f "$input" ]]; then
echo "Error: no existe el archivo: $input"
exit 1
fi
if [[ "${input,,}" != *.png ]]; then
echo "Error: el archivo debe tener extensión .png"
exit 1
fi
if [[ $# -eq 2 ]]; then
output=$2
else
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 1
fi
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
fi
done
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=0
fi
echo
echo "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:

Terminal window
chmod +x ~/comprimir-png.sh

el recorrido completo cabe en una línea:

Terminal window
~/comprimir-png.sh portada.png

El original permanece intacto y el resultado aparece como portada-comprimida.png. También puede elegirse otro destino:

Terminal window
~/comprimir-png.sh portada.png portada-optimizada.png

En Termux, después de conceder acceso al almacenamiento compartido con termux-setup-storage, la misma herramienta alcanza las Descargas de Android:

Terminal window
~/comprimir-png.sh /sdcard/Download/imagen.png

La 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 imagenRango inicial sugeridoQué conviene vigilar
Portadas 3D, fotografías y degradados80-95Bandas en sombras y luces suaves
Imágenes generales para web70-90Variaciones en colores cercanos
Iconos y gráficos planos50-80Bordes 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:

Terminal window
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:

  • pngquant produce 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.