/*
 *  q4toppm  -  convert XLD4(Q4) format image to PPM format image.
 *
 *  Author:  Tetsuya INOUE <tin329@chino.it.okayama-u.ac.jp>
 *
 *
 *  これは、高尾　実 <GGC01363@niftyserve.or.jp> 氏が、fj.sources に
 *  投稿された xq4.tar.gz :
 *    Subject: xq4 - Q4 loader for X Window
 *    Message-ID: <INETNEWS-1-18-1.00049@niftyserve.or.jp>
 *  を改変したものです。
 */

/*-----------------------------------------------------------
 *  (C) Copyright 1992 FUJI SYSTEM Co.,LTD
 *          All Rights Reserved.
 *      Ｑ４ イメージロードプログラム     <xq4.c>
 *-----------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

/* If you want to use this within xv, uncomment "#define USE_WITH_XV" and
 * edit to use LoadQ4 in "xv.h","xv.c","xvdir.c","xvbrowse.c" and etc.
 */
/* xv に組み込んでのテストはまだ行なっていません。組み込む場合に不都合が
 * あれば修正して下さい。
 */
/* #define USE_WITH_XV */

#ifdef USE_WITH_XV
#include "copyright.h"
#include "xv.h"
#endif /* USE_WITH_XV */

#define WIDTH  640
#define HEIGHT 400

#ifndef SEEK_SET
#define SEEK_SET 0
#endif
#ifndef SEEK_CUR
#define SEEK_CUR 1
#endif
#ifndef SEEK_END
#define SEEK_END 2
#endif


#ifdef USE_WITH_XV
int LoadQ4(char *, PICINFO *);
#endif /* USE_WITH_XV */

#undef ARGS
#ifdef __STDC__
#  define ARGS(a) a
#else
#  define ARGS(a) ()
#endif

static int getq4img ARGS((FILE *, char *, char *, char *, unsigned char *, int));
static int decode_1 ARGS((void));
static int decode_2 ARGS((void));


#ifndef USE_WITH_XV
void usage()
{
  fprintf(stderr,"q4toppm : convert XLD4(Q4) format image to PPM format image.\n");
  fprintf(stderr,"usage   : q4toppm <q4_file>\n");
}

int main(argc, argv)
     int argc;
     char **argv;
{
  struct stat    sbuf;
  int fsize;
  char *pic;
  char r[16],g[16],b[16];
  FILE *fp;
  int i;
  char *fname;

  if(argc > 2){
    usage();
    return 1;

  }else if(argc == 2){
    if(!strcmp(argv[1],"-help") || !strcmp(argv[1],"-h")){
      usage();
      return 1;
    }else if(!strcmp(argv[1],"-")){
      if(isatty(0)){ /* if stdin is terminal device (key) */
	usage();
	return 1;
      }else{
	fname = "stdin";
	fsize = -1;  /* don't check file size in getq4img */
	fp = stdin;
      }
    }else{
      fname = argv[1];
      if( stat( fname, &sbuf ) == -1  || (sbuf.st_mode & S_IFMT) != S_IFREG ) {
	fprintf(stderr, "%s   not found\n", fname);
	return 1;
      }
      fsize = sbuf.st_size;
      if((fp = fopen(fname, "r" )) == NULL ) return 1;
    }

  }else if(argc == 1){
    if(isatty(0)){ /* if stdin is terminal device (key) */
      usage();
      return 1;
    }else{
      fname = "stdin";
      fsize = -1;  /* don't check file size in getq4img */
      fp = stdin;
    }
  }

  if((pic = (char *) malloc((size_t) WIDTH * HEIGHT * sizeof(char)))
     ==NULL){
    fprintf(stderr, "Can't allocate memory\n");
    return 1;
  }

  if(getq4img(fp, r, g, b, pic, fsize)!=0){
    fprintf(stderr, "%s   Q4 format error\n", fname);
    free(pic);
    return 1;
  }


  /* パレットのスケールを 0〜15 から 0〜255 に変換 */
  for(i=0; i<16; i++){
    r[i] = r[i] *255/15;
    g[i] = g[i] *255/15;
    b[i] = b[i] *255/15;
  }


  /* PPM ファイルの出力 */
  fprintf(stdout,"P6\n");
  fprintf(stdout,"%d %d\n", WIDTH, HEIGHT);
  fprintf(stdout,"255\n");

  for(i=0; i<WIDTH * HEIGHT; i++){
    fwrite(&r[(int)pic[i]], 1,1,stdout);
    fwrite(&g[(int)pic[i]], 1,1,stdout);
    fwrite(&b[(int)pic[i]], 1,1,stdout);
  }

  return 0;
}
#endif /* !USE_WITH_XV */


#ifdef USE_WITH_XV
int LoadQ4(fname, pinfo)
     char *fname;
     PICINFO *pinfo;
{
  struct stat    sbuf;
  int            fsize;
  FILE          *fp;
  int            i;

  if( stat( fname, &sbuf ) == -1  || (sbuf.st_mode & S_IFMT) != S_IFREG ) {
    fprintf(stderr, "%s   not found\n", fname);
    return 0;
  }
  fsize = sbuf.st_size;
  if((pinfo->pic = (byte *) malloc((size_t) WIDTH * HEIGHT * sizeof(char)))
     ==NULL){
    fprintf(stderr, "Can't allocate memory in LoadQ4\n");
    return 0;
  }
  if((fp = fopen(fname, "r" )) == NULL){
    fprintf(stderr, "Can't open file in LoadQ4\n");
    return 0;
  }
  if( getq4img(fp, pinfo->r, pinfo->g, pinfo->b, &pinfo->pic, fsize)!=0){
    fprintf(stderr, "%s   Q4 format error\n", fname);
    free(pinfo->pic);
    return 0;
  }

  for(i=0; i< 16; i++){
   pinfo->r[i] = pinfo->r[i] *255/15 ;
   pinfo->g[i] = pinfo->g[i] *255/15 ;
   pinfo->b[i] = pinfo->b[i] *255/15 ;
  }

  pinfo->w = pinfo->normw = WIDTH;
  pinfo->h = pinfo->normh = HEIGHT;
  pinfo->type = PIC8;
  pinfo->frmType = F_MAG;
  pinfo->colType = F_FULLCOLOR;
  sprintf(pinfo->fullInfo, "XLD(Q4), 16 colors (%ld bytes)", sbuf.st_size);
  sprintf(pinfo->shrtInfo, "%dx%d XLD(Q4)", WIDTH, HEIGHT);

  return 1;
}
#endif /* USE_WITH_XV */


/*-----------------------------------------------------------
 *      Ｑ４ファイルリード処理プログラム
 *-----------------------------------------------------------*/

#define    cnvint( tbl, ix )    ( ((unsigned char *)(tbl))[ix+1] << 8 | \
                                  ((unsigned char *)(tbl))[ix] )
#define    strint(tbl,ix,num)   ( ((unsigned char *)(tbl))[ix] \
                                             = (unsigned char)((num)&0xff), \
                                  ((unsigned char *)(tbl))[ix+1] \
                                             = (unsigned char)((num)>>8&0xff) )

#define    MIDBUF  (unsigned)0x7f58
#define    ENDBUF  (unsigned)0xfdd5

static unsigned   orig_len, data_len;
static char       palette_ct[] = {
  0x00, 0x02, 0x04, 0x06, 0x01, 0x03, 0x05, 0x07,
  0x08, 0x0A, 0x0C, 0x0E, 0x09, 0x0B, 0x0D, 0x0F};
static unsigned char gbuf0[0xfdf0],gbuf1[0xfdf0],gbuf2[0xfdf0],gbuf3[0xfdf0];


static int getq4img( fp, pt_r, pt_g, pt_b, gbuf, ss )
     FILE            *fp;
     char            *pt_r, *pt_g, *pt_b;     /* palette table */
     unsigned char   *gbuf;                   /* graphic buffa */
     int              ss;
{
  unsigned char fbuf[22];
  long        fsize;
  int         repeatn, num, skipcnt, i;
  unsigned    si, dotcnt;
  unsigned char color, palette_no, rep_palette_no, ch, *dptr;

  if( fread( fbuf, 1, 22, fp ) != 22 ){
    fclose(fp);
    return( 2 );
  }
  if( fbuf[2] != 2  &&  (fbuf[1] > 1 || fbuf[3] > 1) ){
    fclose(fp);
    return( 2 );
  }
  if(ss > 0){
    if( (fsize = cnvint( fbuf, 8 )) != 0 &&
	/**** xmodem 等で、データが大きくなる場合のため ****/
	(fsize |= (long)fbuf[10] << 16) > ss){
      fclose(fp);
      return( 2 );
    }
  }
  if( memcmp( fbuf + 11, "MAJYO", 5 ) != 0 ) {
    fclose(fp);
    return( 2 );
  }
  skipcnt = 0;
  if( (fbuf[4] & 0x02) != 0 )    skipcnt++;
  if( (fbuf[4] & 0x08) != 0 )    skipcnt++;

  /* パレット取得 */
  data_len = cnvint( fbuf, 0x10 );
  if( fread( gbuf1, 1, data_len, fp ) != data_len ) {
    fclose(fp);
    return( 2 );
  }
  data_len = decode_1();                       /* デコード　１ */
  decode_2();                                  /* デコード　２ */

  si = 0;                                      /* デコード　３ */
  dptr = gbuf0;
  while( 1 ) {
    if( (color = gbuf1[si++]) != 0x10 ) {
      repeatn = 1;
    } else {
      if( (ch = gbuf1[si++]) == 0 ) color = gbuf1[si++], ch = gbuf1[si++];
      else color = 0;
      repeatn = (ch << 4) + ch + gbuf1[si++];
    }
    for( ; repeatn > 0; repeatn-- ) *dptr++ = color;
    if( si >= data_len ) break;
  }

  /* パレット出力 */
  for( i = 0; i < 16; i++ ) {
    pt_r[i] = gbuf0[6 * i + 1];
    pt_g[i] = gbuf0[6 * i + 3];
    pt_b[i] = gbuf0[6 * i + 5];
  }

  /* ブロック読み飛ばし処理 */ /* パイプだとエラーになる？ */
  for( ; skipcnt > 0; skipcnt-- )
    if( fread( fbuf, 1, 6, fp ) != 6  ||
       fseek( fp, (long)cnvint( fbuf, 0 ), SEEK_CUR ) != 0 ) {
      fclose(fp);
      return( 2 );
    }

  /* 画像データ処理 */
  while( fread( fbuf, 1, 6, fp ) > 0 ) {
    data_len = cnvint( fbuf, 0 );
    orig_len = cnvint( fbuf, 4 );
    if( fread( gbuf1, 1, data_len, fp ) != data_len ) {
      fclose(fp);
      return( 2 );
    }
    data_len = decode_1();                   /* デコード　１ */
    decode_2();                              /* デコード　２ */

    si = 0;
    dotcnt = orig_len * 2;
    rep_palette_no = 0x00;
    while( 1 ) {
      if( (num = gbuf1[si++]) != 0x10 ) {
	palette_no = palette_ct[num];
	repeatn = 1;
      } else {
	if( (ch = gbuf1[si++]) == 0 )
	  rep_palette_no = palette_ct[gbuf1[si++]], ch = gbuf1[si++];
	palette_no = rep_palette_no;
	repeatn = (ch << 4) + ch + gbuf1[si++];
      }
      for( ; repeatn > 0 && dotcnt > 0; repeatn--, dotcnt-- ) 
	*gbuf++ = palette_no;
      if( si >= data_len ) {
	for( ; dotcnt > 0; dotcnt-- )    /* ドット出力不足時は穴埋め */
	  *gbuf++ = palette_no;
	break;
      }
    }
  }

  fclose( fp );
  return( 0 );
}

/* デコード　１ */
static int decode_1()
{
  unsigned    outnum, misyori, si, di;
  int         syoribitw, incbitw;

  strint( gbuf1, data_len, 0 );
  si = 0, di = 0;
  misyori = gbuf1[si++];
  incbitw = 3;

  for( syoribitw = 0; si <= data_len; ) {
    syoribitw += incbitw;
    if( syoribitw < 8 ) {
      outnum = misyori >> (8 - syoribitw);
      misyori &= 0xff >> syoribitw;
    } else if( syoribitw < 16 ) {
      outnum = gbuf1[si] >> (16 - syoribitw);
      outnum |= misyori << (syoribitw - 8);
      misyori = (0xff >> (syoribitw - 8)) & gbuf1[si++];
    } else if( syoribitw < 24 ) {
      outnum = misyori << (syoribitw - 8);
      outnum |= gbuf1[si++] << (syoribitw - 16);
      outnum |= gbuf1[si] >> (24 - syoribitw);
      misyori = gbuf1[si++] & (0xff >> (syoribitw - 16));
    } else break;

    syoribitw &= 0x07;
    if( outnum == 0 ) break;
    if( outnum == 1 ) incbitw++;
    else strint( gbuf0, di, outnum - 2 ), di += 2;
  }
  strint( gbuf0, di, 0 );

  return(di / 2);
}

/* デコード　２ */
static int decode_2()
{
  unsigned    ix, len, si, di,soffset, doffset;
  unsigned    gbuf1_len, gbuf3_int, prev_int, next_int, cur_int;
  unsigned char *sptr, *dptr;

  for( ix = 0; ix <= 0x11; ix++ ) {
    gbuf2[ENDBUF + ix] = (char)ix;
    strint( gbuf3, ix * 2, ENDBUF + ix );
    strint( gbuf3 + MIDBUF, ix * 2, 1 );
  }
  strint( gbuf3, ix * 2, 0 );
  strint( gbuf3 + MIDBUF, ix * 2, 0 );

  gbuf1_len = 0, si = 1;
  doffset = 0;
  prev_int = cnvint( gbuf0, 0 );
  cur_int = 0x11;

  while( 1 ) {
    next_int = cnvint( gbuf0, si * 2 );
    si++;
    if( next_int > cur_int ) {
      data_len = gbuf1_len;
      return( 0 );
    }
    di = prev_int * 2;
    gbuf3_int = soffset = cnvint( gbuf3, di );
    len = cnvint( gbuf3, MIDBUF + di );
    if( next_int != cur_int ) gbuf3_int = cnvint( gbuf3, next_int * 2 );
    strint( gbuf3, cur_int*2, doffset );
    strint( gbuf3, MIDBUF + cur_int * 2, len + 1 );
    for( ix = len, sptr = gbuf2 + soffset,
	dptr = gbuf2 + doffset; ix > 0; ix-- ) *dptr++ = *sptr++;
    *dptr++ = gbuf2[gbuf3_int];
    doffset += len + 1;
    for( ix = len, sptr = gbuf2 + soffset,
	dptr = gbuf1 + gbuf1_len; ix > 0; ix-- ) *dptr++ = *sptr++;
    gbuf1_len += len;
    if( si >= data_len ) {
      gbuf1[gbuf1_len] = 0;
      data_len = gbuf1_len;
      return( 0 );
    }
    cur_int++;
    prev_int = next_int;
  }
}
