/* * cg.c load and display cg * * Copyright (C) 1997-1998 Masaki Chikama (Wren) * 1998- * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* $Id: cg.c,v 1.12 2001/09/16 15:59:11 chikama Exp $ */ #include #include #include #include #include #ifdef __EMSCRIPTEN__ #include #endif #include "portab.h" #include "system.h" #include "nact.h" #include "ags.h" #include "cg.h" #include "vsp.h" #include "pms.h" #include "bmp.h" #include "qnt.h" #include "jpeg.h" #ifdef HAVE_WEBP #include "webp.h" #endif #include "ald_manager.h" #include "filecheck.h" #include "cache.h" static int *censor_list; // sorted list of cg numbers static int censor_list_size; /* VSPのパレット展開バンク */ int cg_vspPB; /* cg,palette 展開フラグ (funciotn flag) */ int cg_fflg; /* CGをロードした回数を書き込む変数 */ vmvar_t *cg_loadCountVar; /* Brightness (PD command) */ int cg_brightness; #define MOSAIC_SIZE 16 #define GCMD_EXTRACTCG(c) ((c) & 0x01) #define GCMD_SET_PALETTE(c) ((c) & 0x02) #define GCMD_LOAD_PALETTE(c) ((c) & 0x04) /* CG表示位置に関する情報 */ static CG_WHERETODISP loc_policy, loc_policy0; static SDL_Point loc_where, loc_where0; static Cache *cg_cache; #ifndef CG_CACHE_SIZE #define CG_CACHE_SIZE (10 << 20) #endif /* static methods */ static CG_TYPE check_cgformat(uint8_t *data); static void set_vspbank(uint8_t *pic, int bank, int width, int height); static SDL_Point set_display_loc(cgdata *cg); static void clear_display_loc(); static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend); static cgdata *loader(int no); static void release_cg(cgdata *cg); static uint32_t int_hash(const void *key) { return (uint32_t)*(const int *)key; } static bool int_equal(const void *a, const void *b) { return *(const int *)a == *(const int *)b; } static void destroy_cg(void *data) { cgdata_free(data); } static bool cg_is_pinned(const void *data) { return ((const cgdata *)data)->refcnt != 0; } static int compare_integers(const void *a, const void *b) { int int_a = *(const int *)a; int int_b = *(const int *)b; return (int_a > int_b) - (int_a < int_b); } static bool is_censored_cg(int no) { no += 1; // CG numbers are 1-based. if (!censor_list) return false; return bsearch(&no, censor_list, censor_list_size, sizeof(int), compare_integers) != NULL; } static void cg_mosaic(cgdata *cg) { Uint32 format = 0; switch (cg->depth) { case 8: format = SDL_PIXELFORMAT_INDEX8; break; case 16: format = SDL_PIXELFORMAT_RGB565; break; case 24: format = SDL_PIXELFORMAT_RGB24; break; default: SYSERROR("Unsupported cg depth %d", cg->depth); } SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormatFrom( cg->pic, cg->width, cg->height, cg->depth, cg->width * (cg->depth / 8), format); SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat( 0, (cg->width + MOSAIC_SIZE - 1) / MOSAIC_SIZE, (cg->height + MOSAIC_SIZE - 1) / MOSAIC_SIZE, cg->depth, format); if (sf->format->palette) SDL_SetSurfacePalette(tmp, sf->format->palette); // NOTE: SDL_BlitScaled() does not support 8-bit surfaces. SDL_SoftStretch(sf, NULL, tmp, NULL); SDL_SoftStretch(tmp, NULL, sf, NULL); SDL_FreeSurface(tmp); SDL_FreeSurface(sf); } /* * Identify cg format * data: pointer to compressed data * return: cg type */ static CG_TYPE check_cgformat(uint8_t *data) { if (qnt_checkfmt(data)) { return ALCG_QNT; } else if (pms256_checkfmt(data)) { return ALCG_PMS8; } else if (pms64k_checkfmt(data) && nact->ags.world_depth >= 15) { return ALCG_PMS16; } else if (bmp16m_checkfmt(data) && nact->ags.world_depth >= 15) { return ALCG_BMP24; } else if (bmp256_checkfmt(data)) { return ALCG_BMP8; } else if (vsp_checkfmt(data)) { return ALCG_VSP; } else if (jpeg_checkfmt(data) && nact->ags.world_depth >= 15) { return ALCG_JPEG; #ifdef HAVE_WEBP } else if (webp_checkfmt(data)) { return ALCG_WEBP; #endif } WARNING("Unknown Cg Type"); return ALCG_UNKNOWN; } /* * Modify pixel accoding to palette bank (vsp only) * pic : pixel to be modifyied. * bank : palette bank (use only MSB 4bit) * width : image width * height: image height */ static void set_vspbank(uint8_t *pic, int bank, int width, int height) { int pixels = width * height; while (pixels--) { *pic = (*pic & 0x0f) | (uint8_t)bank; pic++; } } /* * Free data * cg: freeing data object */ void cgdata_free(cgdata *cg) { if (cg->pic) free(cg->pic); if (cg->pal) free(cg->pal); if (cg->alpha) free(cg->alpha); free(cg); } /* * Determine the location of display image * cg: cg information * return: x and y for display location */ static SDL_Point set_display_loc(cgdata *cg) { SDL_Point p; switch(loc_policy) { case OFFSET_ABSOLUTE_GC: case OFFSET_ABSOLUTE_JC: p.x = loc_where.x; p.y = loc_where.y; break; case OFFSET_RELATIVE_GC: case OFFSET_RELATIVE_JC: p.x = cg->x + loc_where.x; p.y = cg->y + loc_where.y; break; case OFFSET_NOMOVE: p.x = cg->x; p.y = cg->y; break; } return p; } /* * Reset location movement according to loc_policy */ static void clear_display_loc() { switch(loc_policy) { case OFFSET_ABSOLUTE_GC: case OFFSET_RELATIVE_GC: if (loc_policy0 == OFFSET_ABSOLUTE_JC || loc_policy0 == OFFSET_RELATIVE_JC ) { loc_policy = loc_policy0; loc_where = loc_where0; loc_policy0 = OFFSET_NOMOVE; } else { loc_policy = OFFSET_NOMOVE; } break; default: break; } } /* * Call ags to display cg * cg: cg to be drawn * x : display location x * y : display location y */ static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend) { ags_drawCg(cg, x, y, cg_brightness, sprite_color, alpha_blend); ags_updateArea(x, y, cg->width, cg->height); } /* * Load cg data from file or cache * no: file no ( >= 0) * return: cg object(extracted) */ static cgdata *loader(int no) { dridata *dfile; cgdata *cg = NULL; /* search in cache */ if (NULL != (cg = cache_get(cg_cache, &no))) { cg->refcnt++; return cg; } /* read from file */ if (NULL == (dfile = ald_getdata(DRIFILE_CG, no))) return NULL; /* update load cg counter */ if (cg_loadCountVar != NULL) { (*(cg_loadCountVar + no + 1))++; } /* extract cg */ switch (check_cgformat(dfile->data)) { case ALCG_VSP: cg = vsp_extract(dfile->data); break; case ALCG_PMS8: cg = pms256_extract(dfile->data); break; case ALCG_PMS16: cg = pms64k_extract(dfile->data); break; case ALCG_BMP8: cg = bmp256_extract(dfile->data); break; case ALCG_BMP24: cg = bmp16m_extract(dfile->data); break; case ALCG_QNT: cg = qnt_extract(dfile->data); break; case ALCG_JPEG: cg = jpeg_extract(dfile->data, dfile->size); break; default: break; } if (cg) { if (is_censored_cg(no)) { cg_mosaic(cg); } /* insert to cache */ size_t size = (size_t)cg->width * cg->height * (cg->depth / 8); cg->refcnt = 1; cg->cached = cache_insert(cg_cache, &no, cg, size) == CACHE_INSERT_OK; } /* ok to free */ ald_freedata(dfile); return cg; } static void release_cg(cgdata *cg) { if (!cg) return; cg->refcnt--; if (!cg->cached && cg->refcnt == 0) cgdata_free(cg); } /* * Initilize cache */ void cg_init(void) { CacheOps ops = { .key_size = sizeof(int), .hash = int_hash, .equal = int_equal, .destroy = destroy_cg, .is_pinned = cg_is_pinned, }; cache_destroy(cg_cache); cg_cache = cache_new((size_t)CG_CACHE_SIZE, &ops); cg_reset(); } void cg_reset(void) { cg_vspPB = -1; cg_fflg = 7; cg_loadCountVar = NULL; cg_brightness = 255; loc_policy = loc_policy0 = OFFSET_NOMOVE; memset(&loc_where, 0, sizeof(loc_where)); memset(&loc_where0, 0, sizeof(loc_where0)); } CacheStats cg_get_cache_stats(void) { return cache_get_stats(cg_cache); } /* * Set cg display location * x : display location x * y : display location y * policy: location offset policy */ void cg_set_display_location(int x, int y, CG_WHERETODISP policy) { if (policy == OFFSET_ABSOLUTE_GC || policy == OFFSET_RELATIVE_GC) { if (loc_policy == OFFSET_ABSOLUTE_JC || loc_policy == OFFSET_RELATIVE_JC) { loc_policy0 = loc_policy; loc_where0 = loc_where; } } loc_policy = policy; loc_where.x = x; loc_where.y = y; } /* * Load and display cg * no : file no ( >= 0) * flg: sprite color(!-1) */ void cg_load(int no, int flg) { cgdata *cg; SDL_Point p; int i, bank = cg_vspPB; /* load and extract cg */ if (NULL == (cg = loader(no))) { return; } /* need to set vsp bank or not */ if (GCMD_EXTRACTCG(cg_fflg) && cg->type == ALCG_VSP) { bank = cg_vspPB == -1 ? cg->vsp_bank : cg_vspPB; set_vspbank(cg->pic, bank << 4, cg->width, cg->height); /* copy palettes 0 -> bank */ if (bank != 0) { memcpy(cg->pal + bank * 16, cg->pal, 16 * sizeof(cg->pal[0])); } if (flg != -1) { flg |= (bank << 4); } } /* copy palette to system */ if (GCMD_LOAD_PALETTE(cg_fflg)) { switch(cg->type) { case ALCG_VSP: ags_setPalettes(cg->pal, bank << 4, 16); break; case ALCG_PMS8: // Avoid changing the Windows' reserved palette area (0-9, 246-255) if (cg->pms_bank & 1) ags_setPalettes(cg->pal + 10, 10, 6); if (cg->pms_bank & (1 << 15)) ags_setPalettes(cg->pal + 240, 240, 6); for (i = 1; i < 15; i++) { if (cg->pms_bank & (1 << i)) { ags_setPalettes(cg->pal + i * 16, i * 16, 16); } } break; case ALCG_BMP8: ags_setPalettes(cg->pal + 10, 10, 236); break; default: break; } } /* refrect palette change */ if (GCMD_SET_PALETTE(cg_fflg) && cg->depth == 8) ags_setPaletteToSystem(0, 256); /* draw cg */ if (GCMD_EXTRACTCG(cg_fflg)) { /* set display offset */ p = set_display_loc(cg); /* draw alpha pixel */ if (cg->alpha) { ags_alpha_setPixel(p.x, p.y, cg->width, cg->height, cg->alpha); } /* draw cg pixel */ display_cg(cg, p.x, p.y, flg, flg != -1); } /* clear display offset */ clear_display_loc(); release_cg(cg); } /* * Load and display cg with alpha * cgno : file no for cg ( >= 0 or -1(alpha only)) * shadowno: file no for alpha ( >= 0 ) */ void cg_load_with_alpha(int cgno, int shadowno) { cgdata *cg = NULL, *scg = NULL; SDL_Point p; /* load pixel */ if (cgno >= 0) { if (NULL == (cg = loader(cgno))) return; if (cg->type != ALCG_PMS16) { WARNING("commandGX cg_no != 16bitPMS"); goto cleanup; } } /* load alpha pixel */ if (NULL == (scg = loader(shadowno))) goto cleanup; if (scg->type != ALCG_PMS8) { WARNING("commandGX shadow_no != 8bitPMS"); goto cleanup; } /* set alpha pixel offset */ p = set_display_loc(scg); /* draw alpha pixel */ ags_alpha_setPixel(p.x, p.y, scg->width, scg->height, scg->pic); /* draw pixel */ if (cg) { p = set_display_loc(cg); display_cg(cg, p.x, p.y, -1, true); } /* clear display offset */ clear_display_loc(); cleanup: release_cg(cg); release_cg(scg); } static uint8_t* load_cg_from_file(char *fname_utf8, int *status, long *filesize) { int size; FILE *fp; static uint8_t *tmp; *status = 0; if (NULL == (fp = fc_open(fname_utf8, 'r'))) { *status = SAVE_LOADERR; return NULL; } fseek(fp, 0L, SEEK_END); *filesize = ftell(fp); if (*filesize == 0) { *status = SAVE_LOADERR; return NULL; } tmp = (char *)malloc(*filesize); if (tmp == NULL) { WARNING("Out of memory"); *status = SAVE_LOADERR; return NULL; } fseek(fp, 0L, SEEK_SET); size = fread(tmp, 1, *filesize,fp); if (size != *filesize) { *status = SAVE_LOADSHORTAGE; } else { *status = SAVE_LOADOK; } fclose(fp); return tmp; } /* * Load and display cg from file 'name' (not cached right now) * name: file name to be read * x : display location x * y : display location y * return: file read status */ int cg_load_with_filename(char *fname_utf8, int x, int y) { int status, type; long filesize; uint8_t *data; cgdata *cg = NULL; SDL_Point p; data = load_cg_from_file(fname_utf8, &status, &filesize); if (data == NULL) return status; cg_set_display_location(x, y, OFFSET_ABSOLUTE_GC); type = check_cgformat(data); switch(type) { case ALCG_BMP8: cg = bmp256_extract(data); break; case ALCG_BMP24: cg = bmp16m_extract(data); break; case ALCG_JPEG: cg = jpeg_extract(data, filesize); break; default: return status; } /* load palette if not extracted */ if (cg->depth == 8) { if (GCMD_LOAD_PALETTE(cg_fflg)) ags_setPalettes(cg->pal + 10, 10, 236); if (GCMD_SET_PALETTE(cg_fflg)) ags_setPaletteToSystem(0, 256); } /* draw cg */ if (GCMD_EXTRACTCG(cg_fflg)) { /* set display offset */ p = set_display_loc(cg); /* draw cg pixel */ display_cg(cg, p.x, p.y, -1, false); /* clear display offset */ clear_display_loc(); } cgdata_free(cg); return status; } /* * Get cg information * no : file no for cg ( >= 0 ) * info: information to be retored */ void cg_get_info(int no, SDL_Rect *info) { cgdata *cg = loader(no); SDL_Point p; if (cg == NULL) { info->x = info->y = info->w = info->h = 0; } else { p = set_display_loc(cg); info->x = p.x; info->y = p.y; info->w = cg->width; info->h = cg->height; release_cg(cg); } } void cg_clear_display_loc() { clear_display_loc(); } SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mosaic, bool as_alpha) { CG_TYPE type = check_cgformat(data); #ifdef HAVE_WEBP if (type == ALCG_WEBP) return webp_extract(data, size); #endif cgdata *cg = NULL; switch (type) { case ALCG_PMS8: cg = pms256_extract(data); break; case ALCG_PMS16: cg = pms64k_extract(data); break; case ALCG_QNT: cg = qnt_extract(data); break; case ALCG_BMP24: cg = bmp16m_extract(data); break; default: WARNING("Invalid CG type"); break; } if (!cg) return NULL; if (mosaic) { cg_mosaic(cg); } SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, cg->width, cg->height, 32, (cg->alpha || (type == ALCG_PMS8 && as_alpha)) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_XRGB8888); if (type == ALCG_PMS8) { if (as_alpha) { // Treat PMS8 as an alpha map. Palette is ignored. SDL_FillRect(sf, NULL, SDL_MapRGBA(sf->format, 255, 255, 255, 0)); cg->alpha = cg->pic; cg->pic = NULL; } else { // Treat PMS8 as a paletted image. SDL_Surface *pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 8, cg->width, SDL_PIXELFORMAT_INDEX8); SDL_Palette *palette = SDL_AllocPalette(256); SDL_SetPaletteColors(palette, cg->pal, 0, 256); SDL_SetSurfacePalette(pic, palette); SDL_FreePalette(palette); SDL_BlitSurface(pic, NULL, sf, NULL); SDL_FreeSurface(pic); } } else { SDL_Surface *pic; if (type == ALCG_PMS16 || type == ALCG_BMP24) { pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 16, cg->width * 2, SDL_PIXELFORMAT_RGB565); } else { pic = SDL_CreateRGBSurfaceWithFormatFrom(cg->pic, cg->width, cg->height, 24, cg->width * 3, SDL_PIXELFORMAT_RGB24); } SDL_BlitSurface(pic, NULL, sf, NULL); SDL_FreeSurface(pic); } if (cg->alpha) { // Copy alpha values from cg->alpha to sf->pixels. uint8_t *p_ds = ALPHA_AT(sf, 0, 0); uint8_t *adata = cg->alpha; for (int y = 0; y < cg->height; y++) { uint8_t *p_dst = p_ds; for (int x = 0; x < cg->width; x++) { *p_dst = *adata++; p_dst += 4; } p_ds += sf->pitch; } } cgdata_free(cg); return sf; } SDL_Surface *cg_load_as_sdlsurface(int no) { dridata *dfile = ald_getdata(DRIFILE_CG, no); if (!dfile) return NULL; SDL_Surface *sf = cg_load_as_sdlsurface_from_data(dfile->data, dfile->size, is_censored_cg(no), false); ald_freedata(dfile); return sf; } EMSCRIPTEN_KEEPALIVE void load_censor_list(const char *path) { if (cg_cache) cache_clear(cg_cache); if (censor_list) { free(censor_list); censor_list = NULL; censor_list_size = 0; } if (!path || !*path) return; FILE *fp = fopen(path, "r"); if (!fp) { SYSERROR("failed to open censor list file %s", path); return; } char line[256]; int count = 0, cap = 256; censor_list = malloc(cap * sizeof(int)); int line_no = 0; while (fgets(line, sizeof(line), fp)) { line_no++; char *p = line; char *comment = strchr(p, '#'); if (comment) *comment = '\0'; while (*p && isspace((unsigned char)*p)) p++; if (*p == '\0') continue; if (count >= cap) { cap *= 2; censor_list = realloc(censor_list, cap * sizeof(int)); } char *endptr; censor_list[count++] = (int)strtol(p, &endptr, 10); while (*endptr && isspace((unsigned char)*endptr)) endptr++; if (*endptr != '\0') WARNING("invalid line %d in %s", line_no, path); } censor_list_size = count; fclose(fp); if (censor_list_size > 0) { qsort(censor_list, censor_list_size, sizeof(int), compare_integers); } else { free(censor_list); censor_list = NULL; } }