Cómo extraer iconos .ico de un .exe en Windows: API, Python y .NET

Última actualización: 09/10/2025
Autor: Isaac
  • La API de Windows permite obtener HICON y leer píxeles con GetDIBits (BGRA).
  • En Python, ctypes cubre ExtractIconExW y la conversión a imágenes con Pillow.
  • .NET simplifica mostrar el icono asociado a un archivo con ImageList + ListView.
  • Gestiona manejadores GDI y el orden de canales para evitar fugas y errores.

Usar imágenes como iconos personalizados para carpetas en windows 11

Si trabajas en Windows, tarde o temprano querrás sacar el icono de un ejecutable o de un archivo .ico para reutilizarlo en otra app, en un listado o para tus pruebas. En el ecosistema Windows hay varias rutas: la API nativa (ExtractIconExW, GetDIBits…), Python con ctypes, .NET con Icon.ExtractAssociatedIcon e incluso soluciones veteranas en Visual Basic clásico. Cada una tiene sus matices y ventajas. Puedes revisar una guía sobre NirSoft Utilities.

En esta guía unimos todas esas piezas en un único sitio. Verás desde cómo pedir a Windows los HICON “pequeño” y “grande” que expone un .exe o un .ico, a leer el mapa de bits en formato BGRA, convertirlo a RGBA si lo necesitas para PyGame o Pillow, y cómo mostrar el icono asociado a un archivo en un ListView de Windows Forms con un ImageList. También repasamos un enfoque clásico con VB para enumerar y guardar iconos, y comentamos consideraciones prácticas.

Qué hay dentro de un .ico y de un .exe: tamaños y dónde aparecen

Un mismo icono puede incluir varias imágenes internas de distintos tamaños (16×16, 32×32, 48×48, etc.). En Windows es habitual que los llamados iconos “pequeños” (16×16) se usen en ventanas y en el Explorador, donde cachés como thumbs.db de Windows pueden almacenar miniaturas, mientras que los “grandes” (32×32) se muestran en la barra de tareas y al alternar con Alt+Tab. Los ejecutables (.exe) y librerías (.dll) almacenan iconos como recursos, y los propios .ico son contenedores de múltiples resoluciones y profundidades de color.

La API de Windows facilita esta lectura: con ExtractIconExW (en shell32.dll) puedes pedir el icono pequeño o el grande que expone un archivo, ya sea un .exe o un .ico. Esa función devuelve manejadores HICON listos para dibujar o, si quieres ir más abajo, para leer el bitmap subyacente y manipular los píxeles.

Si necesitas lo más “crudo” (por ejemplo, los bytes del mapa de bits para procesarlos con tus propias librerías), el camino incluye GetIconInfo para ir del HICON a sus HBITMAP y GetDIBits para volcar el contenido a un buffer que controlas. Esto te da control total: tamaño exacto, orden de bytes, transparencia y formato.

Hay que tener presente el formato interno que devuelve Windows en estos casos: normalmente estamos ante 32 bits por píxel en BGRA (azul, verde, rojo y alpha). Esa diferencia con el convencional RGBA es importante si planeas pasar la imagen a motores o librerías que esperan otro orden de canales.

La clave está en entender que la API devuelve datos en el orden de lectura propio de GDI. Con la cabecera correcta y la altura negativa, puedes recibir la imagen en top-down (de arriba abajo) y facilitar así su uso directo en otras librerías sin tener que voltear el buffer.

iconos tamaños y ubicaciones en Windows

Extraer iconos con Python y la API de Windows (ctypes): del HICON a los bytes de imagen

Si usas Python en Windows, la ruta más directa hoy es tirar de ctypes para llamar a la API nativa. Podríamos pensar en pywin32, pero hay un detalle importante: pywin32 no expone GetDIBits, y esa función es necesaria para obtener los píxeles reales a partir de un HICON. Por tanto, ctypes resulta la vía práctica para definir estructuras, prototipos y llamadas exactamente como los espera la API de Windows.

La secuencia típica es la siguiente: crear un DC compatible (CreateCompatibleDC), llamar a ExtractIconExW para conseguir el icono pequeño o grande, resolver los bitmaps de color y máscara con GetIconInfo, preparar un BITMAPINFO con cabecera de 32 bits, indicar altura negativa para coordenadas de arriba a abajo, y finalmente volcar los píxeles con GetDIBits en un buffer de Python (ctypes.create_string_buffer).

<!-- Esqueleto ilustrativo en Python (ctypes) -->
from ctypes import byref, sizeof
from ctypes import c_int, c_void_p
from ctypes import create_string_buffer
from ctypes.wintypes import BOOL, BYTE, DWORD, HBITMAP, HDC, HGDIOBJ, HICON, LONG, LPCWSTR, LPVOID, UINT, WORD
import ctypes

# Constantes habituales
BI_RGB = 0
DIB_RGB_COLORS = 0

# Estructuras mínimas para GDI
class ICONINFO(ctypes.Structure):
    _fields_ = 

class RGBQUAD(ctypes.Structure):
    _fields_ = 

class BITMAPINFOHEADER(ctypes.Structure):
    _fields_ = 

class BITMAPINFO(ctypes.Structure):
    _fields_ = 

# DLLs
shell32 = ctypes.WinDLL('shell32', use_last_error=True)
user32  = ctypes.WinDLL('user32',  use_last_error=True)
gdi32   = ctypes.WinDLL('gdi32',   use_last_error=True)

# Prototipos esenciales
shell32.ExtractIconExW.argtypes = 
shell32.ExtractIconExW.restype  = UINT
user32.GetIconInfo.argtypes     = 
user32.GetIconInfo.restype      = BOOL
user32.DestroyIcon.argtypes     = 
user32.DestroyIcon.restype      = BOOL
gdi32.CreateCompatibleDC.argtypes = 
gdi32.CreateCompatibleDC.restype  = HDC
gdi32.GetDIBits.argtypes          = 
gdi32.GetDIBits.restype           = c_int
gdi32.DeleteObject.argtypes       = 
gdi32.DeleteObject.restype        = BOOL

# Enumerado simple para tamaños
class IconSize:
    SMALL = 1
    LARGE = 2
    @staticmethod
    def to_wh(size):
        return {IconSize.SMALL: (16, 16), IconSize.LARGE: (32, 32)}

# Función de alto nivel (boceto)
def extract_icon(filename, size):
    dc = gdi32.CreateCompatibleDC(0)
    if dc == 0:
        raise ctypes.WinError()

    hicon = HICON()
    # Pedimos el grande o el pequeño según convenga
    out_large = byref(hicon) if size == IconSize.LARGE else None
    out_small = byref(hicon) if size == IconSize.SMALL else None
    got = shell32.ExtractIconExW(filename, 0, out_large, out_small, 1)
    if got != 1:
        raise ctypes.WinError()

    info = ICONINFO()
    if not user32.GetIconInfo(hicon, byref(info)):
        user32.DestroyIcon(hicon)
        raise ctypes.WinError()

    w, h = IconSize.to_wh(size)
    bmi = BITMAPINFO()
    ctypes.memset(byref(bmi), 0, sizeof(bmi))
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
    bmi.bmiHeader.biWidth = w
    bmi.bmiHeader.biHeight = -h   # negativo para top-down
    bmi.bmiHeader.biPlanes = 1
    bmi.bmiHeader.biBitCount = 32
    bmi.bmiHeader.biCompression = BI_RGB
    bmi.bmiHeader.biSizeImage = w * h * 4

    bits = create_string_buffer(bmi.bmiHeader.biSizeImage)
    copied = gdi32.GetDIBits(dc, info.hbmColor, 0, h, bits, byref(bmi), DIB_RGB_COLORS)
    if copied == 0:
        # Limpieza mínima
        if info.hbmColor: gdi32.DeleteObject(info.hbmColor)
        if info.hbmMask:  gdi32.DeleteObject(info.hbmMask)
        user32.DestroyIcon(hicon)
        raise ctypes.WinError()

    # Limpieza de GDI y HICON
    if info.hbmColor: gdi32.DeleteObject(info.hbmColor)
    if info.hbmMask:  gdi32.DeleteObject(info.hbmMask)
    user32.DestroyIcon(hicon)

    return bits  # bytes BGRA

Con lo anterior ya puedes obtener el icono pequeño o grande de cualquier ejecutable. Por ejemplo, si quieres probar con el propio intérprete de Python (ruta disponible en sys.executable), basta con extraer ambos tamaños y trabajar con sus buffers.

# Ejemplo de uso
import sys
small_icon = extract_icon(sys.executable, IconSize.SMALL)
large_icon = extract_icon(sys.executable, IconSize.LARGE)

El resultado es un bloque de bytes donde cada píxel ocupa 4 bytes en BGRA. En 16×16 tendrás 16×16×4 = 1024 bytes, y en 32×32, 4096. El orden es fila a fila y columna a columna, de arriba a abajo y de izquierda a derecha si usaste altura negativa en la cabecera, lo que simplifica leer el índice correcto para cada (x, y).

  Solución a imagen borrosa y frecuencia incorrecta en Windows 11

Si quisieras validar el tamaño, puedes imprimir la longitud del buffer de 16×16 y comprobar que son 1024 bytes. Ese chequeo te ahorra sorpresas cuando trabajas con recursos que incluyen varias resoluciones y profundidades de color.

Para visualizar píxel a píxel y apreciar el orden de lectura, una técnica didáctica es usar PyGame: recorres el buffer fila a fila, conviertes cada BGRA a RGBA y pintas con set_at. Incluso puedes introducir un pequeño retardo inicial para “ver” cómo se rellena el icono en pantalla.

# Renderizar el icono 32x32 con PyGame, convirtiendo BGRA -> RGBA
from winicon import IconSize, extract_icon
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((320, 240))
icon_size = IconSize.LARGE
w, h = IconSize.to_wh(icon_size)
icon_large = extract_icon(sys.executable, icon_size)
rendered = False
offset_x, offset_y = 50, 50

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

    screen.fill((0, 0, 0))
    for row in range(h):
        for col in range(w):
            index = row * w * 4 + col * 4
            b, g, r, a = icon_large
            color = (r, g, b, a)
            screen.set_at((offset_x + col, offset_y + row), color)
        if not rendered:
            pygame.time.wait(10)
    pygame.display.flip()
    rendered = True

En la práctica, no siempre querrás tocar píxeles a mano. Para guardar el icono en disco como imagen, Pillow (PIL) facilita mucho la vida: creas la imagen desde los bytes con modo ‘RGBA’ indicando que el input es ‘BGRA’ y la guardas como BMP, PNG o lo que convenga. Un adaptador como win32_icon_to_image(icon_bits, size) te centraliza esa conversión.

# Conversión del buffer BGRA a una Image de PIL y guardado
from PIL import Image

def win32_icon_to_image(icon_bits, size):
    w, h = IconSize.to_wh(size)
    img = Image.frombytes('RGBA', (w, h), icon_bits, 'raw', 'BGRA')
    return img

img_small = win32_icon_to_image(small_icon, IconSize.SMALL)
img_large = win32_icon_to_image(large_icon, IconSize.LARGE)
img_small.save('python1.bmp')
img_large.save('python2.bmp')

extracción con Python y API de Windows

Mostrar el icono asociado a un archivo en .NET (WinForms): ListView + ImageList

Otra necesidad habitual es enseñar, junto al nombre de cada archivo, el icono que Windows asocia a su extensión (por ejemplo, .docx con el icono de Word). En WinForms puedes resolverlo con Icon.ExtractAssociatedIcon y almacenando los resultados en un ImageList que se enlaza a un ListView configurado en vista SmallIcon.

La idea es recorrer los archivos de un directorio, y para cada uno comprobar si ya tienes una imagen en el ImageList con la clave igual a la extensión; si no, extraes el icono y lo agregas. Así evitas duplicados y acelerarás la carga. Para el elemento del ListView, asignas la propiedad ImageKey con esa extensión y añades el ítem. Si algo falla, puedes usar un fallback como SystemIcons.WinLogo.

<!-- Esqueleto en C# para WinForms (con &quot; escapadas) -->
ListView listView1 = new ListView();
ImageList imageList1 = new ImageList();
listView1.SmallImageList = imageList1;
listView1.View = View.SmallIcon;

// Recorrer archivos (requiere using System.IO)
var dir = new System.IO.DirectoryInfo(@"C:\\");
foreach (var file in dir.GetFiles())
{
    var item = new ListViewItem(file.Name);
    var key = file.Extension;
    if (!imageList1.Images.ContainsKey(key))
    {
        var icon = System.Drawing.Icon.ExtractAssociatedIcon(file.FullName);
        imageList1.Images.Add(key, icon);
    }
    item.ImageKey = key;
    listView1.Items.Add(item);
}

Para compilarlo rápido, pega el código en un Formulario de Windows Forms, crea y configura el ListView y el ImageList en el constructor o en el evento Load, y recuerda importar el espacio de nombres System.IO. No necesitas tratar manualmente con HICON ni con píxeles; aquí te apoyas por completo en las asociaciones de icono del sistema.

  Cómo conectar los AirPods al MacBook, Mac Mini o iMac

Enfoque clásico con Visual Basic (extractor/visor y guardado a BMP)

Antes de .NET y de los envoltorios modernos, muchas utilidades en VB 4/5/6 ya hacían este trabajo llamando al API: ExtractIcon desde shell32 para obtener HICON, DrawIcon para pintar en un PictureBox, y BitBlt para copiar a otra superficie y crear una tira de miniaturas. Estos programas enumeraban los iconos de un .exe o .dll incrementando el índice de icono hasta que la función dejaba de devolver resultados.

El flujo típico era: tras seleccionar un archivo, iniciar en el índice 0 y, mientras ExtractIcon devolviera un handle válido, dibujarlo en el PictureBox, refrescar y pasar al siguiente índice. En paralelo, con BitBlt, se copiaban los 32×32 a una segunda superficie a posiciones desplazadas (por ejemplo, a X = 2 + índice × celda) para ver todos los iconos incrustados de un vistazo.

Para guardar, VB ofrecía dos rutas con SavePicture: almacenar la propiedad Picture tal cual o la propiedad Image (esta última siempre como BMP). Además, algunos formularios añadían un selector de archivos con filtros por extensión para .ico, .bmp, .exe, .dll y otros, e incluso mostraban una vista previa si el archivo era una imagen.

Este enfoque sigue siendo válido para comprender cómo interactuar con el API desde lenguajes “tradicionales” de Windows. No obstante, si hoy necesitas el buffer de píxeles, conviene ir a GDI y GetDIBits (o, en .NET moderno, a métodos que serialicen la imagen) en lugar de depender solo de DrawIcon sobre controles.

Detalle importante: tamaño, orden de bytes y lectura fila/columna

Al recuperar los píxeles con GetDIBits, especificar biHeight en negativo hace que el origen de coordenadas sea la esquina superior izquierda, que es lo que esperan muchas librerías gráficas. Si no lo haces, obtendrás un buffer bottom-up (de abajo arriba) y tendrás que invertir los bloques por filas.

En 16×16, la primera fila ocupa 16×4 = 64 bytes (4 por píxel). Por tanto, el desplazamiento a la fila n es n × 64 y el de la columna m es m × 4. En 32×32, cada fila son 128 bytes y el total 4096. El orden BGRA (B, G, R, A) es el que devuelven estas llamadas de GDI; si tu librería objetivo quiere RGBA, tendrás que reordenar los canales como se hizo con PyGame o indicarlo a la hora de crear la imagen con Pillow.

  Descubre para qué sirve la partición Recovery de Windows y cómo gestionarla

Si vas a manipular transparencia, el canal alpha será el cuarto byte del píxel. Recuerda que los iconos también usan una máscara (hbmMask); cuando trabalas con 32 bits BGRA, la transparencia suele estar ya representada en el alpha del hbmColor, de modo que la máscara puede ignorarse para usos comunes.

Iconos en recursos y reemplazo: consideraciones rápidas

Los ejecutables y librerías guardan sus iconos como recursos. Puedes extraerlos con la API como se ha explicado o, si prefieres algo visual, existen editores de recursos y utilidades con interfaz gráfica que permiten reemplazar o exportar iconos. Si vas a automatizar, la vía programática con HICON, GetIconInfo y GetDIBits te da control total y reproducibilidad.

Para leer iconos “asociados” a extensiones (sin entrar a extraer de recursos del propio .exe), .NET simplifica el trabajo con Icon.ExtractAssociatedIcon. Ese método se apoya en las asociaciones del registro y devuelve el icono que el sistema mostraría para cada archivo, lo cual es ideal para listas, exploradores y visores.

Buenas prácticas y limpieza de recursos

Trabajar con GDI implica gestionar manejadores y memoria de forma explícita. Cada vez que obtengas un HICON o un HBITMAP debes prever su ciclo de vida: destruir el HICON con DestroyIcon y liberar los bitmaps con DeleteObject. Olvidarlo puede generar pérdidas de recursos que, en aplicaciones con muchas llamadas, se traducen en fugas y comportamientos erráticos.

En Python/ctypes resulta cómodo encapsular la limpieza en una pequeña función o en un context manager. Asegúrate de ejecutar siempre la limpieza en las ramas de error, especialmente si GetDIBits devuelve 0 o si ExtractIconExW no entrega el número esperado de iconos. Asimismo, valida que el DC devuelto por CreateCompatibleDC sea válido antes de seguir.

Cuándo elegir cada enfoque

Si solo necesitas mostrar el icono “que Windows mostraría” para un archivo, .NET con ExtractAssociatedIcon es simple y eficaz. Si quieres leer el mapa de bits real de un icono incrustado en un .exe o .ico para editarlo, convertirlo o guardarlo tú mismo, la API con ctypes te aporta todo el control que necesitas. Para herramientas de legado o prototipado rápido, el enfoque VB clásico sigue siendo ilustrativo y funcional en su contexto.

Por su parte, si el objetivo es guardar iconos como imágenes con capa alpha y sin pérdidas, considera formatos como PNG mediante Pillow, aportando la ordenación de canales adecuada. BMP es directo y ampliamente compatible, pero si prefieres compresión y transparencia, PNG o ICO (si reempaquetas) pueden ser más apropiados.

Al integrar API nativa, Python y .NET, puedes cubrir desde el caso sencillo de mostrar iconos asociados a la extensión hasta la extracción de los bytes BGRA con control total de tamaño, transparencia y orden de lectura, además de enumerar iconos incrustados en ejecutables antiguos o modernos. Esta variedad de rutas te permitirá resolver desde visores y listados hasta pipelines de exportación y edición de iconos.

NirSoft Utilities-5
Artículo relacionado:
La guía definitiva sobre NirSoft Utilities: más de 250 herramientas gratuitas para mejorar Windows