Blob Blame History Raw

#include "img.h"


#pragma pack(push,1)
typedef struct {
  unsigned char  idLength;
  unsigned char  colormapType;
  unsigned char  imageType;
  unsigned char  colormapIndex[2];
  unsigned char  colormapLength[2];
  unsigned char  colormapSize;
  unsigned char  xOrigin[2];
  unsigned char  yOrigin[2];
  unsigned char  width[2];
  unsigned char  height[2];
  unsigned char  pixelSize;
  unsigned char  attributes;
} TgaHeader;
#pragma pack(pop)


Image imgCreate(int w, int h) {
  if (w <= 0 || h <= 0)
    return (Image){};
  Color *pixels = malloc(sizeof(Color)*w*h);
  if (!pixels)
    return LOGERR("imgCreate: cannot alocate memory for pixels (%dx%d)", w, h), (Image){};
  return (Image){ .w = w, .h = h, .data = pixels };
}


void imgFree(Image *img) {
  if (!img) return;
  LOGDBG("imgFree");
  free(img->data);
  *img = (Image){};
}


Image imgCopy(Image img) {
  if (!imgValid(img)) return (Image){};
  Image res = imgCreate(img.w, img.h);
  if (!imgValid(res)) return (Image){};
  memcpy(res.data, img.data, sizeof(Color)*img.w*img.h);
  return res;
}


Image imgSub(Image img, int x, int y, int w, int h) {
  if (!imgValid(img)) return (Image){};
  if (x < 0) w += x, x = 0;
  if (y < 0) h += y, y = 0;
  if (x + w > img.w) w = img.w - x;
  if (y + h > img.h) h = img.h - y;
  if (w <= 0 || h <= 0) return (Image){};
  Image res = imgCreate(w, h);
  if (!imgValid(res)) return (Image){};
  for(int i = 0; i < h; ++i)
    memcpy(res.data + i*w, img.data + (y+i)*img.w + x, sizeof(Color)*w);
  return res;
}


void imgClear(Image img) {
  if (!imgValid(img)) return;
  memset(img.data, 0, sizeof(Color)*img.w*img.h);
}


void imgMultA(Image img) {
  if (!imgValid(img)) return;
  for(Color *p = img.data, *e = p + img.w*img.h; p < e; ++p) {
    unsigned int a = p->a;
    p->r = (p->r*a + 0xff) >> 8;
    p->g = (p->g*a + 0xff) >> 8;
    p->b = (p->b*a + 0xff) >> 8;
  }
}


void imgDivA(Image img) {
  if (!imgValid(img)) return;
  for(Color *p = img.data, *e = p + img.w*img.h; p < e; ++p) {
    unsigned int a = p->a;
    if (!a) continue;
    p->r = (((unsigned int)p->r << 8) - 1)/a;
    p->g = (((unsigned int)p->g << 8) - 1)/a;
    p->b = (((unsigned int)p->b << 8) - 1)/a;
  }
}


int imgVisible(Image img) {
  if (!imgValid(img)) return 0;
  for(Color *p = img.data, *e = p + img.w*img.h; p < e; ++p)
    if (p->a) return 1;
  return 0;
}


int imgHasPicture(Image img) {
  if (!imgValid(img)) return 0;
  Color c = {};
  for(Color *p = img.data, *e = p + img.w*img.h; p < e; ++p)
    if (p->a) { c = *p; break; }
  if (!c.a) return 0;
  for(Color *p = img.data, *e = p + img.w*img.h; p < e; ++p)
    for(int i = 0; i < 4; ++i)
      if (p->c[i] != c.c[i]) return 1;
  return 0;
}


static void rowResample(
  Color *dst, unsigned int dcnt, unsigned int dstep,
  Color *src, unsigned int scnt, unsigned int sstep
) {
  if (dcnt < scnt) {
    // downscale
    unsigned int sistep = dcnt << 8, si = sistep, di1 = 0x100, w = 0, sum[4] = {};
    for(Color *e = src + scnt*sstep; src != e; src += sstep, si += sistep) {
      unsigned int di = si/scnt;
      if (di < di1) {
        w += 0xff;
        for(int i = 0; i < 4; ++i)
          sum[i] += src->c[i];
      } else {
        unsigned int k1 = (di - di1)*scnt/dcnt;
        unsigned int k0 = 0xff - k1;
        w += k0;
        for(int i = 0; i < 4; ++i) {
          unsigned int c = src->c[i];
          unsigned int cc = (c*k0 + 0xff) >> 8;
          dst->c[i] = (sum[i] + cc)*255/w;
          sum[i] = (c*k1 + 0xff) >> 8;
        }
        w = k1;
        di1 += 0x100;
        dst += dstep;
      }
    }
  } else {
    // upscale
    if (!--dcnt) dcnt = 1;
    if (!--scnt) sstep = 0;
    unsigned int di = 0, distep = (scnt << 8) - 1;
    for(Color *e = dst + dcnt*dstep + dstep; dst != e; dst += dstep, di += distep) {
      unsigned int si = di/dcnt;
      unsigned int k1 = si & 0xff;
      unsigned int k0 = 0xff - k1;
      si >>= 8;
      Color *s0 = src + si*sstep;
      Color *s1 = s0 + sstep;
      for(int i = 0; i < 4; ++i)
        dst->c[i] = (s0->c[i]*k0 + s1->c[i]*k1 + 0xff) >> 8;
    }
  }
}


Image imgResampleX(Image img, int w) {
  if (!imgValid(img) || w <= 0)
    return (Image){};
  if (img.w == w)
    return imgCopy(img);
  Image res = imgCreate(w, img.h);
  for(int i = 0; i < res.h; ++i)
    rowResample(res.data + i*w, w, 1, img.data + i*img.w, img.w, 1);
  return res;
}


Image imgResampleY(Image img, int h) {
  if (!imgValid(img) || h <= 0)
    return (Image){};
  if (img.h == h)
    return imgCopy(img);
  Image res = imgCreate(img.w, h);
  for(int i = 0; i < res.w; ++i)
    rowResample(res.data + i, h, img.w, img.data + i, img.h, img.w);
  return res;
}


Image imgResample(Image img, int w, int h) {
  if (!imgValid(img) || w <= 0 || h <= 0)
    return (Image){};
  if (img.w == w && img.h == h)
    return imgCopy(img);
  if (img.h == h)
    return imgResampleX(img, w);
  if (img.w == w)
    return imgResampleY(img, h);

  //clock_t start = clock();
  Image tmp = imgResampleX(img, w);
  Image res = imgResampleY(tmp, h);
  imgFree(&tmp);
  //LOGDBG("imgResample: %f sec", (clock() - start)/(double)CLOCKS_PER_SEC);

  return res;
}


XImage* imgToX(Image *img) {
  LOGDBG("imgToX: create XImage");
  if (!img)
    return LOGERR("imgToX: bad args"), NULL;
  XImage *res = XCreateImage(dpy, visual, 24, ZPixmap, 0, (char*)img->data, img->w, img->h, 32, img->w*sizeof(Color));
  if (!res)
    return LOGERR("imgToX: cannot create XImage (%dx%d)", img->w, img->h), imgFree(img), NULL;
  *img = (Image){};
  return res;
}


Image imgLoadTga(const char *filename) {
  LOGDBG("imgLoadTga: open: %s", filename);
  FILE *f = fopen(filename, "rb");
  if (!f)
    return LOGERR("imgLoadTga: cannot open file for read: %s", filename), (Image){};

  LOGDBG("imgLoadTga: read header");
  TgaHeader header = {};
  fread(&header, sizeof(header), 1, f);
  if ( header.imageType != 2
    || (!header.width[0] && !header.width[1])
    || (!header.height[0] && !header.height[1])
    || header.colormapType
    || (header.pixelSize != 24 && header.pixelSize != 32) )
    return LOGERR("imgLoadTga: unsupported format, only uncompressed 24 or 32 bits TGA are supported: %s", filename), fclose(f), (Image){};

  int w = header.width[1]*256 + header.width[0];
  int h = header.height[1]*256 + header.height[0];

  LOGDBG("imgLoadTga: create image (%d x %d)", w, h);
  Image res = imgCreate(w, h);
  if (!imgValid(res))
    return LOGERR("imgLoadTga: cannot create image (%d x %d): %s", w, h, filename), fclose(f), (Image){};

  LOGDBG("imgLoadTga: read data");
  fseek(f, header.idLength, SEEK_CUR);
  Color *row = res.data + w*h;
  for(unsigned short r = h; r; --r, row -= w) {
    for(Color *c = row - w; c < row; ++c) {
      c->r = fgetc(f);
      c->g = fgetc(f);
      c->b = fgetc(f);
      c->a = header.pixelSize == 32 ? fgetc(f) : 255;
    }
  }

  LOGDBG("imgLoadTga: done");
  fclose(f);
  return res;
}