From: divverent Date: Fri, 11 Jul 2008 10:14:59 +0000 (+0000) Subject: adding two new extensions: DP_QC_WHICHPACK (identify a pk3 containing a file), and... X-Git-Tag: xonotic-v0.1.0preview~2183 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=commitdiff_plain;h=542f6670e0f2250b940e987348b8c454c59495a2 adding two new extensions: DP_QC_WHICHPACK (identify a pk3 containing a file), and DP_QC_WRITEIMAGE (writes a small low quality JPEG image from svqc to csqc) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8396 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/clvm_cmds.c b/clvm_cmds.c index 2a80aa54..cf2ddba2 100644 --- a/clvm_cmds.c +++ b/clvm_cmds.c @@ -4,6 +4,8 @@ #include "csprogs.h" #include "cl_collision.h" #include "r_shadow.h" +#include "jpeg.h" +#include "image.h" //============================================================================ // Client @@ -1308,6 +1310,54 @@ static void VM_CL_ReadFloat (void) PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadFloat(); } +//#501 string() readpicture (DP_CSQC_READWRITEPICTURE) +static void VM_CL_ReadPicture (void) +{ + const char *name; + unsigned char *data; + unsigned char *buf; + int size; + int i; + cachepic_t *pic; + + VM_SAFEPARMCOUNT(0, VM_CL_ReadPicture); + + name = MSG_ReadString(); + size = MSG_ReadShort(); + + // check if a texture of that name exists + // if yes, it is used and the data is discarded + // if not, the (low quality) data is used to build a new texture, whose name will get returned + + pic = Draw_CachePic_Flags (name, CACHEPICFLAG_NOTPERSISTENT); + + if(size) + { + if(pic->tex == r_texture_notexture) + pic->tex = NULL; // don't overwrite the notexture by Draw_NewPic + if(pic->tex) + { + // texture found and loaded + // skip over the jpeg as we don't need it + for(i = 0; i < size; ++i) + MSG_ReadByte(); + } + else + { + // texture not found + // use the attached jpeg as texture + buf = Mem_Alloc(tempmempool, size); + MSG_ReadBytes(size, buf); + data = JPEG_LoadImage_BGRA(buf, size); + Mem_Free(buf); + Draw_NewPic(name, image_width, image_height, false, data); + Mem_Free(data); + } + } + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(name); +} + ////////////////////////////////////////////////////////// static void VM_CL_makestatic (void) @@ -3422,9 +3472,9 @@ VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (D VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) -NULL, // #501 +VM_CL_ReadPicture, // #501 string() ReadPicture = #501; NULL, // #502 -NULL, // #503 +VM_whichpack, // #503 string(string) whichpack = #503; NULL, // #504 NULL, // #505 NULL, // #506 diff --git a/image.c b/image.c index 2a224515..6bca15aa 100644 --- a/image.c +++ b/image.c @@ -26,14 +26,14 @@ void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int if (inputflipdiagonal) { for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) - for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numinputcomponents) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; } else { for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) - for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numinputcomponents) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; } @@ -44,14 +44,14 @@ void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int if (inputflipdiagonal) { for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) - for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numinputcomponents) + for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = in[outputinputcomponentindices[c]]; } else { for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) - for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numinputcomponents) + for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = in[outputinputcomponentindices[c]]; } diff --git a/jpeg.c b/jpeg.c index 28904b11..529a1f88 100644 --- a/jpeg.c +++ b/jpeg.c @@ -439,6 +439,7 @@ typedef struct qfile_t* outfile; unsigned char* buffer; + size_t bufsize; // used if outfile is NULL } my_destination_mgr; typedef my_destination_mgr* my_dest_ptr; @@ -688,7 +689,7 @@ static void JPEG_TermDestination (j_compress_ptr cinfo) error_in_jpeg = true; } -static void JPEG_MemDest (j_compress_ptr cinfo, qfile_t* outfile) +static void JPEG_FileDest (j_compress_ptr cinfo, qfile_t* outfile) { my_dest_ptr dest; @@ -703,6 +704,42 @@ static void JPEG_MemDest (j_compress_ptr cinfo, qfile_t* outfile) dest->outfile = outfile; } +static void JPEG_Mem_InitDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; +} + +static jboolean JPEG_Mem_EmptyOutputBuffer (j_compress_ptr cinfo) +{ + error_in_jpeg = true; + return false; +} + +static void JPEG_Mem_TermDestination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + dest->bufsize = dest->pub.next_output_byte - dest->buffer; +} +static void JPEG_MemDest (j_compress_ptr cinfo, void* buf, size_t bufsize) +{ + my_dest_ptr dest; + + // First time for this JPEG object? + if (cinfo->dest == NULL) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); + + dest = (my_dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_Mem_InitDestination; + dest->pub.empty_output_buffer = JPEG_Mem_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_Mem_TermDestination; + dest->outfile = NULL; + + dest->buffer = buf; + dest->bufsize = bufsize; +} + /* ==================== @@ -736,7 +773,7 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, error_in_jpeg = false; qjpeg_create_compress (&cinfo); - JPEG_MemDest (&cinfo, file); + JPEG_FileDest (&cinfo, file); // Set the parameters for compression cinfo.image_width = width; @@ -775,3 +812,218 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, FS_Close (file); return true; } + +static size_t JPEG_try_SaveImage_to_Buffer (struct jpeg_compress_struct *cinfo, char *jpegbuf, size_t jpegsize, int quality, int width, int height, unsigned char *data) +{ + unsigned char *scanline; + unsigned int linesize; + int offset; + + error_in_jpeg = false; + + JPEG_MemDest (cinfo, jpegbuf, jpegsize); + + // Set the parameters for compression + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->in_color_space = JCS_RGB; + cinfo->input_components = 3; + qjpeg_set_defaults (cinfo); + qjpeg_set_quality (cinfo, quality, FALSE); + + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + cinfo->optimize_coding = 1; + + qjpeg_start_compress (cinfo, true); + + // Compress each scanline + linesize = width * 3; + offset = linesize * (cinfo->image_height - 1); + while (cinfo->next_scanline < cinfo->image_height) + { + scanline = &data[offset - cinfo->next_scanline * linesize]; + + qjpeg_write_scanlines (cinfo, &scanline, 1); + if (error_in_jpeg) + break; + } + + qjpeg_finish_compress (cinfo); + + return error_in_jpeg ? 0 : ((my_dest_ptr) cinfo->dest)->bufsize; +} + +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + int quality; + int quality_guess; + size_t result; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; + + qjpeg_create_compress (&cinfo); + +#if 0 + // used to get the formula below + { + char buf[1048576]; + unsigned char *img; + int i; + + img = Mem_Alloc(tempmempool, width * height * 3); + for(i = 0; i < width * height * 3; ++i) + img[i] = rand() & 0xFF; + + for(i = 0; i <= 100; ++i) + { + Con_Printf("! %d %d %d %d\n", width, height, i, (int) JPEG_try_SaveImage_to_Buffer(&cinfo, buf, sizeof(buf), i, width, height, img)); + } + + Mem_Free(img); + } +#endif + + //quality_guess = (100 * jpegsize - 41000) / (width*height) + 2; // fits random data + quality_guess = (256 * jpegsize - 81920) / (width*height) - 8; // fits Nexuiz's map pictures + + quality_guess = bound(0, quality_guess, 90); + quality = quality_guess + 10; // assume it can do 10 failed attempts + + while(!(result = JPEG_try_SaveImage_to_Buffer(&cinfo, jpegbuf, jpegsize, quality, width, height, data))) + { + --quality; + if(quality < 0) + { + Con_Printf("couldn't write image at all, probably too big\n"); + return 0; + } + } + Con_DPrintf("JPEG_SaveImage_to_Buffer: guessed quality/size %d/%d, actually got %d/%d\n", quality_guess, (int)jpegsize, quality, (int)result); + + return result; +} + +typedef struct CompressedImageCacheItem +{ + char imagename[MAX_QPATH]; + size_t maxsize; + void *compressed; + size_t compressed_size; + struct CompressedImageCacheItem *next; +} +CompressedImageCacheItem; +#define COMPRESSEDIMAGECACHE_SIZE 4096 +static CompressedImageCacheItem *CompressedImageCache[COMPRESSEDIMAGECACHE_SIZE]; + +static void CompressedImageCache_Add(const char *imagename, size_t maxsize, void *compressed, size_t compressed_size) +{ + const char *hashkey = va("%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i; + + if(strlen(imagename) >= MAX_QPATH) + return; // can't add this + + i = Z_Malloc(sizeof(CompressedImageCacheItem)); + strlcpy(i->imagename, imagename, sizeof(i->imagename)); + i->maxsize = maxsize; + i->compressed = compressed; + i->compressed_size = compressed_size; + i->next = CompressedImageCache[hashindex]; + CompressedImageCache[hashindex] = i; +} + +static CompressedImageCacheItem *CompressedImageCache_Find(const char *imagename, size_t maxsize) +{ + const char *hashkey = va("%s:%d", imagename, (int) maxsize); + int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; + CompressedImageCacheItem *i = CompressedImageCache[hashindex]; + + while(i) + { + if(i->maxsize == maxsize) + if(!strcmp(i->imagename, imagename)) + return i; + } + return NULL; +} + +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size) +{ + unsigned char *imagedata, *newimagedata; + int maxPixelCount; + int components[3] = {2, 1, 0}; + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + CompressedImageCacheItem *i = CompressedImageCache_Find(imagename, maxsize); + if(i) + { + *size = i->compressed_size; + *buf = i->compressed; + } + + // load the image + imagedata = loadimagepixelsbgra(imagename, true, false); + if(!imagedata) + return false; + + // find an appropriate size for somewhat okay compression + if(maxsize <= 768) + maxPixelCount = 64 * 64; + else if(maxsize <= 1024) + maxPixelCount = 128 * 128; + else if(maxsize <= 4096) + maxPixelCount = 256 * 256; + else + maxPixelCount = 512 * 512; + + while(image_width * image_height > maxPixelCount) + { + int one = 1; + Image_MipReduce32(imagedata, imagedata, &image_width, &image_height, &one, image_width/2, image_height/2, 1); + } + + newimagedata = Mem_Alloc(tempmempool, image_width * image_height * 3); + + // convert the image from BGRA to RGB + Image_CopyMux(newimagedata, imagedata, image_width, image_height, false, false, false, 3, 4, components); + Mem_Free(imagedata); + + // try to compress it to JPEG + *buf = Z_Malloc(maxsize); + *size = JPEG_SaveImage_to_Buffer(*buf, maxsize, image_width, image_height, newimagedata); + if(!*size) + { + Z_Free(*buf); + *buf = NULL; + Con_Printf("could not compress image %s to %d bytes\n", imagename, (int)maxsize); + // return false; + // also cache failures! + } + + // store it in the cache + CompressedImageCache_Add(imagename, maxsize, *buf, *size); + return (*buf != NULL); +} diff --git a/jpeg.h b/jpeg.h index ccc86b8a..f0f27c67 100644 --- a/jpeg.h +++ b/jpeg.h @@ -29,6 +29,8 @@ qboolean JPEG_OpenLibrary (void); void JPEG_CloseLibrary (void); unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize); qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data); +size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data); // returns 0 if failed, or the size actually used +qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size); #endif diff --git a/mvm_cmds.c b/mvm_cmds.c index 91bb2007..ddf72aea 100644 --- a/mvm_cmds.c +++ b/mvm_cmds.c @@ -26,6 +26,7 @@ char *vm_m_extensions = "DP_QC_TOKENIZEBYSEPARATOR " "DP_QC_UNLIMITEDTEMPSTRINGS " "DP_QC_URI_ESCAPE " +"DP_QC_WHICHPACK " "FTE_STRINGS " ; @@ -1329,7 +1330,7 @@ NULL, // #499 NULL, // #500 NULL, // #501 NULL, // #502 -NULL, // #503 +VM_whichpack, // #503 string(string) whichpack = #503; NULL, // #504 NULL, // #505 NULL, // #506 diff --git a/prvm_cmds.c b/prvm_cmds.c index 8a8bdd99..7ab9143e 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -4822,3 +4822,15 @@ nohex: PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(dest); } +// #502 string(string filename) whichpack (DP_QC_WHICHPACK) +// returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant) +void VM_whichpack (void) +{ + const char *fn, *pack; + + fn = PRVM_G_STRING(OFS_PARM0); + pack = FS_WhichPack(fn); + + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(pack ? pack : ""); +} + diff --git a/prvm_cmds.h b/prvm_cmds.h index 373cb00d..d6ab8277 100644 --- a/prvm_cmds.h +++ b/prvm_cmds.h @@ -426,5 +426,6 @@ void VM_Cmd_Reset(void); void VM_uri_escape (void); void VM_uri_unescape (void); +void VM_whichpack (void); void VM_etof (void); diff --git a/svvm_cmds.c b/svvm_cmds.c index 36ec9c83..d979e2fb 100644 --- a/svvm_cmds.c +++ b/svvm_cmds.c @@ -1,6 +1,7 @@ #include "quakedef.h" #include "prvm_cmds.h" +#include "jpeg.h" //============================================================================ // Server @@ -93,6 +94,7 @@ char *vm_sv_extensions = "DP_QC_URI_ESCAPE " "DP_QC_VECTOANGLES_WITH_ROLL " "DP_QC_VECTORVECTORS " +"DP_QC_WHICHPACK " "DP_QUAKE2_MODEL " "DP_QUAKE2_SPRITE " "DP_QUAKE3_MAP " @@ -130,6 +132,7 @@ char *vm_sv_extensions = "DP_SV_SHUTDOWN " "DP_SV_SLOWMO " "DP_SV_SPAWNFUNC_PREFIX " +"DP_SV_WRITEPICTURE " "DP_SV_WRITEUNTERMINATEDSTRING " "DP_TE_BLOOD " "DP_TE_BLOODSHOWER " @@ -1295,6 +1298,39 @@ static void VM_SV_WriteEntity (void) MSG_WriteShort (WriteDest(), PRVM_G_EDICTNUM(OFS_PARM1)); } +// writes a picture as at most size bytes of data +// message: +// IMGNAME \0 SIZE(short) IMGDATA +// if failed to read/compress: +// IMGNAME \0 \0 \0 +//#501 void(float dest, string name, float maxsize) WritePicture (DP_SV_WRITEPICTURE)) +static void VM_SV_WritePicture (void) +{ + const char *imgname; + void *buf; + size_t size; + + VM_SAFEPARMCOUNT(3, VM_SV_WritePicture); + + imgname = PRVM_G_STRING(OFS_PARM1); + size = PRVM_G_FLOAT(OFS_PARM2); + if(size > 65535) + size = 65535; + + MSG_WriteString(WriteDest(), imgname); + if(Image_Compress(imgname, size, &buf, &size)) + { + // actual picture + MSG_WriteShort(WriteDest(), size); + SZ_Write(WriteDest(), buf, size); + } + else + { + // placeholder + MSG_WriteShort(WriteDest(), 0); + } +} + ////////////////////////////////////////////////////////// static void VM_SV_makestatic (void) @@ -3362,9 +3398,9 @@ VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (D VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) -NULL, // #501 +VM_SV_WritePicture, // #501 NULL, // #502 -NULL, // #503 +VM_whichpack, // #503 string(string) whichpack = #503; NULL, // #504 NULL, // #505 NULL, // #506