chromium/tools/mac/icons/additional_tools/unmakeicns.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string>

#include "makepng.h"
#include "unpackicon.h"

#define arraysize(x) (sizeof(x) / sizeof(x[0]))

namespace {

struct IconType {
  uint32_t magic;
  const char* filename;
};

// clang-format off
const IconType icon_types[] = {
  { 'icp4', "16x16" },
  { 'icp5', "32x32" },
  { 'icp6', "64x64" },
  { 'ic07', "128x128" },
  { 'ic08', "256x256" },
  { 'ic09', "512x512" },
  { 'ic10', "512x512@2x" },  // previously 1024x1024
  { 'ic11', "16x16@2x" },
  { 'ic12', "32x32@2x" },
  { 'ic13', "128x128@2x" },
  { 'ic14', "256x256@2x" },
};
// clang-format on

struct IcnsHeader {
  uint32_t magic;
  uint32_t length;
};

struct IconHeader {
  uint32_t magic;
  uint32_t length;
};

bool IsPrintableASCIINoSlash(char c) {
  return c >= ' ' && c <= '~' && c != '/';
}

std::string FourCCToASCII(uint32_t fourcc) {
  const char chars[] = {
      fourcc >> 24,
      (fourcc >> 16) & 0xff,
      (fourcc >> 8) & 0xff,
      fourcc & 0xff,
  };

  bool use_ascii = true;
  for (size_t i = 0; i < arraysize(chars); ++i) {
    if (!IsPrintableASCIINoSlash(chars[i])) {
      use_ascii = false;
      break;
    }
  }

  char buf[11];
  if (use_ascii) {
    snprintf(buf, sizeof(buf), "%c%c%c%c", chars[0], chars[1], chars[2],
             chars[3]);
  } else {
    snprintf(buf, sizeof(buf), "0x%x", fourcc);
  }

  return std::string(buf);
}

}  // namespace

int main(int argc, char* argv[]) {
  const char* me = argv[0];

  if (argc != 3) {
    fprintf(stderr, "usage: %s <icns> <iconset>\n", me);
    return EXIT_FAILURE;
  }

  const char* input_path = argv[1];
  int input_fd = open(input_path, O_RDONLY);
  if (input_fd < 0) {
    fprintf(stderr, "%s: open %s: %s\n", me, input_path, strerror(errno));
    return EXIT_FAILURE;
  }

  const char* output_path = argv[2];
  if (mkdir(output_path, 0755) != 0 && errno != EEXIST) {
    fprintf(stderr, "%s: mkdir %s: %s\n", me, output_path, strerror(errno));
    return EXIT_FAILURE;
  }

  size_t total_read = 0;
  IcnsHeader icns_header;
  ssize_t nread = read(input_fd, &icns_header, sizeof(icns_header));
  if (nread < 0) {
    fprintf(stderr, "%s: read: %s\n", me, strerror(errno));
    return EXIT_FAILURE;
  } else if (nread != sizeof(icns_header)) {
    fprintf(stderr, "%s: read: expected %zu, observed %zd\n", me,
            sizeof(icns_header), nread);
    return EXIT_FAILURE;
  }
  total_read += nread;

  const uint32_t icns_header_magic = ntohl(icns_header.magic);
  if (icns_header_magic != 'icns') {
    fprintf(stderr, "%s: icns file magic: expected 0x%x, observed 0x%x\n", me,
            'icns', icns_header_magic);
    return EXIT_FAILURE;
  }

  const uint32_t icns_header_length = ntohl(icns_header.length);

  struct ImageAndMask {
    std::string image_path;
    std::string mask_path;
    std::string png_path;
  };
  ImageAndMask images_and_masks[4];
  enum ImageAndMaskIndex {
    IMAGE_16,
    IMAGE_32,
    IMAGE_48,
    IMAGE_128,
    IMAGE_NONE,
  };
  const size_t kImageAndMaskDimensions[] = {16, 32, 48, 128};

  while (total_read < icns_header_length) {
    IconHeader icon_header;
    nread = read(input_fd, &icon_header, sizeof(icon_header));
    if (nread < 0) {
      fprintf(stderr, "%s: read: %s\n", me, strerror(errno));
      return EXIT_FAILURE;
    } else if (nread != sizeof(icon_header)) {
      fprintf(stderr, "%s: read: expected %zd, observed %zu\n", me,
              sizeof(icon_header), nread);
      return EXIT_FAILURE;
    }
    total_read += nread;

    size_t icon_length = ntohl(icon_header.length) - sizeof(icon_header);
    char* icon_data = reinterpret_cast<char*>(malloc(icon_length));
    if (!icon_data) {
      fprintf(stderr, "%s: malloc %zu: %s\n", me, icon_length, strerror(errno));
      return EXIT_FAILURE;
    }

    size_t icon_data_read = 0;
    while (icon_length - icon_data_read > 0) {
      nread = read(input_fd, icon_data + icon_data_read,
                   icon_length - icon_data_read);
      if (nread < 0) {
        fprintf(stderr, "%s: read: %s\n", me, strerror(errno));
        return EXIT_FAILURE;
      } else if (nread > icon_length - icon_data_read) {
        fprintf(stderr, "%s: read: expected %zu, observed %zd\n", me,
                icon_length - icon_data_read, nread);
        return EXIT_FAILURE;
      }
      icon_data_read += nread;
    }
    total_read += icon_data_read;

    const uint32_t icon_header_magic = ntohl(icon_header.magic);
    bool found = false;
    std::string output_icon_path = output_path;
    output_icon_path += '/';
    for (size_t icon_type_index = 0; icon_type_index < arraysize(icon_types);
         ++icon_type_index) {
      const IconType& icon_type = icon_types[icon_type_index];
      if (icon_header_magic == icon_type.magic) {
        found = true;
        output_icon_path += "icon_";
        output_icon_path += icon_type.filename;
        output_icon_path += ".png";
        break;
      }
    }

    if (!found) {
      output_icon_path += FourCCToASCII(icon_header_magic);
    }

    const char* output_icon_path_c = output_icon_path.c_str();
    printf("%s\n", output_icon_path_c);

    int output_fd;
    output_fd = open(output_icon_path_c, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (output_fd < 0) {
      fprintf(stderr, "%s: open %s: %s\n", me, output_icon_path_c,
              strerror(errno));
      return EXIT_FAILURE;
    }

    size_t icon_data_written = 0;
    while (icon_length - icon_data_written > 0) {
      ssize_t nwritten = write(output_fd, icon_data + icon_data_written,
                               icon_length - icon_data_written);
      if (nwritten < 0) {
        fprintf(stderr, "%s: write: %s\n", me, strerror(errno));
        return EXIT_FAILURE;
      } else if (nwritten > icon_length - icon_data_written) {
        fprintf(stderr, "%s: write: expected %zu, observed %zd\n", me,
                icon_length - icon_data_written, nwritten);
        return EXIT_FAILURE;
      }
      icon_data_written += nwritten;
    }

    if (close(output_fd) < 0) {
      fprintf(stderr, "%s: close %s: %s\n", me, output_icon_path_c,
              strerror(errno));
      return EXIT_FAILURE;
    }

    free(icon_data);

    ImageAndMaskIndex index = IMAGE_NONE;
    switch (icon_header_magic) {
      case 'is32':
        index = IMAGE_16;
        break;
      case 'il32':
        index = IMAGE_32;
        break;
      case 'ih32':
        index = IMAGE_48;
        break;
      case 'it32':
        index = IMAGE_128;
        break;
      case 's8mk':
        images_and_masks[IMAGE_16].mask_path = output_icon_path;
        break;
      case 'l8mk':
        images_and_masks[IMAGE_32].mask_path = output_icon_path;
        break;
      case 'h8mk':
        images_and_masks[IMAGE_48].mask_path = output_icon_path;
        break;
      case 't8mk':
        images_and_masks[IMAGE_128].mask_path = output_icon_path;
        break;
    }

    if (index != IMAGE_NONE) {
      images_and_masks[index].image_path = output_icon_path;
      images_and_masks[index].png_path = output_icon_path + ".png";
    }

    if ((icon_header_magic == 'is32' && icon_length != 16 * 16 * 3) ||
        (icon_header_magic == 'il32' && icon_length != 32 * 32 * 3) ||
        (icon_header_magic == 'ih32' && icon_length != 48 * 48 * 3) ||
        (icon_header_magic == 'it32' && icon_length != 128 * 128 * 3)) {
      const std::string output_icon_unpacked_path =
          output_icon_path + ".unpacked";
      const char* output_icon_unpacked_path_c =
          output_icon_unpacked_path.c_str();
      printf("%s\n", output_icon_unpacked_path_c);

      // is32 and il32 definitely don’t use skip. it32 definitely does. I’m not
      // sure about ih32, but I think it doesn’t use skip.
      const bool skip = icon_header_magic == 'it32';

      if (!UnpackIcon(output_icon_path_c, output_icon_unpacked_path_c, skip)) {
        return EXIT_FAILURE;
      }

      assert(index != IMAGE_NONE);
      images_and_masks[index].image_path = output_icon_unpacked_path;
    }
  }

  for (size_t index = 0; index < arraysize(images_and_masks); ++index) {
    const ImageAndMask& image_and_mask = images_and_masks[index];
    if (!image_and_mask.image_path.empty() &&
        !image_and_mask.mask_path.empty()) {
      printf("%s\n", image_and_mask.png_path.c_str());
      if (!EncodePNG(image_and_mask.image_path.c_str(),
                     image_and_mask.mask_path.c_str(),
                     image_and_mask.png_path.c_str(),
                     kImageAndMaskDimensions[index])) {
        return EXIT_FAILURE;
      }
    }
  }

  if (total_read != icns_header_length) {
    fprintf(stderr, "%s: icns file length: expected %d, observed %zu\n", me,
            icns_header_length, total_read);
    return EXIT_FAILURE;
  }

  if (close(input_fd) < 0) {
    fprintf(stderr, "%s: close %s: %s\n", me, input_path, strerror(errno));
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}