#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 #include #include #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>1),fp); for(i=0;idata,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;isize; 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;ysize;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;ysize;y++) { for(x=0;xsize;x++) { memcpy(p,gridlines,z); for(i=1;ipitch]=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;ipitch]=i&1?0xF8:0xFF; p+=z*(screen->pitch-pict[sel]->size); } for(x=0;xsize;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.xsize+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.xw-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," Save/Quit Add Delete 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; } } }