hlfw.ca

binpack

ref: 55f497647e7c913d8627222a5e8afe62970fb689
dir: /binpack.c/

View raw version
/* Simple bin packing algorithm implementation */

#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <stdbool.h>

struct mbin {
  /* from -x -y -g flags */
  unsigned x, y, gaps;
};

struct input {
  /* from stdin */
  unsigned min_w, min_h, max_w, max_h;
  unsigned long wid;
};

struct output {
  /* To hold final output */
  unsigned x, y, w, h;
  unsigned long wid;
};

struct bins {
  /* temporary storage of available sub-bins */
  unsigned x, y, w, h;
};

unsigned create_rect(struct input r[]) {
  /* Read from stdin, create each rect */
  size_t length = 0;
  char line[50];
  for (unsigned i = 0; fgets(line, sizeof(line), stdin); ++i) {
    sscanf(line, "%d %d %d %d %lx", &r[i].min_w, &r[i].min_h, &r[i].max_w,
           &r[i].max_h, &r[i].wid);
    length++;
  }
  return length;
}

void center(const size_t length, struct output r[], struct mbin mb) {
  /* (max bin size - blob size) / 2 */
  unsigned w = 0, h = 0;
  for (size_t i = 0; i < length; i++) {
    if (w < (r[i].w + r[i].x)) {
      w = r[i].w + r[i].x;
    }
    if (h < (r[i].h + r[i].y)) {
      h = r[i].h + r[i].y;
    }
  }
  for (size_t i = 0; i < length; i++) {
    r[i].x += (mb.x - w) / 2;
    r[i].y += (mb.y - h) / 2;
  }
}

void sort_bins(struct bins b[], size_t *bin_count) {
  /* given set of bins, arrange them smallest to largest w*h */
  struct bins temp;
  for (unsigned i = 1; i < *bin_count; i++) {
    for (unsigned j = 0; j < *bin_count - i; j++) {
      if ((b[j + 1].w * b[j + 1].h) < (b[j].w * b[j].h)) {
        temp = b[j];
        b[j] = b[j + 1];
        b[j + 1] = temp;
      }
    }
  }
}

void sort_input(struct input r[], const size_t length) {
  /* arrange rectangles largest to smallest, normalized some over min/max */
  struct input temp;
  for (size_t i = 1; i < length; i++) {
    for (size_t j = 0; j < length - i; j++) {
      if ((r[j + 1].max_w * r[j + 1].min_h) > (r[j].max_w * r[j].min_h)) {
        temp = r[j];
        r[j] = r[j + 1];
        r[j + 1] = temp;
      }
    }
  }
}

void create_bins(struct bins bin[], struct output out[], size_t i, size_t j,
                 size_t *bin_count, struct mbin mb) {
  /* New bins based on subsection of old  */
  unsigned x, y, w, h;
  x = bin[j].x;
  y = bin[j].y;
  w = bin[j].w;
  h = bin[j].h;
  /* rect smaller, make two sub bins */
  if (out[i].h + mb.gaps < h && out[i].w + mb.gaps < w) {
    bin[*bin_count] =
        (struct bins){.x = x + out[i].w + mb.gaps, .y = y, .w = w - out[i].w - mb.gaps, .h = h};
    bin[j].y += (out[i].h + mb.gaps);
    bin[j].h -= (out[i].h - mb.gaps);
    *bin_count += 1;
    /* rect same height */
  } else if (out[i].h + mb.gaps < h) {
    bin[j].y += (out[i].h + mb.gaps);
    bin[j].h -= (out[i].h - mb.gaps);
    /* rect same width */
  } else if (out[i].w + mb.gaps < w) {
    bin[j].x += (out[i].w + mb.gaps);
    bin[j].w -= (out[i].w - mb.gaps);
    /* rect fills space, lose a bin */
  } else {
    bin[j].w = 0;
    bin[j].h = 0;
    *bin_count -= 1;
  }
}

void save_rect(struct bins bin[], struct output out[], struct input r[],
               size_t i, size_t j, struct mbin mb) {
  /* Store rect x y w h wid */
  out[i] = (struct output){.x = bin[j].x + (mb.gaps / 2),
                           .y = bin[j].y + (mb.gaps / 2),
                           .w = r[i].min_w,
                           .h = r[i].min_h,
                           .wid = r[i].wid};
}

bool pack_bin(struct output out[], struct input r[], const size_t length,
              unsigned *bin_width, unsigned *bin_height, struct mbin mb) {
  /* Main algorithm */
  struct bins bin[50];
  for (int i = 0; i < 50; i++) {
    bin[i] = (struct bins){.x = 0, .y = 0, .w = 0, .h = 0};
  }
  /* default bin */
  bin[0] = (struct bins){.x = 0, .y = 0, .w = *bin_width + mb.gaps, .h = *bin_height + mb.gaps};

  size_t bin_count = 1;
  bool rect_fits;

  /* loop through each rect */
  for (size_t i = 0; i < length; i++) {
    rect_fits = false;
    /* loop through each bin */
    for (size_t j = 0; j < bin_count; j++) {
      /* rect fits in current bin */
      if (r[i].min_w + mb.gaps <= bin[j].w && r[i].min_h + mb.gaps <= bin[j].h) {
        rect_fits = true;
        save_rect(bin, out, r, i, j, mb);
        create_bins(bin, out, i, j, &bin_count, mb);
        sort_bins(bin, &bin_count);
        break;
      }
    }
    /* if rect does not fit all bin */
    if (rect_fits == false) {
      /* Grow main bin if possible */
      if (mb.x > *bin_width) {
        *bin_width += 2;
      }
      if (mb.y > *bin_height) {
        *bin_height += 2;
      }
      return true;
    }
  }
  return false;
}

bool resize(struct output out[], struct input r[], const size_t length,
            unsigned *bin_width, unsigned *bin_height, struct mbin mb) {
  /* When a bin fills all the space available, pack_bin */

  for (size_t i = 0; i < length; i++) {
    unsigned limitcheck = 0;
    while (pack_bin(out, r, length, bin_width, bin_height, mb)) {
      if (limitcheck == mb.x) {
        return false;
      }
      limitcheck++;
    }
    /* If rect can grow */
    if (out[i].h < r[i].max_h) {
      r[i].min_h += 1;
    }
    if (out[i].w < r[i].max_w) {
      r[i].min_w += 1;
    }
    sort_input(r, length);
  }

  /* max_h to handle cases of smaller windows */
  unsigned w = 0, h = 0;
  for (size_t i = 0; i < length; i++) {
    if (w < (out[i].w + out[i].x)) {
      w = out[i].w + out[i].x;
    }
    if (h < (out[i].h + out[i].y)) {
      h = out[i].h + out[i].y;
    }
  }

  if (h >= mb.y - mb.gaps) {
    return false;
  }
  return true;
}

int main(int argc, char *argv[]) {
  struct mbin mb;
  mb.gaps = 0;
  int c;
  while ((c = getopt(argc, argv, "hg:x:y:")) != -1) {
    switch (c) {
      /* read in, all vars unsigned int */
      case 'g': {
        sscanf(optarg, "%u", &mb.gaps);
        break;
      }
      case 'x': {
        sscanf(optarg, "%u", &mb.x);
        break;
      }
      case 'y': {
        sscanf(optarg, "%u", &mb.y);
        break;
      }
      case 'h': {
        fputs("Usage: bin_pack -x screen_width -y screen_height -g gaps\n",
              stderr);
        return EXIT_SUCCESS;
      }
    }
  }

  struct input r[50];
  struct output out[50];

  const size_t length = (create_rect(r));

  /* If we have no windows, exit */
  if (length == 0) {
    return EXIT_SUCCESS;
  }

  sort_input(r, length);

  unsigned bin_width = mb.x;
  unsigned bin_height = mb.y;

  /* If we have no large windows */
  bool no_large_windows = true;
  for (size_t i = 0; i < length; i++) {
    struct input temp = r[i];
    r[i].min_w = r[i].max_w;
    r[i].min_h = r[i].max_h;
    if (pack_bin(out, r, length, &bin_width, &bin_height, mb)) {
      r[i] = temp;
      no_large_windows = false;
    }
  }
  unsigned limitcheck = 0;

  if (no_large_windows == false) {
    bin_width = r[0].min_w;
    bin_height = r[0].min_h;

    while (pack_bin(out, r, length, &bin_width, &bin_height, mb)) {
      /* if we've ran this long, something is up */
      if (limitcheck == mb.x) {
        return EXIT_FAILURE;
      }
      limitcheck++;
    }

    limitcheck = 0;

    /* Square out the blob as best as we can */
    while (resize(out, r, length, &bin_width, &bin_height, mb)) {
      if (limitcheck == mb.x) {
        return EXIT_FAILURE;
      }
      limitcheck++;
    }
  }

  center(length, out, mb);

  for (size_t i = 0; i < length; i++) {
    printf("%d %d %d %d %lx\n", out[i].x, out[i].y, out[i].w, out[i].h,
           out[i].wid);
  }
  return EXIT_SUCCESS;
}