X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=jpeg.c;h=8df1b484083a2c9bf0951ee91ec12492ea5aa240;hb=44bfaab727457c0b4d084da92bedea1969cfb1db;hp=28904b11a19c4a78d154d9e3599439ee0e6a8897;hpb=afb9f98d2cc60eb453314e35518bf9090c2fa71d;p=xonotic%2Fdarkplaces.git diff --git a/jpeg.c b/jpeg.c index 28904b11..8df1b484 100644 --- a/jpeg.c +++ b/jpeg.c @@ -25,8 +25,38 @@ #include "quakedef.h" #include "image.h" #include "jpeg.h" +#include "image_png.h" +cvar_t sv_writepicture_quality = {CVAR_SAVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; +cvar_t r_texture_jpeg_fastpicmip = {CVAR_SAVE, "r_texture_jpeg_fastpicmip", "1", "perform gl_picmip during decompression for JPEG files (faster)"}; +// jboolean is unsigned char instead of int on Win32 +#ifdef WIN32 +typedef unsigned char jboolean; +#else +typedef int jboolean; +#endif + +#ifdef LINK_TO_LIBJPEG +#include +#define qjpeg_create_compress jpeg_create_compress +#define qjpeg_create_decompress jpeg_create_decompress +#define qjpeg_destroy_compress jpeg_destroy_compress +#define qjpeg_destroy_decompress jpeg_destroy_decompress +#define qjpeg_finish_compress jpeg_finish_compress +#define qjpeg_finish_decompress jpeg_finish_decompress +#define qjpeg_resync_to_restart jpeg_resync_to_restart +#define qjpeg_read_header jpeg_read_header +#define qjpeg_read_scanlines jpeg_read_scanlines +#define qjpeg_set_defaults jpeg_set_defaults +#define qjpeg_set_quality jpeg_set_quality +#define qjpeg_start_compress jpeg_start_compress +#define qjpeg_start_decompress jpeg_start_decompress +#define qjpeg_std_error jpeg_std_error +#define qjpeg_write_scanlines jpeg_write_scanlines +#define qjpeg_simple_progression jpeg_simple_progression +#define jpeg_dll true +#else /* ================================================================= @@ -38,18 +68,12 @@ ================================================================= */ -// jboolean is unsigned char instead of int on Win32 -#ifdef WIN32 -typedef unsigned char jboolean; -#else -typedef int jboolean; -#endif - -#define JPEG_LIB_VERSION 62 // Version 6b - typedef void *j_common_ptr; typedef struct jpeg_compress_struct *j_compress_ptr; typedef struct jpeg_decompress_struct *j_decompress_ptr; + +#define JPEG_LIB_VERSION 62 // Version 6b + typedef enum { JCS_UNKNOWN, @@ -81,16 +105,16 @@ typedef unsigned int JDIMENSION; struct jpeg_memory_mgr { void* (*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); - void (*alloc_large) (); - void (*alloc_sarray) (); - void (*alloc_barray) (); - void (*request_virt_sarray) (); - void (*request_virt_barray) (); - void (*realize_virt_arrays) (); - void (*access_virt_sarray) (); - void (*access_virt_barray) (); - void (*free_pool) (); - void (*self_destruct) (); + void (*_reserve_space_for_alloc_large) (void *dummy, ...); + void (*_reserve_space_for_alloc_sarray) (void *dummy, ...); + void (*_reserve_space_for_alloc_barray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_request_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_realize_virt_arrays) (void *dummy, ...); + void (*_reserve_space_for_access_virt_sarray) (void *dummy, ...); + void (*_reserve_space_for_access_virt_barray) (void *dummy, ...); + void (*_reserve_space_for_free_pool) (void *dummy, ...); + void (*_reserve_space_for_self_destruct) (void *dummy, ...); long max_memory_to_use; long max_alloc_chunk; @@ -405,6 +429,7 @@ static jboolean (*qjpeg_start_compress) (j_compress_ptr cinfo, jboolean write_al static jboolean (*qjpeg_start_decompress) (j_decompress_ptr cinfo); static struct jpeg_error_mgr* (*qjpeg_std_error) (struct jpeg_error_mgr *err); static JDIMENSION (*qjpeg_write_scanlines) (j_compress_ptr cinfo, unsigned char** scanlines, JDIMENSION num_lines); +static void (*qjpeg_simple_progression) (j_compress_ptr cinfo); static dllfunction_t jpegfuncs[] = { @@ -423,14 +448,18 @@ static dllfunction_t jpegfuncs[] = {"jpeg_start_decompress", (void **) &qjpeg_start_decompress}, {"jpeg_std_error", (void **) &qjpeg_std_error}, {"jpeg_write_scanlines", (void **) &qjpeg_write_scanlines}, + {"jpeg_simple_progression", (void **) &qjpeg_simple_progression}, {NULL, NULL} }; // Handle for JPEG DLL dllhandle_t jpeg_dll = NULL; +qboolean jpeg_tried_loading = 0; +#endif static unsigned char jpeg_eoi_marker [2] = {0xFF, JPEG_EOI}; -static qboolean error_in_jpeg; +static jmp_buf error_in_jpeg; +static qboolean jpeg_toolarge; // Our own output manager for JPEG compression typedef struct @@ -439,6 +468,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; @@ -460,11 +490,12 @@ Try to load the JPEG DLL */ qboolean JPEG_OpenLibrary (void) { +#ifdef LINK_TO_LIBJPEG + return true; +#else const char* dllnames [] = { -#if defined(WIN64) - "libjpeg64.dll", -#elif defined(WIN32) +#if defined(WIN32) "libjpeg.dll", #elif defined(MACOSX) "libjpeg.62.dylib", @@ -479,8 +510,14 @@ qboolean JPEG_OpenLibrary (void) if (jpeg_dll) return true; + if (jpeg_tried_loading) // only try once + return false; + + jpeg_tried_loading = true; + // Load the DLL return Sys_LoadLibrary (dllnames, &jpeg_dll, jpegfuncs); +#endif } @@ -493,7 +530,10 @@ Unload the JPEG DLL */ void JPEG_CloseLibrary (void) { +#ifndef LINK_TO_LIBJPEG Sys_UnloadLibrary (&jpeg_dll); + jpeg_tried_loading = false; // allow retry +#endif } @@ -545,7 +585,7 @@ static void JPEG_MemSrc (j_decompress_ptr cinfo, const unsigned char *buffer, si static void JPEG_ErrorExit (j_common_ptr cinfo) { ((struct jpeg_decompress_struct*)cinfo)->err->output_message (cinfo); - error_in_jpeg = true; + longjmp(error_in_jpeg, 1); } @@ -556,27 +596,37 @@ JPEG_LoadImage Load a JPEG image into a BGRA buffer ==================== */ -unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize) +unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; - unsigned char *image_buffer, *scanline; + unsigned char *image_buffer = NULL, *scanline = NULL; unsigned int line; + int submip = 0; // No DLL = no JPEGs if (!jpeg_dll) return NULL; + if(miplevel && r_texture_jpeg_fastpicmip.integer) + submip = bound(0, *miplevel, 3); + cinfo.err = qjpeg_std_error (&jerr); qjpeg_create_decompress (&cinfo); + if(setjmp(error_in_jpeg)) + goto error_caught; + cinfo.err = qjpeg_std_error (&jerr); + cinfo.err->error_exit = JPEG_ErrorExit; JPEG_MemSrc (&cinfo, f, filesize); qjpeg_read_header (&cinfo, TRUE); + cinfo.scale_num = 1; + cinfo.scale_denom = (1 << submip); qjpeg_start_decompress (&cinfo); - image_width = cinfo.image_width; - image_height = cinfo.image_height; + image_width = cinfo.output_width; + image_height = cinfo.output_height; - if (image_width > 4096 || image_height > 4096 || image_width <= 0 || image_height <= 0) + if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) { Con_Printf("JPEG_LoadImage: invalid image size %ix%i\n", image_width, image_height); return NULL; @@ -636,12 +686,23 @@ unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize) line++; } - Mem_Free (scanline); + Mem_Free (scanline); scanline = NULL; qjpeg_finish_decompress (&cinfo); qjpeg_destroy_decompress (&cinfo); + if(miplevel) + *miplevel -= submip; + return image_buffer; + +error_caught: + if(scanline) + Mem_Free (scanline); + if(image_buffer) + Mem_Free (image_buffer); + qjpeg_destroy_decompress (&cinfo); + return NULL; } @@ -667,10 +728,7 @@ static jboolean JPEG_EmptyOutputBuffer (j_compress_ptr cinfo) my_dest_ptr dest = (my_dest_ptr)cinfo->dest; if (FS_Write (dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE) != (size_t) JPEG_OUTPUT_BUF_SIZE) - { - error_in_jpeg = true; - return false; - } + longjmp(error_in_jpeg, 1); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; @@ -685,10 +743,10 @@ static void JPEG_TermDestination (j_compress_ptr cinfo) // Write any data remaining in the buffer if (datacount > 0) if (FS_Write (dest->outfile, dest->buffer, datacount) != (fs_offset_t)datacount) - error_in_jpeg = true; + longjmp(error_in_jpeg, 1); } -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 +761,45 @@ 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) +{ + my_dest_ptr dest = (my_dest_ptr)cinfo->dest; + jpeg_toolarge = true; + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = dest->bufsize; + return true; +} + +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 = (unsigned char *) buf; + dest->bufsize = bufsize; +} + /* ==================== @@ -727,16 +824,17 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, } // Open the file - file = FS_Open (filename, "wb", true, false); + file = FS_OpenRealFile(filename, "wb", true); if (!file) return false; + if(setjmp(error_in_jpeg)) + goto error_caught; cinfo.err = qjpeg_std_error (&jerr); cinfo.err->error_exit = JPEG_ErrorExit; - 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; @@ -745,6 +843,7 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, cinfo.input_components = 3; qjpeg_set_defaults (&cinfo); qjpeg_set_quality (&cinfo, (int)(scr_screenshot_jpeg_quality.value * 100), TRUE); + qjpeg_simple_progression (&cinfo); // turn off subsampling (to make text look better) cinfo.optimize_coding = 1; @@ -765,8 +864,6 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, scanline = &data[offset - cinfo.next_scanline * linesize]; qjpeg_write_scanlines (&cinfo, &scanline, 1); - if (error_in_jpeg) - break; } qjpeg_finish_compress (&cinfo); @@ -774,4 +871,236 @@ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, FS_Close (file); return true; + +error_caught: + qjpeg_destroy_compress (&cinfo); + FS_Close (file); + return false; +} + +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; + + jpeg_toolarge = 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; + while (cinfo->next_scanline < cinfo->image_height) + { + scanline = &data[cinfo->next_scanline * linesize]; + + qjpeg_write_scanlines (cinfo, &scanline, 1); + } + + qjpeg_finish_compress (cinfo); + + if(jpeg_toolarge) + return 0; + + return ((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; + } + + if(setjmp(error_in_jpeg)) + goto error_caught; + 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/Xonotic's map pictures + + quality_guess = bound(0, quality_guess, 100); + quality = bound(0, quality_guess + sv_writepicture_quality.integer, 100); // 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; + } + } + qjpeg_destroy_compress (&cinfo); + 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; + +error_caught: + qjpeg_destroy_compress (&cinfo); + return 0; +} + +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 = (CompressedImageCacheItem*) 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; + i = i->next; + } + 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}; + CompressedImageCacheItem *i; + + JPEG_OpenLibrary (); // for now; LH had the idea of replacing this by a better format + PNG_OpenLibrary (); // for loading + + // No DLL = no JPEGs + if (!jpeg_dll) + { + Con_Print("You need the libjpeg library to save JPEG images\n"); + return false; + } + + i = CompressedImageCache_Find(imagename, maxsize); + if(i) + { + *size = i->compressed_size; + *buf = i->compressed; + } + + // load the image + imagedata = loadimagepixelsbgra(imagename, true, false, false, NULL); + if(!imagedata) + return false; + + // find an appropriate size for somewhat okay compression + if(maxsize <= 768) + maxPixelCount = 32 * 32; + else if(maxsize <= 1024) + maxPixelCount = 64 * 64; + else if(maxsize <= 4096) + maxPixelCount = 128 * 128; + else + maxPixelCount = 256 * 256; + + 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 = (unsigned char *) 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((char *) *buf, maxsize, image_width, image_height, newimagedata); + Mem_Free(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); }