Skip to content

Extracting Icons

Learn how to extract launcher icons with different densities.

Basic Icon Extraction

typescript
import { Apk } from "node-apk";
import { promises as fs } from "node:fs";

const apk = new Apk("app.apk");

// Get the best available launcher icon
const icon = await apk.getLauncherIcon();
await fs.writeFile("icon.png", icon);

Density-Specific Icons

typescript
import { Apk, type IconDensity } from "node-apk";

const apk = new Apk("app.apk");

// Available densities
const densities: IconDensity[] = [
  "mdpi",    // ~160 dpi
  "hdpi",    // ~240 dpi
  "xhdpi",   // ~320 dpi
  "xxhdpi",  // ~480 dpi
  "xxxhdpi", // ~640 dpi
  "nodpi",   // No scaling
  "anydpi",  // Vector/adaptive
];

for (const density of densities) {
  try {
    const icon = await apk.getLauncherIcon({ density });
    await fs.writeFile(`icon-${density}.png`, icon);
    console.log(`Extracted ${density}: ${icon.length} bytes`);
  } catch (error) {
    console.log(`No ${density} icon available`);
  }
}

Fallback Behavior

When the preferred density isn't available, the method automatically selects the best alternative:

typescript
// Request xxxhdpi - will fall back if not available
const icon = await apk.getLauncherIcon({ density: "xxxhdpi" });
// Always returns an icon (best available)

Saving Icons with Metadata

typescript
import { Apk } from "node-apk";
import { promises as fs } from "node:fs";
import * as path from "node:path";

async function extractAllIcons(apkPath: string, outputDir: string) {
  const apk = new Apk(apkPath);
  await fs.mkdir(outputDir, { recursive: true });
  
  const densities = ["mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"] as const;
  
  for (const density of densities) {
    const icon = await apk.getLauncherIcon({ density });
    const filename = path.join(outputDir, `icon-${density}.png`);
    await fs.writeFile(filename, icon);
    console.log(`Saved ${filename}`);
  }
}

extractAllIcons("app.apk", "./icons");

Detecting Icon Format

Icons can be either PNG or WebP format:

typescript
function detectImageFormat(buffer: Buffer): "png" | "webp" | "unknown" {
  // PNG: starts with 89 50 4E 47
  if (buffer[0] === 0x89 && buffer[1] === 0x50) {
    return "png";
  }
  
  // WebP: starts with 52 49 46 46 (RIFF)
  if (buffer[0] === 0x52 && buffer[1] === 0x49) {
    return "webp";
  }
  
  return "unknown";
}

const apk = new Apk("app.apk");
const icon = await apk.getLauncherIcon();
const format = detectImageFormat(icon);
console.log(`Icon format: ${format}`);

// Save with correct extension
const ext = format === "webp" ? "webp" : "png";
await fs.writeFile(`icon.${ext}`, icon);

Using with Image Processing

typescript
import { Apk } from "node-apk";
import sharp from "sharp"; // npm install sharp

async function processIcon(apkPath: string) {
  const apk = new Apk(apkPath);
  const icon = await apk.getLauncherIcon();
  
  // Resize to specific dimensions
  await sharp(icon)
    .resize(512, 512)
    .png()
    .toFile("icon-512.png");
  
  // Create multiple sizes for app stores
  const sizes = [192, 144, 96, 72, 48, 36];
  
  for (const size of sizes) {
    await sharp(icon)
      .resize(size, size)
      .png()
      .toFile(`icon-${size}.png`);
  }
}

Responsive Icon Generation

typescript
import { Apk } from "node-apk";
import sharp from "sharp";

async function generateResponsiveIcons(apkPath: string) {
  const apk = new Apk(apkPath);
  
  // Get highest quality icon
  const xxxhdpi = await apk.getLauncherIcon({ density: "xxxhdpi" });
  
  // Generate responsive set
  const breakpoints = [
    { name: "sm", size: 32 },
    { name: "md", size: 64 },
    { name: "lg", size: 128 },
    { name: "xl", size: 256 },
    { name: "2xl", size: 512 },
  ];
  
  for (const bp of breakpoints) {
    await sharp(xxxhdpi)
      .resize(bp.size, bp.size)
      .png()
      .toFile(`icon-${bp.name}.png`);
  }
}

Released under the MIT License.