Free Hero Mesh

Artifact [e208362902]
Login
This is a mirror of the main repository for Free Hero Mesh. New tickets and changes will not be accepted at this mirror.

Artifact e208362902f6b7415a122cf4d014706b92e4a0cc:


#if 0
gcc ${CFLAGS:--s -O2} -c -Wno-multichar picedit.c `sdl-config --cflags`
exit
#endif

/*
  This program is part of Free Hero Mesh and is public domain.
*/

#include "SDL.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqlite3.h"
#include "smallxrm.h"
#include "heromesh.h"
#include "quarks.h"
#include "cursorshapes.h"

typedef struct {
  Uint8 size,meth;
  Uint8 data[0]; // the first row is all 0, since the compression algorithm requires this
} Picture;

static int load_picture_file(void) {
  sqlite3_stmt*st=0;
  FILE*fp;
  char*nam;
  char*buf=0;
  int r=0;
  int i,j;
  i=sqlite3_exec(userdb,
    "BEGIN;"
    "CREATE TEMPORARY TABLE `PICEDIT`(`ID` INTEGER PRIMARY KEY,`NAME` TEXT NOT NULL COLLATE NOCASE,`TYPE` INT,`DATA` BLOB);"
    "CREATE INDEX `PICEDIT_I1` ON `PICEDIT`(`NAME`,`TYPE`);"
  ,0,0,0);
  if(i) fatal("SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
  nam=sqlite3_mprintf("%s.xclass",basefilename);
  if(!nam) fatal("Allocation failed\n");
  fprintf(stderr,"Loading pictures...\n");
  fp=fopen(nam,"r");
  sqlite3_free(nam);
  if(!fp) {
    nam=0;
    fprintf(stderr,"%m\n");
    goto done;
  }
  nam=malloc(256);
  if(!nam) fatal("Allocation failed\n");
  i=sqlite3_prepare_v2(userdb,"INSERT INTO `PICEDIT`(`NAME`,`TYPE`,`DATA`) VALUES(?1,?2,?3);",-1,&st,0);
  if(i) fatal("SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
  while(!feof(fp)) {
    i=0;
    while(j=fgetc(fp)) {
      if(j==EOF) goto done;
      if(i<255) nam[i++]=j;
    }
    nam[i]=0;
    sqlite3_reset(st);
    sqlite3_bind_text(st,1,nam,i,SQLITE_TRANSIENT);
    sqlite3_bind_int(st,2,j=(i>4 && !memcmp(".IMG",nam+i-4,4)?1:0));
    r+=j;
    i=fgetc(fp)<<16;
    i|=fgetc(fp)<<24;
    i|=fgetc(fp)<<0;
    i|=fgetc(fp)<<8;
    if(!i) continue;
    buf=realloc(buf,i);
    if(!buf) fatal("Allocation failed\n");
    fread(buf,1,i,fp);
    sqlite3_bind_blob(st,3,buf,i,SQLITE_TRANSIENT);
    while((i=sqlite3_step(st))==SQLITE_ROW);
    if(i!=SQLITE_DONE) fatal("SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
  }
done:
  if(st) sqlite3_finalize(st);
  if(fp) fclose(fp);
  free(nam);
  free(buf);
  fprintf(stderr,"Done\n");
  return r;
}

static void save_picture_file(void) {
  sqlite3_stmt*st;
  FILE*fp;
  char*s=sqlite3_mprintf("%s.xclass",basefilename);
  int i;
  const char*nam;
  const char*buf;
  if(!s) fatal("Allocation failed\n");
  fprintf(stderr,"Saving pictures...\n");
  fp=fopen(s,"w");
  if(!fp) fatal("Cannot open picture file for writing: %m\n");
  sqlite3_free(s);
  i=sqlite3_prepare_v2(userdb,"SELECT `NAME`,`DATA` FROM `PICEDIT`;",-1,&st,0);
  if(i) fatal("SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
  while((i=sqlite3_step(st))==SQLITE_ROW) {
    nam=sqlite3_column_text(st,0);
    buf=sqlite3_column_blob(st,1);
    i=sqlite3_column_bytes(st,1);
    if(!nam) continue;
    fwrite(nam,1,strlen(nam)+1,fp);
    fputc(i>>16,fp);
    fputc(i>>24,fp);
    fputc(i>>0,fp);
    fputc(i>>8,fp);
    if(i) fwrite(buf,1,i,fp);
  }
  if(i!=SQLITE_DONE) fprintf(stderr,"SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
done:
  if(st) sqlite3_finalize(st);
  if(fp) fclose(fp);
  fprintf(stderr,"Done\n");
}

static sqlite3_int64 ask_picture_id(const char*t) {
  sqlite3_stmt*st;
  const char*r=screen_prompt(t);
  int i;
  sqlite3_int64 id=0;
  if(!r || !*r) return 0;
  i=sqlite3_prepare_v2(userdb,"SELECT `ID` FROM `PICEDIT` WHERE `NAME` = (?1 || '.IMG');",-1,&st,0);
  if(i) {
    screen_message(sqlite3_errmsg(userdb));
    return 0;
  }
  sqlite3_bind_text(st,1,r,-1,0);
  i=sqlite3_step(st);
  if(i==SQLITE_ROW) id=sqlite3_column_int64(st,0);
  if(i==SQLITE_DONE) screen_message("Picture not found");
  sqlite3_finalize(st);
  return id;
}

static void uncompress_picture(FILE*fp,Picture*pic) {
  Uint8*p=pic->data+pic->size;
  int c,n,t,x,y;
  n=t=0;
  y=pic->size*pic->size;
  while(y--) {
    if(!n) {
      n=fgetc(fp);
      if(n<85) {
        // Homogeneous run
        n++;
        x=fgetc(fp);
        if(t==1 && x==c) n*=85; else n++;
        c=x;
        t=1;
      } else if(n<170) {
        // Heterogeneous run
        n-=84;
        t=2;
      } else {
        // Copy-above run
        n-=169;
        if(t==3) n*=85;
        t=3;
      }
    }
    n--;
    if(t==2) c=fgetc(fp);
    if(t==3) c=p[-pic->size];
    *p++=c;
  }
}

static inline void load_rotate(Picture*pic) {
  int x,y;
  int m=pic->meth;
  int s=pic->size;
  Uint8*d=pic->data+s;
  static Uint8*buf;
  Uint8*p;
  if(!buf) {
    buf=malloc(255*255);
    if(!buf) fatal("Allocation failed\n");
  }
  p=buf;
  for(y=0;y<s;y++) for(x=0;x<s;x++) {
    if(m&1) x=s-x-1;
    if(m&2) y=s-y-1;
    *p++=d[m&4?x*s+y:y*s+x];
    if(m&1) x=s-x-1;
    if(m&2) y=s-y-1;
  }
  memcpy(d,buf,s*s);
}

static void load_picture_lump(const unsigned char*data,int len,Picture**pict) {
  Uint8 buf[32];
  FILE*fp;
  int i,j,n;
  if(!len) return;
  fp=fmemopen((unsigned char*)data,len,"r");
  if(!fp) fatal("Failed to open in-memory stream of size %d\n",len);
  *buf=fgetc(fp);
  n=*buf&15;
  fread(buf+1,1,n+(n>>1),fp);
  for(i=0;i<n;i++) {
    j=buf[i+1];
    pict[i]=malloc(sizeof(Picture)+(j+1)*j);
    if(!pict[i]) fatal("Allocation failed\n");
    memset(pict[i]->data,0,pict[i]->size=j);
    j=(i?buf[n+1+((i-1)>>1)]>>(i&1?0:4):*buf>>4)&15;
    pict[i]->meth=j^((j==5 || j==6)?3:0);
  }
  for(i=0;i<n;i++) {
    j=pict[i]->size;
    if(pict[i]->meth==15) fread(pict[i]->data+j,j,j,fp),pict[i]->meth=0; else uncompress_picture(fp,pict[i]);
    if(pict[i]->meth) load_rotate(pict[i]);
  }
  fclose(fp);
}

static inline void show_cursor_xy(int x,int y) {
  char buf[64];
  if(x>=0) snprintf(buf,64,"(%d,%d)%63s",x,y,"");
  else snprintf(buf,64,"%63s","");
  SDL_LockSurface(screen);
  draw_text(0,32,buf,0xF0,0xF9);
  SDL_UnlockSurface(screen);
  SDL_Flip(screen);
}

static inline void edit_picture_1(Picture**pict,const char*name) {
  static const Uint8 shade[64]=
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
    "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
  ;
  static const Uint8 gridlines[32]=
    "\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF"
    "\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF\xF8\xFF"
  ;
  static Picture*pclip=0;
  Uint8*p;
  Uint8*q;
  Uint8 sel=0;
  Uint8 cc=0;
  SDL_Rect r;
  SDL_Event ev;
  int i,j,x,y,z;
  unsigned char buf[256];
  set_cursor(XC_arrow);
  redraw:
  if((sel&~15) || !pict[sel]) sel=0;
  z=screen->w-pict[sel]->size-169;
  if(z>screen->h-49) z=screen->h-49;
  z/=pict[sel]->size;
  if(z<2) return;
  if(z>32) z=32;
  SDL_LockSurface(screen);
  p=screen->pixels;
  r.x=r.y=0; r.w=screen->w; r.h=screen->h;
  SDL_FillRect(screen,&r,0xF0);
  draw_text(0,0,name,0xF0,0xF5);
  x=strlen(name)+1;
  for(i=0;i<16;i++) if(pict[i]) {
    j=snprintf(buf,255,"%c%d%c",i==sel?'<':' ',pict[i]->size,i==sel?'>':' ');
    draw_text(x<<3,0,buf,0xF0,i==sel?0xFF:0xF8);
    x+=j;
  }
  p=screen->pixels+40*screen->pitch;
  q=pict[sel]->data+pict[sel]->size;
  for(y=0;y<pict[sel]->size;y++) {
    memcpy(p,q,pict[sel]->size);
    p+=screen->pitch;
    q+=pict[sel]->size;
  }
  p=screen->pixels+40*screen->pitch-screen->w-161;
  for(x=0;x<256;x++) {
    for(y=0;y<10;y++) memset(p+y*screen->pitch,x,10);
    if(x==cc) {
      memset(p+1,0xF0,8);
      for(y=1;y<9;y++) {
        p[y*screen->pitch]=0xF0;
        p[y*screen->pitch+(y&1)+4]=(y&1)+0xF7;
        p[y*screen->pitch+9]=0xFF;
      }
      memset(p+9*screen->pitch+1,0xFF,8);
    }
    if(15&~x) p+=10; else p+=10*screen->pitch-150;
  }
  p=screen->pixels+41*screen->pitch+pict[sel]->size+2;
  q=pict[sel]->data+pict[sel]->size;
  for(y=0;y<pict[sel]->size;y++) {
    for(x=0;x<pict[sel]->size;x++) {
      memcpy(p,gridlines,z);
      for(i=1;i<z;i++) {
        p[i*screen->pitch]=i&1?0xF8:0xFF;
        if(*q) memset(p+i*screen->pitch+1,*q,z-1);
        else memcpy(p+i*screen->pitch+1,shade+(i&15),z-1);
      }
      p+=z;
      q++;
    }
    for(i=1;i<z;i++) p[i*screen->pitch]=i&1?0xF8:0xFF;
    p+=z*(screen->pitch-pict[sel]->size);
  }
  for(x=0;x<pict[sel]->size;x++) {
    memcpy(p,gridlines,z);
    p+=z;
  }
  SDL_UnlockSurface(screen);
  SDL_Flip(screen);
  while(SDL_WaitEvent(&ev)) {
    switch(ev.type) {
      case SDL_QUIT:
        exit(0);
        return;
      case SDL_MOUSEMOTION:
        if(ev.motion.x<pict[sel]->size+2 || ev.motion.x>=pict[sel]->size*(z+1)+2
         || ev.motion.y<41 || ev.motion.y>=z*pict[sel]->size+41) {
          x=-1;
          set_cursor(XC_arrow);
        } else {
          x=(ev.motion.x-pict[sel]->size-2)/z;
          y=(ev.motion.y-41)/z;
          set_cursor(XC_tcross);
        }
        show_cursor_xy(x,y);
        break;
      case SDL_MOUSEBUTTONDOWN:
        if(ev.button.x>=screen->w-161 && ev.button.x<screen->w-1 && ev.button.y>=40 && ev.button.y<200) {
          x=(ev.button.x+161-screen->w)/10;
          y=(ev.button.y-40)/10;
          i=y*16+x;
          switch(ev.button.button) {
            case 1: cc=i; break;
          }
          goto redraw;
        }
        break;
      case SDL_KEYDOWN:
        switch(ev.key.keysym.sym) {
          case SDLK_ESCAPE: return;
          case SDLK_LEFTBRACKET: case SDLK_LEFTPAREN: --sel; goto redraw;
          case SDLK_RIGHTBRACKET: case SDLK_RIGHTPAREN: ++sel; goto redraw;
          case SDLK_1 ... SDLK_9: sel=ev.key.keysym.sym-SDLK_1; goto redraw;
        }
        break;
      case SDL_VIDEOEXPOSE:
        goto redraw;
    }
  }
}

static void edit_picture(sqlite3_int64 id) {
  sqlite3_stmt*st;
  Picture*pict[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  char*name;
  const unsigned char*data;
  int i;
  if(i=sqlite3_prepare_v2(userdb,"SELECT SUBSTR(`NAME`,1,LENGTH(`NAME`)-4),`DATA` FROM `PICEDIT` WHERE `ID`=?1;",-1,&st,0)) {
    screen_message(sqlite3_errmsg(userdb));
    return;
  }
  sqlite3_bind_int64(st,1,id);
  i=sqlite3_step(st);
  if(i!=SQLITE_ROW) {
    screen_message(i==SQLITE_DONE?"No such ID":sqlite3_errmsg(userdb));
    sqlite3_finalize(st);
    return;
  }
  data=sqlite3_column_blob(st,1);
  i=sqlite3_column_bytes(st,1);
  load_picture_lump(data,i,pict);
  name=strdup(sqlite3_column_text(st,0)?:(const unsigned char*)"???");
  if(!name) fatal("Allocation failed\n");
  sqlite3_finalize(st);
  if(!*pict) {
    i=picture_size;
    *pict=malloc(sizeof(Picture)+(i+1)*i);
    if(!*pict) fatal("Allocation failed");
    pict[0]->size=i;
    memset(pict[0]->data,0,(i+1)*i);
  }
  edit_picture_1(pict,name);
  free(name);
  for(i=0;i<16;i++) {
    
    free(pict[i]);
  }
  
}

static void set_caption(void) {
  char buf[256];
  snprintf(buf,255,"Free Hero Mesh - %s - Picture",basefilename);
  SDL_WM_SetCaption(buf,buf);
}

void run_picture_editor(void) {
  sqlite3_int64*ids;
  SDL_Event ev;
  SDL_Rect r;
  sqlite3_stmt*st;
  int sc=0;
  int max=load_picture_file();
  int i,n;
  init_palette();
  optionquery[1]=Q_imageSize;
  picture_size=strtol(xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"16",0,10);
  set_cursor(XC_arrow);
  set_caption();
  i=sqlite3_prepare_v3(userdb,"SELECT `ID`,SUBSTR(`NAME`,1,LENGTH(`NAME`)-4),`TYPE` FROM `PICEDIT` WHERE `TYPE` ORDER BY `NAME` LIMIT ?1 OFFSET ?2;",-1,SQLITE_PREPARE_PERSISTENT,&st,0);
  if(i) fatal("SQL error (%d): %s\n",i,sqlite3_errmsg(userdb));
  ids=calloc(screen->h/8,sizeof(sqlite3_int64));
  if(!ids) fatal("Allocation failed\n");
  redraw:
  sqlite3_reset(st);
  if(sc>max-screen->h/8+1) sc=max-screen->h/8+1;
  if(sc<0) sc=0;
  sqlite3_bind_int(st,1,screen->h/8-1);
  sqlite3_bind_int(st,2,sc);
  SDL_LockSurface(screen);
  r.x=r.y=0; r.w=screen->w; r.h=screen->h;
  SDL_FillRect(screen,&r,0xF0);
  draw_text(0,0,"<ESC> Save/Quit  <F1> Add  <F2> Delete  <F3> Edit",0xF0,0xFB);
  n=0;
  while((i=sqlite3_step(st))==SQLITE_ROW) {
    ids[n++]=sqlite3_column_int64(st,0);
    draw_text(16,8*n,sqlite3_column_text(st,1),0xF0,0xF7);
    if(8*n+8>screen->h-8) break;
  }
  SDL_UnlockSurface(screen);
  sqlite3_reset(st);
  r.y=8; r.h-=8;
  scrollbar(&sc,r.h/8,max,0,&r);
  SDL_Flip(screen);
  while(SDL_WaitEvent(&ev)) {
    if(ev.type!=SDL_VIDEOEXPOSE && scrollbar(&sc,screen->h/8,max,&ev,&r)) goto redraw;
    switch(ev.type) {
      case SDL_QUIT:
        return;
      case SDL_KEYDOWN:
        switch(ev.key.keysym.sym) {
          case SDLK_ESCAPE:
            if(!(ev.key.keysym.mod&(KMOD_SHIFT|KMOD_CTRL))) save_picture_file();
            return;
          case SDLK_q:
            if(!(ev.key.keysym.mod&KMOD_CTRL)) break;
            if(!(ev.key.keysym.mod&KMOD_SHIFT)) save_picture_file();
            return;
          case SDLK_HOME: case SDLK_KP7: sc=0; goto redraw;
          case SDLK_UP: case SDLK_KP8: if(sc) --sc; goto redraw;
          case SDLK_DOWN: case SDLK_KP2: ++sc; goto redraw;
          case SDLK_END: case SDLK_KP1: sc=max-screen->h/8+1; goto redraw;
          case SDLK_PAGEUP: case SDLK_KP9: sc-=screen->h/8-1; goto redraw;
          case SDLK_PAGEDOWN: case SDLK_KP3: sc+=screen->h/8-1; goto redraw;
          case SDLK_F3:
            *ids=ask_picture_id("Edit:");
            if(*ids) edit_picture(*ids);
            goto redraw;
        }
        break;
      case SDL_MOUSEMOTION:
        set_cursor(XC_arrow);
        break;
      case SDL_VIDEOEXPOSE:
        goto redraw;
    }
  }
}